From 122f7ea9d61086f404a5083dbb2cd31cf2e5646d Mon Sep 17 00:00:00 2001 From: Andrii Pasternak Date: Wed, 17 Jun 2026 23:46:13 +0100 Subject: [PATCH 1/2] fix(compose): forward INTERNAL_API_SECRET to prod mcp-server + CORS/SSH/LOG levers to prod backend (#722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #722's /validate-config report flagged 5 config issues; 3 were already fixed (GOOGLE_API_KEY, FRONTEND_URL dup, TRINITY_DATA_PATH). The 2 remaining are the "prod packaging gap" class: a var forwarded in docker-compose.yml but absent from docker-compose.prod.yml. There is no env_file: in either compose, so a missing environment: entry = a dead lever in prod. docker-compose.prod.yml: - mcp-server: add INTERNAL_API_SECRET (SEC-001, no default — matches backend/scheduler prod style). Corrected failure mode: without it, src/mcp-server/src/audit.ts postAudit early-returns (`if (!INTERNAL_SECRET) return;`) and ALL MCP tool-call audit is silently dropped in prod — no request is sent (so not "every audit 401s"). Backend + scheduler already receive the secret in prod, so forwarding it to mcp-server makes both ends share the same value (internal.py returns 403 only on a *mismatch*). - backend: add EXTRA_CORS_ORIGINS (config.py), SSH_HOST (ssh_service.py), and LOG_ARCHIVE_ENABLED/LOG_RETENTION_DAYS/LOG_CLEANUP_HOUR (log_archive_service.py). LOG_ARCHIVE_PATH is intentionally omitted — the code default /data/archives lands in the TRINITY_DATA_PATH bind mount (/data), so no new volume is needed. src/frontend/src/components/HostTelemetry.vue: - Drop the dead `import.meta.env.VITE_API_BASE` reference (the var is never set anywhere, so API_BASE was always ''); hardcode `API_BASE = ''` to match api.js `baseURL: ''` (relative, same-origin). Zero behavior change. The naive "point it at VITE_API_URL" fix would be a regression (VITE_API_URL build-defaults to http://localhost:8000). Verification: `docker compose -f docker-compose.prod.yml config` and the +enterprise overlay both render clean; the 6 added vars resolve into the correct service blocks (no cross-block leak); `vite build` passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.prod.yml | 22 +++++++++++++++++++ src/frontend/src/components/HostTelemetry.vue | 6 ++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 941deb522..acc9e86c3 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -97,6 +97,10 @@ services: - PUBLIC_CHAT_URL=${PUBLIC_CHAT_URL:-} # Frontend URL for post-OAuth redirects - FRONTEND_URL=${FRONTEND_URL:-} + # CORS — comma-separated extra allowed origins (read by config.py via + # EXTRA_CORS_ORIGINS). Mirrors docker-compose.yml; without this line the + # .env value is inert in prod (the #1039 packaging-gap class). + - EXTRA_CORS_ORIGINS=${EXTRA_CORS_ORIGINS:-} # Backend's own public origin — used to build OAuth redirect URIs # ({BACKEND_URL}/api/oauth/{provider}/callback, read by config.py). Mirrors # docker-compose.yml; without this line the .env value is inert in prod (the @@ -121,6 +125,18 @@ services: # packaging-gap class). - TELEMETRY_CONTAINER_STATS_TTL=${TELEMETRY_CONTAINER_STATS_TTL:-10} - TELEMETRY_DOCKER_POOL_SIZE=${TELEMETRY_DOCKER_POOL_SIZE:-16} + # SSH access host override (read by services/ssh_service.py). Mirrors + # docker-compose.yml; without this line the .env value is inert in prod (the + # #1039 packaging-gap class) and SSH host detection falls back to localhost. + - SSH_HOST=${SSH_HOST:-} + # Log retention & archival (services/log_archive_service.py). Mirrors + # docker-compose.yml; without these lines the .env knobs are inert in prod + # (the #1039 packaging-gap class). LOG_ARCHIVE_PATH is intentionally omitted: + # the code default (/data/archives) already lands in the TRINITY_DATA_PATH + # bind mount (/data), so no new volume is needed. + - LOG_ARCHIVE_ENABLED=${LOG_ARCHIVE_ENABLED:-true} + - LOG_RETENTION_DAYS=${LOG_RETENTION_DAYS:-90} + - LOG_CLEANUP_HOUR=${LOG_CLEANUP_HOUR:-3} volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./config/agent-templates:/agent-configs/templates:ro @@ -364,6 +380,12 @@ services: # Fail-loud at compose render rather than silently use a well-known fallback (issue #692). - "TRINITY_PASSWORD=${ADMIN_PASSWORD:?ADMIN_PASSWORD must be set in .env}" - MCP_REQUIRE_API_KEY=${MCP_REQUIRE_API_KEY:-true} + # INTERNAL_API_SECRET (SEC-001) — shared secret for POST /api/internal/audit + # so MCP tool-call audit reaches the backend. Mirrors docker-compose.yml; + # without it audit.ts (postAudit) early-returns and ALL MCP tool-call audit + # is silently dropped in prod (the #1039 packaging-gap class). No default — + # matches the backend/scheduler prod style. + - INTERNAL_API_SECRET=${INTERNAL_API_SECRET} # No REDIS_URL: src/mcp-server/ (TypeScript) has zero Redis imports. # Don't hand a credential to a process that has no use for it. depends_on: diff --git a/src/frontend/src/components/HostTelemetry.vue b/src/frontend/src/components/HostTelemetry.vue index b15209444..7f53550d3 100644 --- a/src/frontend/src/components/HostTelemetry.vue +++ b/src/frontend/src/components/HostTelemetry.vue @@ -2,7 +2,11 @@ import { ref, onMounted, onUnmounted } from 'vue' import SparklineChart from './SparklineChart.vue' -const API_BASE = import.meta.env.VITE_API_BASE || '' +// Relative, same-origin API base (nginx/Vite proxy) — matches api.js baseURL: ''. +// Intentionally not an env var: VITE_API_BASE was never set anywhere, so this was +// always '' (see #722 — do not point it at VITE_API_URL, that build-defaults to +// http://localhost:8000 and would break same-origin calls). +const API_BASE = '' // History configuration: 60 samples at 5s intervals = 5 minutes const MAX_POINTS = 60 From d3a5842ef0790e662c08514f60bfc215b1f0a7f9 Mon Sep 17 00:00:00 2001 From: Andrii Pasternak Date: Thu, 18 Jun 2026 00:15:28 +0100 Subject: [PATCH 2/2] fix(compose): mount Vector logs read-only into prod backend so LOG_* archival runs (#722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PR forwarded LOG_ARCHIVE_ENABLED/LOG_RETENTION_DAYS/LOG_CLEANUP_HOUR to the prod backend, but archival still no-opped: log_archive_service.py reads LOG_DIR=/data/logs, and the prod backend mounted only the trinity-data bind mount at /data, never Vector's trinity-logs volume (dev mounts it at docker-compose.yml:145). Add the same read-only mount so the forwarded knobs actually take effect — completing the #1039 packaging-gap fix one layer down. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.prod.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index acc9e86c3..736632f07 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -144,6 +144,10 @@ services: - ./config/process-docs:/app/config/process-docs:ro - agent-configs:/agent-configs - ${TRINITY_DATA_PATH:-./trinity-data}:/data + # Read-only access to Vector's logs so log_archive_service.py (reads + # /data/logs) can run in prod. Mirrors docker-compose.yml; without it the + # forwarded LOG_* knobs are inert (LOG_DIR not found) — the #1039 class. + - trinity-logs:/data/logs:ro depends_on: redis: condition: service_healthy