feat(betrieb): Docker Compose und LXC-Installer
Ein Laufzeit-Paket (scripts/paket-bauen.sh) für beide Wege: Next standalone, worker als Bundle mit nur den nötigen Paketen, Migrationen ohne Prisma-CLI. Compose lokal komplett hochgefahren; der Installer auf pve-demo in einem echten Debian-13-LXC durchgelaufen. Closes #019 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Executable
+214
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env bash
|
||||
# Läuft im Debian-13-Container (von install-lxc.sh per `pct exec` aufgerufen):
|
||||
#
|
||||
# im-container.sh installieren --url http://10.0.10.50:3000
|
||||
# im-container.sh aktualisieren
|
||||
#
|
||||
# Erwartet das Paket unter /root/proxdeploy-paket.tar.gz.
|
||||
set -euo pipefail
|
||||
# Über SSH/pct exec reisen die Locales des Aufrufers mit; im frischen Container gibt es sie nicht.
|
||||
export LC_ALL=C.UTF-8 LANG=C.UTF-8
|
||||
unset LANGUAGE
|
||||
|
||||
NODE_VERSION="24.21.0"
|
||||
BASIS="/opt/proxdeploy"
|
||||
ENV_DATEI="/etc/proxdeploy/proxdeploy.env"
|
||||
PAKET="/root/proxdeploy-paket.tar.gz"
|
||||
|
||||
fehler() { echo "✗ $*" >&2; exit 1; }
|
||||
schritt() { echo "◐ $*"; }
|
||||
fertig() { echo "✓ $*"; }
|
||||
|
||||
node_installieren() {
|
||||
case "$(uname -m)" in
|
||||
x86_64) arch=x64 ;;
|
||||
aarch64) arch=arm64 ;;
|
||||
*) fehler "Architektur $(uname -m) wird nicht unterstützt." ;;
|
||||
esac
|
||||
if [ -x /opt/node/bin/node ] && [ "$(/opt/node/bin/node --version)" = "v$NODE_VERSION" ]; then return; fi
|
||||
schritt "Node.js $NODE_VERSION"
|
||||
local datei="node-v$NODE_VERSION-linux-$arch.tar.xz" tmp
|
||||
tmp="$(mktemp -d)"
|
||||
curl -fsSL -o "$tmp/$datei" "https://nodejs.org/dist/v$NODE_VERSION/$datei"
|
||||
curl -fsSL -o "$tmp/SHASUMS256.txt" "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt"
|
||||
(cd "$tmp" && grep " $datei\$" SHASUMS256.txt | sha256sum -c --quiet) || fehler "Prüfsumme von $datei stimmt nicht."
|
||||
rm -rf /opt/node && mkdir -p /opt/node
|
||||
tar -xJf "$tmp/$datei" -C /opt/node --strip-components=1
|
||||
rm -rf "$tmp"
|
||||
fertig "Node.js $(/opt/node/bin/node --version)"
|
||||
}
|
||||
|
||||
paket_auspacken() {
|
||||
[ -f "$PAKET" ] || fehler "$PAKET fehlt."
|
||||
local version ziel
|
||||
version="$(tar -xzOf "$PAKET" ./VERSION)"
|
||||
ziel="$BASIS/versionen/$version"
|
||||
rm -rf "$ziel" && mkdir -p "$ziel"
|
||||
tar -xzf "$PAKET" -C "$ziel"
|
||||
chown -R root:root "$ziel"
|
||||
ln -sfn "$ziel" "$BASIS/aktuell"
|
||||
rm -f "$PAKET"
|
||||
# Die letzten drei Fassungen behalten – für ein Zurück per Symlink.
|
||||
find "$BASIS/versionen" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\n' | sort -rn | tail -n +4 |
|
||||
cut -d' ' -f2- | xargs -r rm -rf
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
dienste_schreiben() {
|
||||
cat > /etc/systemd/system/proxdeploy-migrate.service <<EOF
|
||||
[Unit]
|
||||
Description=ProxDeploy – Datenbank-Migrationen
|
||||
After=postgresql.service network-online.target
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
User=proxdeploy
|
||||
EnvironmentFile=$ENV_DATEI
|
||||
WorkingDirectory=$BASIS/aktuell/app
|
||||
ExecStart=/opt/node/bin/node ../worker/migrieren.mjs
|
||||
EOF
|
||||
for dienst in app worker; do
|
||||
if [ "$dienst" = app ]; then start="server.js"; beschreibung="Web-GUI und REST-API"; else start="../worker/worker.mjs"; beschreibung="worker (Aufträge)"; fi
|
||||
cat > "/etc/systemd/system/proxdeploy-$dienst.service" <<EOF
|
||||
[Unit]
|
||||
Description=ProxDeploy – $beschreibung
|
||||
After=proxdeploy-migrate.service
|
||||
Requires=proxdeploy-migrate.service
|
||||
|
||||
[Service]
|
||||
User=proxdeploy
|
||||
EnvironmentFile=$ENV_DATEI
|
||||
Environment=NODE_ENV=production
|
||||
WorkingDirectory=$BASIS/aktuell/app
|
||||
ExecStart=/opt/node/bin/node $start
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=40
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectControlGroups=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
done
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
auf_gesundheit_warten() {
|
||||
for _ in $(seq 1 60); do
|
||||
if curl -fsS -o /dev/null "http://127.0.0.1:3000/api/v1/gesundheit" 2>/dev/null; then return 0; fi
|
||||
sleep 2
|
||||
done
|
||||
journalctl -u proxdeploy-app -u proxdeploy-migrate --no-pager -n 30 >&2
|
||||
fehler "Die app antwortet nicht."
|
||||
}
|
||||
|
||||
installieren() {
|
||||
local url=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--url) url="$2"; shift 2 ;;
|
||||
*) fehler "Unbekannte Option $1" ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$url" ] || fehler "--url fehlt."
|
||||
[ -f "$ENV_DATEI" ] && fehler "ProxDeploy ist hier schon eingerichtet – zum Aktualisieren: install-lxc.sh --aktualisieren <ctid>"
|
||||
|
||||
schritt "Pakete (PostgreSQL, curl)"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq --no-install-recommends ca-certificates curl xz-utils openssl postgresql >/dev/null
|
||||
fertig "PostgreSQL $(psql --version | awk '{print $3}')"
|
||||
node_installieren
|
||||
|
||||
schritt "Benutzer und Datenbank"
|
||||
id proxdeploy >/dev/null 2>&1 || useradd --system --home-dir /var/lib/proxdeploy --create-home --shell /usr/sbin/nologin proxdeploy
|
||||
local db_passwort admin_passwort
|
||||
db_passwort="$(openssl rand -hex 24)"
|
||||
admin_passwort="$(openssl rand -base64 18 | tr -d '/+=' | cut -c1-20)"
|
||||
su postgres -c "psql -q -v ON_ERROR_STOP=1" <<SQL
|
||||
DO \$\$ BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'proxdeploy') THEN
|
||||
CREATE ROLE proxdeploy LOGIN PASSWORD '$db_passwort';
|
||||
ELSE
|
||||
ALTER ROLE proxdeploy PASSWORD '$db_passwort';
|
||||
END IF;
|
||||
END \$\$;
|
||||
SQL
|
||||
su postgres -c "psql -tAc \"SELECT 1 FROM pg_database WHERE datname = 'proxdeploy'\"" | grep -q 1 ||
|
||||
su postgres -c "createdb -O proxdeploy proxdeploy"
|
||||
fertig "Datenbank proxdeploy"
|
||||
|
||||
schritt "Konfiguration $ENV_DATEI"
|
||||
mkdir -p /etc/proxdeploy
|
||||
umask 027
|
||||
cat > "$ENV_DATEI" <<EOF
|
||||
# ProxDeploy – erzeugt von im-container.sh am $(date -Iseconds)
|
||||
DATABASE_URL=postgresql://proxdeploy:$db_passwort@127.0.0.1:5432/proxdeploy
|
||||
PROXDEPLOY_SCHLUESSEL=$(openssl rand -base64 32)
|
||||
SITZUNG_GEHEIMNIS=$(openssl rand -base64 48)
|
||||
APP_URL=$url
|
||||
# Hinter einem Reverse-Proxy auf true setzen (wertet X-Forwarded-* aus).
|
||||
TRUST_PROXY=false
|
||||
PORT=3000
|
||||
HOSTNAME=0.0.0.0
|
||||
LOG_LEVEL=info
|
||||
WORKER_PARALLEL=4
|
||||
ADMIN_BENUTZER=admin
|
||||
ADMIN_PASSWORT=$admin_passwort
|
||||
EOF
|
||||
chown root:proxdeploy "$ENV_DATEI"
|
||||
chmod 0640 "$ENV_DATEI"
|
||||
umask 022
|
||||
|
||||
schritt "ProxDeploy"
|
||||
local version
|
||||
version="$(paket_auspacken)"
|
||||
dienste_schreiben
|
||||
systemctl enable --now proxdeploy-app proxdeploy-worker >/dev/null 2>&1
|
||||
auf_gesundheit_warten
|
||||
# Der erste Admin ist angelegt – das Passwort hat in der Datei nichts mehr zu suchen.
|
||||
sed -i '/^ADMIN_PASSWORT=/d' "$ENV_DATEI"
|
||||
fertig "ProxDeploy $version läuft"
|
||||
|
||||
cat <<EOF
|
||||
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
ProxDeploy ist bereit: $url
|
||||
Benutzer: admin
|
||||
Passwort: $admin_passwort
|
||||
(wird nur jetzt angezeigt – nach der Anmeldung unter
|
||||
Einstellungen → Passwort ändern)
|
||||
Konfiguration: $ENV_DATEI
|
||||
Logs: journalctl -u proxdeploy-app -u proxdeploy-worker
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
EOF
|
||||
}
|
||||
|
||||
aktualisieren() {
|
||||
[ -f "$ENV_DATEI" ] || fehler "Hier ist ProxDeploy noch nicht eingerichtet."
|
||||
node_installieren
|
||||
local alt version
|
||||
alt="$(cat "$BASIS/aktuell/VERSION" 2>/dev/null || echo –)"
|
||||
systemctl stop proxdeploy-app proxdeploy-worker
|
||||
version="$(paket_auspacken)"
|
||||
dienste_schreiben
|
||||
# migrate bleibt nach dem ersten Lauf „aktiv“ – für die neue Fassung ausdrücklich neu starten.
|
||||
systemctl restart proxdeploy-migrate
|
||||
systemctl start proxdeploy-app proxdeploy-worker
|
||||
auf_gesundheit_warten
|
||||
fertig "ProxDeploy $alt → $version"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
installieren) shift; installieren "$@" ;;
|
||||
aktualisieren) shift; aktualisieren "$@" ;;
|
||||
*) fehler "Aufruf: $0 installieren --url URL | aktualisieren" ;;
|
||||
esac
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
# ProxDeploy in einem Debian-13-LXC einrichten – auf dem Proxmox-Host als root ausführen.
|
||||
#
|
||||
# ./install-lxc.sh --paket proxdeploy-0.6.0.tar.gz [Optionen]
|
||||
# ./install-lxc.sh --aktualisieren <ctid> --paket proxdeploy-0.6.1.tar.gz
|
||||
#
|
||||
# Optionen (alle optional außer --paket):
|
||||
# --ctid N Container-ID (Standard: nächste freie)
|
||||
# --hostname NAME Hostname (Standard: proxdeploy)
|
||||
# --storage NAME Storage für die Disk (Standard: local-lvm)
|
||||
# --disk GB Disk in GB (Standard: 8)
|
||||
# --cores N CPU-Kerne (Standard: 2)
|
||||
# --memory MB RAM (Standard: 2048)
|
||||
# --bridge NAME Bridge (Standard: vmbr0)
|
||||
# --vlan TAG VLAN-Tag
|
||||
# --ip CIDR feste Adresse, z. B. 10.0.10.50/24 (Standard: DHCP); dazu --gw
|
||||
# --gw ADRESSE Gateway bei fester Adresse
|
||||
# --url URL öffentliche Adresse (APP_URL), z. B. https://proxdeploy.example
|
||||
# (Standard: http://<IP des Containers>:3000)
|
||||
# --tags LISTE zusätzliche Proxmox-Tags, z. B. proxdeploy-test
|
||||
#
|
||||
# Das Paket baut `scripts/paket-bauen.sh <version> --tar` im Repository.
|
||||
set -euo pipefail
|
||||
|
||||
fehler() { echo "✗ $*" >&2; exit 1; }
|
||||
schritt() { echo "◐ $*"; }
|
||||
fertig() { echo "✓ $*"; }
|
||||
|
||||
[ "$(id -u)" = 0 ] || fehler "Bitte als root auf dem Proxmox-Host ausführen."
|
||||
command -v pct >/dev/null || fehler "pct fehlt – läuft das auf einem Proxmox-VE-Host?"
|
||||
|
||||
ctid="" hostname="proxdeploy" storage="local-lvm" disk=8 cores=2 memory=2048 bridge="vmbr0"
|
||||
vlan="" ip="dhcp" gw="" url="" paket="" aktualisieren="" tags=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--ctid) ctid="$2"; shift 2 ;;
|
||||
--hostname) hostname="$2"; shift 2 ;;
|
||||
--storage) storage="$2"; shift 2 ;;
|
||||
--disk) disk="$2"; shift 2 ;;
|
||||
--cores) cores="$2"; shift 2 ;;
|
||||
--memory) memory="$2"; shift 2 ;;
|
||||
--bridge) bridge="$2"; shift 2 ;;
|
||||
--vlan) vlan="$2"; shift 2 ;;
|
||||
--ip) ip="$2"; shift 2 ;;
|
||||
--gw) gw="$2"; shift 2 ;;
|
||||
--url) url="$2"; shift 2 ;;
|
||||
--tags) tags="$2"; shift 2 ;;
|
||||
--paket) paket="$2"; shift 2 ;;
|
||||
--aktualisieren) aktualisieren="$2"; shift 2 ;;
|
||||
-h|--help) sed -n '2,25p' "$0"; exit 0 ;;
|
||||
*) fehler "Unbekannte Option $1 (siehe --help)" ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$paket" ] || fehler "--paket fehlt (proxdeploy-<version>.tar.gz)."
|
||||
[ -f "$paket" ] || fehler "$paket gibt es nicht."
|
||||
tar -tzf "$paket" ./VERSION >/dev/null 2>&1 || fehler "$paket ist kein ProxDeploy-Paket (VERSION fehlt)."
|
||||
version="$(tar -xzOf "$paket" ./VERSION)"
|
||||
innen="$(dirname "$0")/im-container.sh"
|
||||
[ -f "$innen" ] || fehler "im-container.sh fehlt neben diesem Skript."
|
||||
|
||||
pushen() {
|
||||
pct push "$1" "$paket" /root/proxdeploy-paket.tar.gz
|
||||
pct push "$1" "$innen" /root/proxdeploy-im-container.sh --perms 0700
|
||||
}
|
||||
|
||||
# ── Aktualisieren ───────────────────────────────────────────────────────────────────────────────
|
||||
if [ -n "$aktualisieren" ]; then
|
||||
pct status "$aktualisieren" | grep -q running || fehler "Container $aktualisieren läuft nicht."
|
||||
schritt "Aktualisiere Container $aktualisieren auf $version"
|
||||
pushen "$aktualisieren"
|
||||
pct exec "$aktualisieren" -- bash /root/proxdeploy-im-container.sh aktualisieren
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Neu einrichten ──────────────────────────────────────────────────────────────────────────────
|
||||
[ -n "$ctid" ] || ctid="$(pvesh get /cluster/nextid)"
|
||||
pct status "$ctid" >/dev/null 2>&1 && fehler "Container $ctid gibt es schon."
|
||||
if [ "$ip" != dhcp ]; then
|
||||
[[ "$ip" =~ ^[0-9.]+/[0-9]+$ ]] || fehler "--ip: Format 10.0.10.50/24"
|
||||
[ -n "$gw" ] || fehler "Bei fester Adresse fehlt --gw."
|
||||
fi
|
||||
|
||||
schritt "LXC-Template Debian 13"
|
||||
pveam update >/dev/null
|
||||
vorlage="$(pveam available --section system | awk '/debian-13-standard_.*_amd64\.tar\.zst/ {print $2}' | sort -V | tail -1)"
|
||||
[ -n "$vorlage" ] || fehler "Kein Debian-13-Template in pveam gefunden."
|
||||
template_storage="$(pvesm status --content vztmpl | awk 'NR>1 {print $1; exit}')"
|
||||
[ -n "$template_storage" ] || fehler "Kein Storage für Container-Templates (Inhalt vztmpl)."
|
||||
if ! pveam list "$template_storage" | grep -q "$vorlage"; then
|
||||
pveam download "$template_storage" "$vorlage" >/dev/null
|
||||
fi
|
||||
fertig "$vorlage auf $template_storage"
|
||||
|
||||
netz="name=eth0,bridge=$bridge,firewall=1"
|
||||
[ -n "$vlan" ] && netz="$netz,tag=$vlan"
|
||||
if [ "$ip" = dhcp ]; then netz="$netz,ip=dhcp"; else netz="$netz,ip=$ip,gw=$gw"; fi
|
||||
|
||||
schritt "Container $ctid anlegen"
|
||||
pct create "$ctid" "$template_storage:vztmpl/$vorlage" \
|
||||
--hostname "$hostname" --cores "$cores" --memory "$memory" --swap 512 \
|
||||
--rootfs "$storage:$disk" --net0 "$netz" \
|
||||
--unprivileged 1 --features nesting=1 --onboot 1 \
|
||||
--tags "proxdeploy${tags:+;$tags}" \
|
||||
--description "ProxDeploy $version – eingerichtet mit install-lxc.sh" >/dev/null
|
||||
pct start "$ctid"
|
||||
fertig "Container $ctid ($hostname) läuft"
|
||||
|
||||
schritt "Netzwerk"
|
||||
adresse=""
|
||||
for _ in $(seq 1 60); do
|
||||
adresse="$(pct exec "$ctid" -- ip -4 -o addr show dev eth0 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1)"
|
||||
[ -n "$adresse" ] && pct exec "$ctid" -- getent hosts deb.debian.org >/dev/null 2>&1 && break
|
||||
adresse=""
|
||||
sleep 2
|
||||
done
|
||||
[ -n "$adresse" ] || fehler "Container $ctid bekommt keine Adresse oder kein DNS."
|
||||
fertig "IP $adresse"
|
||||
|
||||
[ -n "$url" ] || url="http://$adresse:3000"
|
||||
pushen "$ctid"
|
||||
pct exec "$ctid" -- bash /root/proxdeploy-im-container.sh installieren --url "$url"
|
||||
Reference in New Issue
Block a user