lightman-agent 1.0.2 → 1.0.4

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/bin/cms-agent.js CHANGED
@@ -24,6 +24,7 @@ Options:
24
24
  --slug <value> Device slug (example: C-AV01)
25
25
  --server <url> Server URL (example: http://192.168.1.100:3401)
26
26
  --timezone <tz> Timezone override (default: Asia/Kolkata)
27
+ --pair-timeout <s> Wait time for pairing in seconds (default: 900, 0 = no timeout)
27
28
  --no-restart Skip reboot after successful install/update
28
29
  -h, --help Show help
29
30
  `);
@@ -57,6 +58,9 @@ function parseArgs(argv) {
57
58
  } else if (part === '--timezone') {
58
59
  args.timezone = next.trim();
59
60
  i += 1;
61
+ } else if (part === '--pair-timeout') {
62
+ args.pairTimeout = next.trim();
63
+ i += 1;
60
64
  }
61
65
  }
62
66
  return args;
@@ -134,12 +138,15 @@ function resolveInstallScript() {
134
138
  throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
135
139
  }
136
140
 
137
- function installUsingPowerShell({ scriptPath, slug, server, timezone, noRestart }) {
141
+ function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
138
142
  console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
139
143
  const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
140
144
  if (timezone) {
141
145
  args.push('-Timezone', timezone);
142
146
  }
147
+ if (Number.isInteger(pairingTimeoutSeconds) && pairingTimeoutSeconds >= 0) {
148
+ args.push('-PairingTimeoutSeconds', String(pairingTimeoutSeconds));
149
+ }
143
150
  runOrFail('powershell.exe', args);
144
151
 
145
152
  if (!noRestart) {
@@ -161,6 +168,7 @@ async function runInstall(opts) {
161
168
  const defaultSlug = opts.slug || localConfig.deviceSlug || installedConfig.deviceSlug || '';
162
169
  const server = opts.server || DEFAULT_SERVER;
163
170
  const timezone = opts.timezone || localConfig?.powerSchedule?.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
171
+ const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
164
172
  const noRestart = Boolean(opts.noRestart);
165
173
 
166
174
  console.log('Detected MAC addresses:');
@@ -178,7 +186,7 @@ async function runInstall(opts) {
178
186
  const scriptPath = resolveInstallScript();
179
187
 
180
188
  console.log(`Installing with slug=${slug}, server=${server}, shellReplace=true`);
181
- installUsingPowerShell({ scriptPath, slug, server, timezone, noRestart });
189
+ installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
182
190
  }
183
191
 
184
192
  async function runUpdate(opts) {
@@ -193,6 +201,7 @@ async function runUpdate(opts) {
193
201
  const slug = opts.slug || installedConfig.deviceSlug;
194
202
  const server = opts.server || DEFAULT_SERVER;
195
203
  const timezone = opts.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
204
+ const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
196
205
  const noRestart = Boolean(opts.noRestart);
197
206
  const scriptPath = resolveInstallScript();
198
207
 
@@ -201,7 +210,7 @@ async function runUpdate(opts) {
201
210
  }
202
211
 
203
212
  console.log(`Updating with slug=${slug}, server=${server}, shellReplace=true`);
204
- installUsingPowerShell({ scriptPath, slug, server, timezone, noRestart });
213
+ installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
205
214
  }
206
215
 
207
216
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightman-agent",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "LIGHTMAN Agent - System-level daemon for museum display machines",
5
5
  "private": false,
6
6
  "type": "module",
@@ -9,13 +9,14 @@
9
9
  # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://..." -ShellReplace
10
10
  #Requires -RunAsAdministrator
11
11
 
12
- param(
13
- [Parameter(Mandatory=$true)] [string]$Slug,
14
- [Parameter(Mandatory=$true)] [string]$Server,
15
- [string]$Timezone = "Asia/Kolkata",
16
- [string]$Username = "",
17
- [switch]$ShellReplace = $false
18
- )
12
+ param(
13
+ [Parameter(Mandatory=$true)] [string]$Slug,
14
+ [Parameter(Mandatory=$true)] [string]$Server,
15
+ [string]$Timezone = "Asia/Kolkata",
16
+ [string]$Username = "",
17
+ [switch]$ShellReplace = $false,
18
+ [int]$PairingTimeoutSeconds = 900
19
+ )
19
20
 
20
21
  $ErrorActionPreference = "Stop"
21
22
 
@@ -300,11 +301,54 @@ for ($i = 0; $i -lt 10; $i++) {
300
301
  if ($n) { $portUp = $true; break }
301
302
  Start-Sleep -Seconds 2
302
303
  }
303
- if ($portUp) { Write-Host " Port 3403 LISTENING" -ForegroundColor Green }
304
- else { Write-Host " Port 3403 not yet up (may take a moment)" -ForegroundColor Yellow }
305
-
306
- # --- 12. Firewall ---
307
- Write-Host "[12/19] Configuring firewall..." -ForegroundColor Yellow
304
+ if ($portUp) { Write-Host " Port 3403 LISTENING" -ForegroundColor Green }
305
+ else { Write-Host " Port 3403 not yet up (may take a moment)" -ForegroundColor Yellow }
306
+
307
+ # Wait until provisioning is complete (auto-provision or manual pairing)
308
+ Write-Host "[11b/19] Waiting for device provisioning/pairing..." -ForegroundColor Yellow
309
+ $identityPath = Join-Path $InstallDir ".lightman-identity.json"
310
+ $deadline = if ($PairingTimeoutSeconds -gt 0) { (Get-Date).AddSeconds($PairingTimeoutSeconds) } else { $null }
311
+ $paired = $false
312
+ $lastHint = ""
313
+
314
+ while (-not $paired) {
315
+ if (Test-Path $identityPath) {
316
+ try {
317
+ $identity = Get-Content $identityPath -Raw | ConvertFrom-Json
318
+ if ($identity.deviceId -and $identity.apiKey) {
319
+ $paired = $true
320
+ break
321
+ }
322
+ } catch {
323
+ # File may be mid-write, retry
324
+ }
325
+ }
326
+
327
+ $logPath = Join-Path $LogDir "service-stdout.log"
328
+ if (Test-Path $logPath) {
329
+ try {
330
+ $hint = Get-Content $logPath -Tail 40 | Where-Object {
331
+ $_ -match "Pairing required|Waiting for admin to approve pairing|Auto-provisioned|Pairing complete"
332
+ } | Select-Object -Last 1
333
+ if ($hint -and $hint -ne $lastHint) {
334
+ Write-Host " Agent: $hint" -ForegroundColor DarkGray
335
+ $lastHint = $hint
336
+ }
337
+ } catch { }
338
+ }
339
+
340
+ if ($deadline -and (Get-Date) -ge $deadline) {
341
+ Write-Host " FATAL: Pairing timed out after $PairingTimeoutSeconds seconds." -ForegroundColor Red
342
+ Write-Host " Check server pairing UI, then re-run installer (or increase -PairingTimeoutSeconds)." -ForegroundColor Yellow
343
+ exit 1
344
+ }
345
+
346
+ Start-Sleep -Seconds 5
347
+ }
348
+ Write-Host " Provisioning/pairing complete" -ForegroundColor Green
349
+
350
+ # --- 12. Firewall ---
351
+ Write-Host "[12/19] Configuring firewall..." -ForegroundColor Yellow
308
352
  $ErrorActionPreference = "Continue"
309
353
  if (-not (Get-NetFirewallRule -DisplayName "LIGHTMAN Agent WebSocket" -ErrorAction SilentlyContinue)) {
310
354
  New-NetFirewallRule -DisplayName "LIGHTMAN Agent WebSocket" -Direction Outbound -Action Allow -Protocol TCP -RemotePort 3001 -Description "LIGHTMAN Agent" | Out-Null
@@ -13,10 +13,11 @@ REM 5. Launches Chrome fullscreen
13
13
  REM 6. If Chrome crashes, relaunches in 3 seconds (infinite loop)
14
14
  REM ================================================================
15
15
 
16
- set INSTALL_DIR=C:\Program Files\Lightman\Agent
17
- set CONFIG_FILE=%INSTALL_DIR%\agent.config.json
18
- set CHROME_DATA=C:\ProgramData\Lightman\chrome-kiosk
19
- set LOG_FILE=C:\ProgramData\Lightman\logs\shell.log
16
+ set INSTALL_DIR=C:\Program Files\Lightman\Agent
17
+ set CONFIG_FILE=%INSTALL_DIR%\agent.config.json
18
+ set URL_SIDECAR=C:\ProgramData\Lightman\kiosk-url.txt
19
+ set CHROME_DATA=C:\ProgramData\Lightman\chrome-kiosk
20
+ set LOG_FILE=C:\ProgramData\Lightman\logs\shell.log
20
21
 
21
22
  REM Ensure directories exist
22
23
  if not exist "C:\ProgramData\Lightman\logs" mkdir "C:\ProgramData\Lightman\logs"
@@ -59,14 +60,23 @@ if exist "%CONFIG_FILE%" (
59
60
 
60
61
  :use_fallbacks
61
62
 
62
- REM Build URL from slug (ALWAYS from config, never from sidecar)
63
- if not "%DEVICE_SLUG%"=="" (
64
- set URL=http://localhost:3403/display/%DEVICE_SLUG%
65
- echo [%date% %time%] Slug: %DEVICE_SLUG% >> "%LOG_FILE%"
66
- ) else (
67
- set URL=http://localhost:3403/display
68
- echo [%date% %time%] WARNING: No slug in config! >> "%LOG_FILE%"
69
- )
63
+ REM Build URL from slug (ALWAYS from config, never from sidecar)
64
+ if not "%DEVICE_SLUG%"=="" (
65
+ set URL=http://localhost:3403/display/%DEVICE_SLUG%
66
+ echo [%date% %time%] Slug: %DEVICE_SLUG% >> "%LOG_FILE%"
67
+ ) else (
68
+ set URL=http://localhost:3403/display
69
+ echo [%date% %time%] WARNING: No slug in config! >> "%LOG_FILE%"
70
+ )
71
+
72
+ REM If agent wrote a URL sidecar (includes deviceId/apiKey), prefer it.
73
+ if exist "%URL_SIDECAR%" (
74
+ for /f "usebackq delims=" %%u in ("%URL_SIDECAR%") do set SIDE_URL=%%u
75
+ if not "%SIDE_URL%"=="" (
76
+ set URL=%SIDE_URL%
77
+ echo [%date% %time%] Using sidecar URL >> "%LOG_FILE%"
78
+ )
79
+ )
70
80
 
71
81
  REM Fallback browser
72
82
  if "%BROWSER%"=="" (
@@ -108,15 +118,22 @@ echo [%date% %time%] Agent ready >> "%LOG_FILE%"
108
118
  REM ----------------------------------------------------------------
109
119
  REM Infinite Chrome loop
110
120
  REM ----------------------------------------------------------------
111
- :loop
112
- REM Re-read slug from config on every loop iteration
113
- REM This way if someone changes agent.config.json, the next
114
- REM Chrome restart picks up the new slug automatically.
115
- if exist "%CONFIG_FILE%" (
116
- for /f "delims=" %%a in ('node -e "try{console.log(JSON.parse(require('fs').readFileSync(String.raw`%CONFIG_FILE%`,'utf8')).deviceSlug)}catch(e){console.log('')}" 2^>nul') do (
117
- if not "%%a"=="" set URL=http://localhost:3403/display/%%a
118
- )
119
- )
121
+ :loop
122
+ REM Prefer sidecar URL for auth params/device routing; fallback to slug URL.
123
+ set SIDE_URL=
124
+ if exist "%URL_SIDECAR%" (
125
+ for /f "usebackq delims=" %%u in ("%URL_SIDECAR%") do set SIDE_URL=%%u
126
+ )
127
+ if not "%SIDE_URL%"=="" (
128
+ set URL=%SIDE_URL%
129
+ ) else (
130
+ REM Re-read slug from config on every loop iteration.
131
+ if exist "%CONFIG_FILE%" (
132
+ for /f "delims=" %%a in ('node -e "try{console.log(JSON.parse(require('fs').readFileSync(String.raw`%CONFIG_FILE%`,'utf8')).deviceSlug)}catch(e){console.log('')}" 2^>nul') do (
133
+ if not "%%a"=="" set URL=http://localhost:3403/display/%%a
134
+ )
135
+ )
136
+ )
120
137
 
121
138
  echo [%date% %time%] Launching Chrome: %URL% >> "%LOG_FILE%"
122
139
 
@@ -169,17 +169,19 @@ export class KioskManager {
169
169
  // Shell Mode Methods
170
170
  // =====================================================================
171
171
 
172
- private async shellLaunch(targetUrl: string): Promise<KioskStatus> {
173
- this.currentUrl = targetUrl;
174
-
175
- // Shell mode: Chrome is managed by lightman-shell.bat.
176
- // Shell reads slug from agent.config.json directly.
177
- // Agent NEVER kills Chrome on startup - only on explicit navigate().
178
- if (this.isChromeRunning()) {
179
- this.logger.info('Shell mode: Chrome already running. Not touching it.');
180
- } else {
181
- this.logger.info('Shell mode: Chrome not running. Shell BAT will launch it.');
182
- }
172
+ private async shellLaunch(targetUrl: string): Promise<KioskStatus> {
173
+ this.currentUrl = targetUrl;
174
+ // Keep shell sidecar updated so shell BAT can launch with auth query params.
175
+ this.writeUrlSidecar(targetUrl);
176
+
177
+ // Shell mode: Chrome is managed by lightman-shell.bat.
178
+ // Shell prefers sidecar URL (if present), then falls back to slug in config.
179
+ if (this.isChromeRunning()) {
180
+ this.logger.info('Shell mode: Chrome already running. Restarting once to apply sidecar URL.');
181
+ this.killAllChrome();
182
+ } else {
183
+ this.logger.info('Shell mode: Chrome not running. Shell BAT will launch it.');
184
+ }
183
185
 
184
186
  this.startedAt = this.startedAt || Date.now();
185
187
  return this.getStatus();