Skip to content

Merge pull request #26 from AdaInTheLab/feat/type-filter-endpoint #120

Merge pull request #26 from AdaInTheLab/feat/type-filter-endpoint

Merge pull request #26 from AdaInTheLab/feat/type-filter-endpoint #120

Workflow file for this run

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
with:
submodules: recursive
- 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 -R content deploy_bundle/content
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/ssh-action@v1.0.3
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/ssh-action@v1.0.3
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
echo "== PM2 env for process 0 (look for PORT/HOST) =="
pm2 env 0 | egrep -i '^(PORT|HOST|NODE_ENV|BASE_URL|API_PORT)=' || true
echo "== What ports is node actually listening on? =="
ss -ltnp | grep node || true
ss -ltnp | egrep ':(8001|3000|8000|8080)\b' || true
echo "== Show the wrapper + ecosystem so we know what it runs =="
ls -la ecosystem.config.cjs pm2-start.cjs || true
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 "== Listening sockets (8001) =="
ss -ltnp | grep ':8001' || true
echo "== Try both IPv4 + IPv6 =="
curl -v --max-time 2 "http://127.0.0.1:8001/health" || true
curl -v --max-time 2 "http://[::1]:8001/health" || true
curl -v --max-time 2 "http://localhost:8001/health" || true
echo "== Health check (local) =="
for i in 1 2 3 4 5 6 7 8 9 10; do
code="$(curl -s -o /tmp/health.txt -w "%{http_code}" "http://127.0.0.1:8001/health" || true)"
if [ "$code" = "200" ]; then
echo "✅ Health check OK (/health)"
cat /tmp/health.txt || true
exit 0
fi
echo "…health not ready (attempt $i/10) http=$code"
echo "--- body ---"
cat /tmp/health.txt || true
echo "------------"
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/ssh-action@v1.0.3
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