R2D2-OPS Status Using Uptime Kuma for Probes
  • JavaScript 55%
  • Vue 38.4%
  • HTML 4.3%
  • TypeScript 0.9%
  • SCSS 0.5%
  • Other 0.6%
Find a file
Joshua Belke b5d26d633e deploy(k8s): R2D2 Ops board as front door over a Kuma backend
Self-contained manifests + ops image Dockerfile to run the ops console as the
public entry point (ops.r2d2.office.ilab.zone / VIP 192.168.3.153) in the
r2d2-status-kuma namespace on the RAID cluster, fronting an Uptime Kuma backend.

- ops-server (git.office.ilab.zone/raid/r2d2-ops) serves the FMV/telemetry UI and
  logs into Kuma over socket.io on localhost; Service/LB/Ingress target it (4013).
- Kuma (git.office.ilab.zone/raid/r2d2-status-kuma:2.4.0) is a private backend
  on a MariaDB sidecar.
- Ordered native sidecars mariadb -> netbird -> kuma -> wait-kuma-login gate, so
  the DB, the mesh, and a working Kuma socket-login are all ready before
  ops-server starts. Fixes the boot-time login hang (timeout-less emitWithAck in
  serve.mjs) and the mesh EHOSTUNREACH false-downs.
- Secrets (db creds, netbird setup key, admin/ops creds) live only in-cluster,
  referenced by secretKeyRef; nothing sensitive in the manifest.
