Update deploy-api.yml #66
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy Lab API to Dreamhost 🧪 | |
| on: | |
| push: | |
| branches: ["main"] | |
| concurrency: | |
| group: deploy-lab-api | |
| cancel-in-progress: true | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@v4 | |
| - name: 🔍 Reveal NODE_ENV (runner) | |
| run: | | |
| echo "RUNNER NODE_ENV=$NODE_ENV" | |
| env | grep -E '^NODE_ENV=' || true | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: "npm" | |
| - name: Install dependencies (incl dev for tests) | |
| run: npm ci | |
| - name: Run tests | |
| run: npm test | |
| - name: Build API | |
| run: npm run build | |
| - name: Verify build output exists | |
| run: | | |
| set -e | |
| test -d dist | |
| ls -la dist | |
| - name: Create deploy bundle | |
| run: | | |
| set -e | |
| rm -rf deploy_bundle | |
| mkdir -p deploy_bundle | |
| cp -R dist deploy_bundle/dist | |
| cp package.json package-lock.json deploy_bundle/ | |
| # Include ecosystem file if it's tracked in the repo | |
| if [ -f ecosystem.config.cjs ]; then | |
| cp ecosystem.config.cjs deploy_bundle/ | |
| echo "✅ Included ecosystem.config.cjs (from repo)" | |
| else | |
| echo "⚠️ ecosystem.config.cjs not found in repo checkout; continuing without it" | |
| ls -la | |
| fi | |
| - name: Validate deploy secrets (fail fast) | |
| run: | | |
| test -n "${{ secrets.SFTP_HOST }}" || (echo "Missing SFTP_HOST" && exit 1) | |
| test -n "${{ secrets.SFTP_USER }}" || (echo "Missing SFTP_USER" && exit 1) | |
| test -n "${{ secrets.SFTP_PATH }}" || (echo "Missing SFTP_PATH" && exit 1) | |
| test -n "${{ secrets.SSH_PRIVATE_KEY }}" || (echo "Missing SSH_PRIVATE_KEY" && exit 1) | |
| test -n "${{ secrets.SSH_PORT }}" || (echo "Missing SSH_PORT" && exit 1) | |
| - name: Ensure remote deploy dir exists | |
| uses: appleboy/[email protected] | |
| with: | |
| host: ${{ secrets.SFTP_HOST }} | |
| username: ${{ secrets.SFTP_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| port: ${{ secrets.SSH_PORT }} | |
| script_stop: true | |
| script: | | |
| set -e | |
| mkdir -p "${{ secrets.SFTP_PATH }}" | |
| mkdir -p "${{ secrets.SFTP_PATH }}/logs" | |
| mkdir -p "${{ secrets.SFTP_PATH }}/data" | |
| ls -ld "${{ secrets.SFTP_PATH }}" "${{ secrets.SFTP_PATH }}/logs" "${{ secrets.SFTP_PATH }}/data" | |
| - name: Upload deploy bundle via scp (key auth) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| PORT="${{ secrets.SSH_PORT }}" | |
| PORT="${PORT:-22}" | |
| mkdir -p ~/.ssh | |
| chmod 700 ~/.ssh | |
| echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 | |
| chmod 600 ~/.ssh/id_ed25519 | |
| ssh-keyscan -p "$PORT" -H "${{ secrets.SFTP_HOST }}" >> ~/.ssh/known_hosts | |
| scp -P "$PORT" -r deploy_bundle/* \ | |
| "${{ secrets.SFTP_USER }}@${{ secrets.SFTP_HOST }}:${{ secrets.SFTP_PATH }}/" | |
| - name: Restart API with PM2 (production + health check) | |
| uses: appleboy/[email protected] | |
| with: | |
| host: ${{ secrets.SFTP_HOST }} | |
| username: ${{ secrets.SFTP_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| port: ${{ secrets.SSH_PORT }} | |
| script_stop: true | |
| script: | | |
| set -e | |
| echo "User: $(whoami)" | |
| echo "Host: $(hostname)" | |
| # Known-good Node/pm2 path | |
| export PATH="$HOME/.nvm/versions/node/v20.19.6/bin:$PATH" | |
| echo "PATH=$PATH" | |
| command -v node | |
| command -v npm | |
| command -v pm2 | |
| node -v | |
| npm -v | |
| pm2 -v | |
| cd "${{ secrets.SFTP_PATH }}" | |
| echo "🔍 Reveal NODE_ENV (server shell)" | |
| echo "SERVER NODE_ENV=$NODE_ENV" | |
| env | grep -E '^NODE_ENV=' || true | |
| echo "== Verify dist on server ==" | |
| test -f dist/index.js || (echo "❌ dist/index.js missing on server" && ls -la && ls -la dist || true && exit 1) | |
| mkdir -p logs data | |
| rm -f ecosystem.config.js || true | |
| echo "== Install production deps ==" | |
| npm ci --omit=dev | |
| echo "== PM2 startOrReload (production) ==" | |
| if [ -f ecosystem.config.cjs ]; then | |
| echo "✅ Using ecosystem.config.cjs" | |
| pm2 startOrReload ecosystem.config.cjs --env production --update-env | |
| else | |
| echo "⚠️ No ecosystem.config.cjs found; fallback" | |
| pm2 restart lab-api --update-env || pm2 start dist/index.js --name lab-api --update-env | |
| fi | |
| pm2 save | |
| pm2 list | |
| echo "== Health check (local) ==" | |
| for i in 1 2 3 4 5 6 7 8 9 10; do | |
| if curl -fsS "http://127.0.0.1:8001/health" >/dev/null; then | |
| echo "✅ Health check OK (/health)" | |
| exit 0 | |
| fi | |
| echo "…not up yet (attempt $i/10). waiting 1s" | |
| sleep 1 | |
| done | |
| echo "❌ Health check failed after retries" | |
| pm2 list || true | |
| pm2 describe lab-api || true | |
| pm2 logs lab-api --err --lines 200 --nostream || true | |
| pm2 logs lab-api --out --lines 200 --nostream || true | |
| exit 1 | |
| - name: On failure show PM2 logs (last 120 lines) | |
| if: failure() | |
| uses: appleboy/[email protected] | |
| with: | |
| host: ${{ secrets.SFTP_HOST }} | |
| username: ${{ secrets.SFTP_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| port: ${{ secrets.SSH_PORT }} | |
| script_stop: false | |
| script: | | |
| export PATH="$HOME/.nvm/versions/node/v20.19.6/bin:$PATH" | |
| pm2 list || true | |
| pm2 logs lab-api --out --lines 120 --nostream || true |