From 94508eaa9fabd6f0f00de28965dbd561cd4c1b9f Mon Sep 17 00:00:00 2001 From: hafzism Date: Sun, 3 May 2026 03:11:35 +0530 Subject: [PATCH 1/2] fix: rsync --relative to preserve nginx dir structure, suppress Node 20 warning --- .github/workflows/cd.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e2ada63..2f3ebc5 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -7,8 +7,9 @@ on: env: REGISTRY: ghcr.io - # e.g. ghcr.io/devxtra-community/hayon IMAGE_BASE: ghcr.io/${{ github.repository_owner }}/hayon + # Opt into Node.js 24 to suppress deprecation warnings from actions + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: # ============================================================ @@ -114,16 +115,20 @@ jobs: chmod 600 ~/.ssh/ec2.pem ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts - # ---- Sync config files (compose, nginx, rabbitmq config) ---- - # We only need non-built files. Source code is NOT needed on EC2. + # ---- Sync config files to EC2 ---- + # --relative preserves full directory structure: + # nginx/nginx.conf → $APP_DIR/nginx/nginx.conf ✅ + # rabbitmq/enabled_plugins → $APP_DIR/rabbitmq/enabled_plugins ✅ + # Without --relative, a trailing slash on nginx/ strips the folder name + # and dumps nginx.conf directly into $APP_DIR/ — that's what caused the error. - name: Sync config files to EC2 run: | - rsync -avz \ + rsync -avz --relative \ -e "ssh -i ~/.ssh/ec2.pem" \ - docker-compose.prod.yml \ - nginx/ \ - rabbitmq/enabled_plugins \ - scripts/ \ + ./docker-compose.prod.yml \ + ./nginx/nginx.conf \ + ./rabbitmq/enabled_plugins \ + ./scripts/ \ ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${{ secrets.APP_DIR }}/ # ---- Write backend .env ---- From 775590f473cb1addda6889a474ef55c90f990cfd Mon Sep 17 00:00:00 2001 From: hafzism Date: Sun, 3 May 2026 04:10:46 +0530 Subject: [PATCH 2/2] feat: add PostHog env vars to build pipeline; fix S3 image hostname --- .github/workflows/cd.yml | 99 +++++++++++++++++++++------------------- docker-compose.yml | 2 + frontend/Dockerfile | 4 ++ frontend/next.config.ts | 9 ++++ nginx/nginx.conf | 31 +++++++------ 5 files changed, 85 insertions(+), 60 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 2f3ebc5..74c21ab 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -84,6 +84,8 @@ jobs: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} NEXT_PUBLIC_FIREBASE_APP_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} + NEXT_PUBLIC_POSTHOG_KEY=${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }} + NEXT_PUBLIC_POSTHOG_HOST=${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }} # ---- Build & push custom RabbitMQ image ---- - name: Build and push rabbitmq @@ -132,61 +134,66 @@ jobs: ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:${{ secrets.APP_DIR }}/ # ---- Write backend .env ---- + # Using printf per line instead of heredoc to avoid YAML-indentation + # leaking as leading spaces into the .env file. - name: Write backend .env on EC2 run: | ssh -i ~/.ssh/ec2.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} bash << 'ENDSSH' mkdir -p ${{ secrets.APP_DIR }}/backend - cat > ${{ secrets.APP_DIR }}/backend/.env << 'EOF' - NODE_ENV=production - PORT=5000 - FRONTEND_URL=${{ secrets.FRONTEND_URL }} - BACKEND_URL=${{ secrets.BACKEND_URL }} - GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} - GOOGLE_CALLBACK_URL=${{ secrets.GOOGLE_CALLBACK_URL }} - MONGODB_URI=${{ secrets.MONGODB_URI }} - ACCESS_TOKEN_SECRET=${{ secrets.ACCESS_TOKEN_SECRET }} - REFRESH_TOKEN_SECRET=${{ secrets.REFRESH_TOKEN_SECRET }} - JWT_EXPIRES_IN=7d - STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }} - STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PUBLISHABLE_KEY }} - STRIPE_PRO_PRICE_ID=${{ secrets.STRIPE_PRO_PRICE_ID }} - STRIPE_WEBHOOK_SECRET=${{ secrets.STRIPE_WEBHOOK_SECRET }} - EMAIL_USER=${{ secrets.EMAIL_USER }} - EMAIL_PASS=${{ secrets.EMAIL_PASS }} - AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION=${{ secrets.AWS_REGION }} - AWS_S3_BUCKET_NAME=${{ secrets.AWS_S3_BUCKET_NAME }} - TUMBLR_CONSUMER_KEY=${{ secrets.TUMBLR_CONSUMER_KEY }} - TUMBLR_CONSUMER_SECRET=${{ secrets.TUMBLR_CONSUMER_SECRET }} - META_APP_ID=${{ secrets.META_APP_ID }} - META_APP_SECRET=${{ secrets.META_APP_SECRET }} - META_REDIRECT_URI=${{ secrets.META_REDIRECT_URI }} - THREADS_APP_ID=${{ secrets.THREADS_APP_ID }} - THREADS_APP_SECRET=${{ secrets.THREADS_APP_SECRET }} - THREADS_REDIRECT_URI=${{ secrets.THREADS_REDIRECT_URI }} - MASTODON_CALLBACK_URL=${{ secrets.MASTODON_CALLBACK_URL }} - MASTODON_CLIENT_KEY=${{ secrets.MASTODON_CLIENT_KEY }} - MASTODON_CLIENT_SECRET=${{ secrets.MASTODON_CLIENT_SECRET }} - MASTODON_INSTANCE_URL=https://mastodon.social - RABBITMQ_URL=amqp://${{ secrets.RABBITMQ_USER }}:${{ secrets.RABBITMQ_PASS }}@rabbitmq:5672 - REDIS_HOST=redis - REDIS_PORT=6379 - GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }} - BETTER_STACK_TOKEN=${{ secrets.BETTER_STACK_TOKEN }} - EOF + ENV_FILE="${{ secrets.APP_DIR }}/backend/.env" + printf '%s\n' \ + 'NODE_ENV=production' \ + 'PORT=5000' \ + 'FRONTEND_URL=${{ secrets.FRONTEND_URL }}' \ + 'BACKEND_URL=${{ secrets.BACKEND_URL }}' \ + 'GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}' \ + 'GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}' \ + 'GOOGLE_CALLBACK_URL=${{ secrets.GOOGLE_CALLBACK_URL }}' \ + 'MONGODB_URI=${{ secrets.MONGODB_URI }}' \ + 'ACCESS_TOKEN_SECRET=${{ secrets.ACCESS_TOKEN_SECRET }}' \ + 'REFRESH_TOKEN_SECRET=${{ secrets.REFRESH_TOKEN_SECRET }}' \ + 'JWT_EXPIRES_IN=7d' \ + 'STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }}' \ + 'STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PUBLISHABLE_KEY }}' \ + 'STRIPE_PRO_PRICE_ID=${{ secrets.STRIPE_PRO_PRICE_ID }}' \ + 'STRIPE_WEBHOOK_SECRET=${{ secrets.STRIPE_WEBHOOK_SECRET }}' \ + 'EMAIL_USER=${{ secrets.EMAIL_USER }}' \ + 'EMAIL_PASS=${{ secrets.EMAIL_PASS }}' \ + 'AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}' \ + 'AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}' \ + 'AWS_REGION=${{ secrets.AWS_REGION }}' \ + 'AWS_S3_BUCKET_NAME=${{ secrets.AWS_S3_BUCKET_NAME }}' \ + 'TUMBLR_CONSUMER_KEY=${{ secrets.TUMBLR_CONSUMER_KEY }}' \ + 'TUMBLR_CONSUMER_SECRET=${{ secrets.TUMBLR_CONSUMER_SECRET }}' \ + 'META_APP_ID=${{ secrets.META_APP_ID }}' \ + 'META_APP_SECRET=${{ secrets.META_APP_SECRET }}' \ + 'META_REDIRECT_URI=${{ secrets.META_REDIRECT_URI }}' \ + 'THREADS_APP_ID=${{ secrets.THREADS_APP_ID }}' \ + 'THREADS_APP_SECRET=${{ secrets.THREADS_APP_SECRET }}' \ + 'THREADS_REDIRECT_URI=${{ secrets.THREADS_REDIRECT_URI }}' \ + 'MASTODON_CALLBACK_URL=${{ secrets.MASTODON_CALLBACK_URL }}' \ + 'MASTODON_CLIENT_KEY=${{ secrets.MASTODON_CLIENT_KEY }}' \ + 'MASTODON_CLIENT_SECRET=${{ secrets.MASTODON_CLIENT_SECRET }}' \ + 'MASTODON_INSTANCE_URL=https://mastodon.social' \ + 'RABBITMQ_URL=amqp://${{ secrets.RABBITMQ_USER }}:${{ secrets.RABBITMQ_PASS }}@rabbitmq:5672' \ + 'REDIS_HOST=redis' \ + 'REDIS_PORT=6379' \ + 'GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }}' \ + 'BETTER_STACK_TOKEN=${{ secrets.BETTER_STACK_TOKEN }}' \ + > "$ENV_FILE" + echo "backend .env written:" + cat "$ENV_FILE" ENDSSH - # ---- Write root .env (registry + rabbitmq creds for compose) ---- + # ---- Write root .env (IMAGE_BASE + rabbitmq creds for compose) ---- - name: Write root .env on EC2 run: | ssh -i ~/.ssh/ec2.pem ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} bash << 'ENDSSH' - cat > ${{ secrets.APP_DIR }}/.env << 'EOF' - RABBITMQ_USER=${{ secrets.RABBITMQ_USER }} - RABBITMQ_PASS=${{ secrets.RABBITMQ_PASS }} - IMAGE_BASE=ghcr.io/${{ github.repository_owner }}/hayon - EOF + printf '%s\n' \ + 'RABBITMQ_USER=${{ secrets.RABBITMQ_USER }}' \ + 'RABBITMQ_PASS=${{ secrets.RABBITMQ_PASS }}' \ + 'IMAGE_BASE=ghcr.io/${{ github.repository_owner }}/hayon' \ + > "${{ secrets.APP_DIR }}/.env" ENDSSH # ---- Pull pre-built images and restart ---- diff --git a/docker-compose.yml b/docker-compose.yml index 3a82549..e7328ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,6 +55,8 @@ services: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID} NEXT_PUBLIC_FIREBASE_APP_ID: ${NEXT_PUBLIC_FIREBASE_APP_ID} NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID} + NEXT_PUBLIC_POSTHOG_KEY: ${NEXT_PUBLIC_POSTHOG_KEY} + NEXT_PUBLIC_POSTHOG_HOST: ${NEXT_PUBLIC_POSTHOG_HOST} container_name: hayon_frontend expose: - "3000" diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 3fbb7cd..2b131c1 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -32,6 +32,8 @@ ARG NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET ARG NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID ARG NEXT_PUBLIC_FIREBASE_APP_ID ARG NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID +ARG NEXT_PUBLIC_POSTHOG_KEY +ARG NEXT_PUBLIC_POSTHOG_HOST ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL ENV NEXT_PUBLIC_VAPID_KEY=$NEXT_PUBLIC_VAPID_KEY @@ -42,6 +44,8 @@ ENV NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=$NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET ENV NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=$NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID ENV NEXT_PUBLIC_FIREBASE_APP_ID=$NEXT_PUBLIC_FIREBASE_APP_ID ENV NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=$NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID +ENV NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY +ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST RUN sed -i "s|NEXT_PUBLIC_FIREBASE_API_KEY_PLACEHOLDER|${NEXT_PUBLIC_FIREBASE_API_KEY}|g" /app/frontend/public/firebase-messaging-sw.js && \ diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 17102dc..2921d70 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -20,6 +20,15 @@ const nextConfig: NextConfig = { protocol: "https", hostname: "hayon-app-images.s3.ap-south-1.amazonaws.com", }, + // hayon-app-images-2 is the actual production bucket + { + protocol: "https", + hostname: "hayon-app-images-2.s3.amazonaws.com", + }, + { + protocol: "https", + hostname: "hayon-app-images-2.s3.ap-south-1.amazonaws.com", + }, { protocol: "https", hostname: "github.com", diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 769e3a8..e986531 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -17,16 +17,13 @@ http { gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - # -------------------------------------------------------- - # Upstream definitions - # -------------------------------------------------------- - upstream frontend { - server frontend:3000; - } - - upstream backend { - server backend:5000; - } + # Docker's internal DNS resolver. + # Forces nginx to re-resolve upstream hostnames dynamically on each request. + # Critical: when backend/frontend containers are recreated (new deploy), + # they get new internal IPs. Without this resolver + variable proxy_pass, + # nginx caches the old IP at startup and gets ECONNREFUSED → 502. + resolver 127.0.0.11 valid=10s ipv6=off; + resolver_timeout 5s; # -------------------------------------------------------- # HTTP → HTTPS redirect (handles ALL domains) @@ -82,7 +79,10 @@ http { client_max_body_size 50m; location / { - proxy_pass http://frontend; + # Variable-based proxy_pass forces nginx to use the resolver + # for DNS lookup on every request, not just at startup. + set $frontend_upstream http://frontend:3000; + proxy_pass $frontend_upstream; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; @@ -117,7 +117,8 @@ http { # All API routes location /api/ { - proxy_pass http://backend; + set $backend_upstream http://backend:5000; + proxy_pass $backend_upstream; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; @@ -131,7 +132,8 @@ http { # WebSocket (Socket.io) location /socket.io/ { - proxy_pass http://backend; + set $backend_upstream http://backend:5000; + proxy_pass $backend_upstream; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; @@ -144,7 +146,8 @@ http { # Health check location /health { - proxy_pass http://backend; + set $backend_upstream http://backend:5000; + proxy_pass $backend_upstream; proxy_set_header Host $host; } }