Skip to content

feat(docker): add container support#422

Open
hwcopeland wants to merge 1250 commits intokoala73:mainfrom
hwcopeland:main
Open

feat(docker): add container support#422
hwcopeland wants to merge 1250 commits intokoala73:mainfrom
hwcopeland:main

Conversation

@hwcopeland
Copy link

  • Multi-stage Dockerfile: node:20-alpine builder + nginx/node runner
  • Builder compiles sebuf RPC handlers (api/[domain]/v1/[rpc].ts → .js) so the local API sidecar can discover and load them at runtime
  • Runner stage pinned to linux/amd64 to avoid QEMU during npm build
  • docker/nginx.conf: serves Vite SPA, proxies /api/* to Node sidecar
  • docker/entrypoint.sh: starts local-api-server.mjs sidecar then nginx
  • deploy/k8s/worldmonitor.yaml: Deployment + Service + HTTPRoute + Secret template with generic placeholders

Type of change

New feature

koala73 and others added 30 commits February 19, 2026 08:23
…riants

WORLDMONITOR-28: Twitter in-app browser (iPad/iOS) injects CONFIG variable.
Existing filter used literal apostrophe which may miss Safari's U+2019.
Changed Can't → Can.t to match any apostrophe character.

Closes WORLDMONITOR-28
- Comprehensive README update: live webcams, ultra-wide layout, Linux
  AppImage, theme system, auto-updater, error tracking, responsive
  layout, virtual scrolling, 13 languages, and 8 new roadmap items
- Sentry triage: WORLDMONITOR-28 noise filter broadened for smart quotes
- Ultra-wide layout: CSS float L-shape for 2000px+ screens (koala73#114)
- Version bump: 2.4.0 → 2.4.1
- fixed incorrect spacing on diagram boxes and arrows
- formatted the tables
everything stays same, no visual changes
- WORLDMONITOR-2A: filter AbortError from fetch abort on navigation
- WORLDMONITOR-29: broaden maplibre _layers null crash pattern in beforeSend
- WORLDMONITOR-Q: filter stale dynamic import module errors (post-deploy 404s)
Follow llmstxt.org standard so LLMs can understand the project.
Concise version (~5KB) and extended version (~21KB) with full
data layers, sources, and architecture details.
Add Turkish (tr) as the 14th supported language with full translation
of all 1,134 locale keys, locale loader, tr-TR formatting, and flag
mapping. Update documentation to reflect 14 languages.
Move pattern from beforeSend extension-only check to ignoreErrors array.
All 16 events were iOS Safari with no stack trace — stale cached assets
after deploys, not actionable bugs.
…ions

- Add i18n keys for all 9 strategic posture theater names (Iran Theater,
  Baltic Theater, Taiwan Strait, etc.) across all 14 languages
- Wire StrategicPosturePanel to use t() for theater display names with
  fallback to English if key is missing
- Include webcam region button translations (ALL, MIDEAST, EUROPE, etc.)
  that were missing from deployed locale files

Fixes koala73#121
- CIIPanel: use existing t('components.cii.noSignals') for empty state
- CountryBriefPage: localize level badge and trend indicator labels
- ETFFlowsPanel: localize summary labels, table headers, net direction
- LiveWebcamsPanel: localize region filter tab labels
- MacroSignalsPanel: localize verdict, signal names, and bullish count
- StrategicRiskPanel: localize score levels, trend label, and trend values
Add Ollama as the primary summarization provider for desktop builds,
sitting before Groq/OpenRouter in the fallback chain. This enables
fully local, unlimited LLM inference via Ollama's OpenAI-compatible
endpoint (/v1/chat/completions).

Changes across six layers:
- runtime-config: OLLAMA_API_URL + OLLAMA_MODEL secret keys, aiOllama
  feature toggle (default on), URL validation
- sidecar: allowlist + endpoint probe validation (tries /v1/models
  then /api/tags)
- api/ollama-summarize.js: new handler mirroring Groq/OpenRouter with
  shared Redis cache keys
- summarization.ts: tryOllama() + updated chain order in normal, beta,
  and translation paths (Ollama → Groq → OpenRouter → Browser T5)
- RuntimeConfigPanel: signup URLs + i18n help text for new keys
- desktop-readiness: aiOllama in key-backed features + readiness check

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
…ayers

Three test files covering Ollama integration:

api/ollama-summarize.test.mjs (9 tests):
- Fallback signal when unconfigured, on API error, on empty response
- Success path with correct provider label and response shape
- Model selection via OLLAMA_MODEL env / default fallback
- Network error handling (ECONNREFUSED)
- Translate mode prompt verification

tests/summarization-chain.test.mjs (7 tests):
- Ollama success short-circuits chain (Groq never called)
- Ollama fail → Groq success fallback
- Full fallback when both unconfigured
- Provider label correctness for Ollama and Groq
- Uniform response shape across providers
- Identical fallback signal shapes

src-tauri/sidecar/local-api-server.test.mjs (8 new tests):
- OLLAMA_API_URL and OLLAMA_MODEL accepted via env-update allowlist
- Unknown keys rejected (403)
- Validation via /v1/models probe (reachable mock)
- Validation via /api/tags native fallback
- OLLAMA_MODEL pass-through validation
- Non-http protocol rejection (422)
- Auth-required behavior preserved with token

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
Server-side: extract shared CORS, validation, caching, prompt building,
and response shaping into api/_summarize-handler.js factory. Each
endpoint (Groq, OpenRouter, Ollama) becomes a thin wrapper calling
createSummarizeHandler() with a provider config: credentials, API URL,
model, headers, and provider label.

Client-side: replace three near-identical tryOllama/tryGroq/tryOpenRouter
functions with a single tryApiProvider() driven by an API_PROVIDERS
config array. Add runApiChain() helper that loops the chain with
progress callbacks. Simplify translateText() from three copy-pasted
blocks to a single loop over the same provider array.

- groq-summarize.js: 297 → 30 lines
- openrouter-summarize.js: 295 → 33 lines
- ollama-summarize.js: 289 → 34 lines
- summarization.ts: 336 → 239 lines
- New _summarize-handler.js: 315 lines (shared)
- Net: -566 lines of duplication removed

Adding a new LLM provider now requires only a provider config object
in the endpoint file + one entry in the API_PROVIDERS array.

Tests: 13 new tests for the shared factory (cache key, dedup, handler
creation, fallback, error casing, HTTP methods). All 42 existing tests
pass unchanged.

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
- Use for...of entries() instead of index-based loops in summarization.ts
  to satisfy strict noUncheckedIndexedAccess (7 TS18048/TS2345 errors)
- Replace fragile API_PROVIDERS[1] with .find(p => p.name === groq)
- Add OLLAMA_API_URL and OLLAMA_MODEL to SUPPORTED_SECRET_KEYS in main.rs
  so keychain secrets are injected into sidecar on desktop startup
…and Ollama UX

- Split settings window into 3 tabs: LLMs (Ollama/Groq/OpenRouter),
  API Keys (data feeds), and Debug & Logs
- Add featureFilter option to RuntimeConfigPanel for rendering subsets
- Consolidate keychain to single JSON vault entry (1 macOS prompt vs 20)
- Add Ollama model discovery with /api/tags + /v1/models fallback
- Strip <think> reasoning tokens from Ollama responses
- Suppress thinking with think:false in Ollama request body
- Parallel secret verification with 15s global timeout
- Fix manual model input overlapping dropdown (CSS grid-area + hidden-input class)
- Add loading spinners to settings tab panels
- Suppress notification popups when settings window is open
- Filter embed models from Ollama dropdown
- Fix settings window black screen flash with inline dark background
Add local LLM support mentions across feature descriptions, talking
points, screenshot suggestions, and changelog. New dedicated section
for Ollama/LM Studio as feature koala73#11.
DeckGLMap: guard updateLayers/debounce/raf against null maplibreMap,
null out references in destroy() to prevent post-destroy setProps crash.

main.ts: filter contentWindow.postMessage (Facebook WebView),
vertex shader compile (GPU driver), objectStoreNames (iOS background),
Unexpected identifier https (Safari 16), _0x obfuscated vars (extensions),
WKWebView deallocated (Tauri lifecycle).
…koala73#124)

## Summary

Extracts shared summarization logic (CORS, validation, caching, prompt
building) into a reusable `_summarize-handler.js` factory, then uses it
to add Ollama as the first provider in the fallback chain. This reduces
code duplication across Groq, OpenRouter, and the new Ollama endpoint
while maintaining identical behavior.

**Fallback chain is now:** Ollama → Groq → OpenRouter → Browser T5

## Type of change

- [x] New feature
- [x] Refactor / code cleanup
- [x] API endpoints (`/api/*`)

## Affected areas

- [x] AI Insights / World Brief
- [x] Desktop app (Tauri)
- [x] API endpoints (`/api/*`)
- [x] Config / Settings

## Changes

### New Files
- **`api/_summarize-handler.js`** – Shared handler factory with:
- `createSummarizeHandler(providerConfig)` – Creates edge handlers for
any LLM provider
- `getCacheKey()` – Stable cache key generation (extracted from
per-provider code)
- `deduplicateHeadlines()` – Headline deduplication logic (extracted
from per-provider code)
- Unified prompt building for brief/analysis/translate modes with
tech/full variants
  - CORS, validation, caching, and error handling pipeline

- **`api/ollama-summarize.js`** – New Ollama endpoint (34 lines):
- Calls local/remote Ollama instance via OpenAI-compatible
`/v1/chat/completions`
  - Reads `OLLAMA_API_URL` and `OLLAMA_MODEL` from environment
  - Shares Redis cache with Groq/OpenRouter (same cache key strategy)
  - Returns fallback signal when unconfigured or API fails

- **`api/ollama-summarize.test.mjs`** – Unit tests for Ollama endpoint:
  - Fallback signal when `OLLAMA_API_URL` not configured
  - Success response with provider="ollama"
  - Error handling (API errors, empty responses)
  - Model selection via `OLLAMA_MODEL` env

- **`api/_summarize-handler.test.mjs`** – Unit tests for shared factory:
  - Cache key stability and variation by mode/variant/lang/geoContext
  - Headline deduplication logic
  - Handler creation with missing credentials
  - API provider calls and response shaping

- **`tests/summarization-chain.test.mjs`** – Integration tests for
fallback chain:
  - Ollama success short-circuits (no downstream calls)
  - Ollama failure → Groq success
  - Both fail → fallback signals propagate

### Modified Files

**`api/groq-summarize.js`** & **`api/openrouter-summarize.js`**
- Replaced ~290 lines of duplicated logic with single call to
`createSummarizeHandler()`
- Now thin wrappers: 26 lines (Groq) and 28 lines (OpenRouter)
- Identical behavior, zero functional changes

**`src/services/summarization.ts`**
- Updated fallback chain: `Ollama → Groq → OpenRouter → Browser T5`
- Refactored `tryGroq()` and `tryOpenRouter()` into unified
`tryApiProvider()` function
- Added `API_PROVIDERS` config array for provider ordering
- Updated `SummarizationProvider` type to include `'ollama'`

**`src-tauri/sidecar/local-api-server.mjs`**
- Added `OLLAMA_API_URL` and `OLLAMA_MODEL` to `ALLOWED_ENV_KEYS`
- Added validation for `OLLAMA_API_URL` in
`validateSecretAgainstProvider()`:
  - Probes `/v1/models` (OpenAI-compatible endpoint)
  - Falls back to `/api/tags` (native Ollama endpoint)
- Returns "Ollama endpoint verified" or "Ollama endpoint verified
(native API
…buffers

Toggling cables/pipelines off then on caused deck.gl assertion failure
because the cached PathLayer had its WebGL resources destroyed on removal.
…keychain vault

- Ollama/LM Studio integration with auto model discovery and 4-tier fallback chain
- Settings window split into LLMs, API Keys, and Debug tabs
- Consolidated keychain vault (1 OS prompt instead of 20+)
- README expanded with privacy architecture, summarization chain docs
- CHANGELOG updated with full v2.5.0 release notes
- 5 new defense/intel RSS feeds, Koeberg nuclear plant added
koala73 and others added 24 commits February 26, 2026 08:53
…sabled (koala73#390)

mlWorker.init() was called unconditionally in App.init(), ignoring the
browserModel setting. This caused ONNX model downloads, 120s embed
timeouts, and clustering warnings even with the toggle OFF.

- Gate mlWorker.init() on browserModel setting (or desktop runtime)
- Subscribe to setting changes for dynamic init/terminate on toggle
- Replace mlWorker.init() in sentiment-gate with isAvailable check
- Clean up subscription in App.destroy() to prevent HMR listener leaks
…ature gate (koala73#391)

- Replace non-existent BDIY (Baltic Dry Index) FRED series with real
  public series: PCU483111483111 (Deep Sea Freight PPI) and TSIFRGHT
  (Freight Transportation Index). BDIY is proprietary Baltic Exchange
  data not available on FRED — all shipping requests were failing.
- Make "upstream unavailable" banner per-tab instead of global — shipping
  failure no longer shows the warning on chokepoints and minerals tabs
  that have valid data.
- Remove inconsistent isFeatureAvailable('supplyChain') gate from
  frontend fetchShippingRates() — server handler already returns empty
  when FRED_API_KEY is missing, and chokepoints/minerals had no gate.
… filters (koala73#393)

PR koala73#382 accidentally removed the maplibregl.FilterSpecification type
cast, causing tsc to infer (string | string[])[] which doesn't satisfy
setFilter's parameter type. This broke the Vercel build.
…#392)

- Add to tech variant security feed group
- Add to main variant global feeds as type: 'cyber'
- Add www.ransomware.live to RSS proxy allowlist
- Set source tier to 3 (specialty)
Runs npm run typecheck (tsc --noEmit) on every PR to catch type
errors before merge. Would have prevented the koala73#382 regression.
…73#395)

Linux AppImage (koala73#370, koala73#257):
- Upgrade CI from Ubuntu 22.04 to 24.04 (GLib 2.80 fixes g_task_set_static_name symbol mismatch)
- Set GDK_BACKEND=wayland,x11 when WAYLAND_DISPLAY detected (fixes GTK init on niri/river)
- Disable WebKit bubblewrap sandbox in AppImage context (FUSE mount breaks it → blank screen)
- Set GTK_IM_MODULE to built-in simple context in AppImage (prevents host module conflicts)

RSS proxy:
- Add 32 missing domains to allowlist (EuroNews lang variants, international news feeds)
- Normalize www prefix in domain validation (prevents entire class of www/non-www mismatch 403s)

Console cleanup:
- TrendingKeywords: console.log → console.debug for suppressed terms (30+ noisy log lines)
- PostHog: add ui_host for reverse proxy setup (fixes /ingest/ 404s)
…73#396)

Add a "Streaming" section to the GENERAL settings tab with a quality
dropdown (Auto / Low 360p / Medium 480p / High / HD 720p). The setting
persists to localStorage and applies to all live streams:

- LiveWebcamsPanel: appends vq= to direct embeds, passes through proxy
- LiveNewsPanel: sets quality via YT.Player API onReady + desktop proxy
- YouTube embed proxy: accepts vq param, calls setPlaybackQuality()

Closes koala73#365
…koala73#400)

* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns
…ala73#399)

* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns

* fix: sort supply chain chokepoints by disruption score descending
* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns

* chore: bump version to 2.5.9 and make pre-push hook executable

* docs: update README with supply chain intel, universal CII, Happy Monitor, security hardening, and recent features
…oise filters (koala73#402)

- CSS.escape() on data-news-id querySelector to prevent SyntaxError
  when news item IDs contain special characters (WORLDMONITOR-5J)
- typeof guard on this.player.destroy() for partially-initialized
  YouTube players (WORLDMONITOR-5C/5B)
- 11 new ignoreErrors patterns for IndexedDB races, browser vendor
  internals, and extension-injected errors
- beforeSend filter for blob: URL extension frames
…7 locales (koala73#403)

The header.settings i18n key was incorrectly set to "PANELS" (and its
translations) for the settings modal title. The modal contains General,
Panels, and Sources tabs — the overall title should be "SETTINGS".
…3#404)

* fix: sequential Yahoo calls, sector fallback, and missing try-catch guards

- list-commodity-quotes: replace Promise.all with fetchYahooQuotesBatch to prevent Yahoo 429
- get-sector-summary: add Yahoo Finance fallback when FINNHUB_API_KEY is missing
- list-etf-flows: sequential fetch loop + add missing try-catch around cachedFetchJson
- get-macro-signals: replace unnecessary Promise.allSettled([single]) with direct await

* fix: tighten AI summary prompts to 2 sentences / 60 words max

Summaries were often verbose walls of text despite "2-3 sentences" in
the prompt. Changed to "2 concise sentences MAX (under 60 words total)"
across all brief, analysis, and fallback prompts. Reduced max_tokens
from 150 to 100 to hard-cap output length.
…uards (koala73#406)

- list-commodity-quotes: replace Promise.all with fetchYahooQuotesBatch to prevent Yahoo 429
- get-sector-summary: add Yahoo Finance fallback when FINNHUB_API_KEY is missing
- list-etf-flows: sequential fetch loop + add missing try-catch around cachedFetchJson
- get-macro-signals: replace unnecessary Promise.allSettled([single]) with direct await
…sistence (koala73#407)

- Show rate-limited message instead of generic "Failed to load" on Markets,
  ETF, Commodities, and Sector panels when Yahoo returns 429
- fetchYahooQuotesBatch returns rateLimited flag; early-exit after 3 misses
- ETF panel skips retry loop when rate-limited, shows specific i18n message
- Fallback Finnhub symbols through Yahoo when API key missing
- 401-retry in runtime fetch patch for stale sidecar token after restart
- diagFetch auth helper for settings window diagnostic endpoints
- Verbose toggle writes to writable dataDir instead of read-only app bundle
* chore: bump v2.5.10 and update README for recent fixes

Version 2.5.9 → 2.5.10. Roadmap entries for:
- Yahoo Finance rate-limit UX across all market panels
- Sidecar auth resilience (401-retry, settings diagFetch)
- Verbose toggle persistence to writable data directory
- Finnhub-to-Yahoo fallback routing

* chore: add v2.5.10 changelog entry
…oala73#347) (koala73#410)

showOfflineMessage() and showEmbedError() replaced innerHTML without
cleaning up this.player, so initializePlayer() bailed early on the
next channel switch — leaving a black screen. destroyPlayer() is
idempotent so the onError path won't double-destroy.

Co-authored-by: N Cho-chin <Niboshi-Wasabi@users.noreply.github.com>
Fixes:
- Linux AppImage black screen on WebKit/GStreamer (koala73#411)
- Destroy live news player before showing offline/error message (koala73#410)
Launch the built AppImage under Xvfb after the Linux build to catch
startup crashes and render failures automatically. Uploads a screenshot
artifact for visual inspection.
…oala73#414)

The squash merge of koala73#413 put smoke test steps into build-desktop.yml
instead of the intended standalone workflow. This commit:
- Reverts build-desktop.yml to its original state
- Adds test-linux-app.yml as a separate Linux-only smoke test workflow
- Multi-stage Dockerfile: node:20-alpine builder + nginx/node runner
- Builder compiles sebuf RPC handlers (api/[domain]/v1/[rpc].ts → .js)
  so the local API sidecar can discover and load them at runtime
- Runner stage pinned to linux/amd64 to avoid QEMU during npm build
- docker/nginx.conf: serves Vite SPA, proxies /api/* to Node sidecar
- docker/entrypoint.sh: starts local-api-server.mjs sidecar then nginx
- deploy/k8s/worldmonitor.yaml: Deployment + Service + HTTPRoute + Secret
  template with generic placeholders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 26, 2026 15:48
@vercel
Copy link

vercel bot commented Feb 26, 2026

@hwcopeland is attempting to deploy a commit to the Elie Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive Docker containerization support for World Monitor, enabling deployment beyond the existing Vercel serverless architecture. The implementation provides a multi-stage Docker build that packages the Vite frontend with nginx and runs a Node.js API sidecar (repurposed from the Tauri desktop app) to handle the 60+ API endpoints locally instead of relying on Vercel Edge Functions.

Changes:

  • Multi-stage Dockerfile with node:20-alpine builder and nginx+node runner, compiling sebuf RPC handlers for runtime discovery
  • nginx configuration serving the SPA, proxying /api/* to the Node.js sidecar on port 3001, and relaying PostHog analytics through /ingest/
  • Entrypoint script that starts the local-api-server.mjs sidecar in the background before launching nginx in foreground mode
  • Kubernetes manifests (Deployment, Service, HTTPRoute, Secret) providing a production-ready template with configurable API keys and resource limits

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 16 comments.

File Description
Dockerfile Multi-stage build: builder compiles sebuf handlers and Vite SPA; runner combines nginx + Node.js sidecar with platform pinning to linux/amd64
docker/nginx.conf nginx server config with gzip compression, 1-year static asset caching, /api/ proxy to localhost:3001, and PostHog analytics relay
docker/entrypoint.sh Container startup script that launches the API sidecar (local-api-server.mjs) then nginx in daemon-off mode
deploy/k8s/worldmonitor.yaml K8s deployment template with Secret for API keys, Deployment with 512Mi memory limit, Service, and Gateway API HTTPRoute

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

proxy_pass https://us.i.posthog.com/;
proxy_http_version 1.1;
proxy_set_header Host us.i.posthog.com;
proxy_ssl_server_name on;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PostHog analytics proxy at /ingest/ forwards requests to an external HTTPS endpoint (us.i.posthog.com) without configuring SSL verification or timeout settings. Consider adding proxy_ssl_verify on and a reasonable proxy_connect_timeout (e.g., 10s) for production reliability. Also consider whether you want to expose this analytics endpoint in production containers, as it could be used by third parties if the container is publicly accessible.

Suggested change
proxy_ssl_server_name on;
proxy_ssl_server_name on;
proxy_ssl_verify on;
proxy_connect_timeout 10s;

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;

Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nginx configuration lacks security headers that are important for production web applications. Consider adding headers like X-Content-Type-Options: nosniff, X-Frame-Options: DENY or SAMEORIGIN, and Referrer-Policy: strict-origin-when-cross-origin in a server-level add_header directive. These headers help protect against common web vulnerabilities like clickjacking and MIME type confusion.

Copilot uses AI. Check for mistakes.
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proxy_read_timeout is set to 30 seconds, but there's no corresponding proxy_connect_timeout or proxy_send_timeout configured. For production reliability, consider adding proxy_connect_timeout 10s and proxy_send_timeout 30s to handle connection establishment and request transmission separately from response reading. This provides better control over different phases of the proxy lifecycle.

Suggested change
proxy_set_header Connection "";
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_send_timeout 30s;

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +49
# Pin to amd64 so the image runs on x86_64 cluster nodes regardless of
# the build host platform (avoids QEMU emulation for the npm build stage).
FROM --platform=linux/amd64 node:20-alpine AS runner
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The platform is pinned to linux/amd64 in the runner stage with the comment explaining it avoids QEMU during npm build. However, the builder stage (lines 12-44) is not platform-pinned, which means the builder stage will use the host platform. If cross-compilation is needed (e.g., building on Apple Silicon for linux/amd64), consider also pinning the builder stage to --platform=linux/amd64, or clarify that native builds are expected.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +23
# The API sidecar: prefer the repurposed Tauri local-api-server (primary),
# then fall back to a compiled server/index.js (legacy), then warn.
if [ -f /app/sidecar/local-api-server.mjs ]; then
LOCAL_API_PORT="${API_PORT}" node /app/sidecar/local-api-server.mjs &
elif [ -f /app/server/index.js ]; then
node /app/server/index.js &
elif [ -f /app/server/index.ts ]; then
# Dev image fallback only — ts-node is not installed in production images
npx ts-node /app/server/index.ts &
else
echo " [warn] No API sidecar found — API calls will fall back to worldmonitor.app (cloud)."
fi

Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback checks for non-existent files. According to the Dockerfile, only local-api-server.mjs is copied to /app/sidecar/, and there are no /app/server/index.js or /app/server/index.ts files built or copied. The server directory in the repo contains only TypeScript modules that are not compiled to a standalone server entry point. These fallback branches will never execute successfully and should be removed to avoid confusion.

Suggested change
# The API sidecar: prefer the repurposed Tauri local-api-server (primary),
# then fall back to a compiled server/index.js (legacy), then warn.
if [ -f /app/sidecar/local-api-server.mjs ]; then
LOCAL_API_PORT="${API_PORT}" node /app/sidecar/local-api-server.mjs &
elif [ -f /app/server/index.js ]; then
node /app/server/index.js &
elif [ -f /app/server/index.ts ]; then
# Dev image fallback only — ts-node is not installed in production images
npx ts-node /app/server/index.ts &
else
echo " [warn] No API sidecar found — API calls will fall back to worldmonitor.app (cloud)."
fi
# The API sidecar: use the repurposed Tauri local-api-server when present,
# otherwise warn and allow API calls to fall back to worldmonitor.app (cloud).
if [ -f /app/sidecar/local-api-server.mjs ]; then
LOCAL_API_PORT="${API_PORT}" node /app/sidecar/local-api-server.mjs &
else
echo " [warn] No API sidecar found — API calls will fall back to worldmonitor.app (cloud)."
fi

Copilot uses AI. Check for mistakes.
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proxy configuration is missing the X-Forwarded-Proto header, which is important for the Node.js backend to determine whether the original request came over HTTP or HTTPS. This is especially critical when the application is behind a load balancer or ingress controller that terminates TLS. Add the following line after line 25: proxy_set_header X-Forwarded-Proto $scheme;

Suggested change
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +99
# Runtime API keys (all optional — dashboard degrades gracefully)
# See .env.example for full descriptions and registration links.
ENV GROQ_API_KEY=""
ENV OPENROUTER_API_KEY=""
ENV UPSTASH_REDIS_REST_URL=""
ENV UPSTASH_REDIS_REST_TOKEN=""
ENV FINNHUB_API_KEY=""
ENV EIA_API_KEY=""
ENV FRED_API_KEY=""
ENV WINGBITS_API_KEY=""
ENV ACLED_ACCESS_TOKEN=""
ENV CLOUDFLARE_API_TOKEN=""
ENV NASA_FIRMS_API_KEY=""
ENV AISSTREAM_API_KEY=""
ENV OPENSKY_CLIENT_ID=""
ENV OPENSKY_CLIENT_SECRET=""
ENV WS_RELAY_URL=""
ENV RELAY_SHARED_SECRET=""
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN environment variables are missing from the Dockerfile's ENV declarations but are present in the Kubernetes Secret. For consistency and documentation purposes, these should be added to the Dockerfile's ENV section (lines 82-99) even if set to empty strings as defaults, since they're used by the API sidecar for cross-user caching (as noted in .env.example). This ensures the runtime contract is clear and complete.

Copilot uses AI. Check for mistakes.

# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gzip_types directive doesn't include application/x-javascript, text/javascript, or application/json+sebuf (if the sebuf RPC format uses a custom content-type). Modern best practice is to include text/javascript explicitly alongside application/javascript. Consider expanding the gzip_types list to: text/plain text/css text/javascript application/json application/javascript application/x-javascript text/xml application/xml image/svg+xml

Suggested change
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript text/xml application/xml image/svg+xml;

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +19
elif [ -f /app/server/index.ts ]; then
# Dev image fallback only — ts-node is not installed in production images
npx ts-node /app/server/index.ts &
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using npx ts-node in the container entrypoint can cause the runtime to download and execute the latest ts-node package from the npm registry whenever it is not installed locally, without version pinning or integrity verification. If this fallback branch is hit (for example when a server/index.ts exists), a compromised or hijacked npm package could execute arbitrary code in the container and exfiltrate environment variables or other secrets. Consider removing this fallback for production images or ensuring ts-node is a pinned, vendored dependency that is invoked from node_modules instead of via a bare npx call.

Copilot uses AI. Check for mistakes.
API readiness probe

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.