# ============================================================================= # Deploy to Production Server # ============================================================================= # Triggers after successful build on main/master branch # SSHs to production server and updates containers # Uses alpine:3.19 (~7MB) instead of catthehacker/ubuntu (~1.5GB) # ============================================================================= name: Deploy to Production on: workflow_run: workflows: ["Build and Push"] types: [completed] branches: [main, master] workflow_dispatch: # Enable manual trigger env: REGISTRY: qbit.realms.pub jobs: deploy: # Only deploy if build succeeded OR manual trigger if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest container: image: alpine:3.19 steps: - name: Install dependencies run: apk add --no-cache openssh-client curl git - name: Checkout code run: | git clone --depth 1 --branch ${GITHUB_REF_NAME:-main} https://qbit.realms.pub/${GITHUB_REPOSITORY}.git . - name: Setup SSH key run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key # Add host key (skip strict checking for first connection) ssh-keyscan -p ${{ secrets.DEPLOY_PORT }} ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true - name: Prepare server directory run: | ssh -i ~/.ssh/deploy_key -p ${{ secrets.DEPLOY_PORT }} \ -o StrictHostKeyChecking=no \ root@${{ secrets.DEPLOY_HOST }} ' mkdir -p /opt/realms # Remove any stale directories that should be files [ -d /opt/realms/Server.xml ] && rm -rf /opt/realms/Server.xml [ -d /opt/realms/init.sql ] && rm -rf /opt/realms/init.sql [ -d /opt/realms/config.json ] && rm -rf /opt/realms/config.json [ -d /opt/realms/config.json.template ] && rm -rf /opt/realms/config.json.template [ -d /opt/realms/docker-compose.yml ] && rm -rf /opt/realms/docker-compose.yml true ' - name: Copy config files to server run: | # Copy docker-compose scp -i ~/.ssh/deploy_key -P ${{ secrets.DEPLOY_PORT }} \ -o StrictHostKeyChecking=no \ docker-compose.prod.yml \ root@${{ secrets.DEPLOY_HOST }}:/opt/realms/docker-compose.yml # Copy OvenMediaEngine config scp -i ~/.ssh/deploy_key -P ${{ secrets.DEPLOY_PORT }} \ -o StrictHostKeyChecking=no \ ovenmediaengine/Server.xml \ root@${{ secrets.DEPLOY_HOST }}:/opt/realms/Server.xml # Copy database init script scp -i ~/.ssh/deploy_key -P ${{ secrets.DEPLOY_PORT }} \ -o StrictHostKeyChecking=no \ database/init.sql \ root@${{ secrets.DEPLOY_HOST }}:/opt/realms/init.sql # Copy backend config template scp -i ~/.ssh/deploy_key -P ${{ secrets.DEPLOY_PORT }} \ -o StrictHostKeyChecking=no \ backend/config.json.example \ root@${{ secrets.DEPLOY_HOST }}:/opt/realms/config.json.template - name: Generate config.json from .env run: | ssh -i ~/.ssh/deploy_key -p ${{ secrets.DEPLOY_PORT }} \ -o StrictHostKeyChecking=no \ root@${{ secrets.DEPLOY_HOST }} ' cd /opt/realms if [ -f .env ]; then # Load environment variables export $(grep -v "^#" .env | xargs) # Generate config.json from template with actual values sed -e "s/CHANGE_ME_database_password/${DB_PASSWORD}/g" \ -e "s/CHANGE_ME_ome_api_token/${OME_API_TOKEN}/g" \ config.json.template > config.json rm -f config.json.template echo "Generated config.json with actual credentials" else echo "WARNING: No .env file found! Using template as-is (will fail to connect)" mv config.json.template config.json fi ' - name: Deploy to Production run: | ssh -i ~/.ssh/deploy_key -p ${{ secrets.DEPLOY_PORT }} \ -o StrictHostKeyChecking=no \ root@${{ secrets.DEPLOY_HOST }} ' set -e cd /opt/realms # Login to registry echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin # Pull latest images docker compose pull # Bring up services with zero-downtime restart docker compose up -d --remove-orphans # Prune old images docker image prune -f # Show running containers docker compose ps ' - name: Cleanup SSH key if: always() run: rm -f ~/.ssh/deploy_key - name: Health Check run: | sleep 10 ssh -i ~/.ssh/deploy_key -p ${{ secrets.DEPLOY_PORT }} \ -o StrictHostKeyChecking=no \ root@${{ secrets.DEPLOY_HOST }} ' # Check if services are running docker compose ps --format "table {{.Name}}\t{{.Status}}" # Basic health check for frontend curl -sf http://localhost:80/health || echo "Frontend health check pending" ' || true