Skip to content

fix(restart): skip systemctl probe on Windows; route through OS-aware strategy#102

Merged
AdaInTheLab merged 1 commit into
mainfrom
fix/restart-os-detect
Jun 4, 2026
Merged

fix(restart): skip systemctl probe on Windows; route through OS-aware strategy#102
AdaInTheLab merged 1 commit into
mainfrom
fix/restart-os-detect

Conversation

@AdaInTheLab

Copy link
Copy Markdown
Collaborator

Summary

POST /api/server/restart always tried sudo -n systemctl restart 7daystodie.service first and waited up to 5 seconds for it to either succeed or fail before falling back to in-game shutdown. On Linux this is correct (systemd Restart=always bounces the service). On Windows it is wrong twice over:

  • systemctl does not exist on Windows, so the probe always fails fast and wastes ~5s plus logs a misleading "systemctl restart failed or not available..." warning on every restart click.

  • On a live Windows prod box today this entire code path was the trigger for a cascading crash: the fallback in-game shutdown ran concurrent with a stuck main-thread operation and snowballed into a stack overflow. Beyond cosmetic, it was risk-shaped.

Fix

New Core/OsRestartStrategy.cs as a small pure helper. OsRestartStrategy.Decide(bool isWindows) returns one of two kinds:

  • SystemctlThenInGameShutdown (Linux): unchanged behavior — try systemctl, fall back to in-game shutdown for systemd Restart=always.

  • InGameShutdownOnly (Windows): skip the systemctl probe entirely, go straight to in-game shutdown. NSSM (the standard Windows service supervisor for 7DTD) ships with AppExit Restart as its default and auto-bounces the service when the game process exits, so this is the correct supervised-restart path on Windows.

Windows path logs INFO ("Windows install detected; using in-game shutdown + service-supervisor restart") instead of WARNING — the operator should see we chose this path, not that we fell back into it.

OsRestartStrategy.DecideForCurrentHost() reads the host platform via the existing PlatformHelper.IsWindows (Environment.OSVersion.Platform, works under both .NET Framework and Mono). The pure Decide(bool) overload keeps the decision testable without touching the real OS check — covered by 2 NUnit tests in KitsuneCommand.Tests/Core/OsRestartStrategyTests.cs.

krestart console command was checked and does not need the same treatment — it goes through GracefulRestartFeature.TriggerNow, which is pure in-game shutdown and never shells out.

Test plan

  • Build passes on Windows .NET Framework 4.8
  • OsRestartStrategyTests pass
  • Deploy to Windows prod box, click Restart Server in panel — confirm INFO log line + no systemctl warning + service auto-restarts via NSSM
  • Linux still routes through systemctl first (unchanged behavior)

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

… strategy

POST /api/server/restart always tried `sudo -n systemctl restart
7daystodie.service` first and waited up to 5 seconds for it to either
succeed or fail before falling back to in-game shutdown. On Linux
this is correct (systemd Restart=always bounces the service). On
Windows it is wrong twice over:

  - `systemctl` does not exist on Windows, so the probe always fails
    fast and wastes ~5s plus logs a misleading "systemctl restart
    failed or not available..." warning on every restart click.

  - On a live Windows prod box today this entire code path was the
    trigger for a cascading crash: the fallback in-game shutdown ran
    concurrent with a stuck main-thread operation and snowballed into
    a stack overflow. Beyond cosmetic, it was risk-shaped.

Fix: introduce Core/OsRestartStrategy.cs as a small pure helper.
`OsRestartStrategy.Decide(bool isWindows)` returns one of two kinds:

  - SystemctlThenInGameShutdown (Linux): unchanged behavior — try
    systemctl, fall back to in-game shutdown for systemd
    Restart=always.

  - InGameShutdownOnly (Windows): skip the systemctl probe entirely,
    go straight to in-game shutdown. NSSM (the standard Windows
    service supervisor for 7DTD) ships with `AppExit Restart` as its
    default and auto-bounces the service when the game process
    exits, so this is the correct supervised-restart path on Windows.

Windows path logs INFO ("Windows install detected; using in-game
shutdown + service-supervisor restart") instead of WARNING — the
operator should see we *chose* this path, not that we fell back into
it.

`OsRestartStrategy.DecideForCurrentHost()` reads the host platform via
the existing `PlatformHelper.IsWindows` (Environment.OSVersion.Platform,
works under both .NET Framework and Mono). The pure `Decide(bool)`
overload keeps the decision testable without touching the real OS
check — covered by two NUnit tests in
KitsuneCommand.Tests/Core/OsRestartStrategyTests.cs.

`krestart` console command was checked and does not need the same
treatment — it goes through GracefulRestartFeature.TriggerNow, which
is pure in-game shutdown and never shells out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AdaInTheLab AdaInTheLab force-pushed the fix/restart-os-detect branch from 01e4417 to 863be23 Compare June 4, 2026 00:16
@AdaInTheLab AdaInTheLab merged commit 5822266 into main Jun 4, 2026
3 checks passed
@AdaInTheLab AdaInTheLab deleted the fix/restart-os-detect branch June 4, 2026 00:21
AdaInTheLab added a commit that referenced this pull request Jun 4, 2026
Cuts v2.8.2. Three fixes from the live Windows prod box plus the
timezone field on the Server Restart panel grows up into a host-
resolvable dropdown. No new pages, no schema changes — patch release
per docs/RELEASES.md conventions.

What lands:

- #101 fix(websocket): stop LogCallbackEvent broadcast recursion in
  shutdown. ThreadStatic re-entrancy guard + suppressFailureLogging
  fallback to Console.Error so a failed log-broadcast can't fire a
  fresh LogCallbackEvent and recurse to stack overflow.
- #102 fix(restart): skip systemctl probe on Windows; route through
  OS-aware strategy. New Core/OsRestartStrategy.cs picks per OS;
  Windows goes straight to in-game shutdown + NSSM AppExit Restart
  bounce, no more wasted 5s probe or misleading warning.
- #103 feat(restart): host-resolvable timezone dropdown + heal-on-read.
  LoadPersistedSettings heals an unresolvable TZ ID to TimeZoneInfo
  .Local and persists. New GET /api/server/timezones endpoint feeds a
  PrimeVue Select in Settings → Server Restart, replacing the free-
  text input that admins kept filling with strings the host couldn't
  parse.

Version bumps:
- src/KitsuneCommand/ModInfo.xml: 2.8.1 → 2.8.2
- frontend/package.json: 2.7.4 → 2.8.2 (reconciles prior drift —
  frontend version was stuck at 2.7.4 since v2.8.0)
- CHANGELOG.md: promote [Unreleased] → [2.8.2] - 2026-06-04

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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