#!/bin/bash # Backup Odoo 19 database script # Description: Dumps Odoo DB, manages backup rotation, and maintains a current.zip symlink set -euo pipefail # Fail on error, undefined variables, and pipe errors log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } # Load helper functions source /4server/sbin/helpers # Get contract info (also exports ODOO_DB_PASSWORD via helpers) get_contract_info log "UUID: $UUID" log "Backup slots: $BACKUP_SLOTS" log "DB host: $POSTGRES_HOST" # Build paths FILENAME="$(date +"%Y%m%d_%H%M").zip" BACKUP_DIR="/BACKUP/$UUID" BB_DIR="$BACKUP_DIR/bb" # Ensure backup directories exist (requires elevated permissions like other ops) doas mkdir -p "$BACKUP_DIR" doas mkdir -p "$BB_DIR" # Perform database dump inside the container log "Starting dump -> $BACKUP_DIR/$FILENAME" doas docker exec "$UUID" odoo db \ --db_host "$POSTGRES_HOST" \ -r "$UUID" \ -w "$ODOO_DB_PASSWORD" \ --data-dir /home/odoo/.local/share/Odoo/ \ dump "$UUID" "/mnt/backup/$FILENAME" log "Dump complete: $(doas du -sh "$BACKUP_DIR/$FILENAME" | cut -f1)" # Secure timestamped backups (scoped glob — leaves current.zip symlink untouched) doas chmod 600 "$BACKUP_DIR"/[0-9]*.zip # Hand ownership back to odoo inside the container doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup # Update the current.zip symlink inside bb/ pointing at the backup just created. # Target is relative (../$FILENAME) because the symlink lives one level deeper # than the actual .zip files, so it stays valid if the directory is moved. doas ln -sf "../$FILENAME" "$BB_DIR/current.zip" log "bb/current.zip -> ../$FILENAME" # Remove old backups beyond the configured slot count # Process substitution (< <(...)) keeps the loop in the main shell so # set -e / exit codes propagate correctly — unlike a pipe subshell. while IFS= read -r old_file; do log "Deleting old backup: $old_file" doas rm -f "$old_file" done < <(ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null | tail -n +$((BACKUP_SLOTS + 1))) log "Backup finished successfully." # --- Custom modules snapshot --- # Archive /4server/data/$UUID/git-server/repos/local/custom/ into .current.tar.gz. # Rebuilt only when at least one file inside is newer than the existing archive, # so borg backup dedup sees an identical blob on unchanged runs. CUSTOM_SRC="/4server/data/$UUID/git-server/repos/local/custom" CUSTOM_ARCHIVE="$BB_DIR/current_modules.tar.gz" if [ ! -d "$CUSTOM_SRC" ]; then log "Custom modules directory not found, skipping: $CUSTOM_SRC" elif [ ! -f "$CUSTOM_ARCHIVE" ] || \ doas find "$CUSTOM_SRC" -newer "$CUSTOM_ARCHIVE" -print -quit | grep -q .; then log "Changes detected in custom modules — rebuilding current.tar.gz" # Write to a temp file first; mv is atomic, so borg never sees a partial archive doas tar -czf "${CUSTOM_ARCHIVE}.tmp" \ -C "/4server/data/$UUID/git-server/repos/local" custom doas mv "${CUSTOM_ARCHIVE}.tmp" "$CUSTOM_ARCHIVE" log ".current.tar.gz updated: $(doas du -sh "$CUSTOM_ARCHIVE" | cut -f1)" else log "No changes in custom modules — .current.tar.gz is up to date" fi