chore(ops): harden deployment and runtime setup

This commit is contained in:
Schubert Ferenc 2026-07-03 22:59:08 +02:00
parent 1d7e7c41c9
commit c35bd5877e
30 changed files with 534 additions and 61 deletions

13
scripts/backup.sh Executable file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env sh
set -eu
BACKUP_DIR="${BACKUP_DIR:-backups}"
STAMP="$(date +%Y%m%d-%H%M%S)"
TARGET="${BACKUP_DIR}/funktechnik-data-${STAMP}.tar.gz"
mkdir -p "$BACKUP_DIR"
tar -czf "$TARGET" data public/uploads
echo "Backup erstellt: $TARGET"
echo "Hinweis: .env wird aus Sicherheitsgruenden nicht automatisch gesichert."
echo "Bitte .env separat und sicher ausserhalb des Repositories sichern."

19
scripts/deploy.sh Executable file
View file

@ -0,0 +1,19 @@
#!/usr/bin/env sh
set -eu
COMPOSE="${COMPOSE:-docker compose}"
BASE_URL="${BASE_URL:-http://localhost:3010}"
echo "Pruefe Docker Compose Konfiguration..."
$COMPOSE config >/dev/null
echo "Baue Container..."
$COMPOSE build
echo "Starte Container..."
$COMPOSE up -d
echo "Fuehre Healthcheck aus..."
BASE_URL="$BASE_URL" scripts/healthcheck.sh
echo "Deployment abgeschlossen."

14
scripts/healthcheck.sh Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env sh
set -eu
BASE_URL="${BASE_URL:-http://localhost:3010}"
HEALTH_URL="${BASE_URL%/}/api/health"
echo "Checking ${HEALTH_URL}"
response="$(curl -fsS "$HEALTH_URL")"
echo "$response"
case "$response" in
*'"status":"ok"'*) exit 0 ;;
*) echo "Healthcheck failed" >&2; exit 1 ;;
esac

17
scripts/restore.sh Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env sh
set -eu
if [ "${1:-}" = "" ]; then
echo "Usage: scripts/restore.sh <backup.tar.gz>" >&2
exit 1
fi
BACKUP_FILE="$1"
if [ ! -f "$BACKUP_FILE" ]; then
echo "Backup nicht gefunden: $BACKUP_FILE" >&2
exit 1
fi
tar -xzf "$BACKUP_FILE"
echo "Restore abgeschlossen: $BACKUP_FILE"

36
scripts/runtime-start.js Normal file
View file

@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const fs = require("fs");
const path = require("path");
const requiredEnv = ["ADMIN_EMAIL", "ADMIN_PASSWORD", "ADMIN_SESSION_SECRET"];
const requiredDirs = [
path.join(process.cwd(), "data"),
path.join(process.cwd(), "public", "uploads", "images"),
path.join(process.cwd(), ".next", "cache"),
];
function fail(message) {
process.stderr.write(`[funktechnik-website] ${message}\n`);
process.exit(1);
}
function ensureWritableDirectory(directory) {
fs.mkdirSync(directory, { recursive: true });
const probe = path.join(directory, `.write-check-${Date.now()}`);
fs.writeFileSync(probe, "ok", "utf8");
fs.rmSync(probe, { force: true });
}
const missingEnv = requiredEnv.filter((name) => !process.env[name]);
if (missingEnv.length > 0) {
fail(`Start abgebrochen: fehlende Umgebungsvariablen: ${missingEnv.join(", ")}. Bitte .env anhand von .env.example konfigurieren.`);
}
try {
for (const directory of requiredDirs) ensureWritableDirectory(directory);
} catch (error) {
fail(`Start abgebrochen: Runtime-Verzeichnis ist nicht beschreibbar. Details: ${error instanceof Error ? error.message : "unbekannter Fehler"}`);
}
process.stdout.write(`[funktechnik-website] Runtime checks ok. Starting version ${process.env.NEXT_PUBLIC_APP_VERSION ?? "0.2.2"}.\n`);
require("./server.js");