fix(system): surface a failed restart sentinel write instead of a stuck state

Mirror the update button (0.9.15): requestRestart() now returns bool and
restartNow() shows an error toast (pointing at sudo ./update.sh) when the
sentinel can't be written, instead of a 'restarting' state that never resolves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/v1-foundation v0.9.16
boban 2026-06-19 20:10:28 +02:00
parent 703207c03c
commit 6f2159a93a
6 changed files with 22 additions and 4 deletions

View File

@ -13,6 +13,15 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
## [0.9.16] - 2026-06-19
### Behoben
- **Neustart-Knopf meldet einen fehlgeschlagenen Sentinel-Schreibvorgang** — konsistent mit dem
Update-Knopf (0.9.15). Konnte die Sentinel-Datei nicht geschrieben werden (z. B. bei einem
uid-Mismatch vor dem Fix), zeigte „Jetzt neu starten" nur „Neustart wird ausgeführt …" ohne dass
etwas passierte; jetzt erscheint stattdessen ein Fehler-Toast mit dem Hinweis auf `sudo ./update.sh`.
`DeploymentService::requestRestart()` gibt nun `bool` zurück.
## [0.9.15] - 2026-06-19
### Behoben

View File

@ -145,7 +145,12 @@ class Index extends Component
*/
public function restartNow(DeploymentService $deployment): void
{
$deployment->requestRestart();
// Surface a failed sentinel write instead of a "restarting" state that never resolves.
if (! $deployment->requestRestart()) {
$this->dispatch('notify', message: __('system.restart_write_failed'), level: 'error');
return;
}
$this->restartRequested = true;
$this->dispatch('notify', message: __('system.restart_running'));
}

View File

@ -173,7 +173,7 @@ class DeploymentService
* `docker compose restart`, then removes it. The content is a non-sensitive marker
* (an ISO timestamp) the trigger is the file existing, not what it holds. Never throws.
*/
public function requestRestart(): void
public function requestRestart(): bool
{
$path = $this->restartSignalPath();
$dir = dirname($path);
@ -182,7 +182,9 @@ class DeploymentService
@mkdir($dir, 0775, true);
}
@file_put_contents($path, now()->toIso8601String().PHP_EOL);
// Returns false when the (bind-mounted) sentinel dir is not writable by the app user, so
// the caller can surface that instead of a "restarting" state that never resolves.
return @file_put_contents($path, now()->toIso8601String().PHP_EOL) !== false;
}
/** True while a restart request is pending (the host watcher clears it once handled). */

View File

@ -2,7 +2,7 @@
return [
// First tagged release is v0.1.0 (semantic, not -dev).
'version' => '0.9.15',
'version' => '0.9.16',
// Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod
// image ships no .git); the Versions page prefers them and falls back to a live .git read in

View File

@ -43,6 +43,7 @@ return [
'restart_body' => 'Die Änderung ist gespeichert. Starte den Stack neu, damit sie übernommen wird.',
'restart_now' => 'Jetzt neu starten',
'restart_running' => 'Neustart wird ausgeführt …',
'restart_write_failed' => 'Neustart konnte nicht angestoßen werden (Sentinel nicht schreibbar). Bitte einmalig per SSH „sudo ./update.sh" aktualisieren.',
'restart_watcher_hint' => 'Falls nach ~30 s nichts passiert, ist der Host-Watcher evtl. nicht installiert — siehe Doku (docker/restart-sentinel).',
'restart_lockout_hint' => 'Nach dem Neustart eventuell neu anmelden. Falls die neue Domain (DNS/Zertifikat) noch nicht erreichbar ist, bleibt das Panel über http://<Server-IP> als Rückfallweg erreichbar.',

View File

@ -43,6 +43,7 @@ return [
'restart_body' => 'The change is saved. Restart the stack to apply it.',
'restart_now' => 'Restart now',
'restart_running' => 'Restart is running …',
'restart_write_failed' => 'Could not start the restart (sentinel not writable). Please update once via SSH with "sudo ./update.sh".',
'restart_watcher_hint' => 'If nothing happens after ~30 s, the host watcher may not be installed — see the docs (docker/restart-sentinel).',
'restart_lockout_hint' => 'You may need to sign in again after the restart. If the new domain (DNS/certificate) is not reachable yet, the panel stays available at http://<server-IP> as a fallback.',