Skip to content

-feat: add self-hosted Docker image (roadmap)#77

Open
InlitX wants to merge 812 commits intokoala73:mainfrom
InlitX:feature/self-hosted-docker
Open

-feat: add self-hosted Docker image (roadmap)#77
InlitX wants to merge 812 commits intokoala73:mainfrom
InlitX:feature/self-hosted-docker

Conversation

@InlitX
Copy link
Contributor

@InlitX InlitX commented Feb 15, 2026

Implements the roadmap item "Self-hosted Docker image".

  • Dockerfile: multi-stage build, serves SPA + 45+ API handlers on port 3000.
  • docker-compose.yml: one service, optional env file for API keys.
  • Sidecar: LOCAL_API_MODE=standalone serves dist/ and listens on 0.0.0.0; static serving uses path.relative() to prevent path traversal.
  • README: Option 4 (Self-hosted Docker) and platform table updated; roadmap checkbox done.
  • CHANGELOG: entry under [Unreleased].

Desktop (Tauri) unchanged; standalone is opt-in via env. node --test src-tauri/sidecar/local-api-server.test.mjs — all 8 tests pass.

- Add more US military prefixes (ARMY, NAVY, USAF, OPS, etc.)
- Add Middle East military prefixes (RSAF, UAE, EMIRI, etc.)
- Add NATO tactical callsigns (SWORD, LANCE, etc.)
- Add short tactical pattern (3 letters + 1-2 digits) with airline exclusion
- Exclude known airline codes (SVA, QTR, THY, etc.) from short patterns
- Remove UAE from military prefixes (conflicts with Emirates airline)
- Use more specific UAEAF, BAHAF, OMAAF for Gulf military
- Expand airline exclusion list with major carriers worldwide
- This improves accuracy by removing commercial flight false positives
Military aircraft have specific transponder ID ranges by country.
This matches how OpenSky UI identifies military aircraft.
Now checks both callsign patterns AND hex ranges.

Ranges added for: USA, UK, France, Germany, Italy, NATO, Israel,
Saudi, UAE, Qatar, Turkey, Russia, China, Iran, India, Pakistan,
Australia, Japan, South Korea
Previous ranges were too broad, catching commercial airlines.
Now using confirmed military-only blocks based on OSINT sources.
Focus on ranges where military/civilian separation is clear:
- US (AE/AF), UK, France, Germany, NATO, Russia, China, Australia, Japan

For other countries, rely on callsign detection since hex ranges
include commercial aircraft mixed with military.
- 20,396 verified military hex IDs from adsbexchange.com
- Updated daily by ADS-B Exchange community
- O(1) lookup using Set
- Combines with callsign detection for comprehensive coverage
- Remove CCA from MILITARY_PREFIXES (Air China airline, not military)
- Remove duplicate IAF entry from Middle East section
- Move AIRLINE_CODES to module level Set for O(1) lookup
- Remove duplicate entries (ELY, THY, SWR)
The pan formula was incorrectly dividing by zoom:
  pan.x = width/(2*zoom) - pos[0]  // Wrong

Corrected to:
  pan.x = width/2 - pos[0]  // Correct

The zoom-independent formula ensures clicking theaters in Strategic
Posture panel correctly centers on that region at any zoom level.
…with fixes

- PR koala73#33: Disambiguate NAVY callsign by origin country (US vs UK)
- PR koala73#34: Use typecode fallback in determineAircraftInfo + Wingbits
  enrichment (guarded: only when type is still 'unknown')
- PR koala73#35: Persist typeCode in enriched data (skip the broken
  pre-filter removal and false-positive regex from original PR)

Closes koala73#33, koala73#34, koala73#35
SVA is Saudia's ICAO code - was causing false positive military
classification for civilian Saudi flights.
DeckGLMap.setCenter() uses maplibreMap.flyTo() (animated, 500ms) but
callers immediately followed with setZoom() which interrupted the
animation. Added optional zoom param to setCenter() and updated all
callers in App.ts to use setCenter(lat, lon, 4) instead of separate calls.
Added navalThresholds per theater and recalcPostureWithVessels() that
uses "either triggers" logic: if aircraft OR vessels exceed their
respective thresholds, the theater level escalates. Previously only
aircraft counts were used, so 40 vessels + 10 aircraft showed "normal".
Prevents the jarring effect of all detected locations flashing
simultaneously when the app first loads. Flashes only trigger
after the initial feed collection completes.
Click empty map area to detect country via Nominatim reverse geocoding,
highlight it with GeoJSON boundaries, and show an AI-generated intel
brief (Groq, Redis-cached 2h) with CII score, active signals, and
contextual news headlines.
- Add transparent interactive fill layer for country hover detection
- Hover shows subtle highlight + pointer cursor so user knows it's clickable
- Show loading modal immediately on click (before geocode completes)
- Dismiss modal if geocode fails (ocean click etc)
…t, expanded prompt

