Files
DragonCoreSSH-NewWEB/server_config_api.go
2026-05-02 23:20:13 -03:00

125 lines
2.9 KiB
Go

package main
import (
"encoding/json"
"io"
"net/http"
"os"
"sync"
)
// Note: applyFullConfigReload is defined in hotreload.go
// ---------- Global config holder ----------
var (
globalCfgMu sync.RWMutex
globalCfg *Config
globalCfgPath string // set in main() from the -config flag
bannerMu sync.RWMutex
currentBannerText string
)
func setGlobalCfg(c *Config) {
globalCfgMu.Lock()
globalCfg = c
globalCfgMu.Unlock()
}
func getGlobalCfg() *Config {
globalCfgMu.RLock()
defer globalCfgMu.RUnlock()
return globalCfg
}
func setBannerText(s string) {
bannerMu.Lock()
currentBannerText = s
bannerMu.Unlock()
}
func getBannerText() string {
bannerMu.RLock()
defer bannerMu.RUnlock()
return currentBannerText
}
// ---------- HTTP handler ----------
// handleServerConfig dispatches GET (read) and POST (write) for /api/server/config.
func handleServerConfig(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
serverConfigGet(w, r)
case http.MethodPost:
serverConfigPost(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func serverConfigGet(w http.ResponseWriter, _ *http.Request) {
if globalCfgPath == "" {
http.Error(w, "config path not set", http.StatusInternalServerError)
return
}
data, err := os.ReadFile(globalCfgPath)
if err != nil {
http.Error(w, "failed to read config: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(data)
}
func serverConfigPost(w http.ResponseWriter, r *http.Request) {
if globalCfgPath == "" {
http.Error(w, "config path not set", http.StatusInternalServerError)
return
}
body, err := io.ReadAll(io.LimitReader(r.Body, 512*1024))
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
var newCfg Config
if err := json.Unmarshal(body, &newCfg); err != nil {
http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
return
}
if newCfg.Listen == "" {
http.Error(w, "listen address required", http.StatusBadRequest)
return
}
// Preserve file-based users array (not editable through the UI).
globalCfgMu.RLock()
if globalCfg != nil {
newCfg.Users = globalCfg.Users
}
globalCfgMu.RUnlock()
portWarnings := normalizeRuntimePorts(&newCfg)
out, err := json.MarshalIndent(newCfg, "", " ")
if err != nil {
http.Error(w, "marshal error", http.StatusInternalServerError)
return
}
if err := os.WriteFile(globalCfgPath, out, 0o644); err != nil {
http.Error(w, "failed to write config: "+err.Error(), http.StatusInternalServerError)
return
}
// Apply all changes live and return health checks to the panel.
report := applyFullConfigReload(&newCfg)
if len(portWarnings) > 0 {
report.Warnings = append(portWarnings, report.Warnings...)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(report)
}