feat(watchdog): improve watchdog instance tracking

- Add Close() function to watchdog for graceful shutdown
- Update instance message format for better consistency
- Add watchdog status tracking in StatusInfo
- Include watchdog field in FrpcInstance struct and database
- Change port fields from string to int in FrpcProxyInfo
- Add auth_token support in InstanceInfo
This commit is contained in:
2026-04-05 22:44:53 +08:00
parent f006c2307a
commit 37c10f1c07
6 changed files with 75 additions and 31 deletions
+4 -2
View File
@@ -27,6 +27,7 @@ type InstanceInfo struct {
ServerAddr string `json:"serverAddr"` ServerAddr string `json:"serverAddr"`
ServerPort string `json:"serverPort"` ServerPort string `json:"serverPort"`
AuthMethod string `json:"auth_method"` AuthMethod string `json:"auth_method"`
AuthToken string `json:"auth_token"`
RunUser string `json:"runUser"` RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"` Additional map[string]interface{} `json:"additionalProperties"`
} }
@@ -35,8 +36,8 @@ type FrpcProxyInfo struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
LocalIP string `json:"local_ip"` LocalIP string `json:"local_ip"`
LocalPort string `json:"local_port"` LocalPort int `json:"local_port"`
RemotePort string `json:"remote_port"` RemotePort int `json:"remote_port"`
} }
type CreateInstanceRequest struct { type CreateInstanceRequest struct {
@@ -238,6 +239,7 @@ func generateFrpcConfig(info InstanceInfo) string {
config.Global["serverAddr"] = info.ServerAddr config.Global["serverAddr"] = info.ServerAddr
config.Global["serverPort"] = info.ServerPort config.Global["serverPort"] = info.ServerPort
config.Global["auth.method"] = info.AuthMethod config.Global["auth.method"] = info.AuthMethod
config.Global["auth.token"] = info.AuthToken
for key, value := range info.Additional { for key, value := range info.Additional {
config.Global[key] = value config.Global[key] = value
+13 -11
View File
@@ -36,6 +36,7 @@ type FrpcInstance struct {
ConfigPath string ConfigPath string
CreatedAt time.Time CreatedAt time.Time
CreatedBy string CreatedBy string
Watchdog int
} }
func InitDatabase(dbPath_data string, dbPath_log string) error { func InitDatabase(dbPath_data string, dbPath_log string) error {
@@ -276,6 +277,7 @@ func InitFrpcDatabase(dbPath string) error {
runUser TEXT NOT NULL DEFAULT 'root', runUser TEXT NOT NULL DEFAULT 'root',
configPath TEXT NOT NULL, configPath TEXT NOT NULL,
createdAt TEXT NOT NULL, createdAt TEXT NOT NULL,
watchdog INTEGER NOT NULL,
UNIQUE(userID, name) UNIQUE(userID, name)
); );
` `
@@ -359,7 +361,7 @@ func DBUpdateUser(userID int, username, passwd string) error {
return nil return nil
} }
func DBUpdateUserType (userID int, newType string) error { func DBUpdateUserType(userID int, newType string) error {
_, err := db.Exec("UPDATE userLogin SET type = ? WHERE userID = ?", newType, userID) _, err := db.Exec("UPDATE userLogin SET type = ? WHERE userID = ?", newType, userID)
if err != nil { if err != nil {
return fmt.Errorf("failed to update user type: %w", err) return fmt.Errorf("failed to update user type: %w", err)
@@ -368,8 +370,8 @@ func DBUpdateUserType (userID int, newType string) error {
} }
func DBAddFrpcInstance(instance FrpcInstance) error { func DBAddFrpcInstance(instance FrpcInstance) error {
_, err := frpcDB.Exec("INSERT INTO frpcInstances (userID, name, bootAtStart, runUser, configPath, createdAt) VALUES (?, ?, ?, ?, ?, ?)", _, err := frpcDB.Exec("INSERT INTO frpcInstances (userID, name, bootAtStart, runUser, configPath, createdAt, watchdog) VALUES (?, ?, ?, ?, ?, ?, ?)",
instance.UserID, instance.Name, 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), instance.Watchdog)
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)
} }
@@ -379,8 +381,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, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE id = ?", instanceID).Scan( err := frpcDB.QueryRow("SELECT id, userID, name, bootAtStart, runUser, configPath, createdAt, watchdog FROM frpcInstances WHERE id = ?", instanceID).Scan(
&instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr) &instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr, &instance.Watchdog)
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)
} }
@@ -391,8 +393,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, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE userID = ? AND name = ?", userID, instanceName).Scan( err := frpcDB.QueryRow("SELECT id, userID, name, bootAtStart, runUser, configPath, createdAt, watchdog FROM frpcInstances WHERE userID = ? AND name = ?", userID, instanceName).Scan(
&instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr) &instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr, &instance.Watchdog)
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)
} }
@@ -409,8 +411,8 @@ func DBRemoveFrpcInstanceByID(instanceID int) error {
} }
func DBUpdateFrpcInstance(instance FrpcInstance) error { func DBUpdateFrpcInstance(instance FrpcInstance) error {
_, err := frpcDB.Exec("UPDATE frpcInstances SET bootAtStart = ?, runUser = ?, configPath = ? WHERE id = ?", _, err := frpcDB.Exec("UPDATE frpcInstances SET bootAtStart = ?, runUser = ?, configPath = ?, watchdog = ? WHERE id = ?",
instance.BootAtStart, instance.RunUser, instance.ConfigPath, instance.ID) instance.BootAtStart, instance.RunUser, instance.ConfigPath, instance.Watchdog, 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)
} }
@@ -419,7 +421,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.bootAtStart, fi.runUser, fi.configPath, fi.createdAt, u.username SELECT fi.id, fi.userID, fi.name, fi.bootAtStart, fi.runUser, fi.configPath, fi.createdAt, fi.watchdog, u.username
FROM frpcInstances fi FROM frpcInstances fi
JOIN userLogin u ON fi.userID = u.userID JOIN userLogin u ON fi.userID = u.userID
`) `)
@@ -432,7 +434,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.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.Watchdog, &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)
+21 -6
View File
@@ -10,6 +10,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"super-frpc/postLog" "super-frpc/postLog"
"super-frpc/watchdog"
"time" "time"
) )
@@ -123,6 +124,7 @@ func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) {
BootAtStart: req.BootAtStart, BootAtStart: req.BootAtStart,
RunUser: runUser, RunUser: runUser,
ConfigPath: configPath, ConfigPath: configPath,
Watchdog: 0,
} }
if err := DBAddFrpcInstance(instance); err != nil { if err := DBAddFrpcInstance(instance); err != nil {
@@ -459,7 +461,7 @@ func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance F
func GetUserInstances(userID int) ([]FrpcInstance, error) { func GetUserInstances(userID int) ([]FrpcInstance, error) {
rows, err := frpcDB.Query(` rows, err := frpcDB.Query(`
SELECT id, userID, name, bootAtStart, runUser, configPath, createdAt SELECT id, userID, name, bootAtStart, runUser, configPath, createdAt, watchdog
FROM frpcInstances WHERE userID = ? FROM frpcInstances WHERE userID = ?
`, userID) `, userID)
if err != nil { if err != nil {
@@ -472,7 +474,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.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr, &instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr, &instance.Watchdog,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -490,6 +492,16 @@ func getStringFromMap(m map[string]interface{}, key string) string {
return "" return ""
} }
func getNumFromMap(m map[string]interface{}, key string) int {
if v, ok := m[key].(int); ok {
return v
}
if v, ok := m[key].(float64); ok {
return int(v)
}
return 0
}
func ListInstancesHandler(w http.ResponseWriter, r *http.Request) { func ListInstancesHandler(w http.ResponseWriter, r *http.Request) {
userID, err := Auth(w, r, http.MethodGet) userID, err := Auth(w, r, http.MethodGet)
if err != nil { if err != nil {
@@ -583,7 +595,7 @@ func StartInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
switch initType { switch initType { // Lunch frpc instance by init system
case "windows": case "windows":
if err := StartWindowsService(serviceName); err != nil { if err := StartWindowsService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start Windows service %s: %v", serviceName, err)) postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start Windows service %s: %v", serviceName, err))
@@ -591,7 +603,6 @@ func StartInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
postLog.Info(fmt.Sprintf("[StartInstanceHandler] Windows service %s started successfully", serviceName)) postLog.Info(fmt.Sprintf("[StartInstanceHandler] Windows service %s started successfully", serviceName))
SendSuccessResponse(w, "Instance started successfully", nil)
case "systemd": case "systemd":
if err := StartSystemdService(serviceName); err != nil { if err := StartSystemdService(serviceName); err != nil {
@@ -600,7 +611,6 @@ func StartInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
postLog.Info(fmt.Sprintf("[StartInstanceHandler] Systemd service %s started successfully", serviceName)) postLog.Info(fmt.Sprintf("[StartInstanceHandler] Systemd service %s started successfully", serviceName))
SendSuccessResponse(w, "Instance started successfully", nil)
case "init.d": case "init.d":
if err := StartInitDService(serviceName); err != nil { if err := StartInitDService(serviceName); err != nil {
@@ -609,13 +619,18 @@ func StartInstanceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
postLog.Info(fmt.Sprintf("[StartInstanceHandler] Init.d service %s started successfully", serviceName)) postLog.Info(fmt.Sprintf("[StartInstanceHandler] Init.d service %s started successfully", serviceName))
SendSuccessResponse(w, "Instance started successfully", nil)
default: default:
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Unsupported init system: %s", initType)) postLog.Error(fmt.Sprintf("[StartInstanceHandler] Unsupported init system: %s", initType))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType))
return return
} }
if is.watchdogConnected {
watchdog.AddInstance(serviceName)
}
SendSuccessResponse(w, "Instance started successfully", nil)
} }
func StopInstanceHandler(w http.ResponseWriter, r *http.Request) { func StopInstanceHandler(w http.ResponseWriter, r *http.Request) {
+6 -6
View File
@@ -53,12 +53,12 @@ func CreateProxyHandler(w http.ResponseWriter, r *http.Request) {
Name: getStringFromMap(proxyInfoMap, "name"), Name: getStringFromMap(proxyInfoMap, "name"),
Type: getStringFromMap(proxyInfoMap, "type"), Type: getStringFromMap(proxyInfoMap, "type"),
LocalIP: getStringFromMap(proxyInfoMap, "localIP"), LocalIP: getStringFromMap(proxyInfoMap, "localIP"),
LocalPort: getStringFromMap(proxyInfoMap, "localPort"), LocalPort: getNumFromMap(proxyInfoMap, "localPort"),
RemotePort: getStringFromMap(proxyInfoMap, "remotePort"), RemotePort: getNumFromMap(proxyInfoMap, "remotePort"),
} }
if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" || if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" ||
proxyInfo.LocalPort == "" || proxyInfo.RemotePort == "" { proxyInfo.LocalPort == 0 || proxyInfo.RemotePort == 0 {
postLog.Error("[CreateProxyHandler] Missing required fields in proxyInfo") postLog.Error("[CreateProxyHandler] Missing required fields in proxyInfo")
SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo") SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo")
return return
@@ -148,12 +148,12 @@ func ModifyProxyHandler(w http.ResponseWriter, r *http.Request) {
Name: getStringFromMap(proxyInfoMap, "name"), Name: getStringFromMap(proxyInfoMap, "name"),
Type: getStringFromMap(proxyInfoMap, "type"), Type: getStringFromMap(proxyInfoMap, "type"),
LocalIP: getStringFromMap(proxyInfoMap, "localIP"), LocalIP: getStringFromMap(proxyInfoMap, "localIP"),
LocalPort: getStringFromMap(proxyInfoMap, "localPort"), LocalPort: getNumFromMap(proxyInfoMap, "localPort"),
RemotePort: getStringFromMap(proxyInfoMap, "remotePort"), RemotePort: getNumFromMap(proxyInfoMap, "remotePort"),
} }
if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" || if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" ||
proxyInfo.LocalPort == "" || proxyInfo.RemotePort == "" { proxyInfo.LocalPort == 0 || proxyInfo.RemotePort == 0 {
postLog.Error("[ModifyProxyHandler] Missing required fields in proxyInfo") postLog.Error("[ModifyProxyHandler] Missing required fields in proxyInfo")
SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo") SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo")
return return
+15 -4
View File
@@ -31,7 +31,8 @@ var softwareInfo SoftwareInfo = SoftwareInfo{
} }
type StatusInfo struct { type StatusInfo struct {
Status string ServerStatus string
WatchdogStatus string
} }
type Is struct { type Is struct {
@@ -84,7 +85,7 @@ func main() {
postLog.Info("Database initialized successfully") postLog.Info("Database initialized successfully")
if err := InitFrpcDatabase(*dbPath_data); err != nil { if err := InitFrpcDatabase(*dbPath_data); err != nil {
postLog.Warning(fmt.Sprintf("Failed to initialize frpc database: %v", err)) postLog.Fatal(fmt.Sprintf("Failed to initialize frpc database: %v", err))
} }
frpLogger.SetDatabase(db, frpcDB) frpLogger.SetDatabase(db, frpcDB)
@@ -154,15 +155,25 @@ func main() {
postLog.Error(fmt.Sprintf("Error closing frpc database: %v", err)) postLog.Error(fmt.Sprintf("Error closing frpc database: %v", err))
} }
watchdog.Close()
postLog.Info("Server stopped") postLog.Info("Server stopped")
} }
func GetStatusHandler(w http.ResponseWriter, r *http.Request) { func GetStatusHandler(w http.ResponseWriter, r *http.Request) {
statusInfo := StatusInfo{ statusInfo := StatusInfo{
Status: "Online", ServerStatus: "Offline",
WatchdogStatus: "Offline",
} }
if !is.online { if !is.online {
statusInfo.Status = "Offline" statusInfo.ServerStatus = "Offline"
} else {
statusInfo.ServerStatus = "Online"
}
if !is.watchdogConnected {
statusInfo.WatchdogStatus = "Offline"
} else {
statusInfo.WatchdogStatus = "Online"
} }
SendSuccessResponse(w, "getStatus", statusInfo) SendSuccessResponse(w, "getStatus", statusInfo)
} }
+16 -2
View File
@@ -9,7 +9,7 @@ func AddInstance(serviceName string) bool {
return false return false
} }
message := fmt.Sprintf("[addInstance] <serviceName>%s</serviceName>", serviceName) message := fmt.Sprintf("[instance.add] <serviceName>%s</serviceName>", serviceName)
response, err := sendMsg(message, 3) response, err := sendMsg(message, 3)
if err != nil { if err != nil {
return false return false
@@ -23,7 +23,7 @@ func RemoveInstance(serviceName string) bool {
return false return false
} }
message := fmt.Sprintf("[removeInstance] <serviceName>%s</serviceName>", serviceName) message := fmt.Sprintf("[instance.remove] <serviceName>%s</serviceName>", serviceName)
response, err := sendMsg(message, 3) response, err := sendMsg(message, 3)
if err != nil { if err != nil {
return false return false
@@ -31,3 +31,17 @@ func RemoveInstance(serviceName string) bool {
return response == "success" return response == "success"
} }
func Close() bool {
if !IsConnected() {
return false
}
message := "watchdog.shutdown"
response, err := sendMsg(message, 3)
if err != nil {
return false
}
return response == "success"
}