Skip to content

Add GracefulRestart feature: scheduled restarts with in-game warnings#57

Merged
AdaInTheLab merged 1 commit intomainfrom
feature/graceful-restart
May 9, 2026
Merged

Add GracefulRestart feature: scheduled restarts with in-game warnings#57
AdaInTheLab merged 1 commit intomainfrom
feature/graceful-restart

Conversation

@AdaInTheLab
Copy link
Copy Markdown
Collaborator

Summary

Daily server restarts with a player-friendly countdown, owned by KC instead of by an out-of-band systemd timer + shell script.

The systemd timer + script we set up earlier today still works as a stopgap — but it's invisible to anyone using the panel, can't be tweaked without SSH, and conflicts with this feature once enabled. After this merges and deploys, the systemd timer should be disabled.

What it does

  • Schedule: one configurable wall-clock time per day (HH:mm) in any IANA timezone (default `04:00 America/Los_Angeles`). DST shifts are handled by the timezone DB.
  • Warning ladder: configurable per-step list of `{ minutesBefore, message, colorHex }`. Default: 10 / 5 / 1 / 0 with escalating colors (FFAA00 → FF6600 → FF0000). `{minutes}` token in messages substitutes the count.
  • At fire time: timer detects the start-of-countdown moment, plays the ladder over `say` broadcasts (marshaled to the Unity main thread), then issues `shutdown`. systemd's `Restart=always` (added in this morning's OOM hardening) brings the service back up automatically.
  • Manual triggers: `krestart [minutes]` console command and `POST /api/restart/now` with optional `{ leadMinutes }`. Re-entrancy guard prevents overlapping countdowns.

Surface area

File What
`Features/GracefulRestartSettings.cs` Settings POCO + RestartWarning step
`Features/GracefulRestartFeature.cs` Timer, countdown core, manual trigger, broadcast helpers
`Commands/RestartCommand.cs` `krestart [minutes]` console command (admin-only)
`Web/Controllers/GracefulRestartController.cs` `/api/restart/{settings,now}`

DI is convention-based (auto-registers `IFeature` types and Web API controllers), so no `ServiceRegistry.cs` changes.

Defaults

`Enabled = false`. Admins opt in by configuring the schedule + toggling on in the Settings UI (panel tab is the next PR — for now configurable via REST).

Test plan

  • Pull, build, deploy
  • Boot log shows `GracefulRestart feature loaded. Schedule=OFF 04:00 America/Los_Angeles, ladder steps=4.`
  • `PUT /api/restart/settings` with a schedule 12 minutes from now + Enabled=true
  • Console runs `krestart 2` — in-game broadcasts at 2min, 1min, 0min, then shutdown
  • `POST /api/restart/now` with `{leadMinutes:0}` — single "Restarting now..." broadcast then shutdown
  • Try `krestart 5` while a countdown is already running — second call returns "already in progress"
  • Disable the systemd `7daystodie-daily-restart.timer` to retire the stopgap
  • Watch the next scheduled fire — countdown plays out, server restarts, systemd brings it back, panel reachable again

Frontend

Settings tab + Restart Now button → next PR. For now the feature is admin-via-API or admin-via-console.

🤖 Generated with Claude Code

Previously the only daily-restart mechanism was a systemd timer that
called systemctl restart with no warning. Anyone playing got booted
mid-action. Most server admins use a countdown ladder (10 min / 5 / 1 /
0) so players have time to wrap up.

Moves the responsibility into KC where it belongs:

- GracefulRestartFeature : FeatureBase<GracefulRestartSettings>
  • Timer ticks every 30s, fires at the configured wall-clock minute
  • Schedule honors IANA timezone names so DST shifts don't drift
  • Re-entrancy guard blocks overlapping countdowns from sched + manual

- Warning ladder is configurable per step: minutes-before, message
  (with {minutes} token), color hex. Defaults: 10/5/1/0 with escalating
  severity colors (FFAA00 → FF6600 → FF0000).

- Manual trigger via krestart console command and POST /api/restart/now.
  Lead time arg defaults to longest configured step. krestart 0 = say
  one final "Restarting now..." then shutdown immediately.

- Uses SdtdConsole.Instance.ExecuteSync('say ...') marshaled to the
  game thread for broadcasts, then ExecuteSync('shutdown') for the
  actual save+exit. Systemd's Restart=always (added in the post-OOM
  hardening earlier) brings the service back up automatically — KC
  doesn't need to know about the supervisor.

REST surface:
  GET    /api/restart/settings    current schedule + ladder
  PUT    /api/restart/settings    overwrite
  POST   /api/restart/now         {leadMinutes?: 0..1440}

Defaults to disabled — admins opt in by configuring the schedule and
flipping the toggle in the Settings UI (panel tab is the next PR).

Replaces the systemd timer + shell script combo we set up earlier today
to bridge the gap. Once this is deployed, the systemd timer should be
disabled to avoid double-firing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AdaInTheLab AdaInTheLab merged commit b53334b into main May 9, 2026
2 checks passed
@AdaInTheLab AdaInTheLab deleted the feature/graceful-restart branch May 9, 2026 11:06
AdaInTheLab added a commit that referenced this pull request May 9, 2026
UI surface for the GracefulRestartFeature backend that landed in #57.

What's in the tab:

- Master Enable Daily Restart toggle
- Schedule: time of day (HH:mm) + IANA timezone, side-by-side
- Inline ladder editor: add/remove steps, edit minutes-before, message,
  and BBGGRR color hex per row. Default ladder rendered descending so
  the top row is what fires first
- Restart Now panel on the right: lead-time input + danger button with
  a confirm() prompt before kicking off

Layout mirrors the Vote Rewards tab (2-col, schedule + ladder left,
trigger right). Collapses to single col under 768px.

i18n: canonical strings in en.ts; ja/ko/zh-CN/zh-TW carry English
placeholders with a "translations TBD" comment, same pattern as the
Vote Rewards roll-out (vue-i18n's structural typecheck requires same
shape across all locales, so faking shape > skipping locales).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AdaInTheLab AdaInTheLab mentioned this pull request May 9, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant