Files
werkbaum/scripts/werkbaum-backend.service
mhoennigandClaude Opus 5 6494a7cb38 fix(deploy): sagen, WARUM der Dienst nicht startet
Bei einem Fehlstart zeigte das Skript `tail -30` des Logs — also das Ende
eines Stacktrace, die Rahmenliste, gerade das was nichts erklaert. Jetzt wird
der letzte Startversuch herausgeschnitten und daraus die Ursachenkette gezeigt;
die tiefste Zeile ist die Antwort. Dazu ein Hinweis auf den einen Fall, der
beim Umstieg zwangslaeufig auftritt: eine Datenbank aus der Zeit mit
MODE=PostgreSQL passt nicht mehr und muss einmal weg.

Ausserdem eine Neustart-Grenze in der Unit: Ohne sie laeuft ein kaputtes
Deployment endlos im Kreis, `is-active` sagt dauerhaft "activating", und der
eigentliche Fehler steht irgendwo weit oben im wachsenden Log. Mit ihr endet
es nach fuenf Fehlstarts sichtbar in "failed".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:09:35 +02:00

89 lines
3.5 KiB
Desktop File

[Unit]
# Werkbaum — Backend als systemd-User-Unit (D77).
#
# Vorlage: scripts/deploy-backend.sh setzt __JAVA__, __DIR__, __XMX__ und
# __PORT__ ein und legt die Datei als ~/.config/systemd/user/werkbaum-backend.service
# ab. Ein User-Unit braucht kein root; dass er auch ohne offene Sitzung
# weiterläuft, liegt an `Linger=yes` — auf der Zielumgebung gemessen gesetzt
# (D76-Nachtrag 1). Fehlt es, hilft `loginctl enable-linger`.
#
# Die Pfade kommen als `%h/…` herein, nicht als `$HOME/…`: systemd expandiert
# in `WorkingDirectory` und `EnvironmentFile` **keine** Shell-Variablen. Ein
# `$HOME` stünde dort wörtlich, und der Dienst startete nicht — der Fehler
# säße in einer Datei, die man nur auf dem Server zu sehen bekommt.
Description=Werkbaum backend
After=network.target
# Nach fuenf Fehlstarts in fuenf Minuten gibt systemd auf. Ohne diese Grenze
# laeuft ein kaputtes Deployment endlos im Kreis: `is-active` sagt dann
# dauerhaft „activating", das Log waechst, und der eigentliche Fehler steht
# irgendwo weit oben. Mit ihr endet es sichtbar in „failed".
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=__DIR__
# Zugangsdaten kommen aus einer Datei mit Modus 600, nie aus dem Repository
# und nie aus der Unit selbst (`systemctl --user show` gäbe sie sonst preis).
EnvironmentFile=__DIR__/env
# Speicher ist auf diesem Host die knappe Größe, nicht die Rechenzeit
# (D76-Nachtrag 3): 3,9 GB gesamt, davon rund 300 MB frei, ein weiteres GB
# liegt schon im Swap. Die cgroup-Grenze des Pakets (3147 MB) ist eine
# Erlaubnis, keine Reservierung; bemessen wird am Freien.
#
# Gemessen mit 30 angelegten Dokumenten und anschließendem GC (D77):
#
# ohne Angaben RSS 291 MB (Heap belegt 359 MB)
# nur -Xmx384m RSS 254 MB (Heap belegt 359 MB)
# -Xmx192m -Xms48m + Ratios RSS 174 MB (Heap belegt 46 MB)
# dito mit G1 statt Serial RSS 194 MB
#
# Der große Hebel sind NICHT die Obergrenzen, sondern die
# Freiraum-Verhältnisse: Ohne sie behält der Kollektor den einmal gewachsenen
# Heap, obwohl nach einem GC nur ~45 MB leben. Mit ihnen gibt er ihn zurück.
# SerialGC statt G1 spart weitere 20 MB und kostet bei zehn Beobachtern nichts,
# was auffiele.
#
# ExitOnOutOfMemoryError: lieber ein sauberer Neustart durch systemd als ein
# Dienst, der noch läuft und nichts mehr beantwortet.
ExecStart=__JAVA__ \
-Xmx__XMX__ \
-Xms48m \
-XX:MinHeapFreeRatio=20 \
-XX:MaxHeapFreeRatio=40 \
-Xss512k \
-XX:MaxMetaspaceSize=192m \
-XX:ReservedCodeCacheSize=64m \
-XX:MaxDirectMemorySize=64m \
-XX:+UseSerialGC \
-XX:+ExitOnOutOfMemoryError \
-Dserver.port=__PORT__ \
-Dserver.address=127.0.0.1 \
-jar __DIR__/werkbaum-backend.jar
# Der Dienst lauscht nur auf 127.0.0.1 (siehe -Dserver.address): Von außen
# kommt man ausschließlich über den Apache-Proxy, und damit gilt dessen
# HTTPS — das Master-Passwort geht nie im Klartext über das Netz.
Restart=on-failure
RestartSec=10
# Beim Stoppen erst freundlich fragen; ein laufender Long-Poll hält bis zu
# 25 s, deshalb darf das Beenden etwas dauern.
TimeoutStopSec=40
KillSignal=SIGTERM
# Bewusst ohne die Sandbox-Optionen (`PrivateTmp`, `ProtectHome` und
# Verwandte): In einem User-Unit brauchen sie unprivilegierte
# Benutzer-Namensräume, und wo die abgeschaltet sind, startet der Dienst
# gar nicht erst. `NoNewPrivileges` ist ein schlichtes prctl und geht immer.
NoNewPrivileges=true
StandardOutput=append:__DIR__/backend.log
StandardError=append:__DIR__/backend.log
[Install]
WantedBy=default.target