Files
DOLPHIN/prod/services/service_manager.py

204 lines
6.7 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Dolphin Service Manager - Centralized userland service control
No root required! Uses systemd --user
"""
import argparse
import subprocess
import sys
import os
from typing import List, Optional
SERVICES = {
'exf': 'dolphin-exf.service',
'ob': 'dolphin-ob.service',
'watchdog': 'dolphin-watchdog.service',
'mc': 'dolphin-mc.service',
'mc-timer': 'dolphin-mc.timer',
}
def run_cmd(cmd: List[str], check: bool = True) -> subprocess.CompletedProcess:
"""Run systemctl command for user services"""
full_cmd = ['systemctl', '--user'] + cmd
print(f"Running: {' '.join(full_cmd)}")
return subprocess.run(full_cmd, check=check, capture_output=True, text=True)
def status(service: Optional[str] = None):
"""Show status of all or specific service"""
if service:
svc = SERVICES.get(service, service)
result = run_cmd(['status', svc], check=False)
print(result.stdout or result.stderr)
else:
print("=== Dolphin Services Status ===\n")
for name, svc in SERVICES.items():
result = run_cmd(['is-active', svc], check=False)
status = "✓ RUNNING" if result.returncode == 0 else "✗ STOPPED"
print(f"{name:12} {status}")
print("\n=== Recent Logs ===")
result = run_cmd(['--lines=20', 'status'], check=False)
print(result.stdout[-2000:] if result.stdout else "No recent output")
def start(service: Optional[str] = None):
"""Start service(s)"""
if service:
svc = SERVICES.get(service, service)
run_cmd(['start', svc])
print(f"Started {service}")
else:
for name, svc in SERVICES.items():
if name == 'mc': # Skip mc service, use timer
continue
run_cmd(['start', svc])
print(f"Started {name}")
def stop(service: Optional[str] = None):
"""Stop service(s)"""
if service:
svc = SERVICES.get(service, service)
run_cmd(['stop', svc])
print(f"Stopped {service}")
else:
for name, svc in SERVICES.items():
run_cmd(['stop', svc])
print(f"Stopped {name}")
def restart(service: Optional[str] = None):
"""Restart service(s)"""
if service:
svc = SERVICES.get(service, service)
run_cmd(['restart', svc])
print(f"Restarted {service}")
else:
for name, svc in SERVICES.items():
run_cmd(['restart', svc])
print(f"Restarted {name}")
def logs(service: str, follow: bool = False, lines: int = 50):
"""Show logs for a service"""
svc = SERVICES.get(service, service)
cmd = ['journalctl', '--user', '-u', svc, f'--lines={lines}']
if follow:
cmd.append('--follow')
subprocess.run(cmd)
def enable():
"""Enable services to start on boot"""
for name, svc in SERVICES.items():
run_cmd(['enable', svc])
print(f"Enabled {name}")
def disable():
"""Disable services from starting on boot"""
for name, svc in SERVICES.items():
run_cmd(['disable', svc])
print(f"Disabled {name}")
def daemon_reload():
"""Reload systemd daemon (after editing .service files)"""
run_cmd(['daemon-reload'])
print("Daemon reloaded")
def main():
parser = argparse.ArgumentParser(
description='Dolphin Service Manager - Userland service control',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s status # Show all service status
%(prog)s start exf # Start ExF service
%(prog)s logs ob -f # Follow OB service logs
%(prog)s restart # Restart all services
%(prog)s enable # Enable auto-start on boot
"""
)
subparsers = parser.add_subparsers(dest='command', help='Command')
# Status
p_status = subparsers.add_parser('status', help='Show service status')
p_status.add_argument('service', nargs='?', help='Specific service')
# Start
p_start = subparsers.add_parser('start', help='Start service(s)')
p_start.add_argument('service', nargs='?', help='Specific service')
# Stop
p_stop = subparsers.add_parser('stop', help='Stop service(s)')
p_stop.add_argument('service', nargs='?', help='Specific service')
# Restart
p_restart = subparsers.add_parser('restart', help='Restart service(s)')
p_restart.add_argument('service', nargs='?', help='Specific service')
# Logs
p_logs = subparsers.add_parser('logs', help='Show service logs')
p_logs.add_argument('service', help='Service name')
p_logs.add_argument('-f', '--follow', action='store_true', help='Follow logs')
p_logs.add_argument('-n', '--lines', type=int, default=50, help='Number of lines')
# Enable/Disable
subparsers.add_parser('enable', help='Enable auto-start')
subparsers.add_parser('disable', help='Disable auto-start')
subparsers.add_parser('reload', help='Reload systemd daemon')
args = parser.parse_args()
if not args.command:
parser.print_help()
return
try:
if args.command == 'status':
status(args.service)
elif args.command == 'start':
start(args.service)
elif args.command == 'stop':
stop(args.service)
elif args.command == 'restart':
restart(args.service)
elif args.command == 'logs':
logs(args.service, args.follow, args.lines)
elif args.command == 'enable':
enable()
elif args.command == 'disable':
disable()
elif args.command == 'reload':
daemon_reload()
except subprocess.CalledProcessError as e:
print(f"Error: {e}", file=sys.stderr)
if e.stderr:
print(e.stderr, file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
# =============================================================================
# SUPERVISOR-SPECIFIC COMMANDS
# =============================================================================
def supervisor_status():
"""Show supervisor internal component status"""
import subprocess
result = subprocess.run(
['journalctl', '--user', '-u', 'dolphin-supervisor', '--lines=100', '-o', 'json'],
capture_output=True, text=True
)
print("=== Supervisor Component Status ===")
print("(Parse logs for component health)")
print(result.stdout[-2000:] if result.stdout else "No logs")
def supervisor_components():
"""List components managed by supervisor"""
print("""
Components managed by dolphin-supervisor.service:
- exf (0.5s) External Factors
- ob (0.5s) Order Book Streamer
- watchdog (10s) Survival Stack
- mc (4h) MC-Forewarner
""")
# Add to main() argument parser if needed