Files
shell/backup-gitea.sh

225 lines
7.1 KiB
Bash
Executable File

#!/bin/bash
# backup-gitea.sh - Backup Gitea, Postgres, and Runner
# Enhanced for NAS support and Runner integration
set -e
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ==========================================
# 1. CONFIGURATION
# ==========================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COMPOSE_DIR="/home/acedanger/docker/gitea"
BACKUP_DIR="/home/acedanger/backups/gitea"
NAS_DIR="/mnt/share/media/backups/gitea"
COMPOSE_FILE="$COMPOSE_DIR/docker-compose.yml"
LOG_FILE="$SCRIPT_DIR/logs/gitea-backup.log"
DATE=$(date +%Y%m%d_%H%M%S)
# Ensure directories exist
mkdir -p "$(dirname "$LOG_FILE")"
mkdir -p "$BACKUP_DIR"
# Load .env variables from the COMPOSE_DIR to ensure DB credentials match
if [ -f "$COMPOSE_DIR/.env" ]; then
export $(grep -v '^#' "$COMPOSE_DIR/.env" | xargs)
fi
# Logging function (Fixed to interpret colors correctly)
log() {
# Print to console with colors (interpreting escapes with -e)
echo -e "$(date '+%Y-%m-%d %H:%M:%S') - $1"
# Strip colors for the log file to keep it clean
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | sed 's/\x1b\[[0-9;]*m//g' >> "$LOG_FILE"
}
# Display usage information
usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Backup Gitea data, Runner, and PostgreSQL database"
echo "Options:"
echo " -h, --help Show this help message"
echo " -l, --list List available local backups"
echo " -n, --no-nas Skip copying to NAS (Local only)"
echo ""
}
# Check dependencies
check_dependencies() {
if ! command -v docker &> /dev/null; then
log "${RED}Error: docker is not installed.${NC}"
exit 1
fi
# Verify the compose file exists where we expect it
if [ ! -f "$COMPOSE_FILE" ]; then
log "${RED}Error: Docker compose file not found at: $COMPOSE_FILE${NC}"
log "${YELLOW}Please update the COMPOSE_DIR variable in this script.${NC}"
exit 1
fi
}
# List available backups
list_backups() {
echo -e "${BLUE}=== Available Gitea Backups ===${NC}"
ls -lh "$BACKUP_DIR"/*.tar.gz 2>/dev/null || echo "No backups found."
}
# ==========================================
# 2. BACKUP LOGIC
# ==========================================
perform_backup() {
local SKIP_NAS=$1
log "Starting backup process..."
# Switch context to the directory where Gitea is actually running
cd "$COMPOSE_DIR" || { log "${RED}Could not change to directory $COMPOSE_DIR${NC}"; exit 1; }
# PRE-FLIGHT CHECK: Is the DB actually running?
if ! docker compose ps --services --filter "status=running" | grep -q "db"; then
log "${RED}CRITICAL ERROR: The 'db' service is not running in $COMPOSE_DIR${NC}"
log "${YELLOW}Docker sees these running services:$(docker compose ps --services --filter "status=running" | xargs)${NC}"
log "Aborting backup to prevent empty files."
exit 1
fi
# Create a temporary staging directory for this specific backup
TEMP_BACKUP_PATH="$BACKUP_DIR/temp_$DATE"
mkdir -p "$TEMP_BACKUP_PATH"
# 1. Backup Database
log "Step 1/5: Dumping PostgreSQL database..."
# Using -T to disable TTY allocation (fixes some cron issues)
if docker compose exec -T db pg_dump -U "${POSTGRES_USER:-gitea}" "${POSTGRES_DB:-gitea}" > "$TEMP_BACKUP_PATH/database.sql"; then
echo -e "${GREEN}Database dump successful.${NC}"
else
log "${RED}Database dump failed!${NC}"
rm -rf "$TEMP_BACKUP_PATH"
exit 1
fi
# 2. Backup Gitea Data
log "Step 2/5: Backing up Gitea data volume..."
docker run --rm \
--volumes-from gitea \
-v "$TEMP_BACKUP_PATH":/backup \
alpine tar czf /backup/gitea_data.tar.gz -C /data .
# 3. Backup Runner Data
log "Step 3/5: Backing up Runner data..."
# Check if runner exists before backing up to avoid errors if you removed it
if docker compose ps --services | grep -q "runner"; then
docker run --rm \
--volumes-from gitea-runner \
-v "$TEMP_BACKUP_PATH":/backup \
alpine tar czf /backup/runner_data.tar.gz -C /data .
else
log "${YELLOW}Runner service not found, skipping runner backup.${NC}"
fi
# 4. Config Files & Restore Script
log "Step 4/5: Archiving configurations and generating restore script..."
cp "$COMPOSE_FILE" "$TEMP_BACKUP_PATH/"
[ -f ".env" ] && cp ".env" "$TEMP_BACKUP_PATH/"
# Generate the Restore Script inside the backup folder
create_restore_script "$TEMP_BACKUP_PATH"
# 5. Final Archive Creation
log "Step 5/5: Compressing full backup..."
FINAL_ARCHIVE_NAME="gitea_backup_$DATE.tar.gz"
# Tar the temp folder into one final file
tar -czf "$BACKUP_DIR/$FINAL_ARCHIVE_NAME" -C "$TEMP_BACKUP_PATH" .
# Remove temp folder
rm -rf "$TEMP_BACKUP_PATH"
log "${GREEN}Local Backup completed: $BACKUP_DIR/$FINAL_ARCHIVE_NAME${NC}"
# 6. NAS Transfer
if [[ "$SKIP_NAS" != "true" ]]; then
if [ -d "$NAS_DIR" ]; then
log "Copying to NAS ($NAS_DIR)..."
cp "$BACKUP_DIR/$FINAL_ARCHIVE_NAME" "$NAS_DIR/"
if [ $? -eq 0 ]; then
log "${GREEN}NAS Copy Successful.${NC}"
else
log "${RED}NAS Copy Failed. Check permissions on $NAS_DIR${NC}"
fi
else
log "${YELLOW}NAS Directory $NAS_DIR not found. Skipping NAS copy.${NC}"
fi
else
log "NAS copy skipped by user request."
fi
# 7. Cleanup Old Local Backups (Keep 7 Days)
find "$BACKUP_DIR" -name "gitea_backup_*.tar.gz" -mtime +7 -exec rm {} \;
log "Cleanup of old local backups complete."
}
# Function to generate the restore script
create_restore_script() {
local TARGET_DIR=$1
cat > "$TARGET_DIR/restore.sh" << 'EOF'
#!/bin/bash
# RESTORE SCRIPT
echo "WARNING: This will overwrite your current Gitea/DB/Runner data."
read -p "Are you sure? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; fi
docker compose down
echo "Restoring Database Volume..."
docker compose up -d db
echo "Waiting for DB to initialize..."
sleep 15
cat database.sql | docker compose exec -T db psql -U ${POSTGRES_USER:-gitea} -d ${POSTGRES_DB:-gitea}
echo "Restoring Gitea Files..."
docker run --rm --volumes-from gitea -v $(pwd):/backup alpine tar xzf /backup/gitea_data.tar.gz -C /data
echo "Restoring Runner Files..."
docker run --rm --volumes-from gitea-runner -v $(pwd):/backup alpine tar xzf /backup/runner_data.tar.gz -C /data
echo "Restarting stack..."
docker compose up -d
echo "Restore Complete."
EOF
chmod +x "$TARGET_DIR/restore.sh"
}
# ==========================================
# 3. EXECUTION FLOW
# ==========================================
check_dependencies
# Parse Arguments
if [ $# -eq 0 ]; then
perform_backup "false"
exit 0
fi
while [[ "$#" -gt 0 ]]; do
case $1 in
-h|--help) usage; exit 0 ;;
-l|--list) list_backups; exit 0 ;;
-n|--no-nas) perform_backup "true"; exit 0 ;;
*) echo "Unknown parameter: $1"; usage; exit 1 ;;
esac
shift
done