#!/usr/bin/env bash # Nexus One AI backup/restore helper. # # Usage: # sudo bash scripts/cezen-backup.sh backup # sudo bash scripts/cezen-backup.sh list # sudo bash scripts/cezen-backup.sh restore /opt/cezen/backups/cezen-backup-YYYYmmdd-HHMMSS.zip set -euo pipefail ACTION="${1:-backup}" TARGET="${2:-}" DATA_DIR="${CEZEN_DATA:-/opt/cezen/data}" BACKUP_DIR="${CEZEN_BACKUP_DIR:-/opt/cezen/backups}" python3 - "$ACTION" "$TARGET" "$DATA_DIR" "$BACKUP_DIR" <<'PY' import json import shutil import sys import zipfile from datetime import datetime, timezone from pathlib import Path action, target, data_dir, backup_dir = sys.argv[1:5] data_dir = Path(data_dir) backup_dir = Path(backup_dir) backup_dir.mkdir(parents=True, exist_ok=True) def now_tag(): return datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S") def iso_now(): return datetime.now(timezone.utc).isoformat() def write_backup(dest): manifest = { "schema": "cezen.backup_manifest.v1", "created_at": iso_now(), "data_dir": str(data_dir), "source": "cezen-backup.sh", } with zipfile.ZipFile(dest, "w", compression=zipfile.ZIP_DEFLATED) as zf: zf.writestr("manifest.json", json.dumps(manifest, indent=2)) if data_dir.exists(): for path in data_dir.rglob("*"): if path.is_file(): zf.write(path, path.relative_to(data_dir).as_posix()) def restore(src): src = Path(src) if not src.exists(): raise SystemExit(f"Backup not found: {src}") safety = backup_dir / f"pre-restore-{now_tag()}.zip" write_backup(safety) root = data_dir.resolve() with zipfile.ZipFile(src, "r") as zf: for member in zf.infolist(): if member.filename == "manifest.json" or member.is_dir(): continue target_path = (data_dir / member.filename).resolve() if root not in target_path.parents and target_path != root: raise SystemExit(f"Unsafe archive path: {member.filename}") target_path.parent.mkdir(parents=True, exist_ok=True) with zf.open(member) as source, open(target_path, "wb") as out: shutil.copyfileobj(source, out) print(json.dumps({"ok": True, "restored": str(src), "pre_restore_snapshot": str(safety)}, indent=2)) if action == "backup": dest = backup_dir / f"cezen-backup-{now_tag()}.zip" write_backup(dest) print(json.dumps({"ok": True, "backup": str(dest)}, indent=2)) elif action == "list": rows = [] for path in sorted(backup_dir.glob("cezen-backup-*.zip"), key=lambda p: p.stat().st_mtime, reverse=True): rows.append({"name": path.name, "path": str(path), "size_bytes": path.stat().st_size}) print(json.dumps({"backup_dir": str(backup_dir), "backups": rows}, indent=2)) elif action == "restore": if not target: raise SystemExit("Usage: cezen-backup.sh restore /path/to/backup.zip") restore(target) else: raise SystemExit("Usage: cezen-backup.sh backup|list|restore [backup.zip]") PY