Spaces:
Sleeping
Sleeping
File size: 8,055 Bytes
ff0e46c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | #!/usr/bin/env python3
"""
Atom OS Daemon Manager - Background service management.
Allows Atom OS to run as background daemon service for agent-to-agent execution.
Supports PID file tracking, graceful shutdown, and status monitoring.
Usage:
atom-os daemon # Start as background service
atom-os status # Check daemon status
atom-os stop # Stop daemon
atom-os execute <command> # Run on-demand
"""
import os
import sys
import signal
import subprocess
from pathlib import Path
from typing import Optional
try:
import psutil
except ImportError:
psutil = None
print("Warning: psutil not installed. Daemon features limited.")
print("Install with: pip install psutil>=6.0.0")
# Daemon configuration
PID_DIR = Path.home() / ".atom" / "pids"
PID_FILE = PID_DIR / "atom-os.pid"
LOG_DIR = Path.home() / ".atom" / "logs"
LOG_FILE = LOG_DIR / "daemon.log"
class DaemonManager:
"""Manage Atom OS as background daemon service."""
@staticmethod
def get_pid() -> Optional[int]:
"""Get running daemon PID from PID file.
Returns:
PID if file exists and contains valid integer, None otherwise
"""
if PID_FILE.exists():
try:
with open(PID_FILE, 'r') as f:
return int(f.read().strip())
except (ValueError, IOError):
return None
return None
@staticmethod
def is_running() -> bool:
"""Check if daemon process is running.
Returns:
True if process is alive, False otherwise
"""
pid = DaemonManager.get_pid()
if pid is None:
return False
if psutil is None:
# Fallback: try sending signal 0
try:
os.kill(pid, 0)
return True
except OSError:
return False
try:
return psutil.pid_exists(pid)
except Exception:
return False
@staticmethod
def start_daemon(
port: int = 8000,
host: str = "0.0.0.0",
workers: int = 1,
host_mount: bool = False,
dev: bool = False
) -> int:
"""Start Atom OS as background daemon.
Creates background subprocess with PID file tracking.
Detaches from terminal for long-running service.
Args:
port: Port for web server (default: 8000)
host: Host to bind to (default: 0.0.0.0)
workers: Number of worker processes (default: 1)
host_mount: Enable host filesystem mount (default: False)
dev: Enable development mode (default: False)
Returns:
Daemon process PID
Raises:
RuntimeError: If daemon is already running
IOError: If PID file cannot be written
"""
if DaemonManager.is_running():
current_pid = DaemonManager.get_pid()
raise RuntimeError(f"Atom OS is already running (PID: {current_pid})")
# Ensure directories exist
PID_DIR.mkdir(parents=True, exist_ok=True)
LOG_DIR.mkdir(parents=True, exist_ok=True)
# Prepare environment
env = os.environ.copy()
if host_mount:
env["ATOM_HOST_MOUNT_ENABLED"] = "true"
# Prepare command
cmd = [
sys.executable, "-m", "uvicorn",
"main_api_app:app",
"--host", host,
"--port", str(port),
"--workers", str(workers)
]
if dev:
cmd.append("--reload")
# Open log file
try:
log_file = open(LOG_FILE, 'a')
except IOError as e:
raise IOError(f"Cannot open log file {LOG_FILE}: {e}")
# Start subprocess
try:
process = subprocess.Popen(
cmd,
env=env,
stdout=log_file,
stderr=subprocess.STDOUT,
start_new_session=True # Detach from parent process
)
except Exception as e:
log_file.close()
raise RuntimeError(f"Failed to start daemon: {e}")
# Write PID file
try:
with open(PID_FILE, 'w') as f:
f.write(str(process.pid))
except IOError as e:
process.terminate()
log_file.close()
raise IOError(f"Cannot write PID file {PID_FILE}: {e}")
log_file.close()
return process.pid
@staticmethod
def stop_daemon() -> bool:
"""Stop daemon gracefully.
Attempts graceful shutdown with SIGTERM, then SIGKILL after timeout.
Returns:
True if stopped, False if not running
Raises:
IOError: If PID file cannot be removed
"""
pid = DaemonManager.get_pid()
if pid is None:
return False
try:
# Try graceful shutdown first
os.kill(pid, signal.SIGTERM)
# Wait up to 10 seconds for graceful shutdown
import time
for _ in range(100):
time.sleep(0.1)
if not DaemonManager.is_running():
break
# Force kill if still running
if DaemonManager.is_running():
os.kill(pid, signal.SIGKILL)
time.sleep(0.5)
# Clean up PID file
try:
PID_FILE.unlink(missing_ok=True)
except IOError as e:
raise IOError(f"Cannot remove PID file {PID_FILE}: {e}")
return True
except ProcessLookupError:
# Process already dead, clean up PID file
try:
PID_FILE.unlink(missing_ok=True)
except IOError:
pass
return True
@staticmethod
def get_status() -> dict:
"""Get daemon status information.
Returns:
Dict with running status, PID, uptime, memory usage, CPU
Example:
{
"running": True,
"pid": 12345,
"uptime_seconds": 3600,
"memory_mb": 256.5,
"cpu_percent": 5.2,
"status": "running"
}
"""
pid = DaemonManager.get_pid()
if pid is None:
return {
"running": False,
"pid": None,
"uptime_seconds": None,
"memory_mb": None,
"cpu_percent": None,
"status": "not_running"
}
if not DaemonManager.is_running():
return {
"running": False,
"pid": pid,
"uptime_seconds": None,
"memory_mb": None,
"cpu_percent": None,
"status": "stale_pid_file",
"note": "Stale PID file"
}
if psutil is None:
# Limited status without psutil
return {
"running": True,
"pid": pid,
"uptime_seconds": None,
"memory_mb": None,
"cpu_percent": None,
"status": "running"
}
try:
process = psutil.Process(pid)
return {
"running": True,
"pid": pid,
"uptime_seconds": process.cpu_times().system,
"memory_mb": process.memory_info().rss / 1024 / 1024,
"cpu_percent": process.cpu_percent(interval=0.1),
"status": "running"
}
except psutil.NoSuchProcess:
return {
"running": False,
"pid": pid,
"uptime_seconds": None,
"memory_mb": None,
"cpu_percent": None,
"status": "died_unexpectedly",
"note": "Process died unexpectedly"
}
|