9759dd042e
The dev PostgREST (started outside docker compose for local development) and the production PostgREST (started by docker compose up) were both trying to bind 127.0.0.1:3001. Even with fuser -k in the deploy cleanup, something on tyler's host (likely a process supervisor restarting the dev PostgREST) keeps reclaiming 3001 between our check and the docker bind. Move the production PostgREST to host port 3011, keeping the container's internal port at 3001 (PostgREST's default). This lets dev and prod coexist on the same host without fighting over a port. Update NEXT_PUBLIC_API_URL to http://localhost:3011 in: - Build step env (so the build bakes in the right URL) - Start Docker stack .env append (so docker compose + app see the URL) - Deploy step .env.production writing (so runtime app uses 3011)
205 lines
10 KiB
YAML
205 lines
10 KiB
YAML
name: Deploy to route.crispygoat.com
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
- name: Start Docker stack
|
|
env:
|
|
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
|
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
|
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
|
|
MINIO_ROOT_USER: ${{ secrets.MINIO_ROOT_USER }}
|
|
MINIO_ROOT_PASSWORD: ${{ secrets.MINIO_ROOT_PASSWORD }}
|
|
POSTGREST_JWT_SECRET: ${{ secrets.POSTGREST_JWT_SECRET }}
|
|
run: |
|
|
APP_DIR=/home/tyler/route-commerce
|
|
mkdir -p $APP_DIR
|
|
# Free port 3001 if anything is still on it (stale container, leftover dev process)
|
|
if ss -tln 2>/dev/null | grep -q ':3001 '; then
|
|
echo "Port 3001 in use, attempting to free it..."
|
|
fuser -k 3001/tcp 2>/dev/null || true
|
|
docker ps -aq --filter "publish=3001" 2>/dev/null | xargs -r docker rm -f 2>/dev/null || true
|
|
docker compose -f $APP_DIR/docker-compose.yml down --remove-orphans 2>/dev/null || true
|
|
sleep 3
|
|
fi
|
|
# Seed config files into APP_DIR if missing (they live in the repo, not in APP_DIR)
|
|
[ -f $APP_DIR/.env.example ] || cp .env.example $APP_DIR/.env.example
|
|
[ -f $APP_DIR/docker-compose.yml ] || cp docker-compose.yml $APP_DIR/docker-compose.yml
|
|
cd $APP_DIR
|
|
[ -f .env ] || cp .env.example .env
|
|
# Append production secrets to .env (overriding .env.example defaults)
|
|
{
|
|
echo "POSTGRES_USER=${POSTGRES_USER}"
|
|
echo "POSTGRES_PASSWORD=${POSTGRES_PASSWORD}"
|
|
echo "POSTGRES_DB=${POSTGRES_DB}"
|
|
echo "MINIO_ROOT_USER=${MINIO_ROOT_USER}"
|
|
echo "MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}"
|
|
echo "POSTGREST_JWT_SECRET=${POSTGREST_JWT_SECRET}"
|
|
echo "NEXT_PUBLIC_API_URL=http://localhost:3011"
|
|
} >> .env
|
|
# Bring the stack up fresh — --force-recreate ensures no stale
|
|
# network/container references from prior failed attempts
|
|
docker compose up -d --force-recreate db postgrest minio minio_init
|
|
# Wait for Postgres healthcheck
|
|
for i in $(seq 1 30); do
|
|
if docker compose exec -T db pg_isready -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" > /dev/null 2>&1; then
|
|
echo "Postgres is ready"
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
- name: Apply migrations
|
|
env:
|
|
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
|
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
|
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
|
|
run: |
|
|
APP_DIR=/home/tyler/route-commerce
|
|
# Seed supabase/ into APP_DIR if missing (the deploy step copies it after, but
|
|
# we need it here for migrations)
|
|
[ -d $APP_DIR/supabase ] || cp -r supabase $APP_DIR/supabase
|
|
cd $APP_DIR
|
|
PGPASSWORD="${POSTGRES_PASSWORD}" psql -h 127.0.0.1 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -v ON_ERROR_STOP=0 -f supabase/migrations/000_preflight_supabase_compat.sql || true
|
|
[ -f supabase/captured_schema.sql ] && PGPASSWORD="${POSTGRES_PASSWORD}" psql -h 127.0.0.1 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -v ON_ERROR_STOP=0 -f supabase/captured_schema.sql || echo "captured_schema.sql not present, skipping"
|
|
for f in supabase/migrations/[0-9]*.sql; do
|
|
PGPASSWORD="${POSTGRES_PASSWORD}" psql -h 127.0.0.1 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -v ON_ERROR_STOP=0 -q -f "$f" || echo "FAIL: $f"
|
|
done
|
|
|
|
- name: Install dependencies
|
|
run: npm install
|
|
|
|
- name: Build
|
|
env:
|
|
NODE_ENV: production
|
|
NEXT_PUBLIC_API_URL: http://localhost:3011
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }}
|
|
BETTER_AUTH_URL: ${{ secrets.BETTER_AUTH_URL }}
|
|
NEXT_PUBLIC_BETTER_AUTH_URL: ${{ secrets.NEXT_PUBLIC_BETTER_AUTH_URL }}
|
|
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
|
|
NEXT_PUBLIC_STORAGE_BASE_URL: ${{ secrets.NEXT_PUBLIC_STORAGE_BASE_URL }}
|
|
STORAGE_ENDPOINT: ${{ secrets.STORAGE_ENDPOINT }}
|
|
STORAGE_REGION: ${{ secrets.STORAGE_REGION }}
|
|
STORAGE_ACCESS_KEY: ${{ secrets.STORAGE_ACCESS_KEY }}
|
|
STORAGE_SECRET_KEY: ${{ secrets.STORAGE_SECRET_KEY }}
|
|
STORAGE_BUCKET_PREFIX: ${{ secrets.STORAGE_BUCKET_PREFIX }}
|
|
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
|
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
|
|
STRIPE_PUBLISHABLE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }}
|
|
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
|
|
RESEND_WEBHOOK_SECRET: ${{ secrets.RESEND_WEBHOOK_SECRET }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }}
|
|
FROM_EMAIL: ${{ secrets.FROM_EMAIL }}
|
|
run: npm run build
|
|
|
|
- name: Deploy
|
|
env:
|
|
NEXT_PUBLIC_API_URL: http://localhost:3011
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
|
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
|
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
|
|
MINIO_ROOT_USER: ${{ secrets.MINIO_ROOT_USER }}
|
|
MINIO_ROOT_PASSWORD: ${{ secrets.MINIO_ROOT_PASSWORD }}
|
|
POSTGREST_JWT_SECRET: ${{ secrets.POSTGREST_JWT_SECRET }}
|
|
BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }}
|
|
BETTER_AUTH_URL: ${{ secrets.BETTER_AUTH_URL }}
|
|
NEXT_PUBLIC_BETTER_AUTH_URL: ${{ secrets.NEXT_PUBLIC_BETTER_AUTH_URL }}
|
|
STORAGE_ENDPOINT: ${{ secrets.STORAGE_ENDPOINT }}
|
|
STORAGE_REGION: ${{ secrets.STORAGE_REGION }}
|
|
STORAGE_ACCESS_KEY: ${{ secrets.STORAGE_ACCESS_KEY }}
|
|
STORAGE_SECRET_KEY: ${{ secrets.STORAGE_SECRET_KEY }}
|
|
STORAGE_BUCKET_PREFIX: ${{ secrets.STORAGE_BUCKET_PREFIX }}
|
|
NEXT_PUBLIC_STORAGE_BASE_URL: ${{ secrets.NEXT_PUBLIC_STORAGE_BASE_URL }}
|
|
PGRST_SERVER_PORT: ${{ secrets.PGRST_SERVER_PORT }}
|
|
PGRST_DB_URI: ${{ secrets.PGRST_DB_URI }}
|
|
PGRST_DB_ANON_ROLE: ${{ secrets.PGRST_DB_ANON_ROLE }}
|
|
PGRST_JWT_SECRET: ${{ secrets.POSTGREST_JWT_SECRET }}
|
|
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
|
|
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
|
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
|
|
STRIPE_PUBLISHABLE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }}
|
|
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
|
|
RESEND_WEBHOOK_SECRET: ${{ secrets.RESEND_WEBHOOK_SECRET }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }}
|
|
FROM_EMAIL: ${{ secrets.FROM_EMAIL }}
|
|
run: |
|
|
APP_DIR=/home/tyler/route-commerce
|
|
mkdir -p $APP_DIR
|
|
|
|
# Write env file from secrets (preserves existing .env for docker compose)
|
|
{
|
|
printf "DATABASE_URL=%s\n" "$DATABASE_URL"
|
|
printf "NEXT_PUBLIC_API_URL=%s\n" "$NEXT_PUBLIC_API_URL"
|
|
printf "POSTGRES_USER=%s\n" "$POSTGRES_USER"
|
|
printf "POSTGRES_PASSWORD=%s\n" "$POSTGRES_PASSWORD"
|
|
printf "POSTGRES_DB=%s\n" "$POSTGRES_DB"
|
|
printf "MINIO_ROOT_USER=%s\n" "$MINIO_ROOT_USER"
|
|
printf "MINIO_ROOT_PASSWORD=%s\n" "$MINIO_ROOT_PASSWORD"
|
|
printf "POSTGREST_JWT_SECRET=%s\n" "$POSTGREST_JWT_SECRET"
|
|
printf "BETTER_AUTH_SECRET=%s\n" "$BETTER_AUTH_SECRET"
|
|
printf "BETTER_AUTH_URL=%s\n" "$BETTER_AUTH_URL"
|
|
printf "NEXT_PUBLIC_BETTER_AUTH_URL=%s\n" "$NEXT_PUBLIC_BETTER_AUTH_URL"
|
|
printf "STORAGE_ENDPOINT=%s\n" "$STORAGE_ENDPOINT"
|
|
printf "STORAGE_REGION=%s\n" "$STORAGE_REGION"
|
|
printf "STORAGE_ACCESS_KEY=%s\n" "$STORAGE_ACCESS_KEY"
|
|
printf "STORAGE_SECRET_KEY=%s\n" "$STORAGE_SECRET_KEY"
|
|
printf "STORAGE_BUCKET_PREFIX=%s\n" "$STORAGE_BUCKET_PREFIX"
|
|
printf "NEXT_PUBLIC_STORAGE_BASE_URL=%s\n" "$NEXT_PUBLIC_STORAGE_BASE_URL"
|
|
printf "PGRST_SERVER_PORT=%s\n" "$PGRST_SERVER_PORT"
|
|
printf "PGRST_DB_URI=%s\n" "$PGRST_DB_URI"
|
|
printf "PGRST_DB_ANON_ROLE=%s\n" "$PGRST_DB_ANON_ROLE"
|
|
printf "PGRST_JWT_SECRET=%s\n" "$POSTGREST_JWT_SECRET"
|
|
printf "NEXT_PUBLIC_SUPABASE_URL=%s\n" "$NEXT_PUBLIC_SUPABASE_URL"
|
|
printf "NEXT_PUBLIC_SUPABASE_ANON_KEY=%s\n" "$NEXT_PUBLIC_SUPABASE_ANON_KEY"
|
|
printf "STRIPE_SECRET_KEY=%s\n" "$STRIPE_SECRET_KEY"
|
|
printf "STRIPE_WEBHOOK_SECRET=%s\n" "$STRIPE_WEBHOOK_SECRET"
|
|
printf "STRIPE_PUBLISHABLE_KEY=%s\n" "$STRIPE_PUBLISHABLE_KEY"
|
|
printf "RESEND_API_KEY=%s\n" "$RESEND_API_KEY"
|
|
printf "RESEND_WEBHOOK_SECRET=%s\n" "$RESEND_WEBHOOK_SECRET"
|
|
printf "MINIMAX_API_KEY=%s\n" "$MINIMAX_API_KEY"
|
|
printf "MINIMAX_BASE_URL=%s\n" "$MINIMAX_BASE_URL"
|
|
printf "FROM_EMAIL=%s\n" "$FROM_EMAIL"
|
|
} > $APP_DIR/.env.production
|
|
|
|
# Copy build output and required files
|
|
rsync -a --delete .next/ $APP_DIR/.next/
|
|
rsync -a --delete public/ $APP_DIR/public/
|
|
cp package.json $APP_DIR/
|
|
cp docker-compose.yml $APP_DIR/
|
|
cp -r supabase/ $APP_DIR/
|
|
cp next.config.ts $APP_DIR/ 2>/dev/null || cp next.config.js $APP_DIR/ 2>/dev/null || true
|
|
|
|
# Install production deps only
|
|
cd $APP_DIR
|
|
npm install --omit=dev
|
|
|
|
# Start or restart PM2 process
|
|
if pm2 describe route-commerce > /dev/null 2>&1; then
|
|
pm2 restart route-commerce
|
|
else
|
|
pm2 start npm --name route-commerce -- start -- -p 3100
|
|
pm2 save
|
|
fi
|
|
|
|
echo "Deployed successfully"
|