Set up comprehensive backup and disaster recovery for AWX (Ansible Tower) with automated PostgreSQL database snapshots, configuration backups, and tested restoration procedures to ensure business continuity.
Prerequisites
- AWX installed and running
- Docker or Podman environment
- PostgreSQL client tools
- Root or sudo access
What this solves
AWX is critical infrastructure that orchestrates your automation workflows. When AWX fails, your entire automation pipeline stops, affecting deployments, configuration management, and operational tasks. This tutorial establishes automated backup procedures for AWX's PostgreSQL database, configuration files, and project data, plus tested disaster recovery procedures to minimize downtime during failures.
AWX backup architecture and planning
Identify AWX components to backup
AWX stores critical data in multiple locations that require different backup strategies.
sudo docker exec awx_postgres pg_dump -U awx awx > /dev/null
sudo ls -la /var/lib/docker/volumes/awx_projects/_data/
sudo ls -la /var/lib/docker/volumes/awx_redis/_data/
Create backup directory structure
Organize backup locations with proper permissions and retention policies.
sudo mkdir -p /opt/awx-backup/{database,projects,configs,logs}
sudo mkdir -p /opt/awx-backup/database/{daily,weekly,monthly}
sudo chown -R $(whoami):$(whoami) /opt/awx-backup
chmod -R 750 /opt/awx-backup
Install backup dependencies
Install PostgreSQL client tools and compression utilities for efficient backups.
sudo apt update
sudo apt install -y postgresql-client-15 gzip rsync gpg
PostgreSQL database backup automation
Create PostgreSQL backup script
This script creates compressed database dumps with timestamps and implements retention policies.
#!/bin/bash
set -euo pipefail
Configuration
BACKUP_DIR="/opt/awx-backup/database"
PG_CONTAINER="awx_postgres"
DB_NAME="awx"
DB_USER="awx"
DATE=$(date +%Y%m%d_%H%M%S)
DAILY_RETENTION=7
WEEKLY_RETENTION=4
MONTHLY_RETENTION=12
Create timestamp-based backup
echo "Starting AWX database backup at $(date)"
docker exec $PG_CONTAINER pg_dump -U $DB_USER -d $DB_NAME --verbose --no-password | gzip > "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz"
Verify backup integrity
if [ -f "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz" ] && [ -s "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz" ]; then
echo "Database backup completed successfully: awx_db_$DATE.sql.gz"
# Create checksums
cd "$BACKUP_DIR/daily"
sha256sum "awx_db_$DATE.sql.gz" > "awx_db_$DATE.sql.gz.sha256"
else
echo "ERROR: Database backup failed or is empty"
exit 1
fi
Weekly backup (Sundays)
if [ $(date +%u) -eq 7 ]; then
cp "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz" "$BACKUP_DIR/weekly/awx_db_weekly_$DATE.sql.gz"
cp "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz.sha256" "$BACKUP_DIR/weekly/awx_db_weekly_$DATE.sql.gz.sha256"
fi
Monthly backup (1st of month)
if [ $(date +%d) -eq 01 ]; then
cp "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz" "$BACKUP_DIR/monthly/awx_db_monthly_$DATE.sql.gz"
cp "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz.sha256" "$BACKUP_DIR/monthly/awx_db_monthly_$DATE.sql.gz.sha256"
fi
Cleanup old backups
find "$BACKUP_DIR/daily" -name "awx_db_*.sql.gz" -mtime +$DAILY_RETENTION -delete
find "$BACKUP_DIR/daily" -name "awx_db_*.sql.gz.sha256" -mtime +$DAILY_RETENTION -delete
find "$BACKUP_DIR/weekly" -name "awx_db_weekly_.sql.gz" -mtime +$((WEEKLY_RETENTION 7)) -delete
find "$BACKUP_DIR/weekly" -name "awx_db_weekly_.sql.gz.sha256" -mtime +$((WEEKLY_RETENTION 7)) -delete
find "$BACKUP_DIR/monthly" -name "awx_db_monthly_.sql.gz" -mtime +$((MONTHLY_RETENTION 30)) -delete
find "$BACKUP_DIR/monthly" -name "awx_db_monthly_.sql.gz.sha256" -mtime +$((MONTHLY_RETENTION 30)) -delete
echo "AWX database backup completed at $(date)"
Make backup script executable
Set proper permissions and test the backup script functionality.
chmod +x /opt/awx-backup/backup-awx-db.sh
sudo /opt/awx-backup/backup-awx-db.sh
Configure automated scheduling
Schedule daily database backups using systemd timers for reliable execution.
[Unit]
Description=AWX Database Backup Service
Wants=awx-backup.timer
[Service]
Type=oneshot
User=root
ExecStart=/opt/awx-backup/backup-awx-db.sh
StandardOutput=journal
StandardError=journal
Create systemd timer
Configure the backup timer to run daily at 2 AM with randomized delays to avoid system load spikes.
[Unit]
Description=AWX Database Backup Timer
Requires=awx-backup.service
[Timer]
OnCalendar=--* 02:00:00
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
Enable and start backup timer
Activate the systemd timer and verify it's scheduled correctly.
sudo systemctl daemon-reload
sudo systemctl enable awx-backup.timer
sudo systemctl start awx-backup.timer
sudo systemctl status awx-backup.timer
AWX configuration and project data backup
Create comprehensive backup script
This script backs up AWX projects, inventories, and configuration files in addition to the database.
#!/bin/bash
set -euo pipefail
Configuration
BACKUP_BASE="/opt/awx-backup"
DATE=$(date +%Y%m%d_%H%M%S)
AWX_PROJECTS_VOLUME="awx_projects"
AWX_CONFIG_DIR="/var/lib/docker/volumes"
LOG_FILE="$BACKUP_BASE/logs/backup_$DATE.log"
Start logging
exec 1> >(tee -a "$LOG_FILE")
exec 2> >(tee -a "$LOG_FILE" >&2)
echo "Starting full AWX backup at $(date)"
Backup database first
echo "Backing up PostgreSQL database..."
/opt/awx-backup/backup-awx-db.sh
Backup AWX projects
echo "Backing up AWX projects and playbooks..."
if docker volume inspect $AWX_PROJECTS_VOLUME >/dev/null 2>&1; then
PROJECT_MOUNT=$(docker volume inspect $AWX_PROJECTS_VOLUME | jq -r '.[0].Mountpoint')
tar -czf "$BACKUP_BASE/projects/awx_projects_$DATE.tar.gz" -C "$PROJECT_MOUNT" .
echo "Projects backup completed: awx_projects_$DATE.tar.gz"
else
echo "WARNING: AWX projects volume not found"
fi
Backup AWX configuration
echo "Backing up AWX configuration..."
tar -czf "$BACKUP_BASE/configs/awx_config_$DATE.tar.gz" \
--exclude='*.sock' \
--exclude='*.pid' \
--exclude='*.log' \
-C /var/lib/docker/volumes/ awx_redis awx_receptor
Backup Docker Compose configuration if exists
if [ -f "/opt/awx/docker-compose.yml" ]; then
cp "/opt/awx/docker-compose.yml" "$BACKUP_BASE/configs/docker-compose_$DATE.yml"
fi
if [ -f "/opt/awx/.env" ]; then
cp "/opt/awx/.env" "$BACKUP_BASE/configs/env_$DATE.txt"
fi
Create backup manifest
echo "Creating backup manifest..."
cat > "$BACKUP_BASE/configs/backup_manifest_$DATE.txt" << EOF
AWX Full Backup Manifest
Date: $(date)
Backup ID: $DATE
Database Backup:
- File: awx_db_$DATE.sql.gz
- Size: $(du -h $BACKUP_BASE/database/daily/awx_db_$DATE.sql.gz | cut -f1)
- Checksum: $(cat $BACKUP_BASE/database/daily/awx_db_$DATE.sql.gz.sha256 | cut -d' ' -f1)
Projects Backup:
- File: awx_projects_$DATE.tar.gz
- Size: $(du -h $BACKUP_BASE/projects/awx_projects_$DATE.tar.gz 2>/dev/null | cut -f1 || echo "N/A")
Configuration Backup:
- File: awx_config_$DATE.tar.gz
- Size: $(du -h $BACKUP_BASE/configs/awx_config_$DATE.tar.gz | cut -f1)
Total Backup Size: $(du -sh $BACKUP_BASE | cut -f1)
EOF
Cleanup old project and config backups (keep 30 days)
find "$BACKUP_BASE/projects" -name "awx_projects_*.tar.gz" -mtime +30 -delete
find "$BACKUP_BASE/configs" -name "awx_config_*.tar.gz" -mtime +30 -delete
find "$BACKUP_BASE/configs" -name "backup_manifest_*.txt" -mtime +30 -delete
find "$BACKUP_BASE/logs" -name "backup_*.log" -mtime +30 -delete
echo "Full AWX backup completed at $(date)"
echo "Backup manifest: $BACKUP_BASE/configs/backup_manifest_$DATE.txt"
Install jq for JSON parsing
Install jq utility needed by the backup script to parse Docker volume information.
sudo apt install -y jq
Make full backup script executable and test
Set permissions and run a test backup to verify all components work correctly.
chmod +x /opt/awx-backup/backup-awx-full.sh
sudo /opt/awx-backup/backup-awx-full.sh
Configure weekly full backup
Schedule comprehensive backups weekly to capture all AWX components.
[Unit]
Description=AWX Full Backup Service
Wants=awx-full-backup.timer
[Service]
Type=oneshot
User=root
ExecStart=/opt/awx-backup/backup-awx-full.sh
StandardOutput=journal
StandardError=journal
Create weekly backup timer
Schedule full backups every Sunday at 1 AM to avoid conflicts with daily database backups.
[Unit]
Description=AWX Full Backup Timer
Requires=awx-full-backup.service
[Timer]
OnCalendar=Sun --* 01:00:00
RandomizedDelaySec=600
Persistent=true
[Install]
WantedBy=timers.target
Enable weekly backup timer
Activate the weekly full backup schedule.
sudo systemctl daemon-reload
sudo systemctl enable awx-full-backup.timer
sudo systemctl start awx-full-backup.timer
sudo systemctl list-timers | grep awx
Disaster recovery testing and validation
Create disaster recovery script
This script automates the restoration process for testing disaster recovery procedures.
#!/bin/bash
set -euo pipefail
Configuration
BACKUP_BASE="/opt/awx-backup"
RESTORE_LOG="/opt/awx-backup/logs/restore_$(date +%Y%m%d_%H%M%S).log"
Function to show usage
show_usage() {
echo "Usage: $0 [backup_date] [--dry-run]"
echo "Example: $0 20241201_140000"
echo "Available backups:"
ls -1 $BACKUP_BASE/database/daily/awx_db_*.sql.gz | head -10
exit 1
}
Parse arguments
BACKUP_DATE="$1"
DRY_RUN="${2:-}"
if [ -z "$BACKUP_DATE" ]; then
show_usage
fi
Start logging
exec 1> >(tee -a "$RESTORE_LOG")
exec 2> >(tee -a "$RESTORE_LOG" >&2)
echo "Starting AWX disaster recovery for backup: $BACKUP_DATE"
echo "Dry run mode: $([ "$DRY_RUN" = "--dry-run" ] && echo "YES" || echo "NO")"
Verify backup files exist
DB_BACKUP="$BACKUP_BASE/database/daily/awx_db_$BACKUP_DATE.sql.gz"
PROJECTS_BACKUP="$BACKUP_BASE/projects/awx_projects_$BACKUP_DATE.tar.gz"
CONFIG_BACKUP="$BACKUP_BASE/configs/awx_config_$BACKUP_DATE.tar.gz"
CHECKSUM_FILE="$BACKUP_BASE/database/daily/awx_db_$BACKUP_DATE.sql.gz.sha256"
if [ ! -f "$DB_BACKUP" ]; then
echo "ERROR: Database backup not found: $DB_BACKUP"
exit 1
fi
echo "Verifying backup integrity..."
if [ -f "$CHECKSUM_FILE" ]; then
cd "$(dirname "$DB_BACKUP")"
if sha256sum -c "$(basename "$CHECKSUM_FILE")"; then
echo "Database backup checksum verified"
else
echo "ERROR: Database backup checksum verification failed"
exit 1
fi
fi
if [ "$DRY_RUN" = "--dry-run" ]; then
echo "DRY RUN - Would perform the following actions:"
echo "1. Stop AWX containers"
echo "2. Restore database from: $DB_BACKUP"
[ -f "$PROJECTS_BACKUP" ] && echo "3. Restore projects from: $PROJECTS_BACKUP"
[ -f "$CONFIG_BACKUP" ] && echo "4. Restore configuration from: $CONFIG_BACKUP"
echo "5. Start AWX containers"
echo "6. Verify AWX functionality"
exit 0
fi
Stop AWX services
echo "Stopping AWX containers..."
cd /opt/awx
docker-compose down
Restore database
echo "Restoring AWX database..."
docker-compose up -d postgres
sleep 10
Drop and recreate database
docker exec awx_postgres psql -U postgres -c "DROP DATABASE IF EXISTS awx;"
docker exec awx_postgres psql -U postgres -c "CREATE DATABASE awx OWNER awx;"
Restore database content
zcat "$DB_BACKUP" | docker exec -i awx_postgres psql -U awx -d awx
echo "Database restoration completed"
Restore projects if available
if [ -f "$PROJECTS_BACKUP" ]; then
echo "Restoring AWX projects..."
PROJECT_MOUNT=$(docker volume inspect awx_projects | jq -r '.[0].Mountpoint')
sudo rm -rf "$PROJECT_MOUNT"/*
sudo tar -xzf "$PROJECTS_BACKUP" -C "$PROJECT_MOUNT"
echo "Projects restoration completed"
fi
Restore configuration if available
if [ -f "$CONFIG_BACKUP" ]; then
echo "Restoring AWX configuration..."
sudo tar -xzf "$CONFIG_BACKUP" -C /var/lib/docker/volumes/
echo "Configuration restoration completed"
fi
Start all AWX services
echo "Starting AWX services..."
docker-compose up -d
Wait for services to be ready
echo "Waiting for AWX to become available..."
sleep 30
for i in {1..12}; do
if curl -k -s https://localhost/api/v2/ping/ | grep -q '"version"'; then
echo "AWX is responding to API requests"
break
fi
echo "Waiting for AWX... ($i/12)"
sleep 10
done
echo "AWX disaster recovery completed at $(date)"
echo "Restoration log: $RESTORE_LOG"
echo "Please verify AWX functionality manually"
Make restore script executable
Set proper permissions for the disaster recovery script.
chmod +x /opt/awx-backup/restore-awx.sh
Test disaster recovery procedure
Perform a dry-run test to validate the restoration process without making changes.
# Find a recent backup date
ls -la /opt/awx-backup/database/daily/
Test with dry-run (replace with actual backup date)
sudo /opt/awx-backup/restore-awx.sh 20241201_020000 --dry-run
Create backup monitoring script
Monitor backup health and send alerts if backups fail or become outdated.
#!/bin/bash
set -euo pipefail
Configuration
BACKUP_BASE="/opt/awx-backup"
MAX_AGE_HOURS=25 # Alert if no backup in 25 hours
ALERT_EMAIL="admin@example.com" # Configure your email
Check if recent backup exists
LATEST_BACKUP=$(find "$BACKUP_BASE/database/daily" -name "awx_db_*.sql.gz" -mtime -1 | sort | tail -1)
if [ -z "$LATEST_BACKUP" ]; then
echo "CRITICAL: No AWX database backup found in the last 24 hours"
# Send email alert (configure mail server)
# echo "No AWX backup in 24 hours" | mail -s "AWX Backup Alert" $ALERT_EMAIL
exit 1
fi
Check backup file size (should be > 1MB for a typical AWX database)
BACKUP_SIZE=$(stat -c%s "$LATEST_BACKUP")
if [ "$BACKUP_SIZE" -lt 1048576 ]; then
echo "WARNING: Latest backup file is suspiciously small: $(du -h "$LATEST_BACKUP")"
exit 1
fi
Check backup age
BACKUP_AGE=$(stat -c%Y "$LATEST_BACKUP")
CURRENT_TIME=$(date +%s)
AGE_HOURS=$(( (CURRENT_TIME - BACKUP_AGE) / 3600 ))
if [ "$AGE_HOURS" -gt "$MAX_AGE_HOURS" ]; then
echo "WARNING: Latest backup is $AGE_HOURS hours old"
exit 1
fi
echo "OK: Latest backup is $(basename "$LATEST_BACKUP"), size $(du -h "$LATEST_BACKUP" | cut -f1), $AGE_HOURS hours old"
echo "Backup directory usage: $(du -sh $BACKUP_BASE | cut -f1)"
Show backup counts by type
echo "Backup inventory:"
echo "- Daily: $(find "$BACKUP_BASE/database/daily" -name "*.sql.gz" | wc -l) backups"
echo "- Weekly: $(find "$BACKUP_BASE/database/weekly" -name "*.sql.gz" | wc -l) backups"
echo "- Monthly: $(find "$BACKUP_BASE/database/monthly" -name "*.sql.gz" | wc -l) backups"
echo "- Projects: $(find "$BACKUP_BASE/projects" -name "*.tar.gz" | wc -l) backups"
Make monitoring script executable and schedule
Set up automated backup monitoring to catch issues early.
chmod +x /opt/awx-backup/monitor-backups.sh
Test the monitoring script
sudo /opt/awx-backup/monitor-backups.sh
Add monitoring to cron
Schedule backup monitoring to run every 6 hours for proactive alerting.
echo "0 /6 /opt/awx-backup/monitor-backups.sh > /var/log/awx-backup-monitor.log 2>&1" | sudo crontab -
Verify your setup
# Check backup timers are active
sudo systemctl list-timers | grep awx
Verify recent backups exist
ls -la /opt/awx-backup/database/daily/
ls -la /opt/awx-backup/projects/
Test backup monitoring
sudo /opt/awx-backup/monitor-backups.sh
Check AWX is running normally
curl -k -s https://localhost/api/v2/ping/ | jq .
Verify backup directory permissions
ls -la /opt/awx-backup/
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Backup script fails with "permission denied" | Incorrect file permissions or ownership | chown -R root:root /opt/awx-backup && chmod +x /opt/awx-backup/*.sh |
| Database backup is empty or very small | PostgreSQL container not accessible or credentials wrong | Check AWX container status: docker ps and verify database connectivity |
| Restore script cannot find backup files | Backup file naming mismatch or files moved | Verify backup exists: ls -la /opt/awx-backup/database/daily/ |
| AWX won't start after restoration | Volume permissions or configuration mismatch | Check docker logs: docker-compose logs awx_web and fix volume ownership |
| Timer not running backups | Systemd timer not enabled or service path wrong | sudo systemctl status awx-backup.timer and check service file paths |
| Backup monitoring script shows old backups | Backup script failed silently or timezone issues | Check systemd journal: sudo journalctl -u awx-backup.service |
Next steps
- Set up GitLab backup and disaster recovery for protecting your version control and CI/CD infrastructure
- Configure Zabbix alerting to get notified when backup failures occur
- Implement backup encryption with GPG for enhanced security compliance
- Set up AWX high availability clustering to reduce the need for disaster recovery
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# AWX Backup and Disaster Recovery Setup Script
# Configures automated PostgreSQL snapshots and restoration procedures
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration variables
BACKUP_DIR="/opt/awx-backup"
AWX_CONTAINER_PREFIX="awx"
PG_CONTAINER=""
REDIS_CONTAINER=""
WEB_CONTAINER=""
# Usage message
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -h, --help Show this help message"
echo " -c, --container AWX container prefix (default: awx)"
echo " -b, --backup-dir Backup directory (default: /opt/awx-backup)"
exit 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
-c|--container)
AWX_CONTAINER_PREFIX="$2"
shift 2
;;
-b|--backup-dir)
BACKUP_DIR="$2"
shift 2
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
usage
;;
esac
done
# Error handling and cleanup
cleanup() {
echo -e "${RED}[ERROR] Script failed. Cleaning up...${NC}"
# Remove incomplete backup directory if it was just created
if [[ -d "$BACKUP_DIR" && ! -f "$BACKUP_DIR/.setup_complete" ]]; then
rm -rf "$BACKUP_DIR"
fi
}
trap cleanup ERR
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root or with sudo${NC}"
exit 1
fi
}
# Detect OS and set package manager
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
PG_CLIENT_PKG="postgresql-client"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf makecache"
PKG_INSTALL="dnf install -y"
PG_CLIENT_PKG="postgresql"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum makecache"
PKG_INSTALL="yum install -y"
PG_CLIENT_PKG="postgresql"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected OS: $PRETTY_NAME${NC}"
else
echo -e "${RED}Cannot detect operating system${NC}"
exit 1
fi
}
# Check prerequisites
check_prerequisites() {
echo -e "${BLUE}[1/8] Checking prerequisites...${NC}"
# Check if Docker is installed and running
if ! command -v docker &> /dev/null; then
echo -e "${RED}Docker is not installed${NC}"
exit 1
fi
if ! systemctl is-active --quiet docker; then
echo -e "${RED}Docker service is not running${NC}"
exit 1
fi
# Detect AWX containers
PG_CONTAINER=$(docker ps --format "table {{.Names}}" | grep -E "${AWX_CONTAINER_PREFIX}.*postgres" | head -1 || true)
REDIS_CONTAINER=$(docker ps --format "table {{.Names}}" | grep -E "${AWX_CONTAINER_PREFIX}.*redis" | head -1 || true)
WEB_CONTAINER=$(docker ps --format "table {{.Names}}" | grep -E "${AWX_CONTAINER_PREFIX}.*web" | head -1 || true)
if [[ -z "$PG_CONTAINER" ]]; then
echo -e "${RED}AWX PostgreSQL container not found${NC}"
exit 1
fi
echo -e "${GREEN}Found AWX containers: PG=$PG_CONTAINER${NC}"
}
# Install dependencies
install_dependencies() {
echo -e "${BLUE}[2/8] Installing backup dependencies...${NC}"
$PKG_UPDATE
$PKG_INSTALL $PG_CLIENT_PKG gzip rsync gnupg2 cron
echo -e "${GREEN}Dependencies installed successfully${NC}"
}
# Create backup directory structure
create_backup_structure() {
echo -e "${BLUE}[3/8] Creating backup directory structure...${NC}"
mkdir -p "$BACKUP_DIR"/{database/{daily,weekly,monthly},projects,configs,logs,scripts}
# Set proper permissions
chown -R root:root "$BACKUP_DIR"
chmod -R 750 "$BACKUP_DIR"
echo -e "${GREEN}Backup directory structure created at $BACKUP_DIR${NC}"
}
# Create database backup script
create_backup_script() {
echo -e "${BLUE}[4/8] Creating database backup script...${NC}"
cat > "$BACKUP_DIR/scripts/backup-awx-db.sh" << 'EOF'
#!/bin/bash
set -euo pipefail
# AWX Database Backup Script
BACKUP_DIR="/opt/awx-backup/database"
PG_CONTAINER="awx_postgres"
DB_NAME="awx"
DB_USER="awx"
DATE=$(date +%Y%m%d_%H%M%S)
DAILY_RETENTION=7
WEEKLY_RETENTION=4
MONTHLY_RETENTION=12
LOG_FILE="/opt/awx-backup/logs/backup-$(date +%Y%m).log"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# Auto-detect PostgreSQL container
PG_CONTAINER=$(docker ps --format "table {{.Names}}" | grep -E "awx.*postgres" | head -1 || true)
if [[ -z "$PG_CONTAINER" ]]; then
log "ERROR: AWX PostgreSQL container not found"
exit 1
fi
log "Starting AWX database backup"
# Create database backup
docker exec "$PG_CONTAINER" pg_dump -U "$DB_USER" -d "$DB_NAME" --verbose --no-password | gzip > "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz"
# Verify backup integrity
if [ -f "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz" ] && [ -s "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz" ]; then
log "Database backup completed successfully: awx_db_$DATE.sql.gz"
# Create checksums
cd "$BACKUP_DIR/daily"
sha256sum "awx_db_$DATE.sql.gz" > "awx_db_$DATE.sql.gz.sha256"
else
log "ERROR: Database backup failed or is empty"
exit 1
fi
# Weekly backup (Sundays)
if [ $(date +%u) -eq 7 ]; then
cp "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz" "$BACKUP_DIR/weekly/awx_db_weekly_$DATE.sql.gz"
cp "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz.sha256" "$BACKUP_DIR/weekly/awx_db_weekly_$DATE.sql.gz.sha256"
log "Weekly backup created"
fi
# Monthly backup (1st of month)
if [ $(date +%d) -eq 01 ]; then
cp "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz" "$BACKUP_DIR/monthly/awx_db_monthly_$DATE.sql.gz"
cp "$BACKUP_DIR/daily/awx_db_$DATE.sql.gz.sha256" "$BACKUP_DIR/monthly/awx_db_monthly_$DATE.sql.gz.sha256"
log "Monthly backup created"
fi
# Cleanup old backups
find "$BACKUP_DIR/daily" -name "awx_db_*.sql.gz" -mtime +$DAILY_RETENTION -delete
find "$BACKUP_DIR/daily" -name "awx_db_*.sql.gz.sha256" -mtime +$DAILY_RETENTION -delete
find "$BACKUP_DIR/weekly" -name "awx_db_weekly_*.sql.gz" -mtime +$((WEEKLY_RETENTION * 7)) -delete
find "$BACKUP_DIR/weekly" -name "awx_db_weekly_*.sql.gz.sha256" -mtime +$((WEEKLY_RETENTION * 7)) -delete
find "$BACKUP_DIR/monthly" -name "awx_db_monthly_*.sql.gz" -mtime +$((MONTHLY_RETENTION * 30)) -delete
find "$BACKUP_DIR/monthly" -name "awx_db_monthly_*.sql.gz.sha256" -mtime +$((MONTHLY_RETENTION * 30)) -delete
log "AWX database backup completed successfully"
EOF
# Update container name in script
sed -i "s/awx_postgres/$PG_CONTAINER/g" "$BACKUP_DIR/scripts/backup-awx-db.sh"
sed -i "s|/opt/awx-backup|$BACKUP_DIR|g" "$BACKUP_DIR/scripts/backup-awx-db.sh"
chmod +x "$BACKUP_DIR/scripts/backup-awx-db.sh"
chown root:root "$BACKUP_DIR/scripts/backup-awx-db.sh"
echo -e "${GREEN}Database backup script created${NC}"
}
# Create projects backup script
create_projects_backup_script() {
echo -e "${BLUE}[5/8] Creating projects backup script...${NC}"
cat > "$BACKUP_DIR/scripts/backup-awx-projects.sh" << EOF
#!/bin/bash
set -euo pipefail
# AWX Projects Backup Script
BACKUP_DIR="$BACKUP_DIR/projects"
DATE=\$(date +%Y%m%d_%H%M%S)
LOG_FILE="$BACKUP_DIR/logs/projects-backup-\$(date +%Y%m).log"
# Logging function
log() {
echo "[\$(date '+%Y-%m-%d %H:%M:%S')] \$1" | tee -a "\$LOG_FILE"
}
log "Starting AWX projects backup"
# Backup AWX projects volume
if docker volume inspect awx_projects &>/dev/null; then
docker run --rm -v awx_projects:/source -v "\$BACKUP_DIR":/backup alpine tar czf "/backup/awx_projects_\$DATE.tar.gz" -C /source .
log "Projects backup completed: awx_projects_\$DATE.tar.gz"
else
log "WARNING: awx_projects volume not found"
fi
# Cleanup old project backups (keep 30 days)
find "\$BACKUP_DIR" -name "awx_projects_*.tar.gz" -mtime +30 -delete
log "AWX projects backup completed"
EOF
chmod +x "$BACKUP_DIR/scripts/backup-awx-projects.sh"
chown root:root "$BACKUP_DIR/scripts/backup-awx-projects.sh"
echo -e "${GREEN}Projects backup script created${NC}"
}
# Create systemd service and timer
create_systemd_units() {
echo -e "${BLUE}[6/8] Creating systemd service and timer...${NC}"
# Create backup service
cat > /etc/systemd/system/awx-backup.service << EOF
[Unit]
Description=AWX Database and Projects Backup Service
Wants=awx-backup.timer
[Service]
Type=oneshot
User=root
ExecStart=$BACKUP_DIR/scripts/backup-awx-db.sh
ExecStartPost=$BACKUP_DIR/scripts/backup-awx-projects.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Create backup timer
cat > /etc/systemd/system/awx-backup.timer << 'EOF'
[Unit]
Description=AWX Backup Timer
Requires=awx-backup.service
[Timer]
OnCalendar=daily
RandomizedDelaySec=900
Persistent=true
[Install]
WantedBy=timers.target
EOF
# Reload systemd and enable timer
systemctl daemon-reload
systemctl enable awx-backup.timer
systemctl start awx-backup.timer
echo -e "${GREEN}Systemd service and timer configured${NC}"
}
# Create restore script
create_restore_script() {
echo -e "${BLUE}[7/8] Creating restore script...${NC}"
cat > "$BACKUP_DIR/scripts/restore-awx-db.sh" << 'EOF'
#!/bin/bash
set -euo pipefail
# AWX Database Restore Script
BACKUP_DIR="/opt/awx-backup/database"
PG_CONTAINER=""
DB_NAME="awx"
DB_USER="awx"
usage() {
echo "Usage: $0 <backup_file.sql.gz>"
echo "Example: $0 /opt/awx-backup/database/daily/awx_db_20240101_120000.sql.gz"
exit 1
}
if [[ $# -ne 1 ]]; then
usage
fi
BACKUP_FILE="$1"
if [[ ! -f "$BACKUP_FILE" ]]; then
echo "ERROR: Backup file not found: $BACKUP_FILE"
exit 1
fi
# Auto-detect PostgreSQL container
PG_CONTAINER=$(docker ps --format "table {{.Names}}" | grep -E "awx.*postgres" | head -1 || true)
if [[ -z "$PG_CONTAINER" ]]; then
echo "ERROR: AWX PostgreSQL container not found"
exit 1
fi
echo "WARNING: This will overwrite the current AWX database!"
read -p "Are you sure you want to continue? (yes/no): " -r
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
echo "Restore cancelled"
exit 0
fi
echo "Stopping AWX services..."
docker stop $(docker ps -q --filter "name=awx" | grep -v "$PG_CONTAINER") || true
echo "Restoring database from $BACKUP_FILE..."
zcat "$BACKUP_FILE" | docker exec -i "$PG_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME"
echo "Starting AWX services..."
docker start $(docker ps -aq --filter "name=awx" | grep -v "$PG_CONTAINER") || true
echo "Database restore completed successfully"
EOF
sed -i "s|/opt/awx-backup|$BACKUP_DIR|g" "$BACKUP_DIR/scripts/restore-aw
Review the script before running. Execute with: bash install.sh