@trillboards/edge-sdk 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/deploy/docker/Dockerfile.cloud-sense +117 -0
- package/deploy/docker/Dockerfile.cpu +30 -21
- package/deploy/docker/Dockerfile.cuda +30 -21
- package/deploy/docker/Dockerfile.openvino +30 -20
- package/deploy/docker/README.md +9 -4
- package/deploy/docker/connector-entrypoint.sh +122 -0
- package/deploy/helm/README.md +1 -1
- package/deploy/k8s/daemonset.yaml +1 -1
- package/deploy/macos/README.md +161 -0
- package/deploy/macos/TrillboardsSetupApp.m +603 -0
- package/deploy/macos/build-macos-pkg.sh +588 -0
- package/deploy/macos/com.trillboards.edge.plist +62 -0
- package/deploy/macos/com.trillboards.litertlm.plist +45 -0
- package/deploy/macos/pair.command +519 -0
- package/deploy/macos/scripts/ensure-litertlm.sh +62 -0
- package/deploy/macos/scripts/postinstall +153 -0
- package/deploy/macos/scripts/setup-litertlm.sh +272 -0
- package/deploy/macos/test-install-pair.sh +383 -0
- package/deploy/macos/test-setup-litertlm-reinstall.sh +109 -0
- package/deploy/systemd/install.sh +25 -15
- package/deploy/systemd/trillboards-edge.service +4 -1
- package/deploy/windows/README.md +70 -0
- package/deploy/windows/build-windows.ps1 +225 -0
- package/deploy/windows/bundle-windows-installer.ps1 +153 -0
- package/deploy/windows/check-feeds.js +26 -0
- package/deploy/windows/e2e/fixtures/person.jpg +0 -0
- package/deploy/windows/e2e/test-local-sense.ps1 +185 -0
- package/deploy/windows/install-update.ps1 +101 -0
- package/deploy/windows/installer.nsi +138 -0
- package/deploy/windows/register-litertlm-task.ps1 +42 -0
- package/deploy/windows/register-task.ps1 +44 -0
- package/deploy/windows/run-connector.ps1 +69 -0
- package/deploy/windows/setup-litertlm.ps1 +361 -0
- package/deploy/windows/stop-existing.ps1 +72 -0
- package/deploy/windows/trillboards-edge-task.vbs +25 -0
- package/deploy/windows/trillboards-edge.cmd +33 -0
- package/deploy/windows/trillboards-pair.cmd +276 -0
- package/deploy/windows/trillboards-setup.ps1 +456 -0
- package/deploy/windows/trillboards-setup.vbs +13 -0
- package/dist/AudienceAnalyzeClient.d.ts +136 -0
- package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
- package/dist/AudienceAnalyzeClient.js +246 -0
- package/dist/AudienceAnalyzeClient.js.map +1 -0
- package/dist/ChannelProber.d.ts +150 -0
- package/dist/ChannelProber.d.ts.map +1 -0
- package/dist/ChannelProber.js +887 -0
- package/dist/ChannelProber.js.map +1 -0
- package/dist/CloudEscalationPolicy.d.ts +143 -0
- package/dist/CloudEscalationPolicy.d.ts.map +1 -0
- package/dist/CloudEscalationPolicy.js +137 -0
- package/dist/CloudEscalationPolicy.js.map +1 -0
- package/dist/CloudSensingManager.d.ts +533 -0
- package/dist/CloudSensingManager.d.ts.map +1 -0
- package/dist/CloudSensingManager.js +1254 -0
- package/dist/CloudSensingManager.js.map +1 -0
- package/dist/DiscoveryReportClient.d.ts +66 -0
- package/dist/DiscoveryReportClient.d.ts.map +1 -0
- package/dist/DiscoveryReportClient.js +85 -0
- package/dist/DiscoveryReportClient.js.map +1 -0
- package/dist/EdgeAgent.d.ts +12 -2
- package/dist/EdgeAgent.d.ts.map +1 -1
- package/dist/EdgeAgent.js +55 -13
- package/dist/EdgeAgent.js.map +1 -1
- package/dist/LiteRtVlmProvider.d.ts +193 -0
- package/dist/LiteRtVlmProvider.d.ts.map +1 -0
- package/dist/LiteRtVlmProvider.js +436 -0
- package/dist/LiteRtVlmProvider.js.map +1 -0
- package/dist/MosaicSource.d.ts +108 -0
- package/dist/MosaicSource.d.ts.map +1 -0
- package/dist/MosaicSource.js +274 -0
- package/dist/MosaicSource.js.map +1 -0
- package/dist/OnvifClient.d.ts +83 -0
- package/dist/OnvifClient.d.ts.map +1 -0
- package/dist/OnvifClient.js +209 -0
- package/dist/OnvifClient.js.map +1 -0
- package/dist/PairClient.d.ts +106 -0
- package/dist/PairClient.d.ts.map +1 -0
- package/dist/PairClient.js +186 -0
- package/dist/PairClient.js.map +1 -0
- package/dist/RecorderAddressRecovery.d.ts +53 -0
- package/dist/RecorderAddressRecovery.d.ts.map +1 -0
- package/dist/RecorderAddressRecovery.js +359 -0
- package/dist/RecorderAddressRecovery.js.map +1 -0
- package/dist/SceneChangeDetector.d.ts +291 -0
- package/dist/SceneChangeDetector.d.ts.map +1 -0
- package/dist/SceneChangeDetector.js +682 -0
- package/dist/SceneChangeDetector.js.map +1 -0
- package/dist/SensingProfileClient.d.ts +59 -0
- package/dist/SensingProfileClient.d.ts.map +1 -0
- package/dist/SensingProfileClient.js +78 -0
- package/dist/SensingProfileClient.js.map +1 -0
- package/dist/SetupSessionClient.d.ts +114 -0
- package/dist/SetupSessionClient.d.ts.map +1 -0
- package/dist/SetupSessionClient.js +95 -0
- package/dist/SetupSessionClient.js.map +1 -0
- package/dist/VlmAdmissionGate.d.ts +126 -0
- package/dist/VlmAdmissionGate.d.ts.map +1 -0
- package/dist/VlmAdmissionGate.js +166 -0
- package/dist/VlmAdmissionGate.js.map +1 -0
- package/dist/autoSense.d.ts +216 -0
- package/dist/autoSense.d.ts.map +1 -0
- package/dist/autoSense.js +521 -0
- package/dist/autoSense.js.map +1 -0
- package/dist/cli.d.ts +27 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +511 -21
- package/dist/cli.js.map +1 -1
- package/dist/combinedSense.d.ts +128 -0
- package/dist/combinedSense.d.ts.map +1 -0
- package/dist/combinedSense.js +261 -0
- package/dist/combinedSense.js.map +1 -0
- package/dist/config.d.ts +351 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +99 -2
- package/dist/config.js.map +1 -1
- package/dist/connectorCadence.d.ts +135 -0
- package/dist/connectorCadence.d.ts.map +1 -0
- package/dist/connectorCadence.js +151 -0
- package/dist/connectorCadence.js.map +1 -0
- package/dist/dataDir.d.ts +33 -0
- package/dist/dataDir.d.ts.map +1 -0
- package/dist/dataDir.js +76 -0
- package/dist/dataDir.js.map +1 -0
- package/dist/deployedCadence.d.ts +167 -0
- package/dist/deployedCadence.d.ts.map +1 -0
- package/dist/deployedCadence.js +209 -0
- package/dist/deployedCadence.js.map +1 -0
- package/dist/deployedCadenceSource.d.ts +48 -0
- package/dist/deployedCadenceSource.d.ts.map +1 -0
- package/dist/deployedCadenceSource.js +74 -0
- package/dist/deployedCadenceSource.js.map +1 -0
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.d.ts.map +1 -1
- package/dist/diagnose.js +13 -12
- package/dist/diagnose.js.map +1 -1
- package/dist/discovery.d.ts +131 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +499 -0
- package/dist/discovery.js.map +1 -0
- package/dist/gemmaModelOta.d.ts +118 -0
- package/dist/gemmaModelOta.d.ts.map +1 -0
- package/dist/gemmaModelOta.js +291 -0
- package/dist/gemmaModelOta.js.map +1 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +41 -18
- package/dist/init.js.map +1 -1
- package/dist/keepAwake.d.ts +33 -0
- package/dist/keepAwake.d.ts.map +1 -0
- package/dist/keepAwake.js +54 -0
- package/dist/keepAwake.js.map +1 -0
- package/dist/legacyRecorderProfile.d.ts +20 -0
- package/dist/legacyRecorderProfile.d.ts.map +1 -0
- package/dist/legacyRecorderProfile.js +27 -0
- package/dist/legacyRecorderProfile.js.map +1 -0
- package/dist/localInferenceSchema.d.ts +132 -0
- package/dist/localInferenceSchema.d.ts.map +1 -0
- package/dist/localInferenceSchema.js +274 -0
- package/dist/localInferenceSchema.js.map +1 -0
- package/dist/localSenseE2eHarness.d.ts +101 -0
- package/dist/localSenseE2eHarness.d.ts.map +1 -0
- package/dist/localSenseE2eHarness.js +279 -0
- package/dist/localSenseE2eHarness.js.map +1 -0
- package/dist/localSensing.d.ts +268 -0
- package/dist/localSensing.d.ts.map +1 -0
- package/dist/localSensing.js +492 -0
- package/dist/localSensing.js.map +1 -0
- package/dist/modelIntegrity.d.ts +59 -0
- package/dist/modelIntegrity.d.ts.map +1 -0
- package/dist/modelIntegrity.js +165 -0
- package/dist/modelIntegrity.js.map +1 -0
- package/dist/pairVerify.d.ts +100 -0
- package/dist/pairVerify.d.ts.map +1 -0
- package/dist/pairVerify.js +296 -0
- package/dist/pairVerify.js.map +1 -0
- package/dist/qrScanner.d.ts +33 -0
- package/dist/qrScanner.d.ts.map +1 -0
- package/dist/qrScanner.js +103 -0
- package/dist/qrScanner.js.map +1 -0
- package/dist/runtimePaths.d.ts +10 -0
- package/dist/runtimePaths.d.ts.map +1 -0
- package/dist/runtimePaths.js +79 -0
- package/dist/runtimePaths.js.map +1 -0
- package/dist/sensingRunMode.d.ts +68 -0
- package/dist/sensingRunMode.d.ts.map +1 -0
- package/dist/sensingRunMode.js +346 -0
- package/dist/sensingRunMode.js.map +1 -0
- package/dist/setupMode.d.ts +98 -0
- package/dist/setupMode.d.ts.map +1 -0
- package/dist/setupMode.js +828 -0
- package/dist/setupMode.js.map +1 -0
- package/dist/setupProgress.d.ts +31 -0
- package/dist/setupProgress.d.ts.map +1 -0
- package/dist/setupProgress.js +173 -0
- package/dist/setupProgress.js.map +1 -0
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -184,7 +184,7 @@ docker run -d \
|
|
|
184
184
|
-e DEVICE_TOKEN=your-token \
|
|
185
185
|
-p 9090:9090 \
|
|
186
186
|
--device /dev/video0:/dev/video0 \
|
|
187
|
-
trillboards/edge-sdk:latest
|
|
187
|
+
ghcr.io/trillboards/edge-sdk:latest
|
|
188
188
|
|
|
189
189
|
# NVIDIA GPU
|
|
190
190
|
docker run -d \
|
|
@@ -193,7 +193,7 @@ docker run -d \
|
|
|
193
193
|
-e DEVICE_TOKEN=your-token \
|
|
194
194
|
-p 9090:9090 \
|
|
195
195
|
--device /dev/video0:/dev/video0 \
|
|
196
|
-
trillboards/edge-sdk:cuda
|
|
196
|
+
ghcr.io/trillboards/edge-sdk:cuda
|
|
197
197
|
```
|
|
198
198
|
|
|
199
199
|
**docker-compose:**
|
|
@@ -201,7 +201,7 @@ docker run -d \
|
|
|
201
201
|
```yaml
|
|
202
202
|
services:
|
|
203
203
|
edge-agent:
|
|
204
|
-
image: trillboards/edge-sdk:latest
|
|
204
|
+
image: ghcr.io/trillboards/edge-sdk:latest
|
|
205
205
|
restart: unless-stopped
|
|
206
206
|
environment:
|
|
207
207
|
- DEVICE_TOKEN=${DEVICE_TOKEN}
|
|
@@ -249,6 +249,7 @@ Or register via the [Trillboards API](https://api.trillboards.com/developer).
|
|
|
249
249
|
| [docs/CLI.md](../../docs/CLI.md) | Full CLI reference with all commands, flags, and configuration |
|
|
250
250
|
| [docs/MCP.md](../../docs/MCP.md) | MCP server reference for AI agent integration |
|
|
251
251
|
| [docs/STATUS_API.md](../../docs/STATUS_API.md) | Status endpoint API reference |
|
|
252
|
+
| [deploy/macos/README.md](deploy/macos/README.md) | macOS one-click `.pkg` installer (cloud-sense CCTV connector) |
|
|
252
253
|
| [deploy/docker/README.md](../../deploy/docker/README.md) | Docker deployment guide |
|
|
253
254
|
| [deploy/helm/README.md](../../deploy/helm/README.md) | Helm chart deployment guide |
|
|
254
255
|
| [create-trillboards-edge](../../../create-trillboards-edge/README.md) | Project scaffolding tool |
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Trillboards Edge AI SDK — BYO-CCTV connector container
|
|
2
|
+
#
|
|
3
|
+
# No local ONNX models bundled, so this image never downloads models and is much
|
|
4
|
+
# lighter than the cpu/cuda/openvino variants. It pairs with a short-lived claim
|
|
5
|
+
# code (the device token is delivered at pair, NEVER baked into the image) then
|
|
6
|
+
# senses. Same connector + same CLI as the macOS/Windows installers — ONE image,
|
|
7
|
+
# not a parallel one.
|
|
8
|
+
#
|
|
9
|
+
# TWO TAGS, ONE IMAGE. `SENSE_MODE` bakes the default the entrypoint runs:
|
|
10
|
+
# :auto-sense (SENSE_MODE=auto-sense, the default and the recommended tag)
|
|
11
|
+
# probes localSensing.litertLmBaseUrl at startup and commits to
|
|
12
|
+
# on-device inference if a healthy litert-lm is hosting the model.
|
|
13
|
+
# Because this image bundles NO model, the entrypoint also asserts
|
|
14
|
+
# localSensing.allowCloudFallback=true — auto-sense otherwise
|
|
15
|
+
# treats cloud as opt-in and would WAIT forever for a runtime that
|
|
16
|
+
# is never going to appear here. With that assert, a plain
|
|
17
|
+
# `docker run` behaves exactly like :cloud-sense (frames to the
|
|
18
|
+
# cloud VLM) and additionally AUTO-UPGRADES to on-device with no
|
|
19
|
+
# restart the moment a litert-lm becomes reachable.
|
|
20
|
+
# :cloud-sense (SENSE_MODE=cloud-sense) forces the cloud VLM. Kept because the
|
|
21
|
+
# portal one-liner and existing installs reference it.
|
|
22
|
+
# TRILLBOARDS_SENSE_MODE overrides either at run time.
|
|
23
|
+
#
|
|
24
|
+
# WHAT CLOUD MODE COSTS. Measured 2026-08-12 over 255,812 image-bearing cloud VLM
|
|
25
|
+
# calls: $0.007008 per analyzed frame, so one camera at the 300s default cadence
|
|
26
|
+
# is $60.55/month and a 16-camera store is $969/month. On-device inference has no
|
|
27
|
+
# per-frame cost. That gap is the entire reason this image defaults to probing.
|
|
28
|
+
#
|
|
29
|
+
# Build: docker build -f packages/edge-sdk/deploy/docker/Dockerfile.cloud-sense \
|
|
30
|
+
# --build-arg SENSE_MODE=auto-sense \
|
|
31
|
+
# -t trillboards/edge-sdk:auto-sense . # context = trillboards-edge-sdk
|
|
32
|
+
# Run: docker run -e TRILLBOARDS_CLAIM_CODE=XXXX-XXXX \
|
|
33
|
+
# -v trillboards:/app/data \
|
|
34
|
+
# ghcr.io/trillboards/edge-sdk:auto-sense
|
|
35
|
+
|
|
36
|
+
FROM node:20-slim AS builder
|
|
37
|
+
|
|
38
|
+
WORKDIR /build
|
|
39
|
+
|
|
40
|
+
# Workspace package files first (layer caching).
|
|
41
|
+
COPY package.json package-lock.json* ./
|
|
42
|
+
COPY packages/edge-core/package.json packages/edge-core/
|
|
43
|
+
COPY packages/edge-sensing/package.json packages/edge-sensing/
|
|
44
|
+
COPY packages/edge-federated/package.json packages/edge-federated/
|
|
45
|
+
COPY packages/edge-ads/package.json packages/edge-ads/
|
|
46
|
+
COPY packages/edge-cloud/package.json packages/edge-cloud/
|
|
47
|
+
COPY packages/edge-sdk/package.json packages/edge-sdk/
|
|
48
|
+
COPY packages/edge-platform-linux/package.json packages/edge-platform-linux/
|
|
49
|
+
COPY packages/edge-platform-rtsp/package.json packages/edge-platform-rtsp/
|
|
50
|
+
|
|
51
|
+
RUN npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps
|
|
52
|
+
|
|
53
|
+
COPY . .
|
|
54
|
+
RUN npm run build --workspaces --if-present
|
|
55
|
+
|
|
56
|
+
# ─── Production image ─────────────────────────────────────────────────────
|
|
57
|
+
FROM node:20-slim
|
|
58
|
+
|
|
59
|
+
LABEL maintainer="Trillboards <engineering@trillboards.com>"
|
|
60
|
+
ARG SENSE_MODE=auto-sense
|
|
61
|
+
LABEL description="Trillboards Edge AI SDK — BYO-CCTV connector (no bundled models)"
|
|
62
|
+
|
|
63
|
+
# ffmpeg = RTSP frame capture; ca-certificates/curl = TLS + healthcheck.
|
|
64
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
65
|
+
ffmpeg \
|
|
66
|
+
ca-certificates \
|
|
67
|
+
curl \
|
|
68
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
69
|
+
|
|
70
|
+
WORKDIR /app
|
|
71
|
+
|
|
72
|
+
# Built packages (the cloud path lazy-requires the local ONNX stack, so it is
|
|
73
|
+
# never loaded at runtime — but the workspace deps must resolve).
|
|
74
|
+
COPY --from=builder /build/packages/edge-core/dist packages/edge-core/dist
|
|
75
|
+
COPY --from=builder /build/packages/edge-core/package.json packages/edge-core/
|
|
76
|
+
COPY --from=builder /build/packages/edge-sensing/dist packages/edge-sensing/dist
|
|
77
|
+
COPY --from=builder /build/packages/edge-sensing/package.json packages/edge-sensing/
|
|
78
|
+
COPY --from=builder /build/packages/edge-federated/dist packages/edge-federated/dist
|
|
79
|
+
COPY --from=builder /build/packages/edge-federated/package.json packages/edge-federated/
|
|
80
|
+
COPY --from=builder /build/packages/edge-ads/dist packages/edge-ads/dist
|
|
81
|
+
COPY --from=builder /build/packages/edge-ads/package.json packages/edge-ads/
|
|
82
|
+
COPY --from=builder /build/packages/edge-cloud/dist packages/edge-cloud/dist
|
|
83
|
+
COPY --from=builder /build/packages/edge-cloud/package.json packages/edge-cloud/
|
|
84
|
+
COPY --from=builder /build/packages/edge-sdk/dist packages/edge-sdk/dist
|
|
85
|
+
COPY --from=builder /build/packages/edge-sdk/package.json packages/edge-sdk/
|
|
86
|
+
COPY --from=builder /build/packages/edge-platform-linux/dist packages/edge-platform-linux/dist
|
|
87
|
+
COPY --from=builder /build/packages/edge-platform-linux/package.json packages/edge-platform-linux/
|
|
88
|
+
COPY --from=builder /build/packages/edge-platform-rtsp/dist packages/edge-platform-rtsp/dist
|
|
89
|
+
COPY --from=builder /build/packages/edge-platform-rtsp/package.json packages/edge-platform-rtsp/
|
|
90
|
+
|
|
91
|
+
# Production deps only (NO model download — inference is cloud-side, or on a
|
|
92
|
+
# litert-lm the operator runs themselves and points this at).
|
|
93
|
+
COPY --from=builder /build/package.json /build/package-lock.json* ./
|
|
94
|
+
RUN npm ci --legacy-peer-deps --omit=dev 2>/dev/null || npm install --legacy-peer-deps --omit=dev
|
|
95
|
+
|
|
96
|
+
# Pairing writes the config here; mount /app/data on a volume so the paired
|
|
97
|
+
# config (device token + feeds) survives container restarts.
|
|
98
|
+
RUN mkdir -p /app/data
|
|
99
|
+
|
|
100
|
+
# First-run pair → sense (claim code in, token never baked).
|
|
101
|
+
COPY packages/edge-sdk/deploy/docker/connector-entrypoint.sh /usr/local/bin/connector-entrypoint.sh
|
|
102
|
+
RUN chmod +x /usr/local/bin/connector-entrypoint.sh
|
|
103
|
+
|
|
104
|
+
ENV NODE_ENV=production
|
|
105
|
+
ENV TRILLBOARDS_API_BASE="https://api.trillboards.com"
|
|
106
|
+
ENV TRILLBOARDS_CONFIG="/app/data/trillboards.config.json"
|
|
107
|
+
# Baked per tag by the publish matrix; overridable per run.
|
|
108
|
+
ENV TRILLBOARDS_SENSE_MODE=${SENSE_MODE}
|
|
109
|
+
|
|
110
|
+
# No HEALTHCHECK / status-server port: the sensing CLI paths build
|
|
111
|
+
# CloudSensingManager directly and never start the StatusServer (that wiring is
|
|
112
|
+
# only in EdgeAgent.start), so a /health:9090 probe would never respond and Docker
|
|
113
|
+
# would mark a perfectly-streaming container unhealthy (Codex P2 on #7730).
|
|
114
|
+
# Liveness for this streaming worker is observed server-side via the cloud ingest,
|
|
115
|
+
# not a local HTTP probe.
|
|
116
|
+
|
|
117
|
+
ENTRYPOINT ["/usr/local/bin/connector-entrypoint.sh"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Trillboards Edge AI SDK — CPU-only container
|
|
2
2
|
# Usage: docker build -f Dockerfile.cpu -t trillboards/edge-sdk:latest ../../
|
|
3
|
-
# Run: docker run -e DEVICE_TOKEN=xxx trillboards/edge-sdk:latest
|
|
3
|
+
# Run: docker run -e DEVICE_TOKEN=xxx ghcr.io/trillboards/edge-sdk:latest
|
|
4
4
|
|
|
5
5
|
FROM node:20-slim AS builder
|
|
6
6
|
|
|
@@ -65,26 +65,35 @@ RUN npm ci --legacy-peer-deps --omit=dev 2>/dev/null || npm install --legacy-pee
|
|
|
65
65
|
# Create models directory
|
|
66
66
|
RUN mkdir -p /app/models /app/data
|
|
67
67
|
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
68
|
+
# NO build-time model download. `models.trillboards.com` was never provisioned:
|
|
69
|
+
# the trillboards.com zone has no `models` record, so the name is caught by the
|
|
70
|
+
# `*.trillboards.com` wildcard A-alias and answered by the application ALB, which
|
|
71
|
+
# returns the marketing SPA's index.html for every path. (An invented subdomain
|
|
72
|
+
# resolves to the identical IPs — it is the wildcard, not a host.) No CloudFront
|
|
73
|
+
# alias, no S3 bucket, no terraform for it has ever existed; the hostname was
|
|
74
|
+
# invented in the same commit as the downloader (#996, 2026-03-09).
|
|
75
|
+
#
|
|
76
|
+
# The download this replaces verified only `if (size < 1000)`, and the SPA response
|
|
77
|
+
# is 2490 bytes — so it PASSED, printed "All models downloaded successfully", and
|
|
78
|
+
# baked two copies of a web page into the image as blazeface.onnx and yamnet.onnx.
|
|
79
|
+
# Reproduced verbatim on 2026-08-13: both files `HTML document text`, build exit 0.
|
|
80
|
+
#
|
|
81
|
+
# That was invisible only because this image had never been built: `runs-on` named
|
|
82
|
+
# a label no runner carries, so cpu/cuda/openvino queued until GitHub's 24h
|
|
83
|
+
# auto-cancel and ECR holds zero `latest`/`cuda`/`openvino` tags, ever. Fixing the
|
|
84
|
+
# runner label without this change would have published corrupt models under
|
|
85
|
+
# `:latest` — the default tag customers pull — on the very first successful build.
|
|
86
|
+
#
|
|
87
|
+
# Shipping model-free is the documented and verified-supported state: ONNX loads
|
|
88
|
+
# are wrapped, a missing file marks the camera/microphone subsystem ERROR and the
|
|
89
|
+
# agent continues (deploy/docker/README.md). It is also strictly better than the
|
|
90
|
+
# HTML it replaces — for audio, EdgeAgent only wires the classifier when the file
|
|
91
|
+
# EXISTS, so an absent file cleanly skips it while a 2490-byte HTML file loads,
|
|
92
|
+
# throws `Protobuf parsing failed`, and reports ERROR.
|
|
93
|
+
#
|
|
94
|
+
# To add models back, provision a real origin first, then restore a download that
|
|
95
|
+
# validates the ONNX magic bytes — never a size floor, which cannot tell a model
|
|
96
|
+
# from a web page. `trillboards-edge download-models` now refuses non-ONNX bytes.
|
|
88
97
|
|
|
89
98
|
# Environment variables with defaults
|
|
90
99
|
ENV NODE_ENV=production
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Trillboards Edge AI SDK — NVIDIA CUDA container
|
|
2
2
|
# Usage: docker build -f Dockerfile.cuda -t trillboards/edge-sdk:cuda ../../
|
|
3
|
-
# Run: docker run --gpus all -e DEVICE_TOKEN=xxx trillboards/edge-sdk:cuda
|
|
3
|
+
# Run: docker run --gpus all -e DEVICE_TOKEN=xxx ghcr.io/trillboards/edge-sdk:cuda
|
|
4
4
|
|
|
5
5
|
FROM nvidia/cuda:12.3.2-runtime-ubuntu22.04 AS base
|
|
6
6
|
|
|
@@ -67,26 +67,35 @@ RUN npm ci --legacy-peer-deps --omit=dev 2>/dev/null || npm install --legacy-pee
|
|
|
67
67
|
|
|
68
68
|
RUN mkdir -p /app/models /app/data
|
|
69
69
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
70
|
+
# NO build-time model download. `models.trillboards.com` was never provisioned:
|
|
71
|
+
# the trillboards.com zone has no `models` record, so the name is caught by the
|
|
72
|
+
# `*.trillboards.com` wildcard A-alias and answered by the application ALB, which
|
|
73
|
+
# returns the marketing SPA's index.html for every path. (An invented subdomain
|
|
74
|
+
# resolves to the identical IPs — it is the wildcard, not a host.) No CloudFront
|
|
75
|
+
# alias, no S3 bucket, no terraform for it has ever existed; the hostname was
|
|
76
|
+
# invented in the same commit as the downloader (#996, 2026-03-09).
|
|
77
|
+
#
|
|
78
|
+
# The download this replaces verified only `if (size < 1000)`, and the SPA response
|
|
79
|
+
# is 2490 bytes — so it PASSED, printed "All models downloaded successfully", and
|
|
80
|
+
# baked two copies of a web page into the image as blazeface.onnx and yamnet.onnx.
|
|
81
|
+
# Reproduced verbatim on 2026-08-13: both files `HTML document text`, build exit 0.
|
|
82
|
+
#
|
|
83
|
+
# That was invisible only because this image had never been built: `runs-on` named
|
|
84
|
+
# a label no runner carries, so cpu/cuda/openvino queued until GitHub's 24h
|
|
85
|
+
# auto-cancel and ECR holds zero `latest`/`cuda`/`openvino` tags, ever. Fixing the
|
|
86
|
+
# runner label without this change would have published corrupt models under
|
|
87
|
+
# `:latest` — the default tag customers pull — on the very first successful build.
|
|
88
|
+
#
|
|
89
|
+
# Shipping model-free is the documented and verified-supported state: ONNX loads
|
|
90
|
+
# are wrapped, a missing file marks the camera/microphone subsystem ERROR and the
|
|
91
|
+
# agent continues (deploy/docker/README.md). It is also strictly better than the
|
|
92
|
+
# HTML it replaces — for audio, EdgeAgent only wires the classifier when the file
|
|
93
|
+
# EXISTS, so an absent file cleanly skips it while a 2490-byte HTML file loads,
|
|
94
|
+
# throws `Protobuf parsing failed`, and reports ERROR.
|
|
95
|
+
#
|
|
96
|
+
# To add models back, provision a real origin first, then restore a download that
|
|
97
|
+
# validates the ONNX magic bytes — never a size floor, which cannot tell a model
|
|
98
|
+
# from a web page. `trillboards-edge download-models` now refuses non-ONNX bytes.
|
|
90
99
|
|
|
91
100
|
ENV NODE_ENV=production
|
|
92
101
|
ENV DEVICE_TOKEN=""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Trillboards Edge AI SDK — Intel OpenVINO container
|
|
2
2
|
# Usage: docker build -f Dockerfile.openvino -t trillboards/edge-sdk:openvino ../../
|
|
3
|
-
# Run: docker run -e DEVICE_TOKEN=xxx trillboards/edge-sdk:openvino
|
|
3
|
+
# Run: docker run -e DEVICE_TOKEN=xxx ghcr.io/trillboards/edge-sdk:openvino
|
|
4
4
|
# Optimized for Intel HD 620+ GPUs (Backpackster, NUC, mini-PCs)
|
|
5
5
|
|
|
6
6
|
FROM openvino/ubuntu22_runtime:2024.0.0 AS base
|
|
@@ -68,25 +68,35 @@ RUN npm ci --legacy-peer-deps --omit=dev 2>/dev/null || npm install --legacy-pee
|
|
|
68
68
|
|
|
69
69
|
RUN mkdir -p /app/models /app/data
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
# NO build-time model download. `models.trillboards.com` was never provisioned:
|
|
72
|
+
# the trillboards.com zone has no `models` record, so the name is caught by the
|
|
73
|
+
# `*.trillboards.com` wildcard A-alias and answered by the application ALB, which
|
|
74
|
+
# returns the marketing SPA's index.html for every path. (An invented subdomain
|
|
75
|
+
# resolves to the identical IPs — it is the wildcard, not a host.) No CloudFront
|
|
76
|
+
# alias, no S3 bucket, no terraform for it has ever existed; the hostname was
|
|
77
|
+
# invented in the same commit as the downloader (#996, 2026-03-09).
|
|
78
|
+
#
|
|
79
|
+
# The download this replaces verified only `if (size < 1000)`, and the SPA response
|
|
80
|
+
# is 2490 bytes — so it PASSED, printed "All models downloaded successfully", and
|
|
81
|
+
# baked two copies of a web page into the image as blazeface.onnx and yamnet.onnx.
|
|
82
|
+
# Reproduced verbatim on 2026-08-13: both files `HTML document text`, build exit 0.
|
|
83
|
+
#
|
|
84
|
+
# That was invisible only because this image had never been built: `runs-on` named
|
|
85
|
+
# a label no runner carries, so cpu/cuda/openvino queued until GitHub's 24h
|
|
86
|
+
# auto-cancel and ECR holds zero `latest`/`cuda`/`openvino` tags, ever. Fixing the
|
|
87
|
+
# runner label without this change would have published corrupt models under
|
|
88
|
+
# `:latest` — the default tag customers pull — on the very first successful build.
|
|
89
|
+
#
|
|
90
|
+
# Shipping model-free is the documented and verified-supported state: ONNX loads
|
|
91
|
+
# are wrapped, a missing file marks the camera/microphone subsystem ERROR and the
|
|
92
|
+
# agent continues (deploy/docker/README.md). It is also strictly better than the
|
|
93
|
+
# HTML it replaces — for audio, EdgeAgent only wires the classifier when the file
|
|
94
|
+
# EXISTS, so an absent file cleanly skips it while a 2490-byte HTML file loads,
|
|
95
|
+
# throws `Protobuf parsing failed`, and reports ERROR.
|
|
96
|
+
#
|
|
97
|
+
# To add models back, provision a real origin first, then restore a download that
|
|
98
|
+
# validates the ONNX magic bytes — never a size floor, which cannot tell a model
|
|
99
|
+
# from a web page. `trillboards-edge download-models` now refuses non-ONNX bytes.
|
|
90
100
|
|
|
91
101
|
ENV NODE_ENV=production
|
|
92
102
|
ENV DEVICE_TOKEN=""
|
package/deploy/docker/README.md
CHANGED
|
@@ -179,14 +179,19 @@ Two directories should be persisted across container restarts:
|
|
|
179
179
|
| `/app/models` | ONNX model files | `blazeface.onnx`, `yamnet.onnx`, and any OTA-updated models |
|
|
180
180
|
| `/app/data` | Agent persistent state | `signal_buffer.db` (SQLite signal buffer), device identity fingerprint, federated learning gradients |
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
**These images ship with NO bundled ONNX models.** `/app/models` is created empty.
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
They used to claim a build-time download from `https://models.trillboards.com/`, but that host was never provisioned: `trillboards.com` has no `models` DNS record, so the name is caught by the `*.trillboards.com` wildcard and answered by the application load balancer, which returns the marketing site's `index.html` — HTTP 200, `text/html`, 2490 bytes — for every path. The build's only integrity check was `if (size < 1000)`, which 2490 clears, so the download "succeeded" and wrote a web page into `blazeface.onnx`. That was never noticed because these three images had never been built at all (their `runs-on` named a label no runner carries, so every run queued until GitHub's 24h auto-cancel).
|
|
185
|
+
|
|
186
|
+
Running without these models is a supported state: the ONNX load is wrapped, and a missing model marks the camera/microphone subsystem `ERROR` while the agent keeps running. Face detection and audio classification are unavailable until you supply the files.
|
|
187
|
+
|
|
188
|
+
**Supplying models on the host:**
|
|
189
|
+
|
|
190
|
+
Place `blazeface.onnx` (BlazeFace 128×128) and `yamnet.onnx` (YAMNet classification) in a directory and bind-mount it. `trillboards-edge download-models` and `scripts/download-models.sh` will fetch them once a real origin is configured; both now verify the ONNX magic bytes and refuse — loudly, deleting the file — anything that is not a model, so they can no longer write an HTML error page and report `[OK]`.
|
|
185
191
|
|
|
186
192
|
```bash
|
|
187
193
|
mkdir -p /opt/trillboards/models
|
|
188
|
-
|
|
189
|
-
curl -o /opt/trillboards/models/yamnet.onnx https://models.trillboards.com/yamnet_classification.onnx
|
|
194
|
+
# Copy blazeface.onnx and yamnet.onnx into /opt/trillboards/models
|
|
190
195
|
```
|
|
191
196
|
|
|
192
197
|
Then mount as a bind volume:
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# connector-entrypoint.sh — Docker entrypoint for the BYO-CCTV connector. On first
|
|
3
|
+
# run it pairs with a short-lived claim code (exchanging it for a per-feed device
|
|
4
|
+
# token, which is NEVER baked into the image) and writes the paired config;
|
|
5
|
+
# thereafter it senses. Mount /app/data on a volume so the paired config survives
|
|
6
|
+
# restarts — re-pairing needs a fresh code from the portal.
|
|
7
|
+
#
|
|
8
|
+
# ── The mode is a build arg, and the DEFAULT is auto-sense ──────────────────
|
|
9
|
+
#
|
|
10
|
+
# This entrypoint used to `exec cloud-sense` unconditionally, so the Docker path
|
|
11
|
+
# forced the expensive mode while §A5 of cctv-rushi-activation.md recommended
|
|
12
|
+
# auto-sense and §0 told the reader "the connector runs auto-sense". It did not.
|
|
13
|
+
#
|
|
14
|
+
# auto-sense probes `localSensing.litertLmBaseUrl` at startup and commits to
|
|
15
|
+
# local if a healthy litert-lm is hosting the model. An operator who runs
|
|
16
|
+
# `litert-lm serve` on the host and points the connector at it (--network host,
|
|
17
|
+
# or litertLmBaseUrl: http://host.docker.internal:9379 in the config) gets the
|
|
18
|
+
# on-device path from this SAME image, at no per-frame cloud cost.
|
|
19
|
+
#
|
|
20
|
+
# ── Why this image must also enable cloud FALLBACK ─────────────────────────
|
|
21
|
+
#
|
|
22
|
+
# `auto-sense` treats cloud inference as opt-in: with `allowCloudFallback`
|
|
23
|
+
# unset it probes, fails, and WAITS — uploading nothing — because on a laptop or
|
|
24
|
+
# a mini-PC that is downloading a 2.4 GB model, idling briefly is better than
|
|
25
|
+
# silently starting a meter. That is right for the macOS/Windows installers,
|
|
26
|
+
# which provision a local model.
|
|
27
|
+
#
|
|
28
|
+
# It is wrong here. This image bundles NO model and never downloads one, so a
|
|
29
|
+
# plain `docker run` has nothing to wait FOR: without the fallback the connector
|
|
30
|
+
# would sit idle forever and the operator would see cameras that never report.
|
|
31
|
+
# So the entrypoint asserts `localSensing.allowCloudFallback: true` into the
|
|
32
|
+
# paired config for auto-sense. Net behaviour for a plain run is identical to
|
|
33
|
+
# the old `:cloud-sense` — frames go to the cloud VLM — except the connector now
|
|
34
|
+
# AUTO-UPGRADES to on-device with no restart the moment a litert-lm becomes
|
|
35
|
+
# reachable.
|
|
36
|
+
#
|
|
37
|
+
# TRILLBOARDS_SENSE_MODE overrides at run time: cloud-sense forces the cloud VLM,
|
|
38
|
+
# local-sense refuses to fall back.
|
|
39
|
+
set -eu
|
|
40
|
+
|
|
41
|
+
CONFIG="${TRILLBOARDS_CONFIG:-/app/data/trillboards.config.json}"
|
|
42
|
+
API_BASE="${TRILLBOARDS_API_BASE:-https://api.trillboards.com}"
|
|
43
|
+
CLI="node packages/edge-sdk/dist/cli.js"
|
|
44
|
+
# Baked per image variant (:auto-sense -> auto-sense, :cloud-sense -> cloud-sense),
|
|
45
|
+
# overridable per run.
|
|
46
|
+
MODE="${TRILLBOARDS_SENSE_MODE:-auto-sense}"
|
|
47
|
+
|
|
48
|
+
case "$MODE" in
|
|
49
|
+
auto-sense|cloud-sense|local-sense) ;;
|
|
50
|
+
*)
|
|
51
|
+
echo "[connector] Unknown TRILLBOARDS_SENSE_MODE '$MODE' (want auto-sense|cloud-sense|local-sense)." >&2
|
|
52
|
+
exit 1
|
|
53
|
+
;;
|
|
54
|
+
esac
|
|
55
|
+
|
|
56
|
+
if [ ! -f "$CONFIG" ]; then
|
|
57
|
+
if [ -z "${TRILLBOARDS_CLAIM_CODE:-}" ]; then
|
|
58
|
+
echo "[connector] No paired config and no TRILLBOARDS_CLAIM_CODE." >&2
|
|
59
|
+
echo "[connector] Get a code from the Trillboards portal (Add a camera -> Install), then:" >&2
|
|
60
|
+
echo "[connector] docker run -e TRILLBOARDS_CLAIM_CODE=XXXX-XXXX -v trillboards:/app/data ghcr.io/trillboards/edge-sdk:auto-sense" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
echo "[connector] Pairing with claim code (token delivered at pair, never baked)..."
|
|
64
|
+
# shellcheck disable=SC2086
|
|
65
|
+
$CLI pair --code "$TRILLBOARDS_CLAIM_CODE" --config "$CONFIG" --api-base "$API_BASE"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [ "$MODE" = "auto-sense" ]; then
|
|
69
|
+
# This image ships no local model, so auto-sense MUST be allowed to fall back
|
|
70
|
+
# or it waits forever for something that will never arrive. Idempotent, and it
|
|
71
|
+
# never touches a value the operator set themselves.
|
|
72
|
+
#
|
|
73
|
+
# The assert WRITES, and a config file is very often mounted read-only
|
|
74
|
+
# (-v ./trillboards.config.json:/app/data/trillboards.config.json:ro)
|
|
75
|
+
# which is a perfectly reasonable way to run this image and skips pairing
|
|
76
|
+
# entirely. Under `set -e` an unguarded writeFileSync there killed the
|
|
77
|
+
# container on every single start, with a raw stack trace, because a DEFAULT
|
|
78
|
+
# could not be persisted. So a failed write degrades to exactly the behaviour
|
|
79
|
+
# the write was there to guarantee — cloud inference this run — and says so.
|
|
80
|
+
set +e
|
|
81
|
+
node -e '
|
|
82
|
+
const fs = require("fs");
|
|
83
|
+
const p = process.argv[1];
|
|
84
|
+
let c;
|
|
85
|
+
try { c = JSON.parse(fs.readFileSync(p, "utf-8")); } catch (e) {
|
|
86
|
+
console.error("[connector] cannot read config for fallback assert: " + e.message);
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
c.localSensing = c.localSensing || {};
|
|
90
|
+
if (c.localSensing.allowCloudFallback === true) process.exit(0);
|
|
91
|
+
if (c.localSensing.allowCloudFallback === false) {
|
|
92
|
+
console.error("[connector] localSensing.allowCloudFallback is explicitly false — honoring it; this connector will WAIT for a local runtime and upload nothing until one is reachable.");
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
c.localSensing.allowCloudFallback = true;
|
|
96
|
+
try {
|
|
97
|
+
fs.writeFileSync(p, JSON.stringify(c, null, 2));
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error("[connector] cannot persist localSensing.allowCloudFallback to " + p + ": " + e.message);
|
|
100
|
+
process.exit(3);
|
|
101
|
+
}
|
|
102
|
+
console.error("[connector] enabled localSensing.allowCloudFallback (this image bundles no local model).");
|
|
103
|
+
' "$CONFIG"
|
|
104
|
+
FALLBACK_ASSERT_RC=$?
|
|
105
|
+
set -e
|
|
106
|
+
if [ "$FALLBACK_ASSERT_RC" = "3" ]; then
|
|
107
|
+
echo "[connector] config is not writable, so auto-sense cannot record its cloud-fallback policy." >&2
|
|
108
|
+
echo "[connector] Running cloud-sense for this process instead — cameras still report. To get the" >&2
|
|
109
|
+
echo "[connector] on-device auto-upgrade, mount the config writable or set localSensing.allowCloudFallback." >&2
|
|
110
|
+
MODE=cloud-sense
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [ "$MODE" = "auto-sense" ]; then
|
|
115
|
+
echo "[connector] Starting $MODE (API $API_BASE) — probing for on-device inference," >&2
|
|
116
|
+
echo "[connector] falling back to the cloud VLM if none is reachable, and" >&2
|
|
117
|
+
echo "[connector] auto-upgrading to on-device (no restart) if one appears." >&2
|
|
118
|
+
else
|
|
119
|
+
echo "[connector] Starting explicitly selected $MODE (API $API_BASE)..." >&2
|
|
120
|
+
fi
|
|
121
|
+
# shellcheck disable=SC2086
|
|
122
|
+
exec $CLI "$MODE" --config "$CONFIG"
|
package/deploy/helm/README.md
CHANGED
|
@@ -450,7 +450,7 @@ kubectl label node <node-name> trillboards.com/edge-device=true
|
|
|
450
450
|
### 4. Customizing the kubectl Manifest
|
|
451
451
|
|
|
452
452
|
Edit `daemonset.yaml` directly to change:
|
|
453
|
-
- Image tag (line: `image: trillboards/edge-sdk:latest`)
|
|
453
|
+
- Image tag (line: `image: ghcr.io/trillboards/edge-sdk:latest`)
|
|
454
454
|
- Environment variable values
|
|
455
455
|
- Resource requests and limits
|
|
456
456
|
- Node selector labels
|