#!/usr/bin/env bash # export-backup.sh – Standalone-Export für CalBook (braucht nur Docker + .env) # Funktioniert mit jeder CalBook-Version, auch ohne die Backup-API. # Output: calbook-backup-YYYY-MM-DD.json set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ENV_FILE="${ROOT_DIR}/.env" if [[ ! -f "${ENV_FILE}" ]]; then echo "Fehler: .env nicht gefunden in ${ROOT_DIR}" exit 1 fi get_env() { local key="$1" local line val line="$(grep -E "^${key}=" "${ENV_FILE}" | head -n1 || true)" [[ -z "${line}" ]] && { echo ""; return; } val="${line#*=}" val="${val#\"}"; val="${val%\"}" val="${val#\'}"; val="${val%\'}" echo "${val}" } if ! command -v docker >/dev/null 2>&1; then echo "Fehler: Docker wird benötigt." exit 1 fi STACK_NAME="$(get_env STACK_NAME)" [[ -z "${STACK_NAME}" ]] && STACK_NAME="calbook" CONTAINER="${STACK_NAME}-db" if ! docker ps --format '{{.Names}}' | grep -qx "${CONTAINER}"; then echo "Fehler: DB-Container '${CONTAINER}' läuft nicht." exit 1 fi OUTPUT_FILE="calbook-backup-$(date '+%Y-%m-%d').json" TEMP_DIR="$(mktemp -d)" trap 'rm -rf "${TEMP_DIR}"' EXIT echo "Exportiere Datenbank via Container '${CONTAINER}'..." run_query() { docker exec "${CONTAINER}" psql -U calbook -d calbook -t -A -F $'\t' -c "$1" > "$2" } # ── Settings ───────────────────────────────────────── run_query "SELECT json_agg(t) FROM (SELECT key, value FROM \"Setting\" WHERE key NOT IN ('PUBLIC_URL', 'NEXTAUTH_URL', 'APP_BASE_URL') ORDER BY key) t;" \ "${TEMP_DIR}/settings.json" # ── Users ──────────────────────────────────────────── run_query "SELECT json_agg(t) FROM (SELECT id, name, email, \"hashedPassword\", role, slug, bio, \"avatarUrl\", timezone, \"isActive\", \"createdAt\", \"updatedAt\" FROM \"User\" ORDER BY email) t;" \ "${TEMP_DIR}/users.json" # ── Calendar connections ───────────────────────────── run_query "SELECT json_agg(t) FROM (SELECT id, \"userId\", name, \"bookingAllowedWeekdays\", \"bookingDayStartTime\", \"bookingDayEndTime\", \"bookingDayRangesJson\", url, username, \"notificationEmail\", \"encryptedPassword\", color, \"syncEnabled\", \"lastSyncedAt\", \"createdAt\", \"updatedAt\" FROM \"CalendarConn\" ORDER BY \"createdAt\") t;" \ "${TEMP_DIR}/calendars.json" # ── Appointments ───────────────────────────────────── run_query "SELECT json_agg(t) FROM (SELECT * FROM \"Appointment\" ORDER BY \"startAt\") t;" \ "${TEMP_DIR}/appointments.json" # ── Busy blocks ────────────────────────────────────── run_query "SELECT json_agg(t) FROM (SELECT * FROM \"BusyBlock\" ORDER BY \"startAt\") t;" \ "${TEMP_DIR}/busyblocks.json" # ── Delivery issues ────────────────────────────────── run_query "SELECT json_agg(t) FROM (SELECT * FROM \"DeliveryIssue\" ORDER BY \"firstSeenAt\") t;" \ "${TEMP_DIR}/delivery.json" # ── Sync runs + logs ───────────────────────────────── run_query "SELECT json_agg(t) FROM (SELECT r.*, (SELECT json_agg(e) FROM (SELECT * FROM \"CalendarSyncLogEntry\" e WHERE e.\"syncRunId\" = r.id ORDER BY e.\"createdAt\") e) AS entries FROM \"CalendarSyncRun\" r ORDER BY r.\"startedAt\") t;" \ "${TEMP_DIR}/syncruns.json" # ── Assemble via node (keine externen Dependencies) ── echo "Erstelle Backup-Datei..." EXPORTED_AT="$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" CALDAV_KEY="$(get_env CALDAV_ENCRYPTION_KEY)" node - "$CALDAV_KEY" "$EXPORTED_AT" "$OUTPUT_FILE" "$TEMP_DIR" << 'NODEEOF' const fs = require('fs'); const caldavKey = process.argv[2] || ""; const exportedAt = process.argv[3]; const outputFile = process.argv[4]; const tempDir = process.argv[5]; const files = ['settings','users','calendars','appointments','busyblocks','delivery','syncruns']; const data = {}; for (const f of files) { const raw = fs.readFileSync(tempDir + '/' + f + '.json', 'utf8').trim(); data[f] = raw ? JSON.parse(raw) : []; } const backup = { version: 1, exportedAt: exportedAt, caldavEncryptionKey: caldavKey || undefined, settings: data.settings || [], users: data.users || [], calendarConns: data.calendars || [], appointments: data.appointments || [], busyBlocks: data.busyblocks || [], deliveryIssues: data.delivery || [], syncRuns: data.syncruns || [] }; fs.writeFileSync(outputFile, JSON.stringify(backup, null, 2)); NODEEOF echo echo "Backup erstellt: ${OUTPUT_FILE}" echo "Größe: $(du -h "${OUTPUT_FILE}" | cut -f1)" echo echo "Import auf neuer Instanz:" echo " Admin → Backup → Datei auswählen → Importieren"