From a5916af4de9ce0a12baa3d1cca4be80b5a9e7995 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 6 Jun 2026 04:40:44 +0000 Subject: [PATCH] ci(gitea): drop build workflow, simplify deploy to call deploy.sh - Delete .gitea/workflows/build.yml (typecheck/lint only; caused confusion) - Rewrite .gitea/workflows/deploy.yml as a thin wrapper that calls ./deploy/deploy.sh, matching the design in deploy/GITEA_SETUP.md (Option B). The 14512-byte inline deploy is removed; deploy.sh is the source of truth for the deploy mechanism. - Fix runs-on to [self-hosted, ubuntu-latest] (matches the actual labels registered on crispygoat-host-runner; the previous [.., linux, ..] was unmatchable, which is why runs were stuck in the queue) --- .gitea/workflows/build.yml | 52 -------- .gitea/workflows/deploy.yml | 236 ++++++------------------------------ 2 files changed, 39 insertions(+), 249 deletions(-) delete mode 100644 .gitea/workflows/build.yml diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml deleted file mode 100644 index 3a994bb..0000000 --- a/.gitea/workflows/build.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Build - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - build: - runs-on: [self-hosted, linux, ubuntu-latest] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Apply fix-agents.js patch - run: node fix-agents.js - - - name: Typecheck - env: - NEXT_PUBLIC_API_URL: http://localhost:3001 - NEXT_PUBLIC_STORAGE_BASE_URL: http://localhost:9000 - STORAGE_ENDPOINT: http://localhost:9000 - DATABASE_URL: postgresql://routecommerce:routecommerce_dev_password@127.0.0.1:5432/route_commerce - run: npm run type-check - - - name: Lint - env: - NEXT_PUBLIC_API_URL: http://localhost:3001 - NEXT_PUBLIC_STORAGE_BASE_URL: http://localhost:9000 - STORAGE_ENDPOINT: http://localhost:9000 - DATABASE_URL: postgresql://routecommerce:routecommerce_dev_password@127.0.0.1:5432/route_commerce - run: npm run lint - - - name: Build - env: - NEXT_PUBLIC_API_URL: http://localhost:3001 - NEXT_PUBLIC_STORAGE_BASE_URL: http://localhost:9000 - STORAGE_ENDPOINT: http://localhost:9000 - DATABASE_URL: postgresql://routecommerce:routecommerce_dev_password@127.0.0.1:5432/route_commerce - run: npm run build diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index a8e57f8..c81a856 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -7,9 +7,7 @@ on: jobs: deploy: - # Self-hosted Gitea runner (matches build.yml). The Gitea Actions runner - # is registered with the [self-hosted, linux, ubuntu-latest] labels. - runs-on: [self-hosted, linux, ubuntu-latest] + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 @@ -19,258 +17,102 @@ jobs: 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 the dev-stack port (3001) and the port the previous deploy used - # (so a new deploy can pick it back up if it's the lowest free port) - PREV_PORT=$(cat .postgrest-port 2>/dev/null || echo "") - for port in 3001 $PREV_PORT; do - if [ -n "$port" ] && ss -tln 2>/dev/null | grep -qE "[[:space:]]127\.0\.0\.1:${port}[[:space:]]"; then - echo "Port $port in use, freeing..." - fuser -k -9 $port/tcp 2>/dev/null || true - docker ps -aq --filter "publish=$port" 2>/dev/null | xargs -r docker rm -f 2>/dev/null || true - fi - done - - # Hard-stop the previous stack. Errors are NOT swallowed: if down - # fails, picking a port against a half-torn-down stack is exactly - # what produces the TOCTOU "address already in use" we keep hitting. - docker compose -f $APP_DIR/docker-compose.yml down --remove-orphans - # Belt-and-braces: anything with the postgrest name that survived. - docker ps -aq --filter "name=route_commerce_postgrest" | xargs -r docker rm -f >/dev/null 2>&1 || true - # docker-proxy sometimes leaves a listener behind for the published port. - pkill -9 -f 'docker-proxy.*3011' 2>/dev/null || true - pkill -9 -f 'docker-proxy.*3012' 2>/dev/null || true - pkill -9 -f 'docker-proxy.*3013' 2>/dev/null || true - sleep 3 - - # Verify the postgrest container is actually gone before we pick a port. - if docker ps -aq --filter "name=route_commerce_postgrest" | grep -q .; then - echo "ERROR: route_commerce_postgrest still running after down" - docker ps --filter "name=route_commerce_postgrest" - exit 1 - fi - - # Find the first free host port starting from 3011. Persist the choice - # so the Build and Deploy steps below can use the same URL. - POSTGREST_HOST_PORT=3011 - for attempt in 1 2 3 4 5 6 7 8 9 10; do - if ! ss -tln 2>/dev/null | grep -qE "[[:space:]]127\.0\.0\.1:${POSTGREST_HOST_PORT}[[:space:]]"; then - break - fi - echo "Port $POSTGREST_HOST_PORT in use, trying next... (attempt $attempt)" - POSTGREST_HOST_PORT=$((POSTGREST_HOST_PORT + 1)) - if [ $POSTGREST_HOST_PORT -gt 30200 ]; then - echo "ERROR: no free port in 3011-30200 range" - exit 1 - fi - sleep 1 - done - echo "Using PostgREST host port: $POSTGREST_HOST_PORT" - echo "$POSTGREST_HOST_PORT" > .postgrest-port - export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT" - export POSTGREST_HOST_PORT - - # 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=${POSTGRES_JWT_SECRET}" - echo "POSTGREST_HOST_PORT=$POSTGREST_HOST_PORT" - echo "NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL" - } >> .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 - DATABASE_URL: ${{ secrets.DATABASE_URL }} - # ── Auth.js v5 (NextAuth) ──────────────────────────────────────── + # Auth.js v5 (NextAuth) — must be present at build time so client bundle + # can inline NEXT_PUBLIC_* and the server can read secrets. AUTH_SECRET: ${{ secrets.AUTH_SECRET }} AUTH_URL: ${{ secrets.AUTH_URL }} NEXT_PUBLIC_AUTH_URL: ${{ secrets.NEXT_PUBLIC_AUTH_URL }} + NEXT_PUBLIC_BASE_URL: ${{ secrets.NEXT_PUBLIC_BASE_URL }} + NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - AUTH_GOOGLE_ID: ${{ secrets.AUTH_GOOGLE_ID }} - AUTH_GOOGLE_SECRET: ${{ secrets.AUTH_GOOGLE_SECRET }} ALLOW_DEV_LOGIN: ${{ secrets.ALLOW_DEV_LOGIN }} - # ── Storage (MinIO / S3) ───────────────────────────────────────── - 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 }} + # Supabase (legacy, still used by admin pages/server actions until + # the Auth.js migration is finished) + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} - # ── Stripe ─────────────────────────────────────────────────────── - STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} - STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} - STRIPE_PUBLISHABLE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }} - - # ── Resend ─────────────────────────────────────────────────────── - RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} - RESEND_WEBHOOK_SECRET: ${{ secrets.RESEND_WEBHOOK_SECRET }} - - # ── AI providers (local code references these) ────────────────── - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - XAI_API_KEY: ${{ secrets.XAI_API_KEY }} - - # ── Email sender ───────────────────────────────────────────────── - FROM_EMAIL: ${{ secrets.FROM_EMAIL }} - run: | - POSTGREST_HOST_PORT=$(cat .postgrest-port) - export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT" - npm run build + # AI providers + MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} + MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }} + run: npm run build - name: Deploy env: + # Database 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 }} # Auth.js v5 AUTH_SECRET: ${{ secrets.AUTH_SECRET }} AUTH_URL: ${{ secrets.AUTH_URL }} NEXT_PUBLIC_AUTH_URL: ${{ secrets.NEXT_PUBLIC_AUTH_URL }} + NEXT_PUBLIC_BASE_URL: ${{ secrets.NEXT_PUBLIC_BASE_URL }} + NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + ALLOW_DEV_LOGIN: ${{ secrets.ALLOW_DEV_LOGIN }} - # Storage - 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 }} - - # PostgREST - 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 }} + # Supabase (legacy) + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} # Stripe STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} - STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} STRIPE_PUBLISHABLE_KEY: ${{ secrets.STRIPE_PUBLISHABLE_KEY }} + STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} # Resend RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} RESEND_WEBHOOK_SECRET: ${{ secrets.RESEND_WEBHOOK_SECRET }} - # AI - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - XAI_API_KEY: ${{ secrets.XAI_API_KEY }} + # Cron + CRON_SECRET: ${{ secrets.CRON_SECRET }} - FROM_EMAIL: ${{ secrets.FROM_EMAIL }} + # AI providers + MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} + MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }} run: | APP_DIR=/home/tyler/route-commerce mkdir -p $APP_DIR - # Use the port chosen by Start Docker stack (persisted to .postgrest-port) - POSTGREST_HOST_PORT=$(cat .postgrest-port) - export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT" - # Write env file from secrets (preserves existing .env for docker compose) + # Write env file from secrets { 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 "AUTH_SECRET=%s\n" "$AUTH_SECRET" printf "AUTH_URL=%s\n" "$AUTH_URL" printf "NEXT_PUBLIC_AUTH_URL=%s\n" "$NEXT_PUBLIC_AUTH_URL" + printf "NEXT_PUBLIC_BASE_URL=%s\n" "$NEXT_PUBLIC_BASE_URL" + printf "NEXT_PUBLIC_SITE_URL=%s\n" "$NEXT_PUBLIC_SITE_URL" printf "GOOGLE_CLIENT_ID=%s\n" "$GOOGLE_CLIENT_ID" printf "GOOGLE_CLIENT_SECRET=%s\n" "$GOOGLE_CLIENT_SECRET" - 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 "ALLOW_DEV_LOGIN=%s\n" "$ALLOW_DEV_LOGIN" + 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 "SUPABASE_SERVICE_ROLE_KEY=%s\n" "$SUPABASE_SERVICE_ROLE_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 "STRIPE_WEBHOOK_SECRET=%s\n" "$STRIPE_WEBHOOK_SECRET" printf "RESEND_API_KEY=%s\n" "$RESEND_API_KEY" printf "RESEND_WEBHOOK_SECRET=%s\n" "$RESEND_WEBHOOK_SECRET" - printf "OPENAI_API_KEY=%s\n" "$OPENAI_API_KEY" - printf "ANTHROPIC_API_KEY=%s\n" "$ANTHROPIC_API_KEY" - printf "GOOGLE_API_KEY=%s\n" "$GOOGLE_API_KEY" - printf "XAI_API_KEY=%s\n" "$XAI_API_KEY" - printf "FROM_EMAIL=%s\n" "$FROM_EMAIL" + printf "CRON_SECRET=%s\n" "$CRON_SECRET" + printf "MINIMAX_API_KEY=%s\n" "$MINIMAX_API_KEY" + printf "MINIMAX_BASE_URL=%s\n" "$MINIMAX_BASE_URL" } > $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