Commit graph

378 commits

Author SHA1 Message Date
Mitchell R
9db8d1d65b fix: clean up secret key generation log message
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 02:56:27 +02:00
Mitchell R
b739533ce1 fix: mount betterframe-data volume on server container
secret.key, firmware signing keys, and all encrypted data require
persistent storage at /var/lib/betterframe. Without this volume,
every redeploy regenerates keys and breaks all encrypted fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 02:53:36 +02:00
Mitchell R
d6a52df27a feat: global Settings page for AbleSign + Cloud Cam account config
- New /admin/settings page with AbleSign account setup (API key) and
  link to Cloud Cams config
- Settings nav item in sidebar (gear icon, before Account)
- Removed AbleSign Config from AbleSign dropdown (now in Settings)
- AbleSign account delete redirects to Settings
- Cloud Cams nav item kept for its own CRUD page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 02:47:54 +02:00
Mitchell R
f0088836e9 fix: add AbleSign Config link back to nav dropdown
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 02:45:17 +02:00
Mitchell R
01fcb66402 fix: surface AbleSign screen creation errors instead of swallowing
Previously caught and silently ignored. Now shows error message on
the screens page so we can debug the pairing flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 02:41:13 +02:00
Mitchell R
10f5cf7fac fix: per-connection search_path + sidebar tenant dropdown
Some checks are pending
release / meta (push) Waiting to run
release / build (push) Blocked by required conditions
PG adapter: setSearchPath now stores schema name, runner applies
SET search_path on every connection checkout. Eliminates cross-request
schema bleed (previous: setSearchPath mutated shared connection state).

Middleware: always set search_path (removed 'public' skip condition).

Sidebar: tenant switcher dropdown at bottom, loaded via htmx from
/admin/_tenant_switcher. Hidden when only one tenant. Auto-submits
on change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 02:15:00 +02:00
Mitchell R
1dbb56752c fix: tenant switch auto-copies global admin into tenant users
isSetupComplete() now checks public.global_admins — if a global admin
exists but tenant has no local users, copies admin into tenant's users
table and marks setup complete. Prevents setup wizard on tenant switch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 02:10:56 +02:00
Mitchell R
65de42d495 refactor: AbleSign UI — single account, screen detail, no kiosk assign
- Remove Accounts from AbleSign nav (one account per tenant)
- Screens page: create button, no kiosk assignment
- Screen detail page with config form
- Internal/External badge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 02:09:40 +02:00
Mitchell R
e0941f533d
feat: AbleSign dropdown nav + screens/content/playlists pages
- Sidebar: NavGroup component (details/summary) for AbleSign dropdown
  with Accounts, Screens, Content, Playlists sub-items
- Global screens page (/admin/ablesign/screens) — all screens across
  accounts with Internal/External badge
- Content page — aggregates media files + web apps from all accounts
- Playlists page — shows per-screen playlist items
- Auto-sync screens on account creation
- Internal/External: Internal = created via "Create & Pair" (has
  screenToken, gets entity). External = synced from AbleSign (no token,
  no entity, management-only). Only internal screens become entities.
- Entity creation only on Create & Pair path — not on sync or assign

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 01:47:17 +02:00
Mitchell R
e1a3cd1d05
fix: ONVIF single-kiosk ownership + rate limiting
Server-side:
- Bundle gen: when camera event_source is "auto", first kiosk to fetch
  bundle claims ownership → writes "kiosk:<id>" to camera row. Other
  kiosks see assigned owner and skip ONVIF subscription.
- Kiosk deletion resets event_source back to "auto" so next kiosk
  takes over.
- repo.getActiveOnvifOwners() for future use.

Kiosk-side:
- Only subscribe when event_source is "auto" or "kiosk:<MY_ID>".
  Skips "kiosk:<other_id>", "server", "none", "disabled".
- Poll interval: 3s → 10s (cameras were getting overwhelmed)
- CreatePullPoint backoff: exponential 30s→60s→120s...→600s max
- Pull errors: exponential 15s→30s→45s...→300s, resubscribe after 5
  consecutive failures instead of immediately.
