Keep v1 as the default video graph, relay image edits to the Qwen Lightning box, and keep /api/health up if one Comfy host is unset. Co-authored-by: Cursor <cursoragent@cursor.com>
139 lines
5.2 KiB
Python
139 lines
5.2 KiB
Python
"""Install scripts/comfy-host-agent.mjs on the sidecar image-Comfy PC over SSH.
|
|
|
|
Reads AIGEN_IMAGE_SSH_HOST, AIGEN_IMAGE_SSH_USER, and AIGEN_IMAGE_SSH_KEY
|
|
(default: %USERPROFILE%\\.ssh\\aigen_image_comfy) or AIGEN_IMAGE_SSH_PASS.
|
|
Installs the host agent only. Does not start Comfy or load models.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import paramiko
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
AGENT = ROOT / "scripts" / "comfy-host-agent.mjs"
|
|
REMOTE_DIR = r"C:\Users\comfyapi\aigen-agent"
|
|
REMOTE_AGENT = REMOTE_DIR + r"\comfy-host-agent.mjs"
|
|
REMOTE_START = REMOTE_DIR + r"\start.cmd"
|
|
NODE_ZIP = "https://nodejs.org/dist/v22.18.0/node-v22.18.0-win-x64.zip"
|
|
|
|
|
|
def connect():
|
|
host = os.environ.get("AIGEN_IMAGE_SSH_HOST", "192.168.77.101")
|
|
user = os.environ.get("AIGEN_IMAGE_SSH_USER", "comfyapi")
|
|
password = os.environ.get("AIGEN_IMAGE_SSH_PASS")
|
|
key_path = os.environ.get("AIGEN_IMAGE_SSH_KEY") or str(Path.home() / ".ssh" / "aigen_image_comfy")
|
|
if not Path(key_path).exists():
|
|
key_path = os.environ.get("AIGEN_IMAGE_SSH_KEY")
|
|
client = paramiko.SSHClient()
|
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
kwargs = {
|
|
"hostname": host,
|
|
"username": user,
|
|
"timeout": 15,
|
|
"allow_agent": False,
|
|
"look_for_keys": False,
|
|
}
|
|
if key_path:
|
|
kwargs["key_filename"] = key_path
|
|
elif password:
|
|
kwargs["password"] = password
|
|
else:
|
|
raise SystemExit("Set AIGEN_IMAGE_SSH_PASS or AIGEN_IMAGE_SSH_KEY")
|
|
client.connect(**kwargs)
|
|
return client, host, user
|
|
|
|
|
|
def run(client, cmd, timeout=120):
|
|
stdin, stdout, stderr = client.exec_command(cmd, timeout=timeout)
|
|
out = stdout.read().decode("utf-8", "replace")
|
|
err = stderr.read().decode("utf-8", "replace")
|
|
code = stdout.channel.recv_exit_status()
|
|
return code, out, err
|
|
|
|
|
|
def main():
|
|
if not AGENT.exists():
|
|
raise SystemExit(f"missing {AGENT}")
|
|
token = os.environ.get("IMAGE_COMFY_CONTROL_TOKEN") or os.urandom(24).hex()
|
|
token_file = Path(os.environ.get("TEMP", ".")) / "image_comfy_control_token"
|
|
token_file.write_text(token, encoding="ascii")
|
|
pubkey = Path.home() / ".ssh" / "aigen_image_comfy.pub"
|
|
|
|
start_cmd = "\r\n".join([
|
|
"@echo off",
|
|
f"cd /d {REMOTE_DIR}",
|
|
"set COMFY_CONTROL_PORT=8199",
|
|
"set COMFY_PROXY_PORT=8198",
|
|
"set COMFY_HOST=http://127.0.0.1:8188",
|
|
f"set COMFY_CONTROL_TOKEN={token}",
|
|
r"set COMFY_LAUNCH_CWD=C:\ComfyUI-QwenEdit\ComfyUI",
|
|
r"set COMFY_LAUNCH_CMD=C:\ComfyUI-QwenEdit\python\python.exe C:\ComfyUI-QwenEdit\ComfyUI\main.py --listen 0.0.0.0 --port 8188",
|
|
r'"C:\Users\comfyapi\aigen-agent\node.exe" comfy-host-agent.mjs',
|
|
"",
|
|
])
|
|
|
|
client, host, user = connect()
|
|
print("connected", user, host)
|
|
code, out, err = run(client, f'mkdir "{REMOTE_DIR}" 2>nul & echo dir-ok')
|
|
print("mkdir", code, out.strip() or err.strip())
|
|
|
|
sftp = client.open_sftp()
|
|
with sftp.file(REMOTE_AGENT, "wb") as handle:
|
|
handle.write(AGENT.read_bytes())
|
|
with sftp.file(REMOTE_START, "w") as handle:
|
|
handle.write(start_cmd)
|
|
if pubkey.exists():
|
|
run(client, r'if not exist "%USERPROFILE%\.ssh" mkdir "%USERPROFILE%\.ssh"')
|
|
existing = ""
|
|
try:
|
|
with sftp.file(r"C:\Users\comfyapi\.ssh\authorized_keys", "r") as handle:
|
|
existing = handle.read().decode("utf-8", "replace")
|
|
except FileNotFoundError:
|
|
existing = ""
|
|
key_text = pubkey.read_text(encoding="ascii").strip()
|
|
if key_text not in existing:
|
|
with sftp.file(r"C:\Users\comfyapi\.ssh\authorized_keys", "a") as handle:
|
|
handle.write(("\n" if existing and not existing.endswith("\n") else "") + key_text + "\n")
|
|
print("authorized_keys updated")
|
|
sftp.close()
|
|
|
|
code, out, err = run(client, f'if exist "{REMOTE_DIR}\\node.exe" echo node-present')
|
|
if "node-present" not in out:
|
|
print("downloading node")
|
|
code, out, err = run(
|
|
client,
|
|
f'curl.exe -L --retry 3 -o "{REMOTE_DIR}\\node.zip" {NODE_ZIP}',
|
|
timeout=180,
|
|
)
|
|
print("curl", code, (err or out)[-400:])
|
|
code, out, err = run(
|
|
client,
|
|
f'"C:\\Program Files\\7-Zip\\7z.exe" x -y -o"{REMOTE_DIR}\\node-dist" "{REMOTE_DIR}\\node.zip"',
|
|
timeout=120,
|
|
)
|
|
print("unzip", code, out[-400:] or err[-400:])
|
|
run(client, f'copy /Y "{REMOTE_DIR}\\node-dist\\node-v22.18.0-win-x64\\node.exe" "{REMOTE_DIR}\\node.exe"')
|
|
|
|
run(client, r'powershell -NoProfile -Command "Get-Process node -ErrorAction SilentlyContinue | Where-Object { $_.Path -like \'*aigen-agent*\' } | Stop-Process -Force"')
|
|
code, out, err = run(
|
|
client,
|
|
r'cmd /c start "" /MIN C:\Users\comfyapi\aigen-agent\start.cmd',
|
|
)
|
|
print("start", code, out.strip(), err.strip())
|
|
run(
|
|
client,
|
|
r'schtasks /Create /TN AigenImageComfyAgent /TR "C:\Users\comfyapi\aigen-agent\start.cmd" /SC ONLOGON /F',
|
|
)
|
|
client.close()
|
|
print("token-file", str(token_file))
|
|
print("wait for agent")
|
|
time.sleep(2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|