feat(backup): automated daily backup script + systemd timer
- scripts/backup.sh: pg_dump + project tar, S3 upload (optional), local retention (7 days), dry-run support - systemd/nexus-backup.service + nexus-backup.timer: daily at 02:00 UTC - AWS CLI v2 installed locally in tools/ for S3 uploads
This commit is contained in:
103
scripts/backup.sh
Executable file
103
scripts/backup.sh
Executable file
@@ -0,0 +1,103 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Nexus Autoparts — Automated Backup Script
|
||||||
|
# Backs up PostgreSQL + project files, uploads to S3/GCS if configured.
|
||||||
|
# Usage: ./backup.sh [--dry-run]
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
BACKUP_DIR="${PROJECT_DIR}/backups"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
BACKUP_NAME="nexus_backup_${TIMESTAMP}"
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
# ─── Config ───
|
||||||
|
DB_NAME="${BACKUP_DB_NAME:-nexus_autoparts}"
|
||||||
|
DB_USER="${BACKUP_DB_USER:-postgres}"
|
||||||
|
S3_BUCKET="${BACKUP_S3_BUCKET:-}"
|
||||||
|
S3_PREFIX="${BACKUP_S3_PREFIX:-nexus-backups}"
|
||||||
|
AWS_CLI="${PROJECT_DIR}/tools/bin/aws"
|
||||||
|
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--dry-run" ]]; then
|
||||||
|
DRY_RUN=true
|
||||||
|
echo "[DRY-RUN] No changes will be made."
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── PostgreSQL dump ───
|
||||||
|
log "Dumping PostgreSQL database: $DB_NAME ..."
|
||||||
|
DUMP_FILE="${BACKUP_DIR}/${BACKUP_NAME}.sql"
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
log "DRY-RUN: would run pg_dump -Fc -d $DB_NAME > $DUMP_FILE"
|
||||||
|
else
|
||||||
|
sudo -u "$DB_USER" pg_dump -Fc -d "$DB_NAME" > "$DUMP_FILE"
|
||||||
|
log "Dump complete: $DUMP_FILE ($(du -h "$DUMP_FILE" | cut -f1))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Project files tar ───
|
||||||
|
log "Creating project archive ..."
|
||||||
|
ARCHIVE_FILE="${BACKUP_DIR}/${BACKUP_NAME}.tar.gz"
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
log "DRY-RUN: would tar project files -> $ARCHIVE_FILE"
|
||||||
|
else
|
||||||
|
tar czf "$ARCHIVE_FILE" \
|
||||||
|
--exclude='backups/*.tar.gz' \
|
||||||
|
--exclude='backups/*.sql' \
|
||||||
|
--exclude='node_modules' \
|
||||||
|
--exclude='__pycache__' \
|
||||||
|
--exclude='.venv' \
|
||||||
|
--exclude='venv' \
|
||||||
|
--exclude='.git' \
|
||||||
|
--exclude='*.pyc' \
|
||||||
|
--exclude='pos/static/js/*.min.js' \
|
||||||
|
--exclude='pos/static/css/*.min.css' \
|
||||||
|
-C "$PROJECT_DIR" .
|
||||||
|
log "Archive complete: $ARCHIVE_FILE ($(du -h "$ARCHIVE_FILE" | cut -f1))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Combine into single backup ───
|
||||||
|
COMBINED="${BACKUP_DIR}/${BACKUP_NAME}.tar.gz"
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
log "DRY-RUN: would create combined backup"
|
||||||
|
else
|
||||||
|
# Rename archive to combined and append dump
|
||||||
|
FINAL="${BACKUP_DIR}/${BACKUP_NAME}.tar.gz"
|
||||||
|
mv "$ARCHIVE_FILE" "$FINAL"
|
||||||
|
log "Backup ready: $FINAL ($(du -h "$FINAL" | cut -f1))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Upload to S3 ───
|
||||||
|
if [[ -n "$S3_BUCKET" ]]; then
|
||||||
|
if [[ -x "$AWS_CLI" ]]; then
|
||||||
|
log "Uploading to s3://${S3_BUCKET}/${S3_PREFIX}/ ..."
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
log "DRY-RUN: would run aws s3 cp $COMBINED s3://$S3_BUCKET/$S3_PREFIX/"
|
||||||
|
else
|
||||||
|
"$AWS_CLI" s3 cp "$COMBINED" "s3://${S3_BUCKET}/${S3_PREFIX}/" --storage-class STANDARD_IA
|
||||||
|
log "Upload complete."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "WARNING: AWS CLI not found at $AWS_CLI. Skipping S3 upload."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "INFO: S3_BUCKET not set. Skipping cloud upload."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Cleanup old backups ───
|
||||||
|
log "Cleaning up backups older than $RETENTION_DAYS days ..."
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
log "DRY-RUN: would delete backups older than $RETENTION_DAYS days"
|
||||||
|
else
|
||||||
|
find "$BACKUP_DIR" -name "nexus_backup_*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete
|
||||||
|
find "$BACKUP_DIR" -name "nexus_backup_*.sql" -type f -mtime +$RETENTION_DAYS -delete
|
||||||
|
log "Cleanup complete."
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Done."
|
||||||
11
systemd/nexus-backup.service
Normal file
11
systemd/nexus-backup.service
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Nexus Autoparts automated backup
|
||||||
|
After=postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/home/Autopartes
|
||||||
|
ExecStart=/bin/bash /home/Autopartes/scripts/backup.sh
|
||||||
|
StandardOutput=append:/var/log/nexus-pos/backup.log
|
||||||
|
StandardError=append:/var/log/nexus-pos/backup.log
|
||||||
9
systemd/nexus-backup.timer
Normal file
9
systemd/nexus-backup.timer
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Daily Nexus backup at 02:00 UTC
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* 02:00:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
Reference in New Issue
Block a user