@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,153 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# postinstall — runs after the home-domain payload is laid down under
|
|
4
|
+
# ~/Library/Trillboards/edge. Home-domain installs run this AS THE USER (no
|
|
5
|
+
# root), so $HOME is the operator's home.
|
|
6
|
+
#
|
|
7
|
+
# Responsibilities (idempotent, no customer terminal):
|
|
8
|
+
# - ensure the runtime dirs exist (Application Support config dir + Logs dir)
|
|
9
|
+
# - make the bundled binaries executable (pkg payloads can lose +x)
|
|
10
|
+
# - install and health-check the bundled local AI model
|
|
11
|
+
# - open camera pairing only after local AI is ready
|
|
12
|
+
# - DO NOT start the LaunchAgent: there is no paired config yet, and a
|
|
13
|
+
# config-less cloud-sense crash-loops. The agent is bootstrapped only after
|
|
14
|
+
# a successful setup (the installed pairing engine does that).
|
|
15
|
+
#
|
|
16
|
+
# Cosmetic permission repairs remain non-fatal. Local AI is a required customer
|
|
17
|
+
# dependency, so a failed model install stops setup before pairing can begin.
|
|
18
|
+
|
|
19
|
+
set -u
|
|
20
|
+
|
|
21
|
+
# Resolve the OWNER of this install. A home-domain install runs us AS THE USER,
|
|
22
|
+
# but an admin/system install (the operator clicked through with admin rights)
|
|
23
|
+
# runs us AS ROOT. macOS still sets $USER to the invoking account even in the
|
|
24
|
+
# system domain; fall back to the logged-in console user. Knowing the real owner
|
|
25
|
+
# lets us hand the runtime dirs back so a root install can't leave them
|
|
26
|
+
# ROOT-OWNED — which is exactly what made the operator's later setup attempt hit
|
|
27
|
+
# `EACCES` writing the paired config (misreported as "check the code").
|
|
28
|
+
OWNER="${USER:-}"
|
|
29
|
+
if [ -z "$OWNER" ] || [ "$OWNER" = "root" ]; then
|
|
30
|
+
OWNER="$(stat -f %Su /dev/console 2>/dev/null || true)"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Resolve the install root. On a home-domain install $HOME is already the
|
|
34
|
+
# operator's home. Under a root/admin install $HOME is /var/root — derive the
|
|
35
|
+
# target user's home from $OWNER instead, so the runtime dirs land under
|
|
36
|
+
# /Users/<owner> and NEVER /var/root.
|
|
37
|
+
if [ "$(id -u)" = "0" ] && [ -n "$OWNER" ] && [ "$OWNER" != "root" ]; then
|
|
38
|
+
USER_HOME="$(eval echo "~$OWNER")"
|
|
39
|
+
else
|
|
40
|
+
USER_HOME="${HOME:-$(eval echo "~$(id -un)")}"
|
|
41
|
+
fi
|
|
42
|
+
EDGE_ROOT="$USER_HOME/Library/Trillboards/edge"
|
|
43
|
+
APP_SUPPORT="$USER_HOME/Library/Application Support/Trillboards"
|
|
44
|
+
LOG_DIR="$USER_HOME/Library/Logs/Trillboards"
|
|
45
|
+
|
|
46
|
+
log() { /usr/bin/logger -t com.trillboards.edge "postinstall: $*" 2>/dev/null || true; printf '[trillboards postinstall] %s\n' "$*"; }
|
|
47
|
+
|
|
48
|
+
# Hand a path (dir or file) back to the installing user. Meaningful ONLY under a
|
|
49
|
+
# root/admin install — a home-domain install already runs as the user, so this
|
|
50
|
+
# is a no-op there. Pass "-R" first to recurse (runtime dirs we fully own); omit
|
|
51
|
+
# it for a shallow chown (~/Applications, so we don't rewrite the operator's
|
|
52
|
+
# OTHER apps). Fully non-fatal: a chown failure must never fail the install.
|
|
53
|
+
reassert_owner() {
|
|
54
|
+
[ "$(id -u)" = "0" ] || return 0
|
|
55
|
+
[ -n "$OWNER" ] && [ "$OWNER" != "root" ] || return 0
|
|
56
|
+
if [ "${1:-}" = "-R" ]; then
|
|
57
|
+
shift
|
|
58
|
+
chown -R "$OWNER" "$@" 2>/dev/null || true
|
|
59
|
+
else
|
|
60
|
+
chown "$OWNER" "$@" 2>/dev/null || true
|
|
61
|
+
fi
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
log "install root: $EDGE_ROOT"
|
|
65
|
+
|
|
66
|
+
# 1. Runtime dirs (config lives in Application Support, logs in Logs).
|
|
67
|
+
mkdir -p "$APP_SUPPORT" 2>/dev/null || log "could not create $APP_SUPPORT"
|
|
68
|
+
mkdir -p "$LOG_DIR" 2>/dev/null || log "could not create $LOG_DIR"
|
|
69
|
+
chmod 700 "$APP_SUPPORT" 2>/dev/null || true
|
|
70
|
+
|
|
71
|
+
# Under a root/admin install the dirs just created — and the bundled payload —
|
|
72
|
+
# are ROOT-OWNED. Hand them to the operator NOW so the pairing helper (which
|
|
73
|
+
# runs as the user) can write the paired config without hitting EACCES. No-op on
|
|
74
|
+
# a home-domain install (we already run as the user).
|
|
75
|
+
reassert_owner -R "$APP_SUPPORT" "$LOG_DIR" "$EDGE_ROOT"
|
|
76
|
+
|
|
77
|
+
# 2. Re-assert +x on the bundled executables (idempotent).
|
|
78
|
+
for b in node ffmpeg trillboards-edge trillboards-pair; do
|
|
79
|
+
if [ -f "$EDGE_ROOT/bin/$b" ]; then
|
|
80
|
+
chmod +x "$EDGE_ROOT/bin/$b" 2>/dev/null || true
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
# 3. Install the bundled local AI model before pairing. Under an admin install,
|
|
85
|
+
# run the per-user provisioner in the console user's launchd session so its
|
|
86
|
+
# model store and LaunchAgent never land under /var/root.
|
|
87
|
+
AI_SETUP="$EDGE_ROOT/share/setup-litertlm.sh"
|
|
88
|
+
if [ ! -x "$AI_SETUP" ]; then
|
|
89
|
+
log "ERROR: required local AI installer is missing at $AI_SETUP"
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
log "installing and verifying bundled local AI before camera pairing"
|
|
94
|
+
AI_RC=0
|
|
95
|
+
if [ "$(id -u)" = "0" ] && [ -n "$OWNER" ] && [ "$OWNER" != "root" ]; then
|
|
96
|
+
LAUNCH_UID="$(id -u "$OWNER" 2>/dev/null || echo '')"
|
|
97
|
+
if [ -z "$LAUNCH_UID" ]; then
|
|
98
|
+
log "ERROR: could not resolve the installing user's launch session"
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
/bin/launchctl asuser "$LAUNCH_UID" /usr/bin/sudo -u "$OWNER" \
|
|
102
|
+
/usr/bin/env HOME="$USER_HOME" TRILLBOARDS_REQUIRE_BUNDLED_MODEL=1 \
|
|
103
|
+
/bin/bash "$AI_SETUP" || AI_RC=$?
|
|
104
|
+
else
|
|
105
|
+
TRILLBOARDS_REQUIRE_BUNDLED_MODEL=1 /bin/bash "$AI_SETUP" || AI_RC=$?
|
|
106
|
+
fi
|
|
107
|
+
if [ "$AI_RC" -ne 0 ]; then
|
|
108
|
+
log "ERROR: local AI installation failed (exit $AI_RC); camera pairing was not opened"
|
|
109
|
+
exit "$AI_RC"
|
|
110
|
+
fi
|
|
111
|
+
log "bundled local AI is installed and healthy"
|
|
112
|
+
|
|
113
|
+
# 4. Launch the real native setup application. It runs the canonical pairer as
|
|
114
|
+
# an invisible child process and renders its credential-free progress file,
|
|
115
|
+
# so customers never see Terminal or a command window. The app itself is
|
|
116
|
+
# part of the signed payload under ~/Applications.
|
|
117
|
+
APPS_DIR="$USER_HOME/Applications"
|
|
118
|
+
SETUP_APP="$APPS_DIR/Trillboards CCTV Setup.app"
|
|
119
|
+
LEGACY_PAIR_LAUNCHER="$APPS_DIR/Trillboards Pair.command"
|
|
120
|
+
rm -f "$LEGACY_PAIR_LAUNCHER" 2>/dev/null || true
|
|
121
|
+
if [ -d "$SETUP_APP" ] && [ -x "$EDGE_ROOT/bin/trillboards-pair" ]; then
|
|
122
|
+
log "native setup app ready: \"$SETUP_APP\" (opening it)"
|
|
123
|
+
if [ "$(id -u)" = "0" ] && [ -n "$OWNER" ] && [ "$OWNER" != "root" ]; then
|
|
124
|
+
LAUNCH_UID="$(id -u "$OWNER" 2>/dev/null || echo '')"
|
|
125
|
+
if [ -n "$LAUNCH_UID" ]; then
|
|
126
|
+
/bin/launchctl asuser "$LAUNCH_UID" /usr/bin/sudo -u "$OWNER" \
|
|
127
|
+
/usr/bin/env -u TMPDIR HOME="$USER_HOME" /usr/bin/open "$SETUP_APP" 2>/dev/null \
|
|
128
|
+
|| /bin/launchctl asuser "$LAUNCH_UID" /usr/bin/sudo -u "$OWNER" \
|
|
129
|
+
/usr/bin/env -u TMPDIR HOME="$USER_HOME" /usr/bin/open -R "$SETUP_APP" 2>/dev/null || true
|
|
130
|
+
fi
|
|
131
|
+
else
|
|
132
|
+
/usr/bin/env -u TMPDIR /usr/bin/open "$SETUP_APP" 2>/dev/null \
|
|
133
|
+
|| /usr/bin/env -u TMPDIR /usr/bin/open -R "$SETUP_APP" 2>/dev/null || true
|
|
134
|
+
fi
|
|
135
|
+
else
|
|
136
|
+
log "WARNING: native setup app or pairing engine is missing"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Under a root install the payload app is root-owned. Hand only this app bundle
|
|
140
|
+
# back to the operator, never their other applications.
|
|
141
|
+
reassert_owner "$APPS_DIR"
|
|
142
|
+
reassert_owner -R "$SETUP_APP"
|
|
143
|
+
|
|
144
|
+
# 5. Sanity check (logged only — never fail the install on this).
|
|
145
|
+
if [ -x "$EDGE_ROOT/bin/trillboards-edge" ]; then
|
|
146
|
+
VER="$("$EDGE_ROOT/bin/trillboards-edge" version 2>/dev/null || echo '?')"
|
|
147
|
+
log "bundled connector: $VER"
|
|
148
|
+
else
|
|
149
|
+
log "WARNING: launcher not executable at $EDGE_ROOT/bin/trillboards-edge"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
log "done. Connector and local AI installed; Trillboards CCTV Setup opened automatically."
|
|
153
|
+
exit 0
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# setup-litertlm.sh — provision the LiteRT-LM on-device VLM runtime for the
|
|
4
|
+
# BYO-CCTV LOCAL-inference connector. Runs AS THE USER (home-domain, no sudo).
|
|
5
|
+
#
|
|
6
|
+
# Idempotent and safe to re-run; logs to the Trillboards log dir. Customer
|
|
7
|
+
# installers set TRILLBOARDS_REQUIRE_BUNDLED_MODEL=1 so every provisioning
|
|
8
|
+
# failure is fatal and network model fallback is disabled.
|
|
9
|
+
# Steps:
|
|
10
|
+
# 1. Ensure `uv` (the Python packaging tool) is available — install via the
|
|
11
|
+
# official standalone installer if missing (home-domain, no sudo).
|
|
12
|
+
# 2. Install/repair `litert-lm` with uv-managed Python 3.12. Explicitly
|
|
13
|
+
# choosing Python avoids Apple's legacy system Python 3.9, which cannot
|
|
14
|
+
# import current LiteRT-LM releases.
|
|
15
|
+
# 3. `litert-lm import` the bundled Gemma 4 E2B model
|
|
16
|
+
# (gemma-4-E2B-it.litertlm, ~2.4 GB) after verifying its checksum.
|
|
17
|
+
# 4. Render + bootstrap the `litert-lm serve` LaunchAgent so the OpenAI-
|
|
18
|
+
# compatible endpoint (:9379) is up at login. The connector's
|
|
19
|
+
# LiteRtVlmProvider talks to it.
|
|
20
|
+
#
|
|
21
|
+
# The installer runs this before opening camera pairing. There is no model
|
|
22
|
+
# download after cameras have been created.
|
|
23
|
+
|
|
24
|
+
set -u
|
|
25
|
+
USER_HOME="${HOME:-$(eval echo "~$(id -un)")}"
|
|
26
|
+
LOG_DIR="$USER_HOME/Library/Logs/Trillboards"
|
|
27
|
+
SHARE_DIR="$USER_HOME/Library/Trillboards/edge/share"
|
|
28
|
+
LA_DIR="$USER_HOME/Library/LaunchAgents"
|
|
29
|
+
LOCAL_BIN="$USER_HOME/.local/bin"
|
|
30
|
+
MODEL_ID="gemma-4-E2B-it.litertlm"
|
|
31
|
+
HF_REPO="litert-community/gemma-4-E2B-it-litert-lm"
|
|
32
|
+
SERVE_LABEL="com.trillboards.litertlm"
|
|
33
|
+
SERVE_PORT="9379"
|
|
34
|
+
LITERT_PYTHON="${LITERT_LM_PYTHON:-3.12}"
|
|
35
|
+
REQUIRE_BUNDLED_MODEL="${TRILLBOARDS_REQUIRE_BUNDLED_MODEL:-0}"
|
|
36
|
+
ALLOW_MODEL_DOWNLOAD="${TRILLBOARDS_ALLOW_MODEL_DOWNLOAD:-0}"
|
|
37
|
+
# Customer packages stage the model here. A network fallback remains available
|
|
38
|
+
# only for legacy/engineering invocations that do not require the bundle.
|
|
39
|
+
LITERT_DIR="${LITERT_LM_DIR:-$USER_HOME/.litert-lm}"
|
|
40
|
+
STORE_DIR="$LITERT_DIR/models/$MODEL_ID"
|
|
41
|
+
BUNDLED_MODEL="$SHARE_DIR/models/$MODEL_ID"
|
|
42
|
+
BUNDLED_MODEL_SHA="$BUNDLED_MODEL.sha256"
|
|
43
|
+
# Installed model version — must match the sensing_model_registry `gemma_4_e2b`
|
|
44
|
+
# row. The connector's edge-core ModelManager seeds this (via the marker written
|
|
45
|
+
# below) so its first OTA check does NOT re-download the already-installed model.
|
|
46
|
+
# Bump GEMMA_VLM_VERSION alongside the registry row when a newer model ships.
|
|
47
|
+
MODEL_VERSION="${GEMMA_VLM_VERSION:-1.0.0}"
|
|
48
|
+
VERSION_MARKER="$STORE_DIR/trillboards-bundled-version.json"
|
|
49
|
+
mkdir -p "$LOG_DIR" "$LA_DIR" 2>/dev/null || true
|
|
50
|
+
LOG="$LOG_DIR/litertlm-setup.log"
|
|
51
|
+
log() { printf '[trillboards setup-litertlm] %s %s\n' "$(date '+%H:%M:%S')" "$*" >>"$LOG" 2>&1; printf '[trillboards setup-litertlm] %s\n' "$*"; }
|
|
52
|
+
fail() { log "ERROR: $*"; exit 1; }
|
|
53
|
+
local_ai_healthy() {
|
|
54
|
+
curl -fsS --max-time 3 "http://127.0.0.1:$SERVE_PORT/v1/models" 2>/dev/null \
|
|
55
|
+
| grep -Fq "$MODEL_ID"
|
|
56
|
+
}
|
|
57
|
+
launch_agent_loaded() {
|
|
58
|
+
launchctl print "gui/$(id -u)/$SERVE_LABEL" >/dev/null 2>&1
|
|
59
|
+
}
|
|
60
|
+
wait_for_launch_agent_removal() {
|
|
61
|
+
local removal_attempt=1
|
|
62
|
+
while [ "$removal_attempt" -le 40 ]; do
|
|
63
|
+
launch_agent_loaded || return 0
|
|
64
|
+
sleep 0.25
|
|
65
|
+
removal_attempt=$((removal_attempt + 1))
|
|
66
|
+
done
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
restart_launch_agent() {
|
|
70
|
+
local service_domain service_target bootstrap_attempt
|
|
71
|
+
service_domain="gui/$(id -u)"
|
|
72
|
+
service_target="$service_domain/$SERVE_LABEL"
|
|
73
|
+
|
|
74
|
+
if launch_agent_loaded; then
|
|
75
|
+
log "stopping existing LiteRT-LM LaunchAgent"
|
|
76
|
+
launchctl bootout "$service_target" >>"$LOG" 2>&1 \
|
|
77
|
+
|| log "WARN: launchctl bootout returned non-zero; waiting for service state"
|
|
78
|
+
if ! wait_for_launch_agent_removal; then
|
|
79
|
+
log "ERROR: existing LiteRT-LM LaunchAgent did not finish stopping"
|
|
80
|
+
return 1
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
bootstrap_attempt=1
|
|
85
|
+
while [ "$bootstrap_attempt" -le 10 ]; do
|
|
86
|
+
if launchctl bootstrap "$service_domain" "$TARGET" >>"$LOG" 2>&1; then
|
|
87
|
+
log "litert-lm serve LaunchAgent bootstrapped on :$SERVE_PORT"
|
|
88
|
+
return 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# launchd may accept the job even when bootstrap reports a transient error.
|
|
92
|
+
if launch_agent_loaded; then
|
|
93
|
+
log "litert-lm serve LaunchAgent is loaded after bootstrap retry"
|
|
94
|
+
return 0
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if [ "$bootstrap_attempt" -lt 10 ]; then
|
|
98
|
+
log "WARN: launchctl bootstrap attempt $bootstrap_attempt failed; retrying"
|
|
99
|
+
sleep 1
|
|
100
|
+
fi
|
|
101
|
+
bootstrap_attempt=$((bootstrap_attempt + 1))
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
log "ERROR: launchctl bootstrap failed after $((bootstrap_attempt - 1)) attempts"
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
verify_bundled_model() {
|
|
108
|
+
[ -f "$BUNDLED_MODEL_SHA" ] || fail "bundled model checksum is missing"
|
|
109
|
+
EXPECTED_SHA="$(awk 'NR == 1 { print $1 }' "$BUNDLED_MODEL_SHA" 2>/dev/null || echo '')"
|
|
110
|
+
ACTUAL_SHA="$(shasum -a 256 "$BUNDLED_MODEL" 2>/dev/null | awk '{print $1}')"
|
|
111
|
+
if [ -z "$EXPECTED_SHA" ] || [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
|
|
112
|
+
fail "bundled model checksum mismatch"
|
|
113
|
+
fi
|
|
114
|
+
log "verified bundled model SHA-256"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# One provisioning process per user. Re-opening the pairing helper is a normal
|
|
118
|
+
# recovery path, so duplicate launches must collapse without duplicate downloads.
|
|
119
|
+
LOCK_DIR="$LOG_DIR/litertlm-setup.lock"
|
|
120
|
+
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
121
|
+
LOCK_PID="$(cat "$LOCK_DIR/pid" 2>/dev/null || echo '')"
|
|
122
|
+
if [ -n "$LOCK_PID" ] &&
|
|
123
|
+
kill -0 "$LOCK_PID" 2>/dev/null &&
|
|
124
|
+
ps -p "$LOCK_PID" -o command= 2>/dev/null | grep -q '[s]etup-litertlm.sh'; then
|
|
125
|
+
log "LiteRT-LM provisioning is already running (pid $LOCK_PID)"
|
|
126
|
+
if [ "$REQUIRE_BUNDLED_MODEL" = "1" ]; then
|
|
127
|
+
for _attempt in $(seq 1 120); do
|
|
128
|
+
local_ai_healthy && exit 0
|
|
129
|
+
sleep 2
|
|
130
|
+
done
|
|
131
|
+
fail "the existing local AI installation did not become healthy"
|
|
132
|
+
fi
|
|
133
|
+
exit 0
|
|
134
|
+
fi
|
|
135
|
+
rm -rf "$LOCK_DIR" 2>/dev/null || true
|
|
136
|
+
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
137
|
+
log "LiteRT-LM provisioning is already starting in another process"
|
|
138
|
+
exit 0
|
|
139
|
+
fi
|
|
140
|
+
fi
|
|
141
|
+
printf '%s\n' "$$" > "$LOCK_DIR/pid" 2>/dev/null || true
|
|
142
|
+
cleanup_lock() { rm -rf "$LOCK_DIR" 2>/dev/null || true; }
|
|
143
|
+
trap cleanup_lock EXIT
|
|
144
|
+
trap 'exit 130' INT
|
|
145
|
+
trap 'exit 143' TERM
|
|
146
|
+
|
|
147
|
+
log "starting LiteRT-LM provisioning"
|
|
148
|
+
|
|
149
|
+
HOST_OS="$(uname -s 2>/dev/null || echo unknown)"
|
|
150
|
+
HOST_ARCH="$(uname -m 2>/dev/null || echo unknown)"
|
|
151
|
+
if [ "$HOST_OS" = "Darwin" ] && [ "$HOST_ARCH" != "arm64" ]; then
|
|
152
|
+
log "ERROR: local AI requires Apple silicon; cloud inference remains disabled unless explicitly opted in."
|
|
153
|
+
launchctl bootout "gui/$(id -u)/$SERVE_LABEL" 2>/dev/null || true
|
|
154
|
+
rm -f "$LA_DIR/$SERVE_LABEL.plist" 2>/dev/null || true
|
|
155
|
+
log "removed any stale LiteRT-LM LaunchAgent for unsupported local inference host"
|
|
156
|
+
[ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "local AI requires Apple silicon"
|
|
157
|
+
exit 0
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# 1. uv (PATH first; else official standalone installer, home-domain).
|
|
161
|
+
export PATH="$LOCAL_BIN:$USER_HOME/.cargo/bin:$PATH"
|
|
162
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
163
|
+
log "uv not found — installing via astral.sh standalone installer (no sudo)"
|
|
164
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh >>"$LOG" 2>&1 || log "WARN: uv install failed"
|
|
165
|
+
export PATH="$LOCAL_BIN:$USER_HOME/.local/share/uv/bin:$PATH"
|
|
166
|
+
fi
|
|
167
|
+
UV="$(command -v uv || echo "$LOCAL_BIN/uv")"
|
|
168
|
+
if ! command -v "$UV" >/dev/null 2>&1 && [ ! -x "$UV" ]; then
|
|
169
|
+
[ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "uv is unavailable; cannot install the local AI runtime"
|
|
170
|
+
log "ERROR: uv unavailable; cannot provision LiteRT-LM"; exit 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# 2. litert-lm CLI. Keep a healthy existing install, but actively repair a
|
|
174
|
+
# broken one. Older connector versions could let uv bind the tool to Apple's
|
|
175
|
+
# Python 3.9; current LiteRT-LM uses Python features introduced in 3.10 and then
|
|
176
|
+
# crash-loops before serving. --force rebuilds that existing tool environment
|
|
177
|
+
# with the explicit managed interpreter while --upgrade refreshes the package.
|
|
178
|
+
LITERTLM="$LOCAL_BIN/litert-lm"
|
|
179
|
+
[ -x "$LITERTLM" ] || LITERTLM="$(command -v litert-lm || echo "$LITERTLM")"
|
|
180
|
+
if [ -x "$LITERTLM" ] && "$LITERTLM" list >/dev/null 2>&1; then
|
|
181
|
+
log "litert-lm CLI is healthy — keeping current install"
|
|
182
|
+
else
|
|
183
|
+
log "installing/repairing litert-lm with uv-managed Python $LITERT_PYTHON"
|
|
184
|
+
if ! "$UV" tool install --python "$LITERT_PYTHON" --managed-python --upgrade --force litert-lm >>"$LOG" 2>&1; then
|
|
185
|
+
[ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "litert-lm install/repair failed"
|
|
186
|
+
log "ERROR: litert-lm install/repair failed; local AI will retry later"
|
|
187
|
+
exit 0
|
|
188
|
+
fi
|
|
189
|
+
LITERTLM="$LOCAL_BIN/litert-lm"
|
|
190
|
+
[ -x "$LITERTLM" ] || LITERTLM="$(command -v litert-lm || echo "$LITERTLM")"
|
|
191
|
+
if [ ! -x "$LITERTLM" ] || ! "$LITERTLM" list >/dev/null 2>&1; then
|
|
192
|
+
[ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "litert-lm remains unhealthy after repair"
|
|
193
|
+
log "ERROR: litert-lm remains unhealthy after repair; local AI will retry later"
|
|
194
|
+
exit 0
|
|
195
|
+
fi
|
|
196
|
+
log "litert-lm CLI repaired successfully"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# 3. Model - import the verified bundled file. Idempotent: skip entirely if
|
|
200
|
+
# litert-lm already has the model. Engineering runs may use HF only with the
|
|
201
|
+
# explicit TRILLBOARDS_ALLOW_MODEL_DOWNLOAD=1 opt-in.
|
|
202
|
+
HAVE_MODEL=0
|
|
203
|
+
if "$LITERTLM" list 2>/dev/null | grep -q "$MODEL_ID"; then
|
|
204
|
+
log "model $MODEL_ID already present — skipping import"
|
|
205
|
+
HAVE_MODEL=1
|
|
206
|
+
if [ -f "$BUNDLED_MODEL" ]; then
|
|
207
|
+
verify_bundled_model
|
|
208
|
+
rm -f "$BUNDLED_MODEL" "$BUNDLED_MODEL_SHA" 2>/dev/null || true
|
|
209
|
+
log "removed redundant staged model from upgrade"
|
|
210
|
+
fi
|
|
211
|
+
elif [ -f "$BUNDLED_MODEL" ]; then
|
|
212
|
+
verify_bundled_model
|
|
213
|
+
log "importing $MODEL_ID from BUNDLED file $BUNDLED_MODEL (no download) …"
|
|
214
|
+
if "$LITERTLM" import "$BUNDLED_MODEL" "$MODEL_ID" >>"$LOG" 2>&1; then
|
|
215
|
+
log "model imported from bundle"
|
|
216
|
+
HAVE_MODEL=1
|
|
217
|
+
# Reclaim the staged ~2.4 GB — the litert-lm store now holds the model.
|
|
218
|
+
rm -f "$BUNDLED_MODEL" "$BUNDLED_MODEL_SHA" 2>/dev/null || true
|
|
219
|
+
else
|
|
220
|
+
[ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "bundled model import failed"
|
|
221
|
+
[ "$ALLOW_MODEL_DOWNLOAD" = "1" ] || fail "bundled model import failed and network fallback is disabled"
|
|
222
|
+
log "WARN: bundled import failed - falling back to HF"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
if [ "$HAVE_MODEL" -eq 0 ]; then
|
|
226
|
+
[ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "required bundled model is missing"
|
|
227
|
+
[ "$ALLOW_MODEL_DOWNLOAD" = "1" ] || fail "local AI model is missing and network fallback is disabled"
|
|
228
|
+
log "importing $MODEL_ID from $HF_REPO (~2.4 GB, public repo) …"
|
|
229
|
+
"$LITERTLM" import --from-huggingface-repo "$HF_REPO" "$MODEL_ID" >>"$LOG" 2>&1 \
|
|
230
|
+
&& { log "model imported from HF"; HAVE_MODEL=1; } \
|
|
231
|
+
|| log "WARN: model import failed (serve will retry on next run)"
|
|
232
|
+
fi
|
|
233
|
+
[ "$HAVE_MODEL" -eq 1 ] || { [ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "local AI model is unavailable"; }
|
|
234
|
+
|
|
235
|
+
# Record the installed version next to the store model so the connector's
|
|
236
|
+
# edge-core ModelManager seeds it (readBundledModelVersion) and does not
|
|
237
|
+
# download the same model again on its first gemma-vlm OTA check.
|
|
238
|
+
if [ "$HAVE_MODEL" -eq 1 ]; then
|
|
239
|
+
mkdir -p "$STORE_DIR" 2>/dev/null || true
|
|
240
|
+
printf '{"version":"%s","modelType":"gemma-vlm"}\n' "$MODEL_VERSION" > "$VERSION_MARKER" 2>/dev/null \
|
|
241
|
+
&& log "recorded installed version $MODEL_VERSION at $VERSION_MARKER" \
|
|
242
|
+
|| log "WARN: could not write version marker $VERSION_MARKER"
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# 4. Render + bootstrap the serve LaunchAgent.
|
|
246
|
+
TEMPLATE="$SHARE_DIR/$SERVE_LABEL.plist"
|
|
247
|
+
TARGET="$LA_DIR/$SERVE_LABEL.plist"
|
|
248
|
+
if [ -f "$TEMPLATE" ]; then
|
|
249
|
+
sed -e "s#__LITERTLM__#$LITERTLM#g" \
|
|
250
|
+
-e "s#__PORT__#$SERVE_PORT#g" \
|
|
251
|
+
-e "s#__LOG_DIR__#$LOG_DIR#g" \
|
|
252
|
+
"$TEMPLATE" > "$TARGET" 2>/dev/null || log "WARN: could not render serve plist"
|
|
253
|
+
restart_launch_agent \
|
|
254
|
+
|| { [ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "local AI service could not be started"; log "WARN: launchctl bootstrap failed for serve agent"; }
|
|
255
|
+
else
|
|
256
|
+
[ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "local AI service template is missing"
|
|
257
|
+
log "WARN: serve plist template missing at $TEMPLATE"
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
if [ "$REQUIRE_BUNDLED_MODEL" = "1" ]; then
|
|
261
|
+
log "waiting for local AI health check"
|
|
262
|
+
HEALTHY=0
|
|
263
|
+
for _attempt in $(seq 1 60); do
|
|
264
|
+
if local_ai_healthy; then HEALTHY=1; break; fi
|
|
265
|
+
sleep 2
|
|
266
|
+
done
|
|
267
|
+
[ "$HEALTHY" -eq 1 ] || fail "Gemma is installed, but the local AI service did not respond"
|
|
268
|
+
log "local AI is installed and healthy"
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
log "done"
|
|
272
|
+
exit 0
|