fix: status.json fuer normale Nutzer lesbar machen, Lesefehler nicht mehr verschlucken

server.py legt status.json ueber mkstemp() (Modus 0600) + os.replace() an; da
os.replace() den Modus der temporaeren Datei uebernimmt, war die Datei nach jedem
Schreiben nur fuer root lesbar - der Dienst laeuft als root, das CLI liest aber als
normaler Benutzer. os.chmod(tmp, 0o644) vor dem Rename behebt das umask-fest, auch
bei wiederholten Schreibvorgaengen.

ninlight-station status hat den fehlenden Lesezugriff bisher stillschweigend wie
"noch nie ausgeliefert" behandelt (beides fiel durch denselben "-r"-Test). Jetzt wird
zwischen "Datei fehlt" (unauffaellig) und "Datei da, aber nicht lesbar" (Fehlermeldung
auf stderr) unterschieden.

Neuer Test prueft, dass status.json nach dem ersten und nach einem weiteren
Schreibvorgang world-readable bleibt, auch unter restriktivem umask.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michael Sauer
2026-09-13 00:26:05 +02:00
co-authored by Claude Sonnet 5
parent cb54dac066
commit 62903a7f05
3 changed files with 71 additions and 4 deletions
+19 -3
View File
@@ -10,6 +10,7 @@ Standard library only - the Pi gets no extra packages.
from __future__ import annotations
import argparse
import contextlib
import json
import os
import re
@@ -85,9 +86,24 @@ class Station:
try:
self.state_path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(dir=self.state_path.parent, prefix=".status-")
with os.fdopen(fd, "w") as f:
json.dump(payload, f, indent=2)
os.replace(tmp, self.state_path)
try:
with os.fdopen(fd, "w") as f:
json.dump(payload, f, indent=2)
# The service runs as root and mkstemp() creates the file 0600, so
# without this an operator reading status.json as a normal user (see
# tools/station/ninlight-station) gets "permission denied" - and since
# os.replace() preserves the mode it arrives with, that wrong mode
# would otherwise survive onto the live file on every single write,
# not just the first. os.chmod() ignores the process umask (unlike the
# mode argument to mkstemp/open), so a restrictive umask on the Pi
# can't quietly bring the 0600 back. Set it before the rename so
# there's never a window where the path holds a 0600 file.
os.chmod(tmp, 0o644)
os.replace(tmp, self.state_path)
except BaseException:
with contextlib.suppress(OSError):
os.unlink(tmp)
raise
except OSError as e:
print(f"warning: cannot write {self.state_path}: {e}", flush=True)