feat(betrieb): native Installation, Proxmox-Einzeiler und Abnahme
Zwei Wege in den Betrieb, beide so weit gefahren, wie es hier moeglich war. deploy/install.sh richtet Debian 13 in neun Schritten ein: PostgreSQL 17 hoert nur am Unix-Socket, der Dienstnutzer hat kein Login, die Geheimnisse entstehen im Zielsystem und liegen 0600 bei root. Die Umgebungsdatei wird nie ueberschrieben -- auch nicht beim Aktualisieren, sonst enden alle Sitzungen und die verschluesselten Bind-Passwoerter waeren unlesbar. deploy/proxmox/laufzettel.sh legt den Container an und ruft install.sh darin auf. Der pct-Teil ist ungetestet, weil kein PVE-Host zur Verfuegung stand; die Gegenprobe steht als Pruefliste in docs/betrieb/proxmox.md, das Folge-Issue ist #024. Die Abnahme gegen alle 61 Kriterien hat drei Zusagen ohne Deckung gefunden, alle behoben: der Healthcheck des Webdienstes konnte nie bestehen (busybox loest localhost nach ::1 auf, Next lauscht nur auf IPv4), die CI-Pipeline konnte in keinem Job gruen werden (kein LDAP-Dienst, kein gestarteter Server, kein shellcheck), und der README nannte fuer die Entwicklungsdatenbank den falschen Port. Closes #19
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
# Der native Weg: Proxmox-LXC und Debian 13
|
||||
|
||||
Ohne Docker. Ein Skript legt auf dem Proxmox-Host einen unprivilegierten Debian-13-Container an und
|
||||
installiert darin alles: PostgreSQL 17 nur am Unix-Socket, Node 24, die Anwendung als
|
||||
systemd-Dienst. Wer schon ein leeres Debian 13 hat — als LXC, als virtuelle Maschine oder auf Blech —
|
||||
überspringt den ersten Teil und ruft `deploy/install.sh` direkt auf.
|
||||
|
||||
Der andere Weg, mit Docker Compose, steht in [`inbetriebnahme.md`](inbetriebnahme.md).
|
||||
|
||||
> **Der Proxmox-Teil ist nicht automatisch geprüft.** Für diese Arbeit stand kein PVE-Host zur
|
||||
> Verfügung; alles, was `pct` anfasst, ist geschrieben und gelesen, aber nie gelaufen.
|
||||
> `deploy/install.sh` dagegen ist geprüft — es installiert in einem `debian:13`-Abbild vollständig
|
||||
> durch und bringt mit `--test` seine eigene Selbstprüfung mit. Was du beim ersten echten Lauf
|
||||
> nachsehen musst, steht am Ende dieser Seite in einer Prüfliste. Das ist keine Floskel: geh sie
|
||||
> durch.
|
||||
|
||||
## Was am Ende auf dem System steht
|
||||
|
||||
| Ort | Was |
|
||||
|---|---|
|
||||
| `/opt/laufzettel` | Der Quellcode als Git-Arbeitsverzeichnis, Eigentümer `laufzettel`. Aktualisiert wird mit `git pull`, deshalb ein Checkout und kein Tarball. |
|
||||
| `/opt/laufzettel/.next/standalone` | Der gebaute Server. Von hier startet `node server.js`. |
|
||||
| `/etc/laufzettel/laufzettel.env` | **Die Geheimnisse dieser Installation**: `APP_SECRET`, `DATABASE_URL` mit Passwort, `APP_URL`. Modus 0600, Eigentümer root. Das Skript überschreibt diese Datei nie — auch nicht beim Aktualisieren. |
|
||||
| `/etc/systemd/system/laufzettel.service` | Der Dienst. Läuft als `laufzettel`, mit `NoNewPrivileges`, `ProtectSystem=strict`, `PrivateTmp` und `Restart=on-failure`. |
|
||||
| `/var/cache/laufzettel` | Heimatverzeichnis des Dienstnutzers — nur der Zwischenspeicher von pnpm und corepack, kein Arbeitskonto. |
|
||||
| `/etc/postgresql/17/main/conf.d/laufzettel.conf` | Eine Zeile: `listen_addresses = ''`. |
|
||||
| Datenbank `laufzettel` | Rolle `laufzettel` mit erzeugtem Passwort, erreichbar nur über den Socket `/run/postgresql`. |
|
||||
|
||||
Der Dienstnutzer `laufzettel` ist ein Systemkonto mit `/usr/sbin/nologin` als Shell und ohne
|
||||
Heimatverzeichnis unter `/home`. Anmelden kann sich damit niemand.
|
||||
|
||||
## Weg A: der One-Liner auf dem Proxmox-Host
|
||||
|
||||
In der Shell des PVE-Hosts, als root:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://git.wilde-server.de/florian.wilde/Laufzettel/raw/branch/main/deploy/proxmox/laufzettel.sh)"
|
||||
```
|
||||
|
||||
Das Skript prüft zuerst, ob es auf einem PVE 8 oder 9 als root läuft, holt bei Bedarf die
|
||||
Debian-13-Vorlage über `pveam`, und fragt dann per Dialog: **Standard** oder **Erweitert**.
|
||||
|
||||
**Standard** nimmt diese Werte, ohne zu fragen:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| CTID | die nächste freie |
|
||||
| Hostname | `laufzettel` |
|
||||
| Vorlage | Debian 13 |
|
||||
| Kerne | 2 |
|
||||
| RAM | 2 GB |
|
||||
| Platte | 8 GB |
|
||||
| Swap | 512 MB |
|
||||
| Privilegien | unprivilegiert, mit `nesting=1` — die systemd-Unit braucht es |
|
||||
| Netz | DHCP auf `vmbr0` |
|
||||
|
||||
Das ist auf einen kleinen Betrieb zugeschnitten und reicht für einige Tausend Läufe. Der erste Bau
|
||||
braucht kurzzeitig mehr RAM als der Betrieb — wenn `pnpm build` mit `Killed` abbricht, ist das die
|
||||
Ursache; dann im Erweitert-Dialog 4 GB geben und den Wert danach zurückdrehen.
|
||||
|
||||
**Erweitert** fragt dieselben Werte einzeln ab, dazu eine feste IP statt DHCP, die Bridge und die
|
||||
beiden Speicher getrennt: einen für das Wurzeldateisystem des Containers (Inhalt `rootdir`) und
|
||||
einen für die Debian-Vorlage (Inhalt `vztmpl`). Vorbelegt ist jeweils der erste aktive Speicher, der
|
||||
den Inhalt aufnehmen kann; findet das Skript keinen, bricht es mit dieser Begründung ab.
|
||||
|
||||
Ein **zweiter Aufruf** erkennt einen vorhandenen Container am Hostnamen und bietet an, ihn zu
|
||||
aktualisieren, statt einen zweiten anzulegen.
|
||||
|
||||
Das Skript installiert selbst nichts. Es legt den Container an, holt `deploy/install.sh` hinein und
|
||||
ruft es dort auf — damit der Weg mit und ohne Proxmox nicht auseinanderläuft.
|
||||
|
||||
| Schalter | Bedeutung |
|
||||
|---|---|
|
||||
| `--dry-run` (auch `--trockenlauf`) | Gibt jeden `pct`-, `pveam`- und `pvesh`-Aufruf nur aus. Legt nichts an, lädt nichts, und überspringt auch die Prüfung auf root und PVE — läuft also auf jedem Rechner. |
|
||||
| `--standard` | Ohne Dialog, alle Standardwerte nehmen. |
|
||||
| `--erweitert` | Direkt in den Erweitert-Dialog. |
|
||||
| `--aktualisieren` | Nur aktualisieren, ohne Rückfrage. Bricht ab, wenn es keinen Container mit dem Hostnamen `laufzettel` gibt — es wird also keiner angelegt. Gedacht für den zweiten und jeden weiteren Aufruf, etwa aus einem Wartungsfenster heraus. |
|
||||
| `--zweig <name>` | Git-Zweig, aus dem `install.sh` geholt wird. Standard `main`. |
|
||||
| `--hilfe` | Alle Schalter. |
|
||||
|
||||
### Erst einmal nur hinsehen
|
||||
|
||||
```bash
|
||||
bash laufzettel.sh --dry-run
|
||||
```
|
||||
|
||||
Nimm das beim ersten Mal: der ausgegebene `pct create` lässt sich lesen, prüfen und notfalls von Hand
|
||||
abschicken. Der Trockenlauf braucht keinen PVE-Host und läuft bis zum Abschlussbildschirm durch.
|
||||
|
||||
## Weg B: `install.sh` in einem vorhandenen Debian 13
|
||||
|
||||
Ob das Debian in einem LXC steckt, in einer VM oder auf Blech, ist dem Skript gleich. Es braucht
|
||||
root, `apt` und Internet.
|
||||
|
||||
```bash
|
||||
apt install -y git
|
||||
git clone https://git.wilde-server.de/florian.wilde/Laufzettel.git /tmp/laufzettel-quelle
|
||||
sudo bash /tmp/laufzettel-quelle/deploy/install.sh --url https://laufzettel.firma.de --admin-mail admin@firma.de
|
||||
```
|
||||
|
||||
Die Schalter:
|
||||
|
||||
| Schalter | Bedeutung |
|
||||
|---|---|
|
||||
| `--url <adresse>` | Die **öffentliche** Adresse, also `APP_URL`. Ohne Angabe nimmt das Skript `http://<erste IP des Systems>:<port>` — das läuft, aber die QR-Codes auf den Aufklebern tragen dann diese Adresse. Setz sie richtig, bevor du druckst. |
|
||||
| `--admin-mail <adresse>` | Kennung des ersten Administrators. Standard `admin@firma.example`. |
|
||||
| `--port <nummer>` | Port des Webdienstes. Standard 3000. |
|
||||
| `--bind <adresse>` | Wohin gebunden wird. Standard `127.0.0.1` — davor gehört ein Reverse Proxy. Läuft der Proxy auf einem anderen Rechner, gehört hier die IP des Containers hin, dazu eine Firewall-Regel. |
|
||||
| `--modus <wert>` | `INTERN` (Standard) oder `DIENSTLEISTUNG`. |
|
||||
| `--zweig <name>`, `--quelle <url\|pfad>` | Anderer Zweig oder andere Git-Quelle. Für Testläufe nimmt `--quelle` auch einen lokalen Pfad, wenn dort ein Git-Arbeitsverzeichnis liegt. |
|
||||
| `--passwort-datei <pfad>` | Legt das beim ersten Lauf erzeugte Administrator-Passwort **zusätzlich** in diese Datei (0600, `root`), in den Zeilen `SEED_ADMIN_EMAIL=` und `SEED_ADMIN_PASSWORD=`. Gedacht für Aufrufer, die die Ausgabe nicht mitlesen können — genau so holt sich der Proxmox-Weg das Passwort für seinen Abschlussbildschirm. **Wer den Schalter benutzt, löscht die Datei nach dem Ablesen**, sonst liegt das Passwort dauerhaft im Klartext im System. |
|
||||
| `--test` | Nach der Installation eine Selbstprüfung fahren, siehe unten. |
|
||||
| `--hilfe` | Alle Schalter. |
|
||||
|
||||
Dieselben Werte nimmt das Skript auch aus der **Umgebung** — für Aufrufer ohne Befehlszeile, also
|
||||
`pct exec` im Proxmox-Weg. Ein Schalter schlägt die Umgebung. Die Namen sind dieselben wie in
|
||||
`.env.example`, damit es nur eine Schreibweise gibt: `APP_URL`, `BIND_ADDR`, `APP_PORT`,
|
||||
`SEED_ADMIN_EMAIL`, `SEED_MODE`, dazu `LAUFZETTEL_REPO` (wie `--quelle`), `LAUFZETTEL_ZWEIG` (wie
|
||||
`--zweig`) und `LAUFZETTEL_PASSWORT_DATEI` (wie `--passwort-datei`).
|
||||
|
||||
> **`--url`, `--port`, `--bind`, `--admin-mail` und `--modus` wirken nur beim ersten Lauf.** Danach
|
||||
> steht die Wahrheit in `/etc/laufzettel/laufzettel.env`, und diese Datei wird nie überschrieben. Ein
|
||||
> zweiter Aufruf mit anderen Werten warnt und ignoriert sie. Zum Ändern: die Datei bearbeiten und
|
||||
> `systemctl restart laufzettel`.
|
||||
|
||||
Das Skript läuft in neun Schritten und sagt bei jedem, was es tut. Rechne mit fünf bis fünfzehn
|
||||
Minuten, das meiste davon `pnpm install` und `pnpm build`.
|
||||
|
||||
## Der Abschlussbildschirm
|
||||
|
||||
Auf Weg A siehst du **zwei** Abschlüsse: erst den von `install.sh` aus dem Container, dann den des
|
||||
Proxmox-Skripts, das noch einmal Adresse, Kennung und Passwort zeigt und sagt, wie du in den
|
||||
Container kommst (`pct enter <ctid>`).
|
||||
|
||||
**Das erzeugte Admin-Passwort steht in `laufzettel.env` absichtlich nicht** — dort liegen nur
|
||||
`APP_SECRET` und das Datenbankpasswort. Damit der Abschlussbildschirm es trotzdem zeigen kann,
|
||||
obwohl `install.sh` hinter `pct exec` läuft, gibt das Skript ihm `LAUFZETTEL_PASSWORT_DATEI` mit:
|
||||
`install.sh` legt das Passwort einmalig in `/root/laufzettel-erstpasswort` (0600, `root`), der
|
||||
Aufrufer liest es von dort und **löscht die Datei sofort wieder**, gelesen oder nicht. Beim zweiten
|
||||
Aufruf — beim Aktualisieren — entsteht kein neues Passwort, und der Abschluss zeigt an dieser Stelle
|
||||
„unverändert (das Konto bestand schon)“. Das ist der Normalfall, kein Fehler.
|
||||
|
||||
Die Kennung dagegen wird aus `laufzettel.env` gelesen; steht dort nichts, zeigt der Abschluss den
|
||||
Verweis auf die Datei statt einer Adresse.
|
||||
|
||||
Der Abschluss von `install.sh` sieht so aus:
|
||||
|
||||
```
|
||||
Laufzettel ist eingerichtet.
|
||||
|
||||
Adresse https://laufzettel.firma.de
|
||||
Dienst horcht 127.0.0.1:3000
|
||||
Administrator admin@firma.de
|
||||
Passwort xY7k2mQ4pL9wR3tN8vZa
|
||||
|
||||
Dieses Passwort wird genau hier einmal angezeigt. In der Datenbank steht
|
||||
nur der argon2-Hash, in /etc/laufzettel/laufzettel.env steht es nicht.
|
||||
```
|
||||
|
||||
> **Schreib das Passwort auf, bevor du das Fenster schließt.** Ohne `--passwort-datei` steht es in
|
||||
> keiner Datei — weder in `laufzettel.env` noch im Journal — und es wird nie ein zweites Mal
|
||||
> angezeigt. Ist es weg, hilft nur ein neues über `psql` beziehungsweise, wenn OIDC schon läuft, der
|
||||
> Weg über den Identitätsanbieter.
|
||||
|
||||
Bestand das Admin-Konto schon — also beim zweiten Lauf — erzeugt das Skript kein neues Passwort und
|
||||
schreibt an dieser Stelle `Passwort unveraendert (Konto bestand schon)`. Das ist der
|
||||
Normalfall beim Aktualisieren.
|
||||
|
||||
Der lokale Login ist auf diesem Weg von Anfang an eingeschaltet
|
||||
(`AUTH_LOCAL_ENABLED="true"` in der Umgebungsdatei), denn sonst käme niemand hinein. Sobald OIDC
|
||||
steht, setz ihn auf `false` — bis auf das eine Notfallkonto, siehe
|
||||
[`authentik.md`](authentik.md), Abschnitt 7.
|
||||
|
||||
## Was danach zu tun ist
|
||||
|
||||
1. **Reverse Proxy davorstellen.** Der Dienst bindet auf `127.0.0.1` und spricht kein HTTPS; ohne
|
||||
Proxy erreicht ihn nur der Container selbst. Siehe [`reverse-proxy.md`](reverse-proxy.md).
|
||||
2. **Anmelden** und unter **Admin → Einstellungen** Organisationsname und Kürzel setzen. Das Skript
|
||||
schreibt `SEED_ORG_NAME="Eigene Organisation"` und `SEED_ORG_KUERZEL="EIGEN"` — das Kürzel steht
|
||||
später auf Etiketten, es lohnt sich, es früh richtigzustellen.
|
||||
3. **[authentik anbinden](authentik.md)**: die `OIDC_*`-Zeilen in
|
||||
`/etc/laufzettel/laufzettel.env` sind auskommentiert vorbereitet. Ausfüllen,
|
||||
`systemctl restart laufzettel`.
|
||||
4. **Sicherung einrichten**, bevor echte Daten drin sind: [`sicherung.md`](sicherung.md).
|
||||
|
||||
## Den Dienst bedienen
|
||||
|
||||
```bash
|
||||
systemctl status laufzettel
|
||||
systemctl restart laufzettel # nach jeder Änderung an laufzettel.env
|
||||
journalctl -u laufzettel -f # Protokoll mitlesen
|
||||
journalctl -u laufzettel -n 100 --no-pager
|
||||
```
|
||||
|
||||
Eigene Zusätze zur Unit gehören nach `/etc/systemd/system/laufzettel.service.d/` als Drop-in.
|
||||
Änderungen direkt in `laufzettel.service` überschreibt der nächste Lauf des Installationsskripts.
|
||||
|
||||
Nachsehen, ob die Anwendung antwortet:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:3000/api/gesundheit
|
||||
# {"status":"ok"}
|
||||
```
|
||||
|
||||
In die Datenbank sehen — über den Socket, denn einen Port gibt es nicht:
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql laufzettel
|
||||
```
|
||||
|
||||
## Aktualisieren
|
||||
|
||||
Dasselbe Skript noch einmal aufrufen:
|
||||
|
||||
```bash
|
||||
sudo bash /opt/laufzettel/deploy/install.sh
|
||||
```
|
||||
|
||||
Es holt den Quellcode (`git pull --ff-only`), baut neu, spielt die Migrationen ein, fährt den Seed
|
||||
(der nichts ändert, was schon da ist) und startet den Dienst neu. **Geheimnisse und Daten bleiben:**
|
||||
`laufzettel.env` wird nicht angefasst, die Datenbank bleibt stehen, das Admin-Passwort wird nicht neu
|
||||
erzeugt.
|
||||
|
||||
Vorher eine Sicherung ziehen. Eine Migration fährt nicht rückwärts.
|
||||
|
||||
Schlägt `git pull --ff-only` fehl, liegen in `/opt/laufzettel` eigene Änderungen. Nachsehen mit:
|
||||
|
||||
```bash
|
||||
sudo -u laufzettel git -C /opt/laufzettel status
|
||||
```
|
||||
|
||||
## Die Selbstprüfung
|
||||
|
||||
```bash
|
||||
sudo bash /opt/laufzettel/deploy/install.sh --test
|
||||
```
|
||||
|
||||
Prüft vier Dinge und nennt jedes einzeln mit `[ok]` oder `[FEHLER]`:
|
||||
|
||||
1. `ss -ltn` zeigt **keinen** Port 5432 — PostgreSQL spricht kein TCP
|
||||
2. Die Datenbank ist über den Socket erreichbar, Migration und Seed sind gelaufen (mindestens sieben
|
||||
Vorlagen)
|
||||
3. `/etc/laufzettel/laufzettel.env` hat `600 root:root`
|
||||
4. `/api/gesundheit` antwortet mit `status ok`
|
||||
|
||||
Das ist der schnellste Weg, nach einem Umzug oder einer Aktualisierung zu prüfen, dass der Stand noch
|
||||
der beschriebene ist.
|
||||
|
||||
## Was du beim ersten Lauf selbst nachprüfen musst
|
||||
|
||||
Der `pct`-Teil ist ungetestet — kein PVE-Host stand zur Verfügung. Diese Liste ist die Gegenprobe.
|
||||
Geh sie in dieser Reihenfolge durch; jede Zeile nennt den Schritt, das erwartete Ergebnis und was zu
|
||||
tun ist, wenn es abweicht.
|
||||
|
||||
| Schritt | Erwartet | Wenn es abweicht |
|
||||
|---|---|---|
|
||||
| `bash laufzettel.sh --dry-run` auf dem PVE-Host | Ein vollständiger `pct create`-Aufruf mit CTID, Vorlage, `--cores 2`, `--memory 2048`, `--rootfs …:8`, `--unprivileged 1`, `--net0 name=eth0,bridge=vmbr0,ip=dhcp`. Es wird **nichts** angelegt. | Fehlt ein Wert oder steht ein leerer Platzhalter im Aufruf, den Befehl **nicht** von Hand abschicken. Stattdessen den Container über die PVE-Oberfläche anlegen und Weg B gehen. |
|
||||
| Vorlage vorhanden | `pveam list local` nennt ein `debian-13-standard`-Archiv. Fehlt es, holt das Skript es mit `pveam download`. | Findet `pveam available` keine Debian-13-Vorlage, ist der Vorlagenindex alt: `pveam update`. Danach noch einmal. |
|
||||
| `pct create` läuft durch | Der Container erscheint in `pct list` mit dem Hostnamen `laufzettel`. | Meldet PVE „storage does not support container directories“, ist der gewählte Speicher kein Verzeichnis- oder ZFS-Speicher. Im Erweitert-Dialog einen anderen wählen (`local-lvm`, `local-zfs`). |
|
||||
| Der Container bekommt eine Adresse | `pct exec <ctid> -- ip -4 addr` zeigt eine Adresse auf `eth0`, und `pct exec <ctid> -- ping -c1 deb.debian.org` kommt durch. | Ohne DHCP im Netz bleibt die Adresse leer und die Installation scheitert beim ersten `apt`. Container anhalten, im Erweitert-Dialog eine feste IP samt Gateway setzen. |
|
||||
| `install.sh` läuft im Container | Neun Schritte, jeder mit Meldung, am Ende der Abschlussbildschirm mit Adresse, Kennung und Passwort. | Bricht ein Schritt ab, steht die Ursache in der Meldung. Der Lauf ist wiederholbar: `pct enter <ctid>`, dann `bash /opt/laufzettel/deploy/install.sh` erneut. |
|
||||
| **Kein TCP-Port 5432** | `pct exec <ctid> -- ss -ltn` zeigt 5432 **nicht**. | Steht der Port da, hat `listen_addresses = ''` nicht gegriffen: prüfen, ob `/etc/postgresql/17/main/postgresql.conf` die Zeile `include_dir = 'conf.d'` enthält, dann `systemctl restart postgresql@17-main`. |
|
||||
| Der Dienst läuft | `pct exec <ctid> -- systemctl is-active laufzettel` sagt `active`, und `curl` auf `/api/gesundheit` antwortet `{"status":"ok"}`. | `pct exec <ctid> -- journalctl -u laufzettel -n 50 --no-pager`. Häufigste Ursache: der Bau ist an zu wenig RAM gescheitert und `.next/standalone` ist unvollständig. |
|
||||
| Umgebungsdatei geschützt | `stat -c '%a %U:%G' /etc/laufzettel/laufzettel.env` → `600 root:root`. | Rechte richtigstellen: `chown root:root`, `chmod 0600`. Und prüfen, wer in der Zwischenzeit lesen konnte. |
|
||||
| Neustart übersteht alles | `pct reboot <ctid>`, danach antwortet `/api/gesundheit` ohne Zutun wieder. | Kommt der Dienst nicht von selbst, fehlt das `enable`: `systemctl enable laufzettel` und `systemctl enable postgresql@17-main`. |
|
||||
| Zweiter Lauf aktualisiert | `install.sh` noch einmal: „laufzettel.env ist vorhanden und bleibt unangetastet“, „Datenbank laufzettel vorhanden — Daten bleiben, wie sie sind“, Passwort „unverändert“. | Wird ein **neues** Admin-Passwort angezeigt, war das Konto nicht auffindbar — dann steht in `laufzettel.env` eine andere `SEED_ADMIN_EMAIL` als erwartet. Nachsehen, bevor du weitermachst. |
|
||||
| **Der Dienst ist von außen erreichbar** | `curl http://<container-ip>:3000/api/gesundheit` vom PVE-Host aus antwortet `{"status":"ok"}`. | Antwortet nur `curl` **im** Container, aber nicht von außen, steht in `/etc/laufzettel/laufzettel.env` noch `HOSTNAME="127.0.0.1"`. Das ist die richtige Voreinstellung für einen Proxy im selben Container, aber nicht für einen Proxy davor: auf `0.0.0.0` setzen (oder auf die Container-IP), `systemctl restart laufzettel`, und eine Firewall-Regel, die den Port nur dem Proxy erlaubt. |
|
||||
| **`APP_URL` passt zum Namen, unter dem du später zugreifst** | In `laufzettel.env` steht die öffentliche Adresse. Ohne `--url` setzt `install.sh` `http://<Container-IP>:3000`. | Richtigstellen, **bevor** der erste Aufkleber gedruckt wird: Datei bearbeiten, `systemctl restart laufzettel`. Siehe [`reverse-proxy.md`](reverse-proxy.md). |
|
||||
| Die Anwendung ist bedienbar | Anmelden, einen Lauf öffnen, **einen Schritt abhaken**, Seite neu laden, Haken ist noch da. | Bleibt der Haken nicht, ist der Reverse Proxy falsch eingestellt — mit hoher Wahrscheinlichkeit die `Host`-Kopfzeile. Siehe [`reverse-proxy.md`](reverse-proxy.md). |
|
||||
| Die Sicherung funktioniert | Einmal `pg_dump` ziehen und in eine zweite, leere Datenbank zurückspielen. | Siehe [`sicherung.md`](sicherung.md). Eine Sicherung, die nie zurückgespielt wurde, ist keine. |
|
||||
|
||||
Der letzte Punkt ist der wichtigste. Alles davor kann man reparieren.
|
||||
|
||||
## Wenn es klemmt
|
||||
|
||||
| Symptom | Ursache und was zu tun ist |
|
||||
|---|---|
|
||||
| „Das Skript … braucht root“ | Mit `sudo bash install.sh` aufrufen, nicht ohne. |
|
||||
| „Erwartet ist Debian 13, gefunden …“ | Nur eine Warnung, der Lauf geht weiter. Auf älteren Debians kann die PostgreSQL-Fassung abweichen; das Skript nimmt dann das Metapaket `postgresql`. |
|
||||
| „runuser fehlt (Paket util-linux)“ | Ein sehr schlankes Abbild. `apt install util-linux`. |
|
||||
| „/opt/laufzettel ist nicht leer, enthält aber kein Git-Arbeitsverzeichnis“ | Dort liegt ein alter Stand ohne `.git`. Verschieben oder löschen, dann neu starten. |
|
||||
| `pnpm build` bricht mit `Killed` ab | Zu wenig RAM. Dem Container vorübergehend 4 GB geben (`pct set <ctid> --memory 4096`), erneut laufen lassen, danach zurückdrehen. |
|
||||
| „Der Dienst antwortet nach 60 s nicht“ | `journalctl -u laufzettel -n 50 --no-pager`. Meist fehlt etwas in `laufzettel.env` oder die Datenbank ist nicht erreichbar. |
|
||||
| „In `laufzettel.env` fehlt `DATABASE_URL`, `APP_SECRET` oder `APP_URL`“ | Die Datei ist beschädigt. Ergänzen ist der richtige Weg. Löschen legt sie neu an — dabei geht das bisherige `APP_SECRET` verloren: alle Sitzungen enden, und die verschlüsselt gespeicherten LDAP-Bind-Passwörter sind unlesbar und müssen neu eingegeben werden. |
|
||||
| `psql: FATAL: password authentication failed` | Das Passwort in `DATABASE_URL` passt nicht zur Rolle. Das Skript setzt es nur dann neu, wenn es die Umgebungsdatei selbst neu angelegt hat. Von Hand: `sudo -u postgres psql -c '\password laufzettel'` und den Wert in `laufzettel.env` nachziehen. |
|
||||
| Kein systemd im Container | Das Skript erkennt es, schreibt die Unit trotzdem und sagt, wie der Dienst von Hand zu starten ist. Tritt bei einem `docker run debian:13` auf, nicht in einem LXC. |
|
||||
Reference in New Issue
Block a user