Launch
This commit is contained in:
117
server_config_api.go
Normal file
117
server_config_api.go
Normal file
@@ -0,0 +1,117 @@
|
||||
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()
|
||||
|
||||
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 — no restart needed.
|
||||
applyFullConfigReload(&newCfg)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
Reference in New Issue
Block a user