feat: Windows 服务 安装,启动,停止 功能
This commit is contained in:
@ -15,12 +15,14 @@ import requests
|
||||
from pathlib import Path
|
||||
|
||||
from scientific_surfing.corecfg_manager import CoreConfigManager
|
||||
from scientific_surfing.service_manager import ServiceManager
|
||||
|
||||
|
||||
class CoreManager:
|
||||
"""Manages user configuration with import, export, and edit operations."""
|
||||
|
||||
def __init__(self, core_config_manager: CoreConfigManager):
|
||||
self.storage = core_config_manager.storage
|
||||
self.core_config_manager = core_config_manager
|
||||
|
||||
def update(self, version: Optional[str] = None, force: bool = False) -> bool:
|
||||
@ -222,207 +224,181 @@ class CoreManager:
|
||||
print(f"[ERROR] Upgrade failed: {e}")
|
||||
return False
|
||||
|
||||
def daemon(self, config_path: Optional[str] = None) -> bool:
|
||||
def get_binary_path(self) -> Path:
|
||||
"""Get the path to the mihomo executable."""
|
||||
system = platform.system().lower()
|
||||
binary_dir = self.core_config_manager.storage.config_dir / "bin"
|
||||
binary_path = binary_dir / ("mihomo.exe" if system == "windows" else "mihomo")
|
||||
return binary_path
|
||||
|
||||
def install_service(self, service_name: str = "mihomo", description: str = "Mihomo proxy service") -> bool:
|
||||
"""
|
||||
Run the mihomo executable as a daemon with the generated configuration.
|
||||
Install mihomo as a system service.
|
||||
|
||||
Args:
|
||||
config_path: Path to the configuration file. If None, uses generated_config.yaml
|
||||
service_name: Name for the service (default: "mihomo")
|
||||
description: Service description
|
||||
|
||||
Returns:
|
||||
bool: True if daemon started successfully, False otherwise.
|
||||
bool: True if installation successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Determine binary path
|
||||
system = platform.system().lower()
|
||||
binary_dir = self.core_config_manager.storage.config_dir / "bin"
|
||||
binary_path = binary_dir / ("mihomo.exe" if system == "windows" else "mihomo")
|
||||
|
||||
binary_path = self.get_binary_path()
|
||||
if not binary_path.exists():
|
||||
print(f"❌ Mihomo binary not found at: {binary_path}")
|
||||
print(" Run 'core update' to download the binary first.")
|
||||
print(" Please run 'core update' first to download the binary")
|
||||
return False
|
||||
|
||||
# Determine config path
|
||||
if config_path is None:
|
||||
config_file = self.core_config_manager.storage.config_dir / "generated_config.yaml"
|
||||
else:
|
||||
config_file = Path(config_path)
|
||||
# Setup service arguments
|
||||
config_dir = self.core_config_manager.storage.config_dir
|
||||
config_file = config_dir / "generated_config.yaml"
|
||||
|
||||
# Ensure config file exists
|
||||
if not config_file.exists():
|
||||
print(f"❌ Configuration file not found: {config_file}")
|
||||
print(" Run 'core-config apply' to generate the configuration first.")
|
||||
return False
|
||||
self.core_config_manager.generate_config()
|
||||
print(f"✅ Generated initial configuration: {config_file}")
|
||||
|
||||
print(f"[INFO] Starting mihomo daemon...")
|
||||
print(f" Binary: {binary_path}")
|
||||
print(f" Config: {config_file}")
|
||||
# Build service arguments
|
||||
service_args = f"-d \"{config_dir}\" -f \"{config_file}\""
|
||||
|
||||
# Prepare command
|
||||
cmd = [
|
||||
str(binary_path),
|
||||
"-f", str(config_file),
|
||||
"-d", str(self.core_config_manager.storage.config_dir)
|
||||
]
|
||||
|
||||
# Start the process
|
||||
if system == "windows":
|
||||
# Windows: Use CREATE_NEW_PROCESS_GROUP to avoid console window
|
||||
creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP if hasattr(subprocess, 'CREATE_NEW_PROCESS_GROUP') else 0
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=creation_flags,
|
||||
cwd=str(self.core_config_manager.storage.config_dir)
|
||||
)
|
||||
else:
|
||||
# Unix-like systems
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
preexec_fn=os.setsid if hasattr(os, 'setsid') else None,
|
||||
cwd=str(self.core_config_manager.storage.config_dir)
|
||||
)
|
||||
|
||||
# Check if process started successfully
|
||||
try:
|
||||
return_code = process.poll()
|
||||
if return_code is not None:
|
||||
stdout, stderr = process.communicate(timeout=2)
|
||||
print(f"❌ Failed to start daemon (exit code: {return_code})")
|
||||
if stderr:
|
||||
print(f" Error: {stderr.decode().strip()}")
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
# Process is still running, which is good
|
||||
pass
|
||||
|
||||
# Save PID for later management
|
||||
pid_file = self.core_config_manager.storage.config_dir / "mihomo.pid"
|
||||
with open(pid_file, 'w') as f:
|
||||
f.write(str(process.pid))
|
||||
|
||||
print(f"✅ Daemon started successfully (PID: {process.pid})")
|
||||
print(f" PID file: {pid_file}")
|
||||
service_manager = ServiceManager(self.storage.config_dir)
|
||||
service_manager.install(service_name, str(binary_path), description, service_args)
|
||||
print(f"✅ Service '{service_name}' installed successfully")
|
||||
print(f" Config directory: {config_dir}")
|
||||
print(f" Config file: {config_file}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to start daemon: {e}")
|
||||
print(f"❌ Failed to install service: {e}")
|
||||
return False
|
||||
|
||||
def stop_daemon(self) -> bool:
|
||||
def uninstall_service(self, service_name: str = "mihomo") -> bool:
|
||||
"""
|
||||
Stop the running mihomo daemon.
|
||||
Uninstall mihomo system service.
|
||||
|
||||
Args:
|
||||
service_name: Name of the service to uninstall (default: "mihomo")
|
||||
|
||||
Returns:
|
||||
bool: True if daemon stopped successfully, False otherwise.
|
||||
bool: True if uninstallation successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
pid_file = self.core_config_manager.storage.config_dir / "mihomo.pid"
|
||||
|
||||
if not pid_file.exists():
|
||||
print("❌ No daemon appears to be running (PID file not found)")
|
||||
return False
|
||||
|
||||
with open(pid_file, 'r') as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
system = platform.system().lower()
|
||||
|
||||
try:
|
||||
if system == "windows":
|
||||
# Windows: Use taskkill
|
||||
subprocess.run(["taskkill", "/F", "/PID", str(pid)],
|
||||
check=True, capture_output=True, text=True)
|
||||
else:
|
||||
# Unix-like systems: Use kill
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
|
||||
# Wait a bit and check if process is still running
|
||||
try:
|
||||
os.kill(pid, 0) # Signal 0 just checks if process exists
|
||||
# Process still exists, try SIGKILL
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
# Process already terminated
|
||||
pass
|
||||
|
||||
pid_file.unlink()
|
||||
print(f"✅ Daemon stopped successfully (PID: {pid})")
|
||||
return True
|
||||
|
||||
except (ProcessLookupError, subprocess.CalledProcessError):
|
||||
# Process not found, clean up PID file
|
||||
pid_file.unlink()
|
||||
print("ℹ️ Daemon was not running, cleaned up PID file")
|
||||
return True
|
||||
service_manager = ServiceManager(self.storage.config_dir)
|
||||
service_manager.uninstall(service_name)
|
||||
print(f"✅ Service '{service_name}' uninstalled successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to stop daemon: {e}")
|
||||
print(f"❌ Failed to uninstall service: {e}")
|
||||
return False
|
||||
|
||||
def daemon_status(self) -> Dict[str, Any]:
|
||||
def start_service(self, service_name: str = "mihomo") -> bool:
|
||||
"""
|
||||
Get the status of the mihomo daemon.
|
||||
Start mihomo system service.
|
||||
|
||||
Args:
|
||||
service_name: Name of the service to start (default: "mihomo")
|
||||
|
||||
Returns:
|
||||
Dict containing daemon status information.
|
||||
bool: True if start successful, False otherwise
|
||||
"""
|
||||
status = {
|
||||
"running": False,
|
||||
"pid": None,
|
||||
"binary_path": None,
|
||||
"config_path": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
pid_file = self.core_config_manager.storage.config_dir / "mihomo.pid"
|
||||
service_manager = ServiceManager(self.storage.config_dir)
|
||||
service_manager.start(service_name)
|
||||
print(f"✅ Service '{service_name}' started successfully")
|
||||
return True
|
||||
|
||||
if not pid_file.exists():
|
||||
status["error"] = "PID file not found"
|
||||
return status
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to start service: {e}")
|
||||
return False
|
||||
|
||||
with open(pid_file, 'r') as f:
|
||||
pid = int(f.read().strip())
|
||||
def stop_service(self, service_name: str = "mihomo") -> bool:
|
||||
"""
|
||||
Stop mihomo system service.
|
||||
|
||||
# Check if process is running
|
||||
Args:
|
||||
service_name: Name of the service to stop (default: "mihomo")
|
||||
|
||||
Returns:
|
||||
bool: True if stop successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
service_manager = ServiceManager(self.storage.config_dir)
|
||||
service_manager.stop(service_name)
|
||||
print(f"✅ Service '{service_name}' stopped successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to stop service: {e}")
|
||||
return False
|
||||
|
||||
def restart_service(self, service_name: str = "mihomo") -> bool:
|
||||
"""
|
||||
Restart mihomo system service.
|
||||
|
||||
Args:
|
||||
service_name: Name of the service to restart (default: "mihomo")
|
||||
|
||||
Returns:
|
||||
bool: True if restart successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
service_manager = ServiceManager(self.storage.config_dir)
|
||||
service_manager.restart(service_name)
|
||||
print(f"✅ Service '{service_name}' restarted successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to restart service: {e}")
|
||||
return False
|
||||
|
||||
def get_service_status(self, service_name: str = "mihomo") -> str:
|
||||
"""
|
||||
Get the status of mihomo system service.
|
||||
|
||||
Args:
|
||||
service_name: Name of the service to check (default: "mihomo")
|
||||
|
||||
Returns:
|
||||
str: Service status description
|
||||
"""
|
||||
try:
|
||||
system = platform.system().lower()
|
||||
try:
|
||||
if system == "windows":
|
||||
# Windows: Use tasklist
|
||||
result = subprocess.run(["tasklist", "/FI", f"PID eq {pid}"],
|
||||
capture_output=True, text=True)
|
||||
if str(pid) in result.stdout:
|
||||
status["running"] = True
|
||||
status["pid"] = pid
|
||||
|
||||
if system == "windows":
|
||||
result = subprocess.run(["sc", "query", service_name], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.split('\n'):
|
||||
if "STATE" in line:
|
||||
return line.strip()
|
||||
return "Service exists but status unknown"
|
||||
else:
|
||||
return "Service not installed or not found"
|
||||
|
||||
elif system == "linux":
|
||||
result = subprocess.run(["systemctl", "is-active", service_name], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
status = result.stdout.strip()
|
||||
if status == "active":
|
||||
return "Service is running"
|
||||
else:
|
||||
status["error"] = "Process not found"
|
||||
pid_file.unlink() # Clean up stale PID file
|
||||
return f"Service is {status}"
|
||||
else:
|
||||
# Unix-like systems: Use kill signal 0
|
||||
os.kill(pid, 0) # Signal 0 just checks if process exists
|
||||
status["running"] = True
|
||||
status["pid"] = pid
|
||||
return "Service not installed or not found"
|
||||
|
||||
except (ProcessLookupError, subprocess.CalledProcessError):
|
||||
status["error"] = "Process not found"
|
||||
pid_file.unlink() # Clean up stale PID file
|
||||
elif system == "darwin":
|
||||
result = subprocess.run(["launchctl", "list"], capture_output=True, text=True)
|
||||
if service_name in result.stdout or f"com.{service_name}" in result.stdout:
|
||||
return "Service is loaded (check launchctl status for details)"
|
||||
else:
|
||||
return "Service not installed or not loaded"
|
||||
|
||||
else:
|
||||
return f"Unsupported system: {system}"
|
||||
|
||||
except Exception as e:
|
||||
status["error"] = str(e)
|
||||
return f"Error checking service status: {e}"
|
||||
|
||||
# Add binary and config paths
|
||||
system = platform.system().lower()
|
||||
binary_path = self.core_config_manager.storage.config_dir / "bin" / ("mihomo.exe" if system == "windows" else "mihomo")
|
||||
config_path = self.core_config_manager.storage.config_dir / "generated_config.yaml"
|
||||
|
||||
status["binary_path"] = str(binary_path) if binary_path.exists() else None
|
||||
status["config_path"] = str(config_path) if config_path.exists() else None
|
||||
|
||||
return status
|
||||
|
||||
def deep_merge(dict1, dict2):
|
||||
for k, v in dict2.items():
|
||||
|
||||
Reference in New Issue
Block a user