---
name: Wild Dragon MAM platform
description: Self-hosted media asset management replacing Grass Valley AMPP FramelightX. Deployed on zampp1 (primary) and zampp2 (worker w/ DeckLink Duo 2). Repo at forge.wilddragon.net/zgaetano/wild-dragon.
type: project
originSessionId: 544a289a-0493-4194-9fbd-112ed250e221
---
Self-hosted MAM platform under active development. Stack: Node.js/Express, vanilla HTML/CSS/JS frontend, PostgreSQL 16, Redis 7 + BullMQ, S3-compatible (RustFS), FFmpeg, Blackmagic DeckLink SDK, Docker Compose. Repo: `forge.wilddragon.net/zgaetano/wild-dragon` (HTTPS only, no SSH).

**Why:** Replacing Grass Valley AMPP FramelightX with an in-house alternative. Test deployment is the production deployment — the user authorizes pushing directly to `main` and deploying to zampp1 + zampp2 for continued testing.

**How to apply:** Treat zampp1/zampp2 as authorized targets for MeshCentral / live deploy. Push to `main` directly when changes are validated. The repo's `deploy/onboard-node.sh` is the canonical worker provisioning path.

## Cluster topology
- **zampp1** — primary; runs `mam-api`, `db`, `redis`, `web-ui`, `worker`, `capture` containers via `docker-compose.yml`
- **zampp2** — worker; runs `node-agent` (host-network) + on-demand `capture` sidecars via `docker-compose.worker.yml`; has a **DeckLink Duo 2** (4 BNC ports at `/dev/blackmagic/io0..io3`)
- Workers heartbeat to `POST /api/v1/cluster/heartbeat` with hostname, IP, GPU/DeckLink capabilities
- Cluster registry: unique index on `cluster_nodes.hostname` (migration 007); `pickIp()` in `routes/cluster.js` rejects 172.16/12 docker bridge IPs in favor of request source IP

## Codec selection (recorders)
Per-recorder columns added in migration 008 (`recording_*` and `proxy_*` for video bitrate, framerate, audio codec/bitrate/channels, container). `services/capture/src/capture-manager.js` exports `VIDEO_CODECS`, `AUDIO_CODECS`, `CONTAINER_FMT`, `CONTAINER_EXT` catalogs. `routes/recorders.js` `RECORDER_FIELDS` whitelist passes settings as env vars to the capture sidecar via `bootstrapAutoStart()`.

## DeckLink port picker
`services/web-ui/public/js/bmd-card.js` (window.BMDCards) renders an SVG diagram of the card with click-to-select ports. Models registered: Duo 2, Quad 2, Mini Recorder 4K, Mini Monitor 4K, UltraStudio 4K Mini. Backed by `GET /api/v1/cluster/devices/blackmagic` which flattens every node's DeckLink capabilities. **The `recorders.html` rewrite that consumes this was lost** in a context-compaction event — file on zampp1 still matches the old (pre-tabs, pre-SVG) version.

## Pending work (as of 2026-05-21)
1. Re-do `services/web-ui/public/recorders.html` rewrite — tabbed codec settings (Video/Audio/Container) for both master & proxy, node selector + BMD card SVG picker for SDI source. The previous attempt was transferred to zampp1 mid-session but the chunked b64 assembly never completed.
2. Deploy on zampp1: `cd /opt/wild-dragon && git pull && docker compose up -d --build` to pick up migrations 007/008 and codec changes.
3. Deploy on zampp2: `git pull`, update `.env.worker` with `BMD_COUNT=4`, `BMD_MODEL='DeckLink Duo 2'`, `NODE_IP=<host LAN IP>`, then `docker compose -f docker-compose.worker.yml up -d --build`.
4. Run `deploy/test-cluster.sh` on zampp1 to validate.
5. GUI polish pass with flyonui MCP (explicitly the LAST priority).

## Pushed commits (already on `main`)
- `3b4af6e` node-agent: prefer host LAN IP, NODE_IP override
- `0efef0d` cluster route: pickIp() + /devices/blackmagic endpoint
- `a39c983` migration 007: dedupe hostnames + unique index
- `049beb8` migration 008: expanded codec columns
- `40a66ba` worker compose: network_mode: host for node-agent
- `f4a83ee` capture-manager: dynamic ffmpeg args
- `485af25` capture index.js: bootstrap reads codec env vars
- `4c65753` recorders route: full codec field whitelist
- `0ebb3cf` onboard-node: auto-detect host LAN IP
- `d39f86d` web-ui: bmd-card.js
- `97628bb` chore: remove cloudflare rate-limit probe (.touchtest)
- `8aa3783` deploy: test-cluster.sh + .touchtest cleanup (rebased onto 97628bb and pushed from zampp1 using a Forgejo PAT written to /root/.git-credentials)

## Git creds on zampp1
`/root/.git-credentials` holds the Forgejo PAT for user `zgaetano`. `credential.helper=store` is set in `/root/.gitconfig`. Future pushes from zampp1 just need `HOME=/root` exported (the MeshCentral agent's default $HOME is `/usr/local/mesh_services/Mesh Dragon/MeshDragon`). Rotate the PAT by overwriting that one file.
