#!/usr/bin/env python3 """ Deploy Hermes Zulip plugin as a systemd service. Creates service file, installs dependencies, deploys plugin. Usage: python3 deploy-hermes-zulip.py --agent tanko --email tanko-bot@chat.sysloggh.net --api-key KEY """ import argparse, os, sys, subprocess, json SYSTEMD_TEMPLATE = """[Unit] Description=Hermes Zulip Gateway — {agent_name} ({bot_email}) After=network-online.target Wants=network-online.target Documentation=https://git.sysloggh.net/SyslogSolution/zulip-platform-plugins [Service] Type=simple User=root WorkingDirectory=/opt/hermes-zulip-{agent_name} Environment=ZULIP_SITE=https://chat.sysloggh.net Environment=ZULIP_EMAIL={bot_email} Environment=ZULIP_API_KEY={api_key} Environment=ZULIP_AGENT_NAME={agent_name} Environment=ZULIP_STREAM=agent-hub Environment=ZULIP_POLL_INTERVAL=3 Environment=PYTHONUNBUFFERED=1 ExecStart=/usr/bin/python3 -m hermes_zulip.adapter_standalone Restart=always RestartSec=10 StandardOutput=journal StandardError=journal SyslogIdentifier=hermes-zulip-{agent_name} [Install] WantedBy=multi-user.target """ PLUGIN_FILES = [ "plugins/platforms/zulip/__init__.py", "plugins/platforms/zulip/adapter.py", "plugins/platforms/zulip/plugin.yaml", ] def main(): parser = argparse.ArgumentParser(description="Deploy Hermes Zulip plugin") parser.add_argument("--agent", required=True, help="Agent name (e.g. tanko)") parser.add_argument("--email", required=True, help="Bot email") parser.add_argument("--api-key", required=True, help="Zulip API key") parser.add_argument("--port", type=int, default=None, help="Health port (auto-assigned)") args = parser.parse_args() agent = args.agent.lower() port = args.port or (9201 + hash(agent) % 10) repo_dir = "/root/zulip-platform-plugins" deploy_dir = f"/opt/hermes-zulip-{agent}" print(f"=== Deploying Hermes Zulip Plugin: {agent} ===") print(f" Bot: {args.email}") print(f" Port: {port}") print(f" Deploy dir: {deploy_dir}") print() # 1. Ensure httpx is installed print("[1/5] Installing httpx...") subprocess.run( [sys.executable, "-m", "pip", "install", "--break-system-packages", "--quiet", "httpx"], check=False, ) print(" ✅ httpx installed") # 2. Create deploy directory print(f"[2/5] Setting up {deploy_dir}...") os.makedirs(deploy_dir, exist_ok=True) # 3. Copy plugin files print("[3/5] Copying plugin files...") for f in PLUGIN_FILES: src = os.path.join(repo_dir, f) dst = os.path.join(deploy_dir, os.path.basename(f)) if os.path.exists(src): subprocess.run(["cp", src, dst], check=True) print(f" ✅ {os.path.basename(f)}") else: print(f" ⚠️ {f} not found, skipping") # 4. Create standalone adapter launcher print("[4/5] Creating adapter launcher...") launcher = os.path.join(deploy_dir, "adapter_standalone.py") with open(launcher, "w") as f: f.write(f'''"""Standalone launcher for Hermes Zulip adapter — {agent}.""" import asyncio, os, sys, logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) sys.path.insert(0, "{deploy_dir}") from adapter import ZulipAdapter, check_requirements async def main(): if not check_requirements(): print("Missing Zulip credentials. Set ZULIP_SITE, ZULIP_EMAIL, ZULIP_API_KEY.") sys.exit(1) config = type("Config", (), {{"extra": {{}}}})() adapter = ZulipAdapter(config) ok = await adapter.connect() if not ok: print("Failed to connect to Zulip") sys.exit(1) print(f"Connected as {{adapter._email}} (queue={{adapter._queue_id}})") # Keep alive try: await asyncio.Event().wait() except asyncio.CancelledError: pass finally: await adapter.disconnect() if __name__ == "__main__": asyncio.run(main()) ''') print(" ✅ adapter_standalone.py") # 5. Create and enable systemd service print("[5/5] Installing systemd service...") service_name = f"hermes-zulip-{agent}" service_content = SYSTEMD_TEMPLATE.format( agent_name=agent.title(), bot_email=args.email, api_key=args.api_key, port=port, ) service_path = f"/etc/systemd/system/{service_name}.service" with open(service_path, "w") as f: f.write(service_content) subprocess.run(["systemctl", "daemon-reload"], check=True) subprocess.run(["systemctl", "enable", service_name], check=True) print(f" ✅ Service installed: {service_name}") print(f" Service file: {service_path}") print() print(f" To start: systemctl start {service_name}") print(f" To check: systemctl status {service_name}") print(f" Logs: journalctl -u {service_name} -f") if __name__ == "__main__": main()