feat: add reload service command and update command handling

This commit is contained in:
2026-03-06 11:44:23 +08:00
parent 69a6741c0d
commit e799ea011f
2 changed files with 90 additions and 13 deletions

View File

@ -65,7 +65,7 @@ def create_parser() -> argparse.ArgumentParser:
update_parser.add_argument('--force', action='store_true', help='Force update even if binary already exists')
# Config commands
config_parser = core_subparsers.add_parser('config', help='Manage core configuration')
config_parser = subparsers.add_parser('config', help='Manage core configuration')
config_subparsers = config_parser.add_subparsers(dest='config_command', help='Configuration operations')
# Import config
@ -89,7 +89,7 @@ def create_parser() -> argparse.ArgumentParser:
apply_parser = config_subparsers.add_parser('apply', help='Apply active subscription to generate final config')
# Service management commands
service_parser = core_subparsers.add_parser('service', help='Manage mihomo as a system service')
service_parser = subparsers.add_parser('service', help='Manage mihomo as a system service')
service_subparsers = service_parser.add_subparsers(dest='service_command', help='Service operations')
# Install service command
@ -113,6 +113,10 @@ def create_parser() -> argparse.ArgumentParser:
restart_service_parser = service_subparsers.add_parser('restart', help='Restart mihomo system service')
restart_service_parser.add_argument('--name', default='mihomo', help='Service name (default: mihomo)')
# Reload service command
reload_service_parser = service_subparsers.add_parser('reload', help='Reload mihomo service configuration (via API)')
reload_service_parser.add_argument('--name', default='mihomo', help='Service name (default: mihomo)')
# Status service command
status_service_parser = service_subparsers.add_parser('status', help='Check mihomo system service status')
status_service_parser.add_argument('--name', default='mihomo', help='Service name (default: mihomo)')
@ -138,7 +142,7 @@ def create_parser() -> argparse.ArgumentParser:
return parser
def handle_subscription_command(args, subscription_manager: SubscriptionManager, core_config_manager: CoreConfigManager, parser: argparse.ArgumentParser) -> None:
def handle_subscription_command(args, subscription_manager: SubscriptionManager, core_config_manager: CoreConfigManager, core_manager: CoreManager, parser: argparse.ArgumentParser) -> None:
"""Handle subscription related commands."""
if not hasattr(args, 'subcommand') or not args.subcommand:
parser.parse_args(['subscription', '--help'])
@ -156,7 +160,9 @@ def handle_subscription_command(args, subscription_manager: SubscriptionManager,
subscription_manager.set_subscription_url(args.name, args.url)
elif args.subcommand == 'activate':
subscription_manager.activate_subscription(args.name)
core_config_manager.apply()
if core_config_manager.apply():
# Reload service if config applied successfully
core_manager.reload_service()
elif args.subcommand == 'list':
subscription_manager.list_subscriptions()
elif args.subcommand == 'storage':
@ -173,10 +179,6 @@ def handle_core_command(args, core_manager: CoreManager, core_config_manager: Co
if args.core_command == 'update':
core_manager.update(version=args.version, force=args.force)
elif args.core_command == 'config':
handle_config_command(args, core_config_manager, parser)
elif args.core_command == 'service':
handle_service_command(args, core_manager, parser)
else:
parser.parse_args(['core', '--help'])
@ -184,7 +186,7 @@ def handle_core_command(args, core_manager: CoreManager, core_config_manager: Co
def handle_config_command(args, core_config_manager: CoreConfigManager, parser: argparse.ArgumentParser) -> None:
"""Handle configuration commands."""
if not hasattr(args, 'config_command') or not args.config_command:
parser.parse_args(['core', 'config', '--help'])
parser.parse_args(['config', '--help'])
return
if args.config_command == 'import':
@ -200,13 +202,13 @@ def handle_config_command(args, core_config_manager: CoreConfigManager, parser:
elif args.config_command == 'apply':
core_config_manager.apply()
else:
parser.parse_args(['core', 'config', '--help'])
parser.parse_args(['config', '--help'])
def handle_service_command(args, core_manager: CoreManager, parser: argparse.ArgumentParser) -> None:
"""Handle service commands."""
if not hasattr(args, 'service_command') or not args.service_command:
parser.parse_args(['core', 'service', '--help'])
parser.parse_args(['service', '--help'])
return
if args.service_command == 'install':
@ -232,11 +234,15 @@ def handle_service_command(args, core_manager: CoreManager, parser: argparse.Arg
success = core_manager.restart_service(service_name=args.name)
if not success:
sys.exit(1)
elif args.service_command == 'reload':
success = core_manager.reload_service(service_name=args.name)
if not success:
sys.exit(1)
elif args.service_command == 'status':
status = core_manager.get_service_status(service_name=args.name)
print(f"Service '{args.name}' status: {status}")
else:
parser.parse_args(['core', 'service', '--help'])
parser.parse_args(['service', '--help'])
def handle_hook_command(args, hook_manager: HookManager, parser: argparse.ArgumentParser) -> None:
@ -274,9 +280,13 @@ def main() -> None:
try:
if args.command == 'subscription':
handle_subscription_command(args, subscription_manager, core_config_manager, parser)
handle_subscription_command(args, subscription_manager, core_config_manager, core_manager, parser)
elif args.command == 'core':
handle_core_command(args, core_manager, core_config_manager, parser)
elif args.command == 'config':
handle_config_command(args, core_config_manager, parser)
elif args.command == 'service':
handle_service_command(args, core_manager, parser)
elif args.command == 'hook':
handle_hook_command(args, hook_manager, parser)
else:

View File

@ -433,6 +433,73 @@ class CoreManager:
except Exception as e:
return f"Error checking service status: {e}"
def reload_service(self, service_name: str = "mihomo") -> bool:
"""
Reload mihomo configuration via external controller API without restarting service.
Args:
service_name: Name of the service (default: "mihomo")
Returns:
bool: True if reload successful, False otherwise.
"""
config_path = self.core_config_manager.storage.config_dir / "generated_config.yaml"
if not config_path.exists():
print(f"❌ Configuration file not found: {config_path}")
return False
try:
# Parse generated config to find external-controller
import yaml
with open(config_path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
external_controller = config.get('external-controller', '127.0.0.1:9090')
secret = config.get('secret', '')
# Format API URL
if not external_controller.startswith('http'):
base_url = f"http://{external_controller}"
else:
base_url = external_controller
# Prepare request
url = f"{base_url}/configs?force=true"
headers = {
'Content-Type': 'application/json'
}
if secret:
headers['Authorization'] = f"Bearer {secret}"
payload = {
"path": str(config_path.absolute()),
"payload": "" # Empty payload suggests reload from path
}
# Send reload request
print(f"🔄 Reloading configuration via API: {url}")
# Short timeout as local controller should respond quickly
response = requests.put(url, json=payload, headers=headers, timeout=2)
if response.status_code == 204:
print(f"✅ Configuration reloaded successfully")
return True
else:
print(f"❌ Failed to reload configuration: HTTP {response.status_code}")
print(f" Response: {response.text}")
return False
except requests.exceptions.RequestException as e:
print(f"❌ Failed to connect to external controller: {e}")
print(" Is the service running?")
return False
except ImportError:
print("❌ PyYAML is required to parse configuration.")
return False
except Exception as e:
print(f"❌ Error reloading service: {e}")
return False
def deep_merge(dict1, dict2):
for k, v in dict2.items():