controller: config-apply self-restart + manual restart button (A)
POST /api/config/apply now takes effect via a graceful SELF-RESTART instead of logging "restart needed" and leaving stale in-process singletons (the CF client is built once at startup, so a rotated Cloudflare token never applied until a manual LXC restart). Container is restart:unless-stopped, so a clean os.Exit(0) auto-restarts with fresh config. - New gracefulSelfRestart helper behind an injectable Restarter seam (Router.restart + SetRestarter) so the exit is unit-testable. - configApply: no-op guard (byte-identical re-push → no write, no restart), else write → 200 (flushed) → restart. Removed stale "restart needed" wording. - Removed the dead OnConfigApplied hook (Phase-1-retired infra-backup push; the self-restart reloads everything and a fresh report is pushed on startup). - New POST /api/selfrestart (auth+CSRF via /api/ mount) + "Vezérlő újraindítása" settings button: confirm → POST → poll GET / every 2s → reload. - Tests: changed→restart once; identical→not called (companion); invalid→not called; selfrestart→restart once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// restartDelay gives the in-flight HTTP response time to flush before the process exits.
|
||||
const restartDelay = 500 * time.Millisecond
|
||||
|
||||
// gracefulSelfRestart schedules a clean process exit after a short delay. The container
|
||||
// runs with `restart: unless-stopped`, so exiting 0 makes Docker start a fresh process
|
||||
// that re-reads controller.yaml. This is how a config-apply (e.g. a rotated Cloudflare
|
||||
// API token) and the manual restart button actually take effect: singletons such as the
|
||||
// Cloudflare client are built once at startup and are not reloaded in-process.
|
||||
func gracefulSelfRestart(logger *log.Logger) {
|
||||
go func() {
|
||||
time.Sleep(restartDelay)
|
||||
if logger != nil {
|
||||
logger.Println("[INFO] [api] Graceful self-restart: exiting (0) for container restart")
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
// flushResponse flushes buffered output to the client if the writer supports it, so the
|
||||
// caller receives the response body before a subsequent self-restart kills the process.
|
||||
func flushResponse(w http.ResponseWriter) {
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// selfRestart handles POST /api/selfrestart — a customer-facing self-serve restart so a
|
||||
// user can recover the controller without rebooting the whole guest. Auth + CSRF are
|
||||
// applied by the /api/ mux mount (same protection as every other state-changing endpoint).
|
||||
// Responds first, flushes, then triggers the graceful restart.
|
||||
func (r *Router) selfRestart(w http.ResponseWriter, _ *http.Request) {
|
||||
r.logger.Println("[INFO] [api] Manual controller restart requested")
|
||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Újraindítás folyamatban…"})
|
||||
flushResponse(w)
|
||||
if r.restart != nil {
|
||||
r.restart()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user