@trillboards/edge-sdk 0.2.4 → 0.2.7
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 -5
- 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-service.ps1 +6 -1
- 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 +557 -0
- package/dist/CloudSensingManager.d.ts.map +1 -0
- package/dist/CloudSensingManager.js +1267 -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 +266 -0
- package/dist/autoSense.d.ts.map +1 -0
- package/dist/autoSense.js +606 -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 +526 -27
- 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 +15 -13
- 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 +25 -87
- 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 +284 -0
- package/dist/localSensing.d.ts.map +1 -0
- package/dist/localSensing.js +512 -0
- package/dist/localSensing.js.map +1 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +3 -1
- package/dist/mcp-server.js.map +1 -1
- package/dist/modelIntegrity.d.ts +80 -0
- package/dist/modelIntegrity.d.ts.map +1 -0
- package/dist/modelIntegrity.js +194 -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 +86 -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
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# test-install-pair.sh — local, no-sudo, self-cleaning end-to-end proof that the
|
|
4
|
+
# Trillboards "cloud-sense" CCTV connector .pkg builds, installs home-domain,
|
|
5
|
+
# pairs, and actually senses (real bundled-ffmpeg JPEG capture → POST) on THIS
|
|
6
|
+
# Mac.
|
|
7
|
+
#
|
|
8
|
+
# Acceptance (each step is a TAP assert):
|
|
9
|
+
# 1. Build the .pkg (build-macos-pkg.sh).
|
|
10
|
+
# 2. Install it home-domain (installer -target CurrentUserHomeDirectory, no
|
|
11
|
+
# sudo). Assert node + ffmpeg + cli.js landed and `version` runs via the
|
|
12
|
+
# bundled node.
|
|
13
|
+
# 3. Start a local STUB pair+analyze server returning a valid pair response
|
|
14
|
+
# for a known claim code, with one camera pointed at a LOCAL synthetic
|
|
15
|
+
# ffmpeg source (a generated mp4 via file:), so the real connector code
|
|
16
|
+
# path runs without an RTSP-server rabbit-hole.
|
|
17
|
+
# 4. Pair headlessly (trillboards-edge pair --code … --api-base …). Assert
|
|
18
|
+
# exit 0, config written 0600 with the camera + deviceToken.
|
|
19
|
+
# 5. Start the connector from that config (cloud-sense) and assert it produces
|
|
20
|
+
# a real JPEG frame via the BUNDLED ffmpeg and POSTs it to
|
|
21
|
+
# /v2/earner/audience-analyze carrying the paired X-Device-Token. Also
|
|
22
|
+
# assert the per-user LaunchAgent bootstraps + senses end-to-end.
|
|
23
|
+
# 6. Tear everything down (bootout agent, uninstall home tree, remove temp
|
|
24
|
+
# config/logs, kill the stub) — idempotent / re-runnable.
|
|
25
|
+
#
|
|
26
|
+
# Re-run: bash deploy/macos/test-install-pair.sh
|
|
27
|
+
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
|
|
30
|
+
# ─── Locate ──────────────────────────────────────────────────────────────────
|
|
31
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # deploy/macos
|
|
32
|
+
PKG_ROOT="$(cd "$HERE/../.." && pwd)" # edge-sdk
|
|
33
|
+
WORKSPACE_ROOT="$(cd "$PKG_ROOT/../.." && pwd)"
|
|
34
|
+
|
|
35
|
+
EDGE_ROOT="$HOME/Library/Trillboards/edge"
|
|
36
|
+
APP_SUPPORT="$HOME/Library/Application Support/Trillboards"
|
|
37
|
+
INSTALLED_CONFIG="$APP_SUPPORT/trillboards.config.json"
|
|
38
|
+
LOG_DIR="$HOME/Library/Logs/Trillboards"
|
|
39
|
+
LA_PLIST="$HOME/Library/LaunchAgents/com.trillboards.edge.plist"
|
|
40
|
+
LABEL="com.trillboards.edge"
|
|
41
|
+
# Customer-facing native setup application installed under the user's home.
|
|
42
|
+
SETUP_APP="$HOME/Applications/Trillboards CCTV Setup.app"
|
|
43
|
+
SETUP_APP_BIN="$SETUP_APP/Contents/MacOS/Trillboards CCTV Setup"
|
|
44
|
+
LEGACY_PAIR_LAUNCHER="$HOME/Applications/Trillboards Pair.command"
|
|
45
|
+
UID_N="$(id -u)"
|
|
46
|
+
|
|
47
|
+
# Scratch (under the worktree's build dir so it's easy to clean).
|
|
48
|
+
SCRATCH="$HERE/dist-installer/test-scratch"
|
|
49
|
+
SAMPLE_MP4="$SCRATCH/cctv_sample.mp4"
|
|
50
|
+
STUB_JS="$SCRATCH/stub_server.js"
|
|
51
|
+
STUB_OUT="$SCRATCH/stub.out"
|
|
52
|
+
TEST_CONFIG="$SCRATCH/paired.config.json"
|
|
53
|
+
CS_OUT="$SCRATCH/cloud-sense.out"
|
|
54
|
+
STUB_PID=""
|
|
55
|
+
CS_PID=""
|
|
56
|
+
|
|
57
|
+
# ─── TAP plumbing ────────────────────────────────────────────────────────────
|
|
58
|
+
TAP_N=0
|
|
59
|
+
FAILS=0
|
|
60
|
+
ok() { TAP_N=$((TAP_N+1)); printf 'ok %d - %s\n' "$TAP_N" "$1"; }
|
|
61
|
+
nok() { TAP_N=$((TAP_N+1)); FAILS=$((FAILS+1)); printf 'not ok %d - %s\n' "$TAP_N" "$1"; [ -n "${2:-}" ] && printf ' ---\n %s\n ---\n' "$2"; }
|
|
62
|
+
diag(){ printf '# %s\n' "$1"; }
|
|
63
|
+
|
|
64
|
+
# Portable octal-mode reader. The `stat` on PATH may be GNU coreutils (uses
|
|
65
|
+
# `-c %a`) or BSD (uses `-f %Lp`), and on this Mac Homebrew shims GNU stat in
|
|
66
|
+
# front — so probe both, and finally fall back to the bundled node (always
|
|
67
|
+
# correct). Echoes the 3-4 digit octal mode (e.g. 600) or empty on failure.
|
|
68
|
+
file_mode() {
|
|
69
|
+
local f="$1" m=""
|
|
70
|
+
m="$(stat -c '%a' "$f" 2>/dev/null)" && [ -n "$m" ] && { echo "$m"; return; }
|
|
71
|
+
m="$(stat -f '%Lp' "$f" 2>/dev/null)" && [ -n "$m" ] && { echo "$m"; return; }
|
|
72
|
+
if [ -x "$EDGE_ROOT/bin/node" ]; then
|
|
73
|
+
"$EDGE_ROOT/bin/node" -e "process.stdout.write((require('fs').statSync(process.argv[1]).mode & 0o777).toString(8))" "$f" 2>/dev/null
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# ─── Teardown (idempotent) — runs on EXIT so partial runs still clean up ──────
|
|
78
|
+
teardown() {
|
|
79
|
+
diag "teardown…"
|
|
80
|
+
pkill -x 'Trillboards CCTV Setup' >/dev/null 2>&1 || true
|
|
81
|
+
[ -n "$CS_PID" ] && kill "$CS_PID" >/dev/null 2>&1 || true
|
|
82
|
+
[ -n "$STUB_PID" ] && kill "$STUB_PID" >/dev/null 2>&1 || true
|
|
83
|
+
pkill -f "$STUB_JS" >/dev/null 2>&1 || true
|
|
84
|
+
launchctl bootout "gui/$UID_N/$LABEL" >/dev/null 2>&1 || true
|
|
85
|
+
launchctl unload "$LA_PLIST" >/dev/null 2>&1 || true
|
|
86
|
+
rm -f "$LA_PLIST"
|
|
87
|
+
rm -rf "$HOME/Library/Trillboards" "$APP_SUPPORT" "$LOG_DIR"
|
|
88
|
+
rm -rf "$SETUP_APP"
|
|
89
|
+
rm -f "$LEGACY_PAIR_LAUNCHER"
|
|
90
|
+
# Forget the home-domain receipt so a re-run installs cleanly.
|
|
91
|
+
pkgutil --volume "$HOME" --forget "$LABEL" >/dev/null 2>&1 || true
|
|
92
|
+
rm -rf "$SCRATCH"
|
|
93
|
+
}
|
|
94
|
+
trap teardown EXIT
|
|
95
|
+
|
|
96
|
+
# Pre-clean anything left by a prior aborted run BEFORE we start.
|
|
97
|
+
pkill -x 'Trillboards CCTV Setup' >/dev/null 2>&1 || true
|
|
98
|
+
launchctl bootout "gui/$UID_N/$LABEL" >/dev/null 2>&1 || true
|
|
99
|
+
rm -f "$LA_PLIST"
|
|
100
|
+
rm -rf "$HOME/Library/Trillboards" "$APP_SUPPORT" "$LOG_DIR"
|
|
101
|
+
rm -rf "$SETUP_APP"
|
|
102
|
+
rm -f "$LEGACY_PAIR_LAUNCHER"
|
|
103
|
+
pkgutil --volume "$HOME" --forget "$LABEL" >/dev/null 2>&1 || true
|
|
104
|
+
mkdir -p "$SCRATCH"
|
|
105
|
+
|
|
106
|
+
echo "TAP version 13"
|
|
107
|
+
diag "Trillboards Edge — macOS install+pair self-test"
|
|
108
|
+
diag "edge-sdk root: $PKG_ROOT"
|
|
109
|
+
|
|
110
|
+
# ─── Preconditions ───────────────────────────────────────────────────────────
|
|
111
|
+
command -v ffmpeg >/dev/null 2>&1 || { diag "ffmpeg required on PATH to generate the synthetic source"; }
|
|
112
|
+
if ffmpeg -hide_banner -loglevel error -f lavfi -i "testsrc=size=320x240:rate=5" -frames:v 10 -y "$SAMPLE_MP4" >/dev/null 2>&1 && [ -s "$SAMPLE_MP4" ]; then
|
|
113
|
+
ok "generated synthetic camera source (testsrc → $SAMPLE_MP4)"
|
|
114
|
+
else
|
|
115
|
+
nok "could not generate synthetic camera source" "ffmpeg lavfi testsrc failed"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# ─── STEP 1: build the pkg ───────────────────────────────────────────────────
|
|
119
|
+
diag "STEP 1 — build the .pkg"
|
|
120
|
+
# Offline is fine if the host already has a matching node; default tries the
|
|
121
|
+
# pinned official download and falls back to host node automatically.
|
|
122
|
+
BUILD_LOG="$SCRATCH/build.log"
|
|
123
|
+
if "$HERE/build-macos-pkg.sh" >"$BUILD_LOG" 2>&1; then
|
|
124
|
+
PKG="$(tail -1 "$BUILD_LOG")"
|
|
125
|
+
if [ -f "$PKG" ]; then
|
|
126
|
+
ok "build-macos-pkg.sh produced a pkg ($(du -h "$PKG" | cut -f1)): $PKG"
|
|
127
|
+
else
|
|
128
|
+
nok "build succeeded but pkg path not found" "last line: $PKG"
|
|
129
|
+
PKG=""
|
|
130
|
+
fi
|
|
131
|
+
else
|
|
132
|
+
nok "build-macos-pkg.sh failed" "$(tail -5 "$BUILD_LOG")"
|
|
133
|
+
PKG=""
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# ─── STEP 2: install home-domain (no sudo) + assert the tree ─────────────────
|
|
137
|
+
diag "STEP 2 — install home-domain (no sudo) + assert tree"
|
|
138
|
+
if [ -n "$PKG" ]; then
|
|
139
|
+
if installer -pkg "$PKG" -target CurrentUserHomeDirectory >"$SCRATCH/install.log" 2>&1; then
|
|
140
|
+
ok "installed home-domain with no sudo"
|
|
141
|
+
else
|
|
142
|
+
nok "home-domain install failed" "$(tail -5 "$SCRATCH/install.log")"
|
|
143
|
+
fi
|
|
144
|
+
else
|
|
145
|
+
nok "skipped install (no pkg)"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# node + ffmpeg + launcher present and executable
|
|
149
|
+
for b in node ffmpeg trillboards-edge trillboards-pair; do
|
|
150
|
+
if [ -x "$EDGE_ROOT/bin/$b" ]; then ok "bundled bin/$b is present + executable"; else nok "bundled bin/$b missing or not executable"; fi
|
|
151
|
+
done
|
|
152
|
+
# cli.js present
|
|
153
|
+
if [ -f "$EDGE_ROOT/lib/edge-sdk/dist/cli.js" ]; then ok "bundled cli.js present"; else nok "bundled cli.js missing"; fi
|
|
154
|
+
|
|
155
|
+
# bundled node runs
|
|
156
|
+
NODE_VER="$("$EDGE_ROOT/bin/node" --version 2>/dev/null || true)"
|
|
157
|
+
if printf '%s' "$NODE_VER" | grep -q '^v'; then ok "bundled node runs ($NODE_VER)"; else nok "bundled node did not run" "$NODE_VER"; fi
|
|
158
|
+
|
|
159
|
+
# bundled ffmpeg runs
|
|
160
|
+
if "$EDGE_ROOT/bin/ffmpeg" -version >/dev/null 2>&1; then ok "bundled ffmpeg runs"; else nok "bundled ffmpeg did not run"; fi
|
|
161
|
+
|
|
162
|
+
# the connector runs through the bundled launcher (this also proves the lazy
|
|
163
|
+
# require keeps the cloud-sense entrypoint free of the ONNX stack — the bundle
|
|
164
|
+
# ships NO onnxruntime-node).
|
|
165
|
+
LAUNCH_VER="$("$EDGE_ROOT/bin/trillboards-edge" version 2>&1 || true)"
|
|
166
|
+
if printf '%s' "$LAUNCH_VER" | grep -q 'edge-sdk v'; then ok "trillboards-edge version works via bundled node ($LAUNCH_VER)"; else nok "trillboards-edge version failed" "$LAUNCH_VER"; fi
|
|
167
|
+
|
|
168
|
+
# explicitly assert the heavy stack is NOT bundled (design contract)
|
|
169
|
+
if [ ! -d "$EDGE_ROOT/lib/node_modules/onnxruntime-node" ]; then ok "ONNX runtime correctly EXCLUDED from the bundle"; else nok "onnxruntime-node was bundled (should be excluded)"; fi
|
|
170
|
+
|
|
171
|
+
# postinstall created the runtime dirs
|
|
172
|
+
if [ -d "$APP_SUPPORT" ]; then ok "postinstall created Application Support dir"; else nok "Application Support dir not created"; fi
|
|
173
|
+
if [ -d "$LOG_DIR" ]; then ok "postinstall created Logs dir"; else nok "Logs dir not created"; fi
|
|
174
|
+
|
|
175
|
+
# The native setup application replaces the old .command launcher, so Finder
|
|
176
|
+
# never opens Terminal during customer setup.
|
|
177
|
+
if [ -d "$SETUP_APP" ]; then ok "native setup application installed in ~/Applications"; else nok "native setup application missing" "$SETUP_APP"; fi
|
|
178
|
+
if [ -x "$SETUP_APP_BIN" ]; then ok "native setup application executable is present"; else nok "native setup application executable missing" "$SETUP_APP_BIN"; fi
|
|
179
|
+
if [ ! -e "$LEGACY_PAIR_LAUNCHER" ]; then ok "legacy Terminal .command launcher is absent"; else nok "legacy Terminal launcher still exists" "$LEGACY_PAIR_LAUNCHER"; fi
|
|
180
|
+
|
|
181
|
+
# ─── STEP 3: start the stub pair+analyze server ──────────────────────────────
|
|
182
|
+
diag "STEP 3 — start local stub pair+analyze server"
|
|
183
|
+
STATS_FILE="$SCRATCH/stub.stats.json"
|
|
184
|
+
# Config values are passed via env (NOT templated into JS) so the source stays
|
|
185
|
+
# inert and robust. The camera rtspUrl points at the synthetic mp4 via file:,
|
|
186
|
+
# so the connector's REAL RTSPCameraProvider → bundled ffmpeg → mjpeg path runs
|
|
187
|
+
# without standing up an actual RTSP server.
|
|
188
|
+
# NOTE: quoted heredoc ('EOF') — no shell expansion inside the JS.
|
|
189
|
+
cat > "$STUB_JS" <<'EOF'
|
|
190
|
+
const http = require('http');
|
|
191
|
+
const fs = require('fs');
|
|
192
|
+
const STATS_FILE = process.env.STATS_FILE;
|
|
193
|
+
const SAMPLE = process.env.SAMPLE_MP4;
|
|
194
|
+
const received = { pair: 0, analyze: 0, lastDeviceToken: null, lastBodyLen: 0, sawJpeg: false };
|
|
195
|
+
const CONFIG = {
|
|
196
|
+
apiBaseUrl: 'http://127.0.0.1:__PORT__',
|
|
197
|
+
cloudSensing: { enabled: true, periodicIntervalMs: 1500, faceTriggeredIntervalMs: 1500, jpegQuality: 70 },
|
|
198
|
+
cameras: [{
|
|
199
|
+
name: 'Front Door',
|
|
200
|
+
screenId: 'scr_test_1',
|
|
201
|
+
deviceToken: 'tok_TEST_123',
|
|
202
|
+
rtspUrl: SAMPLE.startsWith('/') ? 'file:' + SAMPLE : SAMPLE,
|
|
203
|
+
width: 320,
|
|
204
|
+
height: 240,
|
|
205
|
+
}],
|
|
206
|
+
};
|
|
207
|
+
function flush() { try { fs.writeFileSync(STATS_FILE, JSON.stringify(received)); } catch (e) {} }
|
|
208
|
+
const server = http.createServer((req, res) => {
|
|
209
|
+
let body = '';
|
|
210
|
+
req.on('data', (c) => (body += c));
|
|
211
|
+
req.on('end', () => {
|
|
212
|
+
if (req.url.endsWith('/v2/earner/cameras/pair')) {
|
|
213
|
+
received.pair++;
|
|
214
|
+
const cfg = JSON.parse(JSON.stringify(CONFIG).replace('__PORT__', String(server.address().port)));
|
|
215
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
216
|
+
res.end(JSON.stringify(cfg));
|
|
217
|
+
} else if (req.url.endsWith('/v2/earner/audience-analyze')) {
|
|
218
|
+
received.analyze++;
|
|
219
|
+
received.lastDeviceToken = req.headers['x-device-token'] || null;
|
|
220
|
+
received.lastBodyLen = body.length;
|
|
221
|
+
// Body is JSON { imageBase64, ... }; decode + verify the JPEG SOI marker.
|
|
222
|
+
try {
|
|
223
|
+
const j = JSON.parse(body);
|
|
224
|
+
if (j.imageBase64) {
|
|
225
|
+
const buf = Buffer.from(j.imageBase64, 'base64');
|
|
226
|
+
received.sawJpeg = buf.length > 2 && buf[0] === 0xff && buf[1] === 0xd8;
|
|
227
|
+
}
|
|
228
|
+
} catch (e) {}
|
|
229
|
+
flush();
|
|
230
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
231
|
+
res.end(JSON.stringify({ ok: true, profileId: 'p_test' }));
|
|
232
|
+
} else {
|
|
233
|
+
res.writeHead(404);
|
|
234
|
+
res.end('nope');
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
server.listen(0, '127.0.0.1', () => { process.stdout.write('PORT=' + server.address().port + '\n'); flush(); });
|
|
239
|
+
process.on('SIGTERM', () => { flush(); process.exit(0); });
|
|
240
|
+
EOF
|
|
241
|
+
|
|
242
|
+
STATS_FILE="$STATS_FILE" SAMPLE_MP4="$SAMPLE_MP4" "$EDGE_ROOT/bin/node" "$STUB_JS" >"$STUB_OUT" 2>&1 &
|
|
243
|
+
STUB_PID=$!
|
|
244
|
+
# Wait for the port line (up to ~5s).
|
|
245
|
+
PORT=""
|
|
246
|
+
for _ in $(seq 1 25); do
|
|
247
|
+
PORT="$(grep -oE 'PORT=[0-9]+' "$STUB_OUT" 2>/dev/null | head -1 | cut -d= -f2)"
|
|
248
|
+
[ -n "$PORT" ] && break
|
|
249
|
+
sleep 0.2
|
|
250
|
+
done
|
|
251
|
+
if [ -n "$PORT" ] && kill -0 "$STUB_PID" 2>/dev/null; then
|
|
252
|
+
ok "stub server listening on 127.0.0.1:$PORT"
|
|
253
|
+
else
|
|
254
|
+
nok "stub server failed to start" "$(cat "$STUB_OUT" 2>/dev/null)"
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
# ─── STEP 4: pair headlessly + assert config ─────────────────────────────────
|
|
258
|
+
diag "STEP 4 — headless pair + assert config (0600, deviceToken, camera)"
|
|
259
|
+
if [ -n "$PORT" ]; then
|
|
260
|
+
if "$EDGE_ROOT/bin/trillboards-edge" pair --code TEST-CODE --api-base "http://127.0.0.1:$PORT" --config "$TEST_CONFIG" >"$SCRATCH/pair.log" 2>&1; then
|
|
261
|
+
ok "pair exited 0"
|
|
262
|
+
else
|
|
263
|
+
nok "pair exited non-zero" "$(cat "$SCRATCH/pair.log")"
|
|
264
|
+
fi
|
|
265
|
+
else
|
|
266
|
+
nok "skipped pair (no stub port)"
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
if [ -f "$TEST_CONFIG" ]; then
|
|
270
|
+
ok "config file written: $TEST_CONFIG"
|
|
271
|
+
MODE="$(file_mode "$TEST_CONFIG")"
|
|
272
|
+
if [ "$MODE" = "600" ]; then ok "config is mode 0600"; else nok "config mode is '$MODE' (expected 600)"; fi
|
|
273
|
+
if grep -q 'tok_TEST_123' "$TEST_CONFIG"; then ok "config carries the paired deviceToken"; else nok "config missing deviceToken"; fi
|
|
274
|
+
if grep -q 'scr_test_1' "$TEST_CONFIG"; then ok "config carries the paired camera (screenId)"; else nok "config missing camera"; fi
|
|
275
|
+
else
|
|
276
|
+
nok "config file was not written"
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
# ─── STEP 5a: connector starts from the config + senses (foreground, bounded) ─
|
|
280
|
+
diag "STEP 5a — cloud-sense from config: real bundled-ffmpeg JPEG → POST"
|
|
281
|
+
if [ -f "$TEST_CONFIG" ]; then
|
|
282
|
+
"$EDGE_ROOT/bin/trillboards-edge" cloud-sense --config "$TEST_CONFIG" >"$CS_OUT" 2>&1 &
|
|
283
|
+
CS_PID=$!
|
|
284
|
+
# Wait until we see at least one analyze POST (cadence is 1.5s) — up to ~12s.
|
|
285
|
+
SENSED=0
|
|
286
|
+
for _ in $(seq 1 40); do
|
|
287
|
+
if grep -q 'analyze → HTTP 200' "$CS_OUT" 2>/dev/null; then SENSED=1; break; fi
|
|
288
|
+
if ! kill -0 "$CS_PID" 2>/dev/null; then break; fi # process died early
|
|
289
|
+
sleep 0.3
|
|
290
|
+
done
|
|
291
|
+
kill "$CS_PID" >/dev/null 2>&1 || true
|
|
292
|
+
wait "$CS_PID" 2>/dev/null || true
|
|
293
|
+
CS_PID=""
|
|
294
|
+
|
|
295
|
+
if [ "$SENSED" -eq 1 ]; then
|
|
296
|
+
ok "cloud-sense captured + POSTed a frame (HTTP 200 in connector log)"
|
|
297
|
+
else
|
|
298
|
+
nok "cloud-sense did not produce a successful POST" "$(tail -8 "$CS_OUT" 2>/dev/null)"
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# Inspect what the stub actually received.
|
|
302
|
+
if [ -f "$STATS_FILE" ]; then
|
|
303
|
+
A_COUNT="$("$EDGE_ROOT/bin/node" -e "process.stdout.write(String((require('$STATS_FILE').analyze)||0))" 2>/dev/null || echo 0)"
|
|
304
|
+
A_TOKEN="$("$EDGE_ROOT/bin/node" -e "process.stdout.write(String((require('$STATS_FILE').lastDeviceToken)||''))" 2>/dev/null || echo '')"
|
|
305
|
+
A_JPEG="$("$EDGE_ROOT/bin/node" -e "process.stdout.write(String((require('$STATS_FILE').sawJpeg)||false))" 2>/dev/null || echo false)"
|
|
306
|
+
if [ "${A_COUNT:-0}" -ge 1 ] 2>/dev/null; then ok "stub received $A_COUNT analyze POST(s)"; else nok "stub received no analyze POSTs"; fi
|
|
307
|
+
if [ "$A_TOKEN" = "tok_TEST_123" ]; then ok "analyze POST carried X-Device-Token from pairing"; else nok "analyze POST missing/incorrect X-Device-Token" "got: '$A_TOKEN'"; fi
|
|
308
|
+
if [ "$A_JPEG" = "true" ]; then ok "analyze POST body was a real JPEG (SOI 0xFFD8) from the bundled ffmpeg"; else nok "analyze POST body was not a valid JPEG"; fi
|
|
309
|
+
else
|
|
310
|
+
nok "stub stats file missing — cannot verify POST contents"
|
|
311
|
+
fi
|
|
312
|
+
else
|
|
313
|
+
nok "skipped cloud-sense (no config)"
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
# ─── STEP 5b: LaunchAgent bootstrap + autonomous sensing ─────────────────────
|
|
317
|
+
diag "STEP 5b — pair via trillboards-pair (GUI helper, headless mode) bootstraps the LaunchAgent"
|
|
318
|
+
if [ -n "$PORT" ]; then
|
|
319
|
+
if "$EDGE_ROOT/bin/trillboards-pair" TEST-CODE --api-base "http://127.0.0.1:$PORT" >"$SCRATCH/pairgui.log" 2>&1; then
|
|
320
|
+
ok "trillboards-pair (headless) exited 0"
|
|
321
|
+
else
|
|
322
|
+
nok "trillboards-pair (headless) failed" "$(cat "$SCRATCH/pairgui.log")"
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
# The agent should now be loaded; give it a moment.
|
|
326
|
+
if launchctl print "gui/$UID_N/$LABEL" >/dev/null 2>&1; then
|
|
327
|
+
ok "LaunchAgent is bootstrapped (launchctl print sees it)"
|
|
328
|
+
else
|
|
329
|
+
nok "LaunchAgent not found after pair"
|
|
330
|
+
fi
|
|
331
|
+
|
|
332
|
+
# Wait for the agent's own log to show a successful POST (autonomous run).
|
|
333
|
+
AGENT_SENSED=0
|
|
334
|
+
for _ in $(seq 1 40); do
|
|
335
|
+
if grep -q 'analyze → HTTP 200' "$LOG_DIR/edge.out.log" 2>/dev/null; then AGENT_SENSED=1; break; fi
|
|
336
|
+
sleep 0.3
|
|
337
|
+
done
|
|
338
|
+
if [ "$AGENT_SENSED" -eq 1 ]; then
|
|
339
|
+
ok "LaunchAgent autonomously captured + POSTed a frame (edge.out.log)"
|
|
340
|
+
else
|
|
341
|
+
nok "LaunchAgent did not sense within timeout" "$(tail -6 "$LOG_DIR/edge.out.log" 2>/dev/null; echo '--- err ---'; tail -4 "$LOG_DIR/edge.err.log" 2>/dev/null)"
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
# The installed config must be 0600 too.
|
|
345
|
+
if [ -f "$INSTALLED_CONFIG" ]; then
|
|
346
|
+
IMODE="$(file_mode "$INSTALLED_CONFIG")"
|
|
347
|
+
if [ "$IMODE" = "600" ]; then ok "installed config is mode 0600"; else nok "installed config mode is '$IMODE' (expected 600)"; fi
|
|
348
|
+
else
|
|
349
|
+
nok "installed config not written by pair helper"
|
|
350
|
+
fi
|
|
351
|
+
else
|
|
352
|
+
nok "skipped LaunchAgent test (no stub port)"
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
# ─── STEP 5c: prove the installed UI payload has the no-Terminal contract ─────
|
|
356
|
+
diag "STEP 5c — native setup application payload"
|
|
357
|
+
if plutil -lint "$SETUP_APP/Contents/Info.plist" >/dev/null 2>&1; then
|
|
358
|
+
ok "native setup application Info.plist is valid"
|
|
359
|
+
else
|
|
360
|
+
nok "native setup application Info.plist is invalid or missing"
|
|
361
|
+
fi
|
|
362
|
+
if file "$SETUP_APP_BIN" 2>/dev/null | grep -q 'Mach-O'; then
|
|
363
|
+
ok "native setup application executable is Mach-O"
|
|
364
|
+
else
|
|
365
|
+
nok "native setup application executable is not Mach-O"
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
# ─── STEP 6: teardown happens in the EXIT trap ───────────────────────────────
|
|
369
|
+
diag "STEP 6 — teardown runs automatically on exit (idempotent)"
|
|
370
|
+
|
|
371
|
+
# ─── Summary ─────────────────────────────────────────────────────────────────
|
|
372
|
+
echo "1..$TAP_N"
|
|
373
|
+
echo ""
|
|
374
|
+
if [ "$FAILS" -eq 0 ]; then
|
|
375
|
+
printf '\033[1;32mPASS\033[0m — all %d checks passed.\n' "$TAP_N"
|
|
376
|
+
else
|
|
377
|
+
printf '\033[1;31mFAIL\033[0m — %d of %d checks failed.\n' "$FAILS" "$TAP_N"
|
|
378
|
+
fi
|
|
379
|
+
echo "Re-run: bash $PKG_ROOT/deploy/macos/test-install-pair.sh"
|
|
380
|
+
|
|
381
|
+
# Explicit exit so the EXIT-trap teardown (which runs AFTER this and would
|
|
382
|
+
# otherwise clobber $?) does not mask a failure.
|
|
383
|
+
if [ "$FAILS" -eq 0 ]; then exit 0; else exit 1; fi
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
SETUP="$SCRIPT_DIR/scripts/setup-litertlm.sh"
|
|
6
|
+
TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/trillboards-litertlm-reinstall.XXXXXX")"
|
|
7
|
+
trap 'rm -rf "$TEST_ROOT"' EXIT
|
|
8
|
+
|
|
9
|
+
export HOME="$TEST_ROOT/home"
|
|
10
|
+
export TRILLBOARDS_TEST_STATE_DIR="$TEST_ROOT/state"
|
|
11
|
+
export LITERT_LM_DIR="$TEST_ROOT/store"
|
|
12
|
+
export TRILLBOARDS_REQUIRE_BUNDLED_MODEL=1
|
|
13
|
+
FAKE_BIN="$HOME/.local/bin"
|
|
14
|
+
SHARE_DIR="$HOME/Library/Trillboards/edge/share"
|
|
15
|
+
mkdir -p "$FAKE_BIN" "$SHARE_DIR" "$TRILLBOARDS_TEST_STATE_DIR"
|
|
16
|
+
|
|
17
|
+
cat >"$FAKE_BIN/uname" <<'EOF'
|
|
18
|
+
#!/bin/bash
|
|
19
|
+
case "${1:-}" in
|
|
20
|
+
-s) printf 'Darwin\n' ;;
|
|
21
|
+
-m) printf 'arm64\n' ;;
|
|
22
|
+
*) printf 'Darwin\n' ;;
|
|
23
|
+
esac
|
|
24
|
+
EOF
|
|
25
|
+
|
|
26
|
+
cat >"$FAKE_BIN/uv" <<'EOF'
|
|
27
|
+
#!/bin/bash
|
|
28
|
+
exit 0
|
|
29
|
+
EOF
|
|
30
|
+
|
|
31
|
+
cat >"$FAKE_BIN/litert-lm" <<'EOF'
|
|
32
|
+
#!/bin/bash
|
|
33
|
+
if [ "${1:-}" = "list" ]; then
|
|
34
|
+
printf 'gemma-4-E2B-it.litertlm\n'
|
|
35
|
+
fi
|
|
36
|
+
exit 0
|
|
37
|
+
EOF
|
|
38
|
+
|
|
39
|
+
cat >"$FAKE_BIN/curl" <<'EOF'
|
|
40
|
+
#!/bin/bash
|
|
41
|
+
printf '{"data":[{"id":"gemma-4-E2B-it.litertlm"}]}\n'
|
|
42
|
+
EOF
|
|
43
|
+
|
|
44
|
+
cat >"$FAKE_BIN/sleep" <<'EOF'
|
|
45
|
+
#!/bin/bash
|
|
46
|
+
exit 0
|
|
47
|
+
EOF
|
|
48
|
+
|
|
49
|
+
cat >"$FAKE_BIN/launchctl" <<'EOF'
|
|
50
|
+
#!/bin/bash
|
|
51
|
+
set -u
|
|
52
|
+
STATE_DIR="${TRILLBOARDS_TEST_STATE_DIR:?}"
|
|
53
|
+
STATE_FILE="$STATE_DIR/service-state"
|
|
54
|
+
ATTEMPTS_FILE="$STATE_DIR/bootstrap-attempts"
|
|
55
|
+
|
|
56
|
+
case "${1:-}" in
|
|
57
|
+
bootout)
|
|
58
|
+
printf '3\n' >"$STATE_FILE"
|
|
59
|
+
exit 0
|
|
60
|
+
;;
|
|
61
|
+
print)
|
|
62
|
+
[ -f "$STATE_FILE" ] || exit 113
|
|
63
|
+
state="$(cat "$STATE_FILE")"
|
|
64
|
+
if [ "$state" = "loaded" ]; then
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
if [ "$state" -gt 1 ]; then
|
|
68
|
+
printf '%s\n' "$((state - 1))" >"$STATE_FILE"
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
rm -f "$STATE_FILE"
|
|
72
|
+
exit 113
|
|
73
|
+
;;
|
|
74
|
+
bootstrap)
|
|
75
|
+
attempts=0
|
|
76
|
+
[ ! -f "$ATTEMPTS_FILE" ] || attempts="$(cat "$ATTEMPTS_FILE")"
|
|
77
|
+
attempts=$((attempts + 1))
|
|
78
|
+
printf '%s\n' "$attempts" >"$ATTEMPTS_FILE"
|
|
79
|
+
if [ "$attempts" -eq 1 ]; then
|
|
80
|
+
printf 'Bootstrap failed: 37: Operation already in progress\n' >&2
|
|
81
|
+
exit 37
|
|
82
|
+
fi
|
|
83
|
+
printf 'loaded\n' >"$STATE_FILE"
|
|
84
|
+
exit 0
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
|
|
88
|
+
exit 0
|
|
89
|
+
EOF
|
|
90
|
+
|
|
91
|
+
chmod +x "$FAKE_BIN/uname" "$FAKE_BIN/uv" "$FAKE_BIN/litert-lm" \
|
|
92
|
+
"$FAKE_BIN/curl" "$FAKE_BIN/sleep" "$FAKE_BIN/launchctl"
|
|
93
|
+
|
|
94
|
+
cat >"$SHARE_DIR/com.trillboards.litertlm.plist" <<'EOF'
|
|
95
|
+
<plist><string>__LITERTLM__</string><string>__PORT__</string><string>__LOG_DIR__</string></plist>
|
|
96
|
+
EOF
|
|
97
|
+
printf 'loaded\n' >"$TRILLBOARDS_TEST_STATE_DIR/service-state"
|
|
98
|
+
|
|
99
|
+
export PATH="$FAKE_BIN:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
100
|
+
if ! OUTPUT="$(/bin/bash "$SETUP" 2>&1)"; then
|
|
101
|
+
printf '%s\n' "$OUTPUT" >&2
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
[ "$(cat "$TRILLBOARDS_TEST_STATE_DIR/bootstrap-attempts")" = "2" ]
|
|
106
|
+
grep -Fq 'Bootstrap failed: 37: Operation already in progress' \
|
|
107
|
+
"$HOME/Library/Logs/Trillboards/litertlm-setup.log"
|
|
108
|
+
grep -Fq 'local AI is installed and healthy' <<<"$OUTPUT"
|
|
109
|
+
printf 'macOS LiteRT-LM reinstall race regression passed\n'
|
|
@@ -27,28 +27,38 @@ fi
|
|
|
27
27
|
echo "Creating trillboards user and directories..."
|
|
28
28
|
sudo useradd --system --home-dir /opt/trillboards --shell /usr/sbin/nologin trillboards 2>/dev/null || true
|
|
29
29
|
sudo mkdir -p /opt/trillboards /var/lib/trillboards /etc/trillboards
|
|
30
|
-
|
|
30
|
+
# /etc/trillboards MUST be trillboards-owned too: pairing runs as the trillboards
|
|
31
|
+
# user and writes /etc/trillboards/config.json directly — without this chown the
|
|
32
|
+
# write fails with EACCES on a fresh install before the service ever has a config
|
|
33
|
+
# (Codex P1 on #7730).
|
|
34
|
+
sudo chown -R trillboards:trillboards /opt/trillboards /var/lib/trillboards /etc/trillboards
|
|
31
35
|
|
|
32
36
|
# Install SDK
|
|
33
37
|
echo "Installing @trillboards/edge-sdk..."
|
|
34
38
|
cd /opt/trillboards
|
|
35
39
|
sudo -u trillboards npm init -y --silent 2>/dev/null
|
|
36
|
-
sudo -u trillboards npm install @trillboards/edge-sdk @trillboards/edge-platform-linux
|
|
40
|
+
sudo -u trillboards npm install @trillboards/edge-sdk @trillboards/edge-platform-linux @trillboards/edge-platform-rtsp
|
|
37
41
|
|
|
38
|
-
#
|
|
39
|
-
echo "Downloading ONNX models..."
|
|
40
|
-
sudo -u trillboards mkdir -p /opt/trillboards/models
|
|
41
|
-
sudo -u trillboards npx trillboards-edge download-models --dir /opt/trillboards/models
|
|
42
|
+
# cloud-sense uses the cloud VLM for inference — NO local ONNX models to download.
|
|
42
43
|
|
|
43
|
-
# Install
|
|
44
|
+
# Pair with a claim code from the portal (Add a camera -> Install). Pairing writes
|
|
45
|
+
# /etc/trillboards/config.json (device token + feeds) — the token is delivered at
|
|
46
|
+
# pair, NEVER baked. Accept the code as $1 for headless installs, else prompt.
|
|
47
|
+
CLAIM_CODE="${1:-}"
|
|
44
48
|
if [ ! -f /etc/trillboards/config.json ]; then
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
if [ -z "$CLAIM_CODE" ]; then
|
|
50
|
+
read -rp "Enter your Trillboards pairing code (from the portal): " CLAIM_CODE || true
|
|
51
|
+
fi
|
|
52
|
+
if [ -n "$CLAIM_CODE" ]; then
|
|
53
|
+
echo "Pairing with the cloud..."
|
|
54
|
+
sudo -u trillboards npx trillboards-edge pair --code "$CLAIM_CODE" --config /etc/trillboards/config.json
|
|
55
|
+
sudo chown trillboards:trillboards /etc/trillboards/config.json
|
|
56
|
+
else
|
|
57
|
+
echo ""
|
|
58
|
+
echo " *** No code entered — pair before starting the service: ***"
|
|
59
|
+
echo " sudo -u trillboards npx trillboards-edge pair --code <CODE> --config /etc/trillboards/config.json"
|
|
60
|
+
echo ""
|
|
61
|
+
fi
|
|
52
62
|
fi
|
|
53
63
|
|
|
54
64
|
# Install systemd service
|
|
@@ -61,7 +71,7 @@ echo ""
|
|
|
61
71
|
echo "=== Installation Complete ==="
|
|
62
72
|
echo ""
|
|
63
73
|
echo "Next steps:"
|
|
64
|
-
echo " 1.
|
|
74
|
+
echo " 1. (If not paired above) sudo -u trillboards npx trillboards-edge pair --code <CODE> --config /etc/trillboards/config.json"
|
|
65
75
|
echo " 2. Start: sudo systemctl start trillboards-edge"
|
|
66
76
|
echo " 3. Logs: sudo journalctl -u trillboards-edge -f"
|
|
67
77
|
echo " 4. Status: sudo systemctl status trillboards-edge"
|
|
@@ -10,7 +10,10 @@ User=trillboards
|
|
|
10
10
|
Group=trillboards
|
|
11
11
|
WorkingDirectory=/opt/trillboards
|
|
12
12
|
ExecStartPre=/usr/bin/node -e "require('@trillboards/edge-sdk')"
|
|
13
|
-
|
|
13
|
+
# This legacy Linux installer explicitly selects cloud-sense and does not install
|
|
14
|
+
# LiteRT-LM. Customer macOS/Windows installers use the local-first auto-sense
|
|
15
|
+
# policy. The paired config is written by install.sh; the token is never baked.
|
|
16
|
+
ExecStart=/usr/bin/npx trillboards-edge cloud-sense --config /etc/trillboards/config.json
|
|
14
17
|
Restart=always
|
|
15
18
|
RestartSec=10
|
|
16
19
|
StartLimitInterval=300
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Trillboards Edge — Windows local-first CCTV connector
|
|
2
|
+
|
|
3
|
+
Self-contained, **no-admin** Windows installer for the BYO-CCTV connector. The
|
|
4
|
+
Windows analogue of `deploy/macos/` — same `cli pair` + `auto-sense` flow, same
|
|
5
|
+
claim-code pairing, same CDN.
|
|
6
|
+
|
|
7
|
+
## What the installer lays down
|
|
8
|
+
|
|
9
|
+
`%LOCALAPPDATA%\Trillboards\edge\` (per-user, no UAC):
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
bin\node.exe official Node x64 runtime (pinned v22.22.0)
|
|
13
|
+
bin\ffmpeg.exe pinned static ffmpeg (eugeneware/ffmpeg-static b6.0,
|
|
14
|
+
SHA-256 verified — same upstream as macOS)
|
|
15
|
+
bin\trillboards-edge.cmd launcher (PATH=bin first; node lib\...\cli.js)
|
|
16
|
+
bin\trillboards-setup.vbs no-console launcher for the customer setup app
|
|
17
|
+
bin\trillboards-setup.ps1 native Windows Forms customer setup window
|
|
18
|
+
bin\trillboards-pair.cmd hidden setup engine used by the setup window
|
|
19
|
+
bin\register-task.ps1 registers the logon Scheduled Task
|
|
20
|
+
lib\edge-sdk\dist\cli.js the connector CLI
|
|
21
|
+
lib\node_modules\... runtime subset (NO onnxruntime/sharp/sqlite; LiteRT-LM
|
|
22
|
+
provides the on-device VLM outside Node)
|
|
23
|
+
trillboards.config.json written by `pair` (device token delivered ONLY here)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
RTSP capture is via the bundled ffmpeg (cross-platform), so a single x64 build
|
|
27
|
+
serves every Windows 10/11 — no per-arch builds.
|
|
28
|
+
|
|
29
|
+
## Operator flow (the "no typing" path)
|
|
30
|
+
|
|
31
|
+
1. Portal → "Add a camera" → download the single **Windows (.exe)** containing
|
|
32
|
+
the connector and private local AI model.
|
|
33
|
+
2. Run it. Production installers are Authenticode-signed with Azure Trusted
|
|
34
|
+
Signing and the release workflow verifies the signature before publishing.
|
|
35
|
+
3. Installer copies the connector, installs and verifies the included local AI,
|
|
36
|
+
registers the logon tasks, then opens **Trillboards CCTV Setup** without a
|
|
37
|
+
command window.
|
|
38
|
+
4. Select **Start setup**. The app opens the Trillboards camera wizard, discovers
|
|
39
|
+
local recorders, and validates each channel. Black, disconnected, and
|
|
40
|
+
unreadable inputs are ignored; only working camera feeds become screens.
|
|
41
|
+
5. Sensing starts with the already-installed local AI and restarts on every
|
|
42
|
+
logon; cloud inference is off by default.
|
|
43
|
+
|
|
44
|
+
## Build (CI: `release-connector-windows.yml`, runs on `windows-latest`)
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
# bundles node + ffmpeg + edge-sdk, then compiles the inner NSIS .exe
|
|
48
|
+
./build-windows.ps1 # -> dist-installer\inner\TrillboardsEdgeSetup-core-<ver>-x64.exe
|
|
49
|
+
./build-windows.ps1 -SkipNsis # stage the tree only (no NSIS) — for checks
|
|
50
|
+
./build-windows.ps1 -Offline # copy host node/ffmpeg (air-gapped)
|
|
51
|
+
|
|
52
|
+
# after the inner installer is signed, create the one-file customer artifact
|
|
53
|
+
./bundle-windows-installer.ps1 -InnerInstaller <signed-inner.exe>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Requires NSIS and 7-Zip on PATH (`choco install nsis 7zip -y`). The release workflow publishes
|
|
57
|
+
the `.exe` to `s3://trillboards-screen-static/connector/trillboards-connector-windows.exe`
|
|
58
|
+
and invalidates the `screen.trillboards.com` CloudFront distribution, so the
|
|
59
|
+
portal's stable download URL never changes.
|
|
60
|
+
|
|
61
|
+
## Notes
|
|
62
|
+
|
|
63
|
+
- Production `.exe` artifacts are signed with Azure Trusted Signing and checked
|
|
64
|
+
with `Get-AuthenticodeSignature` before CDN publication.
|
|
65
|
+
- The one-file wrapper uses the official 7-Zip SFX installer module from the
|
|
66
|
+
LZMA SDK. 7-Zip is licensed under the GNU LGPL; source and license information
|
|
67
|
+
are available at https://www.7-zip.org/.
|
|
68
|
+
- The legacy `install-service.ps1` (local-inference: requires system Node + npm
|
|
69
|
+
install + ONNX models + a hand-edited deviceToken) is a different, older path.
|
|
70
|
+
This local-first installer supersedes it for BYO-CCTV onboarding.
|