- Military signals now counted by geographic bounding box (lat/lon in country)
  instead of operator flag — matches what posture panel shows
- Add country aliases for headline matching (Israel → israeli, gaza, hamas, idf...)
- Feed convergence scores + regional alerts to AI context
- Expanded prompt: 5 sections, 300-400 words, military posture analysis,
  regional context, cross-signal correlation
- Bump max_tokens 500→900, cache version ci-v1→ci-v2
- Add 25+ country bounding boxes for geo-matching
- Stock index API endpoint (Yahoo Finance, Redis 1h cache, ~50 countries)
- Stock chip in modal: shows weekly % change with green/red styling
- Pause deck.gl renders while country modal is open (fixes flickering)
- Suppress signal modal clicks while country modal is visible
- Single stock fetch reused for both UI chip and AI brief context
Yahoo Finance doesn't carry indices for UAE, Vietnam, Nigeria, Kenya,
Colombia, Pakistan, Bangladesh, Greece, or Romania.
- Add DFMGI.AE for UAE (DFM General Index)
- Use range=1mo instead of 5d to handle Sun-Thu trading weeks
- Take last ~5 trading days from monthly data for weekly % change
- Remove broken Qatar symbol (^QSI returns no data)
…dges, and filters

- New threat-classifier.ts: 5-level classification (critical/high/medium/low/info) with
  14 event categories, variant-aware keywords (tech adds outage/breach/layoff etc),
  word-boundary regex for short keywords to prevent false positives
- Wire classifier into RSS pipeline, derive isAlert from threat level (backward compat)
- Aggregate threat at cluster level (max level, most frequent category, tier-weighted confidence)
- NewsPanel: colored threat badges, 5-button filter bar, severity sort toggle
- Persist filter/sort prefs in localStorage
Headlines now ranked critical→high→medium→low→info with time as tiebreaker.
Removed filter buttons, threat badges, and sort toggle — cleaner panel.
Switch from volume-only market fetching to tag-based event queries
for better relevance. Variant-aware tags (geopolitical vs tech).
Deduplicates across tag queries, falls back to top-volume markets
only when needed. Add clickable links to Polymarket event pages.
Wire geo-hub keyword matching into RSS feed parsing to attach lat/lon
to news items, aggregate locations at cluster level, and render as
ScatterplotLayer on the deck.gl map colored by threat level.

Add fetchCountryMarkets() to Polymarket service with country-to-tag
mapping and variant matching, shown in CountryIntelModal with yes/no
bars when clicking a country.
…d localStorage caching

Thresholds were calibrated for classified-level data visibility (50+ aircraft for
Iran ELEVATED). Open-source trackers (OpenSky/Wingbits) see ~10-20% of actual
military flights. Lowered all theaters ~5-6x to match real detection rates.

Added localStorage persistence to cached-theater-posture so the Strategic Posture
panel shows last-known data instantly on page reload instead of starting empty.
- Hybrid keyword + Groq LLM classification with Redis cache (24h TTL)
- Progressive disclosure: bases/nuclear/datacenters hidden at low zoom
- Label deconfliction: BREAKING badges suppress overlaps by priority
- Zoom-adaptive opacity and marker sizing for reduced visual clutter
- Fix unbounded summary growth in alert merging (cap at 3 items)
- Fix Strategic Risk Panel showing "Insufficient Data" on startup
- Remove dead renderLimitedData code
- Expand README with algorithms, architecture, and new features
- Bump version to 2.1.2
koala73 and others added 24 commits February 15, 2026 20:00
…v2.2.6

- Expand SUPPRESSED_TRENDING_TERMS from 13 to ~170 entries to filter
  common English words (department, state, news, etc.) from intelligence
  findings
- Move sidecar admin endpoints (debug-toggle, traffic-log, env-update,
  local-status) before LOCAL_API_TOKEN auth gate — settings window sends
  bare fetch without token, causing silent 401 failures
- Restore Market Radar and Economic Indicators panels to tech variant
- Remove stale Documentation section from README
- Clean up .env.example cyber threat keys (handled internally)
- Bump v2.2.6
… v2.2.7