- load_kiosk_id() helper reads from cached bundle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 01:40:44 +02:00
Mitchell R
a518fe17ea
fix: move AbleSign migrations to end of array (after UUIDv7 backfill)
Some checks are pending
release / meta (push) Waiting to run
release / build (push) Blocked by required conditions
Server already ran past the indices where AbleSign tables were inserted.
Moving to end ensures they get new, unrun version numbers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:28:38 +02:00
Mitchell R
73dbd9b6bf
feat: managed entities (read-only) + AbleSign auto-creates entity
- Entity type: add 'ablesign' to EntityType + CellContentType
- Entity.managed boolean: true for auto-created entities (camera sync,
  cloud cams, AbleSign). UI blocks editing managed entities.
- Entity.ablesign_screen_id: links to ablesign_screens row
- ensureCameraEntity now sets managed=true
- AbleSign screen creation auto-creates managed entity with
  web_url=player.ablesign.tv and ablesign_screen_id FK
- PG migration: alter entities CHECK constraint + add columns
- Entity edit route rejects POST for managed entities

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:08:19 +02:00
Mitchell R
c3bdcbce4c
feat: AbleSign digital signage integration
- DB: ablesign_accounts (api_key_encrypted, workspace_id) +
  ablesign_screens (ablesign_screen_id, kiosk assignment, orientation)
- API client: shared/ablesign.ts — list/register/update/delete screens,
  playlist CRUD, headless pairing (initiate player registration →
  register via admin API key → no UI shown on kiosk)
- Admin routes: account CRUD, screen sync from AbleSign API, headless
  screen creation (Create & Pair), kiosk assignment, remote delete
- Admin UI: AbleSign nav item, accounts page (add/sync/delete),
  screens page (add/assign to kiosk/delete) with kiosk dropdown
- Follows cloud camera pattern: encrypted credentials, sync from
  vendor API, assign to kiosks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:03:42 +02:00
Mitchell R
5ce526eb33
feat: audio controls, reboot button, update lock, ONVIF refresh
- Audio: kiosk/src/audio.rs — PipeWire/ALSA volume, mute, output
  selection. WS commands volume-set/volume-mute/audio-output.
  Heartbeat reports audio state. Admin UI volume buttons + mute.
- Reboot: admin button with confirmation, WS reboot command,
  kiosk runs systemctl reboot.
- Firmware update now reboots (not exit) to clear state fully.
- Update lock: FIRMWARE_LOCK + OS_UPDATE_LOCK mutexes prevent
  concurrent update attempts from heartbeat + WS paths.
- ONVIF: auto-refresh stale/failed subs (>24h or failed state),
  mark_event_received with proper epoch timestamp, parse Key
  section for PlateNumber.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 16:57:41 +02:00
Mitchell R
55b11f2ffa
fix: Node-RED event forwarding + parse ONVIF Key section (PlateNumber)
Some checks are pending
release / meta (push) Waiting to run
release / build (push) Blocked by required conditions
Server bridge was forwarding to raw topic paths that no Node-RED node
listens on. Now forwards to fixed routes: camera.event, onvif.event,
onvif.motion, onvif.anpr — matching what trigger nodes register.

ONVIF XML parser now extracts Key section SimpleItems (PlateNumber,
etc.) into the data map alongside Data section items. Previously only
parsed Source and Data, missing Key-section fields like plate numbers.

Node-RED trigger nodes: camera_id filter changed from Number() to
String() comparison for UUIDv7 compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:38:30 +02:00
Mitchell R
eb8abbdff9
feat: ONVIF subscription auto-refresh on failure or 24h staleness
- Track subscribed_at timestamp per camera in SubStatus
- Fix mark_event_received to use epoch seconds (was OS version string)
- needs_refresh() returns true when any sub is failed/stopped or >24h old
- Heartbeat loop calls maybe_refresh_onvif() every 60s — reloads
  cameras from cached bundle and restarts onvif_events::start() which
  kills old generation threads and creates fresh PullPoint subscriptions
