feat: initialize CalBook project with comprehensive scheduling, admin, and deployment infrastructure
This commit is contained in:
118
scripts/export-backup.sh
Executable file
118
scripts/export-backup.sh
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user