#!/usr/bin/env python3
"""
Cortex Agent Node - Daemon Installer
====================================
Configures the Cortex Agent to run automatically as a background daemon
on macOS (launchd) or Linux (systemd).
Usage:
python3 install_service.py
"""
import os
import sys
import platform
import subprocess
def get_python_path():
return sys.executable
def get_agent_main_path():
return os.path.abspath(os.path.join(os.path.dirname(__file__), "src", "agent_node", "main.py"))
def get_working_dir():
return os.path.abspath(os.path.dirname(__file__))
def install_mac_launchd():
print("Installing macOS launchd service...")
plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.jerxie.cortex.agent</string>
<key>ProgramArguments</key>
<array>
<string>{get_python_path()}</string>
<string>{get_agent_main_path()}</string>
</array>
<key>WorkingDirectory</key>
<string>{get_working_dir()}</string>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>{os.path.expanduser("~")}/.cortex/agent.err.log</string>
<key>StandardOutPath</key>
<string>{os.path.expanduser("~")}/.cortex/agent.out.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
</dict>
</plist>
"""
agents_dir = os.path.expanduser("~/Library/LaunchAgents")
os.makedirs(agents_dir, exist_ok=True)
os.makedirs(os.path.expanduser("~/.cortex"), exist_ok=True)
plist_path = os.path.join(agents_dir, "com.jerxie.cortex.agent.plist")
with open(plist_path, "w") as f:
f.write(plist_content)
print(f"Created plist at {plist_path}")
try:
# Unload if exists
subprocess.run(["launchctl", "unload", plist_path], capture_output=True)
# Load new
subprocess.run(["launchctl", "load", plist_path], check=True)
print("✅ macOS Daemon successfully started!")
print(f"Logs: ~/.cortex/agent.out.log and ~/.cortex/agent.err.log")
print("Commands:")
print(f" Stop: launchctl unload {plist_path}")
print(f" Start: launchctl load {plist_path}")
except subprocess.CalledProcessError as e:
print(f"❌ Failed to load launchd service: {e}")
def _is_systemd_available():
try:
# Check if systemd is running (PID 1)
with open("/proc/1/comm", "r") as f:
if "systemd" in f.read():
# Even if systemd is PID 1, --user might fail if no session D-Bus
r = subprocess.run(["systemctl", "--user", "list-units"], capture_output=True)
return r.returncode == 0
except:
pass
return False
def install_linux_background_loop():
print("Systemd not available or refusing connection. Falling back to background loop (nohup)...")
# Create a small control script to manage the background process
ctl_script = os.path.join(get_working_dir(), "cortex-ctl")
script_content = f"""#!/bin/sh
# Cortex Agent Control Script (Background-loop mode)
PIDFILE="{get_working_dir()}/agent.pid"
LOGFILE="{os.path.expanduser("~")}/.cortex/agent.out.log"
case "$1" in
start)
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
echo "Agent is already running (PID $(cat $PIDFILE))"
exit 0
fi
echo "Starting Cortex Agent..."
mkdir -p "$(dirname "$LOGFILE")"
cd "{get_working_dir()}"
nohup {get_python_path()} {get_agent_main_path()} >> "$LOGFILE" 2>&1 &
echo $! > "$PIDFILE"
echo "Agent started (PID $!)"
;;
stop)
if [ -f "$PIDFILE" ]; then
echo "Stopping Cortex Agent (PID $(cat $PIDFILE))..."
kill $(cat "$PIDFILE")
rm "$PIDFILE"
else
echo "Agent is not running"
fi
;;
status)
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
echo "Agent is RUNNING (PID $(cat $PIDFILE))"
else
echo "Agent is STOPPED"
fi
;;
logs)
tail -f "$LOGFILE"
;;
*)
echo "Usage: $0 {{start|stop|status|logs}}"
exit 1
esac
"""
with open(ctl_script, "w") as f:
f.write(script_content)
os.chmod(ctl_script, 0o755)
# Start it
try:
subprocess.run([ctl_script, "start"], check=True)
print("✅ Linux Background Service successfully started!")
print(f"Management script created at: {ctl_script}")
print(f"Use '{ctl_script} status' to check.")
except Exception as e:
print(f"❌ Failed to start background loop: {e}")
def install_linux_systemd():
if not _is_systemd_available():
install_linux_background_loop()
return
print("Installing Linux systemd user service...")
service_content = f"""[Unit]
Description=Cortex Agent Node
After=network.target
[Service]
Type=simple
ExecStart={get_python_path()} {get_agent_main_path()}
WorkingDirectory={get_working_dir()}
Restart=always
RestartSec=5
StandardOutput=append:{os.path.expanduser("~")}/.cortex/agent.out.log
StandardError=append:{os.path.expanduser("~")}/.cortex/agent.err.log
[Install]
WantedBy=default.target
"""
systemd_dir = os.path.expanduser("~/.config/systemd/user")
os.makedirs(systemd_dir, exist_ok=True)
os.makedirs(os.path.expanduser("~/.cortex"), exist_ok=True)
service_path = os.path.join(systemd_dir, "cortex-agent.service")
with open(service_path, "w") as f:
f.write(service_content)
print(f"Created systemd service at {service_path}")
try:
subprocess.run(["systemctl", "--user", "daemon-reload"], check=True)
subprocess.run(["systemctl", "--user", "enable", "cortex-agent"], check=True)
subprocess.run(["systemctl", "--user", "restart", "cortex-agent"], check=True)
# Ensure user services run even when not logged in
subprocess.run(["loginctl", "enable-linger", os.environ.get("USER", "root")], capture_output=True)
print("✅ Linux Daemon successfully started!")
print(f"Logs: ~/.cortex/agent.out.log and ~/.cortex/agent.err.log")
print("Commands:")
print(" Status: systemctl --user status cortex-agent")
print(" Stop: systemctl --user stop cortex-agent")
print(" Start: systemctl --user start cortex-agent")
print(" Logs: journalctl --user -u cortex-agent -f")
except subprocess.CalledProcessError as e:
print(f"❌ Failed to configure systemd service: {e}")
# Final fallback
install_linux_background_loop()
def main():
if not os.path.exists(get_agent_main_path()):
print(f"❌ Error: Could not find main agent script at {get_agent_main_path()}")
sys.exit(1)
system = platform.system().lower()
if system == "darwin":
install_mac_launchd()
elif system == "linux":
install_linux_systemd()
else:
print(f"❌ Unsupported OS for automated daemon install: {system}")
print("Please configure your system service manager manually.")
if __name__ == "__main__":
main()