- mark_event_received called on each successful event forward

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:34:22 +02:00
Mitchell R
8c59bb6b02
fix: wrap nullable event fields with optional() for missing keys
anyvali nullable() accepts null but rejects undefined (absent field).
Kiosk log events omit camera_id/property_op entirely. Wrap with
optional() so missing fields default to null.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:26:55 +02:00
Mitchell R
9eeddff680
fix: add /var/lib/betterframe/tmp to tmpfiles for bfkiosk write access
Some checks are pending
release / meta (push) Waiting to run
release / build (push) Blocked by required conditions
OS update staging dir on BF_DATA needs bfkiosk ownership.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:17:37 +02:00
Mitchell R
38c78c0bb5
fix: log validation errors with field detail + raw body on event reject
validateBody now extracts per-field error messages from anyvali issues.
Event endpoint logs the raw body (first 500 chars) on validation failure
so we can see exactly what the kiosk sends.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:15:41 +02:00
Mitchell R
8381ed280e
fix: md5 crate v0.7 API (compute not Digest) + clone default_layout_id
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:03:29 +02:00
Mitchell R
5d23079086
feat: add anyvali input validation to all external API endpoints
Create shared/api-schemas.ts with av.object schemas for:
- pair/initiate, pair/claim (pairing flow)
- kiosk/heartbeat (telemetry with displays, partitions, hwmon)
- kiosk/event (ONVIF/system events)
- kiosk/logs (batched log entries)
- firmware/applied, os/applied (update reports)
- auth/login, auth/totp, setup (admin auth)

Each endpoint now calls validateBody(Schema, body) which returns 400
on schema violation. All string fields have maxLength, numeric fields
have min/max ranges, arrays strip unknown keys. Rejects malformed
input before it reaches DB or business logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 14:03:58 +02:00
Mitchell R
515f7088cc
fix: graceful FK violation on event insert + kiosk stale file cleanup
- Event insert: if source_camera_id FK fails (stale kiosk sending old
  integer IDs), retry with camera_id=NULL. Event still logs, just
  without camera association. Stops 500 spam until kiosk updates.
- Kiosk cleanup on first healthy boot: remove stale OS update staging
  files (>24h old) from /var/lib/betterframe/tmp/, and old firmware
  .prev binaries (>7 days) from /opt/betterframe/kiosk/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:56:24 +02:00
Mitchell R
b93e9484ff
fix: drop FKs before UUID backfill, re-add after
SET CONSTRAINTS ALL DEFERRED only works on DEFERRABLE constraints.
Ours aren't. Instead: save all FK definitions to jsonb array, drop
them all, do the id replacements unconstrained, re-add from saved
definitions. Same pattern as the type-conversion migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:42:35 +02:00
Mitchell R
420463afdc
feat: backfill bare-integer IDs with UUIDs in existing rows
Adds migration that finds all rows where id matches ^[0-9]+$ (legacy
integer IDs converted to text strings), generates a UUID for each,
updates all FK references dynamically via information_schema, then
updates the PK. Existing data (cameras, layouts, kiosks, etc.) gets
proper UUID IDs. New rows already use UUIDv7 from repository.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:38:55 +02:00
Mitchell R
9dc6119791
fix: also convert *_by columns (uploaded_by, created_by) to TEXT
Dynamic column detection only matched 'id' and '*_id' patterns.
firmware_releases.uploaded_by and similar FK columns use '_by' suffix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:34:27 +02:00
Mitchell R
02b69713c3
fix: dynamic FK re-add with column existence check
Previous migration hardcoded ALTER TABLE ADD CONSTRAINT for FK re-add,
but production DB may have different columns than CREATE TABLE schema
(api_keys had no user_id). Now uses _bf_add_fk() helper that checks
both source column and target column exist before adding FK. Skips
silently if either is missing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:32:18 +02:00
Mitchell R
fe9c51d3f0
fix: exclude setup_state from UUIDv7 migration
setup_state is a singleton (INTEGER PK CHECK(id=1)), not an entity.
Converting its id to TEXT breaks the CHECK constraint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:28:53 +02:00
Mitchell R
108123fb86
fix: dynamic FK drop in UUIDv7 migration — handle all constraints
Previous migration hard-coded FK constraint names and missed
firmware_releases.uploaded_by, firmware_rollouts.created_by/release_id,
os_update_releases.uploaded_by, os_update_rollouts.created_by/release_id,
pairing_codes.consumed_by_kiosk_id, entities.camera_id.

