feat(deploy): Auslieferung als Docker-Stack und als Helper-Script (#026)

Docker: deploy/update.sh sichert, baut, migriert und prueft die
Erreichbarkeit; der Worker hat jetzt eine Zustandspruefung. Der ganze Stapel
wurde durchgespielt - bauen, starten, migrieren, anmelden, Agent-Skript
abrufen.

Dabei ein echter Fehler aufgefallen: Nach der Anmeldung zeigte die
Weiterleitung auf den internen Containernamen. Hinter einem Reverse Proxy
waere das eine tote Adresse gewesen. Weiterleitungen nehmen jetzt die
eingetragene Adresse, sonst APP_URL, sonst die Proxy-Kopfzeilen.

Dazu Skripte nach Art der Proxmox VE Helper-Scripts (Node und PostgreSQL
direkt im LXC, ohne Docker). Vorerst intern: Eine Einreichung setzt
oeffentlichen Quelltext voraus und laeuft ueber ProxmoxVED.

Ausserdem docs/roadmap.md: Bis 1.0.0 fehlt kein Code mehr, sondern
Erprobung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-19 00:11:33 +02:00
co-authored by Claude Opus 5
parent ffc80a8fcf
commit 6c3729bae8
17 changed files with 601 additions and 19 deletions
+63
View File
@@ -0,0 +1,63 @@
# Installation als LXC-Container nach Art der Proxmox VE Helper-Scripts
Diese beiden Skripte folgen den Konventionen von [community-scripts.org](https://community-scripts.org)
(der Nachfolge von tteck's Helper-Scripts). Sie legen einen unprivilegierten Debian-13-Container an,
installieren Node.js 24 und PostgreSQL 17, holen ProxLedger aus dem Git-Repository, richten zwei
systemd-Dienste ein (Dashboard und Worker) und schreiben die erzeugten Zugangsdaten nach
`/opt/proxledger/.credentials`.
| Datei | Zweck |
|---|---|
| `ct/proxledger.sh` | Container anlegen, Ressourcen abfragen, Aktualisierung (`update_script`) |
| `install/proxledger-install.sh` | Installation im Container |
## Verwendung im eigenen Netz
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)"
```
Das Gerüst (`build.func`, `install.func`) lädt das Skript von den community-scripts nach – das ist
öffentlich und ohne Konto erreichbar. Nur ProxLedger selbst kommt aus dem eigenen Git.
Zwei Umgebungsvariablen steuern die Quelle, solange das Repository nicht öffentlich ist:
```bash
PROXLEDGER_REPO=https://git.example.de/fw/ProxLedger.git PROXLEDGER_BRANCH=main bash -c "$(curl -fsSL …)"
```
Ist das Git-Repository nicht ohne Anmeldung lesbar, gehört ein Lesezugriffs-Token in die Adresse
(`https://<token>@git.example.de/...`). Das Token landet dann in der Prozessliste des Containers –
also besser einen Deploy-Token verwenden, der nur lesen darf.
## Was eine Einreichung bei community-scripts verlangen würde
Wir liefern die Skripte vorerst **nur intern** aus. Für eine Einreichung wären zusätzlich nötig:
1. **Öffentlich erreichbarer Quelltext.** Die Skripte dort holen die Anwendung praktisch immer aus
einem öffentlichen GitHub-Repository (`fetch_and_deploy_gh_release`). Ein privates Gitea genügt nicht.
2. **Pull Request gegen [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED)**, nicht gegen
ProxmoxVE. Neue Skripte, die direkt gegen ProxmoxVE eingereicht werden, werden ohne Prüfung
geschlossen.
3. **MIT-Lizenz** im eigenen Repository und der Lizenzkopf in beiden Skripten (steht bereits darin).
4. **Englische Dokumentation** – die Skripte selbst sind auf Englisch gehalten, die deutschen
Kommentare in diesem Verzeichnis müssten übersetzt werden.
5. **Metadaten für die Webseite.** Die JSON-Dateien liegen inzwischen nicht mehr im Skript-Repository;
wo sie hingehören, steht in der aktuellen Beitragsanleitung. Das ist beim Einreichen zu prüfen.
Die Skripte sind so geschrieben, dass für eine spätere Einreichung nur die Quelle zu tauschen und die
Kommentare zu übersetzen sind – am Ablauf ändert sich nichts.
## Unterschied zum Docker-Weg
| | Helper-Script (LXC) | Docker Compose |
|---|---|---|
| Umgebung | eigener Container auf dem Proxmox-Host | beliebiger Docker-Host |
| Datenbank | PostgreSQL im selben Container | eigener Dienst |
| Aktualisierung | `update`-Weg des Skripts | `bash deploy/update.sh` |
| Sicherung | Proxmox-Sicherung des Containers | `bash deploy/backup.sh` |
Beide Wege sind gleichwertig. Wer ohnehin Proxmox betreibt, fährt mit dem LXC-Weg meist einfacher –
die Sicherung des Containers erledigt dann die gewohnte Proxmox-Sicherung mit.
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# 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
APP="ProxLedger"
var_tags="${var_tags:-monitoring;licensing}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-10}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/proxledger ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
NODE_VERSION="24" setup_nodejs
msg_info "Stopping Services"
systemctl stop proxledger proxledger-worker
msg_ok "Stopped Services"
# Die .env traegt APP_SECRET; geht sie verloren, sind Sitzungen und gespeicherte
# Zugangsdaten unbrauchbar. Deshalb vor jedem Update sichern und danach zurueckspielen.
create_backup /opt/proxledger/.env
msg_info "Updating ${APP}"
cd /opt/proxledger
$STD git pull --ff-only
$STD npm ci
$STD npx prisma generate
$STD npx prisma migrate deploy
$STD npm run build
$STD npm run build:worker
msg_ok "Updated ${APP}"
restore_backup
msg_info "Starting Services"
systemctl start proxledger proxledger-worker
msg_ok "Started Services"
msg_ok "Update Successful"
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}"
echo -e "${INFO}${YW} Login credentials are in:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}/opt/proxledger/.credentials${CL}"
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# 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 /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
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}"
ZWEIG="${PROXLEDGER_BRANCH:-main}"
msg_info "Installing Dependencies"
$STD apt install -y git ca-certificates
msg_ok "Installed Dependencies"
NODE_VERSION="24" setup_nodejs
PG_VERSION="17" setup_postgresql
msg_info "Setting up Database"
DB_NAME="proxledger"
DB_USER="proxledger"
DB_PASS=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24)
ADMIN_PASS=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 20)
# APP_SECRET signiert die Sitzungen und verschluesselt gespeicherte Zugangsdaten.
APP_SECRET=$(openssl rand -base64 48)
$STD sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}';"
$STD sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} WITH OWNER ${DB_USER} ENCODING 'UTF8';"
msg_ok "Set up Database"
msg_info "Setting up ProxLedger"
$STD git clone --branch "$ZWEIG" --depth 1 "$QUELLE" /opt/proxledger
cd /opt/proxledger
IP_ADRESSE=$(hostname -I | awk '{print $1}')
cat <<EOF >/opt/proxledger/.env
DATABASE_URL="postgresql://${DB_USER}:${DB_PASS}@localhost:5432/${DB_NAME}?schema=public"
APP_SECRET="${APP_SECRET}"
APP_URL="http://${IP_ADRESSE}:3000"
ADMIN_EMAIL="admin@proxledger.local"
ADMIN_NAME="Administrator"
ADMIN_PASSWORD="${ADMIN_PASS}"
UPLOAD_DIR="/opt/proxledger/data/uploads"
NODE_ENV="production"
PORT="3000"
EOF
chmod 600 /opt/proxledger/.env
mkdir -p /opt/proxledger/data/uploads
$STD npm ci
$STD npx prisma generate
$STD npx prisma migrate deploy
$STD npx prisma db seed
$STD npm run build
$STD npm run build:worker
msg_ok "Set up ProxLedger"
msg_info "Creating Services"
cat <<EOF >/etc/systemd/system/proxledger.service
[Unit]
Description=ProxLedger Dashboard
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
WorkingDirectory=/opt/proxledger
EnvironmentFile=/opt/proxledger/.env
ExecStart=/usr/bin/npm run start
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
cat <<EOF >/etc/systemd/system/proxledger-worker.service
[Unit]
Description=ProxLedger Worker (Warnregeln und Benachrichtigungen)
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
WorkingDirectory=/opt/proxledger
EnvironmentFile=/opt/proxledger/.env
ExecStart=/usr/bin/node dist/worker.mjs
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now proxledger proxledger-worker
msg_ok "Created Services"
# Die Zugangsdaten liegen fuer den Betreiber lesbar im Container; APP_SECRET steht in der .env.
{
echo "ProxLedger"
echo "Adresse: http://${IP_ADRESSE}:3000"
echo "Benutzer: admin@proxledger.local"
echo "Passwort: ${ADMIN_PASS}"
echo
echo "Datenbank: ${DB_NAME} / ${DB_USER} / ${DB_PASS}"
echo
echo "Wichtig: APP_SECRET aus /opt/proxledger/.env mitsichern."
echo "Ohne diesen Wert sind Sitzungen und gespeicherte Zugangsdaten unbrauchbar."
} >>~/proxledger.creds
cp ~/proxledger.creds /opt/proxledger/.credentials
chmod 600 /opt/proxledger/.credentials
motd_ssh
customize
msg_info "Cleaning up"
$STD apt -y autoremove
$STD apt -y autoclean
msg_ok "Cleaned"
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# ProxLedger aktualisieren (Docker-Installation).
#
# bash deploy/update.sh # Quelltext holen, neu bauen, migrieren, neu starten
# bash deploy/update.sh --ohne-sicherung
#
# Vor dem Aktualisieren wird gesichert – das dauert Sekunden und erspart schlechte Abende.
set -o errexit
set -o nounset
set -o pipefail
VERZEICHNIS=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
cd "$VERZEICHNIS"
blau() { printf '\033[1;36m› %s\033[0m\n' "$*"; }
gruen() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; }
fehler() {
printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2
exit 1
}
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?"
if [ "$SICHERN" = "ja" ]; then
blau "Sicherung anlegen …"
bash deploy/backup.sh
gruen "Sicherung abgeschlossen."
fi
if [ -d .git ]; then
blau "Neuen Stand holen …"
git pull --ff-only
fi
blau "Abbilder neu bauen …"
docker compose build --pull
blau "Datenbank migrieren …"
docker compose run --rm migrate
blau "Dienste neu starten …"
docker compose up -d
# Kurz warten und nachsehen, ob das Dashboard wieder antwortet.
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
gruen "ProxLedger läuft wieder (nach ${versuch} s)."
docker compose ps
exit 0
fi
sleep 1
done
fehler "Das Dashboard antwortet nicht. Protokoll ansehen: docker compose logs -n 50 web"