From 9ffb4263cf8ebcbcf2df45afae70b9098367c611 Mon Sep 17 00:00:00 2001 From: NanamiAdmin Date: Thu, 7 May 2026 21:07:16 +0800 Subject: [PATCH] feat(sys): add self-check functionality for system settings and frpc binary --- docs/api.md | 39 ++++++++++++++++++++++++++++++++ handlers/instance.go | 28 +++++++++++------------ router.go | 3 +++ sys/selfcheck.go | 53 ++++++++++++++++++++++++++++++++++++++++++++ utils/handlers.go | 6 +++++ 5 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 sys/selfcheck.go diff --git a/docs/api.md b/docs/api.md index 9159119..82e96dd 100644 --- a/docs/api.md +++ b/docs/api.md @@ -20,6 +20,7 @@ All API responses are returned in JSON format: ``` **Important Notes:** +- All endpoints are relative to the base URL `/api` - For all requests: Authentication token and timestamp are sent in HTTP headers (prefixed with `X-`) - For **POST** requests: Other data is sent in the request body as JSON - For **GET** requests: All data is sent in HTTP headers (prefixed with `X-`) @@ -90,6 +91,44 @@ Get software version and build information. --- +## Self Check + +**Endpoint:** `/system/selfCheck` +**Method:** POST +**Content-Type:** application/json +**Permission Level:** Visitor + +Check the system configuration and frpc binary. + +**Request Headers:** +``` +X-Token: your_token +X-Timestamp: 1704067200000 +``` +| Header | Type | Required | Description | +|--------|------|----------|-------------| +| X-Token | string | Yes | Authentication token | +| X-Timestamp | int64 | Yes | Client timestamp in milliseconds | + +**Response (Success):** +```json +{ + "success": true, + "message": "Self check passed" +} +``` +> If there were any errors, the response will be in error format. + +**Response (Error):** +```json +{ + "success": false, + "message": "error message" +} +``` + +--- + ## Register User **Endpoint:** `/register` diff --git a/handlers/instance.go b/handlers/instance.go index e364639..6906c2e 100644 --- a/handlers/instance.go +++ b/handlers/instance.go @@ -499,16 +499,16 @@ func getNumFromMap(m map[string]interface{}, key string) int { } func ListInstancesHandler(w http.ResponseWriter, r *http.Request) { - userID, err := utils.Auth(w, r, http.MethodGet) + userID, err := utils.Auth(w, r, http.MethodGet, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[ListInstancesHandler] Auth failed: %v", err)) return } - instances, err := GetUserInstances(userID) + instances, err := database.DBListFrpcInstances() if err != nil { - postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to get user instances: %v", err)) + postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to get all instances: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get instances") return } @@ -650,14 +650,14 @@ func StartInstanceHandler(w http.ResponseWriter, r *http.Request) { if !watchdog.AddInstance(sysName) { postLog.Warning(fmt.Sprintf("[StartInstanceHandler] Failed to add watchdog instance %s", sysName)) utils.SendSuccessResponse(w, "Instance started successfully but watchdog instance add failed", map[string]interface{}{ - "instanceID": instanceID, - "sysName": sysName, + "instanceID": instanceID, + "sysName": sysName, }) return } else { utils.SendSuccessResponse(w, "Instance started successfully", map[string]interface{}{ - "instanceID": instanceID, - "sysName": sysName, + "instanceID": instanceID, + "sysName": sysName, }) } } @@ -767,13 +767,13 @@ func StopInstanceHandler(w http.ResponseWriter, r *http.Request) { if !watchdog.RemoveInstance(sysName) { postLog.Warning(fmt.Sprintf("[StopInstanceHandler] Failed to remove watchdog instance %s", sysName)) utils.SendSuccessResponse(w, "Instance stopped successfully but watchdog instance remove failed", map[string]interface{}{ - "instanceID": instanceID, - "sysName": sysName, + "instanceID": instanceID, + "sysName": sysName, }) } else { utils.SendSuccessResponse(w, "Instance stopped successfully", map[string]interface{}{ - "instanceID": instanceID, - "sysName": sysName, + "instanceID": instanceID, + "sysName": sysName, }) } } @@ -883,8 +883,8 @@ func RestartInstanceHandler(w http.ResponseWriter, r *http.Request) { } utils.SendSuccessResponse(w, "Instance restarted successfully", map[string]interface{}{ - "instanceID": instanceID, - "sysName": sysName, + "instanceID": instanceID, + "sysName": sysName, }) postLog.Info(fmt.Sprintf("[RestartInstanceHandler] Instance %d restarted successfully", instanceID)) } @@ -1005,7 +1005,7 @@ func GetInstanceInfoHandler(w http.ResponseWriter, r *http.Request) { response := map[string]interface{}{ "name": instance.Name, - "sysName": sysName, + "sysName": sysName, "createdAt": instance.CreatedAt.Format(time.RFC3339), "createdBy": instance.CreatedBy, "isRunning": isRunning, diff --git a/router.go b/router.go index dc1996d..d9a5d11 100644 --- a/router.go +++ b/router.go @@ -9,6 +9,7 @@ import ( "super-frpc/session" "super-frpc/utils" "super-frpc/web" + "super-frpc/sys" ) func setupRoutes() { @@ -16,6 +17,8 @@ func setupRoutes() { http.HandleFunc("/api/system/getLogs", systemLogHandler.Handle) http.HandleFunc("/api/system/getStatus", GetStatusHandler) http.HandleFunc("/api/system/getSoftwareInfo", GetSoftwareInfoHandler) + http.HandleFunc("/api/system/selfCheck", sys.SelfCheckHandler) + http.HandleFunc("/api/system/settings/get", handlers.GetSettingsHandler) http.HandleFunc("/api/system/settings/set", handlers.SetSettingsHandler) diff --git a/sys/selfcheck.go b/sys/selfcheck.go new file mode 100644 index 0000000..0ef127c --- /dev/null +++ b/sys/selfcheck.go @@ -0,0 +1,53 @@ +package sys + +import ( + "net/http" + "fmt" + "super-frpc/global" + "super-frpc/utils" + "super-frpc/postLog" +) + +func SelfCheckHandler(w http.ResponseWriter, r *http.Request) { + userID, err := utils.Auth(w, r, http.MethodGet, "visitor", "superuser", "admin") + if err != nil { + utils.SendErrorResponse(w, http.StatusUnauthorized, "invalid token or timestamp") + postLog.Warning(fmt.Sprintf("[SelfCheckHandler] Auth failed: %v, userID: %d", err, userID)) + return + } + err = SettingsSelfCheck() + if err != nil { + utils.SendErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + err = FrpBinaryCheck() + if err != nil { + utils.SendErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + utils.SendSuccessResponse(w, "Self check passed", nil) + postLog.Info(fmt.Sprintf("[SelfCheckHandler] [%d] Self check passed", userID)) +} + +func SettingsSelfCheck() error { + if global.CurrentConfig.ListenAddr == "" { + return fmt.Errorf("listenAddr is not set") + } + if global.CurrentConfig.ListenPort == "0" { + return fmt.Errorf("listenPort is invalid") + } + if global.CurrentConfig.FrpcPath == "" { + return fmt.Errorf("frpcPath is not set") + } + if global.CurrentConfig.InstancePath == "" { + return fmt.Errorf("instancePath is not set") + } + return nil +} + +func FrpBinaryCheck() error { + if !utils.IsFileExist(global.CurrentConfig.FrpcPath) { + return fmt.Errorf("frpc binary is not exist") + } + return nil +} \ No newline at end of file diff --git a/utils/handlers.go b/utils/handlers.go index 1324e7f..429eec4 100644 --- a/utils/handlers.go +++ b/utils/handlers.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "strconv" + "os" "time" "strings" @@ -161,3 +162,8 @@ func GetCmdType(source string) string { func GetCmdParams(source string, param string) string { return GetTextMiddle(source, "<"+param+">", "") } + +func IsFileExist(filePath string) bool { + _, err := os.Stat(filePath) + return !os.IsNotExist(err) +} \ No newline at end of file