From d95e61ca07acd382ace6c4c14a499110a49212d1 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 | 288 ++---------------------------------- 2 files changed, 11 insertions(+), 329 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..02b507f 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,4 +1,4 @@ -name: Deploy to route.crispygoat.com +name: Deploy on: push: @@ -7,282 +7,16 @@ 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] + # Self-hosted Gitea runner. + # Registered labels on crispygoat-host-runner: self-hosted, ubuntu-latest + runs-on: [self-hosted, 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 }} + - name: Update homelab checkout run: | - APP_DIR=/home/tyler/route-commerce - mkdir -p $APP_DIR + cd /home/tyler/route-commerce + git pull --ff-only origin main - # 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_SECRET: ${{ secrets.AUTH_SECRET }} - AUTH_URL: ${{ secrets.AUTH_URL }} - NEXT_PUBLIC_AUTH_URL: ${{ secrets.NEXT_PUBLIC_AUTH_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 }} - - # ── 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 - - - 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 }} - - # Auth.js v5 - AUTH_SECRET: ${{ secrets.AUTH_SECRET }} - AUTH_URL: ${{ secrets.AUTH_URL }} - NEXT_PUBLIC_AUTH_URL: ${{ secrets.NEXT_PUBLIC_AUTH_URL }} - GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - - # 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 }} - - # 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 - 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 }} - - FROM_EMAIL: ${{ secrets.FROM_EMAIL }} - 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) - { - 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 "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 "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 "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" - } > $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" + - name: Run deploy.sh + working-directory: /home/tyler/route-commerce + run: ./deploy/deploy.sh