package main import ( "errors" "fmt" "os" "os/exec" "path/filepath" "runtime" "super-frpc/postLog" "golang.org/x/sys/windows/registry" ) func GetConfigDir() (string, error) { config, err := GetConfig() if err != nil { postLog.Error(fmt.Sprintf("[GetConfigDir] Failed to get config: %v", err)) return "", err } return config.InstancePath, nil } func GetFrpcPath() (string, error) { config, err := GetConfig() if err != nil { postLog.Error(fmt.Sprintf("[GetFrpcPath] Failed to get config: %v", err)) return "", err } return config.FrpcPath, nil } func detectInitSystem() 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(username, instanceName, configPath, runUser string) error { initSystem := detectInitSystem() switch initSystem { case "systemd": return createSystemdService(username, instanceName, configPath, runUser) case "init.d": return createInitDService(username, instanceName, configPath, runUser) case "windows": return createWindowsBootService(username, instanceName, configPath) default: return errors.New("unsupported init system") } } func createSystemdService(username, instanceName, configPath, runUser string) error { frpcPath, err := GetFrpcPath() if err != nil { return err } serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) 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 `, username, instanceName, 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(username, instanceName, configPath, runUser string) error { frpcPath, err := GetFrpcPath() if err != nil { return err } serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) 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, username, instanceName, 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, username, instanceName, 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(username, instanceName, configPath string) error { frpcPath, err := GetFrpcPath() if err != nil { return err } serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) command := fmt.Sprintf("\"%s\" -c \"%s\"", frpcPath, configPath) key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, registry.WRITE|registry.WOW64_64KEY) if err != nil { return fmt.Errorf("failed to open registry key: %w", err) } defer key.Close() if err := key.SetStringValue(serviceName, command); err != nil { return fmt.Errorf("failed to set registry value: %w", err) } return nil } func removeBootService(username, instanceName string) error { initSystem := detectInitSystem() serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) switch initSystem { 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": key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, registry.WRITE|registry.WOW64_64KEY) if err != nil { postLog.Error(fmt.Sprintf("[removeBootService] Failed to open registry key: %v", err)) } else { defer key.Close() if err := key.DeleteValue(serviceName); err != nil { postLog.Error(fmt.Sprintf("[removeBootService] Failed to delete registry value: %v", err)) } } default: postLog.Error(fmt.Sprintf("[removeBootService] Unsupported init system: %s", initSystem)) } return nil }