From 9209fbdcfc1d8371d7f9e7fedd75ca3cc12050ad Mon Sep 17 00:00:00 2001 From: jhodgkin Date: Tue, 7 Jul 2026 22:50:30 -0600 Subject: [PATCH] Add trunk-based dev deployment with smoke tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gitea Action: push to main → SSH deploy to CT114 (lisilou-dev, 192.168.1.192) git pull → docker compose build → up -d → health wait → smoke tests - scripts/smoke-test.sh: 6 curl assertions covering site load, nginx health, API health, config serving, booking POST, and nginx routing sanity - Replaces the non-functional registry-based deploy workflow Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/deploy.yml | 100 ++++++++++++++++-------------------- scripts/smoke-test.sh | 53 +++++++++++++++++++ 2 files changed, 98 insertions(+), 55 deletions(-) create mode 100755 scripts/smoke-test.sh diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index e73fecd..521e570 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,68 +1,58 @@ -name: Build and Deploy +name: Deploy to Dev on: push: - branches: - - main - pull_request: - branches: - - main - -env: - REGISTRY: your-registry.com # Update with your registry - IMAGE_NAME: lisilou-portfolio + branches: [main] jobs: - build: + deploy: + name: Deploy & Smoke Test runs-on: ubuntu-latest + steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Install SSH client + run: which ssh || (apt-get update -qq && apt-get install -y -qq openssh-client) - - name: Log in to Container Registry - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} + - name: Setup SSH key + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEV_SSH_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H ${{ secrets.DEV_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=sha,prefix= - type=raw,value=latest,enable={{is_default_branch}} + - name: Deploy + run: | + ssh -i ~/.ssh/deploy_key \ + -o StrictHostKeyChecking=no \ + -o ConnectTimeout=10 \ + ${{ secrets.DEV_USER }}@${{ secrets.DEV_HOST }} \ + 'set -e + cd /opt/lisilou-portfolio + git pull origin main + docker compose build + docker compose up -d + echo "Deploy complete"' - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Wait for health check + run: | + echo "Waiting for services..." + for i in $(seq 1 24); do + if curl -sf --max-time 4 "http://${{ secrets.DEV_HOST }}:8080/health" > /dev/null 2>&1; then + echo "Services are up (attempt $i)" + exit 0 + fi + echo " Attempt $i/24 — sleeping 5s" + sleep 5 + done + echo "Health check timed out after 2 minutes" + exit 1 - deploy: - needs: build - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - steps: - - name: Deploy to server - uses: appleboy/ssh-action@v1.0.0 - with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USER }} - key: ${{ secrets.DEPLOY_KEY }} - script: | - cd /opt/lisilou-portfolio - docker compose pull - docker compose up -d - docker image prune -f + - name: Smoke tests + run: bash scripts/smoke-test.sh "http://${{ secrets.DEV_HOST }}:8080" + + - name: Cleanup SSH key + if: always() + run: rm -f ~/.ssh/deploy_key diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh new file mode 100755 index 0000000..c8b7156 --- /dev/null +++ b/scripts/smoke-test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Usage: bash scripts/smoke-test.sh http://192.168.1.192:8080 +set -uo pipefail + +BASE="${1:-http://localhost:8080}" +PASS=0 +FAIL=0 + +ok() { echo " ✓ $1"; PASS=$((PASS+1)); } +fail() { echo " ✗ $1"; FAIL=$((FAIL+1)); } + +http_status() { curl -s -o /dev/null -w "%{http_code}" --max-time 8 "$1"; } +http_body() { curl -s --max-time 8 "$1"; } + +echo "" +echo "Smoke tests → $BASE" +echo "────────────────────────────────────────" + +# 1. Portfolio site loads +s=$(http_status "$BASE/") +[ "$s" = "200" ] && ok "GET / → 200" || fail "GET / → 200 (got $s)" + +# 2. Nginx health endpoint +s=$(http_status "$BASE/health") +[ "$s" = "200" ] && ok "GET /health → 200" || fail "GET /health → 200 (got $s)" + +# 3. API health +b=$(http_body "$BASE/api/health") +[[ "$b" == *'"ok":true'* ]] && ok "GET /api/health → {ok:true}" || fail "GET /api/health → {ok:true} (got: $b)" + +# 4. Config file served +s=$(http_status "$BASE/config/site.json") +[ "$s" = "200" ] && ok "GET /config/site.json → 200" || fail "GET /config/site.json → 200 (got $s)" + +# 5. Booking POST creates record +b=$(curl -s --max-time 8 -X POST "$BASE/api/bookings" \ + -H "Content-Type: application/json" \ + -d '{"client_name":"Smoke Test","client_email":"smoke@test.lisilou","session_date":"2099-12-31","session_type":"individual","session_length":"mini","location":"studio"}') +[[ "$b" == *'"id"'* ]] && ok "POST /api/bookings → returns id" || fail "POST /api/bookings → returns id (got: $b)" + +# 6. Nginx does not 500 on unknown /api/ path +s=$(http_status "$BASE/api/nonexistent-route") +[ "$s" != "500" ] && ok "GET /api/unknown → not 500 (got $s)" || fail "GET /api/unknown → 500 (nginx misconfigured)" + +echo "────────────────────────────────────────" +if [ $FAIL -eq 0 ]; then + echo " All $PASS tests passed ✓" +else + echo " Passed: $PASS Failed: $FAIL" +fi +echo "" + +[ $FAIL -eq 0 ]