204 lines
6.7 KiB
Python
204 lines
6.7 KiB
Python
|
|
#!/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
|