package service import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "super-frpc/database" "super-frpc/global" "super-frpc/postLog" ) func GetConfigDir() (string, error) { return global.CurrentConfig.InstancePath, nil } func GetFrpcPath() (string, error) { return global.CurrentConfig.FrpcPath, nil } func GetInitSystem() string { if runtime.GOOS == "windows" { return "windows" } if runtime.GOOS == "linux" { if _, err := os.Stat("/run/systemd/system"); err == nil { return "systemd" } if _, err := os.Stat("/etc/init.d"); err == nil { return "init.d" } } return "unknown" } func CreateBootService(instanceID int) error { instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err != nil { return fmt.Errorf("failed to query frpc instance: %w", err) } initType := GetInitSystem() switch initType { case "systemd": return createSystemdService(instanceID, instance.ConfigPath, instance.RunUser) case "init.d": return createInitDService(instanceID, instance.ConfigPath, instance.RunUser) case "windows": return createWindowsBootService(instanceID, instance.ConfigPath) default: return fmt.Errorf("unsupported init system: %s", initType) } } func createSystemdService(instanceID int, configPath, runUser string) error { frpcPath, err := GetFrpcPath() if err != nil { return err } serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { return err } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err != nil { return fmt.Errorf("failed to query frpc instance: %w", err) } user, err := database.GetUserByID(instance.UserID) if err != nil { return fmt.Errorf("failed to get user info: %w", err) } serviceContent := fmt.Sprintf(`[Unit] Description=superfrpc_%s_%s After=network.target [Service] Type=simple ExecStart=%s -c %s User=%s Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target `, user.Username, instance.Name, frpcPath, configPath, runUser) servicePath := filepath.Join("/etc/systemd/system", serviceName+".service") if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil { return fmt.Errorf("failed to create systemd service file: %w", err) } cmd := exec.Command("systemctl", "enable", serviceName) if output, err := cmd.CombinedOutput(); err != nil { os.Remove(servicePath) return fmt.Errorf("failed to enable service: %s, output: %s", err, output) } cmd = exec.Command("systemctl", "daemon-reload") if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("failed to reload daemon: %s, output: %s", err, output) } return nil } func createInitDService(instanceID int, configPath, runUser string) error { frpcPath, err := GetFrpcPath() if err != nil { return err } serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { return err } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err != nil { return fmt.Errorf("failed to query frpc instance: %w", err) } user, err := database.GetUserByID(instance.UserID) if err != nil { return fmt.Errorf("failed to get user info: %w", err) } var serviceContent string runUserArg := "" if runUser != "" && runUser != "root" { runUserArg = runUser serviceContent = fmt.Sprintf(`#!/bin/sh ### BEGIN INIT INFO # Provides: %s # Required-Start: $network $remote_fs $syslog # Required-Stop: $network $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Description: superfrpc %s %s ### END INIT INFO NAME="%s" PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=%s DAEMON_ARGS="-c %s" SCRIPTNAME=/etc/init.d/$NAME USER=%s [ -x "$DAEMON" ] || exit 0 case "$1" in start) echo -n "Starting $NAME: " start-stop-daemon -S -c $USER -b -m -p /var/run/$NAME.pid --start --exec $DAEMON -- $DAEMON_ARGS echo "$NAME." ;; stop) echo -n "Stopping $NAME: " start-stop-daemon -K -p /var/run/$NAME.pid rm -f /var/run/$NAME.pid echo "$NAME." ;; restart) $0 stop $0 start ;; status) if [ -f /var/run/$NAME.pid ]; then echo "$NAME is running (pid $(cat /var/run/$NAME.pid))" else echo "$NAME is not running" fi ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|status}" exit 1 ;; esac exit 0 `, serviceName, user.Username, instance.Name, serviceName, frpcPath, configPath, runUserArg) } else { serviceContent = fmt.Sprintf(`#!/bin/sh ### BEGIN INIT INFO # Provides: %s # Required-Start: $network $remote_fs $syslog # Required-Stop: $network $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Description: superfrpc %s %s ### END INIT INFO NAME="%s" PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=%s DAEMON_ARGS="-c %s" SCRIPTNAME=/etc/init.d/$NAME [ -x "$DAEMON" ] || exit 0 case "$1" in start) echo -n "Starting $NAME: " start-stop-daemon -S -b -m -p /var/run/$NAME.pid --start --exec $DAEMON -- $DAEMON_ARGS echo "$NAME." ;; stop) echo -n "Stopping $NAME: " start-stop-daemon -K -p /var/run/$NAME.pid rm -f /var/run/$NAME.pid echo "$NAME." ;; restart) $0 stop $0 start ;; status) if [ -f /var/run/$NAME.pid ]; then echo "$NAME is running (pid $(cat /var/run/$NAME.pid))" else echo "$NAME is not running" fi ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|status}" exit 1 ;; esac exit 0 `, serviceName, user.Username, instance.Name, serviceName, frpcPath, configPath) } servicePath := filepath.Join("/etc/init.d", serviceName) if err := os.WriteFile(servicePath, []byte(serviceContent), 0755); err != nil { return fmt.Errorf("failed to create init.d service file: %w", err) } cmd := exec.Command("/etc/init.d", serviceName, "enable") if output, err := cmd.CombinedOutput(); err != nil { os.Remove(servicePath) return fmt.Errorf("failed to enable service: %s, output: %s", err, output) } return nil } func createWindowsBootService(instanceID int, configPath string) error { frpcPath, err := GetFrpcPath() if err != nil { return err } serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { return err } command := fmt.Sprintf("\"%s\" -c \"%s\"", frpcPath, configPath) cmd := exec.Command("sc", "create", serviceName, "binPath=", command, "start=", "auto") output, err := cmd.CombinedOutput() postLog.Debug(fmt.Sprintf("[createWindowsBootService] Sc create output: %s", output)) if err != nil { return fmt.Errorf("failed to create Windows service: %s, output: %s", err, output) } return nil } func SetBootAtStart(instanceID int) error { initType := GetInitSystem() serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { return err } switch initType { case "windows": cmd := exec.Command("sc", "config", serviceName, "start=", "auto") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to set Windows service %s to auto-start: %s, output: %s", serviceName, err, output) } return nil case "systemd": cmd := exec.Command("systemctl", "enable", serviceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to enable systemd service %s: %s, output: %s", serviceName, err, output) } return nil case "init.d": cmd := exec.Command("update-rc.d", serviceName, "enable") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to enable init.d service %s: %s, output: %s", serviceName, err, output) } return nil default: return fmt.Errorf("unsupported init system: %s", initType) } } func RemoveBootAtStart(instanceID int) error { initType := GetInitSystem() serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { return err } switch initType { case "windows": cmd := exec.Command("sc", "config", serviceName, "start=", "disabled") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to set Windows service %s to disabled-start: %s, output: %s", serviceName, err, output) } return nil case "systemd": cmd := exec.Command("systemctl", "disable", serviceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to disable systemd service %s: %s, output: %s", serviceName, err, output) } return nil case "init.d": cmd := exec.Command("update-rc.d", serviceName, "disable") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to disable init.d service %s: %s, output: %s", serviceName, err, output) } return nil default: return fmt.Errorf("unsupported init system: %s", initType) } } func RemoveBootService(instanceID int) error { initType := GetInitSystem() serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { return err } switch initType { case "systemd": cmd := exec.Command("systemctl", "disable", serviceName) cmd.CombinedOutput() servicePath := filepath.Join("/etc/systemd/system", serviceName+".service") os.Remove(servicePath) cmd = exec.Command("systemctl", "daemon-reload") cmd.CombinedOutput() case "init.d": cmd := exec.Command("/etc/init.d", serviceName, "disable") cmd.CombinedOutput() servicePath := filepath.Join("/etc/init.d", serviceName) os.Remove(servicePath) case "windows": cmd := exec.Command("sc", "delete", serviceName) postLog.Debug(fmt.Sprintf("[removeBootService] Sc delete command: %s", cmd.String())) if output, err := cmd.CombinedOutput(); err != nil { postLog.Error(fmt.Sprintf("[removeBootService] Failed to delete Windows service: %s, output: %s", err, output)) } default: postLog.Error(fmt.Sprintf("[removeBootService] Unsupported init system: %s", initType)) } return nil } func IsInstanceRunning(instanceID int) error { initType := GetInitSystem() serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to get service name: %v", err)) return err } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to query instance: %v", err)) return err } switch initType { case "windows": cmd := exec.Command("sc", "query", serviceName) output, err := cmd.CombinedOutput() postLog.Debug(fmt.Sprintf("[IsInstanceRunning] Sc query output: %s", output)) outputStr := string(output) if err != nil { postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to check Windows service status: %s, output: %s", err, output)) return err } if strings.Contains(outputStr, "RUNNING") { postLog.Info(fmt.Sprintf("[IsInstanceRunning] Windows service %s is running", instance.Name)) return nil } else { postLog.Info(fmt.Sprintf("[IsInstanceRunning] Windows service %s is stopped", instance.Name)) return nil } case "systemd": cmd := exec.Command("systemctl", "status", serviceName) output, err := cmd.CombinedOutput() outputStr := string(output) if err != nil { exitError, ok := err.(*exec.ExitError) if ok { exitCode := exitError.ExitCode() if exitCode == 1 { postLog.Debug(fmt.Sprintf("[IsInstanceRunning] Systemd service status command failed with exit code 1: %s, output: %s", serviceName, outputStr)) return fmt.Errorf("service %s is not running", serviceName) } if exitCode == 2 { postLog.Debug(fmt.Sprintf("[IsInstanceRunning] Systemd service status command failed with exit code 2: %s, output: %s", serviceName, outputStr)) return fmt.Errorf("service %s is not running", serviceName) } if exitCode == 3 { postLog.Debug(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is not running: %s", serviceName, outputStr)) return fmt.Errorf("service %s is not running", serviceName) } if exitCode == 4 { postLog.Debug(fmt.Sprintf("[IsInstanceRunning] Systemd service %s does not exist: %s", serviceName, outputStr)) return fmt.Errorf("service %s does not exist", serviceName) } } postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to check systemd service status: %s", err)) return err } if strings.Contains(outputStr, "active (running)") { postLog.Info(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is running", serviceName)) return nil } else { postLog.Info(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is stopped", serviceName)) return nil } case "init.d": servicePath := fmt.Sprintf("/etc/init.d/%s", serviceName) if _, err := os.Stat(servicePath); err != nil { postLog.Warning(fmt.Sprintf("[IsInstanceRunning] Init.d service %s not found", serviceName)) return nil } cmd := exec.Command(servicePath, "status") output, err := cmd.CombinedOutput() outputStr := strings.TrimSpace(string(output)) if err != nil { exitError, ok := err.(*exec.ExitError) if ok { exitCode := exitError.ExitCode() if exitCode != 0 && !strings.Contains(outputStr, "is running") { postLog.Info(fmt.Sprintf("[IsInstanceRunning] Init.d service %s is stopped", serviceName)) return nil } } postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to check init.d service status: %s, output: %s", err, output)) return err } if strings.Contains(outputStr, "is running") { return nil } else { return nil } default: postLog.Error(fmt.Sprintf("[IsInstanceRunning] Unsupported init system: %s", initType)) return nil } } func GetInstancePid(instanceID int) int { initType := GetInitSystem() serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get service name: %v", err)) return 0 } switch initType { case "windows": cmd := exec.Command("sc", "query", serviceName, "info") output, err := cmd.CombinedOutput() if err != nil { postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get Windows service info: %s, output: %s", err, output)) return 0 } outputStr := string(output) lines := strings.Split(outputStr, "\n") for _, line := range lines { if strings.Contains(line, "PID") { parts := strings.Split(line, ":") if len(parts) >= 2 { pidStr := strings.TrimSpace(parts[1]) pid, err := strconv.Atoi(pidStr) if err == nil { return pid } } } } return 0 case "systemd": cmd := exec.Command("systemctl", "show", serviceName, "--property", "MainPID") output, err := cmd.CombinedOutput() if err != nil { postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get PID from systemd: %s, output: %s", err, output)) return 0 } outputStr := strings.TrimSpace(string(output)) parts := strings.Split(outputStr, "=") if len(parts) >= 2 { pidStr := strings.TrimSpace(parts[1]) if pidStr != "" && pidStr != "0" { pid, err := strconv.Atoi(pidStr) if err == nil { return pid } } } return 0 case "init.d": servicePath := fmt.Sprintf("/etc/init.d/%s", serviceName) if _, err := os.Stat(servicePath); err != nil { postLog.Error(fmt.Sprintf("[GetInstancePid] Init.d service %s not found", serviceName)) return 0 } cmd := exec.Command(servicePath, "status") output, err := cmd.CombinedOutput() if err != nil { postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get init.d service status: %s, output: %s", err, output)) return 0 } outputStr := strings.TrimSpace(string(output)) if strings.Contains(outputStr, "is running") { if strings.Contains(outputStr, "pid") { parts := strings.Split(outputStr, "pid") if len(parts) >= 2 { pidStr := strings.TrimSpace(strings.Split(parts[1], ")")[0]) pid, err := strconv.Atoi(pidStr) if err == nil { return pid } } } pidFile := fmt.Sprintf("/var/run/%s.pid", serviceName) if content, err := os.ReadFile(pidFile); err == nil { pidStr := strings.TrimSpace(string(content)) pid, err := strconv.Atoi(pidStr) if err == nil { return pid } } } return 0 default: postLog.Error(fmt.Sprintf("[GetInstancePid] Unsupported init system: %s", initType)) return 0 } } func StartWindowsService(instanceName string) error { cmd := exec.Command("sc", "start", instanceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to start Windows service %s: %s, output: %s", instanceName, err, output) } return nil } func StartSystemdService(instanceName string) error { cmd := exec.Command("systemctl", "start", instanceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to start systemd service %s: %s, output: %s", instanceName, err, output) } return nil } func StartInitDService(instanceName string) error { servicePath := fmt.Sprintf("/etc/init.d/%s", instanceName) cmd := exec.Command(servicePath, "start") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to start init.d service %s: %s, output: %s", instanceName, err, output) } return nil } func StopWindowsService(instanceName string) error { cmd := exec.Command("sc", "stop", instanceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to stop Windows service %s: %s, output: %s", instanceName, err, output) } return nil } func StopSystemdService(instanceName string) error { cmd := exec.Command("systemctl", "stop", instanceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to stop systemd service %s: %s, output: %s", instanceName, err, output) } return nil } func StopInitDService(instanceName string) error { servicePath := fmt.Sprintf("/etc/init.d/%s", instanceName) cmd := exec.Command(servicePath, "stop") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to stop init.d service %s: %s, output: %s", instanceName, err, output) } return nil } func RestartWindowsService(instanceName string) error { cmd := exec.Command("sc", "restart", instanceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to restart Windows service %s: %s, output: %s", instanceName, err, output) } return nil } func RestartSystemdService(instanceName string) error { cmd := exec.Command("systemctl", "restart", instanceName) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to restart systemd service %s: %s, output: %s", instanceName, err, output) } return nil } func RestartInitDService(instanceName string) error { servicePath := fmt.Sprintf("/etc/init.d/%s", instanceName) cmd := exec.Command(servicePath, "restart") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to restart init.d service %s: %s, output: %s", instanceName, err, output) } return nil }