diff --git a/backend/internal/pkg/sysutil/restart.go b/backend/internal/pkg/sysutil/restart.go index 369ffc47..f390a6cf 100644 --- a/backend/internal/pkg/sysutil/restart.go +++ b/backend/internal/pkg/sysutil/restart.go @@ -1,88 +1,39 @@ package sysutil import ( - "fmt" "log" "os" - "os/exec" "runtime" + "time" ) -const serviceName = "sub2api" - -// findExecutable finds the full path of an executable -// by checking common system paths -func findExecutable(name string) string { - // First try exec.LookPath (uses current PATH) - if path, err := exec.LookPath(name); err == nil { - return path - } - - // Fallback: check common paths - commonPaths := []string{ - "/usr/bin/" + name, - "/bin/" + name, - "/usr/sbin/" + name, - "/sbin/" + name, - } - - for _, path := range commonPaths { - if _, err := os.Stat(path); err == nil { - return path - } - } - - // Return the name as-is and let exec fail with a clear error - return name -} - -// RestartService triggers a service restart via systemd. +// RestartService triggers a service restart by gracefully exiting. // -// IMPORTANT: This function initiates the restart and returns immediately. -// The actual restart happens asynchronously - the current process will be killed -// by systemd and a new process will be started. -// -// We use Start() instead of Run() because: -// - systemctl restart will kill the current process first -// - Run() waits for completion, but the process dies before completion -// - Start() spawns the command independently, allowing systemd to handle the full cycle +// This relies on systemd's Restart=always configuration to automatically +// restart the service after it exits. This is the industry-standard approach: +// - Simple and reliable +// - No sudo permissions needed +// - No complex process management +// - Leverages systemd's native restart capability // // Prerequisites: // - Linux OS with systemd -// - NOPASSWD sudo access configured (install.sh creates /etc/sudoers.d/sub2api) +// - Service configured with Restart=always in systemd unit file func RestartService() error { if runtime.GOOS != "linux" { - return fmt.Errorf("systemd restart only available on Linux") + log.Println("Service restart via exit only works on Linux with systemd") + return nil } - log.Println("Initiating service restart...") + log.Println("Initiating service restart by graceful exit...") + log.Println("systemd will automatically restart the service (Restart=always)") - // Find full paths for sudo and systemctl - // This ensures the commands work even if PATH is limited in systemd service - sudoPath := findExecutable("sudo") - systemctlPath := findExecutable("systemctl") + // Give a moment for logs to flush and response to be sent + go func() { + time.Sleep(100 * time.Millisecond) + os.Exit(0) + }() - log.Printf("Using sudo: %s, systemctl: %s", sudoPath, systemctlPath) - - // The sub2api user has NOPASSWD sudo access for systemctl commands - // (configured by install.sh in /etc/sudoers.d/sub2api). - // Use -n (non-interactive) to prevent sudo from waiting for password input - // - // Use setsid to create a new session, ensuring the child process - // survives even if the parent process is killed by systemctl restart - setsidPath := findExecutable("setsid") - cmd := exec.Command(setsidPath, sudoPath, "-n", systemctlPath, "restart", serviceName) - - // Detach from parent's stdio to ensure clean separation - cmd.Stdin = nil - cmd.Stdout = nil - cmd.Stderr = nil - - if err := cmd.Start(); err != nil { - return fmt.Errorf("failed to initiate service restart: %w", err) - } - - log.Println("Service restart initiated successfully") return nil } diff --git a/deploy/install.sh b/deploy/install.sh index 45bfe464..d9f22e7a 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -73,9 +73,6 @@ declare -A MSG_ZH=( ["dirs_configured"]="目录配置完成" ["installing_service"]="正在安装 systemd 服务..." ["service_installed"]="systemd 服务已安装" - ["setting_up_sudoers"]="正在配置 sudoers..." - ["sudoers_configured"]="sudoers 配置完成" - ["sudoers_failed"]="sudoers 验证失败,已移除文件" ["ready_for_setup"]="准备就绪,可以启动设置向导" # Completion @@ -173,9 +170,6 @@ declare -A MSG_EN=( ["dirs_configured"]="Directories configured" ["installing_service"]="Installing systemd service..." ["service_installed"]="Systemd service installed" - ["setting_up_sudoers"]="Setting up sudoers..." - ["sudoers_configured"]="Sudoers configured" - ["sudoers_failed"]="Sudoers validation failed, removing file" ["ready_for_setup"]="Ready for Setup Wizard" # Completion @@ -521,35 +515,6 @@ setup_directories() { print_success "$(msg 'dirs_configured')" } -# Setup sudoers for service restart -setup_sudoers() { - print_info "$(msg 'setting_up_sudoers')" - - # Always generate sudoers file from script (not from tar.gz) - # This ensures the latest configuration is used even with older releases - # Support both /bin/systemctl and /usr/bin/systemctl for different distros - cat > /etc/sudoers.d/sub2api << 'EOF' -# Sudoers configuration for Sub2API -sub2api ALL=(ALL) NOPASSWD: /bin/systemctl restart sub2api -sub2api ALL=(ALL) NOPASSWD: /bin/systemctl stop sub2api -sub2api ALL=(ALL) NOPASSWD: /bin/systemctl start sub2api -sub2api ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart sub2api -sub2api ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop sub2api -sub2api ALL=(ALL) NOPASSWD: /usr/bin/systemctl start sub2api -EOF - - # Set correct permissions (required for sudoers files) - chmod 440 /etc/sudoers.d/sub2api - - # Validate sudoers file - if visudo -c -f /etc/sudoers.d/sub2api &>/dev/null; then - print_success "$(msg 'sudoers_configured')" - else - print_warning "$(msg 'sudoers_failed')" - rm -f /etc/sudoers.d/sub2api - fi -} - # Install systemd service install_service() { print_info "$(msg 'installing_service')" @@ -716,7 +681,6 @@ uninstall() { print_info "$(msg 'removing_files')" rm -f /etc/systemd/system/sub2api.service - rm -f /etc/sudoers.d/sub2api systemctl daemon-reload print_info "$(msg 'removing_install_dir')" @@ -787,7 +751,6 @@ main() { create_user setup_directories install_service - setup_sudoers prepare_for_setup print_completion } diff --git a/deploy/sub2api-sudoers b/deploy/sub2api-sudoers deleted file mode 100644 index fdd5400f..00000000 --- a/deploy/sub2api-sudoers +++ /dev/null @@ -1,17 +0,0 @@ -# Sudoers configuration for Sub2API -# This file allows the sub2api service user to restart the service without password -# -# Installation: -# sudo cp sub2api-sudoers /etc/sudoers.d/sub2api -# sudo chmod 440 /etc/sudoers.d/sub2api -# -# SECURITY NOTE: This grants limited sudo access only for service management - -# Allow sub2api user to restart the service without password -# Support both /bin/systemctl (Debian/Ubuntu) and /usr/bin/systemctl (RHEL/CentOS) -sub2api ALL=(ALL) NOPASSWD: /bin/systemctl restart sub2api -sub2api ALL=(ALL) NOPASSWD: /bin/systemctl stop sub2api -sub2api ALL=(ALL) NOPASSWD: /bin/systemctl start sub2api -sub2api ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart sub2api -sub2api ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop sub2api -sub2api ALL=(ALL) NOPASSWD: /usr/bin/systemctl start sub2api