name: Deploy to Production on: workflow_dispatch: inputs: version: description: 'Git tag to create for this deploy (e.g. v0.1.2) — not the branch/tag above' required: true default: 'v0.1.0' type: string jobs: deploy: name: Build and deploy runs-on: ubuntu-latest steps: - name: Checkout repository run: | git init git remote add origin https://x-access-token:${FORGEJO_TOKEN}@srvr.nu/git/jocke/bilhej.git git fetch --depth 1 origin ${GITHUB_SHA} git checkout FETCH_HEAD - name: Tag version run: | git tag -d ${{ github.event.inputs.version }} 2>/dev/null || true git push origin --delete ${{ github.event.inputs.version }} 2>/dev/null || true git tag ${{ github.event.inputs.version }} git push origin ${{ github.event.inputs.version }} - name: Write production .env env: POSTGRES_DB: ${{ secrets.POSTGRES_DB }} POSTGRES_USER: ${{ secrets.POSTGRES_USER }} POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} JWT_SECRET: ${{ secrets.JWT_SECRET }} STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} STRIPE_PRICE_ID: ${{ secrets.STRIPE_PRICE_ID }} SWISH_NUMBER: ${{ secrets.SWISH_NUMBER }} ADMIN_EMAIL: ${{ secrets.ADMIN_EMAIL }} ADMIN_PASSWORD: ${{ secrets.ADMIN_PASSWORD }} APP_PUBLIC_BASE_URL: ${{ secrets.APP_PUBLIC_BASE_URL }} MAIL_HOST: ${{ secrets.MAIL_HOST }} MAIL_PORT: ${{ secrets.MAIL_PORT }} MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }} MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }} MAIL_FROM: ${{ secrets.MAIL_FROM }} VITE_UMAMI_WEBSITE_ID: ${{ secrets.VITE_UMAMI_WEBSITE_ID }} run: | # Docker Compose treats $ as variable interpolation in .env files. # Escape literal dollar signs (e.g. in passwords) as $$. escape() { printf '%s' "$1" | sed 's/\$/$$/g'; } { printf 'POSTGRES_DB=%s\n' "$(escape "$POSTGRES_DB")" printf 'POSTGRES_USER=%s\n' "$(escape "$POSTGRES_USER")" printf 'POSTGRES_PASSWORD=%s\n' "$(escape "$POSTGRES_PASSWORD")" printf 'JWT_SECRET=%s\n' "$(escape "$JWT_SECRET")" printf 'STRIPE_SECRET_KEY=%s\n' "$(escape "$STRIPE_SECRET_KEY")" printf 'STRIPE_WEBHOOK_SECRET=%s\n' "$(escape "$STRIPE_WEBHOOK_SECRET")" printf 'STRIPE_PRICE_ID=%s\n' "$(escape "$STRIPE_PRICE_ID")" printf 'SWISH_NUMBER=%s\n' "$(escape "$SWISH_NUMBER")" printf 'ADMIN_EMAIL=%s\n' "$(escape "$ADMIN_EMAIL")" printf 'ADMIN_PASSWORD=%s\n' "$(escape "$ADMIN_PASSWORD")" printf 'APP_PUBLIC_BASE_URL=%s\n' "$(escape "${APP_PUBLIC_BASE_URL:-https://bilhej.se}")" printf 'MAIL_HOST=%s\n' "$(escape "$MAIL_HOST")" printf 'MAIL_PORT=%s\n' "$(escape "${MAIL_PORT:-587}")" printf 'MAIL_USERNAME=%s\n' "$(escape "$MAIL_USERNAME")" printf 'MAIL_PASSWORD=%s\n' "$(escape "$MAIL_PASSWORD")" printf 'MAIL_FROM=%s\n' "$(escape "${MAIL_FROM:-noreply@bilhej.se}")" printf 'VITE_UMAMI_WEBSITE_ID=%s\n' "$(escape "$VITE_UMAMI_WEBSITE_ID")" } > .env - name: Build and start production stack run: | docker compose -p bilhej-prod -f docker-compose.prod.yml down docker compose -p bilhej-prod -f docker-compose.prod.yml up --build -d - name: Health checks with rollback run: | echo "Waiting for services to start..." sleep 30 BACKEND_OK=false for i in 1 2 3 4 5 6 7 8 9 10; do if docker run --rm --network bilhej-prod_default curlimages/curl:8.5.0 \ -sf http://bilhej-backend-prod:8080/api/payment/swish-info > /dev/null; then echo "Backend is healthy" BACKEND_OK=true break fi echo "Backend check attempt $i failed, retrying in 5s..." sleep 5 done FRONTEND_OK=false for i in 1 2 3 4 5; do if docker run --rm --network bilhej-prod_default curlimages/curl:8.5.0 \ -s http://bilhej-frontend-prod/ | grep -qi "bilhej\|Bilhej\|BilHej"; then echo "Frontend is serving" FRONTEND_OK=true break fi echo "Frontend check attempt $i failed, retrying in 5s..." sleep 5 done if [ "$BACKEND_OK" != "true" ] || [ "$FRONTEND_OK" != "true" ]; then echo "" echo "═══════════════════════════════════════════════════" echo " HEALTH CHECK FAILED — DIAGNOSTICS" echo "═══════════════════════════════════════════════════" echo "" docker compose -p bilhej-prod -f docker-compose.prod.yml ps echo "" echo "--- Backend logs ---" docker logs bilhej-backend-prod 2>&1 | tail -80 || true echo "" echo "--- Postgres logs ---" docker logs bilhej-postgres-prod 2>&1 | tail -30 || true echo "" echo "═══════════════════════════════════════════════════" echo " ROLLING BACK DEPLOYMENT" echo "═══════════════════════════════════════════════════" echo "" docker compose -p bilhej-prod -f docker-compose.prod.yml down echo "" echo "Rolled back. Containers stopped. DB volume preserved." echo "Read Backend logs above to find the root cause before redeploying." exit 1 fi - name: Print deploy status run: | echo "" echo "═══════════════════════════════════════════════════" echo " Deployed ${{ github.event.inputs.version }} to production" echo "═══════════════════════════════════════════════════" echo "" docker compose -p bilhej-prod -f docker-compose.prod.yml ps echo "" echo "Containers running. Update nginx config on srvr.nu" echo "to point bilhej.se to the frontend container." echo ""