@trillboards/edge-sdk 0.2.3 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/deploy/docker/Dockerfile.cloud-sense +117 -0
- package/deploy/docker/Dockerfile.cpu +30 -21
- package/deploy/docker/Dockerfile.cuda +30 -21
- package/deploy/docker/Dockerfile.openvino +30 -20
- package/deploy/docker/README.md +9 -4
- package/deploy/docker/connector-entrypoint.sh +122 -0
- package/deploy/helm/README.md +1 -1
- package/deploy/k8s/daemonset.yaml +1 -1
- package/deploy/macos/README.md +161 -0
- package/deploy/macos/TrillboardsSetupApp.m +603 -0
- package/deploy/macos/build-macos-pkg.sh +588 -0
- package/deploy/macos/com.trillboards.edge.plist +62 -0
- package/deploy/macos/com.trillboards.litertlm.plist +45 -0
- package/deploy/macos/pair.command +519 -0
- package/deploy/macos/scripts/ensure-litertlm.sh +62 -0
- package/deploy/macos/scripts/postinstall +153 -0
- package/deploy/macos/scripts/setup-litertlm.sh +272 -0
- package/deploy/macos/test-install-pair.sh +383 -0
- package/deploy/macos/test-setup-litertlm-reinstall.sh +109 -0
- package/deploy/systemd/install.sh +25 -15
- package/deploy/systemd/trillboards-edge.service +4 -1
- package/deploy/windows/README.md +70 -0
- package/deploy/windows/build-windows.ps1 +225 -0
- package/deploy/windows/bundle-windows-installer.ps1 +153 -0
- package/deploy/windows/check-feeds.js +26 -0
- package/deploy/windows/e2e/fixtures/person.jpg +0 -0
- package/deploy/windows/e2e/test-local-sense.ps1 +185 -0
- package/deploy/windows/install-update.ps1 +101 -0
- package/deploy/windows/installer.nsi +138 -0
- package/deploy/windows/register-litertlm-task.ps1 +42 -0
- package/deploy/windows/register-task.ps1 +44 -0
- package/deploy/windows/run-connector.ps1 +69 -0
- package/deploy/windows/setup-litertlm.ps1 +361 -0
- package/deploy/windows/stop-existing.ps1 +72 -0
- package/deploy/windows/trillboards-edge-task.vbs +25 -0
- package/deploy/windows/trillboards-edge.cmd +33 -0
- package/deploy/windows/trillboards-pair.cmd +276 -0
- package/deploy/windows/trillboards-setup.ps1 +456 -0
- package/deploy/windows/trillboards-setup.vbs +13 -0
- package/dist/AudienceAnalyzeClient.d.ts +136 -0
- package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
- package/dist/AudienceAnalyzeClient.js +246 -0
- package/dist/AudienceAnalyzeClient.js.map +1 -0
- package/dist/ChannelProber.d.ts +150 -0
- package/dist/ChannelProber.d.ts.map +1 -0
- package/dist/ChannelProber.js +887 -0
- package/dist/ChannelProber.js.map +1 -0
- package/dist/CloudEscalationPolicy.d.ts +143 -0
- package/dist/CloudEscalationPolicy.d.ts.map +1 -0
- package/dist/CloudEscalationPolicy.js +137 -0
- package/dist/CloudEscalationPolicy.js.map +1 -0
- package/dist/CloudSensingManager.d.ts +533 -0
- package/dist/CloudSensingManager.d.ts.map +1 -0
- package/dist/CloudSensingManager.js +1254 -0
- package/dist/CloudSensingManager.js.map +1 -0
- package/dist/DiscoveryReportClient.d.ts +66 -0
- package/dist/DiscoveryReportClient.d.ts.map +1 -0
- package/dist/DiscoveryReportClient.js +85 -0
- package/dist/DiscoveryReportClient.js.map +1 -0
- package/dist/EdgeAgent.d.ts +12 -2
- package/dist/EdgeAgent.d.ts.map +1 -1
- package/dist/EdgeAgent.js +122 -65
- package/dist/EdgeAgent.js.map +1 -1
- package/dist/LiteRtVlmProvider.d.ts +193 -0
- package/dist/LiteRtVlmProvider.d.ts.map +1 -0
- package/dist/LiteRtVlmProvider.js +436 -0
- package/dist/LiteRtVlmProvider.js.map +1 -0
- package/dist/MosaicSource.d.ts +108 -0
- package/dist/MosaicSource.d.ts.map +1 -0
- package/dist/MosaicSource.js +274 -0
- package/dist/MosaicSource.js.map +1 -0
- package/dist/OnvifClient.d.ts +83 -0
- package/dist/OnvifClient.d.ts.map +1 -0
- package/dist/OnvifClient.js +209 -0
- package/dist/OnvifClient.js.map +1 -0
- package/dist/PairClient.d.ts +106 -0
- package/dist/PairClient.d.ts.map +1 -0
- package/dist/PairClient.js +186 -0
- package/dist/PairClient.js.map +1 -0
- package/dist/RecorderAddressRecovery.d.ts +53 -0
- package/dist/RecorderAddressRecovery.d.ts.map +1 -0
- package/dist/RecorderAddressRecovery.js +359 -0
- package/dist/RecorderAddressRecovery.js.map +1 -0
- package/dist/SceneChangeDetector.d.ts +291 -0
- package/dist/SceneChangeDetector.d.ts.map +1 -0
- package/dist/SceneChangeDetector.js +682 -0
- package/dist/SceneChangeDetector.js.map +1 -0
- package/dist/SensingProfileClient.d.ts +59 -0
- package/dist/SensingProfileClient.d.ts.map +1 -0
- package/dist/SensingProfileClient.js +78 -0
- package/dist/SensingProfileClient.js.map +1 -0
- package/dist/SetupSessionClient.d.ts +114 -0
- package/dist/SetupSessionClient.d.ts.map +1 -0
- package/dist/SetupSessionClient.js +95 -0
- package/dist/SetupSessionClient.js.map +1 -0
- package/dist/VlmAdmissionGate.d.ts +126 -0
- package/dist/VlmAdmissionGate.d.ts.map +1 -0
- package/dist/VlmAdmissionGate.js +166 -0
- package/dist/VlmAdmissionGate.js.map +1 -0
- package/dist/autoSense.d.ts +216 -0
- package/dist/autoSense.d.ts.map +1 -0
- package/dist/autoSense.js +521 -0
- package/dist/autoSense.js.map +1 -0
- package/dist/cli.d.ts +27 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +511 -21
- package/dist/cli.js.map +1 -1
- package/dist/combinedSense.d.ts +128 -0
- package/dist/combinedSense.d.ts.map +1 -0
- package/dist/combinedSense.js +261 -0
- package/dist/combinedSense.js.map +1 -0
- package/dist/config.d.ts +351 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +99 -2
- package/dist/config.js.map +1 -1
- package/dist/connectorCadence.d.ts +135 -0
- package/dist/connectorCadence.d.ts.map +1 -0
- package/dist/connectorCadence.js +151 -0
- package/dist/connectorCadence.js.map +1 -0
- package/dist/dataDir.d.ts +33 -0
- package/dist/dataDir.d.ts.map +1 -0
- package/dist/dataDir.js +76 -0
- package/dist/dataDir.js.map +1 -0
- package/dist/deployedCadence.d.ts +167 -0
- package/dist/deployedCadence.d.ts.map +1 -0
- package/dist/deployedCadence.js +209 -0
- package/dist/deployedCadence.js.map +1 -0
- package/dist/deployedCadenceSource.d.ts +48 -0
- package/dist/deployedCadenceSource.d.ts.map +1 -0
- package/dist/deployedCadenceSource.js +74 -0
- package/dist/deployedCadenceSource.js.map +1 -0
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.d.ts.map +1 -1
- package/dist/diagnose.js +13 -12
- package/dist/diagnose.js.map +1 -1
- package/dist/discovery.d.ts +131 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +499 -0
- package/dist/discovery.js.map +1 -0
- package/dist/gemmaModelOta.d.ts +118 -0
- package/dist/gemmaModelOta.d.ts.map +1 -0
- package/dist/gemmaModelOta.js +291 -0
- package/dist/gemmaModelOta.js.map +1 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +41 -18
- package/dist/init.js.map +1 -1
- package/dist/keepAwake.d.ts +33 -0
- package/dist/keepAwake.d.ts.map +1 -0
- package/dist/keepAwake.js +54 -0
- package/dist/keepAwake.js.map +1 -0
- package/dist/legacyRecorderProfile.d.ts +20 -0
- package/dist/legacyRecorderProfile.d.ts.map +1 -0
- package/dist/legacyRecorderProfile.js +27 -0
- package/dist/legacyRecorderProfile.js.map +1 -0
- package/dist/localInferenceSchema.d.ts +132 -0
- package/dist/localInferenceSchema.d.ts.map +1 -0
- package/dist/localInferenceSchema.js +274 -0
- package/dist/localInferenceSchema.js.map +1 -0
- package/dist/localSenseE2eHarness.d.ts +101 -0
- package/dist/localSenseE2eHarness.d.ts.map +1 -0
- package/dist/localSenseE2eHarness.js +279 -0
- package/dist/localSenseE2eHarness.js.map +1 -0
- package/dist/localSensing.d.ts +268 -0
- package/dist/localSensing.d.ts.map +1 -0
- package/dist/localSensing.js +492 -0
- package/dist/localSensing.js.map +1 -0
- package/dist/modelIntegrity.d.ts +59 -0
- package/dist/modelIntegrity.d.ts.map +1 -0
- package/dist/modelIntegrity.js +165 -0
- package/dist/modelIntegrity.js.map +1 -0
- package/dist/pairVerify.d.ts +100 -0
- package/dist/pairVerify.d.ts.map +1 -0
- package/dist/pairVerify.js +296 -0
- package/dist/pairVerify.js.map +1 -0
- package/dist/qrScanner.d.ts +33 -0
- package/dist/qrScanner.d.ts.map +1 -0
- package/dist/qrScanner.js +103 -0
- package/dist/qrScanner.js.map +1 -0
- package/dist/runtimePaths.d.ts +10 -0
- package/dist/runtimePaths.d.ts.map +1 -0
- package/dist/runtimePaths.js +79 -0
- package/dist/runtimePaths.js.map +1 -0
- package/dist/sensingRunMode.d.ts +68 -0
- package/dist/sensingRunMode.d.ts.map +1 -0
- package/dist/sensingRunMode.js +346 -0
- package/dist/sensingRunMode.js.map +1 -0
- package/dist/setupMode.d.ts +98 -0
- package/dist/setupMode.d.ts.map +1 -0
- package/dist/setupMode.js +828 -0
- package/dist/setupMode.js.map +1 -0
- package/dist/setupProgress.d.ts +31 -0
- package/dist/setupProgress.d.ts.map +1 -0
- package/dist/setupProgress.js +173 -0
- package/dist/setupProgress.js.map +1 -0
- package/package.json +13 -4
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# build-macos-pkg.sh — build a self-contained, home-domain macOS installer
|
|
4
|
+
# (.pkg) for the Trillboards local-first CCTV connector.
|
|
5
|
+
#
|
|
6
|
+
# The resulting .pkg lays down a fully self-contained tree under the user's
|
|
7
|
+
# home (~/Library/Trillboards/edge) so the operator needs NO system Node,
|
|
8
|
+
# ffmpeg, Docker, or terminal:
|
|
9
|
+
#
|
|
10
|
+
# ~/Library/Trillboards/edge/
|
|
11
|
+
# bin/node real Node v22 arm64/x64 runtime (pinned, official)
|
|
12
|
+
# bin/ffmpeg bundled ffmpeg (pinned static darwin build, downloaded
|
|
13
|
+
# at build time — NO ffmpeg-static npm dep)
|
|
14
|
+
# bin/trillboards-edge launcher: PATH=bin:$PATH; exec node lib/.../cli.js
|
|
15
|
+
# bin/trillboards-pair first-run pairing helper (GUI + headless)
|
|
16
|
+
# lib/edge-sdk/dist/cli.js the connector CLI
|
|
17
|
+
# lib/node_modules/... the runtime subset (edge-core +
|
|
18
|
+
# edge-platform-rtsp + edge-sdk + their
|
|
19
|
+
# transitive deps), WITHOUT the heavy local
|
|
20
|
+
# ONNX/sharp inference stack.
|
|
21
|
+
#
|
|
22
|
+
# Because cloud-sense + pair are loaded WITHOUT the local-inference stack
|
|
23
|
+
# (cli.ts lazy-requires EdgeAgent), the bundle omits onnxruntime-node/sharp/
|
|
24
|
+
# better-sqlite3 — keeping the pkg small.
|
|
25
|
+
#
|
|
26
|
+
# Install (no sudo, no admin password):
|
|
27
|
+
# installer -pkg <pkg> -target CurrentUserHomeDirectory
|
|
28
|
+
#
|
|
29
|
+
# Usage:
|
|
30
|
+
# build-macos-pkg.sh [--arch arm64|x64] [--universal] [--node-version vX.Y.Z]
|
|
31
|
+
# [--sign-app "Developer ID Application: …"]
|
|
32
|
+
# [--sign-installer "Developer ID Installer: …"]
|
|
33
|
+
# [--bundle-model] (default; include the pinned ~2.4 GB VLM)
|
|
34
|
+
# [--skip-model] (engineering-only slim build)
|
|
35
|
+
# [--offline] (skip the official Node + ffmpeg downloads,
|
|
36
|
+
# copy the host `node`/`ffmpeg` instead — for
|
|
37
|
+
# air-gapped CI)
|
|
38
|
+
#
|
|
39
|
+
# --universal builds ONE fat arm64+x86_64 .pkg (node + ffmpeg lipo'd from both
|
|
40
|
+
# arches; bundled fsevents.node is already universal) that installs + runs on
|
|
41
|
+
# every Mac. This is the RELEASE artifact: a browser can't reliably detect a
|
|
42
|
+
# Mac's CPU arch (Safari reports "MacIntel" even on Apple Silicon), so the portal
|
|
43
|
+
# cannot hand out an arch-specific download. --arch builds a single-arch .pkg
|
|
44
|
+
# (faster, for local testing).
|
|
45
|
+
#
|
|
46
|
+
# ffmpeg: a pinned static darwin build (eugeneware/ffmpeg-static b6.0 — the same
|
|
47
|
+
# upstream the ffmpeg-static npm package ships) is downloaded at build time and
|
|
48
|
+
# its SHA-256 verified, exactly like the pinned Node runtime. There is NO
|
|
49
|
+
# ffmpeg-static npm dependency, so the workspace lockfile carries no ffmpeg
|
|
50
|
+
# binary churn. Override the release/checksums with FFMPEG_STATIC_TAG /
|
|
51
|
+
# FFMPEG_SHA256_ARM64 / FFMPEG_SHA256_X64 if upstream is re-pinned.
|
|
52
|
+
#
|
|
53
|
+
# Signing (two distinct cert classes — BOTH required for a notarized, double-
|
|
54
|
+
# click-clean installer):
|
|
55
|
+
# 1. --sign-app "Developer ID Application: …" — codesigns the embedded Mach-O
|
|
56
|
+
# binaries (node, ffmpeg, any native .node addons) with the hardened runtime
|
|
57
|
+
# + a secure timestamp. The notary service REQUIRES every embedded executable
|
|
58
|
+
# to carry a Developer ID Application signature + hardened runtime, so an
|
|
59
|
+
# unsigned ffmpeg/node would fail notarization even if the .pkg itself is
|
|
60
|
+
# signed.
|
|
61
|
+
# 2. --sign-installer "Developer ID Installer: …" — productsigns the final .pkg.
|
|
62
|
+
# (or set MAC_APP_IDENTITY / MAC_INSTALLER_IDENTITY). With no flags the build is
|
|
63
|
+
# UNSIGNED (fine for `installer -target CurrentUserHomeDirectory` local installs,
|
|
64
|
+
# but Gatekeeper blocks a downloaded copy). Notarize after signing — see
|
|
65
|
+
# README.md for the notarytool follow-on.
|
|
66
|
+
|
|
67
|
+
set -euo pipefail
|
|
68
|
+
|
|
69
|
+
# ─── Locate ourselves + the edge-sdk package root ────────────────────────────
|
|
70
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
71
|
+
# deploy/macos -> deploy -> edge-sdk
|
|
72
|
+
PKG_ROOT="$(cd "$HERE/../.." && pwd)" # …/packages/edge-sdk
|
|
73
|
+
WORKSPACE_ROOT="$(cd "$PKG_ROOT/../.." && pwd)" # …/trillboards-edge-sdk
|
|
74
|
+
|
|
75
|
+
BUNDLE_ID="com.trillboards.edge"
|
|
76
|
+
APP_NAME="TrillboardsEdge"
|
|
77
|
+
INSTALL_SUBDIR="Library/Trillboards/edge" # relative to the user home
|
|
78
|
+
|
|
79
|
+
# ─── Args ────────────────────────────────────────────────────────────────────
|
|
80
|
+
ARCH="$(uname -m)" # arm64 | x86_64
|
|
81
|
+
[ "$ARCH" = "x86_64" ] && ARCH="x64"
|
|
82
|
+
NODE_VERSION="v22.22.0"
|
|
83
|
+
SIGN_IDENTITY="${MAC_INSTALLER_IDENTITY:-}"
|
|
84
|
+
APP_IDENTITY="${MAC_APP_IDENTITY:-}"
|
|
85
|
+
OFFLINE=0
|
|
86
|
+
UNIVERSAL=0 # --universal → bundle a fat arm64+x86_64 node/ffmpeg (runs on every Mac)
|
|
87
|
+
|
|
88
|
+
# Pinned static ffmpeg (darwin) — eugeneware/ffmpeg-static release `b6.0`
|
|
89
|
+
# (ffmpeg 6.0). This is the exact upstream the ffmpeg-static npm package
|
|
90
|
+
# downloads, so the bundled binary is identical to before — we just fetch it
|
|
91
|
+
# directly at build time (like the Node runtime) instead of carrying an npm dep
|
|
92
|
+
# + lockfile entry. SHA-256 verified after download.
|
|
93
|
+
FFMPEG_STATIC_TAG="${FFMPEG_STATIC_TAG:-b6.0}"
|
|
94
|
+
FFMPEG_SHA256_ARM64="${FFMPEG_SHA256_ARM64:-a90e3db6a3fd35f6074b013f948b1aa45b31c6375489d39e572bea3f18336584}"
|
|
95
|
+
FFMPEG_SHA256_X64="${FFMPEG_SHA256_X64:-cfe20936c83ecf5d68e424b87e8cc45b24dd6be81787810123bb964a0df686f9}"
|
|
96
|
+
|
|
97
|
+
# Required pre-bundled on-device VLM (Gemma 4 E2B, LiteRT-LM). Customer packages
|
|
98
|
+
# include the pinned model so installation completes before camera pairing and
|
|
99
|
+
# never starts a multi-gigabyte post-pair download. GEMMA_MODEL_FILE can point
|
|
100
|
+
# at a verified local cache; otherwise the release build fetches the pinned URL.
|
|
101
|
+
# Override GEMMA_MODEL_URL / GEMMA_MODEL_SHA256 only when upstream is re-pinned.
|
|
102
|
+
GEMMA_MODEL_ID="gemma-4-E2B-it.litertlm"
|
|
103
|
+
GEMMA_MODEL_URL="${GEMMA_MODEL_URL:-https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/main/gemma-4-E2B-it.litertlm}"
|
|
104
|
+
GEMMA_MODEL_SHA256="${GEMMA_MODEL_SHA256:-181938105e0eefd105961417e8da75903eacda102c4fce9ce90f50b97139a63c}"
|
|
105
|
+
GEMMA_MODEL_FILE="${GEMMA_MODEL_FILE:-}"
|
|
106
|
+
BUNDLE_MODEL=1
|
|
107
|
+
|
|
108
|
+
while [ $# -gt 0 ]; do
|
|
109
|
+
case "$1" in
|
|
110
|
+
--arch) ARCH="$2"; shift 2;;
|
|
111
|
+
--node-version) NODE_VERSION="$2"; shift 2;;
|
|
112
|
+
--sign-app) APP_IDENTITY="$2"; shift 2;;
|
|
113
|
+
--sign-installer) SIGN_IDENTITY="$2"; shift 2;;
|
|
114
|
+
--offline) OFFLINE=1; shift;;
|
|
115
|
+
--universal) UNIVERSAL=1; shift;;
|
|
116
|
+
--bundle-model) BUNDLE_MODEL=1; shift;;
|
|
117
|
+
--skip-model) BUNDLE_MODEL=0; shift;;
|
|
118
|
+
-h|--help) sed -n '2,40p' "${BASH_SOURCE[0]}"; exit 0;;
|
|
119
|
+
*) echo "Unknown arg: $1" >&2; exit 2;;
|
|
120
|
+
esac
|
|
121
|
+
done
|
|
122
|
+
|
|
123
|
+
# Offline customer builds must provide the model cache explicitly. Slim builds
|
|
124
|
+
# remain available only for focused engineering checks.
|
|
125
|
+
if [ "$OFFLINE" -eq 1 ] && [ "$BUNDLE_MODEL" -eq 1 ] && [ -z "$GEMMA_MODEL_FILE" ]; then
|
|
126
|
+
echo "--offline with the customer model bundle requires GEMMA_MODEL_FILE" >&2
|
|
127
|
+
exit 2
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Normalize Node arch token used in the official tarball name.
|
|
131
|
+
case "$ARCH" in
|
|
132
|
+
arm64) NODE_ARCH="arm64";;
|
|
133
|
+
x64) NODE_ARCH="x64";;
|
|
134
|
+
*) echo "Unsupported --arch '$ARCH' (use arm64 or x64)" >&2; exit 2;;
|
|
135
|
+
esac
|
|
136
|
+
|
|
137
|
+
# A universal build produces ONE fat arm64+x86_64 .pkg that runs on every Mac —
|
|
138
|
+
# necessary because a browser cannot reliably detect a Mac's CPU arch (Safari
|
|
139
|
+
# reports "MacIntel" even on Apple Silicon), so the portal can't pick an
|
|
140
|
+
# arch-specific download.
|
|
141
|
+
ARCH_LABEL="$ARCH"
|
|
142
|
+
[ "$UNIVERSAL" -eq 1 ] && ARCH_LABEL="universal"
|
|
143
|
+
|
|
144
|
+
VERSION="$(node -e "process.stdout.write(require('$PKG_ROOT/package.json').version)")"
|
|
145
|
+
DIST_DIR="$PKG_ROOT/deploy/macos/dist-installer"
|
|
146
|
+
STAGE="$DIST_DIR/stage-$ARCH_LABEL" # the payload root (== the user home view)
|
|
147
|
+
PAYLOAD="$STAGE/$INSTALL_SUBDIR" # …/Library/Trillboards/edge
|
|
148
|
+
SCRATCH="$DIST_DIR/scratch-$ARCH_LABEL"
|
|
149
|
+
PKG_OUT="$DIST_DIR/${APP_NAME}-${VERSION}-${ARCH_LABEL}.pkg"
|
|
150
|
+
COMPONENT_PKG="$SCRATCH/${APP_NAME}-component.pkg"
|
|
151
|
+
SETUP_APP="$STAGE/Applications/Trillboards CCTV Setup.app"
|
|
152
|
+
|
|
153
|
+
log() { printf '\033[1;34m[build]\033[0m %s\n' "$*"; }
|
|
154
|
+
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; }
|
|
155
|
+
die() { printf '\033[1;31m[err]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
156
|
+
|
|
157
|
+
log "edge-sdk v$VERSION arch=$ARCH_LABEL node=$NODE_VERSION sign-app=${APP_IDENTITY:-<none>} sign-installer=${SIGN_IDENTITY:-<none>}"
|
|
158
|
+
|
|
159
|
+
# ─── Clean + recreate the staging tree (idempotent) ──────────────────────────
|
|
160
|
+
rm -rf "$STAGE" "$SCRATCH"
|
|
161
|
+
mkdir -p "$PAYLOAD/bin" "$PAYLOAD/lib" "$SCRATCH"
|
|
162
|
+
|
|
163
|
+
# ─── 1. Build ALL edge workspaces (tsc, dependency order) ─────────────────────
|
|
164
|
+
# edge-sdk's tsc imports edge-core, edge-platform-rtsp/linux/windows, edge-sensing,
|
|
165
|
+
# edge-ads, edge-federated AND edge-cloud — so EVERY workspace's dist/.d.ts must
|
|
166
|
+
# already exist when edge-sdk compiles. Building only edge-core + edge-platform-rtsp
|
|
167
|
+
# first (and the rest AFTER edge-sdk) made edge-sdk's tsc fail on missing dep types
|
|
168
|
+
# in a clean CI `npm ci` checkout. The root `build` is `npm run build --workspaces`,
|
|
169
|
+
# which compiles every workspace in dependency order — the only correct guarantee
|
|
170
|
+
# that edge-sdk's deps are present. (No output suppression: a future build error
|
|
171
|
+
# must surface in the CI log, not be swallowed into a bare "edge-sdk build failed".)
|
|
172
|
+
log "Building all edge workspaces (tsc, dependency order)…"
|
|
173
|
+
( cd "$WORKSPACE_ROOT" && npm run build ) || die "edge workspace build failed (tsc)"
|
|
174
|
+
|
|
175
|
+
# ─── 2. Lay down the edge-sdk dist ───────────────────────────────────────────
|
|
176
|
+
log "Staging edge-sdk dist…"
|
|
177
|
+
mkdir -p "$PAYLOAD/lib/edge-sdk"
|
|
178
|
+
cp -R "$PKG_ROOT/dist" "$PAYLOAD/lib/edge-sdk/dist"
|
|
179
|
+
cp "$PKG_ROOT/package.json" "$PAYLOAD/lib/edge-sdk/package.json"
|
|
180
|
+
|
|
181
|
+
# ─── 3. Lay down the runtime node_modules subset (NO heavy native stack) ──────
|
|
182
|
+
# We copy the workspace's already-resolved node_modules (correct transitive
|
|
183
|
+
# closure) and EXCLUDE the heavy/native/dev packages. cloud-sense + pair never
|
|
184
|
+
# require them (EdgeAgent → edge-sensing → onnxruntime-node is lazy now), so
|
|
185
|
+
# their absence is fine and keeps the pkg ~50 MB instead of ~270 MB.
|
|
186
|
+
log "Staging runtime node_modules (excluding ONNX/sharp/sqlite/dev)…"
|
|
187
|
+
mkdir -p "$PAYLOAD/lib/node_modules"
|
|
188
|
+
rsync -a \
|
|
189
|
+
--exclude 'onnxruntime-node' \
|
|
190
|
+
--exclude 'sharp' \
|
|
191
|
+
--exclude '@img' \
|
|
192
|
+
--exclude 'better-sqlite3' \
|
|
193
|
+
--exclude 'ffmpeg-static' \
|
|
194
|
+
--exclude '.cache' \
|
|
195
|
+
--exclude '.bin' \
|
|
196
|
+
--exclude '.package-lock.json' \
|
|
197
|
+
--exclude 'typescript' \
|
|
198
|
+
--exclude 'jest' \
|
|
199
|
+
--exclude 'jest-*' \
|
|
200
|
+
--exclude '@jest' \
|
|
201
|
+
--exclude 'ts-jest' \
|
|
202
|
+
--exclude '@types' \
|
|
203
|
+
"$WORKSPACE_ROOT/node_modules/" "$PAYLOAD/lib/node_modules/"
|
|
204
|
+
|
|
205
|
+
# Overlay the @trillboards workspace packages with their REAL built dist
|
|
206
|
+
# (in the workspace they are symlinks → copy the materialized dist instead).
|
|
207
|
+
for p in edge-core edge-sensing edge-ads edge-federated edge-cloud \
|
|
208
|
+
edge-platform-rtsp edge-platform-linux edge-platform-windows \
|
|
209
|
+
edge-csi edge-sdk; do
|
|
210
|
+
src="$WORKSPACE_ROOT/packages/$p"
|
|
211
|
+
[ -d "$src" ] || continue
|
|
212
|
+
dest="$PAYLOAD/lib/node_modules/@trillboards/$p"
|
|
213
|
+
rm -rf "$dest"
|
|
214
|
+
mkdir -p "$dest"
|
|
215
|
+
[ -d "$src/dist" ] && cp -R "$src/dist" "$dest/dist"
|
|
216
|
+
cp "$src/package.json" "$dest/package.json"
|
|
217
|
+
done
|
|
218
|
+
|
|
219
|
+
# ─── 4. Bundle the Node runtime ──────────────────────────────────────────────
|
|
220
|
+
NODE_BIN="$PAYLOAD/bin/node"
|
|
221
|
+
# Download the official Node runtime for a given arch into a path (no fallback —
|
|
222
|
+
# a universal build must have the real per-arch slice to lipo).
|
|
223
|
+
fetch_node_arch() { # arch outpath
|
|
224
|
+
local a="$1" out="$2" tb="node-${NODE_VERSION}-darwin-$1.tar.gz"
|
|
225
|
+
curl -fsSL --max-time 180 -o "$SCRATCH/$tb" "https://nodejs.org/dist/${NODE_VERSION}/$tb" \
|
|
226
|
+
|| die "Node download failed for darwin-$1"
|
|
227
|
+
tar -xzf "$SCRATCH/$tb" -C "$SCRATCH"
|
|
228
|
+
cp "$SCRATCH/node-${NODE_VERSION}-darwin-$1/bin/node" "$out"
|
|
229
|
+
}
|
|
230
|
+
if [ "$UNIVERSAL" -eq 1 ]; then
|
|
231
|
+
log "Building universal Node (arm64 + x64) via lipo…"
|
|
232
|
+
fetch_node_arch arm64 "$SCRATCH/node-arm64"
|
|
233
|
+
fetch_node_arch x64 "$SCRATCH/node-x64"
|
|
234
|
+
lipo -create "$SCRATCH/node-arm64" "$SCRATCH/node-x64" -output "$NODE_BIN" || die "lipo node failed"
|
|
235
|
+
elif [ "$OFFLINE" -eq 1 ]; then
|
|
236
|
+
log "Offline mode: copying host node ($(command -v node))…"
|
|
237
|
+
cp "$(command -v node)" "$NODE_BIN"
|
|
238
|
+
else
|
|
239
|
+
TARBALL="node-${NODE_VERSION}-darwin-${NODE_ARCH}.tar.gz"
|
|
240
|
+
URL="https://nodejs.org/dist/${NODE_VERSION}/${TARBALL}"
|
|
241
|
+
log "Downloading official Node runtime: $URL"
|
|
242
|
+
if curl -fsSL --max-time 120 -o "$SCRATCH/$TARBALL" "$URL"; then
|
|
243
|
+
tar -xzf "$SCRATCH/$TARBALL" -C "$SCRATCH"
|
|
244
|
+
cp "$SCRATCH/node-${NODE_VERSION}-darwin-${NODE_ARCH}/bin/node" "$NODE_BIN"
|
|
245
|
+
else
|
|
246
|
+
warn "Node download failed — falling back to host node ($(command -v node))."
|
|
247
|
+
cp "$(command -v node)" "$NODE_BIN"
|
|
248
|
+
fi
|
|
249
|
+
fi
|
|
250
|
+
chmod +x "$NODE_BIN"
|
|
251
|
+
# Verify the bundled node actually runs on this host.
|
|
252
|
+
"$NODE_BIN" --version >/dev/null 2>&1 || die "bundled node failed --version"
|
|
253
|
+
log "Bundled node: $("$NODE_BIN" --version) [$(lipo -archs "$NODE_BIN" 2>/dev/null || echo single-arch)]"
|
|
254
|
+
|
|
255
|
+
# ─── 5. Bundle ffmpeg (pinned static darwin build — no npm dep) ───────────────
|
|
256
|
+
# Download the pinned static ffmpeg for this arch and verify its SHA-256, exactly
|
|
257
|
+
# like the Node runtime above. NO ffmpeg-static npm dependency → zero lockfile
|
|
258
|
+
# churn. cloud-sense uses the bundled ffmpeg (mjpeg muxer → JPEG) so no image
|
|
259
|
+
# library (sharp) is needed.
|
|
260
|
+
FFMPEG_BIN="$PAYLOAD/bin/ffmpeg"
|
|
261
|
+
case "$NODE_ARCH" in
|
|
262
|
+
arm64) FFMPEG_EXPECTED_SHA="$FFMPEG_SHA256_ARM64";;
|
|
263
|
+
x64) FFMPEG_EXPECTED_SHA="$FFMPEG_SHA256_X64";;
|
|
264
|
+
esac
|
|
265
|
+
|
|
266
|
+
# Verify a copied/downloaded ffmpeg against the pinned SHA (when not host-fallback).
|
|
267
|
+
verify_ffmpeg_sha() {
|
|
268
|
+
local f="$1" expected="$2"
|
|
269
|
+
[ -n "$expected" ] || return 0
|
|
270
|
+
local actual
|
|
271
|
+
actual="$(shasum -a 256 "$f" 2>/dev/null | awk '{print $1}')"
|
|
272
|
+
[ "$actual" = "$expected" ]
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
# Download + SHA-verify a single-arch static ffmpeg into a path (for lipo).
|
|
276
|
+
fetch_ffmpeg_arch() { # arch outpath
|
|
277
|
+
local file="ffmpeg-darwin-$1" sha
|
|
278
|
+
case "$1" in arm64) sha="$FFMPEG_SHA256_ARM64";; x64) sha="$FFMPEG_SHA256_X64";; esac
|
|
279
|
+
curl -fsSL --max-time 180 -o "$SCRATCH/$file" \
|
|
280
|
+
"https://github.com/eugeneware/ffmpeg-static/releases/download/${FFMPEG_STATIC_TAG}/$file" \
|
|
281
|
+
|| die "ffmpeg download failed for darwin-$1"
|
|
282
|
+
verify_ffmpeg_sha "$SCRATCH/$file" "$sha" \
|
|
283
|
+
|| die "ffmpeg SHA-256 mismatch darwin-$1 (expected $sha, got $(shasum -a 256 "$SCRATCH/$file" 2>/dev/null | awk '{print $1}'))"
|
|
284
|
+
cp "$SCRATCH/$file" "$2"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if [ "$UNIVERSAL" -eq 1 ]; then
|
|
288
|
+
log "Building universal ffmpeg (arm64 + x64) via lipo…"
|
|
289
|
+
fetch_ffmpeg_arch arm64 "$SCRATCH/ffmpeg-arm64"
|
|
290
|
+
fetch_ffmpeg_arch x64 "$SCRATCH/ffmpeg-x64"
|
|
291
|
+
lipo -create "$SCRATCH/ffmpeg-arm64" "$SCRATCH/ffmpeg-x64" -output "$FFMPEG_BIN" || die "lipo ffmpeg failed"
|
|
292
|
+
log "Verified both arch SHA-256 + lipo'd universal ffmpeg"
|
|
293
|
+
elif [ "$OFFLINE" -eq 1 ]; then
|
|
294
|
+
HOST_FFMPEG="$(command -v ffmpeg || true)"
|
|
295
|
+
[ -n "$HOST_FFMPEG" ] || die "offline mode: no host ffmpeg on PATH to copy."
|
|
296
|
+
log "Offline mode: copying host ffmpeg ($HOST_FFMPEG)…"
|
|
297
|
+
cp "$HOST_FFMPEG" "$FFMPEG_BIN"
|
|
298
|
+
else
|
|
299
|
+
FFMPEG_FILE="ffmpeg-darwin-${NODE_ARCH}"
|
|
300
|
+
FFMPEG_URL="https://github.com/eugeneware/ffmpeg-static/releases/download/${FFMPEG_STATIC_TAG}/${FFMPEG_FILE}"
|
|
301
|
+
log "Downloading pinned static ffmpeg: $FFMPEG_URL"
|
|
302
|
+
if curl -fsSL --max-time 180 -o "$SCRATCH/$FFMPEG_FILE" "$FFMPEG_URL"; then
|
|
303
|
+
if verify_ffmpeg_sha "$SCRATCH/$FFMPEG_FILE" "$FFMPEG_EXPECTED_SHA"; then
|
|
304
|
+
cp "$SCRATCH/$FFMPEG_FILE" "$FFMPEG_BIN"
|
|
305
|
+
log "Verified ffmpeg SHA-256 ($FFMPEG_EXPECTED_SHA)"
|
|
306
|
+
else
|
|
307
|
+
die "ffmpeg SHA-256 mismatch for $FFMPEG_FILE (expected $FFMPEG_EXPECTED_SHA, got $(shasum -a 256 "$SCRATCH/$FFMPEG_FILE" 2>/dev/null | awk '{print $1}')). Refusing to bundle an unverified binary."
|
|
308
|
+
fi
|
|
309
|
+
else
|
|
310
|
+
HOST_FFMPEG="$(command -v ffmpeg || true)"
|
|
311
|
+
[ -n "$HOST_FFMPEG" ] || die "ffmpeg download failed and no host ffmpeg on PATH to fall back to."
|
|
312
|
+
warn "ffmpeg download failed — falling back to host ffmpeg ($HOST_FFMPEG)."
|
|
313
|
+
cp "$HOST_FFMPEG" "$FFMPEG_BIN"
|
|
314
|
+
fi
|
|
315
|
+
fi
|
|
316
|
+
chmod +x "$FFMPEG_BIN"
|
|
317
|
+
# Verify the bundled ffmpeg actually runs on this host.
|
|
318
|
+
"$FFMPEG_BIN" -version >/dev/null 2>&1 || die "bundled ffmpeg failed -version"
|
|
319
|
+
log "Bundled ffmpeg: $("$FFMPEG_BIN" -version 2>/dev/null | head -1) [$(lipo -archs "$FFMPEG_BIN" 2>/dev/null || echo single-arch)]"
|
|
320
|
+
|
|
321
|
+
# ─── 6. Write the launcher ───────────────────────────────────────────────────
|
|
322
|
+
# The launcher puts the bundled bin/ first on PATH (so the connector's default
|
|
323
|
+
# ffmpeg='ffmpeg' resolves to OUR ffmpeg) and execs the bundled node against the
|
|
324
|
+
# bundled cli.js. No system Node or ffmpeg is touched.
|
|
325
|
+
LAUNCHER="$PAYLOAD/bin/trillboards-edge"
|
|
326
|
+
cat > "$LAUNCHER" <<'LAUNCH'
|
|
327
|
+
#!/bin/bash
|
|
328
|
+
# Trillboards Edge connector launcher (bundled runtime — no system deps).
|
|
329
|
+
set -euo pipefail
|
|
330
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # …/edge/bin
|
|
331
|
+
ROOT="$(cd "$HERE/.." && pwd)" # …/edge
|
|
332
|
+
export PATH="$HERE:$PATH" # bundled ffmpeg first
|
|
333
|
+
export NODE_PATH="$ROOT/lib/node_modules"
|
|
334
|
+
|
|
335
|
+
# The agent is loaded only after a complete pairing. If the local AI service
|
|
336
|
+
# needs repair after a reboot, restart it automatically at the next agent start.
|
|
337
|
+
# The setup script's lock collapses this with the pair helper's launch.
|
|
338
|
+
if [ "${1:-}" = "auto-sense" ]; then
|
|
339
|
+
LITERT_ROOT="${LITERT_LM_DIR:-$HOME/.litert-lm}"
|
|
340
|
+
MODEL_MARKER="$LITERT_ROOT/models/gemma-4-E2B-it.litertlm/trillboards-bundled-version.json"
|
|
341
|
+
SERVE_PLIST="$HOME/Library/LaunchAgents/com.trillboards.litertlm.plist"
|
|
342
|
+
SETUP="$ROOT/share/ensure-litertlm.sh"
|
|
343
|
+
LOCAL_AI_HEALTHY=0
|
|
344
|
+
if /usr/bin/curl --fail --silent --show-error --max-time 2 \
|
|
345
|
+
http://127.0.0.1:9379/v1/models >/dev/null 2>&1; then
|
|
346
|
+
LOCAL_AI_HEALTHY=1
|
|
347
|
+
fi
|
|
348
|
+
if { [ ! -f "$MODEL_MARKER" ] || [ ! -f "$SERVE_PLIST" ] || [ "$LOCAL_AI_HEALTHY" -ne 1 ]; } && [ -f "$SETUP" ]; then
|
|
349
|
+
nohup /bin/bash "$SETUP" >/dev/null 2>&1 &
|
|
350
|
+
fi
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
exec "$HERE/node" "$ROOT/lib/edge-sdk/dist/cli.js" "$@"
|
|
354
|
+
LAUNCH
|
|
355
|
+
chmod +x "$LAUNCHER"
|
|
356
|
+
|
|
357
|
+
# ─── 7. Stage the pairing helper (GUI + headless) ────────────────────────────
|
|
358
|
+
cp "$PKG_ROOT/deploy/macos/pair.command" "$PAYLOAD/bin/trillboards-pair"
|
|
359
|
+
chmod +x "$PAYLOAD/bin/trillboards-pair"
|
|
360
|
+
|
|
361
|
+
# Stage the LaunchAgent template so the pair helper can read it at pair time.
|
|
362
|
+
mkdir -p "$PAYLOAD/share"
|
|
363
|
+
cp "$PKG_ROOT/deploy/macos/com.trillboards.edge.plist" "$PAYLOAD/share/com.trillboards.edge.plist"
|
|
364
|
+
|
|
365
|
+
# LOCAL-inference (LiteRT-LM) stack: the serve LaunchAgent template + the
|
|
366
|
+
# provisioning script. The package postinstall imports the bundled model and
|
|
367
|
+
# verifies the private service before it opens camera pairing.
|
|
368
|
+
cp "$PKG_ROOT/deploy/macos/com.trillboards.litertlm.plist" "$PAYLOAD/share/com.trillboards.litertlm.plist"
|
|
369
|
+
cp "$PKG_ROOT/deploy/macos/scripts/setup-litertlm.sh" "$PAYLOAD/share/setup-litertlm.sh"
|
|
370
|
+
cp "$PKG_ROOT/deploy/macos/scripts/ensure-litertlm.sh" "$PAYLOAD/share/ensure-litertlm.sh"
|
|
371
|
+
chmod +x "$PAYLOAD/share/setup-litertlm.sh" "$PAYLOAD/share/ensure-litertlm.sh"
|
|
372
|
+
|
|
373
|
+
# ─── 7b. Stage the pre-bundled Gemma model (.litertlm) into the payload ───────
|
|
374
|
+
# Stage the redistributable (Apache-2.0) Gemma 4 E2B LiteRT-LM model and verify
|
|
375
|
+
# its SHA-256 exactly like the pinned node/ffmpeg. Missing or corrupt model data
|
|
376
|
+
# is a release-blocking build failure, never a reason to ship a deferred download.
|
|
377
|
+
if [ "$BUNDLE_MODEL" -eq 1 ]; then
|
|
378
|
+
mkdir -p "$PAYLOAD/share/models"
|
|
379
|
+
GEMMA_OUT="$PAYLOAD/share/models/$GEMMA_MODEL_ID"
|
|
380
|
+
if [ -n "$GEMMA_MODEL_FILE" ]; then
|
|
381
|
+
[ -f "$GEMMA_MODEL_FILE" ] || die "GEMMA_MODEL_FILE does not exist: $GEMMA_MODEL_FILE"
|
|
382
|
+
log "Staging bundled Gemma model from local cache: $GEMMA_MODEL_FILE"
|
|
383
|
+
cp "$GEMMA_MODEL_FILE" "$GEMMA_OUT"
|
|
384
|
+
else
|
|
385
|
+
log "Downloading bundled Gemma model (~2.4 GB): $GEMMA_MODEL_URL"
|
|
386
|
+
curl -fL --retry 3 --max-time 1800 -o "$GEMMA_OUT" "$GEMMA_MODEL_URL" \
|
|
387
|
+
|| die "Gemma model download failed. Refusing to build a customer installer without local AI."
|
|
388
|
+
fi
|
|
389
|
+
ACTUAL_SHA="$(shasum -a 256 "$GEMMA_OUT" 2>/dev/null | awk '{print $1}')"
|
|
390
|
+
if [ -z "$ACTUAL_SHA" ] || [ "$ACTUAL_SHA" != "$GEMMA_MODEL_SHA256" ]; then
|
|
391
|
+
die "Gemma model SHA-256 mismatch (expected $GEMMA_MODEL_SHA256, got ${ACTUAL_SHA:-missing}). Refusing to bundle an unverified model."
|
|
392
|
+
fi
|
|
393
|
+
printf '%s %s\n' "$GEMMA_MODEL_SHA256" "$GEMMA_MODEL_ID" > "$PAYLOAD/share/models/$GEMMA_MODEL_ID.sha256"
|
|
394
|
+
log "Staged + SHA-verified bundled Gemma model ($(du -h "$GEMMA_OUT" | cut -f1))"
|
|
395
|
+
else
|
|
396
|
+
warn "Building an engineering-only slim package without local AI. Do not publish this artifact to customers."
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
# ─── 7c. Build the native customer setup application ─────────────────────────
|
|
400
|
+
# AppKit keeps the installer small and gives customers a real application window
|
|
401
|
+
# while the existing, proven setup engine runs invisibly in the background.
|
|
402
|
+
SETUP_APP_MACOS="$SETUP_APP/Contents/MacOS"
|
|
403
|
+
SETUP_APP_BIN="$SETUP_APP_MACOS/Trillboards CCTV Setup"
|
|
404
|
+
mkdir -p "$SETUP_APP_MACOS"
|
|
405
|
+
log "Building native Trillboards CCTV Setup.app…"
|
|
406
|
+
if [ "$UNIVERSAL" -eq 1 ]; then
|
|
407
|
+
clang -fobjc-arc -Wall -Wextra -Werror -O2 -mmacosx-version-min=12.0 -arch arm64 -arch x86_64 -framework Cocoa \
|
|
408
|
+
"$PKG_ROOT/deploy/macos/TrillboardsSetupApp.m" -o "$SETUP_APP_BIN" \
|
|
409
|
+
|| die "native setup app universal build failed"
|
|
410
|
+
else
|
|
411
|
+
case "$NODE_ARCH" in
|
|
412
|
+
arm64) SETUP_ARCH="arm64";;
|
|
413
|
+
x64) SETUP_ARCH="x86_64";;
|
|
414
|
+
esac
|
|
415
|
+
clang -fobjc-arc -Wall -Wextra -Werror -O2 -mmacosx-version-min=12.0 -arch "$SETUP_ARCH" -framework Cocoa \
|
|
416
|
+
"$PKG_ROOT/deploy/macos/TrillboardsSetupApp.m" -o "$SETUP_APP_BIN" \
|
|
417
|
+
|| die "native setup app build failed"
|
|
418
|
+
fi
|
|
419
|
+
chmod +x "$SETUP_APP_BIN"
|
|
420
|
+
"$SETUP_APP_BIN" --smoke-test-cancel \
|
|
421
|
+
|| die "native setup app cancellation smoke test failed"
|
|
422
|
+
INSTALLER_TMP="$STAGE/PKInstallSandbox.trillboards-test/tmp"
|
|
423
|
+
mkdir -p "$INSTALLER_TMP"
|
|
424
|
+
TMPDIR="$INSTALLER_TMP" "$SETUP_APP_BIN" --smoke-test-tempdir \
|
|
425
|
+
|| die "native setup app stale-TMPDIR smoke test failed"
|
|
426
|
+
rm -rf "$STAGE/PKInstallSandbox.trillboards-test"
|
|
427
|
+
cat > "$SETUP_APP/Contents/Info.plist" <<PLIST
|
|
428
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
429
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
430
|
+
<plist version="1.0">
|
|
431
|
+
<dict>
|
|
432
|
+
<key>CFBundleDevelopmentRegion</key><string>en</string>
|
|
433
|
+
<key>CFBundleDisplayName</key><string>Trillboards CCTV Setup</string>
|
|
434
|
+
<key>CFBundleExecutable</key><string>Trillboards CCTV Setup</string>
|
|
435
|
+
<key>CFBundleIdentifier</key><string>com.trillboards.cctv-setup</string>
|
|
436
|
+
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
|
437
|
+
<key>CFBundleName</key><string>Trillboards CCTV Setup</string>
|
|
438
|
+
<key>CFBundlePackageType</key><string>APPL</string>
|
|
439
|
+
<key>CFBundleShortVersionString</key><string>$VERSION</string>
|
|
440
|
+
<key>CFBundleVersion</key><string>$VERSION</string>
|
|
441
|
+
<key>LSMinimumSystemVersion</key><string>12.0</string>
|
|
442
|
+
<key>NSHighResolutionCapable</key><true/>
|
|
443
|
+
<key>NSLocalNetworkUsageDescription</key><string>Trillboards uses your local network to find your camera recorder and validate its camera feeds.</string>
|
|
444
|
+
</dict>
|
|
445
|
+
</plist>
|
|
446
|
+
PLIST
|
|
447
|
+
plutil -lint "$SETUP_APP/Contents/Info.plist" >/dev/null || die "native setup app Info.plist is invalid"
|
|
448
|
+
file "$SETUP_APP_BIN" | grep -q 'Mach-O' || die "native setup app executable is not Mach-O"
|
|
449
|
+
log "Native setup app staged: $SETUP_APP"
|
|
450
|
+
|
|
451
|
+
# ─── 8. Smoke-test the staged bundle BEFORE packaging ────────────────────────
|
|
452
|
+
log "Smoke-testing the staged bundle (version)…"
|
|
453
|
+
STAGED_VER="$("$LAUNCHER" version 2>&1 || true)"
|
|
454
|
+
echo "$STAGED_VER" | grep -q "edge-sdk v" || die "staged launcher failed: $STAGED_VER"
|
|
455
|
+
log "Staged launcher OK: $STAGED_VER"
|
|
456
|
+
|
|
457
|
+
# ─── 8.5 Codesign embedded Mach-O (Developer ID Application) ──────────────────
|
|
458
|
+
# The notary service rejects a .pkg unless EVERY embedded Mach-O (node, ffmpeg,
|
|
459
|
+
# any native .node addon) carries a Developer ID Application signature WITH the
|
|
460
|
+
# hardened runtime + a secure timestamp. The "Developer ID Installer" cert used
|
|
461
|
+
# in step 10 can only productsign the .pkg wrapper — it cannot codesign a Mach-O.
|
|
462
|
+
# Node's V8 JIT additionally needs allow-jit + allow-unsigned-executable-memory,
|
|
463
|
+
# and the connector loads our own (differently-signed/unsigned) .js + addons, so
|
|
464
|
+
# disable-library-validation is required too — without these the hardened-runtime
|
|
465
|
+
# node aborts at launch. Sign deepest-first so nested Mach-O are sealed before
|
|
466
|
+
# their containers.
|
|
467
|
+
if [ -n "$APP_IDENTITY" ]; then
|
|
468
|
+
log "Codesigning embedded Mach-O with '$APP_IDENTITY' (hardened runtime + timestamp)…"
|
|
469
|
+
ENT="$SCRATCH/hardened-entitlements.plist"
|
|
470
|
+
cat > "$ENT" <<'PLIST'
|
|
471
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
472
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
473
|
+
<plist version="1.0">
|
|
474
|
+
<dict>
|
|
475
|
+
<key>com.apple.security.cs.allow-jit</key><true/>
|
|
476
|
+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
|
|
477
|
+
<key>com.apple.security.cs.disable-library-validation</key><true/>
|
|
478
|
+
<key>com.apple.security.cs.allow-dyld-environment-variables</key><true/>
|
|
479
|
+
</dict>
|
|
480
|
+
</plist>
|
|
481
|
+
PLIST
|
|
482
|
+
signed_count=0
|
|
483
|
+
while IFS= read -r f; do
|
|
484
|
+
if file "$f" 2>/dev/null | grep -q "Mach-O"; then
|
|
485
|
+
codesign --force --options runtime --timestamp \
|
|
486
|
+
--entitlements "$ENT" --sign "$APP_IDENTITY" "$f" >/dev/null 2>&1 \
|
|
487
|
+
|| die "codesign failed for $f"
|
|
488
|
+
signed_count=$((signed_count + 1))
|
|
489
|
+
fi
|
|
490
|
+
done < <(find "$PAYLOAD" -type f | sort -r)
|
|
491
|
+
codesign --force --options runtime --timestamp --sign "$APP_IDENTITY" "$SETUP_APP" >/dev/null 2>&1 \
|
|
492
|
+
|| die "codesign failed for native setup app bundle"
|
|
493
|
+
log "Codesigned $signed_count Mach-O binar(ies)."
|
|
494
|
+
codesign --verify --strict "$NODE_BIN" || die "codesign verify failed: node"
|
|
495
|
+
codesign --verify --strict "$FFMPEG_BIN" || die "codesign verify failed: ffmpeg"
|
|
496
|
+
codesign --verify --strict --deep "$SETUP_APP" || die "codesign verify failed: native setup app"
|
|
497
|
+
log "Verified node + ffmpeg signatures (Developer ID Application, hardened runtime)."
|
|
498
|
+
elif [ -n "$SIGN_IDENTITY" ]; then
|
|
499
|
+
warn "--sign-installer was passed WITHOUT --sign-app: the embedded node/ffmpeg"
|
|
500
|
+
warn "stay unsigned + lack the hardened runtime, so notarization WILL FAIL."
|
|
501
|
+
warn "Pass --sign-app \"Developer ID Application: …\" too for a notarizable .pkg."
|
|
502
|
+
fi
|
|
503
|
+
|
|
504
|
+
# ─── 9. pkgbuild (component) + productbuild (home-domain distribution) ────────
|
|
505
|
+
# The component pkg captures the payload tree rooted at the user home; the
|
|
506
|
+
# distribution XML sets enable_currentUserHome so `installer -target
|
|
507
|
+
# CurrentUserHomeDirectory` installs without root.
|
|
508
|
+
log "pkgbuild component…"
|
|
509
|
+
pkgbuild \
|
|
510
|
+
--root "$STAGE" \
|
|
511
|
+
--identifier "$BUNDLE_ID" \
|
|
512
|
+
--version "$VERSION" \
|
|
513
|
+
--install-location "/" \
|
|
514
|
+
--scripts "$PKG_ROOT/deploy/macos/scripts" \
|
|
515
|
+
"$COMPONENT_PKG" >/dev/null
|
|
516
|
+
|
|
517
|
+
# productbuild hostArchitectures uses the "x86_64"/"arm64" tokens (NOT "x64").
|
|
518
|
+
case "$ARCH_LABEL" in
|
|
519
|
+
universal) HOST_ARCHS="x86_64,arm64";;
|
|
520
|
+
arm64) HOST_ARCHS="arm64";;
|
|
521
|
+
x64) HOST_ARCHS="x86_64";;
|
|
522
|
+
*) HOST_ARCHS="$NODE_ARCH";;
|
|
523
|
+
esac
|
|
524
|
+
|
|
525
|
+
DIST_XML="$SCRATCH/distribution.xml"
|
|
526
|
+
cat > "$DIST_XML" <<XML
|
|
527
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
528
|
+
<installer-gui-script minSpecVersion="2">
|
|
529
|
+
<title>Trillboards Edge — CCTV Connector</title>
|
|
530
|
+
<organization>com.trillboards</organization>
|
|
531
|
+
<!-- Home-domain install: no root, no admin password. -->
|
|
532
|
+
<domains enable_anywhere="false" enable_currentUserHome="true" enable_localSystem="false"/>
|
|
533
|
+
<options customize="never" require-scripts="false" hostArchitectures="$HOST_ARCHS"/>
|
|
534
|
+
<volume-check>
|
|
535
|
+
<allowed-os-versions>
|
|
536
|
+
<os-version min="12.0"/>
|
|
537
|
+
</allowed-os-versions>
|
|
538
|
+
</volume-check>
|
|
539
|
+
<choices-outline>
|
|
540
|
+
<line choice="default">
|
|
541
|
+
<line choice="$BUNDLE_ID"/>
|
|
542
|
+
</line>
|
|
543
|
+
</choices-outline>
|
|
544
|
+
<choice id="default"/>
|
|
545
|
+
<choice id="$BUNDLE_ID" visible="false">
|
|
546
|
+
<pkg-ref id="$BUNDLE_ID"/>
|
|
547
|
+
</choice>
|
|
548
|
+
<pkg-ref id="$BUNDLE_ID" version="$VERSION" onConclusion="none">$(basename "$COMPONENT_PKG")</pkg-ref>
|
|
549
|
+
</installer-gui-script>
|
|
550
|
+
XML
|
|
551
|
+
|
|
552
|
+
log "productbuild distribution…"
|
|
553
|
+
productbuild \
|
|
554
|
+
--distribution "$DIST_XML" \
|
|
555
|
+
--package-path "$SCRATCH" \
|
|
556
|
+
"$PKG_OUT" >/dev/null
|
|
557
|
+
|
|
558
|
+
# ─── 10. Optional sign ───────────────────────────────────────────────────────
|
|
559
|
+
if [ -n "$SIGN_IDENTITY" ]; then
|
|
560
|
+
log "productsign with '$SIGN_IDENTITY'…"
|
|
561
|
+
SIGNED="$DIST_DIR/${APP_NAME}-${VERSION}-${ARCH}-signed.pkg"
|
|
562
|
+
productsign --sign "$SIGN_IDENTITY" "$PKG_OUT" "$SIGNED"
|
|
563
|
+
mv "$SIGNED" "$PKG_OUT"
|
|
564
|
+
log "Signed: $PKG_OUT"
|
|
565
|
+
echo ""
|
|
566
|
+
echo "NEXT: notarize for Gatekeeper-clean distribution:"
|
|
567
|
+
echo " xcrun notarytool submit '$PKG_OUT' --keychain-profile <profile> --wait"
|
|
568
|
+
echo " xcrun stapler staple '$PKG_OUT'"
|
|
569
|
+
else
|
|
570
|
+
warn "UNSIGNED build (no Developer ID Installer cert / --sign-installer)."
|
|
571
|
+
warn "Fine for local install via 'installer -pkg … -target CurrentUserHomeDirectory'."
|
|
572
|
+
warn "For public distribution, build on a machine with a 'Developer ID Installer'"
|
|
573
|
+
warn "cert (pass --sign-installer) then notarize (see deploy/macos/README.md)."
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
# Keep deliberately slim engineering builds bounded so an unrelated payload
|
|
577
|
+
# regression cannot masquerade as the bundled customer package.
|
|
578
|
+
PKG_BYTES="$(stat -f%z "$PKG_OUT")"
|
|
579
|
+
MAX_SLIM_PKG_BYTES="${MAX_SLIM_PKG_BYTES:-262144000}"
|
|
580
|
+
if [ "$BUNDLE_MODEL" -eq 0 ] && [ "$PKG_BYTES" -gt "$MAX_SLIM_PKG_BYTES" ]; then
|
|
581
|
+
die "slim engineering installer is $PKG_BYTES bytes; maximum is $MAX_SLIM_PKG_BYTES bytes"
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
# ─── Done ────────────────────────────────────────────────────────────────────
|
|
585
|
+
PKG_SIZE="$(du -h "$PKG_OUT" | cut -f1)"
|
|
586
|
+
echo ""
|
|
587
|
+
log "Built: $PKG_OUT ($PKG_SIZE / $PKG_BYTES bytes)"
|
|
588
|
+
echo "$PKG_OUT"
|
|
@@ -0,0 +1,62 @@
|
|
|
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 Trillboards local-first CCTV connector.
|
|
5
|
+
|
|
6
|
+
This is a TEMPLATE: the `pair` helper (pair.command) substitutes the
|
|
7
|
+
__LAUNCHER__ / __CONFIG__ / __LOG_DIR__ tokens with this machine's real
|
|
8
|
+
absolute paths and writes the result to
|
|
9
|
+
~/Library/LaunchAgents/com.trillboards.edge.plist, then loads it via
|
|
10
|
+
`launchctl bootstrap gui/$UID`.
|
|
11
|
+
|
|
12
|
+
It is intentionally NOT loaded by the installer's postinstall: a connector
|
|
13
|
+
with no paired config would just crash-loop (the chosen sense run mode exits
|
|
14
|
+
non-zero when the config is missing), so the agent is bootstrapped only AFTER a successful
|
|
15
|
+
pair has written the config. KeepAlive is therefore scoped to
|
|
16
|
+
SuccessfulExit=false so a clean exit (e.g. operator stop) is not relaunched,
|
|
17
|
+
but a crash is.
|
|
18
|
+
-->
|
|
19
|
+
<plist version="1.0">
|
|
20
|
+
<dict>
|
|
21
|
+
<key>Label</key>
|
|
22
|
+
<string>com.trillboards.edge</string>
|
|
23
|
+
|
|
24
|
+
<key>ProgramArguments</key>
|
|
25
|
+
<array>
|
|
26
|
+
<string>__LAUNCHER__</string>
|
|
27
|
+
<!-- auto-sense: use the installer-provisioned on-device LiteRT-LM (Gemma).
|
|
28
|
+
Cloud inference requires opt-in config. -->
|
|
29
|
+
<string>auto-sense</string>
|
|
30
|
+
<string>--config</string>
|
|
31
|
+
<string>__CONFIG__</string>
|
|
32
|
+
</array>
|
|
33
|
+
|
|
34
|
+
<!-- Start at login and keep running. Only relaunch on a crash, not on a
|
|
35
|
+
clean operator-initiated stop. -->
|
|
36
|
+
<key>RunAtLoad</key>
|
|
37
|
+
<true/>
|
|
38
|
+
<key>KeepAlive</key>
|
|
39
|
+
<dict>
|
|
40
|
+
<key>SuccessfulExit</key>
|
|
41
|
+
<false/>
|
|
42
|
+
</dict>
|
|
43
|
+
|
|
44
|
+
<!-- Throttle relaunches so a misconfigured feed can't hot-loop. -->
|
|
45
|
+
<key>ThrottleInterval</key>
|
|
46
|
+
<integer>10</integer>
|
|
47
|
+
|
|
48
|
+
<key>ProcessType</key>
|
|
49
|
+
<string>Background</string>
|
|
50
|
+
|
|
51
|
+
<key>StandardOutPath</key>
|
|
52
|
+
<string>__LOG_DIR__/edge.out.log</string>
|
|
53
|
+
<key>StandardErrorPath</key>
|
|
54
|
+
<string>__LOG_DIR__/edge.err.log</string>
|
|
55
|
+
|
|
56
|
+
<key>EnvironmentVariables</key>
|
|
57
|
+
<dict>
|
|
58
|
+
<key>NODE_ENV</key>
|
|
59
|
+
<string>production</string>
|
|
60
|
+
</dict>
|
|
61
|
+
</dict>
|
|
62
|
+
</plist>
|