2026-07-08 09:24:43 -04:00
.github chore: add build "pr-test" workflow, add a message for testing to new pr. (#7543) 2026-06-25 18:28:04 +08:00
.settings deploy(k8s): R2D2 Ops board as front door over a Kuma backend 2026-07-08 09:24:43 -04:00
config fix: Remove vite-plugin-pwa, avoid this plugin caches files unexpectedly (#6907) 2026-02-13 10:24:37 +08:00
db chore: change analytics_type to string type from enum (#7544) 2026-06-25 19:06:54 +08:00
docker Helm update 2026-07-08 07:36:35 -04:00
extra fix(push): use multi-arch Node base in push image Dockerfile (#7344) 2026-05-03 12:57:51 +08:00
helm Helm update 2026-07-08 07:36:35 -04:00
public feat(branding): apply TFX rebrand to Kuma at container boot 2026-07-07 10:18:43 -04:00
server feat: add Ooredoo (Maldives) SMS notification provider (#7571) 2026-07-05 13:48:27 +00:00
src feat: add Ooredoo (Maldives) SMS notification provider (#7571) 2026-07-05 13:48:27 +00:00
test fix: resolve Steam monitor hostnames (#7542) 2026-06-25 19:04:02 +08:00
.dockerignore Fix push examples cannot be loaded (Docker only) (#5490) 2025-01-18 23:35:40 +08:00
.editorconfig Fix healthcheck do not check https 2022-12-10 23:30:32 +08:00
.eslintrc.js fix(monitor-list): cannot display monitor name in full width (#7076) 2026-03-02 01:20:10 +00:00
.gitignore Playwright + Native Node Test Runner (#3893) 2023-12-17 19:02:22 +08:00
.npmrc chore: Set 14 days cooldown for npm update to mininize the supply-chain-attack (#7508) 2026-06-12 17:10:44 +08:00
.prettierignore chore: enable formatting over the entire codebase in CI (#6655) 2026-01-09 02:10:36 +01:00
.prettierrc.js chore: fix tab with in the prettier config being different from previously causing hard merges (#6656) 2026-01-09 03:47:31 +00:00
.stylelintrc feat: Add prettier and configure autofix to use it (#6642) 2026-01-08 10:51:36 +01:00
AGENTS.md chore: New AI slop policy (#7405) 2026-05-16 15:49:35 +08:00
CLAUDE.md chore: Add CLAUDE.md to anti ai slop (#7499) 2026-06-10 12:07:47 +08:00
CNAME eslint fixes too much 2022-05-09 21:05:10 +02:00
CODE_OF_CONDUCT.md docs: reword issue templates, pr workflow and review docs (#5728) 2025-04-03 11:05:07 +02:00
compose.yaml Helm update 2026-07-08 07:36:35 -04:00
CONTRIBUTING.md chore: enable formatting over the entire codebase in CI (#6655) 2026-01-09 02:10:36 +01:00
ecosystem.config.js chore: enable formatting over the entire codebase in CI (#6655) 2026-01-09 02:10:36 +01:00
index.html Add noscript tag as stand in for #3553 (#3555) 2023-08-10 19:35:13 +08:00
LICENSE Revert "Update license year to reflect current year" (#6378) 2025-11-20 02:55:10 +08:00
package-lock.json chore: Update dependencies (#7349) 2026-06-22 04:53:08 +00:00
package.json chore: update to 2.4.0 (#7454) 2026-05-31 08:46:42 +08:00
README.md docs(readme): rewrite for TFX FMV console + k8s deployment 2026-07-08 05:44:23 -04:00
SECURITY.md [autofix.ci] apply automated fixes 2026-02-13 14:39:05 +00:00
tsconfig-backend.json chore: fix tab with in the prettier config being different from previously causing hard merges (#6656) 2026-01-09 03:47:31 +00:00
tsconfig.json chore: fix tab with in the prettier config being different from previously causing hard merges (#6656) 2026-01-09 03:47:31 +00:00

Task Force X emblem

R2D2 Status — Task Force X FMV Console

Fleet monitoring and full-motion-video (FMV) operations console for the Task Force X (TFX) unmanned maritime program. It tracks the health of ~118 assets — video feeds, restreamer panels, NodeRed pipelines, and core R2D2 services — across the NATO maritime theatres, and gives operators a single board to see which platforms are live, watch their streams, and capture evidence.

The monitoring engine is Uptime Kuma 2.4.0 (unmodified upstream). Everything specific to TFX lives in .settings/: a seed toolchain that builds the monitor topology, and a purpose-built operator console layered on top of Kuma.

Production console: https://ops.r2d2.office.ilab.zone


What it does

  • Monitors ~118 assets grouped three levels deep — waterway → country → asset — across Central/Eastern Med, Baltic, North Sea and North Atlantic.
  • Video-aware status. A feed that answers HTTP 200 but whose projector snapshot has frozen is downgraded to pending — so "up" means live video, not just a reachable port.
  • Operator console — search/filter the whole fleet, open a native detail view with an HLS player, and record clips or snapshot sessions to disk.
  • Region-grouped public status pages (/status/r2d2, videos, panels, core, nodered) rebuilt idempotently from the seed plan.

Architecture

┌──────────────────────────────────────────────────────────────┐
│  Uptime Kuma 2.4.0 (upstream, unmodified)                     │
│  · monitoring engine · heartbeats/uptime · CSS-only status    │
│  · SQLite (PVC)                    listens :3001              │
└───────────────▲───────────────────────────────┬──────────────┘
                │ socket.io (server-side, admin) │ HTTP status pages
                │                                 ▼
┌───────────────┴──────────────────────────────────────────────┐
│  R2D2 Ops Console  (.settings/ops/serve.mjs, :4013)           │
│  · logs into Kuma once, holds a live in-memory fleet model    │
│  · SPA + JSON snapshot + SSE live stream                      │
│  · snapshot / HLS media proxy (host-allowlisted)              │
│  · liveness poller (snapshot-hash freeze detection)           │
│  · recorder (ffmpeg clip + snapshot capture)                  │
└───────────────────────────────────────────────────────────────┘

Repository layout (TFX-specific work is isolated in .settings/; the rest is stock Uptime Kuma):

Path Purpose
.settings/seed/convert.mjs Builds monitors.json from the legacy endpoint config + holovids CSV exports.
.settings/seed/monitors.json Reviewable seed plan — 21 groups · 118 monitors.
.settings/seed/seed.mjs Seeds groups, monitors, and all status pages into Kuma.
.settings/seed/statuspage.mjs Non-destructive rebuild of the status pages only.
.settings/seed/docker-compose.yml Local Kuma on host port 3011, with TFX branding applied at boot.
.settings/ops/serve.mjs Ops console server (:4013).
.settings/ops/index.html Operator SPA (search, detail, HLS player, capture controls).
.settings/ops/recorder.mjs Clip / snapshot recorder.
.settings/features/ Feature roadmap (01–11).

Local development

Prerequisites: Docker (with Compose) and Node.js 18+.

cd .settings/seed

# 1. Start a local Kuma (SQLite preselected — no setup wizard).
#    Host port 3011 (3001 is commonly taken); TFX branding is applied at boot.
docker compose up -d

# 2. (optional) Regenerate the seed plan. monitors.json is committed.
node convert.mjs

# 3. Seed groups, monitors, and status pages. First run creates the admin user.
KUMA_URL=http://localhost:3011 node seed.mjs

# 4. Rebuild only the status pages later, non-destructively.
KUMA_URL=http://localhost:3011 node statuspage.mjs

Start the operator console against that Kuma:

cd .settings/ops
KUMA_URL=http://localhost:3011 node serve.mjs
# → http://localhost:4013

Configuration

Both the seeder and the ops server are configured entirely through environment variables.

Variable Default Used by Meaning
KUMA_URL http://localhost:3011 seed, ops Kuma base URL.
ADMIN_USER / ADMIN_PASS NATO / NATO1949 seed, ops Kuma admin credentials. Override before any non-local deploy — seeding refuses the defaults against a non-localhost target unless ALLOW_DEFAULT_CREDS=1.
FORCE unset seed Allow re-seeding an already-populated instance.
OPS_PORT 4013 ops Console listen port.
OPS_BIND 127.0.0.1 ops Listen interface. Set 0.0.0.0 to expose beyond localhost.
OPS_SECRET unset ops When set, every state-changing POST must send a matching x-ops-secret header. Set this before binding beyond localhost.
REC_ROOT machine-specific recorder Root directory for captured clips/snapshots.

Security model

The Kuma admin password is used only on the server. The browser never sees a credential or a direct Kuma socket. The console is not read-only, though — it exposes mutating endpoints (delete-monitors, set-interval, record/{start,stop,config}) that act on Kuma as admin. Two controls gate them:

  • OPS_BIND defaults to localhost, so the console answers nothing on the network until you opt in.
  • OPS_SECRET, once set, requires the x-ops-secret header on every mutating request; read endpoints (feed, snapshot, HLS) stay open.

Always set OPS_SECRET before setting OPS_BIND=0.0.0.0.


Kubernetes deployment

The production console is a standalone deployment reachable at ops.r2d2.office.ilab.zone — a Kuma pod plus the ops server, with the pod joined to the mesh so it can actually probe the feeds.

Target topology:

  • Kuma container — louislam/uptime-kuma:2, listening on :3001, SQLite persisted on a Longhorn PVC so monitor config and history survive restarts.
  • Ops console container — runs .settings/ops/serve.mjs on :4013, KUMA_URL pointing at the Kuma container. OPS_BIND=0.0.0.0 with OPS_SECRET set; admin credentials injected from a Kubernetes Secret.
  • NetBird sidecar — most feeds live on the mesh and are unreachable from the cluster's default network. The sidecar puts the pod on the mesh so Kuma's probes and the ops snapshot/HLS proxies resolve real hosts. Off-mesh, feeds read as pending/down by design.
  • Ingress — TLS at ops.r2d2.office.ilab.zone, routing to the ops console (:4013), which fronts both the operator SPA and the Kuma status pages.

Deployment notes:

  • Cluster access uses .settings/kubeconfig.r2d2 — treat as sensitive.
  • Rotate ADMIN_USER / ADMIN_PASS out of the defaults; source them from the Secret, never from image or compose defaults.
  • The packaged Helm chart (Kuma + ops console + NetBird sidecar + Longhorn PVC + ingress) is the deployment vehicle and is tracked as roadmap item R-007; until it lands, deployment is applied from manifests against the topology above.

Roadmap

Feature specs live in .settings/features/ and the umbrella plan in .settings/ops/PLAN.md:

# Feature State
01 Ops Feed (search / filter) Done
02 Holovids feed enrichment Done
05 Deep probes / liveness Partial
06 Native media detail + HLS player Building
07 Live recording (clips + snapshots) Building
08 KLV telemetry discovery Discovery
09 C2 map / common operating picture Planned
10 ReductStore media archive (FIFO) Planned
11 Drone MQTT discovery & video↔node association Planned

Upstream

Built on Uptime Kuma by Louis Lam, licensed under the MIT License. The server/ and src/ trees are stock upstream and are intentionally left unmodified — all TFX customisation is confined to .settings/. See LICENSE for terms.