037 - World Monitor Role behind the oauth2-proxy SSO Gate¶
User Story¶
As a platform administrator of Infinito.Nexus, I want World Monitor integrated as a web-app-worldmonitor role behind the platform’s Keycloak SSO (via the oauth2-proxy gate) so that users reach the real-time global intelligence dashboard with their existing Infinito.Nexus identity, in compose and in swarm, without a second login.
Background¶
World Monitor is an AGPL-3.0 dashboard that aggregates public data feeds (earthquakes, conflicts, markets, vessel tracking and more) on a map. Upstream ships a self-hosted Docker stack of four services.
Upstream facts established by research¶
Verified against upstream main and release v2.10.0 (2026-09-08).
Topic |
Finding |
Source |
|---|---|---|
Services |
|
|
Published image |
Only |
|
User authentication |
None in self-hosted mode. |
|
Mandatory secrets |
|
SELF_HOSTING.md |
Admin routes |
|
SELF_HOSTING.md |
Reverse proxy |
|
SELF_HOSTING.md |
CSP |
The bundled nginx emits its own |
SELF_HOSTING.md |
Data seeding |
Redis starts empty. Upstream seeds with |
SELF_HOSTING.md |
Persistence |
One volume, |
docker-compose.yml |
LLM |
Any OpenAI-compatible endpoint through |
SELF_HOSTING.md |
MCP |
|
SELF_HOSTING.md |
Swarm blockers in the upstream file |
|
docker-compose.yml |
In-repo analogues¶
SSO flavor is
oauth2(oauth2-proxy sidecar against Keycloak), as inweb-app-openclawandweb-app-checkmk. The Keycloak client is provisioned by the platform for every role withsso.enabled.Multi-service compose template with a role-built image:
web-app-moodle. Dockerfile placement andARGwiring follow dockerfile.md.Shared LiteLLM gateway consumer:
litellmservice flag plussecrets.credentials.litellm_api_keyinweb-app-openclaw.Generated credentials:
meta/secrets.ymlas inweb-app-openclaw.
Proposed Decisions¶
The operator MUST confirm or change these before implementation starts. Once confirmed they are not re-litigated during implementation.
# |
Decision |
Rationale |
|---|---|---|
1 |
Role |
Standard single-host convention. |
2 |
SSO is the oauth2-proxy gate only. No native OIDC, no user mapping, no in-app accounts. |
Upstream self-hosted mode has no user model to map onto. A later native OIDC switch is the existing |
3 |
Every authenticated Keycloak user may enter. No |
The dashboard has no per-user state or admin surface in Docker mode. |
4 |
The upstream internal mechanisms stay as shipped: the anonymous |
They protect service-to-service traffic and are independent of the user boundary. |
5 |
|
Only one image is published. No fork is maintained. |
6 |
Redis and the REST proxy run inside the role stack with their own generated credentials, not on the shared |
The app speaks only the Upstash REST dialect through the bundled proxy, and upstream runs Redis with |
7 |
Seeding runs inside the stack as a periodic seeder service. No host Node, no host cron, no published REST proxy port. |
The upstream host-side flow needs a host-bound port and a host runtime, which breaks in swarm and violates service isolation. |
8 |
The Redis volume persists but is excluded from backup. |
Content is re-fetchable cache. A restore is a re-seed. |
9 |
The LLM is wired to the shared LiteLLM gateway when |
One platform gateway instead of a per-app provider key. |
10 |
External data-source API keys are optional operator-supplied inventory values, empty when unset. |
Upstream degrades gracefully per feed. |
11 |
|
The MCP path authenticates with a static key, not with the platform identity. Tracked separately if wanted. |
Target Schema¶
Role layout¶
roles/web-app-worldmonitor/
├── README.md
├── files/Dockerfile
├── files/playwright/{_shared,playwright.spec,test-guest,test-oidc-login,test-isolation}.js
├── meta/{main,info,domains,networks,csp,services,secrets,variants,volumes}.yml
├── tasks/{main,00_core}.yml
├── templates/{compose.yml.j2,env.j2,playwright.env.j2}
└── vars/main.yml
meta/services.yml excerpt¶
worldmonitor:
modes:
compose: { enabled: true }
swarm: { enabled: true }
image: ghcr.io/koala73/worldmonitor
version: "X.Y.Z"
name: worldmonitor
ports:
internal: { http: 8080 }
local: { http: <free port>, sso: <free port> }
lifecycle: alpha
sso:
bond: 1
enabled: "{{ 'web-app-keycloak' in group_names }}"
shared: "{{ 'web-app-keycloak' in group_names }}"
flavor: oauth2
oauth2:
origin: { host: worldmonitor, port: 8080 }
litellm:
bond: 1
enabled: "{{ LITELLM_USABLE }}"
shared: true
meta/secrets.yml credentials¶
relay_shared_secret, redis_password, redis_token, session_secret, litellm_api_key.
Acceptance Criteria¶
Role layout & images¶
[ ]
roles/web-app-worldmonitor/exists per the Target Schema and allmeta/*.ymlpass the role-meta lint.[ ]
worldmonitorrunsghcr.io/koala73/worldmonitorpinned to a concrete semver tag whose existence in GHCR was verified. No:latest.[ ]
ais-relayandredis-restare built from the same pinned upstream tag, with every external value passed as a DockerARGper dockerfile.md.[ ] Every image used by the role is declared once in
meta/services.ymland is found by the image discovery.
Compose¶
[ ] A compose deploy brings
worldmonitor,ais-relay,redis,redis-restand the seeder to a steady running state with no manual step.
Swarm¶
[ ] The same role deploys as a swarm stack and reaches the same steady state.
[ ] The rendered swarm stack contains no
container_name, nomem_limitand no host-bound port publish, and no service fails permanently when its dependencies start after it.[ ] The role-built images are available to every swarm node that may schedule the services.
Secrets¶
[ ]
RELAY_SHARED_SECRET,REDIS_PASSWORD,REDIS_TOKENandWM_SESSION_SECRETare generated throughmeta/secrets.ymland no literal secret value is committed.[ ]
I_UNDERSTAND_THIS_DISABLES_AUTHandALLOW_UNAUTHENTICATED_RELAYare not set anywhere in the role.[ ] Optional external API keys (
FINNHUB_API_KEY,ALPHA_VANTAGE_API_KEY,FRED_API_KEY,EIA_API_KEY,ACLED_*,NASA_FIRMS_API_KEY,AVIATIONSTACK_API,TRAVELPAYOUTS_API_TOKEN,AISSTREAM_API_KEY,CLOUDFLARE_API_TOKEN) reach the containers that read them when the operator sets them, and the stack is healthy when all are empty.
Routing & TLS¶
[ ]
https://worldmonitor.{{ DOMAIN_PRIMARY }}/resolves throughsys-svc-proxyto theworldmonitorservice and serves the dashboard SPA.[ ]
WM_TRUSTED_PROXY_CIDRSis set to the network the fronting proxy reaches the container from, in compose and in swarm, and the app logs the real client address for a proxied request.[ ] The dashboard’s live updates (the WebSocket and streaming endpoints it opens) work through the proxy with no console error.
[ ] The canonical surface answers with exactly one
Content-Security-Policyheader, and the dashboard renders under it with no CSP violation in the browser console.
SSO (Decisions #2 to #4)¶
[ ] With SSO enabled, an unauthenticated request to any path, including
/api/*, is redirected to the Keycloak login.[ ] After login the user lands on the dashboard with no further credential prompt.
[ ] A user with an existing Keycloak session reaches the dashboard without an interactive login.
[ ] After logout through the platform logout, a request to the dashboard is redirected to the Keycloak login again.
[ ] With SSO disabled, the dashboard is reachable anonymously and the stack is otherwise unchanged.
Service isolation¶
[ ] Only the
worldmonitorHTTP port is published, bound to the platform bind host.redis,redis-restandais-relaypublish no port.[ ] From outside the role’s network,
redis:6379,redis-rest:80andais-relay:3004are unreachable.[ ] Redis rejects an unauthenticated command, and
redis-restrejects a request without the bearer token.
Data seeding (Decision #7)¶
[ ] After a fresh deploy the dashboard shows seeded data from the key-free feeds with no command run on the host.
[ ] Seeding repeats on a fixed interval inside the stack, and one hung feed does not block the others.
Persistence & backup (Decision #8)¶
[ ] Seeded data survives a recreate of every container in the stack, in compose and on the swarm storage layer.
[ ] The Redis volume is excluded from the platform backup.
LLM (Decision #9)¶
[ ] When
LITELLM_USABLEis true,LLM_API_URL,LLM_API_KEYandLLM_MODELpoint at the shared LiteLLM gateway with the role’s own virtual key, and an intelligence brief request returns generated text.[ ] When it is false, the three variables are empty and the stack is healthy.
Health checks¶
[ ]
worldmonitorhas a container healthcheck that fails when either nginx or the Node API is down.[ ]
redisandredis-resthave container healthchecks, and theredis-restcheck fails when Redis is unreachable.[ ]
ais-relayhas a container healthcheck.
Variants¶
[ ]
meta/variants.ymlcovers SSO on and SSO off, and every variant deploys cleanly on a fresh host.
Playwright¶
[ ]
test-guest.jsasserts the redirect to Keycloak for the root path and for an/api/path.[ ]
test-oidc-login.jslogs in asbiber, asserts the map renders, and ends logged out with the dashboard gated again.[ ]
test-isolation.jsasserts that no internal service answers on the canonical host.[ ]
templates/playwright.env.j2exposes every environment variable the specs read, andmake compose-playwright role=web-app-worldmonitorexits 0 with no stub test.[ ] The same specs pass against the swarm deploy.
Documentation & quality¶
[ ]
README.mddocuments the gate-only SSO model, the retained internal secrets, the in-stack seeding, the optional API keys with their effect, the image pin and bump policy, and the AGPL-3.0 licence of the upstream.[ ]
make testis green tree-wide.[ ] This requirement is cross-linked from the implementing PR.
Out of Scope¶
Native OIDC, user mapping and in-app roles (no upstream support in self-hosted mode).
Registering
/api/mcpas a platform MCP server.The hosted-only features (Clerk entitlements, premium routes, OAuth for MCP).
Validation Apps¶
make compose-deploy apps=web-app-worldmonitor purge=true full_cycle=true
Cross-mode parity follows the Roundtrip Loop.
Smoke: visit https://worldmonitor.{{ DOMAIN_PRIMARY }}/, pass the Keycloak login, confirm the map shows seeded layers, log out, confirm the gate returns.
Prerequisites¶
Before implementation, the agent MUST read AGENTS.md, then Compose Loop and the Playwright contract.
Implementation Strategy¶
Execute autonomously once the Proposed Decisions are confirmed. Scaffold from roles/web-app-openclaw/ (oauth2 flavor, generated credentials, LiteLLM consumer) and take the multi-service compose shape from roles/web-app-moodle/. Before claiming done, verify against the pinned upstream tag: the environment variable names, whether the published app image contains the seeder scripts, the health endpoint path, and the dashboard selectors used by the specs.
Commit Policy¶
No git commit until every Acceptance Criterion is checked off.
When met and
make testis green, instruct the operator to rungit-sign-pushoutside the sandbox. The agent MUST NOT push.
Context¶
Upstream repo: https://github.com/koala73/worldmonitor
Self-hosting guide: https://github.com/koala73/worldmonitor/blob/main/SELF_HOSTING.md
Closest in-repo analogues:
roles/web-app-openclaw/,029-web-app-checkmk.md