Implement setBootAtStart and removeBootAtStart functions to handle service auto-start configuration separately from service creation/removal. Update handlers to use these new functions for better control of boot behavior. The changes support Windows, systemd and init.d systems.
585 lines
16 KiB
Go
585 lines
16 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"super-frpc/postLog"
|
|
)
|
|
|
|
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 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(username, instanceName, configPath, runUser string) error {
|
|
initType := GetInitSystem()
|
|
|
|
switch initType {
|
|
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 fmt.Errorf("unsupported init system: %s", initType)
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
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(username, instanceName string) error {
|
|
initType := GetInitSystem()
|
|
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName)
|
|
|
|
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(username, instanceName string) error {
|
|
initType := GetInitSystem()
|
|
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName)
|
|
|
|
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(username, instanceName string) error {
|
|
initType := GetInitSystem()
|
|
|
|
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName)
|
|
|
|
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(instanceName string) bool {
|
|
initType := GetInitSystem()
|
|
switch initType {
|
|
case "windows":
|
|
cmd := exec.Command("sc", "query", instanceName)
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
postLog.Debug(fmt.Sprintf("[IsInstanceRunning] Sc query output: %s", output))
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to check Windows service status: %s, output: %s", err, output))
|
|
return false
|
|
}
|
|
|
|
outputStr := string(output)
|
|
if strings.Contains(outputStr, "RUNNING") {
|
|
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Windows service %s is running", instanceName))
|
|
return true
|
|
} else {
|
|
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Windows service %s is stopped", instanceName))
|
|
return false
|
|
}
|
|
|
|
case "systemd":
|
|
cmd := exec.Command("systemctl", "is-active", instanceName)
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to check systemd service status: %s, output: %s", err, output))
|
|
return false
|
|
}
|
|
|
|
status := strings.TrimSpace(string(output))
|
|
if status == "active" {
|
|
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is running", instanceName))
|
|
return true
|
|
} else {
|
|
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is stopped", instanceName))
|
|
return false
|
|
}
|
|
|
|
case "init.d":
|
|
servicePath := fmt.Sprintf("/etc/init.d/%s", instanceName)
|
|
if _, err := os.Stat(servicePath); err != nil {
|
|
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Init.d service %s not found", instanceName))
|
|
return false
|
|
}
|
|
|
|
cmd := exec.Command(servicePath, "status")
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to check init.d service status: %s, output: %s", err, output))
|
|
return false
|
|
}
|
|
|
|
outputStr := strings.TrimSpace(string(output))
|
|
if strings.Contains(outputStr, "is running") {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
default:
|
|
postLog.Error(fmt.Sprintf("[IsInstanceRunning] Unsupported init system: %s", initType))
|
|
return false
|
|
}
|
|
}
|
|
|
|
func GetInstancePid(instanceName string) int {
|
|
initType := GetInitSystem()
|
|
switch initType {
|
|
case "windows":
|
|
cmd := exec.Command("sc", "query", instanceName, "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", instanceName, "--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", instanceName)
|
|
if _, err := os.Stat(servicePath); err != nil {
|
|
postLog.Error(fmt.Sprintf("[GetInstancePid] Init.d service %s not found", instanceName))
|
|
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", instanceName)
|
|
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
|
|
}
|