143 lines
5.3 KiB
Python
143 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import base64
|
|
import pathlib
|
|
import sys
|
|
from typing import Iterable
|
|
|
|
import winrm
|
|
|
|
|
|
def run_ps(session: winrm.Session, script: str) -> str:
|
|
result = session.run_ps(script)
|
|
stdout = result.std_out.decode("utf-8", errors="ignore").strip()
|
|
stderr = result.std_err.decode("utf-8", errors="ignore").strip()
|
|
if result.status_code != 0:
|
|
raise RuntimeError(f"PowerShell failed ({result.status_code}): {stderr or stdout}")
|
|
return stdout
|
|
|
|
|
|
def upload_file(session: winrm.Session, local_path: pathlib.Path, remote_path: str) -> None:
|
|
data_b64 = base64.b64encode(local_path.read_bytes()).decode("ascii")
|
|
temp_b64_path = remote_path + ".b64tmp"
|
|
# WinRM command payloads can be short; use conservative chunking.
|
|
chunk_size = 500
|
|
|
|
run_ps(
|
|
session,
|
|
(
|
|
f"$p='{remote_path}';"
|
|
"$d=Split-Path -Parent $p;"
|
|
"New-Item -ItemType Directory -Force -Path $d | Out-Null;"
|
|
f"[System.IO.File]::WriteAllText('{temp_b64_path}','');"
|
|
"Write-Output 'ready'"
|
|
),
|
|
)
|
|
|
|
for i in range(0, len(data_b64), chunk_size):
|
|
chunk = data_b64[i : i + chunk_size]
|
|
run_ps(
|
|
session,
|
|
f"Add-Content -Path '{temp_b64_path}' -Value '{chunk}' -NoNewline",
|
|
)
|
|
|
|
ps_finalize = (
|
|
f"$b64=[System.IO.File]::ReadAllText('{temp_b64_path}');"
|
|
f"[System.IO.File]::WriteAllBytes('{remote_path}',[Convert]::FromBase64String($b64));"
|
|
f"Remove-Item -Path '{temp_b64_path}' -Force;"
|
|
f"Write-Output ('uploaded:' + '{remote_path}')"
|
|
)
|
|
print(run_ps(session, ps_finalize))
|
|
|
|
|
|
def iter_agent_files(root: pathlib.Path) -> Iterable[pathlib.Path]:
|
|
for p in sorted(root.rglob("*")):
|
|
if p.is_file():
|
|
yield p
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Deploy Windows CODESYS agent via WinRM.")
|
|
parser.add_argument("--host", required=True)
|
|
parser.add_argument("--username", required=True)
|
|
parser.add_argument("--password", required=True)
|
|
parser.add_argument("--token", required=True)
|
|
parser.add_argument("--install-path", default=r"C:\codesys-agent")
|
|
parser.add_argument("--codesys-exe-path", required=True)
|
|
parser.add_argument("--codesys-profile", required=True)
|
|
args = parser.parse_args()
|
|
|
|
session = winrm.Session(
|
|
target=f"http://{args.host}:5985/wsman",
|
|
auth=(args.username, args.password),
|
|
transport="ntlm",
|
|
server_cert_validation="ignore",
|
|
)
|
|
|
|
print("Connected, checking identity...")
|
|
whoami = run_ps(session, "whoami")
|
|
print(f"Remote identity: {whoami}")
|
|
|
|
local_root = pathlib.Path(__file__).resolve().parent / "windows_codesys_agent"
|
|
if not local_root.is_dir():
|
|
raise RuntimeError(f"Local folder not found: {local_root}")
|
|
|
|
remote_root = args.install_path.rstrip("\\") + r"\windows_codesys_agent"
|
|
for f in iter_agent_files(local_root):
|
|
rel = f.relative_to(local_root).as_posix().replace("/", "\\")
|
|
remote_path = remote_root + "\\" + rel
|
|
upload_file(session, f, remote_path)
|
|
|
|
scripts_dir = remote_root + r"\scripts"
|
|
runtime_dir = args.install_path.rstrip("\\") + r"\runtime"
|
|
run_script_path = remote_root + r"\run-agent.ps1"
|
|
run_script = (
|
|
f"$env:CODESYS_AGENT_TOKEN = '{args.token}'\n"
|
|
f"$env:CODESYS_AGENT_SCRIPTS_DIR = '{scripts_dir}'\n"
|
|
f"$env:CODESYS_AGENT_RUNTIME_DIR = '{runtime_dir}'\n"
|
|
"$env:CODESYS_AGENT_ALLOW_DOWNLOAD = 'false'\n"
|
|
"$env:CODESYS_AGENT_TIMEOUT_SEC = '180'\n"
|
|
f"$env:CODESYS_EXE_PATH = '{args.codesys_exe_path}'\n"
|
|
f"$env:CODESYS_PROFILE = '{args.codesys_profile}'\n"
|
|
"$env:CODESYS_NO_UI = 'true'\n"
|
|
"$env:CODESYS_SCRIPT_TIMEOUT_SEC = '600'\n"
|
|
"Set-Location -Path $PSScriptRoot\n"
|
|
".\\.venv\\Scripts\\python.exe -m uvicorn agent:app --host 0.0.0.0 --port 8787\n"
|
|
)
|
|
run_b64 = base64.b64encode(run_script.encode("utf-8")).decode("ascii")
|
|
run_ps(
|
|
session,
|
|
(
|
|
f"$p='{run_script_path}';"
|
|
f"[System.IO.File]::WriteAllBytes($p,[Convert]::FromBase64String('{run_b64}'));"
|
|
"Write-Output ('uploaded:' + $p)"
|
|
),
|
|
)
|
|
|
|
setup_ps = (
|
|
f"Set-Location -Path '{remote_root}';"
|
|
"if (Get-Command py -ErrorAction SilentlyContinue) { py -3 -m venv .venv } "
|
|
"elseif (Get-Command python -ErrorAction SilentlyContinue) { python -m venv .venv } "
|
|
"else { throw 'Python is not installed on the Windows VM.' };"
|
|
".\\.venv\\Scripts\\python.exe -m pip install -r requirements.txt;"
|
|
"Get-Process -Name python -ErrorAction SilentlyContinue | "
|
|
"Where-Object { $_.Path -like '*windows_codesys_agent*' } | Stop-Process -Force -ErrorAction SilentlyContinue;"
|
|
"Start-Process -FilePath powershell.exe "
|
|
f"-ArgumentList '-ExecutionPolicy Bypass -File \"{run_script_path}\"' "
|
|
"-WindowStyle Hidden;"
|
|
"Write-Output 'agent-started'"
|
|
)
|
|
print(run_ps(session, setup_ps))
|
|
print(f"Deployment complete. Agent expected at: http://{args.host}:8787/health")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except Exception as exc:
|
|
print(f"ERROR: {exc}", file=sys.stderr)
|
|
raise
|