2d3c995135
psql was launching less/more for query output in the CI shell, then blocking indefinitely on "Press RETURN to continue". Also batch all numbered migrations into one psql session instead of one process per file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
216 lines
10 KiB
YAML
216 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
|
|
|
|
# Production postgrest always owns port 3010. No dynamic allocation.
|
|
POSTGREST_HOST_PORT=3010
|
|
export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
|
|
|
|
# Tear down previous stack: kill containers by name (two separate calls
|
|
# because multiple --filter name= flags are ANDed, not ORed).
|
|
{
|
|
docker ps -aq --filter "name=route_commerce_"
|
|
docker ps -aq --filter "name=route-commerce-"
|
|
} | sort -u | xargs -r docker rm -f || true
|
|
# Remove network after containers are gone so Docker allocates a fresh subnet.
|
|
docker network rm route-commerce_default 2>/dev/null || true
|
|
|
|
# Write fresh .env for docker compose
|
|
cp docker-compose.yml $APP_DIR/docker-compose.yml
|
|
cat > $APP_DIR/.env <<EOF
|
|
POSTGRES_USER=${POSTGRES_USER}
|
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
|
POSTGRES_DB=${POSTGRES_DB}
|
|
MINIO_ROOT_USER=${MINIO_ROOT_USER}
|
|
MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
|
POSTGREST_JWT_SECRET=${POSTGREST_JWT_SECRET}
|
|
POSTGREST_HOST_PORT=${POSTGREST_HOST_PORT}
|
|
NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
|
EOF
|
|
|
|
cd $APP_DIR
|
|
docker compose up -d db postgrest minio
|
|
|
|
# Wait for Postgres
|
|
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
|
|
# PAGER= prevents psql from launching less/more in a non-interactive shell,
|
|
# which hangs indefinitely waiting for keypress. Batch all files into one
|
|
# connection for speed instead of one psql invocation per file.
|
|
export PAGER=
|
|
export PGPASSWORD="${POSTGRES_PASSWORD}"
|
|
PG="psql -h 127.0.0.1 -U ${POSTGRES_USER} -d ${POSTGRES_DB} --no-psqlrc -v ON_ERROR_STOP=0 -q"
|
|
$PG -f supabase/migrations/000_preflight_supabase_compat.sql || true
|
|
[ -f supabase/captured_schema.sql ] && $PG -f supabase/captured_schema.sql || true
|
|
# Concatenate all numbered migrations and run in one session
|
|
cat supabase/migrations/[0-9]*.sql | $PG
|
|
|
|
- name: Install dependencies
|
|
run: npm install
|
|
|
|
- name: Build
|
|
env:
|
|
NODE_ENV: production
|
|
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: |
|
|
export NEXT_PUBLIC_API_URL="http://localhost:3010"
|
|
npm run build
|
|
|
|
- name: Deploy
|
|
env:
|
|
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
|
|
POSTGREST_HOST_PORT=3010
|
|
export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
|
|
|
|
# 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"
|