diff --git a/.gitignore b/.gitignore index 13bfe6f..f33e93b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ mongo-volume-backup-*.tar.gz # Serena AI assistant .serena/ *.serena-memory +mongo-backup/ diff --git a/README.md b/README.md index 30bcbec..ebfda4b 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,75 @@ Each location record is marked with a user ID. All the actions user `withUser` to fetch user ID, which is then used in all the DB operations. +# Database Backup & Restore + +The project includes multiple backup strategies for different deployment scenarios and requirements. + +## Backup Scripts Overview + +### Standalone Docker Deployments + +**Online Backups (No Downtime):** +- `db-dump--standalone.sh` - Creates online backup of the 'utility-bills' database using mongodump + - Database stays running during backup + - Only backs up the database content, not the full volume + - Output: `./mongo-backup/utility-bills-dump-YYYY-MM-DD_HH-MM.tar.gz` + - Default retention: 7 backups (configurable via `KEEP` env var) + - Usage: `./db-dump--standalone.sh` or `KEEP=10 ./db-dump--standalone.sh` + +- `db-restore-from-dump--standalone.sh` - Restores from mongodump archives + - Database stays running during restore + - **WARNING**: Drops existing collections before restore + - Usage: `./db-restore-from-dump--standalone.sh utility-bills-dump-2025-11-26_14-30.tar.gz` + +**Offline Backups (With Downtime):** +- `db-backup--standalone.sh` - Creates offline backup of the complete mongo-volume directory + - Database container is stopped during backup for consistency + - Backs up the entire MongoDB data directory + - Output: `./backups/mongo-volume-backup-YYYY-MM-DD-HH-MM.tar.gz` + - Default retention: 7 backups (configurable via `KEEP` env var) + - Usage: `./db-backup--standalone.sh` or `KEEP=2 ./db-backup--standalone.sh` + +### Docker Swarm Deployments + +- `db-backup--swarm.sh` - Creates offline backup by scaling down the MongoDB service + - Service is scaled to 0 during backup + - Output: `./backups/mongo-volume-backup-YYYY-MM-DD-HH-MM.tar.gz` + - Usage: `./db-backup--swarm.sh` + +- `db-restore-from-backup--swarm.sh` - Restores volume backup by scaling down the service + - Service is scaled to 0 during restore + - Optional `--pre-backup` flag for safety backup before restore + - Usage: `./db-restore-from-backup--swarm.sh mongo-volume-backup-2025-11-26-14-30.tar.gz` + +## Automated Backup Schedule + +Backups run automatically via cron at 04:00 every day: + +```cron +# Sunday: Full volume backup (offline), keep 2 backups +0 4 * * 0 cd /home/knee-cola/web-pro/evidencija-rezija && KEEP=2 ./db-backup--standalone.sh + +# Monday-Saturday: Database dump (online), keep 7 backups +0 4 * * 1-6 cd /home/knee-cola/web-pro/evidencija-rezija && KEEP=7 ./db-dump--standalone.sh +``` + +**Backup Strategy:** +- **Sundays**: Full volume backup with brief downtime (keeps 2 weeks of full backups) +- **Monday-Saturday**: Online database dumps without downtime (keeps 7 days of incremental backups) + +All backup operations are logged with timestamps: +- Volume backups: `./backups/db-backup-standalone.log` +- Database dumps: `./mongo-backup/db-dump-db-standalone.log` +- Restore operations: `./mongo-backup/db-restore-from-dump.log` + +## Backup Directories + +- `./backups/` - Volume backup archives (full mongo-volume directory) +- `./mongo-backup/` - Database dump archives (utility-bills database only) + +Both directories are excluded from git via `.gitignore`. + # Deploying The deployment is done via Docker: * build docker image diff --git a/db-backup-standalone.sh b/db-backup--standalone.sh similarity index 54% rename from db-backup-standalone.sh rename to db-backup--standalone.sh index eb08f1b..d395cf7 100755 --- a/db-backup-standalone.sh +++ b/db-backup--standalone.sh @@ -1,6 +1,41 @@ #!/usr/bin/env bash set -euo pipefail +# ============================================================================== +# MongoDB Offline Volume Backup Script (Standalone Docker) +# ============================================================================== +# +# PURPOSE: +# Creates an offline backup of the complete mongo-volume directory. +# The database server is stopped during the backup to ensure consistency. +# +# WHEN TO USE: +# - For standalone Docker deployments (not Docker Swarm) +# - When you need a complete volume backup (entire MongoDB data directory) +# - For disaster recovery scenarios +# - When consistency is critical and downtime is acceptable +# +# BACKUP TYPE: +# - Offline (cold) backup - DB server is stopped during backup +# - Volume-level backup - entire mongo-volume directory +# - Creates compressed tarball (.tar.gz) of the volume +# +# OUTPUT: +# - Backup files stored in: ./backups/ +# - Filename format: mongo-volume-backup-YYYY-MM-DD-HH-MM.tar.gz +# - Log file: ./backups/db-backup-standalone.log +# - Automatic rotation: keeps newest 7 backups (configurable via KEEP env var) +# +# USAGE: +# ./db-backup--standalone.sh +# KEEP=10 ./db-backup--standalone.sh # Keep 10 backups instead of 7 +# +# NOTE: +# The MongoDB container will be stopped and restarted automatically. +# Expect brief downtime during the backup process. +# +# ============================================================================== + # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_FILE="$SCRIPT_DIR/backups/db-backup-standalone.log" diff --git a/db-backup--swarm.sh b/db-backup--swarm.sh new file mode 100755 index 0000000..289600a --- /dev/null +++ b/db-backup--swarm.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================== +# MongoDB Offline Volume Backup Script (Docker Swarm) +# ============================================================================== +# +# PURPOSE: +# Creates an offline backup of the complete mongo-volume directory. +# The MongoDB service is scaled down during the backup to ensure consistency. +# +# WHEN TO USE: +# - For Docker Swarm deployments (not standalone Docker) +# - When you need a complete volume backup (entire MongoDB data directory) +# - For disaster recovery scenarios +# - When consistency is critical and downtime is acceptable +# +# BACKUP TYPE: +# - Offline (cold) backup - DB service is scaled to 0 during backup +# - Volume-level backup - entire mongo-volume directory +# - Creates compressed tarball (.tar.gz) of the volume +# +# OUTPUT: +# - Backup files stored in: ./backups/ +# - Filename format: mongo-volume-backup-YYYY-MM-DD-HH-MM.tar.gz +# - Automatic rotation: keeps newest 7 backups (configurable via KEEP env var) +# +# USAGE: +# ./db-backup--swarm.sh +# KEEP=10 ./db-backup--swarm.sh # Keep 10 backups instead of 7 +# +# NOTE: +# The MongoDB service will be scaled down to 0 and then back up to 1. +# Expect brief downtime during the backup process. +# +# ============================================================================== + +# Configuration +MONGO_SERVICE="utility-bills-tracker_mongo" + +# scale down mongo service while we copy its volume +docker service scale "$MONGO_SERVICE"=0 + +# timestamp for filename +TIMESTAMP=$(date +"%Y-%m-%d-%H-%M") + +# backup directory and retention (can be overridden via env) +BACKUP_DIR="${BACKUP_DIR:-backups}" +KEEP="${KEEP:-7}" + +mkdir -p "$BACKUP_DIR" + +BACKUP_FILE="$BACKUP_DIR/mongo-volume-backup-$TIMESTAMP.tar.gz" + +sudo tar -czvpf "$BACKUP_FILE" mongo-volume + +# rotate old backups: keep only the newest $KEEP files +if [ "$KEEP" -gt 0 ]; then + # gather files sorted newest-first + mapfile -t files < <(ls -1t "$BACKUP_DIR"/mongo-volume-backup-*.tar.gz 2>/dev/null || true) + if [ "${#files[@]}" -gt "$KEEP" ]; then + for f in "${files[@]:$KEEP}"; do + echo "Removing old backup: $f" + rm -f -- "$f" + done + fi +fi + +# bring mongo service back up +docker service scale "$MONGO_SERVICE"=1 diff --git a/db-backup-swarm.sh b/db-backup-swarm.sh deleted file mode 100755 index 6783abc..0000000 --- a/db-backup-swarm.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Configuration -MONGO_SERVICE="utility-bills-tracker_mongo" - -# scale down mongo service while we copy its volume -docker service scale "$MONGO_SERVICE"=0 - -# timestamp for filename -TIMESTAMP=$(date +"%Y-%m-%d-%H-%M") - -# backup directory and retention (can be overridden via env) -BACKUP_DIR="${BACKUP_DIR:-backups}" -KEEP="${KEEP:-7}" - -mkdir -p "$BACKUP_DIR" - -BACKUP_FILE="$BACKUP_DIR/mongo-volume-backup-$TIMESTAMP.tar.gz" - -sudo tar -czvpf "$BACKUP_FILE" mongo-volume - -# rotate old backups: keep only the newest $KEEP files -if [ "$KEEP" -gt 0 ]; then - # gather files sorted newest-first - mapfile -t files < <(ls -1t "$BACKUP_DIR"/mongo-volume-backup-*.tar.gz 2>/dev/null || true) - if [ "${#files[@]}" -gt "$KEEP" ]; then - for f in "${files[@]:$KEEP}"; do - echo "Removing old backup: $f" - rm -f -- "$f" - done - fi -fi - -# bring mongo service back up -docker service scale "$MONGO_SERVICE"=1 diff --git a/db-dump--standalone.sh b/db-dump--standalone.sh new file mode 100755 index 0000000..5c49eb0 --- /dev/null +++ b/db-dump--standalone.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================== +# MongoDB Online Dump Script (Standalone Docker) +# ============================================================================== +# +# PURPOSE: +# Creates an online backup of only the 'utility-bills' database using mongodump. +# The database server remains running during the backup process. +# +# WHEN TO USE: +# - For standalone Docker deployments (not Docker Swarm) +# - When you need a quick backup without downtime +# - When you only need the database content, not the full volume +# +# BACKUP TYPE: +# - Online (hot) backup - DB server keeps running +# - Database-level backup - only 'utility-bills' database +# - Creates compressed archive (.tar.gz) using mongodump +# +# OUTPUT: +# - Backup files stored in: ./mongo-backup/ +# - Filename format: utility-bills-dump-YYYY-MM-DD_HH-MM.tar.gz +# - Log file: ./mongo-backup/db-dump-db-standalone.log +# - Automatic rotation: keeps newest 7 backups (configurable via KEEP env var) +# +# USAGE: +# ./db-dump--standalone.sh +# KEEP=10 ./db-dump--standalone.sh # Keep 10 backups instead of 7 +# +# ============================================================================== + +# Configuration + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# create backup directory if it doesn't exist +BACKUP_DIR="$SCRIPT_DIR/mongo-backup" +mkdir -p "$BACKUP_DIR" + +# initialize log file (overwrite if exists) +LOG_FILE="$BACKUP_DIR/db-dump-db-standalone.log" +> "$LOG_FILE" + +# Function to log messages +log() { + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[$timestamp] $*" | tee -a "$LOG_FILE" +} + +log "Starting database dump process..." + +BACKUP_FILE_NAME_BASE="utility-bills-dump-" +BACKUP_FILE_NAME="${BACKUP_FILE_NAME_BASE}$(date +%F_%H-%M).tar.gz" + +CONTAINER_NAME="evidencija-rezija__mongo" +DB_NAME="utility-bills" + +docker exec "$CONTAINER_NAME" sh -c ' + mongodump \ + --username root \ + --password HjktJCPWMBtM1ACrDaw7 \ + --authenticationDatabase admin \ + --dumpDbUsersAndRoles \ + --db '$DB_NAME' \ + --archive=/backup/'$BACKUP_FILE_NAME' \ + --gzip +' + +log "... DB dump created successfully." + +# rotate old backups: keep only the newest $KEEP files +KEEP="${KEEP:-7}" +if [ "$KEEP" -gt 0 ]; then + log "Rotating backups, keeping the newest $KEEP files." + + # gather files sorted newest-first + mapfile -t files < <(ls -1t "$BACKUP_DIR"/${BACKUP_FILE_NAME_BASE}*.tar.gz 2>/dev/null || true) + if [ "${#files[@]}" -gt "$KEEP" ]; then + for f in "${files[@]:$KEEP}"; do + log "Removing old backup: $f" + rm -f -- "$f" + done + fi + + log "Rotation completed." +else + log "Backup rotation disabled (KEEP=$KEEP)." +fi + +log "Database backup process completed successfully." diff --git a/restore.sh b/db-restore-from-backup--swarm.sh similarity index 65% rename from restore.sh rename to db-restore-from-backup--swarm.sh index 095e711..8e00809 100755 --- a/restore.sh +++ b/db-restore-from-backup--swarm.sh @@ -1,6 +1,45 @@ #!/usr/bin/env bash set -euo pipefail +# ============================================================================== +# MongoDB Volume Restore Script (Docker Swarm) +# ============================================================================== +# +# PURPOSE: +# Restores the complete mongo-volume directory from a volume backup tarball. +# The MongoDB service is scaled down during the restore to ensure consistency. +# +# WHEN TO USE: +# - For Docker Swarm deployments (not standalone Docker) +# - To restore from backups created by db-backup--swarm.sh +# - For disaster recovery scenarios +# - When you need to restore the complete MongoDB data directory +# +# RESTORE TYPE: +# - Offline (cold) restore - DB service is scaled to 0 during restore +# - Volume-level restore - entire mongo-volume directory +# - Extracts from compressed tarball (.tar.gz) +# +# INPUT: +# - Requires backup filename as parameter +# - Looks for file in: ./backups/ +# - Optional: --pre-backup flag to create safety backup before restore +# +# USAGE: +# ./db-restore-from-backup--swarm.sh +# ./db-restore-from-backup--swarm.sh mongo-volume-backup-2025-11-26-14-30.tar.gz +# ./db-restore-from-backup--swarm.sh --pre-backup mongo-volume-backup-2025-11-26-14-30.tar.gz +# +# WARNING: +# This will COMPLETELY REPLACE the mongo-volume directory contents! +# Use --pre-backup flag to create a safety backup before restore. +# +# NOTE: +# The MongoDB service will be scaled down to 0 and then back up to 1. +# Expect downtime during the restore process. +# +# ============================================================================== + # Configuration MONGO_SERVICE="utility-bills-tracker_mongo" diff --git a/db-restore-from-dump--standalone.sh b/db-restore-from-dump--standalone.sh new file mode 100755 index 0000000..fcc9f0d --- /dev/null +++ b/db-restore-from-dump--standalone.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================== +# MongoDB Dump Restore Script (Standalone Docker) +# ============================================================================== +# +# PURPOSE: +# Restores the 'utility-bills' database from a mongodump archive. +# The database server remains running during the restore process. +# +# WHEN TO USE: +# - For standalone Docker deployments (not Docker Swarm) +# - To restore from dumps created by db-dump--standalone.sh +# - When you need to restore without shutting down the database +# +# RESTORE TYPE: +# - Online (hot) restore - DB server keeps running +# - Database-level restore - only 'utility-bills' database +# - Drops existing collections before restoring (--drop flag) +# +# INPUT: +# - Requires dump filename as parameter +# - Looks for file in: ./mongo-backup/ +# - Log file: ./mongo-backup/db-restore-from-dump.log +# +# USAGE: +# ./db-restore-from-dump--standalone.sh +# ./db-restore-from-dump--standalone.sh utility-bills-dump-2025-11-26_14-30.tar.gz +# +# WARNING: +# This will DROP all existing collections in the 'utility-bills' database +# before restoring. Make sure you have a backup if needed! +# +# ============================================================================== + +# Configuration + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# create backup directory if it doesn't exist +BACKUP_DIR="$SCRIPT_DIR/mongo-backup" +mkdir -p "$BACKUP_DIR" + +# initialize log file (overwrite if exists) +LOG_FILE="$BACKUP_DIR/db-restore-from-dump.log" +> "$LOG_FILE" + +# Function to log messages +log() { + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[$timestamp] $*" | tee -a "$LOG_FILE" +} + +# Check if dump filename is provided +if [ $# -eq 0 ]; then + log "Error: No dump filename provided" + log "Usage: $0 " + log "Example: $0 utility-bills-dump-2025-11-26_14-30.tar.gz" + exit 1 +fi + +DUMP_FILENAME="$1" +DUMP_FILE="$BACKUP_DIR/$DUMP_FILENAME" + +# Check if dump file exists +if [ ! -f "$DUMP_FILE" ]; then + log "Error: Dump file not found: $DUMP_FILE" + exit 1 +fi + +log "Starting database restore process..." +log "Restoring from: $DUMP_FILE" + +CONTAINER_NAME="evidencija-rezija__mongo" +DB_NAME="utility-bills" + +docker exec "$CONTAINER_NAME" sh -c ' + mongorestore \ + --username root \ + --password HjktJCPWMBtM1ACrDaw7 \ + --authenticationDatabase admin \ + --db '$DB_NAME' \ + --archive=/backup/'$DUMP_FILENAME' \ + --gzip \ + --drop +' + +log "Database restore completed successfully." diff --git a/db-scheduled-backup.sh b/db-scheduled-backup.sh deleted file mode 100755 index e8f89f3..0000000 --- a/db-scheduled-backup.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Script to run scheduled database backups and clean up old files -# This script calls db-backup-standalone.sh and then removes backups older than 7 days - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BACKUP_SCRIPT="$SCRIPT_DIR/db-backup-standalone.sh" -LOG_FILE="$SCRIPT_DIR/backups/scheduled-backup.log" - -# Initialize log file (overwrite if exists) -mkdir -p "$(dirname "$LOG_FILE")" -> "$LOG_FILE" - -# Function to log messages -log() { - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - echo "[$timestamp] $*" | tee -a "$LOG_FILE" -} - -# Ensure the backup script exists and is executable -if [ ! -f "$BACKUP_SCRIPT" ]; then - log "Error: Backup script not found at $BACKUP_SCRIPT" - exit 1 -fi - -if [ ! -x "$BACKUP_SCRIPT" ]; then - log "Making backup script executable..." - chmod +x "$BACKUP_SCRIPT" -fi - -# Run the backup -log "Starting database backup..." -"$BACKUP_SCRIPT" - -# Rotation is already handled in db-backup-standalone.sh, so no need to repeat it here. -# # Delete backups older than 7 days -# BACKUP_DIR="${BACKUP_DIR:-backups}" -# if [ -d "$BACKUP_DIR" ]; then -# log "Cleaning up backups older than 7 days in $BACKUP_DIR..." -# find "$BACKUP_DIR" -name "mongo-volume-backup-*.tar.gz" -type f -mtime +7 -delete -# log "Cleanup completed." -# else -# log "Warning: Backup directory $BACKUP_DIR not found, skipping cleanup." -# fi - -log "Scheduled backup completed successfully." diff --git a/docker-compose-debug.yml b/docker-compose-debug.yml index 4fd6dc5..1eaf025 100644 --- a/docker-compose-debug.yml +++ b/docker-compose-debug.yml @@ -4,6 +4,7 @@ version: "3.7" services: mongo: image: mongo:4.4.27 + container_name: evidencija-rezija__mongo ulimits: nofile: soft: 64000 @@ -16,8 +17,10 @@ services: MONGO_INITDB_ROOT_PASSWORD: HjktJCPWMBtM1ACrDaw7 volumes: - ./mongo-volume:/data/db + - ./mongo-backup:/backup mongo-express: image: mongo-express + container_name: evidencija-rezija__mongo-express restart: always ports: - 8081:8081 diff --git a/docker-compose-standalone.yaml b/docker-compose-standalone.yaml index 10d2f49..3af0c4b 100644 --- a/docker-compose-standalone.yaml +++ b/docker-compose-standalone.yaml @@ -49,6 +49,7 @@ services: MONGO_INITDB_ROOT_PASSWORD: example volumes: - ./mongo-volume:/data/db + - ./mongo-backup:/backup networks: - util-bills-mongo-network mongo-express: