Commit Graph
7 Commits
Author SHA1 Message Date
Logan CusanoandClaude Opus 5 7f1d09c753 Satisfy flake8 so the lint job stops failing
CI / lint (push) Successful in 6s
CI / test (push) Successful in 39s
Build edge-node / build (push) Successful in 7m44s
Pure formatting, no behaviour change: strip trailing whitespace from blank
lines, give top-level defs in system_cacher.py their two blank lines, wrap
the long discord_radio.join signature, and split the duplicated
active_config ternary in main.py and routers/api.py across lines.

Verified clean with flake8 --max-line-length=120, matching CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 13:56:22 -04:00
Logan CusanoandClaude Opus 5 87633ab50d Authenticate the node dashboard, and the broker connection per node
CI / lint (push) Failing after 24s
CI / test (push) Failing after 28s
Build edge-node / build (push) Failing after 43s
Build op25 / build (push) Failing after 47s
Two unauthenticated surfaces closed on the edge node.

Dashboard and API: the local dashboard and every /api/* route were open to
anything on the node's LAN. Adds a login page plus session-cookie auth for
the browser, and cookie-or-Basic for the API so scripted callers stay
possible. Passwords are hashed with stdlib scrypt (no new dependency, this
runs on a Pi) and compared in constant time; the salt and session-signing
secret persist in credentials.json. Startup warns while the default password
is still in place. No non-browser callers of the node API exist today
(C2 talks to nodes over MQTT and nodes call C2 outbound), so nothing breaks.

Adds python-multipart, which FastAPI's Form() needs for the login POST and
which was missing from requirements entirely.

MQTT: nodes authenticated with a shared drb-node password, and the broker
ACL keyed off %c — the client-supplied client id — so any holder of that one
password could claim another node's topic namespace. Nodes now connect as
username=<node_id>, password=<their C2-issued api_key>, which mosquitto's
dynamic-security plugin checks, with the ACL keyed off the authenticated %u.
TLS is gated on MQTT_TLS and uses default CA verification.

The old key_request MQTT path stays in place behind TODO(mqtt-cutover)
markers as the fallback until the cutover is proven; a node with no api_key
on disk logs a clear repeated refusal rather than spinning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 09:34:16 -04:00
Logan CusanoandClaude Opus 5 d6dfe5a293 Drive call boundaries from audio, use the console only for the label
CI / lint (push) Failing after 35s
Build edge-node / build (push) Failing after 36s
Build op25 / build (push) Failing after 40s
CI / test (push) Failing after 40s
The control channel was wrong in both directions. Grants fire 0.84-1.62s
before anyone speaks, and srcaddr can drop to 0 while someone is still
talking - one recording came back "-1.61s lead, -0.00s tail", the trim
finding nothing to remove because the window had closed on live speech.
Confirmed by ear: the cut lands at a word boundary on an unfinished word.

Audio is ground truth for WHEN. The console remains the only source of
WHO, so it still supplies talkgroup, alias and rid.

  START  voice onset in the captured audio, with a 0.25s pre-roll that
         now covers only chunk quantisation and threshold ramp-up rather
         than a variable control-channel offset.
  STOP   call_silence_timeout seconds of silence heard in the audio.
  LABEL  resolved AT CLOSE from a bounded rolling history of console
         observations overlapping the window, +4s/-2s, because there is
         no guaranteed ordering between a grant and its audio.
  SPLIT  a console talkgroup change still forces a cut, since two calls
         with no silence between them would otherwise merge into one.

Capture now emits raw PCM instead of MP3. Silence detection becomes
integer arithmetic per chunk with no decode, trimming becomes a byte
offset slice rather than a second ffmpeg pass, and MP3 encoding happens
exactly once at save - uploads are no longer double-encoded.

Audio with no talkgroup anywhere in its window is discarded rather than
uploaded: an untagged call silently poisons incident correlation, which
is worse than losing the audio. Logged at ERROR and counted on
/api/status.

When capture produces no audio at all the old console state machine
still runs, so a node with a broken audio path keeps reporting radio
activity. That is now the only consumer of call_idle_timeout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 18:19:45 -04:00
Logan Cusano efdbe7d803 Move recording and Discord voice to PulseAudio
CI / lint (push) Failing after 26s
CI / test (push) Successful in 41s
2026-08-04 19:53:45 -04:00
Logan Cusano 857325af85 feat: Add local system override and manual entry controls with offline systems caching
CI / lint (push) Failing after 5s
CI / test (push) Successful in 19s
Build edge-node / build (push) Successful in 32s
2026-07-12 23:06:05 -04:00
Logan Cusano 3fbdc50536 feat: implement FastAPI router, web + local UI updates, and metadata monitoring for the edge node
CI / lint (push) Failing after 31s
CI / test (push) Successful in 45s
Build edge-node / build (push) Successful in 8m44s
2026-07-12 21:30:30 -04:00
Logan 1a9c92b6db Initial commit — DRB client (edge node) stack
Includes edge-node (FastAPI/MQTT/Discord voice), op25-container (SDR decoder),
and icecast (audio streaming).
2026-04-05 19:01:51 -04:00