131 lines
6.3 KiB
Bash
131 lines
6.3 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# /opt/docker/backup.sh
|
||
|
|
# Backs up all Docker service databases and named volumes to /opt/backups/
|
||
|
|
# Run as root via cron. Keeps 7 daily backups.
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
BACKUP_DIR="/opt/backups"
|
||
|
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||
|
|
BACKUP_PATH="${BACKUP_DIR}/${TIMESTAMP}"
|
||
|
|
LOG="${BACKUP_DIR}/backup.log"
|
||
|
|
KEEP_DAYS=7
|
||
|
|
|
||
|
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
|
||
|
|
|
||
|
|
mkdir -p "$BACKUP_PATH"
|
||
|
|
log "=== Backup started: ${TIMESTAMP} ==="
|
||
|
|
|
||
|
|
# ── Matrix PostgreSQL ──────────────────────────────────────────────
|
||
|
|
log "Dumping Matrix PostgreSQL..."
|
||
|
|
if docker ps --format '{{.Names}}' | grep -q '^matrix-postgres$'; then
|
||
|
|
docker exec matrix-postgres pg_dump -U synapse synapse \
|
||
|
|
| gzip > "${BACKUP_PATH}/matrix-postgres-${TIMESTAMP}.sql.gz" \
|
||
|
|
&& log " Matrix Postgres: OK ($(du -sh "${BACKUP_PATH}/matrix-postgres-${TIMESTAMP}.sql.gz" | cut -f1))" \
|
||
|
|
|| log " Matrix Postgres: FAILED"
|
||
|
|
else
|
||
|
|
log " matrix-postgres not running — skipping"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── Rocket.Chat MongoDB ────────────────────────────────────────────
|
||
|
|
log "Dumping Rocket.Chat MongoDB..."
|
||
|
|
if docker ps --format '{{.Names}}' | grep -q '^mongodb$'; then
|
||
|
|
docker exec mongodb mongodump \
|
||
|
|
-u admin -p CHANGE_ME_MONGO_ADMIN_PASSWORD \
|
||
|
|
--authenticationDatabase admin \
|
||
|
|
--db rocketchat \
|
||
|
|
--archive \
|
||
|
|
| gzip > "${BACKUP_PATH}/rocketchat-mongo-${TIMESTAMP}.archive.gz" \
|
||
|
|
&& log " MongoDB: OK ($(du -sh "${BACKUP_PATH}/rocketchat-mongo-${TIMESTAMP}.archive.gz" | cut -f1))" \
|
||
|
|
|| log " MongoDB: FAILED"
|
||
|
|
else
|
||
|
|
log " mongodb not running — skipping"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── Named Docker volumes ───────────────────────────────────────────
|
||
|
|
log "Backing up Docker volumes..."
|
||
|
|
for VOLUME in synapse-media rocketchat-uploads; do
|
||
|
|
if docker volume ls --format '{{.Name}}' | grep -q "^matrix_${VOLUME}\|^rocketchat_${VOLUME}\|^${VOLUME}$"; then
|
||
|
|
ACTUAL_VOL=$(docker volume ls --format '{{.Name}}' | grep "${VOLUME}" | head -1)
|
||
|
|
docker run --rm \
|
||
|
|
-v "${ACTUAL_VOL}:/volume:ro" \
|
||
|
|
-v "${BACKUP_PATH}:/backup" \
|
||
|
|
alpine \
|
||
|
|
tar czf "/backup/${VOLUME}-${TIMESTAMP}.tar.gz" -C /volume . \
|
||
|
|
&& log " Volume ${VOLUME}: OK" \
|
||
|
|
|| log " Volume ${VOLUME}: FAILED"
|
||
|
|
else
|
||
|
|
log " Volume ${VOLUME}: not found — skipping"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
# ── Config files (bind mounts) ─────────────────────────────────────
|
||
|
|
log "Backing up config directories..."
|
||
|
|
tar czf "${BACKUP_PATH}/configs-${TIMESTAMP}.tar.gz" \
|
||
|
|
/opt/docker/traefik/traefik.yml \
|
||
|
|
/opt/docker/traefik/config/ \
|
||
|
|
/opt/docker/matrix/docker-compose.yml \
|
||
|
|
/opt/docker/matrix/element-config/ \
|
||
|
|
/opt/docker/matrix/synapse-config/homeserver.yaml \
|
||
|
|
/opt/docker/matrix/synapse-config/matrix.example.com.log.config \
|
||
|
|
/opt/docker/rocketchat/docker-compose.yml \
|
||
|
|
2>/dev/null && log " Configs: OK" || log " Configs: partial (some files missing)"
|
||
|
|
|
||
|
|
# IMPORTANT: signing key is sensitive — back up separately with tight perms
|
||
|
|
if [ -f /opt/docker/matrix/synapse-config/matrix.example.com.signing.key ]; then
|
||
|
|
cp /opt/docker/matrix/synapse-config/matrix.example.com.signing.key \
|
||
|
|
"${BACKUP_PATH}/synapse-signing-key-${TIMESTAMP}.key"
|
||
|
|
chmod 600 "${BACKUP_PATH}/synapse-signing-key-${TIMESTAMP}.key"
|
||
|
|
log " Synapse signing key: backed up (600)"
|
||
|
|
fi
|
||
|
|
# ── Minecraft server ───────────────────────────────────────────────
|
||
|
|
log "Backing up Minecraft server..."
|
||
|
|
if docker ps --format '{{.Names}}' | grep -q '^minecraft-mc$'; then
|
||
|
|
# Server is running - create consistent world snapshot
|
||
|
|
docker exec minecraft-mc bash -c \
|
||
|
|
"cd /data && tar czf /tmp/mc-world-backup-${TIMESTAMP}.tar.gz world/ world_nether/ world_the_end/ 2>/dev/null" && \
|
||
|
|
docker cp minecraft-mc:/tmp/mc-world-backup-${TIMESTAMP}.tar.gz "${BACKUP_PATH}/" && \
|
||
|
|
docker exec minecraft-mc rm -f /tmp/mc-world-backup-${TIMESTAMP}.tar.gz && \
|
||
|
|
log " Minecraft world: OK ($(du -sh "${BACKUP_PATH}/mc-world-backup-${TIMESTAMP}.tar.gz" | cut -f1))" \
|
||
|
|
|| log " Minecraft world: FAILED"
|
||
|
|
|
||
|
|
# Backup configs and plugins
|
||
|
|
tar czf "${BACKUP_PATH}/minecraft-configs-${TIMESTAMP}.tar.gz" \
|
||
|
|
/opt/docker/minecraft/server.properties \
|
||
|
|
/opt/docker/minecraft/purpur.yml \
|
||
|
|
/opt/docker/minecraft/spigot.yml \
|
||
|
|
/opt/docker/minecraft/paper-*.yml \
|
||
|
|
/opt/docker/minecraft/bukkit.yml \
|
||
|
|
/opt/docker/minecraft/ops.json \
|
||
|
|
/opt/docker/minecraft/banned-*.json \
|
||
|
|
/opt/docker/minecraft/eula.txt \
|
||
|
|
2>/dev/null && \
|
||
|
|
log " Minecraft configs: OK" \
|
||
|
|
|| log " Minecraft configs: partial (expected)"
|
||
|
|
else
|
||
|
|
# Server is stopped - backup everything directly
|
||
|
|
tar czf "${BACKUP_PATH}/minecraft-full-backup-${TIMESTAMP}.tar.gz" \
|
||
|
|
/opt/docker/minecraft/world/ \
|
||
|
|
/opt/docker/minecraft/world_nether/ \
|
||
|
|
/opt/docker/minecraft/world_the_end/ \
|
||
|
|
/opt/docker/minecraft/plugins/ \
|
||
|
|
/opt/docker/minecraft/server.properties \
|
||
|
|
/opt/docker/minecraft/purpur.yml \
|
||
|
|
/opt/docker/minecraft/spigot.yml \
|
||
|
|
2>/dev/null && \
|
||
|
|
log " Minecraft (full, offline): OK ($(du -sh "${BACKUP_PATH}/minecraft-full-backup-${TIMESTAMP}.tar.gz" | cut -f1))" \
|
||
|
|
|| log " Minecraft (offline): partial"
|
||
|
|
fi
|
||
|
|
|
||
|
|
"${BACKUP_PATH}/synapse-signing-key-${TIMESTAMP}.key"
|
||
|
|
chmod 600 "${BACKUP_PATH}/synapse-signing-key-${TIMESTAMP}.key"
|
||
|
|
log " Synapse signing key: backed up (600)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── Prune old backups ──────────────────────────────────────────────
|
||
|
|
log "Pruning backups older than ${KEEP_DAYS} days..."
|
||
|
|
find "$BACKUP_DIR" -maxdepth 1 -type d -mtime "+${KEEP_DAYS}" -exec rm -rf {} + 2>/dev/null || true
|
||
|
|
find "$BACKUP_DIR" -maxdepth 1 -name "*.log" -mtime +30 -delete 2>/dev/null || true
|
||
|
|
|
||
|
|
BACKUP_SIZE=$(du -sh "$BACKUP_PATH" | cut -f1)
|
||
|
|
log "=== Backup complete: ${BACKUP_PATH} (${BACKUP_SIZE}) ==="
|