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: Install dependencies run: npm install - name: Run migrations env: DATABASE_URL: ${{ secrets.DATABASE_URL }} run: | npm run migrate:one || echo "No migration script or migrations already applied" - name: Build env: NODE_ENV: production DATABASE_URL: ${{ secrets.DATABASE_URL }} NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }} NEON_AUTH_BASE_URL: ${{ secrets.NEON_AUTH_BASE_URL }} NEON_AUTH_COOKIE_SECRET: ${{ secrets.NEON_AUTH_COOKIE_SECRET }} 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 }} ALLOW_DEV_LOGIN: ${{ secrets.ALLOW_DEV_LOGIN }} ADMIN_ALLOWED_EMAILS: ${{ secrets.ADMIN_ALLOWED_EMAILS }} NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY }} STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} STRIPE_PRICE_STARTER: ${{ secrets.STRIPE_PRICE_STARTER }} STRIPE_PRICE_FARM: ${{ secrets.STRIPE_PRICE_FARM }} STRIPE_PRICE_ENTERPRISE: ${{ secrets.STRIPE_PRICE_ENTERPRISE }} STRIPE_PRICE_HARVEST_REACH: ${{ secrets.STRIPE_PRICE_HARVEST_REACH }} STRIPE_PRICE_WHOLESALE_PORTAL: ${{ secrets.STRIPE_PRICE_WHOLESALE_PORTAL }} STRIPE_PRICE_WATER_LOG: ${{ secrets.STRIPE_PRICE_WATER_LOG }} STRIPE_PRICE_AI_TOOLS: ${{ secrets.STRIPE_PRICE_AI_TOOLS }} STRIPE_PRICE_SQUARE_SYNC: ${{ secrets.STRIPE_PRICE_SQUARE_SYNC }} STRIPE_PRICE_SMS_CAMPAIGNS: ${{ secrets.STRIPE_PRICE_SMS_CAMPAIGNS }} RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} FROM_EMAIL: ${{ secrets.FROM_EMAIL }} MINIO_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }} MINIO_REGION: ${{ secrets.MINIO_REGION }} MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }} MINIO_SECRET_KEY: ${{ secrets.MINIO_SECRET_KEY }} MINIO_PUBLIC_URL: ${{ secrets.MINIO_PUBLIC_URL }} MINIO_BUCKET_PRODUCTS: ${{ secrets.MINIO_BUCKET_PRODUCTS }} MINIO_BUCKET_BRAND_LOGOS: ${{ secrets.MINIO_BUCKET_BRAND_LOGOS }} MINIO_BUCKET_WATER_LOGS: ${{ secrets.MINIO_BUCKET_WATER_LOGS }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }} run: npm run build - name: Deploy env: DATABASE_URL: ${{ secrets.DATABASE_URL }} NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }} NEON_AUTH_BASE_URL: ${{ secrets.NEON_AUTH_BASE_URL }} NEON_AUTH_COOKIE_SECRET: ${{ secrets.NEON_AUTH_COOKIE_SECRET }} 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 }} ALLOW_DEV_LOGIN: ${{ secrets.ALLOW_DEV_LOGIN }} ADMIN_ALLOWED_EMAILS: ${{ secrets.ADMIN_ALLOWED_EMAILS }} NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY }} STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} STRIPE_PRICE_STARTER: ${{ secrets.STRIPE_PRICE_STARTER }} STRIPE_PRICE_FARM: ${{ secrets.STRIPE_PRICE_FARM }} STRIPE_PRICE_ENTERPRISE: ${{ secrets.STRIPE_PRICE_ENTERPRISE }} STRIPE_PRICE_HARVEST_REACH: ${{ secrets.STRIPE_PRICE_HARVEST_REACH }} STRIPE_PRICE_WHOLESALE_PORTAL: ${{ secrets.STRIPE_PRICE_WHOLESALE_PORTAL }} STRIPE_PRICE_WATER_LOG: ${{ secrets.STRIPE_PRICE_WATER_LOG }} STRIPE_PRICE_AI_TOOLS: ${{ secrets.STRIPE_PRICE_AI_TOOLS }} STRIPE_PRICE_SQUARE_SYNC: ${{ secrets.STRIPE_PRICE_SQUARE_SYNC }} STRIPE_PRICE_SMS_CAMPAIGNS: ${{ secrets.STRIPE_PRICE_SMS_CAMPAIGNS }} RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} FROM_EMAIL: ${{ secrets.FROM_EMAIL }} MINIO_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }} MINIO_REGION: ${{ secrets.MINIO_REGION }} MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }} MINIO_SECRET_KEY: ${{ secrets.MINIO_SECRET_KEY }} MINIO_PUBLIC_URL: ${{ secrets.MINIO_PUBLIC_URL }} MINIO_BUCKET_PRODUCTS: ${{ secrets.MINIO_BUCKET_PRODUCTS }} MINIO_BUCKET_BRAND_LOGOS: ${{ secrets.MINIO_BUCKET_BRAND_LOGOS }} MINIO_BUCKET_WATER_LOGS: ${{ secrets.MINIO_BUCKET_WATER_LOGS }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }} CRON_SECRET: ${{ secrets.CRON_SECRET }} SERVER_SSH_KEY: ${{ secrets.SERVER_SSH_KEY }} run: | set -e APP_DIR=/home/tyler/route-commerce # Setup SSH key mkdir -p ~/.ssh echo "$SERVER_SSH_KEY" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H route.crispygoat.com >> ~/.ssh/known_hosts 2>/dev/null || true # Test SSH connection echo "Testing SSH connection..." ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no tyler@route.crispygoat.com "echo 'SSH OK' && hostname" || { echo "SSH FAILED"; exit 1; } # Write production env file on server ssh tyler@route.crispygoat.com "mkdir -p $APP_DIR && cat > $APP_DIR/.env.production" << 'ENVEOF' DATABASE_URL=$DATABASE_URL NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL NEON_AUTH_BASE_URL=$NEON_AUTH_BASE_URL NEON_AUTH_COOKIE_SECRET=$NEON_AUTH_COOKIE_SECRET AUTH_SECRET=$AUTH_SECRET AUTH_URL=$AUTH_URL NEXT_PUBLIC_AUTH_URL=$NEXT_PUBLIC_AUTH_URL GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET ALLOW_DEV_LOGIN=$ALLOW_DEV_LOGIN ADMIN_ALLOWED_EMAILS=$ADMIN_ALLOWED_EMAILS NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=$NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY STRIPE_SECRET_KEY=$STRIPE_SECRET_KEY STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET STRIPE_PRICE_STARTER=$STRIPE_PRICE_STARTER STRIPE_PRICE_FARM=$STRIPE_PRICE_FARM STRIPE_PRICE_ENTERPRISE=$STRIPE_PRICE_ENTERPRISE STRIPE_PRICE_HARVEST_REACH=$STRIPE_PRICE_HARVEST_REACH STRIPE_PRICE_WHOLESALE_PORTAL=$STRIPE_PRICE_WHOLESALE_PORTAL STRIPE_PRICE_WATER_LOG=$STRIPE_PRICE_WATER_LOG STRIPE_PRICE_AI_TOOLS=$STRIPE_PRICE_AI_TOOLS STRIPE_PRICE_SQUARE_SYNC=$STRIPE_PRICE_SQUARE_SYNC STRIPE_PRICE_SMS_CAMPAIGNS=$STRIPE_PRICE_SMS_CAMPAIGNS RESEND_API_KEY=$RESEND_API_KEY FROM_EMAIL=$FROM_EMAIL MINIO_ENDPOINT=$MINIO_ENDPOINT MINIO_REGION=$MINIO_REGION MINIO_ACCESS_KEY=$MINIO_ACCESS_KEY MINIO_SECRET_KEY=$MINIO_SECRET_KEY MINIO_PUBLIC_URL=$MINIO_PUBLIC_URL MINIO_BUCKET_PRODUCTS=$MINIO_BUCKET_PRODUCTS MINIO_BUCKET_BRAND_LOGOS=$MINIO_BUCKET_BRAND_LOGOS MINIO_BUCKET_WATER_LOGS=$MINIO_BUCKET_WATER_LOGS MINIMAX_API_KEY=$MINIMAX_API_KEY MINIMAX_BASE_URL=$MINIMAX_BASE_URL CRON_SECRET=$CRON_SECRET ENVEOF # Sync build output to server using scp (no apt install needed) echo "Copying .next/..." scp -o ConnectTimeout=15 -o StrictHostKeyChecking=no -r .next/* tyler@route.crispygoat.com:$APP_DIR/.next/ echo "Copying public/..." scp -o ConnectTimeout=15 -o StrictHostKeyChecking=no -r public/* tyler@route.crispygoat.com:$APP_DIR/public/ echo "Copying package.json..." scp -o ConnectTimeout=15 -o StrictHostKeyChecking=no package.json tyler@route.crispygoat.com:$APP_DIR/ scp -o ConnectTimeout=15 -o StrictHostKeyChecking=no next.config.ts tyler@route.crispygoat.com:$APP_DIR/ 2>/dev/null || true # Install deps and restart on server echo "Installing deps and restarting PM2..." ssh -o ConnectTimeout=15 -o StrictHostKeyChecking=no tyler@route.crispygoat.com "cd $APP_DIR && npm install --omit=dev && pm2 restart route-commerce || pm2 start npm --name route-commerce -- start -- -p 3100 && pm2 save" echo "Deployed successfully"