Phase 4 slice 3 - hosted build profile + SSE-safe HAProxy front template + deploy wiring (REQ-84/85 legs) #34
@@ -204,7 +204,10 @@ SSH "cd '$RELEASE_DIR/backend' && \
|
||||
docker build -t '$IMAGE_BACKEND:$VERSION' -t '$IMAGE_BACKEND:latest' ." \
|
||||
|| die "backend docker build failed on host" 4
|
||||
SSH "cd '$RELEASE_DIR/frontend' && \
|
||||
docker build -t '$IMAGE_NAME:$VERSION' -t '$IMAGE_NAME:latest' ." \
|
||||
docker build \
|
||||
--build-arg NEXT_PUBLIC_RESEARCHX_API_MODE=live \
|
||||
--build-arg NEXT_PUBLIC_API_BASE_URL=\"\" \
|
||||
-t '$IMAGE_NAME:$VERSION' -t '$IMAGE_NAME:latest' ." \
|
||||
|| die "frontend docker build failed on host" 4
|
||||
|
||||
# ---- swap container ------------------------------------------------------
|
||||
@@ -260,6 +263,17 @@ log "checking the backend can write its data root"
|
||||
SSH "docker exec '$CONTAINER_BACKEND' sh -c 'touch \"\$RESEARCHX_DATA_DIR/.write-probe\" && rm -f \"\$RESEARCHX_DATA_DIR/.write-probe\"'" \
|
||||
|| die "backend data root is not writable by the container user" 5
|
||||
|
||||
# Auth smoke (RESEARCH-v03.md section 10 pitfall; PLAN.md T-4.7(1) host-side
|
||||
# leg): the auth-guarded surface answers 401 unauthenticated. This proves
|
||||
# the guarded surface is up through the deployed backend -- it does NOT
|
||||
# discriminate the serving entrypoint (both create_app shapes default
|
||||
# auth_enabled=True; the entrypoint is pinned by the Dockerfile CMD + the
|
||||
# build-stage assert). The live-mode tier marker (an authenticated
|
||||
# /api/config) rides the staged smoke suite, owner directive (D-019).
|
||||
log "checking GET /api/config answers 401 unauthenticated"
|
||||
AUTH_CODE=$(SSH "curl -s -o /dev/null -w '%{http_code}' 'http://127.0.0.1:$HOST_PORT_BACKEND/api/config'")
|
||||
[ "$AUTH_CODE" = "401" ] || die "GET /api/config answered '$AUTH_CODE' unauthenticated, expected 401" 5
|
||||
|
||||
log "health-checking frontend http://$DEPLOY_HOST:$HOST_PORT$HEALTH_PATH (retries: $HEALTH_RETRIES)"
|
||||
i=0
|
||||
while [ "$i" -lt "$HEALTH_RETRIES" ]; do
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# research.coreci.dev — HAProxy front config for phase 4 (REQ-84, G-104)
|
||||
#
|
||||
# DOCUMENTATION + idempotent template (PLAN.md T-4.5, RESEARCH-v03.md §9):
|
||||
# the host already runs HAProxy 2.8 with the TLS frontend for
|
||||
# research.coreci.dev. This file is the reviewed shape to fold into that
|
||||
# front. It is applied to the live host ONLY on owner deploy directive
|
||||
# (D-019) — never autonomously from a deploy run. The host's live config
|
||||
# is not readable from the authoring seat, so `timeout client` /
|
||||
# `defaults` values below are stated as requirements, not as the
|
||||
# host's current numbers.
|
||||
|
||||
# ---- requirements the host's defaults must already satisfy ----------------
|
||||
# timeout client 3600s # MUST exceed the 15s sse-starlette ping
|
||||
# # interval (RESEARCH-v03.md §9) or idle SSE
|
||||
# # streams are cut mid-run.
|
||||
# keep-alive default ON # keep http-keep-alive; the SSE stream rides it.
|
||||
|
||||
# ---- frontend routes -------------------------------------------------------
|
||||
frontend https-in
|
||||
acl is_api path_beg /api
|
||||
acl is_health path /healthz
|
||||
use_backend backend_api if is_api or is_health
|
||||
default_backend frontend_next
|
||||
|
||||
# G-104 per-IP login rate limit (PLAN.md T-4.5 scope): stick-table on
|
||||
# /api/auth/*. Threshold below is a STARTING VALUE for the
|
||||
# security-engineer review, not a measured traffic figure — rate
|
||||
# before applying. 30 auth requests / 60s / IP.
|
||||
acl is_auth path_beg /api/auth
|
||||
stick-table type ip size 100k expire 60s store http_req_rate(60s)
|
||||
http-request track-sc0 src if is_auth
|
||||
http-request deny deny_status 429 if is_auth { sc_http_req_rate(0) gt 30 }
|
||||
|
||||
# ---- backend: the API container --------------------------------------------
|
||||
# SSE hazards (RESEARCH-v03.md §9): HAProxy 2.x does NOT buffer response
|
||||
# bodies in HTTP mode by default, so text/event-stream streams natively.
|
||||
# The three things that would break it are deliberately absent here:
|
||||
# - NO `option http-buffer-response` on this backend
|
||||
# - NO compression (gzip) on this backend — gzip buffers
|
||||
# text/event-stream; the "classic" silent SSE killer
|
||||
# - keep the sse-starlette ping (15s) untouched
|
||||
backend backend_api
|
||||
option httpchk GET /healthz
|
||||
default-server inter 5s fall 3 rise 2
|
||||
server api1 127.0.0.1:8000 check
|
||||
timeout server 3600s
|
||||
timeout tunnel 3600s
|
||||
|
||||
# ---- backend: the Next.js container ----------------------------------------
|
||||
backend frontend_next
|
||||
timeout server 300s
|
||||
server next1 127.0.0.1:3000 check
|
||||
|
||||
# ---- health contract -------------------------------------------------------
|
||||
# GET /healthz -> 200 {"status":"ok"} (public router, outside /api per
|
||||
# D-103; the only unauthenticated route — T-4.8).
|
||||
# The API container must publish on 127.0.0.1:8000 (loopback only: the
|
||||
# public surface is the TLS front, so the backend is never directly
|
||||
# reachable from outside the host).
|
||||
@@ -14,6 +14,23 @@ FROM node:22-alpine AS build
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Hosted build profile (PLAN.md T-4.3, RESEARCH-v03.md section 10): Next.js
|
||||
# inlines NEXT_PUBLIC_* at BUILD time, so the profile is ARG/ENV here --
|
||||
# never runtime flags. live mode + EMPTY base ("" = same-origin through the
|
||||
# front proxy's /api path route): a non-empty base would break the
|
||||
# same-origin session cookie behind the TLS front.
|
||||
ARG NEXT_PUBLIC_RESEARCHX_API_MODE=live
|
||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||
ENV NEXT_PUBLIC_RESEARCHX_API_MODE="${NEXT_PUBLIC_RESEARCHX_API_MODE}" \
|
||||
NEXT_PUBLIC_API_BASE_URL="${NEXT_PUBLIC_API_BASE_URL}"
|
||||
|
||||
# Build assert: the hosted profile is hardwired -- mode=live, empty base --
|
||||
# or the build fails before a wrong image can be produced.
|
||||
RUN [ "$NEXT_PUBLIC_RESEARCHX_API_MODE" = "live" ] \
|
||||
&& [ -z "$NEXT_PUBLIC_API_BASE_URL" ] \
|
||||
|| { echo "hosted build profile violated: mode=$NEXT_PUBLIC_RESEARCHX_API_MODE" >&2; exit 1; }
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN pnpm build
|
||||
|
||||
Reference in New Issue
Block a user