| #!/bin/bash |
| |
| |
| |
|
|
| set -euo pipefail |
|
|
| DATE=$(date +%Y%m%d_%H%M%S) |
| BACKUP_DIR=/backups/$DATE |
| KEEP_DAYS=7 |
| LOG=/var/log/backup.log |
|
|
| mkdir -p "$BACKUP_DIR" |
|
|
| log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG"; } |
|
|
| log "=== Backup started ===" |
|
|
| |
| log "Backing up Postgres..." |
| docker exec rmi-postgres pg_dump -U rmi rmi 2>/dev/null | gzip > "$BACKUP_DIR/postgres.sql.gz" |
| PG_SIZE=$(du -h "$BACKUP_DIR/postgres.sql.gz" | cut -f1) |
| log " ✓ postgres.sql.gz ($PG_SIZE)" |
|
|
| |
| log "Backing up Neo4j..." |
| docker exec rmi-neo4j neo4j-admin database dump neo4j --to-path=/tmp 2>/dev/null || true |
| if docker cp rmi-neo4j:/tmp/neo4j.dump "$BACKUP_DIR/" 2>/dev/null; then |
| NEO_SIZE=$(du -h "$BACKUP_DIR/neo4j.dump" | cut -f1) |
| log " ✓ neo4j.dump ($NEO_SIZE)" |
| else |
| log " âš Neo4j dump failed (skipping)" |
| fi |
|
|
| |
| log "Backing up Qdrant..." |
| for collection in $(curl -s http://localhost:6333/collections | python3 -c " |
| import json, sys |
| try: |
| d = json.load(sys.stdin) |
| for c in d.get('result', {}).get('collections', []): |
| print(c['name']) |
| except: pass |
| "); do |
| SNAP=$(curl -s -X POST "http://localhost:6333/collections/$collection/snapshots" 2>/dev/null | python3 -c " |
| import json, sys |
| try: print(json.load(sys.stdin).get('result', {}).get('name', '')) |
| except: pass |
| ") |
| if [ -n "$SNAP" ]; then |
| mkdir -p "$BACKUP_DIR/qdrant/$collection" |
| docker cp "rmi-qdrant:/qdrant/snapshots/$collection/$SNAP" "$BACKUP_DIR/qdrant/$collection/$SNAP" 2>/dev/null || true |
| log " ✓ qdrant/$collection/$SNAP" |
| fi |
| done |
|
|
| |
| log "Backing up MinIO..." |
| if command -v rclone >/dev/null 2>&1; then |
| rclone sync minio:rmi-assets "$BACKUP_DIR/minio/" --transfers 4 2>>"$LOG" || log " âš rclone sync partial" |
| else |
| |
| docker run --rm -v /data:/data --network rmi_network minio/mc:latest \ |
| mirror --overwrite minio/rmi-assets "$BACKUP_DIR/minio/" 2>>"$LOG" || log " âš mc mirror failed" |
| fi |
|
|
| |
| log "Backing up GlitchTip..." |
|
|
| |
| log "Backing up system configs..." |
| tar -czf "$BACKUP_DIR/etc_root.tar.gz" \ |
| --exclude='/root/.cache' \ |
| --exclude='/root/.ollama' \ |
| --exclude='/root/.hermes/sessions' \ |
| --exclude='/root/.cargo' \ |
| --exclude='/root/.rustup' \ |
| --exclude='/root/.local/share/cargo' \ |
| /etc/prometheus \ |
| /root/scripts /root/.hermes /root/.ssh /root/.bashrc /root/.profile \ |
| /root/backend/.env \ |
| 2>/dev/null || true |
| if [ -f "$BACKUP_DIR/etc_root.tar.gz" ]; then |
| ER_SIZE=$(du -h "$BACKUP_DIR/etc_root.tar.gz" | cut -f1) |
| log " ✓ etc_root.tar.gz ($ER_SIZE)" |
| if tar -tzf "$BACKUP_DIR/etc_root.tar.gz" 2>/dev/null | grep -q "etc/prometheus/prometheus.yml"; then |
| log " ✓ etc_root.tar.gz valid" |
| fi |
| else |
| log " âš etc_root.tar.gz creation failed" |
| fi |
| log "Backing up GlitchTip..." |
| docker exec rmi-glitchtip-db pg_dump -U glitchtip glitchtip 2>/dev/null | gzip > "$BACKUP_DIR/glitchtip.sql.gz" || true |
|
|
| |
| log "Rotating old backups (keep ${KEEP_DAYS} days)..." |
| find /backups -maxdepth 1 -type d -mtime +$KEEP_DAYS -exec rm -rf {} \; 2>/dev/null || true |
| REMAINING=$(ls -1d /backups/*/ 2>/dev/null | wc -l) |
| TOTAL_SIZE=$(du -sh /backups 2>/dev/null | cut -f1) |
|
|
| log "=== Backup complete: $BACKUP_DIR ===" |
| log " Remaining snapshots: $REMAINING" |
| log " Total backup size: $TOTAL_SIZE" |
|
|
| |
| if [ "$(date +%d)" = "01" ]; then |
| log "=== Monthly restore test ===" |
| /root/scripts/restore_test.sh 2>&1 | tee -a "$LOG" |
| fi |
|
|
| |
| if curl -s -o /dev/null -w '%{http_code}' http://localhost:9080 2>/dev/null | grep -q '^2'; then |
| curl -s -d "Backup complete: $BACKUP_DIR ($TOTAL_SIZE, $REMAINING snapshots)" \ |
| http://localhost:9080/rmi-backups 2>/dev/null || true |
| fi |
|
|