@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,45 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<!--
|
|
4
|
+
Per-user LaunchAgent for the LiteRT-LM on-device VLM server (Gemma 4 E2B).
|
|
5
|
+
|
|
6
|
+
This is a TEMPLATE: setup-litertlm.sh substitutes __LITERTLM__ (absolute path
|
|
7
|
+
to the uv-installed `litert-lm` binary) / __PORT__ / __LOG_DIR__ and writes the
|
|
8
|
+
result to ~/Library/LaunchAgents/com.trillboards.litertlm.plist, then loads it
|
|
9
|
+
via `launchctl bootstrap gui/$UID`.
|
|
10
|
+
|
|
11
|
+
Runs the OpenAI-compatible server the connector's LiteRtVlmProvider talks to.
|
|
12
|
+
KeepAlive=true (always-on local inference endpoint); ThrottleInterval guards a
|
|
13
|
+
hot crash-loop if the installed runtime becomes temporarily unavailable.
|
|
14
|
+
-->
|
|
15
|
+
<plist version="1.0">
|
|
16
|
+
<dict>
|
|
17
|
+
<key>Label</key>
|
|
18
|
+
<string>com.trillboards.litertlm</string>
|
|
19
|
+
|
|
20
|
+
<key>ProgramArguments</key>
|
|
21
|
+
<array>
|
|
22
|
+
<string>__LITERTLM__</string>
|
|
23
|
+
<string>serve</string>
|
|
24
|
+
<string>--host</string>
|
|
25
|
+
<string>127.0.0.1</string>
|
|
26
|
+
<string>--port</string>
|
|
27
|
+
<string>__PORT__</string>
|
|
28
|
+
</array>
|
|
29
|
+
|
|
30
|
+
<key>RunAtLoad</key>
|
|
31
|
+
<true/>
|
|
32
|
+
<key>KeepAlive</key>
|
|
33
|
+
<true/>
|
|
34
|
+
<key>ThrottleInterval</key>
|
|
35
|
+
<integer>30</integer>
|
|
36
|
+
|
|
37
|
+
<key>ProcessType</key>
|
|
38
|
+
<string>Background</string>
|
|
39
|
+
|
|
40
|
+
<key>StandardOutPath</key>
|
|
41
|
+
<string>__LOG_DIR__/litertlm.out.log</string>
|
|
42
|
+
<key>StandardErrorPath</key>
|
|
43
|
+
<string>__LOG_DIR__/litertlm.err.log</string>
|
|
44
|
+
</dict>
|
|
45
|
+
</plist>
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# pair.command — first-run pairing helper for the Trillboards local-first CCTV
|
|
4
|
+
# connector. Installed as ~/Library/Trillboards/edge/bin/trillboards-pair.
|
|
5
|
+
#
|
|
6
|
+
# Three modes:
|
|
7
|
+
# Native app (`--setup --status-file <path>`):
|
|
8
|
+
# runs validate-first setup without Terminal or AppleScript dialogs and
|
|
9
|
+
# reports credential-free progress to Trillboards CCTV Setup.app.
|
|
10
|
+
# Legacy GUI (no arguments):
|
|
11
|
+
# osascript prompts for the pairing code, runs the bundled
|
|
12
|
+
# `trillboards-edge pair`, bootstraps the per-user LaunchAgent, and shows
|
|
13
|
+
# a success/failure dialog. Kept for compatibility with older launchers.
|
|
14
|
+
# Headless (CI / scripted):
|
|
15
|
+
# trillboards-pair <CODE> [--api-base URL]
|
|
16
|
+
# Pairs, bootstraps the agent, prints status, exits 0/1. Drives the same
|
|
17
|
+
# code path as the GUI without any dialog (so the local test can use it).
|
|
18
|
+
#
|
|
19
|
+
# The device token is delivered ONLY by pairing — it is never baked into the
|
|
20
|
+
# pkg. The paired config is written to
|
|
21
|
+
# ~/Library/Application Support/Trillboards/trillboards.config.json (mode 0600).
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
# ─── Resolve paths ───────────────────────────────────────────────────────────
|
|
26
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # …/edge/bin
|
|
27
|
+
ROOT="$(cd "$HERE/.." && pwd)" # …/edge
|
|
28
|
+
USER_HOME="${HOME:-$(eval echo "~$(id -un)")}"
|
|
29
|
+
|
|
30
|
+
LAUNCHER="$HERE/trillboards-edge"
|
|
31
|
+
NODE_BIN="$HERE/node" # bundled node runtime
|
|
32
|
+
APP_SUPPORT="$USER_HOME/Library/Application Support/Trillboards"
|
|
33
|
+
CONFIG="$APP_SUPPORT/trillboards.config.json"
|
|
34
|
+
LOG_DIR="$USER_HOME/Library/Logs/Trillboards"
|
|
35
|
+
LA_DIR="$USER_HOME/Library/LaunchAgents"
|
|
36
|
+
PLIST="$LA_DIR/com.trillboards.edge.plist"
|
|
37
|
+
PLIST_TEMPLATE="$ROOT/share/com.trillboards.edge.plist"
|
|
38
|
+
LOCAL_AI_SETUP="$ROOT/share/setup-litertlm.sh"
|
|
39
|
+
LABEL="com.trillboards.edge"
|
|
40
|
+
|
|
41
|
+
API_BASE="${TRILLBOARDS_API_BASE:-}"
|
|
42
|
+
HEADLESS_CODE=""
|
|
43
|
+
UI_STATUS_FILE=""
|
|
44
|
+
|
|
45
|
+
# Parse args. First non-flag positional == the code (headless mode).
|
|
46
|
+
while [ $# -gt 0 ]; do
|
|
47
|
+
case "$1" in
|
|
48
|
+
--api-base) API_BASE="$2"; shift 2;;
|
|
49
|
+
--setup) SETUP_MODE=1; shift;;
|
|
50
|
+
--status-file) UI_STATUS_FILE="$2"; shift 2;;
|
|
51
|
+
-h|--help) sed -n '2,20p' "${BASH_SOURCE[0]}"; exit 0;;
|
|
52
|
+
*) if [ -z "$HEADLESS_CODE" ]; then HEADLESS_CODE="$1"; fi; shift;;
|
|
53
|
+
esac
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
mkdir -p "$APP_SUPPORT" "$LOG_DIR" "$LA_DIR"
|
|
57
|
+
chmod 700 "$APP_SUPPORT" 2>/dev/null || true
|
|
58
|
+
|
|
59
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
60
|
+
is_headless() { [ -n "$HEADLESS_CODE" ]; }
|
|
61
|
+
is_ui() { [ -n "$UI_STATUS_FILE" ]; }
|
|
62
|
+
|
|
63
|
+
# Milestone log (rolling transcript for "send diagnostics" — never codes/tokens).
|
|
64
|
+
plog() { printf '[%s] %s\n' "$(date '+%Y-%m-%dT%H:%M:%S')" "$*" >> "$LOG_DIR/pairing.log" 2>/dev/null || true; }
|
|
65
|
+
|
|
66
|
+
# Advance the credential-free state consumed by the native setup app. The CLI
|
|
67
|
+
# writer merges prior metrics (for example cameraCount) and writes atomically.
|
|
68
|
+
write_ui_status() { # stage title message
|
|
69
|
+
is_ui || return 0
|
|
70
|
+
"$LAUNCHER" setup-status \
|
|
71
|
+
--status-file "$UI_STATUS_FILE" \
|
|
72
|
+
--stage "$1" \
|
|
73
|
+
--title "$2" \
|
|
74
|
+
--message "$3" >/dev/null 2>&1 || true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Confirm the installer-owned local AI service is still healthy before starting
|
|
78
|
+
# the camera connector. Required-bundle mode can repair the runtime or restart
|
|
79
|
+
# the service, but it can never download a missing model from the network.
|
|
80
|
+
verify_local_ai_setup() {
|
|
81
|
+
if [ ! -f "$LOCAL_AI_SETUP" ]; then
|
|
82
|
+
plog "local-ai: setup script missing at $LOCAL_AI_SETUP"
|
|
83
|
+
return 1
|
|
84
|
+
fi
|
|
85
|
+
chmod +x "$LOCAL_AI_SETUP" 2>/dev/null || true
|
|
86
|
+
if TRILLBOARDS_REQUIRE_BUNDLED_MODEL=1 /bin/bash "$LOCAL_AI_SETUP" >> "$LOG_DIR/litertlm-setup.log" 2>&1; then
|
|
87
|
+
plog "local-ai: bundled model and service verified"
|
|
88
|
+
return 0
|
|
89
|
+
fi
|
|
90
|
+
plog "local-ai: verification failed"
|
|
91
|
+
return 1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# The LaunchAgent must actually RUN, not just load — "bootstrapped" alone is the
|
|
95
|
+
# same false success the Windows helper shipped (task registered ≠ task running).
|
|
96
|
+
agent_running() {
|
|
97
|
+
launchctl print "gui/$(id -u)/$LABEL" 2>/dev/null | grep -q 'state = running'
|
|
98
|
+
}
|
|
99
|
+
wait_agent_running() { # up to ~12s
|
|
100
|
+
local i
|
|
101
|
+
for i in $(seq 1 12); do
|
|
102
|
+
agent_running && return 0
|
|
103
|
+
sleep 1
|
|
104
|
+
done
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
run_with_timeout() {
|
|
109
|
+
local seconds="$1"; shift
|
|
110
|
+
"$@" &
|
|
111
|
+
local pid=$!
|
|
112
|
+
local elapsed=0
|
|
113
|
+
while kill -0 "$pid" 2>/dev/null; do
|
|
114
|
+
if [ "$elapsed" -ge "$seconds" ]; then
|
|
115
|
+
kill "$pid" 2>/dev/null || true
|
|
116
|
+
sleep 1
|
|
117
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
118
|
+
wait "$pid" 2>/dev/null || true
|
|
119
|
+
return 124
|
|
120
|
+
fi
|
|
121
|
+
sleep 1
|
|
122
|
+
elapsed=$((elapsed + 1))
|
|
123
|
+
done
|
|
124
|
+
wait "$pid"
|
|
125
|
+
local rc=$?
|
|
126
|
+
return "$rc"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Verify the SAVED pairing works from this machine (network + token). Exit codes
|
|
130
|
+
# from `trillboards-edge verify-connection`: 0 ok / 2 network / 3 auth /
|
|
131
|
+
# 4 no-config / 5 server — resume logic branches on these.
|
|
132
|
+
verify_saved_pairing() {
|
|
133
|
+
"$LAUNCHER" verify-connection --config "$CONFIG" >/dev/null 2>&1
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
dialog_get_code() {
|
|
137
|
+
# Returns the entered code on stdout, or exits 2 if cancelled.
|
|
138
|
+
osascript <<'OSA' 2>/dev/null
|
|
139
|
+
try
|
|
140
|
+
set theResult to display dialog "Paste your Trillboards pairing code" & return & "(Get one from the Trillboards portal → Add a camera → Install.)" default answer "" with title "Trillboards Edge — Pair this Mac" buttons {"Cancel", "Pair"} default button "Pair" with icon note
|
|
141
|
+
set theCode to text returned of theResult
|
|
142
|
+
return theCode
|
|
143
|
+
on error
|
|
144
|
+
error "cancelled" number 2
|
|
145
|
+
end try
|
|
146
|
+
OSA
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Dual-mode chooser. DEFAULT = the validate-first SETUP flow (this Mac shows a
|
|
150
|
+
# short code; the portal proves every camera stream BEFORE anything is
|
|
151
|
+
# created). "Classic code…" keeps the legacy portal-minted claim-code flow
|
|
152
|
+
# working (old wizard versions, scripted installs). Echoes "setup"|"classic",
|
|
153
|
+
# exits 2 on cancel.
|
|
154
|
+
dialog_choose_mode() {
|
|
155
|
+
osascript <<'OSA' 2>/dev/null
|
|
156
|
+
try
|
|
157
|
+
set theResult to display dialog "Connect this Mac's cameras to Trillboards." & return & return & "Start setup: this window shows a code, you type it in the portal, and the portal checks every camera BEFORE anything is created." with title "Trillboards Edge — Camera setup" buttons {"Cancel", "Classic code…", "Start setup"} default button "Start setup" with icon note
|
|
158
|
+
if button returned of theResult is "Start setup" then
|
|
159
|
+
return "setup"
|
|
160
|
+
else
|
|
161
|
+
return "classic"
|
|
162
|
+
end if
|
|
163
|
+
on error
|
|
164
|
+
error "cancelled" number 2
|
|
165
|
+
end try
|
|
166
|
+
OSA
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
dialog_info() { # $1 = message
|
|
170
|
+
osascript -e "display dialog \"$1\" with title \"Trillboards Edge\" buttons {\"OK\"} default button \"OK\" with icon note" >/dev/null 2>&1 || true
|
|
171
|
+
}
|
|
172
|
+
dialog_error() { # $1 = message
|
|
173
|
+
osascript -e "display dialog \"$1\" with title \"Trillboards Edge — Pairing failed\" buttons {\"OK\"} default button \"OK\" with icon stop" >/dev/null 2>&1 || true
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# The ONE true remediation for "config dir/file owned by another account" — the
|
|
177
|
+
# signature of a .pkg installed with admin rights (its postinstall ran as root
|
|
178
|
+
# and left ~/Library/Application Support/Trillboards ROOT-OWNED). Names the REAL
|
|
179
|
+
# cause + the exact one-line fix; deliberately NEVER "check the code". Kept in
|
|
180
|
+
# one place so the pre-write guard AND the post-pair failure branch say the same
|
|
181
|
+
# thing (and match cli.ts configWritePermissionMessage). Real newlines mirror the
|
|
182
|
+
# working multi-line dialog below; single-quotes make the command copy-pasteable
|
|
183
|
+
# and are safe to interpolate into osascript's double-quoted string (no " / \).
|
|
184
|
+
# $1 = "fresh" when this fires AFTER the connector already spent the one-time
|
|
185
|
+
# claim code (post-pair net), so the operator must get a NEW code — not reuse the
|
|
186
|
+
# consumed one. The pre-write guards fire BEFORE the code is spent → default.
|
|
187
|
+
perm_remediation_msg() {
|
|
188
|
+
cat <<MSG
|
|
189
|
+
Trillboards can't save its settings because
|
|
190
|
+
$APP_SUPPORT
|
|
191
|
+
(or the config file inside it) is owned by another account — the installer ran
|
|
192
|
+
with admin rights. This is NOT a code problem.
|
|
193
|
+
|
|
194
|
+
Fix ownership once by pasting this in Terminal:
|
|
195
|
+
|
|
196
|
+
sudo chown -R $(whoami) '$APP_SUPPORT'
|
|
197
|
+
MSG
|
|
198
|
+
if [ "${1:-}" = "fresh" ]; then
|
|
199
|
+
printf '\n%s\n' "Then generate a NEW pairing code in the portal (the last one was already used) and run pairing again."
|
|
200
|
+
else
|
|
201
|
+
printf '\n%s\n' "Then run pairing again."
|
|
202
|
+
fi
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Substitute the plist template tokens with this machine's real paths and load
|
|
206
|
+
# the per-user LaunchAgent (bootstrap on modern macOS, load as fallback).
|
|
207
|
+
bootstrap_agent() {
|
|
208
|
+
/usr/bin/sed \
|
|
209
|
+
-e "s|__LAUNCHER__|$LAUNCHER|g" \
|
|
210
|
+
-e "s|__CONFIG__|$CONFIG|g" \
|
|
211
|
+
-e "s|__LOG_DIR__|$LOG_DIR|g" \
|
|
212
|
+
"$PLIST_TEMPLATE" > "$PLIST"
|
|
213
|
+
|
|
214
|
+
local uid; uid="$(id -u)"
|
|
215
|
+
# Reload cleanly: tear down any prior instance first (ignore errors).
|
|
216
|
+
launchctl bootout "gui/$uid/$LABEL" >/dev/null 2>&1 || true
|
|
217
|
+
launchctl unload "$PLIST" >/dev/null 2>&1 || true
|
|
218
|
+
|
|
219
|
+
if launchctl bootstrap "gui/$uid" "$PLIST" >/dev/null 2>&1; then
|
|
220
|
+
run_with_timeout 10 launchctl kickstart -k "gui/$uid/$LABEL" >/dev/null 2>&1 \
|
|
221
|
+
|| plog "agent: launchctl kickstart did not finish cleanly (continuing to running check)"
|
|
222
|
+
return 0
|
|
223
|
+
fi
|
|
224
|
+
# Fallback for older macOS.
|
|
225
|
+
if launchctl load -w "$PLIST" >/dev/null 2>&1; then
|
|
226
|
+
return 0
|
|
227
|
+
fi
|
|
228
|
+
return 1
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Count paired feeds that still need an rtspUrl (creds-stay-on-prem mode: the
|
|
232
|
+
# server delivers the screen + token, the operator pastes the camera URL into
|
|
233
|
+
# this machine's config). Echoes the count (0 = all feeds ready). Mirrors the
|
|
234
|
+
# `needRtsp` guard in cli.ts runPair so the GUI helper never bootstraps a
|
|
235
|
+
# LaunchAgent that would crash-loop on a config whose feeds have no URL.
|
|
236
|
+
count_feeds_missing_rtsp() {
|
|
237
|
+
[ -f "$CONFIG" ] || { echo 0; return; }
|
|
238
|
+
[ -x "$NODE_BIN" ] || { echo 0; return; } # can't parse → don't block
|
|
239
|
+
"$NODE_BIN" -e '
|
|
240
|
+
try {
|
|
241
|
+
const cfg = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
242
|
+
const feeds = Array.isArray(cfg.cameras)
|
|
243
|
+
? cfg.cameras
|
|
244
|
+
: (cfg.camera && (cfg.camera.rtspUrl || cfg.deviceToken) ? [cfg.camera] : []);
|
|
245
|
+
const missing = feeds.filter((f) => !f || !f.rtspUrl).length;
|
|
246
|
+
process.stdout.write(String(missing));
|
|
247
|
+
} catch (e) { process.stdout.write("0"); }
|
|
248
|
+
' "$CONFIG" 2>/dev/null || echo 0
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# ─── Guard: the config dir must be WRITABLE by us ────────────────────────────
|
|
252
|
+
# If the .pkg was installed with admin rights (system domain), its postinstall
|
|
253
|
+
# ran as root and may have left $APP_SUPPORT ROOT-OWNED. We run as the normal
|
|
254
|
+
# user, so writing the paired config there dies with EACCES — which historically
|
|
255
|
+
# surfaced as the misleading "Double-check the code (codes expire)". Detect it up
|
|
256
|
+
# front (before we even ask for a code), self-heal the common empty-dir case,
|
|
257
|
+
# else FAIL LOUDLY with the real cause + the one-line fix.
|
|
258
|
+
if [ -e "$APP_SUPPORT" ] && [ ! -w "$APP_SUPPORT" ]; then
|
|
259
|
+
# Best-effort self-heal. We run as the user, so we can't chown a root-owned
|
|
260
|
+
# dir — but we CAN (a) fix perms on a dir we already own, or (b) replace an
|
|
261
|
+
# EMPTY dir, since its parent (~/Library/Application Support) is ours. rmdir
|
|
262
|
+
# only succeeds when the dir is empty, so this can NEVER destroy a real config.
|
|
263
|
+
chmod u+rwx "$APP_SUPPORT" 2>/dev/null || true
|
|
264
|
+
if [ ! -w "$APP_SUPPORT" ]; then
|
|
265
|
+
{ rmdir "$APP_SUPPORT" 2>/dev/null && mkdir -p "$APP_SUPPORT" 2>/dev/null; } || true
|
|
266
|
+
chmod 700 "$APP_SUPPORT" 2>/dev/null || true
|
|
267
|
+
fi
|
|
268
|
+
fi
|
|
269
|
+
if [ -e "$APP_SUPPORT" ] && [ ! -w "$APP_SUPPORT" ]; then
|
|
270
|
+
if is_ui; then
|
|
271
|
+
write_ui_status error "Camera settings need permission" "Reinstall Trillboards for this account. If the problem continues, contact Trillboards support."
|
|
272
|
+
elif is_headless; then
|
|
273
|
+
printf '[trillboards-pair] ERROR: %s\n' "$(perm_remediation_msg)" >&2
|
|
274
|
+
else
|
|
275
|
+
dialog_error "$(perm_remediation_msg)"
|
|
276
|
+
fi
|
|
277
|
+
exit 1
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
# The config FILE itself may be root-owned inside an otherwise-writable dir (a
|
|
281
|
+
# prior admin run wrote it as root). The paired-config write would then EACCES
|
|
282
|
+
# AFTER the connector already CONSUMED the one-time claim code — so heal it HERE,
|
|
283
|
+
# before we spend the code. We own the parent dir, so we can just remove the
|
|
284
|
+
# stale file (pairing rewrites it); only if that's somehow impossible do we stop
|
|
285
|
+
# — and because this is still before the launcher, the code is unspent, so the
|
|
286
|
+
# default "run pairing again" is correct.
|
|
287
|
+
if [ -e "$CONFIG" ] && [ ! -w "$CONFIG" ]; then
|
|
288
|
+
rm -f "$CONFIG" 2>/dev/null || true
|
|
289
|
+
fi
|
|
290
|
+
if [ -e "$CONFIG" ] && [ ! -w "$CONFIG" ]; then
|
|
291
|
+
if is_ui; then
|
|
292
|
+
write_ui_status error "Camera settings need permission" "Reinstall Trillboards for this account. If the problem continues, contact Trillboards support."
|
|
293
|
+
elif is_headless; then
|
|
294
|
+
printf '[trillboards-pair] ERROR: %s\n' "$(perm_remediation_msg)" >&2
|
|
295
|
+
else
|
|
296
|
+
dialog_error "$(perm_remediation_msg)"
|
|
297
|
+
fi
|
|
298
|
+
exit 1
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# ─── Battery caution (non-blocking) ──────────────────────────────────────────
|
|
302
|
+
# Sensing dies silently if this Mac sleeps or shuts down. The connector holds a
|
|
303
|
+
# caffeinate keep-awake while running, but a closed lid still stops it.
|
|
304
|
+
if pmset -g batt 2>/dev/null | grep -q "Battery Power"; then
|
|
305
|
+
BATT_NOTE="Note: this Mac is on battery. Keep it plugged in and awake — your cameras stop reporting if it sleeps or shuts down."
|
|
306
|
+
if is_ui; then plog "power: running on battery"; elif is_headless; then echo "[trillboards-pair] $BATT_NOTE"; else dialog_info "$BATT_NOTE"; fi
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# ─── Resume path: a saved, complete pairing skips the code prompt ─────────────
|
|
310
|
+
# Branch on WHY verification failed: only an auth rejection (3) or a token-less
|
|
311
|
+
# config (4) needs a new code — a network/server failure (2/5) means the saved
|
|
312
|
+
# pairing is fine and the operator just fixes connectivity (NO new code).
|
|
313
|
+
if [ -f "$CONFIG" ] && [ "$(count_feeds_missing_rtsp)" = "0" ]; then
|
|
314
|
+
set +e
|
|
315
|
+
verify_saved_pairing
|
|
316
|
+
VERIFY_RC=$?
|
|
317
|
+
set -e
|
|
318
|
+
case "$VERIFY_RC" in
|
|
319
|
+
0)
|
|
320
|
+
plog "resume: existing config verified — skipping code prompt"
|
|
321
|
+
if is_headless; then
|
|
322
|
+
echo "[trillboards-pair] already paired (connection verified) — ensuring the background agent runs"
|
|
323
|
+
fi
|
|
324
|
+
CODE="" # sentinel: skip the pair exchange below
|
|
325
|
+
;;
|
|
326
|
+
2|5)
|
|
327
|
+
plog "resume: saved config, connection check failed (rc=$VERIFY_RC)"
|
|
328
|
+
MSG="This Mac already has a saved Trillboards pairing, but it can't reach Trillboards right now. Check the internet connection, then run this helper again — you will NOT need a new code."
|
|
329
|
+
if is_ui; then write_ui_status error "This computer is offline" "Check the internet connection, then try again. Your saved pairing is still secure."; elif is_headless; then echo "[trillboards-pair] $MSG" >&2; else dialog_error "$MSG"; fi
|
|
330
|
+
exit 1
|
|
331
|
+
;;
|
|
332
|
+
*) : ;; # 3/4 → genuinely re-pair below
|
|
333
|
+
esac
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
# ─── Choose mode / get the code ──────────────────────────────────────────────
|
|
337
|
+
if [ "${CODE+set}" = "set" ] && [ -z "$CODE" ]; then
|
|
338
|
+
: # resume path — already paired + verified, jump straight to the agent stage
|
|
339
|
+
elif [ "${SETUP_MODE:-0}" = "1" ]; then
|
|
340
|
+
: # --setup flag (headless/scripted) — handled below
|
|
341
|
+
elif is_headless; then
|
|
342
|
+
CODE="$HEADLESS_CODE"
|
|
343
|
+
else
|
|
344
|
+
MODE="$(dialog_choose_mode)" || exit 0 # cancelled — nothing to do
|
|
345
|
+
if [ "$MODE" = "setup" ]; then
|
|
346
|
+
SETUP_MODE=1
|
|
347
|
+
else
|
|
348
|
+
if ! CODE="$(dialog_get_code)"; then
|
|
349
|
+
exit 0 # user cancelled — nothing to do
|
|
350
|
+
fi
|
|
351
|
+
if [ -z "$CODE" ]; then
|
|
352
|
+
dialog_error "No code entered. Re-run the pairing helper and paste the code from the portal."
|
|
353
|
+
exit 1
|
|
354
|
+
fi
|
|
355
|
+
fi
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
# ─── Validate-first setup mode ────────────────────────────────────────────────
|
|
359
|
+
# The CLI displays progress in legacy/headless mode or writes structured status
|
|
360
|
+
# for the native app. It exits 0 once the portal finalizes cameras, then the same
|
|
361
|
+
# LaunchAgent stages at the bottom of this script run. rc=2 = expired/timeout.
|
|
362
|
+
if [ "${SETUP_MODE:-0}" = "1" ]; then
|
|
363
|
+
plog "setup: starting validate-first setup mode"
|
|
364
|
+
set +e
|
|
365
|
+
if is_ui; then
|
|
366
|
+
"$LAUNCHER" setup --config "$CONFIG" --status-file "$UI_STATUS_FILE"
|
|
367
|
+
else
|
|
368
|
+
"$LAUNCHER" setup --config "$CONFIG"
|
|
369
|
+
fi
|
|
370
|
+
SETUP_RC=$?
|
|
371
|
+
set -e
|
|
372
|
+
if [ "$SETUP_RC" -eq 0 ]; then
|
|
373
|
+
plog "setup: config received"
|
|
374
|
+
CODE="" # fall through to the agent stage exactly like the resume path
|
|
375
|
+
elif [ "$SETUP_RC" -eq 3 ]; then
|
|
376
|
+
plog "setup: server 404 (rc=3) — classic fallback"
|
|
377
|
+
if is_ui; then
|
|
378
|
+
write_ui_status error "Setup is temporarily unavailable" "Try again shortly or contact Trillboards support."
|
|
379
|
+
exit 1
|
|
380
|
+
fi
|
|
381
|
+
if ! CODE="$(dialog_get_code)"; then exit 0; fi
|
|
382
|
+
if [ -z "$CODE" ]; then
|
|
383
|
+
dialog_error "No code entered. Re-run the pairing helper and paste the code from the portal."
|
|
384
|
+
exit 1
|
|
385
|
+
fi
|
|
386
|
+
SETUP_MODE=0
|
|
387
|
+
elif [ "$SETUP_RC" -eq 2 ]; then
|
|
388
|
+
plog "setup: expired/timeout (rc=2)"
|
|
389
|
+
if is_ui; then write_ui_status error "Setup ended before finishing" "Try again when you are ready. No cameras were created."; else dialog_info "Setup ended without finishing (expired or timed out). Open this helper again to start over — nothing was created, nothing is lost."; fi
|
|
390
|
+
exit 1
|
|
391
|
+
else
|
|
392
|
+
plog "setup: failed (rc=$SETUP_RC)"
|
|
393
|
+
if is_ui; then write_ui_status error "Setup needs attention" "Try again. If the problem continues, contact Trillboards support."; else dialog_error "Setup hit an error. Details were saved to: $LOG_DIR/pairing.log — send that file to Trillboards support if it keeps happening."; fi
|
|
394
|
+
exit 1
|
|
395
|
+
fi
|
|
396
|
+
fi
|
|
397
|
+
|
|
398
|
+
# ─── Pair (skipped on the verified-resume path) ──────────────────────────────
|
|
399
|
+
# `pair` exit codes: 0 = paired + connection VERIFIED; 6 = paired + config saved
|
|
400
|
+
# but the connection check failed (code is SPENT — re-running needs NO new code);
|
|
401
|
+
# anything else = pairing failed with a classified, owner-grade reason already
|
|
402
|
+
# printed by the CLI (bad/expired code vs network vs rate-limit vs server).
|
|
403
|
+
if [ -n "${CODE:-}" ]; then
|
|
404
|
+
PAIR_ARGS=(pair --code "$CODE" --config "$CONFIG")
|
|
405
|
+
[ -n "$API_BASE" ] && PAIR_ARGS+=(--api-base "$API_BASE")
|
|
406
|
+
|
|
407
|
+
SAVED_UNVERIFIED_MSG="Paired and saved, but this Mac can't reach Trillboards right now. Fix the internet connection, then run this helper again — it resumes automatically and you will NOT need a new code."
|
|
408
|
+
# rc=7: the API REJECTED the fresh pairing (auth) — unlike rc=6 the saved
|
|
409
|
+
# config is unusable and the owner genuinely needs a new code.
|
|
410
|
+
AUTH_REJECTED_MSG="Trillboards rejected this new pairing. Generate a FRESH code in the portal and run this helper again."
|
|
411
|
+
|
|
412
|
+
if is_headless; then
|
|
413
|
+
echo "[trillboards-pair] pairing (headless)…"
|
|
414
|
+
set +e
|
|
415
|
+
"$LAUNCHER" "${PAIR_ARGS[@]}"
|
|
416
|
+
PAIR_RC=$?
|
|
417
|
+
set -e
|
|
418
|
+
if [ "$PAIR_RC" -eq 0 ]; then
|
|
419
|
+
echo "[trillboards-pair] paired + connection verified → $CONFIG"
|
|
420
|
+
plog "pair: ok (verified)"
|
|
421
|
+
elif [ "$PAIR_RC" -eq 6 ]; then
|
|
422
|
+
echo "[trillboards-pair] $SAVED_UNVERIFIED_MSG" >&2
|
|
423
|
+
plog "pair: saved but unverified (rc=6)"
|
|
424
|
+
exit 1
|
|
425
|
+
elif [ "$PAIR_RC" -eq 7 ]; then
|
|
426
|
+
echo "[trillboards-pair] $AUTH_REJECTED_MSG" >&2
|
|
427
|
+
plog "pair: saved but auth-rejected (rc=7)"
|
|
428
|
+
exit 1
|
|
429
|
+
else
|
|
430
|
+
echo "[trillboards-pair] PAIRING FAILED" >&2
|
|
431
|
+
plog "pair: failed (rc=$PAIR_RC)"
|
|
432
|
+
exit 1
|
|
433
|
+
fi
|
|
434
|
+
else
|
|
435
|
+
set +e
|
|
436
|
+
PAIR_OUT="$("$LAUNCHER" "${PAIR_ARGS[@]}" 2>&1)"
|
|
437
|
+
PAIR_RC=$?
|
|
438
|
+
set -e
|
|
439
|
+
if [ "$PAIR_RC" -eq 6 ]; then
|
|
440
|
+
plog "pair: saved but unverified (rc=6)"
|
|
441
|
+
dialog_info "$SAVED_UNVERIFIED_MSG"
|
|
442
|
+
exit 1
|
|
443
|
+
elif [ "$PAIR_RC" -eq 7 ]; then
|
|
444
|
+
plog "pair: saved but auth-rejected (rc=7)"
|
|
445
|
+
dialog_error "$AUTH_REJECTED_MSG"
|
|
446
|
+
exit 1
|
|
447
|
+
elif [ "$PAIR_RC" -ne 0 ]; then
|
|
448
|
+
plog "pair: failed (rc=$PAIR_RC)"
|
|
449
|
+
# If the connector failed for a PERMISSION reason (config dir/file owned by
|
|
450
|
+
# another account — an admin install left it root-owned), say THAT + the
|
|
451
|
+
# one-line fix; do NOT tell the operator to re-check a code that is fine. The
|
|
452
|
+
# writability pre-check above catches the common root-owned-DIR case first;
|
|
453
|
+
# this net covers a root-owned config FILE inside an otherwise-writable dir.
|
|
454
|
+
# Matches a raw EACCES AND cli.ts's clean message ("owned by another
|
|
455
|
+
# account" / "sudo chown").
|
|
456
|
+
if printf '%s' "$PAIR_OUT" | grep -qiE 'permission denied|EACCES|EPERM|owned by another account|sudo chown'; then
|
|
457
|
+
# Reached only if a permission failure slipped past the pre-write guards
|
|
458
|
+
# above — by now the connector has already spent the one-time code, so tell
|
|
459
|
+
# the operator to fix ownership AND get a fresh code.
|
|
460
|
+
dialog_error "$(perm_remediation_msg fresh)"
|
|
461
|
+
else
|
|
462
|
+
# The CLI's last lines carry the classified reason — show them verbatim
|
|
463
|
+
# instead of the historical blanket "Double-check the code" (a lie for
|
|
464
|
+
# network/server failures; only the CLI knows which it was).
|
|
465
|
+
dialog_error "Pairing did not complete.\n\n$(printf '%s' "$PAIR_OUT" | tail -6)"
|
|
466
|
+
fi
|
|
467
|
+
exit 1
|
|
468
|
+
fi
|
|
469
|
+
plog "pair: ok (verified)"
|
|
470
|
+
fi
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
# ─── Gate on RTSP URLs, then bootstrap the LaunchAgent ───────────────────────
|
|
474
|
+
# In creds-stay-on-prem mode the server returns cameras WITHOUT an rtspUrl, and
|
|
475
|
+
# the connector writes those as empty placeholders. Starting the LaunchAgent now
|
|
476
|
+
# would crash-loop (`cloud-sense` skips URL-less feeds and exits with nothing to
|
|
477
|
+
# run) while the dialog claims "Paired and running" — so if ANY feed still needs
|
|
478
|
+
# a URL, skip the bootstrap and tell the operator to add the URL(s) first, then
|
|
479
|
+
# start. Mirrors the `needRtsp` guard in cli.ts runPair.
|
|
480
|
+
MISSING_RTSP="$(count_feeds_missing_rtsp)"
|
|
481
|
+
if [ "${MISSING_RTSP:-0}" -gt 0 ] 2>/dev/null; then
|
|
482
|
+
MSG="Paired ($MISSING_RTSP camera feed(s) still need an RTSP URL). Camera credentials stay on this Mac — add the rtsp:// URL(s) to:
|
|
483
|
+
$CONFIG
|
|
484
|
+
then run 'trillboards-edge auto-sense' (or re-run this pairing helper) to start. The background agent was NOT started yet so it won't crash-loop on an incomplete config."
|
|
485
|
+
if is_ui; then
|
|
486
|
+
write_ui_status error "Camera links are incomplete" "One or more cameras still need a stream link. Contact Trillboards support."
|
|
487
|
+
elif is_headless; then
|
|
488
|
+
echo "[trillboards-pair] $MISSING_RTSP feed(s) missing rtspUrl — NOT bootstrapping LaunchAgent. Add URL(s) to $CONFIG then run auto-sense." >&2
|
|
489
|
+
else
|
|
490
|
+
dialog_info "$MSG"
|
|
491
|
+
fi
|
|
492
|
+
exit 0
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
# The config is complete and verified. Local AI was installed before this wizard
|
|
496
|
+
# opened; confirm it is still healthy before starting the camera service.
|
|
497
|
+
write_ui_status local_ai "Checking local AI" "The cameras are ready. Verifying the private AI service installed with Trillboards."
|
|
498
|
+
if ! verify_local_ai_setup; then
|
|
499
|
+
write_ui_status local_ai_error "Local AI needs attention" "The included local AI could not be started. Reinstall Trillboards CCTV Setup; your camera pairing is saved."
|
|
500
|
+
exit 1
|
|
501
|
+
fi
|
|
502
|
+
|
|
503
|
+
write_ui_status starting_service "Starting Trillboards" "Installing and verifying the background camera service."
|
|
504
|
+
|
|
505
|
+
# "Bootstrapped" alone is not success — verify the agent PROCESS is actually
|
|
506
|
+
# running before telling the owner anything is running (the Windows twin of this
|
|
507
|
+
# lie left a 16-camera install "paired" with a never-registered task).
|
|
508
|
+
if bootstrap_agent && wait_agent_running; then
|
|
509
|
+
plog "agent: bootstrapped + running"
|
|
510
|
+
MSG="Paired and running. Local AI is installed and healthy. Trillboards starts automatically at login."
|
|
511
|
+
write_ui_status ready "Camera setup is complete" "Trillboards and private local AI are running in the background."
|
|
512
|
+
if is_ui; then :; elif is_headless; then echo "[trillboards-pair] LaunchAgent running ($LABEL)"; else dialog_info "$MSG"; fi
|
|
513
|
+
exit 0
|
|
514
|
+
fi
|
|
515
|
+
|
|
516
|
+
plog "agent: bootstrap or start FAILED (not running after wait)"
|
|
517
|
+
MSG="Paired successfully, but the background agent did not start. Your pairing IS saved — nothing is lost. Try restarting this Mac (the agent starts at login). If it still fails, send this file to Trillboards support: $LOG_DIR/pairing.log (plus edge.err.log in the same folder)."
|
|
518
|
+
if is_ui; then write_ui_status error "Cameras paired, service not running" "Restart this Mac, then open Trillboards CCTV Setup again. Your pairing is saved."; elif is_headless; then echo "[trillboards-pair] ERROR: $MSG" >&2; else dialog_error "$MSG"; fi
|
|
519
|
+
exit 1
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Keep installer-provisioned local AI healthy across transient service or
|
|
3
|
+
# package-runtime failures. The inner setup script is idempotent and owns its
|
|
4
|
+
# own lock; this wrapper owns a second lock so pairing and auto-sense launches
|
|
5
|
+
# collapse into one retry loop.
|
|
6
|
+
|
|
7
|
+
set -u
|
|
8
|
+
USER_HOME="${HOME:-$(eval echo "~$(id -un)")}"
|
|
9
|
+
ROOT="$USER_HOME/Library/Trillboards/edge"
|
|
10
|
+
SETUP="$ROOT/share/setup-litertlm.sh"
|
|
11
|
+
LOG_DIR="$USER_HOME/Library/Logs/Trillboards"
|
|
12
|
+
LOCK_DIR="$LOG_DIR/litertlm-ensure.lock"
|
|
13
|
+
HEALTH_URL="${LITERT_LM_HEALTH_URL:-http://127.0.0.1:9379/v1/models}"
|
|
14
|
+
MODEL_ID="gemma-4-E2B-it.litertlm"
|
|
15
|
+
MAX_DELAY_SECONDS="${LITERT_LM_RETRY_MAX_SECONDS:-300}"
|
|
16
|
+
|
|
17
|
+
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
|
18
|
+
LOG="$LOG_DIR/litertlm-setup.log"
|
|
19
|
+
log() { printf '[trillboards ensure-litertlm] %s %s\n' "$(date '+%H:%M:%S')" "$*" >>"$LOG" 2>&1; }
|
|
20
|
+
local_ai_healthy() {
|
|
21
|
+
/usr/bin/curl --fail --silent --show-error --max-time 5 "$HEALTH_URL" 2>/dev/null |
|
|
22
|
+
/usr/bin/grep -Fq "$MODEL_ID"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
26
|
+
LOCK_PID="$(cat "$LOCK_DIR/pid" 2>/dev/null || echo '')"
|
|
27
|
+
if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
|
|
28
|
+
log "provisioning retry loop is already running (pid $LOCK_PID)"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
rm -rf "$LOCK_DIR" 2>/dev/null || true
|
|
32
|
+
mkdir "$LOCK_DIR" 2>/dev/null || exit 0
|
|
33
|
+
fi
|
|
34
|
+
printf '%s\n' "$$" >"$LOCK_DIR/pid" 2>/dev/null || true
|
|
35
|
+
cleanup() { rm -rf "$LOCK_DIR" 2>/dev/null || true; }
|
|
36
|
+
trap cleanup EXIT
|
|
37
|
+
trap 'exit 130' INT
|
|
38
|
+
trap 'exit 143' TERM
|
|
39
|
+
|
|
40
|
+
if [ ! -f "$SETUP" ]; then
|
|
41
|
+
log "setup script is missing at $SETUP"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
delay=30
|
|
46
|
+
attempt=0
|
|
47
|
+
while ! local_ai_healthy; do
|
|
48
|
+
attempt=$((attempt + 1))
|
|
49
|
+
log "provisioning attempt $attempt"
|
|
50
|
+
TRILLBOARDS_REQUIRE_BUNDLED_MODEL=1 /bin/bash "$SETUP" >>"$LOG" 2>&1 || true
|
|
51
|
+
if local_ai_healthy; then
|
|
52
|
+
log "local AI is healthy"
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
log "local AI is not ready; retrying in ${delay}s"
|
|
56
|
+
sleep "$delay"
|
|
57
|
+
delay=$((delay * 2))
|
|
58
|
+
if [ "$delay" -gt "$MAX_DELAY_SECONDS" ]; then delay="$MAX_DELAY_SECONDS"; fi
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
log "local AI was already healthy"
|
|
62
|
+
exit 0
|