@trillboards/edge-sdk 0.2.24 → 0.2.25
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/deploy/windows/build-windows.ps1 +1 -0
- package/deploy/windows/enroll-connector.ps1 +169 -0
- package/deploy/windows/installer.nsi +102 -6
- package/deploy/windows/run-connector.ps1 +30 -0
- package/dist/ConnectorCameraClient.d.ts +79 -0
- package/dist/ConnectorCameraClient.d.ts.map +1 -0
- package/dist/ConnectorCameraClient.js +114 -0
- package/dist/ConnectorCameraClient.js.map +1 -0
- package/dist/EdgeAgent.js +1 -1
- package/dist/EnrollClient.d.ts +81 -0
- package/dist/EnrollClient.d.ts.map +1 -0
- package/dist/EnrollClient.js +97 -0
- package/dist/EnrollClient.js.map +1 -0
- package/dist/cli.d.ts +49 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +332 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/package.json +1 -1
|
@@ -202,6 +202,7 @@ Copy-Item -Force "$Here\trillboards-hidden-run.vbs" "$Payload\bin\trillboards-hi
|
|
|
202
202
|
Copy-Item -Force "$Here\ensure-sensing.ps1" "$Payload\bin\ensure-sensing.ps1"
|
|
203
203
|
Copy-Item -Force "$Here\run-connector.ps1" "$Payload\bin\run-connector.ps1"
|
|
204
204
|
Copy-Item -Force "$Here\register-task.ps1" "$Payload\bin\register-task.ps1"
|
|
205
|
+
Copy-Item -Force "$Here\enroll-connector.ps1" "$Payload\bin\enroll-connector.ps1"
|
|
205
206
|
Copy-Item -Force "$Here\stop-existing.ps1" "$Payload\bin\stop-existing.ps1"
|
|
206
207
|
Copy-Item -Force "$Here\install-update.ps1" "$Payload\bin\install-update.ps1"
|
|
207
208
|
# The in-field updater. Ships in bin\ so an INSTALLED connector can drive its own
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<#
|
|
2
|
+
enroll-connector.ps1 - UNATTENDED provisioning step for an MDM/Intune/SCCM
|
|
3
|
+
deployment. Invoked by installer.nsi when /ENROLLMENT_TOKEN= was supplied.
|
|
4
|
+
|
|
5
|
+
WHAT IT DOES
|
|
6
|
+
1. Writes the fleet enrollment credential to $InstallDir\trillboards.enrollment
|
|
7
|
+
with an ACL that grants the installing user ONLY (inheritance disabled),
|
|
8
|
+
because a plain %LOCALAPPDATA% file inherits whatever the parent grants
|
|
9
|
+
and this is the one credential in the package that can enroll machines.
|
|
10
|
+
2. Runs `trillboards-edge enroll`, which exchanges it for THIS machine's own
|
|
11
|
+
credential, writes that into trillboards.config.json, and DELETES the
|
|
12
|
+
enrollment file.
|
|
13
|
+
3. Deletes the file itself if the CLI could not (belt and braces - a fleet
|
|
14
|
+
credential left on 200 store PCs is the failure this whole design exists
|
|
15
|
+
to prevent).
|
|
16
|
+
|
|
17
|
+
EXIT CODES - these are the installer's, and they are deliberately NOT fatal to
|
|
18
|
+
the install:
|
|
19
|
+
0 enrolled, or already enrolled (a re-push is idempotent and free)
|
|
20
|
+
1 the credential was rejected: expired, revoked, or every enrollment it was
|
|
21
|
+
issued for is already used. A human must issue a new one.
|
|
22
|
+
6 network/server failure. The credential was NOT spent; the scheduled task
|
|
23
|
+
retries on the next logon.
|
|
24
|
+
|
|
25
|
+
A failed enrollment leaves a correctly INSTALLED connector that is simply not
|
|
26
|
+
yet provisioned, which is recoverable by re-running this script or by the
|
|
27
|
+
ordinary interactive setup. Aborting the whole install instead would leave the
|
|
28
|
+
machine with nothing and no way to retry.
|
|
29
|
+
#>
|
|
30
|
+
[CmdletBinding()]
|
|
31
|
+
param(
|
|
32
|
+
[Parameter(Mandatory = $true)][string]$InstallDir,
|
|
33
|
+
[Parameter(Mandatory = $true)][string]$EnrollmentToken,
|
|
34
|
+
[string]$SiteLabel = "",
|
|
35
|
+
[string]$ApiBase = "",
|
|
36
|
+
# Optional recorder credentials. Passed straight through to the CLI and never
|
|
37
|
+
# written to disk or to the log - only their PRESENCE is logged.
|
|
38
|
+
[string]$NvrUser = "",
|
|
39
|
+
[string]$NvrPass = ""
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
$ErrorActionPreference = "Stop"
|
|
43
|
+
|
|
44
|
+
$logDir = Join-Path $env:LOCALAPPDATA "Trillboards\logs"
|
|
45
|
+
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
|
|
46
|
+
$logPath = Join-Path $logDir "enroll.log"
|
|
47
|
+
|
|
48
|
+
function Write-Log([string]$Message) {
|
|
49
|
+
# NEVER log the token. Only its shape, so support can match it to a portal row
|
|
50
|
+
# without the log becoming a credential store.
|
|
51
|
+
$stamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
52
|
+
Add-Content -Path $logPath -Value "[$stamp] $Message"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
$enrollmentFile = Join-Path $InstallDir "trillboards.enrollment"
|
|
56
|
+
$configPath = Join-Path $InstallDir "trillboards.config.json"
|
|
57
|
+
|
|
58
|
+
function Remove-EnrollmentFile {
|
|
59
|
+
if (Test-Path $enrollmentFile) {
|
|
60
|
+
try {
|
|
61
|
+
Remove-Item -Force $enrollmentFile
|
|
62
|
+
Write-Log "enrollment file removed"
|
|
63
|
+
} catch {
|
|
64
|
+
Write-Log "WARNING: could not remove enrollment file: $($_.Exception.Message)"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
if ([string]::IsNullOrWhiteSpace($EnrollmentToken)) {
|
|
71
|
+
Write-Log "no enrollment token supplied; nothing to do"
|
|
72
|
+
exit 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
$tokenShape = if ($EnrollmentToken.Length -gt 11) {
|
|
76
|
+
"$($EnrollmentToken.Substring(0,7))...$($EnrollmentToken.Substring($EnrollmentToken.Length-4))"
|
|
77
|
+
} else { "(short)" }
|
|
78
|
+
$credState = if ([string]::IsNullOrWhiteSpace($NvrUser)) { "absent" } else { "supplied" }
|
|
79
|
+
Write-Log "starting unattended enrollment (credential $tokenShape, site '$SiteLabel', recorder credentials $credState)"
|
|
80
|
+
|
|
81
|
+
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
|
82
|
+
# -NoNewline so the CLI's trim() sees exactly the token; ASCII because the key
|
|
83
|
+
# alphabet is hex and a BOM would corrupt the first character.
|
|
84
|
+
[System.IO.File]::WriteAllText($enrollmentFile, $EnrollmentToken, [System.Text.UTF8Encoding]::new($false))
|
|
85
|
+
|
|
86
|
+
# Owner-only ACL with inheritance broken. %LOCALAPPDATA% is already per-user,
|
|
87
|
+
# but this file is the one credential that can enroll further machines, so it
|
|
88
|
+
# does not ride on a directory default that a group policy may widen.
|
|
89
|
+
try {
|
|
90
|
+
$acl = Get-Acl $enrollmentFile
|
|
91
|
+
$acl.SetAccessRuleProtection($true, $false) # break inheritance, drop inherited ACEs
|
|
92
|
+
$me = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
93
|
+
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
94
|
+
$me, "FullControl", "None", "None", "Allow")
|
|
95
|
+
$acl.SetAccessRule($rule)
|
|
96
|
+
Set-Acl -Path $enrollmentFile -AclObject $acl
|
|
97
|
+
Write-Log "enrollment file ACL set to owner-only"
|
|
98
|
+
} catch {
|
|
99
|
+
# Non-fatal: the file is deleted seconds from now. Losing the ACL is worth a
|
|
100
|
+
# log line, not a failed rollout.
|
|
101
|
+
Write-Log "WARNING: could not tighten enrollment file ACL: $($_.Exception.Message)"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
$node = Join-Path $InstallDir "bin\node.exe"
|
|
105
|
+
$cli = Join-Path $InstallDir "lib\edge-sdk\dist\cli.js"
|
|
106
|
+
if (-not (Test-Path $node) -or -not (Test-Path $cli)) {
|
|
107
|
+
# Retryable and the credential is UNSPENT - keep it so the next-logon retry
|
|
108
|
+
# can use it once the install finishes laying the tree down.
|
|
109
|
+
Write-Log "ERROR: connector is not installed at $InstallDir (retryable, credential preserved)"
|
|
110
|
+
exit 6
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
$cliArgs = @($cli, "enroll", "--config", $configPath, "--enrollment-file", $enrollmentFile)
|
|
114
|
+
if (-not [string]::IsNullOrWhiteSpace($SiteLabel)) { $cliArgs += @("--site", $SiteLabel) }
|
|
115
|
+
if (-not [string]::IsNullOrWhiteSpace($ApiBase)) { $cliArgs += @("--api-base", $ApiBase) }
|
|
116
|
+
if (-not [string]::IsNullOrWhiteSpace($NvrUser)) { $cliArgs += @("--nvr-user", $NvrUser) }
|
|
117
|
+
if (-not [string]::IsNullOrWhiteSpace($NvrPass)) { $cliArgs += @("--nvr-pass", $NvrPass) }
|
|
118
|
+
|
|
119
|
+
$oldPath = $env:PATH
|
|
120
|
+
$oldNodePath = $env:NODE_PATH
|
|
121
|
+
try {
|
|
122
|
+
$env:PATH = "$(Join-Path $InstallDir 'bin');$oldPath"
|
|
123
|
+
$env:NODE_PATH = Join-Path $InstallDir "lib\node_modules"
|
|
124
|
+
$output = (& $node @cliArgs 2>&1 | Out-String)
|
|
125
|
+
$code = $LASTEXITCODE
|
|
126
|
+
} finally {
|
|
127
|
+
$env:PATH = $oldPath
|
|
128
|
+
$env:NODE_PATH = $oldNodePath
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# The CLI never prints the credential (see maskConnectorKey), so its output is
|
|
132
|
+
# safe to log verbatim and is the only diagnostic a field failure leaves.
|
|
133
|
+
Write-Log "enroll exit=$code output=$($output.Trim())"
|
|
134
|
+
|
|
135
|
+
# DELETE ONLY ON A TERMINAL OUTCOME.
|
|
136
|
+
#
|
|
137
|
+
# The CLI already deletes the file itself on success. The dangerous case is a
|
|
138
|
+
# RETRYABLE failure: an unconditional delete here would destroy the only
|
|
139
|
+
# persisted copy of the fleet credential on a transient outage during install,
|
|
140
|
+
# leaving that store permanently unprovisioned with nothing to retry from - the
|
|
141
|
+
# opposite of what the exit code says. So the file survives exit 6 and the
|
|
142
|
+
# next-logon retry in run-connector.ps1 picks it up.
|
|
143
|
+
#
|
|
144
|
+
# It IS deleted on a credential REJECTION (exit 1), because that token can
|
|
145
|
+
# never enroll anything again and leaving a dead credential on disk is pure
|
|
146
|
+
# exposure.
|
|
147
|
+
switch ($code) {
|
|
148
|
+
0 { Remove-EnrollmentFile; Write-Log "enrollment complete, cameras registered"; exit 0 }
|
|
149
|
+
5 {
|
|
150
|
+
# Provisioned, but no feeds. The credential is SPENT, so the enrollment
|
|
151
|
+
# file is done - what is missing is recorder credentials, not a retry.
|
|
152
|
+
Remove-EnrollmentFile
|
|
153
|
+
Write-Log "enrolled, but NO camera feeds registered (recorder credentials absent or no recorder found)"
|
|
154
|
+
exit 5
|
|
155
|
+
}
|
|
156
|
+
1 { Remove-EnrollmentFile; Write-Log "enrollment credential REJECTED - a new one must be issued"; exit 1 }
|
|
157
|
+
4 { Remove-EnrollmentFile; Write-Log "no enrollment token was readable"; exit 1 }
|
|
158
|
+
default {
|
|
159
|
+
Write-Log "enrollment deferred (retryable) - credential PRESERVED for the next-logon retry"
|
|
160
|
+
exit 6
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
# An exception here is a local/environmental failure, not a rejected
|
|
165
|
+
# credential, so it is retryable and the token is preserved for the same
|
|
166
|
+
# reason as exit 6 above.
|
|
167
|
+
Write-Log "ERROR (retryable, credential preserved): $($_.Exception.Message)"
|
|
168
|
+
exit 6
|
|
169
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
; installer.nsi
|
|
1
|
+
; installer.nsi - Trillboards Edge on-device (local-sense) CCTV connector (Windows).
|
|
2
2
|
; Per-user, no-admin install (RequestExecutionLevel user) that mirrors the
|
|
3
3
|
; home-domain macOS .pkg: lays down the self-contained tree under
|
|
4
4
|
; %LOCALAPPDATA%\Trillboards\edge, registers a per-user Scheduled Task that
|
|
@@ -26,6 +26,16 @@
|
|
|
26
26
|
!define LITERT_TASK_NAME "TrillboardsLiteRtLm"
|
|
27
27
|
|
|
28
28
|
Var ModelSource
|
|
29
|
+
; -- Unattended (MDM/Intune/SCCM) parameters --------------------------------
|
|
30
|
+
; Before these, the installer parsed exactly ONE switch (/MODEL_SOURCE=) and a
|
|
31
|
+
; perfect silent install still left the machine UNPAIRED, because deviceToken is
|
|
32
|
+
; per-camera-feed and the only writers were the interactive pair/setup paths. A
|
|
33
|
+
; silent install that produces a non-working install is not a deployment story.
|
|
34
|
+
Var EnrollmentToken
|
|
35
|
+
Var SiteLabel
|
|
36
|
+
Var NvrUser
|
|
37
|
+
Var NvrPass
|
|
38
|
+
Var IsUnattended
|
|
29
39
|
|
|
30
40
|
Name "${APP_NAME}"
|
|
31
41
|
OutFile "${OUT_FILE}"
|
|
@@ -47,16 +57,61 @@ BrandingText "Trillboards"
|
|
|
47
57
|
|
|
48
58
|
; Finish page: launch the native Windows Forms setup app through wscript. The
|
|
49
59
|
; setup engine and its cmd wrapper run with CreateNoWindow, so no console flashes.
|
|
50
|
-
|
|
60
|
+
; MUI_FINISHPAGE_RUN_FUNCTION rather than a bare RUN: an admin who passes
|
|
61
|
+
; /ENROLLMENT_TOKEN= without /S still gets the pages, and launching the
|
|
62
|
+
; interactive camera wizard on a machine that just provisioned itself is exactly
|
|
63
|
+
; the human step this feature removes. (Under /S the finish page never renders
|
|
64
|
+
; at all, so this only matters for the visible-but-unattended case.)
|
|
65
|
+
!define MUI_FINISHPAGE_RUN
|
|
66
|
+
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchSetupAppUnlessUnattended
|
|
51
67
|
!define MUI_FINISHPAGE_RUN_TEXT "Set up my CCTV cameras now"
|
|
52
68
|
!define MUI_FINISHPAGE_TEXT "The connector and private local AI are installed. Continue in the Trillboards CCTV Setup app to pair your cameras."
|
|
53
69
|
!insertmacro MUI_PAGE_FINISH
|
|
54
70
|
|
|
71
|
+
Function LaunchSetupAppUnlessUnattended
|
|
72
|
+
${If} $IsUnattended == 1
|
|
73
|
+
Return
|
|
74
|
+
${EndIf}
|
|
75
|
+
Exec '"$WINDIR\System32\wscript.exe" "$INSTDIR\bin\trillboards-setup.vbs"'
|
|
76
|
+
FunctionEnd
|
|
77
|
+
|
|
55
78
|
!insertmacro MUI_UNPAGE_CONFIRM
|
|
56
79
|
!insertmacro MUI_UNPAGE_INSTFILES
|
|
57
80
|
!insertmacro MUI_LANGUAGE "English"
|
|
58
81
|
|
|
59
82
|
Function .onInit
|
|
83
|
+
; -- Unattended parameters ------------------------------------------------
|
|
84
|
+
; /S NSIS silent install (no pages, no prompts)
|
|
85
|
+
; /ENROLLMENT_TOKEN=<tok> the FLEET enrollment credential from the portal
|
|
86
|
+
; /SITE="Store 0417" free-text install label carried into the rollout
|
|
87
|
+
; view and prefixed onto camera names
|
|
88
|
+
;
|
|
89
|
+
; The token is written to $INSTDIR\trillboards.enrollment and the connector
|
|
90
|
+
; DELETES that file the moment it has exchanged it for this machine's own
|
|
91
|
+
; credential - a bootstrap credential must not outlive its bootstrap, or every
|
|
92
|
+
; store PC keeps something that can enroll more machines.
|
|
93
|
+
${GetParameters} $R0
|
|
94
|
+
${GetOptions} $R0 "/ENROLLMENT_TOKEN=" $EnrollmentToken
|
|
95
|
+
${GetOptions} $R0 "/SITE=" $SiteLabel
|
|
96
|
+
; Recorder credentials are OPTIONAL and the deployment is useful without them:
|
|
97
|
+
; with none, the machine still provisions its own identity and simply registers
|
|
98
|
+
; no feeds. With them the install is genuinely zero-touch, because the channels
|
|
99
|
+
; on the cheap DVRs this product targets cannot be opened otherwise - and
|
|
100
|
+
; registering a channel we cannot open would mint a screen that is permanently
|
|
101
|
+
; dark. Whether the recorder password belongs in an MDM package is the
|
|
102
|
+
; customer's call, which is why this is a switch and not a requirement.
|
|
103
|
+
${GetOptions} $R0 "/NVR_USER=" $NvrUser
|
|
104
|
+
${GetOptions} $R0 "/NVR_PASS=" $NvrPass
|
|
105
|
+
|
|
106
|
+
; "Unattended" is silent OR token-driven: an admin scripting a visible install
|
|
107
|
+
; still wants no prompts to block the run.
|
|
108
|
+
StrCpy $IsUnattended 0
|
|
109
|
+
${If} ${Silent}
|
|
110
|
+
StrCpy $IsUnattended 1
|
|
111
|
+
${ElseIf} $EnrollmentToken != ""
|
|
112
|
+
StrCpy $IsUnattended 1
|
|
113
|
+
${EndIf}
|
|
114
|
+
|
|
60
115
|
; The SFX launcher passes its absolute extraction path. Keep the environment
|
|
61
116
|
; value as an engineering and recovery fallback for the signed inner package.
|
|
62
117
|
ReadEnvStr $ModelSource "TRILLBOARDS_BUNDLED_MODEL_PATH"
|
|
@@ -70,7 +125,12 @@ Function .onInit
|
|
|
70
125
|
SetErrorLevel 2
|
|
71
126
|
Quit
|
|
72
127
|
${EndIf}
|
|
73
|
-
|
|
128
|
+
; /SD IDOK: under /S a modal has nobody to click it and the install hangs
|
|
129
|
+
; forever. The default answer dismisses the box; the Abort on the next line
|
|
130
|
+
; is what actually decides the outcome, so a silent run FAILS here rather
|
|
131
|
+
; than proceeding without a model.
|
|
132
|
+
MessageBox MB_ICONSTOP|MB_OK "This installer is missing its included local AI model. Download the complete Trillboards CCTV installer and try again." /SD IDOK
|
|
133
|
+
SetErrorLevel 2
|
|
74
134
|
Abort
|
|
75
135
|
${EndIf}
|
|
76
136
|
|
|
@@ -111,7 +171,8 @@ Section "Install"
|
|
|
111
171
|
nsExec::ExecToLog 'powershell -NoProfile -ExecutionPolicy Bypass -File "$PLUGINSDIR\install-update.ps1" -InstallDir "$INSTDIR" -StagingDir "$INSTDIR\.update-staging"'
|
|
112
172
|
Pop $0
|
|
113
173
|
${If} $0 != 0
|
|
114
|
-
MessageBox MB_ICONSTOP|MB_OK "Trillboards CCTV setup could not be updated safely. Your previous connector was restored. Please retry the installer."
|
|
174
|
+
MessageBox MB_ICONSTOP|MB_OK "Trillboards CCTV setup could not be updated safely. Your previous connector was restored. Please retry the installer." /SD IDOK
|
|
175
|
+
SetErrorLevel 3
|
|
115
176
|
Abort
|
|
116
177
|
${EndIf}
|
|
117
178
|
SetOutPath "$INSTDIR"
|
|
@@ -120,18 +181,49 @@ Section "Install"
|
|
|
120
181
|
; is synchronous and required: pairing is never offered while AI is absent or
|
|
121
182
|
; still downloading in the background.
|
|
122
183
|
${IfNot} ${FileExists} "$ModelSource"
|
|
123
|
-
MessageBox MB_ICONSTOP|MB_OK "The local AI model included with this installer could not be found. Download a fresh Trillboards CCTV installer."
|
|
184
|
+
MessageBox MB_ICONSTOP|MB_OK "The local AI model included with this installer could not be found. Download a fresh Trillboards CCTV installer." /SD IDOK
|
|
185
|
+
SetErrorLevel 2
|
|
124
186
|
Abort
|
|
125
187
|
${EndIf}
|
|
126
188
|
DetailPrint "Installing and verifying private local AI..."
|
|
127
189
|
nsExec::ExecToLog '"$WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\bin\setup-litertlm.ps1" -BundledModelPath "$ModelSource" -RequireBundledModel'
|
|
128
190
|
Pop $0
|
|
129
191
|
${If} $0 != 0
|
|
130
|
-
MessageBox MB_ICONSTOP|MB_OK "Private local AI could not be installed. Camera pairing has not started. Download a fresh installer or contact Trillboards support."
|
|
192
|
+
MessageBox MB_ICONSTOP|MB_OK "Private local AI could not be installed. Camera pairing has not started. Download a fresh installer or contact Trillboards support." /SD IDOK
|
|
193
|
+
SetErrorLevel 4
|
|
131
194
|
Abort
|
|
132
195
|
${EndIf}
|
|
133
196
|
DetailPrint "Private local AI is installed and healthy."
|
|
134
197
|
|
|
198
|
+
; -- Unattended provisioning ----------------------------------------------
|
|
199
|
+
; With /ENROLLMENT_TOKEN=, the machine provisions ITSELF here - no code typed
|
|
200
|
+
; at the machine, no browser, no human. enroll-connector.ps1 writes the
|
|
201
|
+
; credential owner-only, exchanges it for this machine's own credential, and
|
|
202
|
+
; deletes it.
|
|
203
|
+
;
|
|
204
|
+
; A failed enrollment does NOT abort the install. The connector is correctly
|
|
205
|
+
; installed either way; what is missing is provisioning, which is retryable
|
|
206
|
+
; (re-run the package, or use the ordinary interactive setup). Aborting would
|
|
207
|
+
; leave the store with nothing and no path forward - strictly worse than an
|
|
208
|
+
; installed-but-unprovisioned connector that a support call can finish.
|
|
209
|
+
${If} $EnrollmentToken != ""
|
|
210
|
+
DetailPrint "Provisioning this connector..."
|
|
211
|
+
nsExec::ExecToLog '"$WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\bin\enroll-connector.ps1" -InstallDir "$INSTDIR" -EnrollmentToken "$EnrollmentToken" -SiteLabel "$SiteLabel" -NvrUser "$NvrUser" -NvrPass "$NvrPass"'
|
|
212
|
+
Pop $0
|
|
213
|
+
${If} $0 == 0
|
|
214
|
+
DetailPrint "Connector provisioned and cameras registered."
|
|
215
|
+
${ElseIf} $0 == 5
|
|
216
|
+
DetailPrint "Connector provisioned, but no camera feeds were registered. Supply /NVR_USER= and /NVR_PASS=, or finish in the portal."
|
|
217
|
+
SetErrorLevel 5
|
|
218
|
+
${ElseIf} $0 == 1
|
|
219
|
+
DetailPrint "Enrollment credential was rejected (expired, revoked, or fully used). Issue a new one in the portal."
|
|
220
|
+
SetErrorLevel 5
|
|
221
|
+
${Else}
|
|
222
|
+
DetailPrint "Enrollment could not reach Trillboards; it will be retried. The credential was not used."
|
|
223
|
+
SetErrorLevel 6
|
|
224
|
+
${EndIf}
|
|
225
|
+
${EndIf}
|
|
226
|
+
|
|
135
227
|
; Start Menu shortcuts.
|
|
136
228
|
CreateDirectory "$SMPROGRAMS\Trillboards"
|
|
137
229
|
CreateShortcut "$SMPROGRAMS\Trillboards\Trillboards CCTV Setup.lnk" "$WINDIR\System32\wscript.exe" '$\"$INSTDIR\bin\trillboards-setup.vbs$\"'
|
|
@@ -154,6 +246,10 @@ Section "Uninstall"
|
|
|
154
246
|
nsExec::ExecToLog 'schtasks /Delete /F /TN "${TASK_NAME}"'
|
|
155
247
|
nsExec::ExecToLog 'schtasks /End /TN "${LITERT_TASK_NAME}"'
|
|
156
248
|
nsExec::ExecToLog 'schtasks /Delete /F /TN "${LITERT_TASK_NAME}"'
|
|
249
|
+
; Explicit, ahead of RMDir /r: a fleet enrollment credential must never
|
|
250
|
+
; survive an uninstall, and relying on the recursive delete makes that a
|
|
251
|
+
; property of statement ORDER rather than of intent.
|
|
252
|
+
Delete "$INSTDIR\trillboards.enrollment"
|
|
157
253
|
Delete "$SMPROGRAMS\Trillboards\Pair a camera.lnk"
|
|
158
254
|
Delete "$SMPROGRAMS\Trillboards\Trillboards CCTV Setup.lnk"
|
|
159
255
|
Delete "$SMPROGRAMS\Trillboards\Uninstall Trillboards Edge.lnk"
|
|
@@ -30,6 +30,36 @@ if ((Test-Path $logPath) -and (Get-Item $logPath).Length -ge $maxLogBytes) {
|
|
|
30
30
|
Move-ConnectorLogs
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Unattended-provisioning retry, at every logon.
|
|
35
|
+
#
|
|
36
|
+
# THE FAILURE THIS EXISTS FOR: enrollment runs once, inside the installer. If the
|
|
37
|
+
# store's link is down at that moment - a DSL blip, a captive portal, a firewall
|
|
38
|
+
# rule not yet pushed - the credential is unspent and the machine is installed
|
|
39
|
+
# but unprovisioned, with nothing scheduled to try again. Across a 200-store push
|
|
40
|
+
# that is a handful of stores permanently dark for a reason nobody can see.
|
|
41
|
+
#
|
|
42
|
+
# The scheduled task already runs this supervisor at every logon, so the retry
|
|
43
|
+
# belongs here rather than in a second task nobody would remember to register.
|
|
44
|
+
#
|
|
45
|
+
# It fires ONLY when the enrollment file still exists, and enroll-connector.ps1
|
|
46
|
+
# leaves it there only for retryable outcomes - a spent or rejected credential is
|
|
47
|
+
# deleted, so this cannot become a loop hammering a dead token.
|
|
48
|
+
$enrollmentFile = Join-Path $InstallDir "trillboards.enrollment"
|
|
49
|
+
$enrollScript = Join-Path $InstallDir "bin\enroll-connector.ps1"
|
|
50
|
+
if ((Test-Path $enrollmentFile) -and (Test-Path $enrollScript)) {
|
|
51
|
+
try {
|
|
52
|
+
$pendingToken = (Get-Content -Raw -Path $enrollmentFile).Trim()
|
|
53
|
+
if ($pendingToken) {
|
|
54
|
+
& $enrollScript -InstallDir $InstallDir -EnrollmentToken $pendingToken | Out-Null
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
# Never let a provisioning retry stop the connector from starting: a machine
|
|
58
|
+
# that is already provisioned and simply has a stale file must still sense.
|
|
59
|
+
# The detail lands in enroll.log, which is where a support call looks.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
33
63
|
$writer = $null
|
|
34
64
|
$process = $null
|
|
35
65
|
try {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type HttpPoster } from './AudienceAnalyzeClient';
|
|
2
|
+
import type { CameraFeedConfig } from './config';
|
|
3
|
+
import type { DiscoveredCamera } from './discovery';
|
|
4
|
+
/**
|
|
5
|
+
* ConnectorCameraClient — registers the feeds this machine found, using THIS
|
|
6
|
+
* machine's own connector credential, and turns the answer into `cameras[]`.
|
|
7
|
+
*
|
|
8
|
+
* This is the consumer of `connector.key` and the producer for
|
|
9
|
+
* `POST /v2/earner/connectors/cameras`. Without it the unattended path stops one
|
|
10
|
+
* step short of useful: the machine has an identity, and no feeds, and no device
|
|
11
|
+
* tokens — so `auto-sense` finds no `cameras[]` and exits through its no-feeds
|
|
12
|
+
* path while the installer reports success. A human would still have to run the
|
|
13
|
+
* interactive camera setup, which is the exact step this whole feature removes.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IT SENDS, AND WHAT IT DOES NOT. The body carries only a stable
|
|
16
|
+
* `cameraKey` per feed plus a display name — never the RTSP URL and never the
|
|
17
|
+
* recorder credentials. Those stay on this machine: the server mints one screen
|
|
18
|
+
* + device token per `cameraKey`, and the URLs are matched back to the returned
|
|
19
|
+
* tokens locally, below. That preserves the property the interactive setup
|
|
20
|
+
* already has and is worth selling — the connection details never leave the
|
|
21
|
+
* venue on this path.
|
|
22
|
+
*
|
|
23
|
+
* The owner is NOT in the body. It comes from the credential server-side.
|
|
24
|
+
*/
|
|
25
|
+
export interface ConnectorCameraClientOptions {
|
|
26
|
+
apiBaseUrl: string;
|
|
27
|
+
/** This machine's connector credential (`connector.key`). */
|
|
28
|
+
connectorKey: string;
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
}
|
|
31
|
+
/** One feed as the server needs to see it — no URL, no credentials. */
|
|
32
|
+
export interface RegisterCameraInput {
|
|
33
|
+
cameraKey: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
brand?: string;
|
|
36
|
+
latitude?: number;
|
|
37
|
+
longitude?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface RegisteredCamera {
|
|
40
|
+
screenId: string;
|
|
41
|
+
deviceId: string;
|
|
42
|
+
deviceToken: string;
|
|
43
|
+
cameraKey: string;
|
|
44
|
+
name: string;
|
|
45
|
+
isReRegistration?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface RegisterResult {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
cameras?: RegisteredCamera[];
|
|
50
|
+
error?: string;
|
|
51
|
+
/** Same taxonomy as EnrollClient — see there for why it matters. */
|
|
52
|
+
kind?: 'auth' | 'network' | 'server' | 'invalid';
|
|
53
|
+
}
|
|
54
|
+
/** The server caps a single registration call; mirrors MAX_CAMERAS_PER_CALL. */
|
|
55
|
+
export declare const MAX_CAMERAS_PER_CALL = 64;
|
|
56
|
+
export declare class ConnectorCameraClient {
|
|
57
|
+
private readonly endpoint;
|
|
58
|
+
private readonly connectorKey;
|
|
59
|
+
private readonly timeoutMs;
|
|
60
|
+
private readonly poster;
|
|
61
|
+
constructor(options: ConnectorCameraClientOptions, poster?: HttpPoster);
|
|
62
|
+
registerCameras(cameras: RegisterCameraInput[]): Promise<RegisterResult>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Join the server's answer back to the LOCAL stream URLs, producing the
|
|
66
|
+
* `cameras[]` the connector runs on.
|
|
67
|
+
*
|
|
68
|
+
* Matching is by `cameraKey` and ONLY by `cameraKey` — never by name and never
|
|
69
|
+
* by array position. Names are truncated server-side (200 chars) so two feeds
|
|
70
|
+
* differing only past that point would collapse onto one screen, and the server
|
|
71
|
+
* is free to return fewer rows than were sent. `cameraKey` is the deterministic
|
|
72
|
+
* idempotency key both sides already agree on.
|
|
73
|
+
*
|
|
74
|
+
* A returned camera with no local match is DROPPED rather than guessed at: a
|
|
75
|
+
* feed configured with the wrong URL would sense the wrong room, which is worse
|
|
76
|
+
* than a feed that is absent and visibly missing from the camera board.
|
|
77
|
+
*/
|
|
78
|
+
export declare function buildCameraFeeds(registered: RegisteredCamera[], discovered: DiscoveredCamera[]): CameraFeedConfig[];
|
|
79
|
+
//# sourceMappingURL=ConnectorCameraClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConnectorCameraClient.d.ts","sourceRoot":"","sources":["../src/ConnectorCameraClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,4BAA4B;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,uEAAuE;AACvE,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;CAClD;AAED,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;gBAExB,OAAO,EAAE,4BAA4B,EAAE,MAAM,GAAE,UAA8B;IAUnF,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;CAmE/E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,gBAAgB,EAAE,EAC9B,UAAU,EAAE,gBAAgB,EAAE,GAC7B,gBAAgB,EAAE,CAiBpB"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectorCameraClient = exports.MAX_CAMERAS_PER_CALL = void 0;
|
|
4
|
+
exports.buildCameraFeeds = buildCameraFeeds;
|
|
5
|
+
const AudienceAnalyzeClient_1 = require("./AudienceAnalyzeClient");
|
|
6
|
+
/** The server caps a single registration call; mirrors MAX_CAMERAS_PER_CALL. */
|
|
7
|
+
exports.MAX_CAMERAS_PER_CALL = 64;
|
|
8
|
+
class ConnectorCameraClient {
|
|
9
|
+
constructor(options, poster = AudienceAnalyzeClient_1.defaultHttpPoster) {
|
|
10
|
+
if (!options?.apiBaseUrl)
|
|
11
|
+
throw new Error('ConnectorCameraClient requires an apiBaseUrl');
|
|
12
|
+
if (!options?.connectorKey)
|
|
13
|
+
throw new Error('ConnectorCameraClient requires a connectorKey');
|
|
14
|
+
const base = options.apiBaseUrl.replace(/\/+$/, '');
|
|
15
|
+
this.endpoint = `${base}/v2/earner/connectors/cameras`;
|
|
16
|
+
this.connectorKey = options.connectorKey;
|
|
17
|
+
this.timeoutMs = options.timeoutMs ?? 60000;
|
|
18
|
+
this.poster = poster;
|
|
19
|
+
}
|
|
20
|
+
async registerCameras(cameras) {
|
|
21
|
+
if (!Array.isArray(cameras) || cameras.length === 0) {
|
|
22
|
+
return { ok: false, error: 'registerCameras() requires at least one camera', kind: 'invalid' };
|
|
23
|
+
}
|
|
24
|
+
if (cameras.length > exports.MAX_CAMERAS_PER_CALL) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: `registerCameras() accepts at most ${exports.MAX_CAMERAS_PER_CALL} cameras per call`,
|
|
28
|
+
kind: 'invalid',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
let response;
|
|
32
|
+
try {
|
|
33
|
+
response = await this.poster(this.endpoint, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
Accept: 'application/json',
|
|
38
|
+
Authorization: `Bearer ${this.connectorKey}`,
|
|
39
|
+
},
|
|
40
|
+
timeoutMs: this.timeoutMs,
|
|
41
|
+
}, JSON.stringify({ cameras }));
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
error: `camera registration failed: ${err?.message ?? 'unknown error'}`,
|
|
47
|
+
kind: 'network',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
let parsed = null;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(response.body);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
parsed = null;
|
|
56
|
+
}
|
|
57
|
+
const code = response.statusCode;
|
|
58
|
+
if (code < 200 || code >= 300) {
|
|
59
|
+
const kind = code === 401 || code === 403 ? 'auth' : code === 400 ? 'invalid' : 'server';
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
error: parsed?.error || `camera registration failed (HTTP ${code})`,
|
|
63
|
+
kind,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const list = Array.isArray(parsed?.cameras) ? parsed.cameras : [];
|
|
67
|
+
// A 2xx that carries no tokens is a failure, not an empty success — treating
|
|
68
|
+
// it as success would write a `cameras[]` with no credentials and the defect
|
|
69
|
+
// would surface much later as a feed that never posts.
|
|
70
|
+
if (list.length === 0 || list.some((c) => !c?.deviceToken || !c?.screenId)) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
error: 'registration response did not contain device tokens for every camera',
|
|
74
|
+
kind: 'server',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return { ok: true, cameras: list };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.ConnectorCameraClient = ConnectorCameraClient;
|
|
81
|
+
/**
|
|
82
|
+
* Join the server's answer back to the LOCAL stream URLs, producing the
|
|
83
|
+
* `cameras[]` the connector runs on.
|
|
84
|
+
*
|
|
85
|
+
* Matching is by `cameraKey` and ONLY by `cameraKey` — never by name and never
|
|
86
|
+
* by array position. Names are truncated server-side (200 chars) so two feeds
|
|
87
|
+
* differing only past that point would collapse onto one screen, and the server
|
|
88
|
+
* is free to return fewer rows than were sent. `cameraKey` is the deterministic
|
|
89
|
+
* idempotency key both sides already agree on.
|
|
90
|
+
*
|
|
91
|
+
* A returned camera with no local match is DROPPED rather than guessed at: a
|
|
92
|
+
* feed configured with the wrong URL would sense the wrong room, which is worse
|
|
93
|
+
* than a feed that is absent and visibly missing from the camera board.
|
|
94
|
+
*/
|
|
95
|
+
function buildCameraFeeds(registered, discovered) {
|
|
96
|
+
const byKey = new Map(discovered.map((d) => [d.cameraKey, d]));
|
|
97
|
+
const feeds = [];
|
|
98
|
+
for (const cam of registered) {
|
|
99
|
+
const local = byKey.get(cam.cameraKey);
|
|
100
|
+
if (!local?.rtspUrl)
|
|
101
|
+
continue;
|
|
102
|
+
feeds.push({
|
|
103
|
+
name: cam.name || local.name,
|
|
104
|
+
screenId: cam.screenId,
|
|
105
|
+
deviceToken: cam.deviceToken,
|
|
106
|
+
rtspUrl: local.rtspUrl,
|
|
107
|
+
sourceType: local.source === 'channel-range' ? 'recorder' : 'direct_url',
|
|
108
|
+
channelKey: cam.cameraKey,
|
|
109
|
+
transport: 'tcp',
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return feeds;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=ConnectorCameraClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConnectorCameraClient.js","sourceRoot":"","sources":["../src/ConnectorCameraClient.ts"],"names":[],"mappings":"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"}
|
package/dist/EdgeAgent.js
CHANGED
|
@@ -44,7 +44,7 @@ const edge_ads_1 = require("@trillboards/edge-ads");
|
|
|
44
44
|
const CommandRouter_1 = require("./CommandRouter");
|
|
45
45
|
const config_1 = require("./config");
|
|
46
46
|
const dataDir_1 = require("./dataDir");
|
|
47
|
-
const SDK_VERSION = '0.2.
|
|
47
|
+
const SDK_VERSION = '0.2.25';
|
|
48
48
|
class EdgeAgent extends events_1.EventEmitter {
|
|
49
49
|
constructor(config, deps) {
|
|
50
50
|
super();
|