Extract the request validation logic from ValidateRequest into a new ValidateRequestWithBody function to allow passing pre-read request bodies. Update all handler functions to use the new validation function for consistency and to avoid duplicate body reading.
748 lines
21 KiB
Go
748 lines
21 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type InstanceInfo struct {
|
|
Name string `json:"name"`
|
|
ServerAddr string `json:"serverAddr"`
|
|
ServerPort string `json:"serverPort"`
|
|
AuthMethod string `json:"auth_method"`
|
|
BootAtStart bool `json:"bootAtStart"`
|
|
RunUser string `json:"runUser"`
|
|
Additional map[string]interface{} `json:"additionalProperties"`
|
|
}
|
|
|
|
type CreateInstanceRequest struct {
|
|
Token string `json:"token"`
|
|
TimeStamp int64 `json:"timeStamp"`
|
|
InstanceInfo InstanceInfo `json:"instanceInfo"`
|
|
BootAtStart bool `json:"bootAtStart"`
|
|
RunUser string `json:"runUser"`
|
|
Additional map[string]interface{} `json:"additionalProperties"`
|
|
}
|
|
|
|
type FrpcInstance struct {
|
|
ID int
|
|
UserID int
|
|
Name string
|
|
ServerAddr string
|
|
ServerPort string
|
|
AuthMethod string
|
|
BootAtStart bool
|
|
RunUser string
|
|
ConfigPath string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
var frpcDB *sql.DB
|
|
|
|
func InitFrpcDatabase(dbPath string) error {
|
|
var err error
|
|
frpcDB, err = sql.Open("sqlite", dbPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open frpc database: %w", err)
|
|
}
|
|
|
|
if err = frpcDB.Ping(); err != nil {
|
|
return fmt.Errorf("failed to ping frpc database: %w", err)
|
|
}
|
|
|
|
createTableSQL := `
|
|
CREATE TABLE IF NOT EXISTS frpcInstances (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
userID INTEGER NOT NULL,
|
|
name TEXT NOT NULL,
|
|
serverAddr TEXT NOT NULL,
|
|
serverPort TEXT NOT NULL,
|
|
auth_method TEXT NOT NULL,
|
|
bootAtStart INTEGER NOT NULL DEFAULT 0,
|
|
runUser TEXT NOT NULL DEFAULT 'root',
|
|
configPath TEXT NOT NULL,
|
|
createdAt TEXT NOT NULL,
|
|
UNIQUE(userID, name)
|
|
);
|
|
`
|
|
_, err = frpcDB.Exec(createTableSQL)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create frpcInstances table: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func CloseFrpcDatabase() error {
|
|
if frpcDB != nil {
|
|
return frpcDB.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "invalid request method")
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "failed to read request body")
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req CreateInstanceRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "invalid request format")
|
|
return
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithBody(w, r, body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
return
|
|
}
|
|
|
|
if err := CheckPermission(userID, "superuser", "admin"); err != nil {
|
|
SendErrorResponse(w, http.StatusForbidden, err.Error())
|
|
return
|
|
}
|
|
|
|
if req.InstanceInfo.Name == "" || req.InstanceInfo.ServerAddr == "" ||
|
|
req.InstanceInfo.ServerPort == "" || req.InstanceInfo.AuthMethod == "" {
|
|
SendErrorResponse(w, http.StatusBadRequest, "missing required fields in instanceInfo")
|
|
return
|
|
}
|
|
|
|
runUser := req.RunUser
|
|
if runUser == "" {
|
|
runUser = "root"
|
|
}
|
|
|
|
user, err := GetUserByID(userID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to get user info")
|
|
return
|
|
}
|
|
|
|
configDir, err := GetConfigDir()
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to get config directory")
|
|
return
|
|
}
|
|
|
|
configFileName := fmt.Sprintf("superfrpc_%s_%s.toml", user.Username, req.InstanceInfo.Name)
|
|
configPath := filepath.Join(configDir, configFileName)
|
|
|
|
configContent := generateFrpcConfig(req.InstanceInfo)
|
|
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to create config file")
|
|
return
|
|
}
|
|
|
|
_, err = frpcDB.Exec(`
|
|
INSERT INTO frpcInstances (userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`, userID, req.InstanceInfo.Name, req.InstanceInfo.ServerAddr, req.InstanceInfo.ServerPort,
|
|
req.InstanceInfo.AuthMethod, req.BootAtStart, runUser, configPath, time.Now().Format(time.RFC3339))
|
|
|
|
if err != nil {
|
|
os.Remove(configPath)
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to save instance to database")
|
|
return
|
|
}
|
|
|
|
if req.BootAtStart {
|
|
if err := createBootService(user.Username, req.InstanceInfo.Name, configPath, runUser); err != nil {
|
|
frpcDB.Exec("DELETE FROM frpcInstances WHERE userID = ? AND name = ?", userID, req.InstanceInfo.Name)
|
|
os.Remove(configPath)
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to create boot service")
|
|
return
|
|
}
|
|
}
|
|
|
|
SendSuccessResponse(w, "instance created successfully", map[string]interface{}{
|
|
"name": req.InstanceInfo.Name,
|
|
"configPath": configPath,
|
|
"bootAtStart": req.BootAtStart,
|
|
})
|
|
}
|
|
|
|
func DeleteInstanceHandler(w http.ResponseWriter, r *http.Request, instanceName string) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "invalid request method")
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "failed to read request body")
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
userID, _, err := ValidateRequestWithBody(w, r, body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
return
|
|
}
|
|
|
|
if err := CheckPermission(userID, "superuser", "admin"); err != nil {
|
|
SendErrorResponse(w, http.StatusForbidden, err.Error())
|
|
return
|
|
}
|
|
|
|
user, err := GetUserByID(userID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to get user info")
|
|
return
|
|
}
|
|
|
|
var instance FrpcInstance
|
|
err = frpcDB.QueryRow(`
|
|
SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt
|
|
FROM frpcInstances WHERE userID = ? AND name = ?
|
|
`, userID, instanceName).Scan(
|
|
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort,
|
|
&instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &instance.CreatedAt)
|
|
|
|
if err == sql.ErrNoRows {
|
|
SendErrorResponse(w, http.StatusNotFound, "instance not found")
|
|
return
|
|
}
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to query instance")
|
|
return
|
|
}
|
|
|
|
if instance.BootAtStart {
|
|
if err := removeBootService(user.Username, instanceName); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to remove boot service")
|
|
return
|
|
}
|
|
}
|
|
|
|
if _, err := os.Stat(instance.ConfigPath); err == nil {
|
|
if err := os.Remove(instance.ConfigPath); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to remove config file")
|
|
return
|
|
}
|
|
}
|
|
|
|
_, err = frpcDB.Exec("DELETE FROM frpcInstances WHERE id = ?", instance.ID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to delete instance from database")
|
|
return
|
|
}
|
|
|
|
SendSuccessResponse(w, "instance deleted successfully", map[string]interface{}{
|
|
"name": instanceName,
|
|
})
|
|
}
|
|
|
|
func ModifyInstanceHandler(w http.ResponseWriter, r *http.Request, instanceName string) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "invalid request method")
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "failed to read request body")
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var reqMap map[string]interface{}
|
|
if err := json.Unmarshal(body, &reqMap); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "invalid request format")
|
|
return
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithBody(w, r, body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
return
|
|
}
|
|
|
|
if err := CheckPermission(userID, "superuser", "admin"); err != nil {
|
|
SendErrorResponse(w, http.StatusForbidden, err.Error())
|
|
return
|
|
}
|
|
|
|
user, err := GetUserByID(userID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to get user info")
|
|
return
|
|
}
|
|
|
|
var instance FrpcInstance
|
|
err = frpcDB.QueryRow(`
|
|
SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt
|
|
FROM frpcInstances WHERE userID = ? AND name = ?
|
|
`, userID, instanceName).Scan(
|
|
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort,
|
|
&instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &instance.CreatedAt)
|
|
|
|
if err == sql.ErrNoRows {
|
|
SendErrorResponse(w, http.StatusNotFound, "instance not found")
|
|
return
|
|
}
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to query instance")
|
|
return
|
|
}
|
|
|
|
newName := instance.Name
|
|
newServerAddr := instance.ServerAddr
|
|
newServerPort := instance.ServerPort
|
|
newAuthMethod := instance.AuthMethod
|
|
newRunUser := instance.RunUser
|
|
newBootAtStart := instance.BootAtStart
|
|
|
|
if v, ok := reqMap["name"].(string); ok && v != "" {
|
|
newName = v
|
|
}
|
|
if v, ok := reqMap["serverAddr"].(string); ok && v != "" {
|
|
newServerAddr = v
|
|
}
|
|
if v, ok := reqMap["serverPort"].(string); ok && v != "" {
|
|
newServerPort = v
|
|
}
|
|
if v, ok := reqMap["auth_method"].(string); ok && v != "" {
|
|
newAuthMethod = v
|
|
}
|
|
if v, ok := reqMap["runUser"].(string); ok && v != "" {
|
|
newRunUser = v
|
|
}
|
|
if v, ok := reqMap["bootAtStart"].(bool); ok {
|
|
newBootAtStart = v
|
|
}
|
|
|
|
oldConfigPath := instance.ConfigPath
|
|
var newConfigPath string
|
|
if newName != instance.Name || newRunUser != instance.RunUser {
|
|
configDir, err := GetConfigDir()
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to get config directory")
|
|
return
|
|
}
|
|
|
|
newConfigFileName := fmt.Sprintf("superfrpc_%s_%s.toml", user.Username, newName)
|
|
newConfigPath = filepath.Join(configDir, newConfigFileName)
|
|
|
|
if oldConfigPath != newConfigPath {
|
|
if _, err := os.Stat(oldConfigPath); err == nil {
|
|
if err := os.Rename(oldConfigPath, newConfigPath); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to rename config file")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
newConfigPath = oldConfigPath
|
|
}
|
|
|
|
info := InstanceInfo{
|
|
Name: newName,
|
|
ServerAddr: newServerAddr,
|
|
ServerPort: newServerPort,
|
|
AuthMethod: newAuthMethod,
|
|
RunUser: newRunUser,
|
|
}
|
|
|
|
configContent := generateFrpcConfig(info)
|
|
if err := os.WriteFile(newConfigPath, []byte(configContent), 0644); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to update config file")
|
|
return
|
|
}
|
|
|
|
_, err = frpcDB.Exec(`
|
|
UPDATE frpcInstances
|
|
SET name = ?, serverAddr = ?, serverPort = ?, auth_method = ?, bootAtStart = ?, runUser = ?, configPath = ?
|
|
WHERE id = ?
|
|
`, newName, newServerAddr, newServerPort, newAuthMethod, newBootAtStart, newRunUser, newConfigPath, instance.ID)
|
|
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to update instance in database")
|
|
return
|
|
}
|
|
|
|
if instance.BootAtStart && !newBootAtStart {
|
|
removeBootService(user.Username, instanceName)
|
|
} else if !instance.BootAtStart && newBootAtStart {
|
|
createBootService(user.Username, newName, newConfigPath, newRunUser)
|
|
} else if instance.BootAtStart && newBootAtStart && (instance.Name != newName || instance.RunUser != newRunUser) {
|
|
removeBootService(user.Username, instanceName)
|
|
createBootService(user.Username, newName, newConfigPath, newRunUser)
|
|
}
|
|
|
|
SendSuccessResponse(w, "instance modified successfully", map[string]interface{}{
|
|
"name": newName,
|
|
"configPath": newConfigPath,
|
|
})
|
|
}
|
|
|
|
func ListInstancesHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "invalid request method")
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "failed to read request body")
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
userID, _, err := ValidateRequestWithBody(w, r, body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
return
|
|
}
|
|
|
|
userType, err := GetUserType(userID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to get user type")
|
|
return
|
|
}
|
|
|
|
rows, err := frpcDB.Query(`
|
|
SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt
|
|
FROM frpcInstances WHERE userID = ?
|
|
`, userID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to query instances")
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var instances []map[string]interface{}
|
|
for rows.Next() {
|
|
var instance FrpcInstance
|
|
var createdAtStr string
|
|
if err := rows.Scan(
|
|
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort,
|
|
&instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr,
|
|
); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "failed to scan instance")
|
|
return
|
|
}
|
|
|
|
instanceData := map[string]interface{}{
|
|
"name": instance.Name,
|
|
"serverAddr": instance.ServerAddr,
|
|
"serverPort": instance.ServerPort,
|
|
"auth_method": instance.AuthMethod,
|
|
"bootAtStart": instance.BootAtStart,
|
|
"runUser": instance.RunUser,
|
|
"configPath": instance.ConfigPath,
|
|
"createdAt": createdAtStr,
|
|
}
|
|
|
|
if userType == "visitor" {
|
|
delete(instanceData, "serverAddr")
|
|
delete(instanceData, "serverPort")
|
|
delete(instanceData, "auth_method")
|
|
}
|
|
|
|
instances = append(instances, instanceData)
|
|
}
|
|
|
|
if instances == nil {
|
|
instances = []map[string]interface{}{}
|
|
}
|
|
|
|
SendSuccessResponse(w, "instances retrieved successfully", instances)
|
|
}
|
|
|
|
func generateFrpcConfig(info InstanceInfo) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("[common]\n")
|
|
sb.WriteString(fmt.Sprintf("server_addr = %s\n", info.ServerAddr))
|
|
sb.WriteString(fmt.Sprintf("server_port = %s\n", info.ServerPort))
|
|
sb.WriteString(fmt.Sprintf("auth_method = %s\n", info.AuthMethod))
|
|
|
|
for key, value := range info.Additional {
|
|
sb.WriteString(fmt.Sprintf("%s = %v\n", key, value))
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
func GetConfigDir() (string, error) {
|
|
config, err := GetConfig()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return config.InstancePath, nil
|
|
}
|
|
|
|
func GetFrpcPath() (string, error) {
|
|
config, err := GetConfig()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return config.FrpcPath, nil
|
|
}
|
|
|
|
func detectInitSystem() string {
|
|
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)
|
|
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 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)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetUserInstances(userID int) ([]FrpcInstance, error) {
|
|
rows, err := frpcDB.Query(`
|
|
SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt
|
|
FROM frpcInstances WHERE userID = ?
|
|
`, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var instances []FrpcInstance
|
|
for rows.Next() {
|
|
var instance FrpcInstance
|
|
var createdAtStr string
|
|
if err := rows.Scan(
|
|
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort,
|
|
&instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
instance.CreatedAt, _ = time.Parse(time.RFC3339, createdAtStr)
|
|
instances = append(instances, instance)
|
|
}
|
|
|
|
return instances, nil
|
|
}
|