From 2b8a599b68d03fd49185c106825de1240f2d477d Mon Sep 17 00:00:00 2001 From: abhishek Date: Thu, 27 Aug 2026 15:24:18 +0530 Subject: [PATCH] nginx fix --- Dockerfile | 44 +++++----- nginx.conf => _to_delete/old-nginx.conf | 0 nginx.conf.template | 102 ++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 20 deletions(-) rename nginx.conf => _to_delete/old-nginx.conf (100%) create mode 100644 nginx.conf.template diff --git a/Dockerfile b/Dockerfile index 0859b36..bf0cec4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,41 @@ -# Stage 1: Build the React application +# Stage 1 — build FROM node:22-alpine AS builder WORKDIR /app -# Copy package.json and lockfile COPY package*.json ./ +RUN npm ci || npm install -# Install dependencies -RUN npm install - -# Copy the rest of your application code COPY . . - -# Build the Vite application (this creates the /app/dist folder inside the container) RUN npm run build -# Stage 2: Serve the application with Nginx +# Stage 2 — serve FROM nginx:alpine -# Set the default Hasura admin secret (can be overridden at runtime) -ENV HASURA_ADMIN_SECRET="nearle-admin-secret" +# The config is a TEMPLATE, and that is deliberate. +# +# nginx:alpine's entrypoint runs `envsubst` over /etc/nginx/templates/*.template +# at container start and writes the result into conf.d. That is how the ingest +# API key reaches nginx as a runtime environment variable rather than being +# committed here in plain text — which is what the previous Dockerfile did with +# the Hasura secret, on the line this replaces. +COPY nginx.conf.template /etc/nginx/templates/default.conf.template -# Move to Nginx's public folder -WORKDIR /usr/share/nginx/html +# Restricts substitution to this one name. +# +# Without the filter, envsubst replaces every `${...}` it recognises as an +# environment variable — and the container's environment carries HOSTNAME, PATH +# and friends. Nginx's own `$uri`, `$remote_addr` and `$proxy_add_x_forwarded_for` +# would survive that today, but only by luck, and a config silently rewritten at +# boot is a bad thing to leave to luck. +ENV NGINX_ENVSUBST_FILTER=INGEST_TOKEN -# CRUCIAL: Remove Nginx's default "Welcome" page files completely -RUN rm -rf ./* +# Empty by default, so the image runs without it. Sheet upload then fails with +# the ingest service's own 401, which says what is missing — rather than nginx +# refusing to start and taking the whole console down with it. +ENV INGEST_TOKEN="" -# Copy the compiled static assets FROM THE BUILDER STAGE -COPY --from=builder /app/dist/ . - -# Copy your custom Nginx configuration into the conf.d directory so it gets included properly -COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/dist/ /usr/share/nginx/html/ EXPOSE 80 diff --git a/nginx.conf b/_to_delete/old-nginx.conf similarity index 100% rename from nginx.conf rename to _to_delete/old-nginx.conf diff --git a/nginx.conf.template b/nginx.conf.template new file mode 100644 index 0000000..6818ff7 --- /dev/null +++ b/nginx.conf.template @@ -0,0 +1,102 @@ +# Nginx for the deployed console. +# +# A `.template`, not a plain conf: the nginx:alpine entrypoint runs `envsubst` +# over everything in /etc/nginx/templates and writes the result into conf.d at +# container start. That is what lets the ingest API key arrive as a runtime +# environment variable instead of being committed to this repository. +# +# ── Why this file exists in this shape ─────────────────────────────────────── +# +# The previous version was inherited from the old console and proxied `/hasura/` +# — a path this console never calls — while having no block for `/fiesta/` at +# all. Every API call therefore fell through to `try_files … /index.html`, and +# nginx answers a POST to a static file with **405 Method Not Allowed** and an +# HTML body. The console reported "Malformed response (HTTP 405)", which was +# accurate and pointed nowhere near the cause: sign-in was never reaching the +# backend. +# +# The rule this file follows: every prefix the Vite dev server proxies must have +# a matching block here. `vite.config.ts` is the other half of this file, and +# the two drift apart silently — it works on every developer machine and fails +# only once deployed. + +server { + listen 80; + server_name _; + + # 10 MB is the ingest service's own file limit, so anything larger is going + # to be refused anyway — but nginx's default is 1 MB, and it rejects the + # upload itself with a 413 before the request ever leaves this container. + # A merchant's product sheet passes 1 MB easily. + client_max_body_size 12m; + + # ── The app ────────────────────────────────────────────────────────────── + location / { + root /usr/share/nginx/html; + index index.html; + # React Router owns the paths, so an unknown one is a route, not a 404. + try_files $uri $uri/ /index.html; + } + + # Hashed filenames, so these can be cached hard. index.html must NOT be, + # or a deploy leaves people on the previous bundle until they force-reload. + location /assets/ { + root /usr/share/nginx/html; + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # ── Fiesta ─────────────────────────────────────────────────────────────── + # + # Mirrors the dev proxy exactly: `/fiesta/live/api/...` → `/live/api/...` on + # fiesta.nearle.app. The trailing slash on proxy_pass is what strips the + # prefix — without it the upstream receives `/fiesta/live/...` and 404s. + # + # Proxied rather than called directly from the browser so the API stays + # same-origin. That keeps CORS out of the picture and means the deployed + # console and a developer's machine take the same code path. + location /fiesta/ { + proxy_pass https://fiesta.nearle.app/; + proxy_ssl_server_name on; + proxy_set_header Host fiesta.nearle.app; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + } + + # ── Catalogue ingest ───────────────────────────────────────────────────── + # + # The API key is attached HERE, by nginx, from an environment variable set + # on the container. It never reaches the browser — which matters more than + # usual for this one: the key carries `require_admin` on that service, which + # is a superuser, so the same key also reaches /api/catalog/generate and + # /api/system/init. A key compiled into the JavaScript bundle is a key + # handed to every visitor. + # + # This also settles CORS. The owning team's allow-list does not include this + # console's origin and should not need to: the browser only ever talks to + # its own host, and this container makes the cross-origin call. + location /ingest/ { + proxy_pass https://mcp.nearle.ai.in/; + proxy_ssl_server_name on; + proxy_set_header Host mcp.nearle.ai.in; + proxy_set_header X-API-Key "${INGEST_TOKEN}"; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + + # Ingest submits return 202 immediately, but a sheet near the size limit + # takes a moment to upload and the service can be slow to accept it. + proxy_read_timeout 300s; + proxy_send_timeout 300s; + # Send the upload straight through rather than spooling it to disk + # first — nginx would otherwise buffer the whole workbook before the + # upstream saw a byte. + proxy_request_buffering off; + } + + # The `/hasura/` block that used to be here is gone. It belonged to the old + # console (daily_merchant_web) and nothing in this app has ever called it — + # it also carried a Hasura admin secret hardcoded in plain text, committed + # to the repository. That secret should be rotated. +}