refactor(frpc): restructure instance management and config handling

- Move instance-related structs and functions to config.go
- Remove serverAddr, serverPort, and authMethod from database schema
- Implement new config parsing and encoding with nested key support
- Update service management to use instanceID instead of username/name
- Add GetServiceNameByInstanceID helper function
- Update API documentation for auth.method field change
This commit is contained in:
2026-03-25 20:00:34 +08:00
parent da729b44ff
commit 92a0e24db7
8 changed files with 491 additions and 281 deletions

View File

@@ -66,6 +66,7 @@ For detailed API documentation, please see [docs/api.md](docs/api.md)
- [ ] Add frpc instance running status management API - [ ] Add frpc instance running status management API
- [ ] Add frpc instance log display API - [ ] Add frpc instance log display API
- [ ] Fix random database lock when processing logs - [ ] Fix random database lock when processing logs
- [ ] Add frpc createdBy storage and display
## License ## License

276
config.go
View File

@@ -5,6 +5,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"strconv"
"strings"
"github.com/BurntSushi/toml"
) )
type Config struct { type Config struct {
@@ -15,6 +19,40 @@ type Config struct {
Debug bool `json:"debug"` Debug bool `json:"debug"`
} }
type InstanceInfo struct {
Name string `json:"name"`
ServerAddr string `json:"serverAddr"`
ServerPort string `json:"serverPort"`
AuthMethod string `json:"auth_method"`
RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"`
}
type FrpcProxyInfo struct {
Name string `json:"name"`
Type string `json:"type"`
LocalIP string `json:"local_ip"`
LocalPort string `json:"local_port"`
RemotePort string `json:"remote_port"`
}
type CreateInstanceRequest struct {
InstanceInfo InstanceInfo `json:"instanceInfo"`
BootAtStart bool `json:"bootAtStart"`
RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"`
}
type CreateProxyRequest struct {
InstanceID string `json:"instanceID"`
ProxyInfo FrpcProxyInfo `json:"proxyInfo"`
}
type FrpcConfig struct {
Global map[string]interface{} `toml:"-"`
Proxies []map[string]interface{} `toml:"proxies"`
}
var globalConfig *Config var globalConfig *Config
func LoadConfig(configPath string) (*Config, error) { func LoadConfig(configPath string) (*Config, error) {
@@ -72,3 +110,241 @@ func SaveConfig(configPath string, config *Config) error {
globalConfig = config globalConfig = config
return nil return nil
} }
func decodeFrpcConfig(configContent string) (FrpcConfig, error) {
var config FrpcConfig
var rawMap map[string]interface{}
meta, err := toml.Decode(configContent, &rawMap)
if err != nil {
return config, fmt.Errorf("failed to parse config: %w", err)
}
config.Global = make(map[string]interface{})
config.Proxies = make([]map[string]interface{}, 0)
getNestedValue := func(m map[string]interface{}, key string) (interface{}, bool) {
parts := strings.Split(key, ".")
current := m
for i, part := range parts {
if i == len(parts)-1 {
val, ok := current[part]
return val, ok
}
next, ok := current[part].(map[string]interface{})
if !ok {
return nil, false
}
current = next
}
return nil, false
}
for _, key := range meta.Keys() {
keyStr := key.String()
if keyStr == "proxies" {
if proxies, ok := rawMap["proxies"].([]map[string]interface{}); ok {
config.Proxies = proxies
} else if proxies, ok := rawMap["proxies"].([]interface{}); ok {
for _, p := range proxies {
if pm, ok := p.(map[string]interface{}); ok {
config.Proxies = append(config.Proxies, pm)
}
}
}
} else if !strings.HasPrefix(keyStr, "proxies.") {
if val, ok := getNestedValue(rawMap, keyStr); ok {
config.Global[keyStr] = val
}
}
}
return config, nil
}
func encodeFrpcConfig(config FrpcConfig) (string, error) {
var buf strings.Builder
if len(config.Global) > 0 {
for key, value := range config.Global {
switch v := value.(type) {
case string:
buf.WriteString(fmt.Sprintf("%s = %q\n", key, v))
case int:
buf.WriteString(fmt.Sprintf("%s = %d\n", key, v))
case int64:
buf.WriteString(fmt.Sprintf("%s = %d\n", key, v))
case float64:
buf.WriteString(fmt.Sprintf("%s = %v\n", key, v))
case bool:
buf.WriteString(fmt.Sprintf("%s = %v\n", key, v))
default:
data, err := json.Marshal(v)
if err != nil {
return "", fmt.Errorf("failed to marshal value for key %s: %w", key, err)
}
buf.WriteString(fmt.Sprintf("%s = %s\n", key, string(data)))
}
}
buf.WriteString("\n")
}
if len(config.Proxies) > 0 {
for _, proxy := range config.Proxies {
buf.WriteString("[[proxies]]\n")
for key, value := range proxy {
switch v := value.(type) {
case string:
buf.WriteString(fmt.Sprintf("%s = %q\n", key, v))
case int:
buf.WriteString(fmt.Sprintf("%s = %d\n", key, v))
case int64:
buf.WriteString(fmt.Sprintf("%s = %d\n", key, v))
case float64:
buf.WriteString(fmt.Sprintf("%s = %v\n", key, v))
case bool:
buf.WriteString(fmt.Sprintf("%s = %v\n", key, v))
default:
data, err := json.Marshal(v)
if err != nil {
return "", fmt.Errorf("failed to marshal value for key %s: %w", key, err)
}
buf.WriteString(fmt.Sprintf("%s = %s\n", key, string(data)))
}
}
buf.WriteString("\n")
}
}
return strings.TrimSuffix(buf.String(), "\n"), nil
}
func generateFrpcConfig(info InstanceInfo) string {
config := FrpcConfig{
Global: make(map[string]interface{}),
}
config.Global["serverAddr"] = info.ServerAddr
config.Global["serverPort"] = info.ServerPort
config.Global["auth.method"] = info.AuthMethod
for key, value := range info.Additional {
config.Global[key] = value
}
result, err := encodeFrpcConfig(config)
if err != nil {
return ""
}
return result
}
func addFrpcProxy(configContent string, info FrpcProxyInfo) (string, error) {
config, err := decodeFrpcConfig(configContent)
if err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
proxy := map[string]interface{}{
"name": info.Name,
"type": info.Type,
"localIP": info.LocalIP,
"localPort": info.LocalPort,
"remotePort": info.RemotePort,
}
config.Proxies = append(config.Proxies, proxy)
result, err := encodeFrpcConfig(config)
if err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return result, nil
}
func removeFrpcProxy(configContent string, proxyName string) (string, error) {
config, err := decodeFrpcConfig(configContent)
if err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
var found bool
var newProxies []map[string]interface{}
for _, proxy := range config.Proxies {
if name, ok := proxy["name"].(string); ok && name == proxyName {
found = true
continue
}
newProxies = append(newProxies, proxy)
}
if !found {
return "", fmt.Errorf("proxy %s not found", proxyName)
}
config.Proxies = newProxies
result, err := encodeFrpcConfig(config)
if err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return result, nil
}
func getKeyText(configPath, key, section string) (value string, err error) {
configContent, err := os.ReadFile(configPath)
if err != nil {
return "", fmt.Errorf("failed to read config file: %w", err)
}
config, err := decodeFrpcConfig(string(configContent))
if err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
if config.Global == nil {
return "", fmt.Errorf("config file has no global fields")
}
if v, ok := config.Global[key]; ok {
value = fmt.Sprintf("%v", v)
}
return value, nil
}
func setKeyText(configPath, key, section string, value string) error {
configContent, err := os.ReadFile(configPath)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
config, err := decodeFrpcConfig(string(configContent))
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
if config.Global == nil {
config.Global = make(map[string]interface{})
}
if intVal, err := strconv.Atoi(value); err == nil {
config.Global[key] = intVal
} else {
config.Global[key] = value
}
result, err := encodeFrpcConfig(config)
if err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
if err := os.WriteFile(configPath, []byte(result), 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}

View File

@@ -31,9 +31,6 @@ type FrpcInstance struct {
ID int ID int
UserID int UserID int
Name string Name string
ServerAddr string
ServerPort string
AuthMethod string
BootAtStart bool BootAtStart bool
RunUser string RunUser string
ConfigPath string ConfigPath string
@@ -275,9 +272,6 @@ func InitFrpcDatabase(dbPath string) error {
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
userID INTEGER NOT NULL, userID INTEGER NOT NULL,
name TEXT 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, bootAtStart INTEGER NOT NULL DEFAULT 0,
runUser TEXT NOT NULL DEFAULT 'root', runUser TEXT NOT NULL DEFAULT 'root',
configPath TEXT NOT NULL, configPath TEXT NOT NULL,
@@ -353,8 +347,8 @@ func DBQuerySpecificUser(userID int) (User, error) { // Query user by ID
} }
func DBAddFrpcInstance(instance FrpcInstance) error { func DBAddFrpcInstance(instance FrpcInstance) error {
_, err := frpcDB.Exec("INSERT INTO frpcInstances (userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", _, err := frpcDB.Exec("INSERT INTO frpcInstances (userID, name, bootAtStart, runUser, configPath, createdAt) VALUES (?, ?, ?, ?, ?, ?)",
instance.UserID, instance.Name, instance.ServerAddr, instance.ServerPort, instance.AuthMethod, instance.BootAtStart, instance.RunUser, instance.ConfigPath, time.Now().Format(time.RFC3339)) instance.UserID, instance.Name, instance.BootAtStart, instance.RunUser, instance.ConfigPath, time.Now().Format(time.RFC3339))
if err != nil { if err != nil {
return fmt.Errorf("failed to insert frpc instance: %w", err) return fmt.Errorf("failed to insert frpc instance: %w", err)
} }
@@ -364,8 +358,8 @@ func DBAddFrpcInstance(instance FrpcInstance) error {
func DBQueryFrpcInstanceByID(instanceID int) (FrpcInstance, error) { func DBQueryFrpcInstanceByID(instanceID int) (FrpcInstance, error) {
var instance FrpcInstance var instance FrpcInstance
var createdAtStr string var createdAtStr string
err := frpcDB.QueryRow("SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE id = ?", instanceID).Scan( err := frpcDB.QueryRow("SELECT id, userID, name, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE id = ?", instanceID).Scan(
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort, &instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr) &instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr)
if err != nil { if err != nil {
return instance, fmt.Errorf("failed to query frpc instance: %w", err) return instance, fmt.Errorf("failed to query frpc instance: %w", err)
} }
@@ -376,8 +370,8 @@ func DBQueryFrpcInstanceByID(instanceID int) (FrpcInstance, error) {
func DBQueryFrpcInstance(userID int, instanceName string) (FrpcInstance, error) { func DBQueryFrpcInstance(userID int, instanceName string) (FrpcInstance, error) {
var instance FrpcInstance var instance FrpcInstance
var createdAtStr string var createdAtStr string
err := frpcDB.QueryRow("SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE userID = ? AND name = ?", userID, instanceName).Scan( err := frpcDB.QueryRow("SELECT id, userID, name, 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, &createdAtStr) &instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr)
if err != nil { if err != nil {
return instance, fmt.Errorf("failed to query frpc instance: %w", err) return instance, fmt.Errorf("failed to query frpc instance: %w", err)
} }
@@ -394,8 +388,8 @@ func DBRemoveFrpcInstanceByID(instanceID int) error {
} }
func DBUpdateFrpcInstance(instance FrpcInstance) error { func DBUpdateFrpcInstance(instance FrpcInstance) error {
_, err := frpcDB.Exec("UPDATE frpcInstances SET name = ?, serverAddr = ?, serverPort = ?, auth_method = ?, bootAtStart = ?, runUser = ?, configPath = ? WHERE id = ?", _, err := frpcDB.Exec("UPDATE frpcInstances SET bootAtStart = ?, runUser = ?, configPath = ? WHERE id = ?",
instance.Name, instance.ServerAddr, instance.ServerPort, instance.AuthMethod, instance.BootAtStart, instance.RunUser, instance.ConfigPath, instance.ID) instance.BootAtStart, instance.RunUser, instance.ConfigPath, instance.ID)
if err != nil { if err != nil {
return fmt.Errorf("failed to update frpc instance: %w", err) return fmt.Errorf("failed to update frpc instance: %w", err)
} }
@@ -404,8 +398,7 @@ func DBUpdateFrpcInstance(instance FrpcInstance) error {
func DBListFrpcInstances() ([]FrpcInstance, error) { func DBListFrpcInstances() ([]FrpcInstance, error) {
rows, err := frpcDB.Query(` rows, err := frpcDB.Query(`
SELECT fi.id, fi.userID, fi.name, fi.serverAddr, fi.serverPort, fi.auth_method, SELECT fi.id, fi.userID, fi.name, fi.bootAtStart, fi.runUser, fi.configPath, fi.createdAt, u.username
fi.bootAtStart, fi.runUser, fi.configPath, fi.createdAt, u.username
FROM frpcInstances fi FROM frpcInstances fi
JOIN userLogin u ON fi.userID = u.userID JOIN userLogin u ON fi.userID = u.userID
`) `)
@@ -418,7 +411,7 @@ func DBListFrpcInstances() ([]FrpcInstance, error) {
for rows.Next() { for rows.Next() {
var instance FrpcInstance var instance FrpcInstance
var createdAtStr string 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, &instance.CreatedBy); err != nil { if err := rows.Scan(&instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr, &instance.CreatedBy); err != nil {
return nil, fmt.Errorf("failed to scan frpc instance: %w", err) return nil, fmt.Errorf("failed to scan frpc instance: %w", err)
} }
instance.CreatedAt, _ = time.Parse(time.RFC3339, createdAtStr) instance.CreatedAt, _ = time.Parse(time.RFC3339, createdAtStr)
@@ -431,3 +424,19 @@ func DBListFrpcInstances() ([]FrpcInstance, error) {
return instances, nil return instances, nil
} }
func GetServiceNameByInstanceID(instanceID int) (string, error) {
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err != nil {
return "", fmt.Errorf("failed to query frpc instance: %w", err)
}
user, err := GetUserByID(instance.UserID)
if err != nil {
return "", fmt.Errorf("failed to get user info: %w", err)
}
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name)
postLog.Debug(fmt.Sprintf("[GetServiceNameByInstanceID] instanceID: %d, serviceName: %s", instanceID, serviceName))
return serviceName, nil
}

View File

@@ -552,8 +552,8 @@ Modify fields in the `[common]` section of the frpc configuration file. Only `[c
"modifiedData": { "modifiedData": {
"server_addr": "192.168.1.1", "server_addr": "192.168.1.1",
"server_port": "7000", "server_port": "7000",
"auth_method": "token", "auth.method": "token",
"auth_token": "my_secret_token" "auth.token": "my_secret_token"
} }
} }
``` ```
@@ -1149,7 +1149,7 @@ X-Timestamp: 1704067200000
"name": "my_frpc", "name": "my_frpc",
"serverAddr": "127.0.0.1", "serverAddr": "127.0.0.1",
"serverPort": "7000", "serverPort": "7000",
"auth_method": "token", "auth.method": "token",
"bootAtStart": true, "bootAtStart": true,
"runUser": "root", "runUser": "root",
"configPath": "./configs/superfrpc_user_my_frpc.toml", "configPath": "./configs/superfrpc_user_my_frpc.toml",
@@ -1185,14 +1185,14 @@ X-Timestamp: 1704067200000
| name | string | Instance name | | name | string | Instance name |
| serverAddr | string | frps server address (admin/superuser only) | | serverAddr | string | frps server address (admin/superuser only) |
| serverPort | string | frps server port (admin/superuser only) | | serverPort | string | frps server port (admin/superuser only) |
| auth_method | string | Authentication method (admin/superuser only) | | auth.method | string | Authentication method (admin/superuser only) |
| bootAtStart | bool | Auto-start on system boot | | bootAtStart | bool | Auto-start on system boot |
| runUser | string | User to run the frpc instance as | | runUser | string | User to run the frpc instance as |
| configPath | string | Path to the configuration file | | configPath | string | Path to the configuration file |
| createdAt | string | Instance creation time (ISO 8601 format) | | createdAt | string | Instance creation time (ISO 8601 format) |
| createdBy | string | Username of the user who created this instance | | createdBy | string | Username of the user who created this instance |
> Note: Visitor users do not see sensitive information (serverAddr, serverPort, auth_method). > Note: Visitor users do not see sensitive information (serverAddr, serverPort, auth.method).
--- ---

228
frpAct.go
View File

@@ -9,43 +9,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"super-frpc/postLog" "super-frpc/postLog"
"time" "time"
"github.com/BurntSushi/toml"
) )
type InstanceInfo struct {
Name string `json:"name"`
ServerAddr string `json:"serverAddr"`
ServerPort string `json:"serverPort"`
AuthMethod string `json:"auth_method"`
RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"`
}
type FrpcProxyInfo struct {
Name string `json:"name"`
Type string `json:"type"`
LocalIP string `json:"local_ip"`
LocalPort string `json:"local_port"`
RemotePort string `json:"remote_port"`
}
type CreateInstanceRequest struct {
InstanceInfo InstanceInfo `json:"instanceInfo"`
BootAtStart bool `json:"bootAtStart"`
RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"`
}
type FrpcConfig struct {
Common map[string]interface{} `toml:"common"`
Proxies []map[string]interface{} `toml:"proxies"`
Additional map[string]interface{} `toml:"-"`
}
var frpcDB *sql.DB var frpcDB *sql.DB
func CloseFrpcDatabase() error { func CloseFrpcDatabase() error {
@@ -165,9 +132,6 @@ func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) {
instance := FrpcInstance{ instance := FrpcInstance{
UserID: userID, UserID: userID,
Name: req.InstanceInfo.Name, Name: req.InstanceInfo.Name,
ServerAddr: req.InstanceInfo.ServerAddr,
ServerPort: req.InstanceInfo.ServerPort,
AuthMethod: req.InstanceInfo.AuthMethod,
BootAtStart: req.BootAtStart, BootAtStart: req.BootAtStart,
RunUser: runUser, RunUser: runUser,
ConfigPath: configPath, ConfigPath: configPath,
@@ -180,7 +144,15 @@ func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if err := createBootService(user.Username, req.InstanceInfo.Name, configPath, runUser); err != nil { createdInstance, err := DBQueryFrpcInstance(userID, req.InstanceInfo.Name)
if err != nil {
os.Remove(configPath)
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to query created instance: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query created instance")
return
}
if err := createBootService(createdInstance.ID); err != nil {
frpcDB.Exec("DELETE FROM frpcInstances WHERE userID = ? AND name = ?", userID, req.InstanceInfo.Name) frpcDB.Exec("DELETE FROM frpcInstances WHERE userID = ? AND name = ?", userID, req.InstanceInfo.Name)
os.Remove(configPath) os.Remove(configPath)
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create boot service for instance %s: %v", req.InstanceInfo.Name, err)) postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create boot service for instance %s: %v", req.InstanceInfo.Name, err))
@@ -189,7 +161,7 @@ func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) {
} }
if req.BootAtStart { if req.BootAtStart {
if err := setBootAtStart(user.Username, req.InstanceInfo.Name); err != nil { if err := setBootAtStart(createdInstance.ID); err != nil {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to set boot at start for instance %s: %v", req.InstanceInfo.Name, err)) postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to set boot at start for instance %s: %v", req.InstanceInfo.Name, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to set boot at start") SendErrorResponse(w, http.StatusInternalServerError, "Failed to set boot at start")
return return
@@ -250,15 +222,7 @@ func DeleteInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := GetUserByID(userID) instance, err := DBQueryFrpcInstanceByID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
return
}
var instance FrpcInstance
instance, err = DBQueryFrpcInstanceByID(instanceID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
SendErrorResponse(w, http.StatusNotFound, "Instance not found") SendErrorResponse(w, http.StatusNotFound, "Instance not found")
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] User %d tried to delete a not existed instance: %d", userID, instanceID)) postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] User %d tried to delete a not existed instance: %d", userID, instanceID))
@@ -270,7 +234,7 @@ func DeleteInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if err := removeBootService(user.Username, instance.Name); err != nil { if err := removeBootService(instanceID); err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to remove boot service for instance %s: %v", instance.Name, err)) postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to remove boot service for instance %s: %v", instance.Name, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove boot service") SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove boot service")
return return
@@ -395,40 +359,40 @@ func ModifyInstanceHandler(w http.ResponseWriter, r *http.Request) {
func handleConfigFileModify(w http.ResponseWriter, instance FrpcInstance, modifiedData map[string]interface{}, username string) { func handleConfigFileModify(w http.ResponseWriter, instance FrpcInstance, modifiedData map[string]interface{}, username string) {
configPath := instance.ConfigPath configPath := instance.ConfigPath
configContent, err := os.ReadFile(configPath) for key, value := range modifiedData {
if err != nil { configKey := key
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to read config file %s: %v", configPath, err)) if key == "auth_method" {
SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file") configKey = "auth.method"
return }
if key == "auth_token" {
configKey = "auth.token"
} }
updatedConfig, err := updateCommonSection(string(configContent), modifiedData) var configValue string
if err != nil { if key == "serverPort" {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to update common section: %v", err)) switch v := value.(type) {
SendErrorResponse(w, http.StatusInternalServerError, "Failed to update config file") case float64:
configValue = strconv.Itoa(int(v))
case int:
configValue = strconv.Itoa(v)
case string:
var intVal int
if _, err := fmt.Sscanf(v, "%d", &intVal); err == nil {
configValue = strconv.Itoa(intVal)
} else {
configValue = v
}
default:
configValue = fmt.Sprintf("%v", value)
}
} else {
configValue = fmt.Sprintf("%v", value)
}
if err := setKeyText(configPath, configKey, "", configValue); err != nil {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to set key %s: %v", key, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to set key %s", key))
return return
} }
if err := os.WriteFile(configPath, []byte(updatedConfig), 0644); err != nil {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to write config file %s: %v", configPath, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file")
return
}
if v, ok := modifiedData["server_addr"].(string); ok && v != "" {
instance.ServerAddr = v
}
if v, ok := modifiedData["server_port"].(string); ok && v != "" {
instance.ServerPort = v
}
if v, ok := modifiedData["auth_method"].(string); ok && v != "" {
instance.AuthMethod = v
}
if err := DBUpdateFrpcInstance(instance); err != nil {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to update instance in database: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to update instance in database")
return
} }
SendSuccessResponse(w, "Config file modified successfully", map[string]interface{}{ SendSuccessResponse(w, "Config file modified successfully", map[string]interface{}{
@@ -439,24 +403,6 @@ func handleConfigFileModify(w http.ResponseWriter, instance FrpcInstance, modifi
postLog.Info(fmt.Sprintf("[handleConfigFileModify] Config file for instance %s modified successfully", instance.Name)) postLog.Info(fmt.Sprintf("[handleConfigFileModify] Config file for instance %s modified successfully", instance.Name))
} }
func updateCommonSection(configContent string, modifiedData map[string]interface{}) (string, error) {
var config FrpcConfig
if _, err := toml.Decode(configContent, &config); err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
for key, value := range modifiedData {
config.Common[key] = value
}
var buf strings.Builder
if err := toml.NewEncoder(&buf).Encode(config); err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return buf.String(), nil
}
func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance FrpcInstance, modifiedData map[string]interface{}, user *User) { func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance FrpcInstance, modifiedData map[string]interface{}, user *User) {
newName := instance.Name newName := instance.Name
newRunUser := instance.RunUser newRunUser := instance.RunUser
@@ -512,12 +458,12 @@ func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance F
} }
if newBootAtStart { if newBootAtStart {
if err := removeBootAtStart(user.Username, instance.Name); err != nil { if err := removeBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove boot at start: %v", err)) postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove boot at start: %v", err))
bootServiceError = fmt.Sprintf("Failed to remove boot at start: %v", err) bootServiceError = fmt.Sprintf("Failed to remove boot at start: %v", err)
} }
instance.Name = newName instance.Name = newName
if err := setBootAtStart(user.Username, newName); err != nil { if err := setBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to set boot at start: %v", err)) postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to set boot at start: %v", err))
if bootServiceError != "" { if bootServiceError != "" {
bootServiceError += "; " bootServiceError += "; "
@@ -525,7 +471,7 @@ func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance F
bootServiceError += fmt.Sprintf("Failed to set boot at start: %v", err) bootServiceError += fmt.Sprintf("Failed to set boot at start: %v", err)
} }
} else { } else {
if err := removeBootAtStart(user.Username, instance.Name); err != nil { if err := removeBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove boot at start: %v", err)) postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove boot at start: %v", err))
bootServiceError = fmt.Sprintf("Failed to remove boot at start: %v", err) bootServiceError = fmt.Sprintf("Failed to remove boot at start: %v", err)
} }
@@ -546,30 +492,9 @@ func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance F
SendSuccessResponse(w, "System config modified successfully", data) SendSuccessResponse(w, "System config modified successfully", data)
} }
func generateFrpcConfig(info InstanceInfo) string {
config := FrpcConfig{
Common: make(map[string]interface{}),
}
config.Common["server_addr"] = info.ServerAddr
config.Common["server_port"] = info.ServerPort
config.Common["auth_method"] = info.AuthMethod
for key, value := range info.Additional {
config.Common[key] = value
}
var buf strings.Builder
if err := toml.NewEncoder(&buf).Encode(config); err != nil {
return ""
}
return buf.String()
}
func GetUserInstances(userID int) ([]FrpcInstance, error) { func GetUserInstances(userID int) ([]FrpcInstance, error) {
rows, err := frpcDB.Query(` rows, err := frpcDB.Query(`
SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt SELECT id, userID, name, bootAtStart, runUser, configPath, createdAt
FROM frpcInstances WHERE userID = ? FROM frpcInstances WHERE userID = ?
`, userID) `, userID)
if err != nil { if err != nil {
@@ -582,8 +507,7 @@ func GetUserInstances(userID int) ([]FrpcInstance, error) {
var instance FrpcInstance var instance FrpcInstance
var createdAtStr string var createdAtStr string
if err := rows.Scan( if err := rows.Scan(
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort, &instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr,
&instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -642,9 +566,15 @@ func ListInstancesHandler(w http.ResponseWriter, r *http.Request) {
} }
if userType == "admin" || userType == "superuser" { if userType == "admin" || userType == "superuser" {
instanceData["serverAddr"] = inst.ServerAddr serverAddr, err := getKeyText(inst.ConfigPath, "serverAddr", "")
instanceData["serverPort"] = inst.ServerPort serverPort, err := getKeyText(inst.ConfigPath, "serverPort", "")
instanceData["auth_method"] = inst.AuthMethod authMethod, err := getKeyText(inst.ConfigPath, "auth.method", "")
if err != nil {
postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to read config for instance %d: %v", inst.ID, err))
}
instanceData["serverAddr"] = serverAddr
instanceData["serverPort"] = serverPort
instanceData["auth_method"] = authMethod
} }
instanceList[i] = instanceData instanceList[i] = instanceData
@@ -725,16 +655,14 @@ func StartInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := GetUserByID(instance.UserID) initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil { if err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get user info: %v", err)) postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info") SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return return
} }
initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name)
switch initType { switch initType {
case "windows": case "windows":
if err := StartWindowsService(serviceName); err != nil { if err := StartWindowsService(serviceName); err != nil {
@@ -841,16 +769,14 @@ func StopInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := GetUserByID(instance.UserID) initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil { if err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get user info: %v", err)) postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info") SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return return
} }
initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name)
switch initType { switch initType {
case "windows": case "windows":
if err := StopWindowsService(serviceName); err != nil { if err := StopWindowsService(serviceName); err != nil {
@@ -957,16 +883,14 @@ func RestartInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := GetUserByID(instance.UserID) initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil { if err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get user info: %v", err)) postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info") SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return return
} }
initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name)
switch initType { switch initType {
case "windows": case "windows":
if err := RestartWindowsService(serviceName); err != nil { if err := RestartWindowsService(serviceName); err != nil {
@@ -1047,16 +971,14 @@ func GetInstanceStatusHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := GetUserByID(instance.UserID)
if err != nil {
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
return
}
initType := GetInitSystem() initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name) serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return
}
responseData := map[string]interface{}{ responseData := map[string]interface{}{
"name": instance.Name, "name": instance.Name,
@@ -1065,7 +987,7 @@ func GetInstanceStatusHandler(w http.ResponseWriter, r *http.Request) {
"isRunning": false, "isRunning": false,
} }
isRunning := IsInstanceRunning(instance.Name) isRunning := IsInstanceRunning(instanceID)
responseData["isRunning"] = isRunning responseData["isRunning"] = isRunning
SendSuccessResponse(w, "Instance status retrieved successfully", responseData) SendSuccessResponse(w, "Instance status retrieved successfully", responseData)

View File

@@ -7,17 +7,11 @@ import (
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"strings"
"super-frpc/postLog" "super-frpc/postLog"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
type CreateProxyRequest struct {
InstanceID string `json:"instanceID"`
ProxyInfo FrpcProxyInfo `json:"proxyInfo"`
}
func CreateProxyHandler(w http.ResponseWriter, r *http.Request) { func CreateProxyHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
@@ -129,30 +123,6 @@ func CreateProxyHandler(w http.ResponseWriter, r *http.Request) {
postLog.Info(fmt.Sprintf("[CreateProxyHandler] Proxy %s created successfully for instance %d", proxyInfo.Name, instance.ID)) postLog.Info(fmt.Sprintf("[CreateProxyHandler] Proxy %s created successfully for instance %d", proxyInfo.Name, instance.ID))
} }
func addFrpcProxy(configContent string, info FrpcProxyInfo) (string, error) {
var config FrpcConfig
if _, err := toml.Decode(configContent, &config); err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
proxy := map[string]interface{}{
"name": info.Name,
"type": info.Type,
"localIP": info.LocalIP,
"localPort": info.LocalPort,
"remotePort": info.RemotePort,
}
config.Proxies = append(config.Proxies, proxy)
var buf strings.Builder
if err := toml.NewEncoder(&buf).Encode(config); err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return buf.String(), nil
}
func DeleteProxyHandler(w http.ResponseWriter, r *http.Request) { func DeleteProxyHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
@@ -249,36 +219,6 @@ func DeleteProxyHandler(w http.ResponseWriter, r *http.Request) {
postLog.Info(fmt.Sprintf("[DeleteProxyHandler] Proxy %s deleted successfully from instance %d", proxyName, instance.ID)) postLog.Info(fmt.Sprintf("[DeleteProxyHandler] Proxy %s deleted successfully from instance %d", proxyName, instance.ID))
} }
func removeFrpcProxy(configContent string, proxyName string) (string, error) {
var config FrpcConfig
if _, err := toml.Decode(configContent, &config); err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
var found bool
var newProxies []map[string]interface{}
for _, proxy := range config.Proxies {
if name, ok := proxy["name"].(string); ok && name == proxyName {
found = true
continue
}
newProxies = append(newProxies, proxy)
}
if !found {
return "", fmt.Errorf("proxy %s not found", proxyName)
}
config.Proxies = newProxies
var buf strings.Builder
if err := toml.NewEncoder(&buf).Encode(config); err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return buf.String(), nil
}
func ListProxiesHandler(w http.ResponseWriter, r *http.Request) { func ListProxiesHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet { if r.Method != http.MethodGet {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")

2
go.mod
View File

@@ -5,7 +5,6 @@ go 1.24.0
require ( require (
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
golang.org/x/sys v0.37.0
modernc.org/sqlite v1.46.1 modernc.org/sqlite v1.46.1
) )
@@ -16,6 +15,7 @@ require (
github.com/ncruces/go-strftime v1.0.0 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/sys v0.37.0 // indirect
modernc.org/libc v1.67.6 // indirect modernc.org/libc v1.67.6 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect

134
os.go
View File

@@ -44,28 +44,47 @@ func GetInitSystem() string {
return "unknown" return "unknown"
} }
func createBootService(username, instanceName, configPath, runUser string) error { func createBootService(instanceID int) error {
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err != nil {
return fmt.Errorf("failed to query frpc instance: %w", err)
}
initType := GetInitSystem() initType := GetInitSystem()
switch initType { switch initType {
case "systemd": case "systemd":
return createSystemdService(username, instanceName, configPath, runUser) return createSystemdService(instanceID, instance.ConfigPath, instance.RunUser)
case "init.d": case "init.d":
return createInitDService(username, instanceName, configPath, runUser) return createInitDService(instanceID, instance.ConfigPath, instance.RunUser)
case "windows": case "windows":
return createWindowsBootService(username, instanceName, configPath) return createWindowsBootService(instanceID, instance.ConfigPath)
default: default:
return fmt.Errorf("unsupported init system: %s", initType) return fmt.Errorf("unsupported init system: %s", initType)
} }
} }
func createSystemdService(username, instanceName, configPath, runUser string) error { func createSystemdService(instanceID int, configPath, runUser string) error {
frpcPath, err := GetFrpcPath() frpcPath, err := GetFrpcPath()
if err != nil { if err != nil {
return err return err
} }
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
return err
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err != nil {
return fmt.Errorf("failed to query frpc instance: %w", err)
}
user, err := GetUserByID(instance.UserID)
if err != nil {
return fmt.Errorf("failed to get user info: %w", err)
}
serviceContent := fmt.Sprintf(`[Unit] serviceContent := fmt.Sprintf(`[Unit]
Description=superfrpc_%s_%s Description=superfrpc_%s_%s
After=network.target After=network.target
@@ -79,7 +98,7 @@ RestartSec=5
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
`, username, instanceName, frpcPath, configPath, runUser) `, user.Username, instance.Name, frpcPath, configPath, runUser)
servicePath := filepath.Join("/etc/systemd/system", serviceName+".service") servicePath := filepath.Join("/etc/systemd/system", serviceName+".service")
if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil { if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil {
@@ -100,13 +119,26 @@ WantedBy=multi-user.target
return nil return nil
} }
func createInitDService(username, instanceName, configPath, runUser string) error { func createInitDService(instanceID int, configPath, runUser string) error {
frpcPath, err := GetFrpcPath() frpcPath, err := GetFrpcPath()
if err != nil { if err != nil {
return err return err
} }
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
return err
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err != nil {
return fmt.Errorf("failed to query frpc instance: %w", err)
}
user, err := GetUserByID(instance.UserID)
if err != nil {
return fmt.Errorf("failed to get user info: %w", err)
}
var serviceContent string var serviceContent string
runUserArg := "" runUserArg := ""
@@ -162,7 +194,7 @@ case "$1" in
esac esac
exit 0 exit 0
`, serviceName, username, instanceName, serviceName, frpcPath, configPath, runUserArg) `, serviceName, user.Username, instance.Name, serviceName, frpcPath, configPath, runUserArg)
} else { } else {
serviceContent = fmt.Sprintf(`#!/bin/sh serviceContent = fmt.Sprintf(`#!/bin/sh
@@ -213,7 +245,7 @@ case "$1" in
esac esac
exit 0 exit 0
`, serviceName, username, instanceName, serviceName, frpcPath, configPath) `, serviceName, user.Username, instance.Name, serviceName, frpcPath, configPath)
} }
servicePath := filepath.Join("/etc/init.d", serviceName) servicePath := filepath.Join("/etc/init.d", serviceName)
@@ -230,13 +262,17 @@ exit 0
return nil return nil
} }
func createWindowsBootService(username, instanceName, configPath string) error { func createWindowsBootService(instanceID int, configPath string) error {
frpcPath, err := GetFrpcPath() frpcPath, err := GetFrpcPath()
if err != nil { if err != nil {
return err return err
} }
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
return err
}
command := fmt.Sprintf("\"%s\" -c \"%s\"", frpcPath, configPath) command := fmt.Sprintf("\"%s\" -c \"%s\"", frpcPath, configPath)
cmd := exec.Command("sc", "create", serviceName, "binPath=", command, "start=", "auto") cmd := exec.Command("sc", "create", serviceName, "binPath=", command, "start=", "auto")
@@ -249,9 +285,12 @@ func createWindowsBootService(username, instanceName, configPath string) error {
return nil return nil
} }
func setBootAtStart(username, instanceName string) error { func setBootAtStart(instanceID int) error {
initType := GetInitSystem() initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
return err
}
switch initType { switch initType {
case "windows": case "windows":
@@ -280,11 +319,14 @@ func setBootAtStart(username, instanceName string) error {
} }
} }
func removeBootAtStart(username, instanceName string) error { func removeBootAtStart(instanceID int) error {
initType := GetInitSystem() initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
return err
}
switch initType{ switch initType {
case "windows": case "windows":
cmd := exec.Command("sc", "config", serviceName, "start=", "disabled") cmd := exec.Command("sc", "config", serviceName, "start=", "disabled")
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
@@ -311,10 +353,12 @@ func removeBootAtStart(username, instanceName string) error {
} }
} }
func removeBootService(username, instanceName string) error { func removeBootService(instanceID int) error {
initType := GetInitSystem() initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
serviceName := fmt.Sprintf("superfrpc_%s_%s", username, instanceName) if err != nil {
return err
}
switch initType { switch initType {
case "systemd": case "systemd":
@@ -347,11 +391,23 @@ func removeBootService(username, instanceName string) error {
return nil return nil
} }
func IsInstanceRunning(instanceName string) bool { func IsInstanceRunning(instanceID int) bool {
initType := GetInitSystem() initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to get service name: %v", err))
return false
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[IsInstanceRunning] Failed to query instance: %v", err))
return false
}
switch initType { switch initType {
case "windows": case "windows":
cmd := exec.Command("sc", "query", instanceName) cmd := exec.Command("sc", "query", serviceName)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
postLog.Debug(fmt.Sprintf("[IsInstanceRunning] Sc query output: %s", output)) postLog.Debug(fmt.Sprintf("[IsInstanceRunning] Sc query output: %s", output))
@@ -362,15 +418,15 @@ func IsInstanceRunning(instanceName string) bool {
outputStr := string(output) outputStr := string(output)
if strings.Contains(outputStr, "RUNNING") { if strings.Contains(outputStr, "RUNNING") {
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Windows service %s is running", instanceName)) postLog.Info(fmt.Sprintf("[IsInstanceRunning] Windows service %s is running", instance.Name))
return true return true
} else { } else {
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Windows service %s is stopped", instanceName)) postLog.Info(fmt.Sprintf("[IsInstanceRunning] Windows service %s is stopped", instance.Name))
return false return false
} }
case "systemd": case "systemd":
cmd := exec.Command("systemctl", "is-active", instanceName) cmd := exec.Command("systemctl", "is-active", serviceName)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@@ -380,17 +436,17 @@ func IsInstanceRunning(instanceName string) bool {
status := strings.TrimSpace(string(output)) status := strings.TrimSpace(string(output))
if status == "active" { if status == "active" {
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is running", instanceName)) postLog.Info(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is running", serviceName))
return true return true
} else { } else {
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is stopped", instanceName)) postLog.Info(fmt.Sprintf("[IsInstanceRunning] Systemd service %s is stopped", serviceName))
return false return false
} }
case "init.d": case "init.d":
servicePath := fmt.Sprintf("/etc/init.d/%s", instanceName) servicePath := fmt.Sprintf("/etc/init.d/%s", serviceName)
if _, err := os.Stat(servicePath); err != nil { if _, err := os.Stat(servicePath); err != nil {
postLog.Info(fmt.Sprintf("[IsInstanceRunning] Init.d service %s not found", instanceName)) postLog.Info(fmt.Sprintf("[IsInstanceRunning] Init.d service %s not found", serviceName))
return false return false
} }
@@ -415,11 +471,17 @@ func IsInstanceRunning(instanceName string) bool {
} }
} }
func GetInstancePid(instanceName string) int { func GetInstancePid(instanceID int) int {
initType := GetInitSystem() initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get service name: %v", err))
return 0
}
switch initType { switch initType {
case "windows": case "windows":
cmd := exec.Command("sc", "query", instanceName, "info") cmd := exec.Command("sc", "query", serviceName, "info")
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get Windows service info: %s, output: %s", err, output)) postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get Windows service info: %s, output: %s", err, output))
@@ -441,7 +503,7 @@ func GetInstancePid(instanceName string) int {
} }
return 0 return 0
case "systemd": case "systemd":
cmd := exec.Command("systemctl", "show", instanceName, "--property", "MainPID") cmd := exec.Command("systemctl", "show", serviceName, "--property", "MainPID")
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get PID from systemd: %s, output: %s", err, output)) postLog.Error(fmt.Sprintf("[GetInstancePid] Failed to get PID from systemd: %s, output: %s", err, output))
@@ -460,9 +522,9 @@ func GetInstancePid(instanceName string) int {
} }
return 0 return 0
case "init.d": case "init.d":
servicePath := fmt.Sprintf("/etc/init.d/%s", instanceName) servicePath := fmt.Sprintf("/etc/init.d/%s", serviceName)
if _, err := os.Stat(servicePath); err != nil { if _, err := os.Stat(servicePath); err != nil {
postLog.Error(fmt.Sprintf("[GetInstancePid] Init.d service %s not found", instanceName)) postLog.Error(fmt.Sprintf("[GetInstancePid] Init.d service %s not found", serviceName))
return 0 return 0
} }
cmd := exec.Command(servicePath, "status") cmd := exec.Command(servicePath, "status")
@@ -483,7 +545,7 @@ func GetInstancePid(instanceName string) int {
} }
} }
} }
pidFile := fmt.Sprintf("/var/run/%s.pid", instanceName) pidFile := fmt.Sprintf("/var/run/%s.pid", serviceName)
if content, err := os.ReadFile(pidFile); err == nil { if content, err := os.ReadFile(pidFile); err == nil {
pidStr := strings.TrimSpace(string(content)) pidStr := strings.TrimSpace(string(content))
pid, err := strconv.Atoi(pidStr) pid, err := strconv.Atoi(pidStr)