419d3d0b4e
PushResponse.ConfigVersion from the report ACK; ConfigRefresher reconciles vs. the last-applied version (settings.applied_config_version) and on a change calls bootstrap.RefreshConfig (re-pull controller.yaml + re-merge local_api) then GracefulSelfRestart. First-run records baseline (no restart); unchanged = no-op (no storm); failed pull keeps config + retries. Companion to hub v0.26.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
55 lines
2.0 KiB
Go
55 lines
2.0 KiB
Go
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) {
|
|
GracefulSelfRestart(logger)
|
|
}
|
|
|
|
// GracefulSelfRestart is the exported entry point to the same graceful restart, so non-api callers
|
|
// (the config-refresh reconcile wired in main.go) reuse this one mechanism instead of reinventing an
|
|
// os.Exit path. See gracefulSelfRestart for the rationale.
|
|
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()
|
|
}
|
|
}
|