Compare commits

..
9 Commits
Author SHA1 Message Date
florian.wildeandClaude Opus 5 11e514dd25 feat(deploy): Update und Sicherung fuer beide Installationsarten (#031)
Prüfung / pruefung (push) Canceled after 0s
deploy/update.sh und deploy/backup.sh konnten nur Docker. Beide erkennen die
Installationsart jetzt selbst und beherrschen auch die Installation als
Dienst: npm ci, prisma migrate deploy, bauen, systemctl restart - und
pg_dump ueber DATABASE_URL fuer die Sicherung.

Beim Durchspielen gegen einen laufenden Stapel zwei Fehler gefunden:

1. Die Zustandspruefung des Web-Containers konnte nie gelingen. Next bindet
   im Standalone-Modus an die Adresse aus HOSTNAME, und Docker setzt dort
   den Containernamen - der Server lauschte nur auf der Container-IP. Der
   Dienst web blieb dauerhaft auf "health: starting". Behoben mit
   ENV HOSTNAME=0.0.0.0; geprueft wird jetzt ueber 127.0.0.1.
2. Eine fehlgeschlagene Sicherung hielt die Aktualisierung nicht auf. Jetzt
   bricht sie ab, bevor etwas veraendert wird, und nennt die Auswege.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 07:26:30 +02:00
florian.wilde 9f1eb189db Merge branch 'phase/031-aktualisierung'
Prüfung / pruefung (push) Canceled after 0s
2026-10-03 00:25:03 +02:00
florian.wildeandClaude Opus 5 585173622d fix(deps): Next.js auf 16.3.8 wegen kritischer Meldung
Fuer Next.js 16.2.0 bis 16.3.5 wurde eine als kritisch eingestufte Luecke in
next/og gemeldet (RCE, GHSA-vcvr-r3jv-pc5j). ProxLedger nutzt next/og nicht
und war damit nicht angreifbar; vor einem Produktivgang gehoert die Fassung
trotzdem aktualisiert.

Ausserdem @simplewebauthn/server auf 14.0.3. Tests, Typen, Lint und Build
gruen, npm audit ohne Befund.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 00:25:03 +02:00
florian.wildeandClaude Opus 5 9c6c6a1c35 docs: echte Repository-Adresse eintragen
Prüfung / pruefung (push) Canceled after 0s
README und Inbetriebnahme nannten Platzhalter, die community-scripts-Dateien
einen falschen Benutzerpfad (fw statt florian.wilde) - der Einzeiler haette
dort ins Leere gezeigt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 15:21:12 +02:00
florian.wilde 2195418867 Merge branch 'phase/030-netbird-proxy'
Prüfung / pruefung (push) Canceled after 0s
2026-09-19 10:56:05 +02:00
florian.wildeandClaude Opus 5 ddca903687 docs(betrieb): netbird Reverse Proxy (Beta) aufnehmen (#030)
netbird bringt seit v0.65 einen eigenen Reverse Proxy mit, v0.72 ergaenzte
private Dienste. Damit entfaellt ein zusaetzlicher nginx: TLS-Terminierung
und Weiterleitung durchs Mesh, ohne offenen Port.

Wichtigster Hinweis in der Anleitung: Eine Anmeldehuerde vor dem Dienst
(SSO, Passwort, PIN) gilt auch fuer /api/agent. Die Agents weisen sich mit
eigenem Token aus und koennen sich nirgends anmelden - sie wuerden stumm ins
Leere melden. Zwei Auswege beschrieben: zwei Dienste oder ein privater
Dienst, der nur aus dem netbird-Netz erreichbar ist.

Beta-Einschraenkungen benannt: kein Rosenpass, selbst gehostet nur mit
Traefik (TLS-Passthrough).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 10:56:05 +02:00
florian.wildeandClaude Opus 5 eaaabd8e75 docs(anleitung): drei Anmeldewege im Handbuch beschreiben (#028)
Prüfung / pruefung (push) Canceled after 0s
Das Kapitel nannte nur Passwort und OIDC; der Passkey-Weg fehlte. Bild der
Anmeldeseite aufgefrischt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 00:53:43 +02:00
florian.wilde 6317b731a2 Merge branch 'phase/029-betriebsdoku'
Prüfung / pruefung (push) Canceled after 0s
2026-09-19 00:51:16 +02:00
florian.wildeandClaude Opus 5 b6fdd4f64a docs(betrieb): Einrichtung Schritt fuer Schritt dokumentieren (#029)
Vier Anleitungen unter docs/betrieb/: Inbetriebnahme in der richtigen
Reihenfolge, HTTPS mit nginx Proxy Manager sowie netbird als privates Netz,
OIDC mit authentik samt Fehlerbildern, und Sicherung einschliesslich
Zurueckspielen.

Beim Aufschreiben zwei Fehler gefunden, die nur hinter einem Reverse Proxy
auftreten: Die OIDC-Rueckkehradresse uebersah die im Dashboard eingetragene
Adresse, und beim Einloesen des Codes wurde die interne Adresse verwendet -
der Anbieter haette den Tausch abgelehnt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 00:51:16 +02:00
33 changed files with 886 additions and 111 deletions
+29
View File
@@ -17,6 +17,35 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
### Sicherheit
## [0.20.0] – 2026-10-03
### Hinzugefügt
- `deploy/update.sh` und `deploy/backup.sh` erkennen die Installationsart selbst und beherrschen jetzt auch die Installation als Dienst (Node und PostgreSQL direkt im Container) – nicht mehr nur Docker (#031)
- Eine fehlgeschlagene Sicherung bricht die Aktualisierung ab, bevor etwas verändert wird (#031)
### Behoben
- Die Zustandsprüfung des Web-Containers konnte nie gelingen: Next bindet im Standalone-Modus an die Adresse aus `HOSTNAME`, und Docker setzt dort den Containernamen – der Server lauschte nur auf der Container-IP. `web` blieb dauerhaft auf „health: starting“ (#031)
## [0.19.2] – 2026-10-03
### Sicherheit
- Next.js auf 16.3.8 angehoben. Für 16.2.0 bis 16.3.5 wurde eine als **kritisch** eingestufte Lücke in `next/og` gemeldet (Remote Code Execution, GHSA-vcvr-r3jv-pc5j). ProxLedger nutzt `next/og` nicht, war also nicht angreifbar – die Fassung gehört trotzdem aktualisiert, bevor etwas produktiv läuft
- `@simplewebauthn/server` auf 14.0.3
## [0.19.1] – 2026-09-19
### Hinzugefügt
- Anleitung für den netbird Reverse Proxy (Beta): Dienst anlegen, Zertifikat, private Dienste – samt Warnung, dass eine Anmeldehürde vor dem Dienst auch die Agents aussperrt (#030)
## [0.19.0] – 2026-09-19
### Hinzugefügt
- `docs/betrieb/`: Inbetriebnahme, Reverse Proxy (nginx Proxy Manager und netbird), OIDC mit authentik und Sicherung – jeweils Schritt für Schritt (#029)
### Behoben
- Die OIDC-Rückkehradresse berücksichtigte die im Dashboard eingetragene Adresse nicht, sondern nur `APP_URL` (#029)
- Beim Einlösen des OIDC-Codes wurde hinter einem Reverse Proxy die interne Adresse verwendet; der Tausch wäre am Anbieter gescheitert (#029)
## [0.18.0] – 2026-09-19
### Hinzugefügt
+17 -2
View File
@@ -2,7 +2,7 @@
## Zeitübersicht
**Gesamt:** 14 h 23 min
**Gesamt:** 16 h 08 min
| Issue | Zeit |
|---|---|
@@ -34,6 +34,9 @@
| #026 Auslieferung | 0 h 40 min |
| #027 Rollen und Benutzerverwaltung | 1 h 20 min |
| #028 Passkeys | 0 h 45 min |
| #029 Einrichtungsdokumentation | 0 h 40 min |
| #030 netbird Reverse Proxy | 0 h 20 min |
| #031 Update für beide Varianten | 0 h 45 min |
| Meilenstein | Zeit |
|---|---|
@@ -56,7 +59,7 @@
## 2026-09-17 · Sitzung 1 · 19:36–laufend
**Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0)
**Version:** 0.18.0
**Version:** 0.20.0
### Erledigt
- Auftrag gelesen, Rückfragen gestellt
@@ -158,6 +161,18 @@
- Dabei aufgefallen: Neu angelegte OIDC-Konten hätten die Modellvorgabe ADMIN geerbt – behoben, mit eigener Einstellung und Test
- Passkeys (#028): passwortlos, mehrere je Konto, Aufgabe in einem kurzlebigen Cookie. Nur über HTTPS nutzbar – die Seite sagt es, statt still zu scheitern
- Einrichtungsdokumentation (#029): Beim Aufschreiben des OIDC-Wegs fielen zwei Fehler auf, die nur hinter einem Reverse Proxy auftreten – Rückkehradresse und Code-Einlösung nahmen die interne Adresse. Beides behoben
- Erkenntnis: Eine Anleitung Schritt für Schritt aufzuschreiben findet Fehler, die kein Test findet, weil sie nur in einer bestimmten Betriebsform auftreten
- netbird Reverse Proxy (#030) in die Anleitung aufgenommen – Florians Hinweis, dass die Beta gut läuft. Beim Durchgehen der Möglichkeiten fiel der Fallstrick auf: Eine Anmeldehürde vor dem Dienst trifft auch `/api/agent` und legt alle Agents still
### 03.10.2026 – Prüfung vor dem Einsatz
- `npm audit` vor der Freigabe erneut laufen lassen: In den zwei Wochen seit dem letzten Stand kam eine kritische Meldung zu Next.js dazu (RCE in `next/og`). Wir nutzen `next/og` nicht, waren also nicht angreifbar – angehoben auf 16.3.8, Tests und Build grün
- Lehre für den Betrieb: Vor jedem Produktivgang und danach regelmäßig `npm audit` prüfen, nicht nur bei Änderungen am Code
- Update- und Sicherungsskript für beide Installationsarten (#031). Beim Durchspielen zwei Fehler gefunden: Die Zustandsprüfung des Web-Containers konnte nie gelingen (Next bindet an `HOSTNAME`, Docker setzt dort den Containernamen), und eine fehlgeschlagene Sicherung hielt die Aktualisierung nicht auf
- Lehre: Ein Skript, das nie unter echten Bedingungen gelaufen ist, ist eine Behauptung. Erst der Durchlauf gegen einen laufenden Stapel hat beide Fehler gezeigt
### Nächster Schritt
- #016 Benutzerhandbuch mit Screenshots, danach #017 Favicons und #019 CI-Pipeline
- Für 1.0.0 fehlt noch: PMG (kein Host verfügbar) und der Fall „aktive Subscription auf PBS“ (#023)
+3
View File
@@ -24,6 +24,9 @@ RUN npx prisma generate \
FROM basis AS laufzeit
ENV NODE_ENV=production
ENV PORT=3000
# Ohne diese Zeile nimmt Next den von Docker gesetzten HOSTNAME als Bindeadresse und lauscht
# nur auf der Container-IP. Die Zustandsprüfung über 127.0.0.1 schlägt dann immer fehl.
ENV HOSTNAME=0.0.0.0
RUN addgroup -g 1001 -S proxledger && adduser -S proxledger -u 1001 -G proxledger
+13 -3
View File
@@ -13,7 +13,7 @@ Auf jedem Host meldet ein schlanker Agent täglich per HTTPS seinen Status (Push
Auf einem Rechner mit Docker:
```bash
git clone <dieses-repo> proxledger && cd proxledger
git clone https://git.wilde-server.de/florian.wilde/ProxLedger.git proxledger && cd proxledger
cp .env.example .env
# Pflichtangaben setzen: APP_SECRET, POSTGRES_PASSWORD, ADMIN_PASSWORD, APP_URL
docker compose up -d
@@ -37,7 +37,7 @@ bash deploy/lxc-installer.sh # eigener Installer: Container mit Docker
```bash
# Nach Art der Proxmox VE Helper-Scripts: Container mit Node und PostgreSQL direkt darin
bash -c "$(curl -fsSL https://<git>/ProxLedger/raw/branch/main/deploy/community-scripts/ct/proxledger.sh)"
bash -c "$(curl -fsSL https://git.wilde-server.de/florian.wilde/ProxLedger/raw/branch/main/deploy/community-scripts/ct/proxledger.sh)"
```
Der zweite Weg folgt den Konventionen von [community-scripts.org](https://community-scripts.org) und
@@ -122,13 +122,23 @@ sie sich mit eigenen Tokens ausweisen.
- **Lokaler Zugang:** E-Mail und Passwort aus dem Seed (`ADMIN_EMAIL`, `ADMIN_PASSWORD`). Fünf Fehlversuche
je Kennung und Viertelstunde, danach Sperre (einstellbar unter *Einstellungen → System*).
Benutzer sperren oder Passwort setzen: `npm run benutzer -- sperren <email>`.
- **OIDC (z. B. authentik):** `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` setzen. Rückkehradresse
- **Passkey:** passwortlos über WebAuthn, mehrere je Konto, Verwaltung unter *Meine Passkeys*. Setzt HTTPS voraus (außer `localhost`).
- **OIDC (z. B. authentik):** `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` setzen – Schritt für Schritt in [docs/betrieb/authentik.md](docs/betrieb/authentik.md). Rückkehradresse
ist `<APP_URL>/api/auth/oidc/callback`. Ohne diese Werte erscheint nur der lokale Zugang – der bleibt
absichtlich immer nutzbar, falls der Anmeldedienst ausfällt. Unter *Einstellungen → System* lässt sich
einschränken, welche E-Mail-Domänen zugelassen sind und ob unbekannte Konten angelegt werden dürfen.
- **`APP_SECRET`** (mindestens 32 Zeichen) signiert die Sitzungs-Cookies. Ändert sich der Wert, sind alle
Sitzungen ungültig.
## Einrichtung im Betrieb
| Anleitung | Inhalt |
|---|---|
| [Inbetriebnahme](docs/betrieb/inbetriebnahme.md) | von null zum meldenden Host, in der richtigen Reihenfolge |
| [Reverse Proxy](docs/betrieb/reverse-proxy.md) | HTTPS mit nginx Proxy Manager, netbird Reverse Proxy (Beta) oder netbird pur |
| [authentik](docs/betrieb/authentik.md) | Anmeldung über OIDC, samt Fehlerbildern |
| [Sicherung](docs/betrieb/sicherung.md) | was gesichert wird – und wie man es zurückspielt |
## Einstellungen
Die meisten Betriebswerte stehen unter *Einstellungen → System* und wirken sofort: Dashboard-Adresse,
+42 -8
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# Sichert Datenbank und hochgeladene PDFs von ProxLedger.
# Sichert Datenbank, hochgeladene PDFs und die Konfiguration von ProxLedger.
# Erkennt selbst, ob die Installation mit Docker oder als Dienst im Container läuft.
#
# ./backup.sh /pfad/zum/sicherungsordner
#
@@ -13,17 +14,50 @@ set -o pipefail
ZIEL="${1:-/var/backups/proxledger}"
AUFBEWAHRUNG_TAGE="${AUFBEWAHRUNG_TAGE:-30}"
STAND=$(date +%Y%m%d-%H%M%S)
COMPOSE_VERZEICHNIS="${COMPOSE_VERZEICHNIS:-/opt/proxledger}"
VERZEICHNIS="${COMPOSE_VERZEICHNIS:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
mkdir -p "$ZIEL"
cd "$COMPOSE_VERZEICHNIS"
cd "$VERZEICHNIS"
echo "Sichere Datenbank …"
docker compose exec -T postgres pg_dump -U "${POSTGRES_USER:-proxledger}" "${POSTGRES_DB:-proxledger}" \
| gzip > "${ZIEL}/datenbank-${STAND}.sql.gz"
# Siehe deploy/update.sh – der Dienst ist das eindeutigere Merkmal.
if systemctl cat proxledger.service >/dev/null 2>&1; then
ART="dienst"
else
ART="docker"
fi
echo "Sichere hochgeladene Dateien …"
docker compose run --rm -T --entrypoint tar web -czf - /data/uploads > "${ZIEL}/uploads-${STAND}.tar.gz" 2>/dev/null || true
echo "Sichere ProxLedger (${ART}) nach ${ZIEL} …"
if [ "$ART" = "docker" ]; then
echo "Sichere Datenbank …"
docker compose exec -T postgres pg_dump -U "${POSTGRES_USER:-proxledger}" "${POSTGRES_DB:-proxledger}" \
| gzip > "${ZIEL}/datenbank-${STAND}.sql.gz"
echo "Sichere hochgeladene Dateien …"
docker compose run --rm -T --entrypoint tar web -czf - /data/uploads > "${ZIEL}/uploads-${STAND}.tar.gz" 2>/dev/null || true
else
# DATABASE_URL steht in der .env; pg_dump versteht sie direkt.
# shellcheck disable=SC1091
set -o allexport
. ./.env
set +o allexport
[ -n "${DATABASE_URL:-}" ] || {
echo "DATABASE_URL fehlt in der .env – ohne sie keine Sicherung." >&2
exit 1
}
echo "Sichere Datenbank …"
pg_dump "$DATABASE_URL" | gzip > "${ZIEL}/datenbank-${STAND}.sql.gz"
echo "Sichere hochgeladene Dateien …"
UPLOADS="${UPLOAD_DIR:-${VERZEICHNIS}/data/uploads}"
if [ -d "$UPLOADS" ]; then
tar -czf "${ZIEL}/uploads-${STAND}.tar.gz" -C "$(dirname "$UPLOADS")" "$(basename "$UPLOADS")"
else
echo " (kein Upload-Verzeichnis unter ${UPLOADS})"
fi
fi
echo "Sichere Konfiguration …"
cp .env "${ZIEL}/env-${STAND}.txt"
+1 -1
View File
@@ -16,7 +16,7 @@ systemd-Dienste ein (Dashboard und Worker) und schreiben die erzeugten Zugangsda
Auf dem Proxmox-VE-Host als root:
```bash
bash -c "$(curl -fsSL https://git.wilde-server.de/fw/ProxLedger/raw/branch/main/deploy/community-scripts/ct/proxledger.sh)"
bash -c "$(curl -fsSL https://git.wilde-server.de/florian.wilde/ProxLedger/raw/branch/main/deploy/community-scripts/ct/proxledger.sh)"
```
Das Gerüst (`build.func`, `install.func`) lädt das Skript von den community-scripts nach – das ist
+1 -1
View File
@@ -5,7 +5,7 @@ source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_
# Copyright (c) 2021-2026 tteck
# Author: Florian Wilde
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://git.wilde-server.de/fw/ProxLedger
# Source: https://git.wilde-server.de/florian.wilde/ProxLedger
APP="ProxLedger"
var_tags="${var_tags:-monitoring;licensing}"
@@ -3,7 +3,7 @@
# Copyright (c) 2021-2026 tteck
# Author: Florian Wilde
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://git.wilde-server.de/fw/ProxLedger
# Source: https://git.wilde-server.de/florian.wilde/ProxLedger
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
@@ -14,7 +14,7 @@ network_check
update_os
# Die Quelle laesst sich ueberschreiben, solange das Repository nicht oeffentlich ist.
QUELLE="${PROXLEDGER_REPO:-https://git.wilde-server.de/fw/ProxLedger.git}"
QUELLE="${PROXLEDGER_REPO:-https://git.wilde-server.de/florian.wilde/ProxLedger.git}"
ZWEIG="${PROXLEDGER_BRANCH:-main}"
msg_info "Installing Dependencies"
+2 -1
View File
@@ -181,4 +181,5 @@ printf ' 3. Für den Betrieb über HTTPS einen Reverse Proxy davorsetzen (Beisp
echo
gelb "APP_URL in /opt/proxledger/.env auf die endgültige Adresse setzen, sobald der Reverse Proxy steht."
printf ' Logs ansehen: pct exec %s -- docker compose -f /opt/proxledger/docker-compose.yml logs -f\n' "$CTID"
printf ' Aktualisieren: pct exec %s -- bash -c "cd /opt/proxledger && git pull && docker compose up -d --build"\n' "$CTID"
printf ' Aktualisieren: pct exec %s -- bash -c "bash /opt/proxledger/deploy/update.sh"\n' "$CTID"
printf ' Sichern: pct exec %s -- bash -c "bash /opt/proxledger/deploy/backup.sh /var/backups/proxledger"\n' "$CTID"
+85 -17
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
# ProxLedger aktualisieren (Docker-Installation).
# ProxLedger aktualisieren – erkennt selbst, ob die Installation mit Docker
# oder als Dienst im Container (Node + PostgreSQL direkt) läuft.
#
# bash deploy/update.sh # Quelltext holen, neu bauen, migrieren, neu starten
# bash deploy/update.sh # sichern, holen, bauen, migrieren, neu starten
# bash deploy/update.sh --ohne-sicherung
#
# Vor dem Aktualisieren wird gesichert – das dauert Sekunden und erspart schlechte Abende.
@@ -23,38 +24,105 @@ fehler() {
SICHERN="ja"
[ "${1:-}" = "--ohne-sicherung" ] && SICHERN="nein"
command -v docker >/dev/null 2>&1 || fehler "Docker nicht gefunden."
[ -f .env ] || fehler "Keine .env gefunden – wird dieses Skript im richtigen Verzeichnis ausgeführt?"
# Wohin gesichert wird. Der Standardort gehört root; wer anders arbeitet, setzt SICHERUNG_ZIEL.
SICHERUNG_ZIEL="${SICHERUNG_ZIEL:-/var/backups/proxledger}"
# ---------------------------------------------------------------- Art der Installation
#
# Der systemd-Dienst ist das eindeutigere Merkmal: Die docker-compose.yml liegt auch im
# Quelltext der Dienst-Installation, ein Dienst namens proxledger aber nur dort.
installationsart() {
if systemctl cat proxledger.service >/dev/null 2>&1; then
printf 'dienst'
elif command -v docker >/dev/null 2>&1 && [ -f docker-compose.yml ]; then
printf 'docker'
fi
}
ART=$(installationsart)
[ -n "$ART" ] || fehler "Keine Installation gefunden – weder der Dienst proxledger noch Docker mit docker-compose.yml."
blau "Installationsart: ${ART}"
# ---------------------------------------------------------------- Sicherung
if [ "$SICHERN" = "ja" ]; then
blau "Sicherung anlegen …"
bash deploy/backup.sh
blau "Sicherung anlegen nach ${SICHERUNG_ZIEL} …"
if ! bash deploy/backup.sh "$SICHERUNG_ZIEL"; then
fehler "Die Sicherung ist fehlgeschlagen – deshalb wird nicht aktualisiert.
Häufigste Ursache: fehlende Rechte am Zielordner. Mögliche Wege:
sudo bash deploy/update.sh
SICHERUNG_ZIEL=/pfad/mit/rechten bash deploy/update.sh
bash deploy/update.sh --ohne-sicherung (nur wenn anderweitig gesichert ist)"
fi
gruen "Sicherung abgeschlossen."
else
printf '\033[1;33m! Ohne Sicherung – auf eigenes Risiko.\033[0m\n'
fi
# ---------------------------------------------------------------- Quelltext
if [ -d .git ]; then
blau "Neuen Stand holen …"
git pull --ff-only
else
printf '\033[1;33m! Kein Git-Verzeichnis – der Quelltext bleibt wie er ist.\033[0m\n'
fi
blau "Abbilder neu bauen …"
docker compose build --pull
# ---------------------------------------------------------------- Aktualisieren
if [ "$ART" = "docker" ]; then
[ -f .env ] || fehler "Keine .env gefunden – wird dieses Skript im richtigen Verzeichnis ausgeführt?"
blau "Datenbank migrieren …"
docker compose run --rm migrate
blau "Abbilder neu bauen …"
docker compose build --pull
blau "Dienste neu starten …"
docker compose up -d
blau "Datenbank migrieren …"
docker compose run --rm migrate
# Kurz warten und nachsehen, ob das Dashboard wieder antwortet.
blau "Dienste neu starten …"
docker compose up -d
else
command -v npm >/dev/null 2>&1 || fehler "npm nicht gefunden – gehört zur Installation als Dienst."
[ -f .env ] || fehler "Keine .env gefunden (erwartet in ${VERZEICHNIS})."
# Die .env trägt APP_SECRET; ein git pull fasst sie nicht an, aber sicher ist sicher.
cp .env "/tmp/proxledger-env-$(date +%s).sicherung"
blau "Abhängigkeiten installieren …"
npm ci
blau "Datenbank migrieren …"
npx prisma generate
npx prisma migrate deploy
blau "Anwendung bauen …"
npm run build
npm run build:worker
blau "Dienste neu starten …"
systemctl restart proxledger proxledger-worker
fi
# ---------------------------------------------------------------- Prüfen
blau "Warte auf das Dashboard …"
for versuch in $(seq 1 30); do
if docker compose exec -T web wget -qO- http://localhost:3000/api/gesundheit >/dev/null 2>&1; then
PORT="${PORT:-3000}"
for versuch in $(seq 1 45); do
if [ "$ART" = "docker" ]; then
erreichbar=$(docker compose exec -T web wget -qO- "http://127.0.0.1:3000/api/gesundheit" >/dev/null 2>&1 && echo ja || echo nein)
else
erreichbar=$(curl -fsS -o /dev/null --max-time 2 "http://localhost:${PORT}/api/gesundheit" && echo ja || echo nein)
fi
if [ "$erreichbar" = "ja" ]; then
gruen "ProxLedger läuft wieder (nach ${versuch} s)."
docker compose ps
[ "$ART" = "docker" ] && docker compose ps
[ "$ART" = "dienst" ] && systemctl --no-pager --lines=0 status proxledger proxledger-worker | grep -E "●|Active:"
exit 0
fi
sleep 1
done
fehler "Das Dashboard antwortet nicht. Protokoll ansehen: docker compose logs -n 50 web"
if [ "$ART" = "docker" ]; then
fehler "Das Dashboard antwortet nicht. Protokoll ansehen: docker compose logs -n 50 web"
else
fehler "Das Dashboard antwortet nicht. Protokoll ansehen: journalctl -u proxledger -n 50"
fi
+1 -1
View File
@@ -60,7 +60,7 @@ services:
volumes:
- uploads:/data
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/gesundheit"]
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/api/gesundheit"]
interval: 30s
timeout: 5s
retries: 5
+36 -12
View File
@@ -24,6 +24,9 @@ auch mit Hosts hinter NAT.
---
> **Einrichtung statt Bedienung?** Reverse Proxy, OIDC und Sicherung stehen in
> [docs/betrieb/](../betrieb/README.md). Dieses Handbuch beschreibt die tägliche Arbeit.
## 1. Installation
### Mit Docker
@@ -73,10 +76,16 @@ Traefik liegen in [`deploy/reverse-proxy/`](../../deploy/reverse-proxy/).
![Anmeldeseite](bilder/login.png)
Anmelden mit der E-Mail-Adresse und dem Passwort aus der `.env`. Ist ein Anmeldedienst über OIDC
eingerichtet (`OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`), erscheint zusätzlich ein Knopf
„Mit authentik anmelden“. Der lokale Zugang bleibt absichtlich immer nutzbar – falls der Anmeldedienst
einmal ausfällt, kommst du trotzdem hinein.
Es gibt drei gleichwertige Wege hinein:
- **Passkey** – der Knopf ganz oben. Der Browser fragt nach Fingerabdruck, Gesicht oder PIN. Anlegen
lässt sich einer unter *Meine Passkeys* ([Kapitel 9b](#9b-benutzer-rollen-und-passkeys)).
- **Passwort** – E-Mail-Adresse und Passwort aus der `.env` bzw. aus der Benutzerverwaltung.
- **OIDC** – ist ein Anmeldedienst eingerichtet, erscheint zusätzlich „Mit authentik anmelden“
(Einrichtung: [docs/betrieb/authentik.md](../betrieb/authentik.md)).
Der lokale Zugang bleibt absichtlich immer nutzbar – falls der Anmeldedienst einmal ausfällt, kommst du
trotzdem hinein.
Nach der Anmeldung landest du auf dem Dashboard:
@@ -387,31 +396,46 @@ Rückweg immer nutzbar.
**Sichern:**
```bash
./deploy/backup.sh /var/backups/proxledger
bash /opt/proxledger/deploy/backup.sh /var/backups/proxledger
```
Das Skript sichert Datenbank, hochgeladene PDFs und die `.env`. Für regelmäßige Sicherungen in die
Crontab eintragen:
Das Skript sichert Datenbank, hochgeladene PDFs und die `.env` – und erkennt selbst, ob die
Installation mit Docker oder als Dienst im Container läuft. Für regelmäßige Sicherungen in die Crontab:
```
30 2 * * * /opt/proxledger/deploy/backup.sh /var/backups/proxledger
```
**Zurückspielen:**
**Zurückspielen** (Docker):
```bash
gunzip -c datenbank-20260917-023000.sql.gz | docker compose exec -T postgres psql -U proxledger proxledger
```
Bei der Installation als Dienst stattdessen:
```bash
gunzip -c datenbank-20260917-023000.sql.gz | psql "$DATABASE_URL"
```
**Aktualisieren:**
```bash
cd /opt/proxledger
git pull
docker compose up -d --build
bash /opt/proxledger/deploy/update.sh
```
Migrationen laufen dabei automatisch.
Ein Befehl für beide Installationsarten. Der Reihe nach: sichern, neuen Stand holen, bauen,
migrieren, neu starten – und zum Schluss prüfen, ob das Dashboard wieder antwortet. Schlägt die
Sicherung fehl, bricht das Skript ab, **bevor** etwas verändert wird.
| Aufruf | Wirkung |
|---|---|
| `bash deploy/update.sh` | der übliche Weg |
| `SICHERUNG_ZIEL=/pfad bash deploy/update.sh` | anderer Ort für die Sicherung |
| `bash deploy/update.sh --ohne-sicherung` | nur, wenn anderweitig gesichert ist |
Beim LXC-Weg nach Art der Helper-Scripts geht es auch über das Skript auf dem Proxmox-Host – dort
erkennt das Gerüst eine vorhandene Installation und bietet die Aktualisierung an.
> `APP_SECRET` unbedingt mitsichern: Ohne diesen Wert sind alle Sitzungen ungültig. Und beachte, dass eine
> Sicherung die Zugangsdaten für SMTP und Webhook im Klartext enthält – sie gehört entsprechend geschützt.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

+13
View File
@@ -0,0 +1,13 @@
# Betrieb
Anleitungen für alles, was rund um ProxLedger eingerichtet wird.
| Dokument | Inhalt |
|---|---|
| [inbetriebnahme.md](inbetriebnahme.md) | Von null zum laufenden Dashboard – Docker oder LXC, erster Agent, erste Zuordnung |
| [reverse-proxy.md](reverse-proxy.md) | HTTPS mit nginx Proxy Manager, netbird Reverse Proxy (Beta) oder netbird pur |
| [authentik.md](authentik.md) | Anmeldung über OIDC |
| [sicherung.md](sicherung.md) | Was gesichert werden muss und wie man es zurückspielt |
Für die tägliche Arbeit **mit** ProxLedger ist das [Handbuch](../anleitung/README.md) gedacht.
Was gegen Angriffe getan wird (und was bewusst nicht), steht in [sicherheit.md](../sicherheit.md).
+118
View File
@@ -0,0 +1,118 @@
# Anmeldung über authentik (OIDC)
ProxLedger kann die Anmeldung an einen OIDC-Anbieter abgeben. Getestet ist authentik; mit jedem
anderen Anbieter, der OpenID Connect Discovery beherrscht, funktioniert es genauso.
**Der lokale Zugang bleibt daneben immer bestehen.** Das ist Absicht: Fällt authentik aus, kommt man
trotzdem noch ins Dashboard.
---
## 1. In authentik: Provider anlegen
*Applications → Providers → Create → OAuth2/OpenID Provider*
| Feld | Wert |
|---|---|
| Name | `ProxLedger` |
| Authorization flow | der übliche (z. B. *default-provider-authorization-implicit-consent*) |
| Client type | **Confidential** |
| Client ID | übernehmen, wird gleich gebraucht |
| Client Secret | übernehmen, wird gleich gebraucht |
| Redirect URIs | `https://proxledger.firma.de/api/auth/oidc/callback` |
| Scopes | `openid`, `email`, `profile` |
| Signing Key | ein vorhandenes Zertifikat auswählen |
Zur Rückkehradresse zwei Hinweise, an denen die meisten Einrichtungen scheitern:
- Sie muss **wortgleich** übereinstimmen – einschließlich `https`, Portangabe und ohne Schrägstrich am
Ende. ProxLedger bildet sie aus der Dashboard-Adresse unter *Einstellungen → System*, ersatzweise aus
`APP_URL`.
- authentik erlaubt auch reguläre Ausdrücke. Für eine einzelne Installation ist die genaue Adresse die
bessere Wahl; ein zu weiter Ausdruck öffnet eine Umleitung auf fremde Ziele.
## 2. In authentik: Application anlegen
*Applications → Applications → Create*
| Feld | Wert |
|---|---|
| Name | `ProxLedger` |
| Slug | `proxledger` |
| Provider | der eben angelegte |
Der Slug bestimmt die Discovery-Adresse:
```
https://authentik.firma.de/application/o/proxledger/.well-known/openid-configuration
```
Diese Adresse **ohne** den Teil `/.well-known/openid-configuration` ist der Aussteller
(`OIDC_ISSUER`) – also `https://authentik.firma.de/application/o/proxledger/`.
Prüfen lässt sie sich mit:
```bash
curl -fsS https://authentik.firma.de/application/o/proxledger/.well-known/openid-configuration | head
```
Wer darf sich anmelden, entscheidet in authentik die **Policy-Bindung** an der Application
(*Applications → ProxLedger → Policy / Group / User Bindings*). Ohne Bindung darf jeder, der bei
authentik ein Konto hat.
## 3. In ProxLedger: Werte eintragen
In die `.env` (Docker) oder `/opt/proxledger/.env` (LXC):
```bash
OIDC_ISSUER="https://authentik.firma.de/application/o/proxledger/"
OIDC_CLIENT_ID="…"
OIDC_CLIENT_SECRET="…"
OIDC_NAME="authentik" # Beschriftung des Knopfes auf der Anmeldeseite
APP_URL="https://proxledger.firma.de"
```
Danach neu starten:
```bash
docker compose up -d # Docker
systemctl restart proxledger # LXC
```
Auf der Anmeldeseite erscheint jetzt **Mit authentik anmelden**.
## 4. Rollen festlegen
ProxLedger liest **keine** Gruppen aus authentik. Wer sich zum ersten Mal anmeldet, bekommt die Rolle
aus *Einstellungen → System → Rolle für neue OIDC-Konten* – Vorgabe ist **Techniker**. Die endgültige
Rolle vergibt ein Administrator unter *Einstellungen → Benutzer*.
Das ist bewusst so: Eine Gruppenzuordnung, die stillschweigend Administratorrechte verteilt, ist
schwerer zu überblicken als eine Liste, in der jeder Zugang sichtbar ist.
Zwei Einstellungen helfen dabei, den Kreis klein zu halten:
- **Erlaubte E-Mail-Domänen** – etwa `firma.de`. Alles andere wird abgewiesen, auch wenn die Anmeldung
bei authentik geklappt hat.
- **Unbekannte OIDC-Anmeldungen legen ein Konto an** – abgeschaltet kommt nur herein, wer hier bereits
ein Konto hat. Dann legt man Konten vorab an (ohne Passwort) und die Anmeldung läuft trotzdem über
authentik.
## 5. Prüfen
1. Abmelden, dann **Mit authentik anmelden**.
2. Unter *Einstellungen → Protokoll* steht `anmeldung.erfolgreich` mit `weg: oidc`.
3. Unter *Einstellungen → Benutzer* steht das Konto mit der vergebenen Rolle und der Anmeldeart `OIDC`.
## Wenn es nicht klappt
| Meldung | Ursache |
|---|---|
| „Die Anmeldung über den Anbieter hat nicht geklappt.“ | Sammelmeldung – die Einzelheiten stehen im Protokoll des Containers (`docker compose logs web`). |
| authentik zeigt *Redirect URI Error* | Die Adresse stimmt nicht wortgleich. Häufig: `http` statt `https`, Schrägstrich am Ende, oder die interne statt der öffentlichen Adresse. |
| `invalid_client` | Client-ID oder -Secret falsch, oder der Provider steht auf *Public* statt *Confidential*. |
| Anmeldung klappt, aber ProxLedger meldet „keine E-Mail-Adresse“ | Der Scope `email` fehlt am Provider. |
| Nach der Anmeldung landet man auf einer unerreichbaren Adresse | `APP_URL` bzw. die Dashboard-Adresse zeigt nicht auf die öffentliche Adresse. |
Siehe auch: [Reverse Proxy](reverse-proxy.md) – ohne HTTPS gibt es weder sinnvolle OIDC-Anmeldung noch
Passkeys.
+81
View File
@@ -0,0 +1,81 @@
# Inbetriebnahme
Der kürzeste Weg von null zu einem Dashboard, das Hosts meldet. Rechnen Sie mit einer halben Stunde.
## Reihenfolge
Die Reihenfolge ist nicht beliebig – zwei Schritte hängen an der endgültigen Adresse:
1. Dashboard installieren
2. **Adresse festlegen** (Reverse Proxy, DNS)
3. OIDC einrichten (falls gewünscht)
4. Passkeys anlegen
5. Agents ausrollen
Wer Schritt 4 oder 5 vorzieht, macht sie nach einem Adresswechsel noch einmal: Passkeys gelten nur für
die Domäne, unter der sie entstanden sind, und die Adresse steckt im Installations-Einzeiler.
## 1. Installieren
**Docker** (beliebiger Host mit Docker):
```bash
git clone https://git.wilde-server.de/florian.wilde/ProxLedger.git proxledger && cd proxledger
cp .env.example .env
# Pflicht: APP_SECRET, POSTGRES_PASSWORD, ADMIN_PASSWORD, APP_URL
docker compose up -d
```
**LXC auf Proxmox**, zwei Möglichkeiten:
```bash
bash deploy/lxc-installer.sh # Container mit Docker darin
```
```bash
# Nach Art der Proxmox VE Helper-Scripts, ohne Docker:
bash -c "$(curl -fsSL https://git.wilde-server.de/florian.wilde/ProxLedger/raw/branch/main/deploy/community-scripts/ct/proxledger.sh)"
```
Der erste Administrator entsteht beim Einrichten aus `ADMIN_EMAIL` und `ADMIN_PASSWORD`; beim
LXC-Weg stehen die erzeugten Zugangsdaten in `/opt/proxledger/.credentials`.
**`APP_SECRET` gehört sofort in die Sicherung.** Er signiert die Sitzungen und verschlüsselt die
gespeicherten Zugangsdaten für SMTP und Webhooks. Ohne ihn sind beide unbrauchbar.
## 2. Adresse festlegen
Siehe [reverse-proxy.md](reverse-proxy.md). Danach unter *Einstellungen → System* die **Adresse des
Dashboards** eintragen – sie landet im Installations-Einzeiler und in den Benachrichtigungen.
## 3. Anmeldung einrichten
- Optional OIDC: [authentik.md](authentik.md)
- Weitere Konten unter *Einstellungen → Benutzer* (Administrator, Techniker, Kunde)
- Jeder legt sich unter *Meine Passkeys* einen Passkey an – danach entfällt das Passwort
## 4. Agents ausrollen
1. *Einstellungen → Agent-Tokens* → Enrollment-Token erzeugen (mit Kundenbindung spart es den
Zuordnungsschritt)
2. Auf jedem Proxmox-Host als root:
```bash
curl -fsSL https://proxledger.firma.de/install.sh | bash -s -- --token <ENROLL_TOKEN>
```
3. **In einem Cluster auf jedem Knoten** – jeder Knoten hat eine eigene Subscription. Das Dashboard
warnt von selbst, wenn ein Knoten fehlt.
4. Danach den Enrollment-Token widerrufen.
## 5. Benachrichtigungen
*Einstellungen → Benachrichtigungen*: SMTP-Zugang oder Webhook eintragen, Testnachricht schicken,
Zusammenfassung auf täglich oder wöchentlich stellen.
## Prüfliste
- [ ] `https://…/api/gesundheit` antwortet mit 200
- [ ] Anmeldung klappt, die Adresszeile bleibt auf der öffentlichen Adresse
- [ ] Ein Host meldet sich (*Hosts* zeigt ihn mit Ablaufdatum)
- [ ] Testnachricht ist angekommen
- [ ] Sicherung läuft und wurde **einmal zurückgespielt** ([sicherung.md](sicherung.md))
- [ ] `APP_SECRET` liegt an einem zweiten Ort
+228
View File
@@ -0,0 +1,228 @@
# HTTPS einrichten: nginx Proxy Manager oder netbird
ProxLedger spricht selbst kein HTTPS – davor gehört ein Reverse Proxy. Ohne ihn fehlen drei Dinge:
- **Passkeys** funktionieren nur im sicheren Kontext (HTTPS oder `localhost`).
- Das Sitzungs-Cookie bekommt kein `Secure`-Kennzeichen.
- Die Agent-Kommunikation läuft unverschlüsselt durchs Netz.
Fertige Beispiele für nginx, Caddy und Traefik liegen unter [`deploy/reverse-proxy/`](../../deploy/reverse-proxy/).
Dieses Dokument beschreibt die beiden Wege, die in der Praxis am häufigsten vorkommen.
---
## Weg A: nginx Proxy Manager
### 1. Proxy Host anlegen
*Hosts → Proxy Hosts → Add Proxy Host*, Reiter **Details**:
| Feld | Wert |
|---|---|
| Domain Names | `proxledger.firma.de` |
| Scheme | `http` |
| Forward Hostname / IP | IP des Docker-Hosts oder des LXC |
| Forward Port | `3000` |
| Cache Assets | aus |
| Block Common Exploits | an |
| Websockets Support | **an** |
Reiter **SSL**: *Request a new SSL Certificate*, dazu **Force SSL** und **HTTP/2 Support**.
### 2. Kopfzeilen ergänzen
Reiter **Advanced**, in das Feld *Custom Nginx Configuration*:
```nginx
# Ohne diese Angaben baut ProxLedger seine Weiterleitungen aus der internen Adresse
# (etwa http://172.18.0.4:3000) – im Browser landet man dann im Nichts.
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Rechnungs-PDFs sind größer als die Standardgrenze von 1 MB.
client_max_body_size 12m;
```
### 3. In ProxLedger eintragen
```bash
APP_URL="https://proxledger.firma.de"
```
und unter *Einstellungen → System* dieselbe Adresse als **Adresse des Dashboards**. Sie steht danach
im Installations-Einzeiler für die Agents und ist die Grundlage für Passkeys und OIDC.
### 4. Prüfen
```bash
curl -fsS -o /dev/null -w "%{http_code}\n" https://proxledger.firma.de/api/gesundheit
curl -fsSI https://proxledger.firma.de/login | grep -i "content-security-policy\|strict-transport"
```
Danach einmal anmelden und darauf achten, dass die Adresszeile auf `proxledger.firma.de` bleibt.
---
## Weg B: netbird Reverse Proxy (Beta)
Seit v0.65 bringt netbird einen **eigenen Reverse Proxy** mit; v0.72 hat private Dienste und „Bring
Your Own Proxy“ ergänzt. Damit entfällt der nginx Proxy Manager vollständig: netbird übernimmt die
TLS-Terminierung und reicht den Verkehr durch das Mesh an den Ziel-Peer weiter – **ohne offenen Port
und ohne Firewall-Regel auf dem ProxLedger-Host**.
Für ProxLedger ist das der bequemste Weg, weil er die beiden Dinge löst, an denen es sonst hakt: ein
gültiges Zertifikat (damit Passkeys funktionieren) und eine feste öffentliche Adresse.
> **Beta.** Die Funktion ist als Beta gekennzeichnet. Sie unterstützt derzeit **kein Rosenpass**, und
> wer netbird selbst hostet, braucht **Traefik** als äußeren Proxy – nur der beherrscht das nötige
> TLS-Passthrough.
### 1. Dienst anlegen
Im netbird-Dashboard unter *Services → Reverse Proxy → Create*:
| Reiter | Einstellung |
|---|---|
| **Details** | Mode `HTTP`; Subdomain `proxledger`, dazu eine Basis-Domäne; als Target den Peer mit ProxLedger, Protokoll `HTTP`, Port `3000` |
| **Authentication** | für ProxLedger **aus lassen** – Begründung unten |
| **Access Control** | nach Bedarf: IP-Bereiche, Länder, CrowdSec-Bewertung |
| **Settings** | *Host header forwarding* **an**, ebenso das Umschreiben von Weiterleitungen |
Das Zertifikat besorgt netbird selbst über ACME (Let's Encrypt); alternativ lassen sich eigene
Zertifikate hinterlegen.
**Host header forwarding ist nicht optional.** ProxLedger bildet Weiterleitungen, die OIDC-Rückkehr
und die `rpID` der Passkeys aus der öffentlichen Adresse. Kommt beim Dienst nur die interne Adresse
an, landet man nach der Anmeldung auf einer Adresse, die es im Browser nicht gibt.
### 2. Adresse eintragen
```bash
APP_URL="https://proxledger.<ihre-basis-domaene>"
```
und dieselbe Adresse unter *Einstellungen → System* als **Adresse des Dashboards**. Danach neu starten.
### 3. Warum die netbird-Anmeldung hier ausbleiben sollte
netbird kann den Dienst zusätzlich absichern – per SSO über den eigenen Identitätsanbieter, per
Passwort, PIN oder Kopfzeile. Verlockend, aber für ProxLedger meist falsch:
**Die Absicherung gilt für den ganzen Dienst, also auch für `/api/agent`.** Die Agents auf den
Proxmox-Hosts können sich dort nicht anmelden – sie weisen sich mit ihrem eigenen Token aus. Steht
eine SSO-Hürde davor, melden alle Hosts stumm ins Leere, bis der Zwischenspeicher voll ist.
Zwei Wege, wenn die zusätzliche Hürde gewünscht ist:
1. **Zwei Dienste anlegen:** einen für die Oberfläche mit SSO, einen zweiten (eigene Subdomain) ohne
Anmeldung für die Agents. Im Installations-Einzeiler dann die Agent-Adresse verwenden
(`--url https://agents.<domaene>`).
2. **Privater Dienst:** netbird kann einen Dienst so schalten, dass er **nur** aus dem eigenen Netz und
nur für ausgewählte Gruppen erreichbar ist. Das ist die sauberste Lösung, wenn sowohl Arbeitsplätze
als auch alle Proxmox-Hosts ohnehin im netbird-Netz sind: kein offener Zugang, trotzdem gültiges
Zertifikat und damit Passkeys.
### 4. Prüfen
```bash
curl -fsS -o /dev/null -w "%{http_code}\n" https://proxledger.<domaene>/api/gesundheit
curl -fsS https://proxledger.<domaene>/install.sh | grep -m1 DASHBOARD_URL
```
Die zweite Zeile muss die **öffentliche** Adresse zeigen – das ist die, die später in jedem Agent steht.
---
## Weg C: netbird ohne Reverse Proxy (kein offener Port)
netbird spannt ein privates Netz über WireGuard. ProxLedger ist dann **nur** für Geräte im Netz
erreichbar – kein Port im Internet, kein Zertifikat von außen nötig.
Das passt gut zu diesem Werkzeug: Die Oberfläche braucht niemand aus dem Internet, und die Proxmox-
Hosts stehen ohnehin meist in Kundennetzen mit netbird oder VPN.
### Aufbau
```
Proxmox-Host (netbird-Client) ──┐
Proxmox-Host (netbird-Client) ──┼── netbird-Netz ──► ProxLedger (netbird-Client, Port 3000)
Arbeitsplatz (netbird-Client) ──┘
```
### 1. netbird auf dem ProxLedger-Host
```bash
curl -fsSL https://pkgs.netbird.io/install.sh | sh
netbird up --setup-key <SETUP-KEY>
netbird status # zeigt die vergebene Adresse, z. B. 100.x.y.z
```
### 2. netbird auf jedem Proxmox-Host
Gleiche Schritte. Danach von dort prüfen:
```bash
curl -fsS -o /dev/null -w "%{http_code}\n" http://100.x.y.z:3000/api/gesundheit
```
### 3. Zugriff einschränken
In der netbird-Verwaltung eine Access-Policy anlegen: Gruppe `proxmox-hosts` darf Gruppe
`proxledger` auf Port 3000 erreichen, sonst nichts. Damit können die Hosts melden, aber nicht
untereinander sprechen.
### 4. HTTPS trotzdem einrichten
netbird verschlüsselt den Transport – der Browser weiß davon aber nichts, und **Passkeys bleiben ohne
HTTPS gesperrt**. Am einfachsten ist hier der Reverse Proxy aus Weg B (auch als privater Dienst
nutzbar). Sonst bleiben zwei Möglichkeiten:
- **Empfohlen:** Zusätzlich einen kleinen Reverse Proxy im netbird-Netz betreiben (nginx oder Caddy)
mit einem Zertifikat für einen internen Namen, ausgestellt über DNS-01 – dafür muss nichts nach
außen offen sein.
- Oder ein eigenes CA-Zertifikat verteilen. Dann brauchen auch die Agents das CA-Zertifikat:
`curl … | bash -s -- --token … --ca-cert /pfad/zur/ca.crt`.
Wer auf Passkeys verzichtet, kann ProxLedger im netbird-Netz auch schlicht über `http://100.x.y.z:3000`
betreiben. In dem Fall `APP_URL` auf genau diese Adresse setzen – sonst setzt ProxLedger ein
`Secure`-Cookie, das der Browser über `http` verwirft, und die Anmeldung läuft ins Leere.
---
## Welcher Weg wofür
| Lage | Empfehlung |
|---|---|
| Eigener Server, Domäne vorhanden, Kunden sollen zugreifen | **Weg A** – nginx Proxy Manager |
| Alles läuft ohnehin über netbird, Zertifikat gewünscht | **Weg B** – netbird Reverse Proxy, bei Bedarf als privater Dienst |
| Kein offener Zugang, keine Passkeys nötig | **Weg C** – netbird pur, `APP_URL` auf die netbird-Adresse |
Weg B ist der kürzeste, wenn netbird ohnehin im Einsatz ist: ein Dienst, ein Zertifikat, kein
offener Port. Nur die Beta-Kennzeichnung sollte man im Hinterkopf behalten und nicht gleichzeitig auf
etwas anderes umstellen, damit bei einem Problem klar ist, woran es liegt.
Der häufigste gemischte Aufbau:
| Zugang | Weg |
|---|---|
| Oberfläche für Mitarbeiter und Kunden | nginx Proxy Manager oder netbird Reverse Proxy |
| Agents aus Kundennetzen | netbird-Netz, oder dieselbe öffentliche Adresse |
Wichtig ist nur, dass die Agents dieselbe Adresse erreichen, die im Installations-Einzeiler steht.
Ändert sich die Adresse später, genügt der Einzeiler erneut auf jedem Host – die Zuordnung bleibt
erhalten, weil sie an der Maschinenkennung hängt, nicht an der Adresse.
## Merkposten
0. **Keine Anmeldehürde vor `/api/agent`.** Egal welcher Proxy: Wer die Oberfläche zusätzlich absichert,
schließt damit auch die Agents aus – die weisen sich mit eigenem Token aus und können sich nirgends
anmelden.
1. **Adresse zuerst festlegen, dann Passkeys anlegen.** Ein Passkey gilt nur für die Domäne, unter der
er registriert wurde.
2. **`APP_URL` und die Dashboard-Adresse müssen auf die öffentliche Adresse zeigen** – daraus entstehen
Weiterleitungen, OIDC-Rückkehradresse und der Agent-Einzeiler.
3. **`client_max_body_size` nicht vergessen**, sonst scheitert das Hochladen von Rechnungs-PDFs mit
einem nichtssagenden 413.
+45
View File
@@ -0,0 +1,45 @@
# Sicherung und Wiederherstellung
## Was gesichert werden muss
| Sache | Wo | Ohne sie |
|---|---|---|
| Datenbank | PostgreSQL | alles weg: Hosts, Kunden, Laufzeiten, Rechnungen |
| `APP_SECRET` | `.env` | alle Sitzungen ungültig, SMTP- und Webhook-Zugangsdaten unlesbar |
| Hochgeladene PDFs | `/data/uploads` (Docker-Volume) bzw. `/opt/proxledger/data/uploads` | Rechnungsanhänge fehlen |
Die Agents brauchen **keine** Sicherung: Sie registrieren sich neu, und die Zuordnung hängt an der
Maschinenkennung, nicht an einem Token.
## Sichern
```bash
bash deploy/backup.sh /var/backups/proxledger
```
Das Skript erkennt selbst, ob die Installation mit Docker oder als Dienst läuft, und sichert in beiden
Fällen Datenbank, Uploads und `.env`. Beim LXC-Weg reicht zusätzlich die gewohnte Proxmox-Sicherung
des Containers.
Vor jeder Aktualisierung läuft es ohnehin automatisch – [`deploy/update.sh`](../../deploy/update.sh)
ruft es als Erstes auf und bricht ab, wenn es fehlschlägt.
## Zurückspielen
```bash
# Docker
docker compose down
docker compose up -d postgres
gunzip -c sicherung.sql.gz | docker compose exec -T postgres psql -U proxledger proxledger
docker compose up -d
```
Danach die `.env` mit **demselben** `APP_SECRET` zurücklegen wie zum Zeitpunkt der Sicherung. Ein
anderer Wert lässt die gespeicherten Zugangsdaten unlesbar werden; das Dashboard sagt das beim
nächsten Versand deutlich, aber die Werte müssen dann neu eingetragen werden.
## Das Wichtigste
**Eine Sicherung, die nie zurückgespielt wurde, ist keine Sicherung.** Spielen Sie sie einmal in eine
leere Testumgebung ein und melden Sie sich dort an. Erst dann wissen Sie, dass Sicherung und
`APP_SECRET` zusammenpassen.
+30
View File
@@ -0,0 +1,30 @@
# #029 Einrichtungsdokumentation
**Status:** erledigt
**Meilenstein:** v0.19.0
**Labels:** doku
**Geschätzt:** 3 h **Tatsächlich:** 0 h 40 min
## Ziel
Anleitungen für alles, was rund um ProxLedger eingerichtet wird: HTTPS, OIDC mit authentik, Sicherung –
und die richtige Reihenfolge, in der das geschehen muss.
## Aufgaben
- [x] `docs/betrieb/inbetriebnahme.md` – von null zum meldenden Host
- [x] `docs/betrieb/reverse-proxy.md` – nginx Proxy Manager und netbird
- [x] `docs/betrieb/authentik.md` – OIDC Schritt für Schritt, mit Fehlerbildern
- [x] `docs/betrieb/sicherung.md` – sichern und **zurückspielen**
- [x] Verweise aus README und Handbuch
## Beim Schreiben gefunden
Zwei Stellen taugten für einen Betrieb hinter einem Reverse Proxy nicht:
1. Die **OIDC-Rückkehradresse** kam allein aus `APP_URL` und übersah die Dashboard-Adresse aus den
Einstellungen. Wer nur letztere gesetzt hatte, bekam vom Anbieter einen *Redirect URI Error*.
2. Beim **Einlösen des Codes** wurde die Adresse der eingehenden Anfrage verwendet – hinter einem
Proxy also die interne. Der Anbieter hat aber die öffentliche hinterlegt; der Tausch wäre
gescheitert. Jetzt wird dieselbe öffentliche Adresse gebildet wie für die Weiterleitungen.
## Entscheidung
Die Reihenfolge steht bewusst am Anfang der Inbetriebnahme: **Adresse festlegen, dann Passkeys und
Agents.** Ein Passkey gilt nur für die Domäne, unter der er entstanden ist, und die Adresse steckt im
Installations-Einzeiler. Wer das übergeht, macht beides zweimal.
+29
View File
@@ -0,0 +1,29 @@
# #030 netbird Reverse Proxy in die Anleitung
**Status:** erledigt
**Meilenstein:** v0.19.1
**Labels:** doku
**Geschätzt:** 1 h **Tatsächlich:** 0 h 20 min
## Ziel
netbird bringt seit v0.65 einen eigenen Reverse Proxy mit (Beta), v0.72 ergänzte private Dienste.
Damit entfällt ein zusätzlicher nginx – das gehört in die Anleitung, weil es der kürzeste Weg zu
HTTPS ist, wenn netbird ohnehin im Einsatz ist.
## Aufgaben
- [x] Weg B: netbird Reverse Proxy – Dienst anlegen, Zertifikat, Einstellungen
- [x] Alter netbird-Abschnitt wird Weg C (ohne Proxy)
- [x] Entscheidungstabelle, welcher Weg wofür taugt
- [x] Beta-Einschränkungen benennen (kein Rosenpass; selbst gehostet nur mit Traefik)
## Der wichtigste Hinweis
Die Absicherung eines Dienstes (SSO, Passwort, PIN) gilt für den **ganzen** Dienst – also auch für
`/api/agent`. Die Agents weisen sich mit eigenem Token aus und können sich nirgends anmelden; eine
SSO-Hürde davor lässt alle Hosts stumm ins Leere melden, bis der Zwischenspeicher voll ist.
Zwei brauchbare Auswege stehen in der Anleitung: zwei Dienste (Oberfläche mit SSO, Agents ohne) oder
ein privater Dienst, der nur aus dem netbird-Netz erreichbar ist.
## Quellen
- <https://docs.netbird.io/manage/reverse-proxy>
- <https://netbird.io/knowledge-hub/reverse-proxy>
- <https://netbird.io/knowledge-hub/netbird-only-private-services>
@@ -0,0 +1,33 @@
# #031 Update- und Sicherungsskript für beide Installationsarten
**Status:** erledigt
**Meilenstein:** v0.20.0
**Labels:** deployment
**Geschätzt:** 2 h **Tatsächlich:** 0 h 45 min
## Ziel
`deploy/update.sh` und `deploy/backup.sh` konnten nur Docker. Die Installation als Dienst (Node und
PostgreSQL direkt im LXC) ging leer aus – dort half nur der Weg über das Gerüst der community-scripts.
## Aufgaben
- [x] Beide Skripte erkennen die Installationsart selbst (systemd-Dienst oder Docker)
- [x] Dienst-Weg: `npm ci`, `prisma migrate deploy`, bauen, `systemctl restart`
- [x] Sicherung für den Dienst-Weg: `pg_dump` über `DATABASE_URL`, Uploads aus `UPLOAD_DIR`
- [x] Fehlgeschlagene Sicherung bricht die Aktualisierung ab – **vor** jeder Änderung
- [x] Hinweis am Ende von `deploy/lxc-installer.sh` auf die Skripte umgestellt
- [x] Handbuch und Betriebsdoku angepasst
- [x] Docker-Weg vollständig durchgespielt
## Zwei Fehler beim Durchspielen gefunden
1. **Die Zustandsprüfung des Web-Containers konnte nie gelingen.** Next bindet im Standalone-Modus an
die Adresse aus `HOSTNAME` – und Docker setzt darin den Containernamen. Der Server lauschte also
nur auf der Container-IP, nicht auf `127.0.0.1`. Der Dienst `web` blieb dauerhaft auf
„health: starting“; von außen war er trotzdem erreichbar, deshalb fiel es bisher nicht auf.
Behoben mit `ENV HOSTNAME=0.0.0.0` im Dockerfile; die Prüfungen verwenden jetzt `127.0.0.1`
statt `localhost` (das löst im Container auch auf `::1` auf).
2. **Die Sicherung scheiterte stumm an fehlenden Rechten** (`/var/backups` gehört root) und die
Aktualisierung lief trotzdem weiter. Jetzt bricht sie ab und nennt die drei Auswege.
## Prüfung
Vollständiger Durchlauf gegen einen laufenden Stapel: Sicherung (Datenbank 6,3 KB mit 25 Tabellen,
Uploads, `.env`), Abbilder gebaut, migriert, neu gestartet, Dashboard nach zwei Sekunden wieder da.
Danach stand `web` zum ersten Mal auf **healthy**.
+4 -1
View File
@@ -32,4 +32,7 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
| [#026](026-auslieferung.md) | Auslieferung finalisieren | erledigt | v0.17.0 | 3 h | 0 h 40 min |
| [#027](027-rollen-benutzerverwaltung.md) | Rollen und Benutzerverwaltung | erledigt | v0.18.0 | 6 h | 1 h 20 min |
| [#028](028-passkeys.md) | Anmeldung mit Passkeys | erledigt | v0.18.0 | 4 h | 0 h 45 min |
| | **Summe** | | | **143 h** | **14 h 23 min** |
| [#029](029-betriebsdokumentation.md) | Einrichtungsdokumentation | erledigt | v0.19.0 | 3 h | 0 h 40 min |
| [#030](030-netbird-reverse-proxy.md) | netbird Reverse Proxy in die Anleitung | erledigt | v0.19.1 | 1 h | 0 h 20 min |
| [#031](031-update-skript-beide-varianten.md) | Update- und Sicherungsskript für beide Varianten | erledigt | v0.20.0 | 2 h | 0 h 45 min |
| | **Summe** | | | **149 h** | **16 h 08 min** |
+54 -54
View File
@@ -1,12 +1,12 @@
{
"name": "proxledger",
"version": "0.17.0",
"version": "0.19.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "proxledger",
"version": "0.17.0",
"version": "0.19.1",
"hasInstallScript": true,
"dependencies": {
"@node-rs/argon2": "^2.2.1",
@@ -14,13 +14,13 @@
"@prisma/client": "^7.10.0",
"@react-pdf/renderer": "^4.9.0",
"@simplewebauthn/browser": "^14.0.0",
"@simplewebauthn/server": "^14.0.2",
"@simplewebauthn/server": "^14.0.3",
"admin-lte": "^4.9.1",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
"croner": "^10.0.1",
"jose": "^6.2.12",
"next": "16.3.5",
"next": "^16.3.8",
"nodemailer": "^10.0.10",
"openid-client": "^6.8.8",
"react": "19.2.8",
@@ -35,7 +35,7 @@
"dotenv": "^17.4.2",
"esbuild": "^0.28.2",
"eslint": "^9",
"eslint-config-next": "16.3.5",
"eslint-config-next": "^16.3.8",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.9.7",
"prisma": "^7.10.0",
@@ -1646,15 +1646,15 @@
}
},
"node_modules/@next/env": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.5.tgz",
"integrity": "sha512-NWEXVDMqoEo0ktmU6u0sE2Vg0LOcsD7NnOTJNo3/fEaTfsg+F1bMIxuDmQbda4e3yTIQwVdUREF2yIuMOusKtg==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.8.tgz",
"integrity": "sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.5.tgz",
"integrity": "sha512-PGfSeItHJ12DH8t+6sEbuMe59NE5rAhCfgk06QKTH2ne9VUL1JlaXdYXY3B8RaiF2SO50rDYvd39TulP6A6xZQ==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.8.tgz",
"integrity": "sha512-eCR9RcLTZrVS+K3+VOhi0xMdUUGNuriCHHhU1ZoABC/QQ/Gp0OWbLKQxV2/9DkmihYdYRcQWFmZXW8M1AMUXYw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1695,9 +1695,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.5.tgz",
"integrity": "sha512-pMmGgETfKvElucLHtVaeiMRbp2zUbvKx7b1yGko0liBz3cw1mKSggWN/Rp/wPz8z+E1O82u3r4L1Co+ZS5hokQ==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.8.tgz",
"integrity": "sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==",
"cpu": [
"arm64"
],
@@ -1711,9 +1711,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.5.tgz",
"integrity": "sha512-76VaGYvf6HPa5/w12yLkE3dXTn9AfdEviI79oEL3aZoAmRLc9rWitjWqyjViVysK/ht/y9YKzFkBrUdi/wGkow==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.8.tgz",
"integrity": "sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==",
"cpu": [
"x64"
],
@@ -1727,9 +1727,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.5.tgz",
"integrity": "sha512-zKDELJ5jSQMHeO/hmXUQsAzagX4bQD4OiMi3pQ5FbUj+yK506oLVHnKA2YXMlbg1EHHqJYtyePOgByIDXD1lqw==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.8.tgz",
"integrity": "sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==",
"cpu": [
"arm64"
],
@@ -1746,9 +1746,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.5.tgz",
"integrity": "sha512-7Vql0pgzCoHagv6+FNOZoqmJqA52c6zeVbhtS/47qFozO1MSx4ms7x7GHiciY8R5CDsSMKMQjJEryoJLcsBIbA==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.8.tgz",
"integrity": "sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==",
"cpu": [
"arm64"
],
@@ -1765,9 +1765,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.5.tgz",
"integrity": "sha512-NH/xzehyHEFWE2nlcZon7TB/0+H4shfWCi7S1zka815XCOhJDYZhoeJtOYy0dh0WVRWACVXSyGNFFytoMxUhRg==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.8.tgz",
"integrity": "sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==",
"cpu": [
"x64"
],
@@ -1784,9 +1784,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.5.tgz",
"integrity": "sha512-lV4+EhWMfS8jcC+EH2nn/Cm5cn6XsgbE07bU9tMH8fCo0tNAqhyzi1b5wQ/Tn6NGFTvKDY65w3ZH95EjwBRAnQ==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.8.tgz",
"integrity": "sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==",
"cpu": [
"x64"
],
@@ -1803,9 +1803,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.5.tgz",
"integrity": "sha512-/wKzAREX2RF++MhicjDbg8tGn2AiBIM0+EFeTFKoUEUbW5D6amCJehd5Z5G1H5/gxNdgnwoXMcHz24H/c2tGkQ==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.8.tgz",
"integrity": "sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==",
"cpu": [
"arm64"
],
@@ -1819,9 +1819,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.5.tgz",
"integrity": "sha512-LNdCHzgLFc+UeqMS84LzXPaeBRKyqDN9OMyFAr1OrB0XrNw78IRrEVtZvvA7245W/HsaoeVOQX9jPjPk8jojwA==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.8.tgz",
"integrity": "sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==",
"cpu": [
"x64"
],
@@ -3285,9 +3285,9 @@
"license": "MIT"
},
"node_modules/@simplewebauthn/server": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-14.0.2.tgz",
"integrity": "sha512-g+m/xv9/8FRWsDY22CwsTYRzt3WwS9fJrR3P7eY3Ti01FXH5FQ36Xn55v6D+5tb6VajlqOj4hywP/Vxsb6a3Sg==",
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-14.0.3.tgz",
"integrity": "sha512-D39/0fiDiYmGV+nraY9s63D1Ho2AX/waMLy22/9gUadTUuvbAnstfb4bpbHYbwZuV5ZLDYN1SmzDKuiHzKAA4Q==",
"license": "MIT",
"dependencies": {
"@hexagon/base64": "^1.1.27",
@@ -5889,13 +5889,13 @@
}
},
"node_modules/eslint-config-next": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.5.tgz",
"integrity": "sha512-wPjq9MLQuWykHs8tsm2gH6OJThk6N27L8Te2JatlaGZ7jT1gtgGmJKukygL28xOlWsg5J1a1bGy/PvLyQC8OYA==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.8.tgz",
"integrity": "sha512-81vovwMe6NGnoFsl0KUJWzlS+y239i3fdsxs49gSEX3pDQW2cuKy+fxUPXNkQqqx6OCPheYPtBlSufc9aZ0k+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@next/eslint-plugin-next": "16.3.5",
"@next/eslint-plugin-next": "16.3.8",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
@@ -8184,12 +8184,12 @@
"license": "MIT"
},
"node_modules/next": {
"version": "16.3.5",
"resolved": "https://registry.npmjs.org/next/-/next-16.3.5.tgz",
"integrity": "sha512-MdtsTgzyfCPRLC6uJ1mN8ao7lyJ4BB0U6Inhnx3gta1UcCIdHK3yxLG0E8OWQteWD8/Q0qb8A5o7wJaL8M9y2w==",
"version": "16.3.8",
"resolved": "https://registry.npmjs.org/next/-/next-16.3.8.tgz",
"integrity": "sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==",
"license": "MIT",
"dependencies": {
"@next/env": "16.3.5",
"@next/env": "16.3.8",
"@swc/helpers": "0.5.23",
"baseline-browser-mapping": "^2.9.19",
"caniuse-lite": "^1.0.30001579",
@@ -8203,14 +8203,14 @@
"node": ">=20.9.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "16.3.5",
"@next/swc-darwin-x64": "16.3.5",
"@next/swc-linux-arm64-gnu": "16.3.5",
"@next/swc-linux-arm64-musl": "16.3.5",
"@next/swc-linux-x64-gnu": "16.3.5",
"@next/swc-linux-x64-musl": "16.3.5",
"@next/swc-win32-arm64-msvc": "16.3.5",
"@next/swc-win32-x64-msvc": "16.3.5",
"@next/swc-darwin-arm64": "16.3.8",
"@next/swc-darwin-x64": "16.3.8",
"@next/swc-linux-arm64-gnu": "16.3.8",
"@next/swc-linux-arm64-musl": "16.3.8",
"@next/swc-linux-x64-gnu": "16.3.8",
"@next/swc-linux-x64-musl": "16.3.8",
"@next/swc-win32-arm64-msvc": "16.3.8",
"@next/swc-win32-x64-msvc": "16.3.8",
"sharp": "^0.35.4"
},
"peerDependencies": {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "proxledger",
"version": "0.18.0",
"version": "0.20.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -35,13 +35,13 @@
"@prisma/client": "^7.10.0",
"@react-pdf/renderer": "^4.9.0",
"@simplewebauthn/browser": "^14.0.0",
"@simplewebauthn/server": "^14.0.2",
"@simplewebauthn/server": "^14.0.3",
"admin-lte": "^4.9.1",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
"croner": "^10.0.1",
"jose": "^6.2.12",
"next": "16.3.5",
"next": "^16.3.8",
"nodemailer": "^10.0.10",
"openid-client": "^6.8.8",
"react": "19.2.8",
@@ -56,7 +56,7 @@
"dotenv": "^17.4.2",
"esbuild": "^0.28.2",
"eslint": "^9",
"eslint-config-next": "16.3.5",
"eslint-config-next": "^16.3.8",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.9.7",
"prisma": "^7.10.0",
+6 -1
View File
@@ -26,7 +26,12 @@ export async function GET(request: Request) {
}
try {
const angaben = await anmeldungAbschliessen(einstellungen, new URL(request.url), pruefwert, zustand);
// Hinter einem Reverse Proxy ist `request.url` die interne Adresse. Der Anbieter hat aber die
// öffentliche als Rückkehradresse hinterlegt – mit der internen scheitert der Tausch des Codes.
const angekommen = new URL(request.url);
const oeffentlich = eigeneAdresse(request, `${angekommen.pathname}${angekommen.search}`, dashboardUrl);
const angaben = await anmeldungAbschliessen(einstellungen, oeffentlich, pruefwert, zustand);
const benutzer = await mitOidcAnmelden(angaben.subject, angaben.email, angaben.name);
const { sitzungsdauerStunden } = await systemEinstellungen();
+9 -3
View File
@@ -1,4 +1,5 @@
import * as client from "openid-client";
import { einstellungen as systemEinstellungen } from "@/server/einstellungen";
/**
* Anmeldung über OIDC (getestet mit authentik). Die Einrichtung erfolgt über Umgebungsvariablen,
@@ -28,8 +29,13 @@ export function oidcEinstellungen(): OidcEinstellungen | null {
};
}
export function rueckkehrAdresse(): string {
const basis = (process.env.APP_URL ?? "http://localhost:3000").replace(/\/$/, "");
/**
* Adresse, an die der Anbieter zurückschickt. Sie muss beim Anbieter **wortgleich** hinterlegt sein.
* Vorrang hat die eingetragene Dashboard-Adresse, dann `APP_URL` (#029).
*/
export async function rueckkehrAdresse(): Promise<string> {
const { dashboardUrl } = await systemEinstellungen();
const basis = (dashboardUrl || process.env.APP_URL || "http://localhost:3000").replace(/\/$/, "");
return `${basis}/api/auth/oidc/callback`;
}
@@ -55,7 +61,7 @@ export async function anmeldungStarten(einstellungen: OidcEinstellungen): Promis
const zustand = client.randomState();
const url = client.buildAuthorizationUrl(konfig, {
redirect_uri: rueckkehrAdresse(),
redirect_uri: await rueckkehrAdresse(),
scope: "openid email profile",
code_challenge: herausforderung,
code_challenge_method: "S256",