frappe_docker/.github/workflows/remove-site.yml
2026-01-12 12:02:19 +02:00

248 lines
9.7 KiB
YAML

name: Remove Frappe Site
on:
# Manual trigger only
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'test'
type: choice
options:
- production
- test
site_name:
description: 'Site name (e.g., academy.example.com)'
required: true
type: string
should_remove_backups:
description: 'Remove backups as well after site removal (⚠️ Backup is created by default before dropping the site. Do not use this option if you want to keep the backups)'
required: false
default: false
type: boolean
env:
HETZNER_HOST: 188.245.211.114
HETZNER_USER: ignis_academy_lms
DEPLOY_PATH: /opt/frappe-deployment
jobs:
remove-site:
runs-on: ubuntu-latest
# Use environment for secrets management
environment: ${{ github.event.inputs.environment }}
steps:
- name: Validate site name
run: |
SITE_NAME="${{ github.event.inputs.site_name }}"
# Validate site name (domain name or IP address)
# Domain name pattern
DOMAIN_PATTERN="^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$"
# IP address pattern (simple IPv4)
IP_PATTERN="^([0-9]{1,3}\.){3}[0-9]{1,3}$"
if [[ "$SITE_NAME" =~ $IP_PATTERN ]]; then
# Validate IP address ranges (0-255 for each octet)
IFS='.' read -ra OCTETS <<< "$SITE_NAME"
for octet in "${OCTETS[@]}"; do
if [[ $octet -lt 0 || $octet -gt 255 ]]; then
echo "❌ Invalid IP address. Each octet must be between 0-255."
exit 1
fi
done
echo "✅ Valid IP address format: $SITE_NAME"
elif [[ "$SITE_NAME" =~ $DOMAIN_PATTERN ]]; then
echo "✅ Valid domain name format: $SITE_NAME"
else
echo "❌ Invalid site name format. Please use a valid domain name or IP address."
echo "Examples: academy.example.com, test-site.local, 192.168.1.100, 188.231.133.113"
exit 1
fi
if [[ ${#SITE_NAME} -lt 3 ]]; then
echo "❌ Site name must be at least 3 characters long."
exit 1
fi
if [[ ${#SITE_NAME} -gt 253 ]]; then
echo "❌ Site name must be less than 253 characters long."
exit 1
fi
echo "✅ Site name validation passed: $SITE_NAME"
- name: Setup SSH key
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.HETZNER_SSH_KEY }}
- name: Add Hetzner server to known hosts
run: |
ssh-keyscan -H ${{ env.HETZNER_HOST }} >> ~/.ssh/known_hosts
- name: Check if deployment exists
run: |
echo "🔍 Checking if deployment exists on server..."
ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} "
if [ ! -d ${{ env.DEPLOY_PATH }} ]; then
echo '❌ Deployment directory not found at ${{ env.DEPLOY_PATH }}'
echo 'Please run the deploy action first to set up the application.'
exit 1
fi
echo '✅ Deployment found and ready'
"
- name: Check if site exists
run: |
echo "🔍 Checking if site exists..."
SITE_EXISTS=$(ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} "
cd ${{ env.DEPLOY_PATH }}
# Check if site directory exists in sites folder
if docker compose exec -T backend test -d '/home/frappe/frappe-bench/sites/${{ github.event.inputs.site_name }}'; then
echo 'true'
else
echo 'false'
fi
")
if [ "$SITE_EXISTS" = "false" ]; then
echo "✅ Site '${{ github.event.inputs.site_name }}' doesn't exist!"
echo "Please choose a different site name"
exit 1
fi
- name: Check services status
run: |
echo "🔍 Checking if services are running..."
ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} "
cd ${{ env.DEPLOY_PATH }}
# Check if containers are running
if ! docker compose ps --services --filter 'status=running' | grep -q 'backend'; then
echo '❌ Backend service is not running'
echo 'Please ensure the application is deployed and running'
exit 1
fi
if ! docker compose ps --services --filter 'status=running' | grep -q 'mariadb'; then
echo '❌ MariaDB service is not running'
echo 'Please ensure the database is running'
exit 1
fi
echo '✅ Required services are running'
"
- name: Removing the site
run: |
echo "🗑️ Removing site: ${{ github.event.inputs.site_name }}"
echo "Environment: ${{ github.event.inputs.environment }}"
ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} "
cd ${{ env.DEPLOY_PATH }}
# Load environment variables
if [ -f .env ]; then
export \$(cat .env | grep -E '^[A-Z_][A-Z0-9_]*=' | sed 's/#.*\$//' | xargs)
else
echo '❌ .env file not found. Please create it from .env.example'
exit 1
fi
# Drop the site (this will create backup files automatically)
docker compose exec -T backend bench drop-site ${{ github.event.inputs.site_name }} --db-root-password \$MARIADB_ROOT_PASSWORD --force
"
- name: Verify site removal
run: |
echo "🔍 Verifying site removal..."
ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} "
cd ${{ env.DEPLOY_PATH }}
# Check if site was created successfully by checking if site directory exists
if docker compose exec -T backend test -d '/home/frappe/frappe-bench/sites/${{ github.event.inputs.site_name }}'; then
echo '❌ Site removal failed!'
exit 1
else
echo '✅ Site removed successfully!'
fi
"
- name: Remove backup files if needed
if: ${{ github.event.inputs.should_remove_backups == 'true' }}
run: |
echo "🗑️ Removing backup files for site: ${{ github.event.inputs.site_name }}"
echo "Environment: ${{ github.event.inputs.environment }}"
ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} "
cd ${{ env.DEPLOY_PATH }}
docker compose exec -T backend bash -c '
cd /home/frappe/frappe-bench/archived/sites/
rm -rf \"${{ github.event.inputs.site_name }}\" \"${{ github.event.inputs.site_name }}\"[0-9]* 2>/dev/null || true
echo \"Removed: ${{ github.event.inputs.site_name }} and ${{ github.event.inputs.site_name }}[0-9]*\"
'
"
- name: Display backup information
if: ${{ github.event.inputs.should_remove_backups == 'false' }}
run: |
# Find and display the latest backup files after removal
echo ''
echo '📦 Searching for the backup created during removal...'
ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} "
cd ${{ env.DEPLOY_PATH }}
BACKUP_INFO=\$(docker compose exec -T backend bash -c \"
# Match the site directory in archived/sites (it might have an incremented suffix)
cd /home/frappe/frappe-bench/archived/sites/ 2>/dev/null || exit 1
# Find latest directory matching site_name* (sorted by time)
LATEST_DIR=\\\$(ls -td ${{ github.event.inputs.site_name }}* 2>/dev/null | head -n 1)
if [ -n \\\"\\\$LATEST_DIR\\\" ]; then
echo \\\"ARCHIVED_DIR=\\\$LATEST_DIR\\\"
cd \\\"\\\$LATEST_DIR/private/backups\\\" 2>/dev/null || exit 1
# Get the latest timestamp (from any file)
LATEST_TIMESTAMP=\\\$(ls -t *.sql.gz 2>/dev/null | head -n 1 | cut -d'-' -f1)
if [ -n \\\"\\\$LATEST_TIMESTAMP\\\" ]; then
echo \\\"TIMESTAMP=\\\$LATEST_TIMESTAMP\\\"
ls -lh \\\${LATEST_TIMESTAMP}-* 2>/dev/null | awk '{print \\\$9, \\\$5}' || echo 'No files found'
else
echo 'No backups found'
fi
else
echo 'No archived directory found'
exit 1
fi
\")
if echo \"\$BACKUP_INFO\" | grep -q \"TIMESTAMP=\"; then
ARCHIVED_DIR=\$(echo \"\$BACKUP_INFO\" | grep -o \"ARCHIVED_DIR=[^ ]*\" | cut -d'=' -f2)
TIMESTAMP=\$(echo \"\$BACKUP_INFO\" | grep -o \"TIMESTAMP=[^ ]*\" | cut -d'=' -f2)
# Extract just the filenames (everything after TIMESTAMP= that looks like a file)
FILES=\$(echo \"\$BACKUP_INFO\" | sed 's/.*TIMESTAMP=[^ ]* //' | tr ' ' '\n' | grep \"^\$TIMESTAMP\")
echo \"\"
echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"
echo \"✅ Site removed successfully!\"
echo \"\"
echo \"📦 Backup location:\"
echo \" /home/frappe/frappe-bench/archived/sites/\$ARCHIVED_DIR/private/backups/\"
echo \"\"
echo \"📋 Backup files (timestamp: \$TIMESTAMP):\"
echo \"\$FILES\" | sed 's/^/ - /'
echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"
else
echo \"⚠️ Could not find the archived backup (but site was removed)\"
fi
"