Files
backend/frpAct.go
NanamiAdmin 25f88249c2 refactor(api): simplify instance listing response and add running status
Remove sensitive information from instance listing response for all users and add isRunning field
Fix backend to prevent starting already running frpc instances
2026-03-25 21:50:52 +08:00

975 lines
34 KiB
Go

package main
import (
"database/sql"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"super-frpc/postLog"
"time"
)
var frpcDB *sql.DB
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")
postLog.Debug(fmt.Sprintf("[CreateInstanceHandler] Invalid request method: %s", r.Method))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to read request body: %v", err))
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 {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to unmarshal request body: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
return
}
bootAtStart := false
if bas, ok := reqMap["bootAtStart"]; ok {
switch v := bas.(type) {
case bool:
bootAtStart = v
case string:
if v == "true" {
bootAtStart = true
}
}
}
instanceInfoMap, ok := reqMap["instanceInfo"].(map[string]interface{})
if !ok {
SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceInfo format")
return
}
instanceInfo := InstanceInfo{
Name: getStringFromMap(instanceInfoMap, "name"),
ServerAddr: getStringFromMap(instanceInfoMap, "serverAddr"),
ServerPort: getStringFromMap(instanceInfoMap, "serverPort"),
AuthMethod: getStringFromMap(instanceInfoMap, "auth_method"),
RunUser: getStringFromMap(reqMap, "runUser"),
}
if additional, ok := reqMap["additionalProperties"].(map[string]interface{}); ok {
instanceInfo.Additional = additional
}
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to validate request header: %v", err))
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
return
}
req := CreateInstanceRequest{
InstanceInfo: instanceInfo,
BootAtStart: bootAtStart,
RunUser: instanceInfo.RunUser,
Additional: instanceInfo.Additional,
}
if err := CheckPermission(userID, "superuser", "admin"); err != nil {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to check permission: %v", err))
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 {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
return
}
configDir, err := GetConfigDir()
if err != nil {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to get config directory: %v", err))
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 {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create config file %s: %v", configPath, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to create config file")
return
}
instance := FrpcInstance{
UserID: userID,
Name: req.InstanceInfo.Name,
BootAtStart: req.BootAtStart,
RunUser: runUser,
ConfigPath: configPath,
}
if err := DBAddFrpcInstance(instance); err != nil {
os.Remove(configPath)
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to save instance %s to database: %v", req.InstanceInfo.Name, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to save instance to database")
return
}
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)
os.Remove(configPath)
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create boot service for instance %s: %v", req.InstanceInfo.Name, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to create boot service")
return
}
if req.BootAtStart {
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))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to set boot at start")
return
}
}
SendSuccessResponse(w, "Instance created successfully", map[string]interface{}{
"name": req.InstanceInfo.Name,
"configPath": configPath,
"bootAtStart": req.BootAtStart,
})
postLog.Info(fmt.Sprintf("[CreateInstanceHandler] Instance %s created successfully: configPath=%s, bootAtStart=%v, runUser=%s, additionalProperties=%v", req.InstanceInfo.Name, configPath, req.BootAtStart, runUser, req.Additional))
}
func DeleteInstanceHandler(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 {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to read request body: %v", err))
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 {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to unmarshal request body: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
return
}
instanceIDStr := getStringFromMap(reqMap, "instanceID")
if instanceIDStr == "" {
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
return
}
instanceID, err := strconv.Atoi(instanceIDStr)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format")
return
}
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to validate request header: %v", err))
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
return
}
if err := CheckPermission(userID, "superuser", "admin"); err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to check permission: %v", err))
SendErrorResponse(w, http.StatusForbidden, err.Error())
return
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err == sql.ErrNoRows {
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] User %d tried to delete a not existed instance: %d", userID, instanceID))
return
}
if err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to query instance: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
return
}
if err := removeBootService(instanceID); err != nil {
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")
return
}
if _, err := os.Stat(instance.ConfigPath); err == nil {
if err := os.Remove(instance.ConfigPath); err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to remove config file %s: %v", instance.ConfigPath, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove config file")
return
}
}
if err := DBRemoveFrpcInstanceByID(instanceID); err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to delete instance %d from database: %v", instanceID, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to delete instance from database")
return
}
SendSuccessResponse(w, "Instance deleted successfully", map[string]interface{}{
"id": instanceID,
})
postLog.Info(fmt.Sprintf("[DeleteInstanceHandler] Instance %d deleted successfully", instanceID))
}
func ModifyInstanceHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Invalid request method: %s", r.Method))
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to read request body: %v", err))
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 {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to unmarshal request body: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
return
}
instanceIDStr := getStringFromMap(reqMap, "instanceID")
if instanceIDStr == "" {
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
return
}
instanceID, err := strconv.Atoi(instanceIDStr)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format")
return
}
modifyType := getStringFromMap(reqMap, "type")
if modifyType == "" {
SendErrorResponse(w, http.StatusBadRequest, "type is required")
return
}
if modifyType != "configFile" && modifyType != "systemConfig" {
SendErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("Unknown modify type %s", modifyType))
return
}
modifiedData, ok := reqMap["modifiedData"].(map[string]interface{})
if !ok || modifiedData == nil {
SendErrorResponse(w, http.StatusBadRequest, "modifiedData is required and must be an object")
return
}
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to validate request header: %v", err))
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
return
}
if err := CheckPermission(userID, "superuser", "admin"); err != nil {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to check permission: %v", err))
SendErrorResponse(w, http.StatusForbidden, err.Error())
return
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err == sql.ErrNoRows {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] User %d tried to modify a not existed instance: %d", userID, instanceID))
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
return
}
if err != nil {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to query instance: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
return
}
if instance.UserID != userID {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] User %d does not have permission to modify instance %d", userID, instanceID))
SendErrorResponse(w, http.StatusForbidden, "Permission denied")
return
}
user, err := GetUserByID(instance.UserID)
if err != nil {
postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
return
}
if modifyType == "configFile" {
handleConfigFileModify(w, instance, modifiedData, user.Username)
} else {
handleSystemConfigModify(w, r, instance, modifiedData, user)
}
}
func handleConfigFileModify(w http.ResponseWriter, instance FrpcInstance, modifiedData map[string]interface{}, username string) {
configPath := instance.ConfigPath
for key, value := range modifiedData {
configKey := key
if key == "auth_method" {
configKey = "auth.method"
}
if key == "auth_token" {
configKey = "auth.token"
}
var configValue string
if key == "serverPort" {
switch v := value.(type) {
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
}
}
SendSuccessResponse(w, "Config file modified successfully", map[string]interface{}{
"instanceName": instance.Name,
"instanceID": instance.ID,
"configPath": configPath,
})
postLog.Info(fmt.Sprintf("[handleConfigFileModify] Config file for instance %s modified successfully", instance.Name))
}
func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance FrpcInstance, modifiedData map[string]interface{}, user *User) {
newName := instance.Name
newRunUser := instance.RunUser
newBootAtStart := instance.BootAtStart
var bootServiceError string
if v, ok := modifiedData["name"].(string); ok && v != "" {
newName = v
}
if v, ok := modifiedData["runUser"].(string); ok {
newRunUser = v
}
if v, ok := modifiedData["bootAtStart"].(bool); ok {
newBootAtStart = v
}
oldConfigPath := instance.ConfigPath
var newConfigPath string
if newName != instance.Name || newRunUser != instance.RunUser {
configDir, err := GetConfigDir()
if err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to get config directory: %v", err))
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 {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to rename config file %s to %s: %v", oldConfigPath, newConfigPath, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to rename config file")
return
}
}
}
} else {
newConfigPath = oldConfigPath
}
instance.RunUser = newRunUser
instance.BootAtStart = newBootAtStart
instance.ConfigPath = newConfigPath
// Update instance new name will be processed in boot service creation or removal
if err := DBUpdateFrpcInstance(instance); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to update instance in database: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to update instance in database")
return
}
if newBootAtStart {
if err := removeBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove boot at start: %v", err))
bootServiceError = fmt.Sprintf("Failed to remove boot at start: %v", err)
}
instance.Name = newName
if err := setBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to set boot at start: %v", err))
if bootServiceError != "" {
bootServiceError += "; "
}
bootServiceError += fmt.Sprintf("Failed to set boot at start: %v", err)
}
} else {
if err := removeBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] 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
data := map[string]interface{}{
"instanceName": newName,
"instanceID": instance.ID,
"configPath": newConfigPath,
"bootAtStart": newBootAtStart,
"runUser": newRunUser,
}
if bootServiceError != "" {
data["bootServiceError"] = bootServiceError
}
SendSuccessResponse(w, "System config modified successfully", data)
}
func GetUserInstances(userID int) ([]FrpcInstance, error) {
rows, err := frpcDB.Query(`
SELECT id, userID, name, 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.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
}
func getStringFromMap(m map[string]interface{}, key string) string {
if v, ok := m[key].(string); ok {
return v
}
return ""
}
func ListInstancesHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
postLog.Debug(fmt.Sprintf("[ListInstancesHandler] Invalid request method: %s", r.Method))
return
}
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to validate request header: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
return
}
instances, err := GetUserInstances(userID)
if err != nil {
postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to get user instances: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get instances")
return
}
instanceList := make([]map[string]interface{}, len(instances))
for i, inst := range instances {
instanceData := map[string]interface{}{
"instanceID": inst.ID,
"name": inst.Name,
"createdAt": inst.CreatedAt,
"createdBy": inst.CreatedBy,
"isRunning": IsInstanceRunning(inst.ID),
}
instanceList[i] = instanceData
}
SendSuccessResponse(w, "Instances retrieved successfully", instanceList)
postLog.Info(fmt.Sprintf("[ListInstancesHandler] Retrieved %d instances for user %d", len(instances), userID))
}
func StartInstanceHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
postLog.Debug(fmt.Sprintf("[StartInstanceHandler] Invalid request method: %s", r.Method))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to read request body: %v", err))
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 {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to unmarshal request body: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
return
}
instanceIDStr := getStringFromMap(reqMap, "instanceID")
if instanceIDStr == "" {
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
return
}
instanceID, err := strconv.Atoi(instanceIDStr)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format")
return
}
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to validate request header: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
return
}
userType, err := GetUserType(userID)
if err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get user type: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
return
}
if userType != "admin" && userType != "superuser" {
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Permission Denied for user %d (type: %s)", userID, userType))
return
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err == sql.ErrNoRows {
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
postLog.Error(fmt.Sprintf("[StartInstanceHandler] User %d tried to start a not existed instance: %d", userID, instanceID))
return
}
if err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to query instance: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
return
}
if instance.UserID != userID {
SendErrorResponse(w, http.StatusForbidden, "Instance not found")
postLog.Error(fmt.Sprintf("[StartInstanceHandler] User %d tried to start instance %d that does not belong to them", userID, instanceID))
return
}
initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return
}
switch initType {
case "windows":
if err := StartWindowsService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start Windows service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start Windows service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[StartInstanceHandler] Windows service %s started successfully", serviceName))
SendSuccessResponse(w, "Instance started successfully", nil)
case "systemd":
if err := StartSystemdService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start systemd service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start systemd service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[StartInstanceHandler] Systemd service %s started successfully", serviceName))
SendSuccessResponse(w, "Instance started successfully", nil)
case "init.d":
if err := StartInitDService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start init.d service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start init.d service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[StartInstanceHandler] Init.d service %s started successfully", serviceName))
SendSuccessResponse(w, "Instance started successfully", nil)
default:
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Unsupported init system: %s", initType))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType))
return
}
}
func StopInstanceHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
postLog.Debug(fmt.Sprintf("[StopInstanceHandler] Invalid request method: %s", r.Method))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to read request body: %v", err))
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 {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to unmarshal request body: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
return
}
instanceIDStr := getStringFromMap(reqMap, "instanceID")
if instanceIDStr == "" {
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
return
}
instanceID, err := strconv.Atoi(instanceIDStr)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format")
return
}
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to validate request header: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
return
}
userType, err := GetUserType(userID)
if err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get user type: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
return
}
if userType != "admin" && userType != "superuser" {
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Permission Denied for user %d (type: %s)", userID, userType))
return
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err == sql.ErrNoRows {
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
postLog.Error(fmt.Sprintf("[StopInstanceHandler] User %d tried to stop a not existed instance: %d", userID, instanceID))
return
}
if err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to query instance: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
return
}
if instance.UserID != userID {
SendErrorResponse(w, http.StatusForbidden, "Instance not found")
postLog.Error(fmt.Sprintf("[StopInstanceHandler] User %d tried to stop instance %d that does not belong to them", userID, instanceID))
return
}
initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return
}
switch initType {
case "windows":
if err := StopWindowsService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop Windows service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop Windows service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[StopInstanceHandler] Windows service %s stopped successfully", serviceName))
SendSuccessResponse(w, "Instance stopped successfully", nil)
case "systemd":
if err := StopSystemdService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop systemd service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop systemd service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[StopInstanceHandler] Systemd service %s stopped successfully", serviceName))
SendSuccessResponse(w, "Instance stopped successfully", nil)
case "init.d":
if err := StopInitDService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop init.d service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop init.d service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[StopInstanceHandler] Init.d service %s stopped successfully", serviceName))
SendSuccessResponse(w, "Instance stopped successfully", nil)
default:
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Unsupported init system: %s", initType))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType))
return
}
}
func RestartInstanceHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
postLog.Debug(fmt.Sprintf("[RestartInstanceHandler] Invalid request method: %s", r.Method))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to read request body: %v", err))
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 {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to unmarshal request body: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
return
}
instanceIDStr := getStringFromMap(reqMap, "instanceID")
if instanceIDStr == "" {
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
return
}
instanceID, err := strconv.Atoi(instanceIDStr)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format")
return
}
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to validate request header: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
return
}
userType, err := GetUserType(userID)
if err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get user type: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
return
}
if userType != "admin" && userType != "superuser" {
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Permission Denied for user %d (type: %s)", userID, userType))
return
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err == sql.ErrNoRows {
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] User %d tried to restart a not existed instance: %d", userID, instanceID))
return
}
if err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to query instance: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
return
}
if instance.UserID != userID {
SendErrorResponse(w, http.StatusForbidden, "Instance not found")
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] User %d tried to restart instance %d that does not belong to them", userID, instanceID))
return
}
initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return
}
switch initType {
case "windows":
if err := RestartWindowsService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart Windows service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart Windows service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[RestartInstanceHandler] Windows service %s restarted successfully", serviceName))
SendSuccessResponse(w, "Instance restarted successfully", nil)
case "systemd":
if err := RestartSystemdService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart systemd service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart systemd service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[RestartInstanceHandler] Systemd service %s restarted successfully", serviceName))
SendSuccessResponse(w, "Instance restarted successfully", nil)
case "init.d":
if err := RestartInitDService(serviceName); err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart init.d service %s: %v", serviceName, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart init.d service: %v", err))
return
}
postLog.Info(fmt.Sprintf("[RestartInstanceHandler] Init.d service %s restarted successfully", serviceName))
SendSuccessResponse(w, "Instance restarted successfully", nil)
default:
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Unsupported init system: %s", initType))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType))
return
}
}
func GetInstanceStatusHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
postLog.Debug(fmt.Sprintf("[GetInstanceStatusHandler] Invalid request method: %s", r.Method))
return
}
queryParams := r.URL.Query()
instanceIDStr := queryParams.Get("instanceID")
if instanceIDStr == "" {
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
return
}
instanceID, err := strconv.Atoi(instanceIDStr)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format")
return
}
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to validate request header: %v", err))
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
return
}
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err == sql.ErrNoRows {
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] User %d tried to get status of a not existed instance: %d", userID, instanceID))
return
}
if err != nil {
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to query instance: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
return
}
if instance.UserID != userID {
SendErrorResponse(w, http.StatusForbidden, "Instance not found")
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] User %d tried to get status of instance %d that does not belong to them", userID, instanceID))
return
}
initType := GetInitSystem()
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{}{
"name": instance.Name,
"initSystem": initType,
"serviceName": serviceName,
"isRunning": false,
}
isRunning := IsInstanceRunning(instanceID)
responseData["isRunning"] = isRunning
SendSuccessResponse(w, "Instance status retrieved successfully", responseData)
postLog.Info(fmt.Sprintf("[GetInstanceStatusHandler] Retrieved status for instance %d (name: %s), isRunning: %v", instanceID, instance.Name, isRunning))
}