#!/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