- Tighten CORS regex to block worldmonitorEVIL.vercel.app spoofing
- Move sidecar /api/local-env-update behind token auth + add key allowlist
- Add postMessage origin/source validation in LiveNewsPanel
- Replace postMessage wildcard '*' targetOrigin with specific origin
- Add isDisallowedOrigin() check to 25 API endpoints missing it
- Migrate gdelt-geo & EIA from custom CORS to shared _cors.js
- Add CORS to firms-fires, stock-index, youtube/live endpoints
- Tighten youtube/embed.js ALLOWED_ORIGINS regex
- Remove 'unsafe-inline' from CSP script-src
- Add iframe sandbox attribute to YouTube embed
- Validate meta-tags URL query params with regex allowlist
Add skipWaiting, clientsClaim, and cleanupOutdatedCaches to workbox
config. Fixes NS_ERROR_CORRUPTED_CONTENT / MIME type errors when
users have a cached service worker serving old HTML that references
asset hashes no longer on the CDN after redeployment.
Major security hardening: CORS enforcement on all API endpoints,
sidecar auth bypass fix, postMessage origin validation, CSP
tightening, and service worker stale cache fix.
- Allow hyphens in Vercel preview URL patterns (worldmonitor-xxx-yyy)
- Harden open_url command with proper URL parsing via reqwest::Url
- Update YouTube embed test assertions for quote style change
Common English verbs like "say" pass frequency thresholds and generate
false intelligence findings. Now runs browser NER (BERT) on spike
headlines before alerting — terms not recognized as named entities
(PER/ORG/LOC/MISC) are suppressed. Falls back gracefully when NER
is unavailable. Also adds ~55 base verb forms to the suppressed list
as hardened fallback for low-spec devices.
- Show "Staged" status/pill for buffered secrets instead of "Missing"
- Add macOS Edit menu (Cmd+C/V/X/Z) for WKWebView clipboard support
- Raise settings window when main gains focus (prevent hide-behind)
- Fix Cloudflare verification to probe Radar API (not token/verify)
- Fix EIA verification URL to valid v2 endpoint
- Force IPv4 globally: monkey-patch fetch() to avoid IPv6 ETIMEDOUT
  on government APIs (EIA, NASA FIRMS) with broken AAAA records
- Soft-pass on network errors during secret verification (don't block save)
- Add desktopRequiredSecrets to skip relay URLs on desktop
- Cross-window sync for secrets and feature toggles via localStorage events
- Add @tauri-apps/cli devDependency
@tauri-apps/cli in devDependencies causes tauri-action to run
`npm run tauri` instead of auto-installing globally.
…, bump v2.3.2

- Save keys that pass verification even when others fail (was all-or-nothing)
- Capture un-blurred input values before render to prevent loss on checkbox toggle
- Fix missing isDisallowedOrigin import in PIZZINT endpoints
MD012 / no-multiple-blanks Multiple consecutive blank lines
MD022 / blanks-around-headings Headings should be surrounded by blank lines
MD032 / blanks-around-lists Lists should be surrounded by blank lines
warontherocks.com, www.aei.org, responsiblestatecraft.org,
www.fpri.org, jamestown.org
FPRI trailing slash fix applied. RUSI was already in SOURCE_TIERS/SOURCE_TYPES from base branch.
MD012 / no-multiple-blanks Multiple consecutive blank lines
MD022 / blanks-around-headings Headings should be surrounded by blank
lines
MD032 / blanks-around-lists Lists should be surrounded by blank lines
keyring v3 ships with NO default platform backends — API keys were
stored in-memory only, lost on every app restart. Add apple-native
and windows-native features to use real OS credential stores.
…mp v2.3.4

Tauri resource_dir() on Windows returns \\?\ extended-length paths that
Node.js module resolution cannot handle, causing EISDIR: lstat 'C:'.
Strip the prefix before passing to Node.js, set current_dir to the
sidecar directory, and add package.json with "type": "module" to prevent
ESM scope walk-up to drive root.
The lock file was out of sync causing npm ci to fail in CI for v2.3.3
and v2.3.4 builds.
@vercel
Copy link

vercel bot commented Feb 15, 2026

@InlitX is attempting to deploy a commit to the eliehabib projects Team on Vercel.

A member of the Team first needs to authorize it.

@SebastienMelki SebastienMelki added enhancement New feature or request docker Docker, self-hosting, containers community Community contribution labels Feb 17, 2026
@SebastienMelki
Copy link
Collaborator

Thanks for working on this @InlitX — self-hosted Docker has been on the roadmap for a while, good to see someone tackling it.

Couple of things I'd like to understand better:

  1. Image size — what's the final image size after the multi-stage build? Curious if there's room to slim it down (alpine base, etc.).
  2. Path traversal fix — you mention path.relative() to prevent traversal. Could you share more about how that's validated? Want to make sure the static serving is locked down properly.
  3. Health check — is there a health endpoint or readiness probe for orchestrators (k8s, docker-compose health checks)?

The 8 passing tests are great. Will review the code in detail soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Community contribution docker Docker, self-hosting, containers enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants