feat(config): configurable restart_apply_mode + graceful shutdown (Stage 3b)

- console section: restart_apply_mode (manual | auto), an operator setting (runtime-safe,
  DB-overridable via /admin/config), env RESTART_APPLY_MODE default manual.
- Graceful shutdown: main serve now awaits SIGTERM/Ctrl-C OR an in-app shutdown signal
  (AppState.shutdown Notify) via with_graceful_shutdown, draining in-flight requests.
- PUT /admin/config in `auto` mode self-restarts shortly after responding to apply a
  restart-required change (systemd Restart=always brings it back with the new config);
  `manual` just flags restart_pending. The console warns before an auto save.

Stage 3 complete: operator config is readable + editable through the real validated,
audited, precedence-based layer; runtime-safe edits apply live, infra edits restart.
cargo all-targets clean, lib tests pass.
This commit is contained in:
jwinterm
2026-07-18 14:08:33 -04:00
parent 22d362b62f
commit a80fa46daf
6 changed files with 130 additions and 1 deletions
+11
View File
@@ -900,6 +900,17 @@ pub async fn admin_config_put(
// the next request); any restart-required field is persisted + flagged so the
// operator applies it with a graceful restart.
if any_restart {
// In `auto` mode, self-restart shortly AFTER responding so the persisted change
// takes effect (systemd `Restart=always` brings us back). The console warns the
// operator before saving in auto mode; `manual` just flags restart_pending.
if state.cfg().console.restart_apply_mode == crate::config::RestartApplyMode::Auto {
let shutdown = state.shutdown.clone();
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(750)).await;
tracing::info!("config auto-apply: self-restart to apply a restart-required change");
shutdown.notify_one();
});
}
Ok(Json(PutConfigResponse {
applied: "restart-required".into(),
restart_pending: true,