Now uses information_schema to dynamically drop ALL FK constraints
before type conversion, and dynamically finds ALL integer id/*_id
columns to convert. No more missed FKs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:26:46 +02:00
Mitchell R
9b4032ca8a
refactor: decommission SQLite + add UUIDv7 PK migration for existing PG
- Delete sqlite-adapter.ts and migrations.ts (SQLite path removed)
- Remove driver/sqlitePath from all config schemas + sec-config template
- init.ts now PG-only, no SQLite branch
- db-adapter.ts dialect narrowed to "postgres" only
- Add in-place UUIDv7 migration: detects INTEGER PKs in existing PG
  databases, drops FK constraints, ALTER COLUMN TYPE to TEXT for all
  15 entity tables + their FK columns, re-adds FK constraints. Idempotent
  (skips if already TEXT). Existing integer IDs become string "1", "2"
  etc — new inserts use proper UUIDv7 from repository.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:22:29 +02:00
Mitchell R
908fd417c0
refactor(kiosk): migrate all bundle IDs to String for UUIDv7 + ONVIF image proxy
- All bundle struct ID fields (kiosk_id, display_id, layout_id,
  camera_id, stream_id, gpio_id) now String with de_flexible_id
  deserializer accepting both JSON numbers and strings.
- PoolKey, DisplayState hashmap, WorkerMsg, ServerMsg all use String
  IDs throughout. Zero u32 ID references remain.
- ONVIF event image proxy: kiosk detects PictureUri in event data,
  downloads image from camera (basic/digest auth), base64 encodes,
  attaches to event payload before forwarding to server.
- Add md5 crate for HTTP Digest auth on camera image fetch.
- ws_client: flexible_id_from_value helper for WS message ID parsing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 13:09:32 +02:00
Mitchell R
0c74e26e42
feat: expand BF_DATA on first boot + wire update progress banner + partition reporting
- Add betterframe-expand-data systemd service: growpart + resize2fs on
  BF_DATA (last partition) so it fills the full SD card on first boot.
  Solves the "No space left on device" issue with OS update downloads.
- Change OS update staging dir from /var/tmp/betterframe to
  /var/lib/betterframe/tmp (on BF_DATA partition, not rootfs).
- Wire firmware and OS update progress callbacks into the GTK overlay
  banner — shows "OS Update v1.2.3: Downloading — 45%" etc.
- Add per-partition disk reporting in heartbeat (/, /boot/firmware,
  /var/lib/betterframe) with total/used/free/percent.
- Display partition table on kiosk detail page in admin UI.
- PG + SQLite migrations for partitions_json column on kiosks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 08:09:20 +02:00
Mitchell R
2e8e783eed
feat(kiosk): update progress banner framework
Added WorkerMsg::UpdateProgress(Option<(label, percent)>) for
showing firmware/OS update progress as an overlay banner on the
display. Handler + label management in place. Actual progress
reporting from firmware.rs/os_update.rs to be wired next.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 07:51:42 +02:00
Mitchell R
bde26a7ed4
feat(kiosk): show FW+OS version on awaiting-layout screen too
Version label was only on pairing screen. Now also shown on the
idle/awaiting-layout logo screen (bottom-left overlay).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 07:38:34 +02:00
Mitchell R
de0a76e01d
fix(onvif): extract SOAP fault reason + log auth state + NTP setup
SOAP errors now extract fault Reason/Text/Code from XML instead of
dumping raw envelope. Logs whether ONVIF password was decrypted
(has_pass=true/false). Added NTP config to pi-gen (pool.ntp.org +
Google/Cloudflare fallback) — WSSE PasswordDigest fails with clock
skew.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 07:33:18 +02:00
Mitchell R
66653af360
feat: implement multi-tenant support with PG schema isolation
Adds tenant management for PostgreSQL deployments. Each tenant gets its
own PG schema (tenant_<slug>) with a full set of BetterFrame tables.
SQLite deployments stay single-tenant with no behavior change.

Key changes:
- Run PUBLIC_MIGRATIONS (tenants + global_admins tables) during PG init
- Auto-create "default" tenant (schema=public) on first boot
- createTenantSchema() runs TENANT_MIGRATIONS in a new PG schema
- DbAdapter.setSearchPath() for per-request schema switching (PG)
- Tenant CRUD in Repository (listTenants, create, update, delete)
- Middleware resolves bf_tenant cookie and sets search_path per request
- Admin UI: /admin/tenants with CRUD + tenant switching via cookie
- Tenant dropdown in topbar (Layout) when >1 tenant exists
- Tenant nav item in sidebar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 07:22:01 +02:00
Mitchell R
64f47a9a6b
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.

Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
  RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
  u32 (old) and String (new) IDs for backward compatibility

Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 07:11:45 +02:00
Mitchell R
69e51197bf
refactor(streams): store RTSP components separately for ONVIF cameras
ONVIF-discovered camera streams now store rtsp_host, rtsp_port, and
rtsp_path as separate columns instead of baking credentials into a
pre-built URL. This fixes XML entity issues (&amp;), special character
password breakage, and credential duplication across streams.

Bundle generation builds the final playable URL at delivery time using
components + camera row credentials with proper URL encoding. Existing
RTSP-type cameras with only rtsp_uri continue to work unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 06:51:33 +02:00
Mitchell R
b6e929d2ad
fix: decode XML entities in ONVIF RTSP URIs
ONVIF returns XML with &amp; in URIs. GStreamer rtspsrc cant parse
these. Now decoded before storing in camera_streams. Fixes RTSP
Unauthorized for ONVIF-discovered cameras with query params.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 06:44:25 +02:00
Mitchell R
2a21ababc0
fix(ui): add source/sink columns to event subscriptions + full width
Event subscriptions table now shows Source and Sink columns.
Camera detail page uses full width instead of max-width 700px.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 06:26:49 +02:00
Mitchell R
69e6ce1cbc
fix(kiosk): cleanup cameras removed from layouts
ONVIF: only subscribe to cameras actually in layout cells, not all
bundle cameras. Purge warm camera pool entries for cameras removed
from the bundle entirely — immediate stop, no cooling period.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 06:25:02 +02:00
Mitchell R
7b9ba13e3a
fix(kiosk): extract RTSP creds as rtspsrc properties for digest auth
Credentials embedded in RTSP URL can skip digest negotiation on
some cameras. Now extract user:pass from URL, set as user-id/user-pw
properties on rtspsrc, pass clean URL as location.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 06:23:38 +02:00
Mitchell R
36a787cc1e
feat(kiosk): show FW + OS version on pairing screen, remove spinner
Version label at bottom-left of pairing screen shows firmware
version (compile-time) and OS version (from /etc/betterframe/
os-version). Spinner removed from pairing screen per request.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:46:08 +02:00
Mitchell R
26a9df6b11
fix(kiosk): return None after ignoring bf_kiosk_deleted
After _check confirms key still valid, code fell through to parse
the bf_kiosk_deleted JSON as a KioskBundle causing parse error.
Now returns None to skip bundle processing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:44:32 +02:00
Mitchell R
1cf77f55c9
fix: deliver encrypt_key in claim response
claimPairing returned kioskKey + clusterKey but NOT encryptKey.
Without it, kiosk cant decrypt ONVIF passwords in the bundle,
causing WSSE auth failure and HTTP 400 on all PullPoint
subscriptions. Now included in claim response and API output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:36:41 +02:00
Mitchell R
37cf03e37c
fix(kiosk): include response body in ONVIF SOAP error messages
HTTP 400 from camera gave no detail. Now includes first 500 chars
of response body in error message so Axiom shows the actual SOAP
fault reason.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:33:29 +02:00
Mitchell R
c51d971819
fix: add event_source/event_sink to CameraEventSubscription type
Mapper referenced these fields but type interface was missing them.
Caused tsc failure in Docker build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:31:59 +02:00
Mitchell R
6b31c09e9d
fix(kiosk): set Axiom kiosk_id from bundle on startup
kiosk_id only set during claim. After restart, loaded from disk,
kiosk_id stayed empty. Now set from bundle.kiosk_id after fetch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:31:15 +02:00
Mitchell R
24f9532adf
feat(events): mark subscriptions active on event receipt
When /api/kiosk/event receives an ONVIF event, call
markEventReceived(camera_id, topic) to flip subscription
status from pending → active (orange → green in admin UI).
Also added event_source/event_sink fields to subscription mapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:04:11 +02:00
Mitchell R
2592a18564
fix(ci): pass Axiom secrets to reusable build workflow
Secrets dont auto-propagate to reusable workflows. Added
BF_AXIOM_KEY + BF_AXIOM_DATASET to both release.yml secrets
block and build.yml workflow_call.secrets declaration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 04:26:35 +02:00
Mitchell R
01d9098af2
chore: gitignore doc files + remove from tracking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 04:16:33 +02:00