lightman-agent 1.0.21 → 1.0.23

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
@@ -8,30 +8,27 @@ import { spawnSync } from 'child_process';
8
8
  import { createInterface } from 'readline/promises';
9
9
  import { stdin as input, stdout as output, cwd, platform, exit } from 'process';
10
10
 
11
- const DEFAULT_SERVER = 'http://192.168.10.100:3401';
12
- const INSTALL_CONFIG_PATH = 'C:\\Program Files\\Lightman\\Agent\\agent.config.json';
13
- const INSTALL_PACKAGE_PATH = 'C:\\Program Files\\Lightman\\Agent\\package.json';
14
-
15
- function printUsage() {
16
- console.log(`
17
- cms-agent <command> [options]
18
-
19
- Commands:
20
- install Prompt slug and server IP, install agent with ShellReplace, reboot
21
- setup Alias of install
22
- update Reinstall/update using installed config, reboot
23
- version Show CLI package version and installed agent version
24
-
25
- Options:
26
- --slug <value> Device slug (example: C-AV01)
27
- --server <url> Server URL or IP (example: 192.168.10.100 or http://192.168.10.100:3401)
28
- --timezone <tz> Timezone override (default: Asia/Kolkata)
29
- --pair-timeout <s> Wait time for pairing in seconds (default: 900, 0 = no timeout)
30
- --no-restart Skip reboot after successful install/update
31
- -v, --version Show version
32
- -h, --help Show help
33
- `);
34
- }
11
+ const DEFAULT_SERVER = 'http://192.168.10.100:3401';
12
+ const INSTALL_CONFIG_PATH = 'C:\\Program Files\\Lightman\\Agent\\agent.config.json';
13
+
14
+ function printUsage() {
15
+ console.log(`
16
+ cms-agent <command> [options]
17
+
18
+ Commands:
19
+ install Prompt slug and server IP, install agent with ShellReplace, reboot
20
+ setup Alias of install
21
+ update Reinstall/update using installed config, reboot
22
+
23
+ Options:
24
+ --slug <value> Device slug (example: C-AV01)
25
+ --server <url> Server URL or IP (example: 192.168.10.100 or http://192.168.10.100:3401)
26
+ --timezone <tz> Timezone override (default: Asia/Kolkata)
27
+ --pair-timeout <s> Wait time for pairing in seconds (default: 900, 0 = no timeout)
28
+ --no-restart Skip reboot after successful install/update
29
+ -h, --help Show help
30
+ `);
31
+ }
35
32
 
36
33
  function parseArgs(argv) {
37
34
  const args = {};
@@ -180,27 +177,14 @@ async function promptServer(defaultServer) {
180
177
  }
181
178
  }
182
179
 
183
- function resolveInstallScript() {
184
- const here = dirname(fileURLToPath(import.meta.url));
185
- const packaged = resolve(here, '../scripts/install-windows.ps1');
180
+ function resolveInstallScript() {
181
+ const here = dirname(fileURLToPath(import.meta.url));
182
+ const packaged = resolve(here, '../scripts/install-windows.ps1');
186
183
  const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
187
184
  if (existsSync(packaged)) return packaged;
188
185
  if (existsSync(localRepo)) return localRepo;
189
- throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
190
- }
191
-
192
- function runVersion() {
193
- const here = dirname(fileURLToPath(import.meta.url));
194
- const bundledPkg = safeReadJson(resolve(here, '../package.json')) || {};
195
- const installedPkg = safeReadJson(INSTALL_PACKAGE_PATH) || {};
196
-
197
- const cliVersion = typeof bundledPkg.version === 'string' ? bundledPkg.version : 'unknown';
198
- const installedVersion = typeof installedPkg.version === 'string' ? installedPkg.version : 'not-installed';
199
-
200
- console.log(`lightman-agent cli: ${cliVersion}`);
201
- console.log(`lightman-agent installed: ${installedVersion}`);
202
- console.log(`node: ${process.version}`);
203
- }
186
+ throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
187
+ }
204
188
 
205
189
  function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
206
190
  console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
@@ -278,24 +262,19 @@ async function runUpdate(opts) {
278
262
  installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
279
263
  }
280
264
 
281
- async function main() {
282
- const [, , commandRaw, ...rest] = process.argv;
283
- const command = commandRaw || '';
284
- const opts = parseArgs(rest);
285
-
286
- if (!command || opts.help || command === 'help' || command === '--help' || command === '-h') {
287
- printUsage();
288
- return;
289
- }
290
-
291
- if (command === 'version' || command === '--version' || command === '-v') {
292
- runVersion();
293
- return;
294
- }
295
-
296
- if (command === 'install' || command === 'setup') {
297
- await runInstall(opts);
298
- return;
265
+ async function main() {
266
+ const [, , commandRaw, ...rest] = process.argv;
267
+ const command = commandRaw || '';
268
+ const opts = parseArgs(rest);
269
+
270
+ if (!command || opts.help || command === 'help' || command === '--help' || command === '-h') {
271
+ printUsage();
272
+ return;
273
+ }
274
+
275
+ if (command === 'install' || command === 'setup') {
276
+ await runInstall(opts);
277
+ return;
299
278
  }
300
279
  if (command === 'update') {
301
280
  await runUpdate(opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightman-agent",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "LIGHTMAN Agent - System-level daemon for museum display machines",
5
5
  "private": false,
6
6
  "type": "module",
@@ -6,6 +6,7 @@ $LogDir = "C:\ProgramData\Lightman\logs"
6
6
  $LogFile = Join-Path $LogDir "guardian.log"
7
7
  $ServiceName = "LightmanAgent"
8
8
  $NssmExe = "C:\ProgramData\Lightman\nssm\nssm.exe"
9
+ $ConfigPath = "C:\Program Files\Lightman\Agent\agent.config.json"
9
10
 
10
11
  function Write-GuardianLog($msg) {
11
12
  $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
@@ -20,6 +21,23 @@ function Write-GuardianLog($msg) {
20
21
  } catch { }
21
22
  }
22
23
 
24
+ function Get-BrowserProcessName {
25
+ try {
26
+ if (Test-Path $ConfigPath) {
27
+ $config = Get-Content $ConfigPath -Raw | ConvertFrom-Json
28
+ $browserPath = $config.kiosk.browserPath
29
+ if ($browserPath) {
30
+ $browserLeaf = Split-Path $browserPath -Leaf
31
+ if ($browserLeaf) {
32
+ return [System.IO.Path]::GetFileNameWithoutExtension($browserLeaf)
33
+ }
34
+ }
35
+ }
36
+ } catch { }
37
+
38
+ return "chrome"
39
+ }
40
+
23
41
  try {
24
42
  # 1. Check LIGHTMAN service
25
43
  $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
@@ -57,15 +75,16 @@ try {
57
75
  }
58
76
  }
59
77
 
60
- # 2. Check Chrome kiosk
61
- $chrome = Get-Process -Name "chrome" -ErrorAction SilentlyContinue
62
- if (-not $chrome) {
78
+ # 2. Check kiosk browser
79
+ $browserProcess = Get-BrowserProcessName
80
+ $browser = Get-Process -Name $browserProcess -ErrorAction SilentlyContinue
81
+ if (-not $browser) {
63
82
  $vbsPath = "C:\Program Files\Lightman\Agent\launch-kiosk.vbs"
64
83
  if (Test-Path $vbsPath) {
65
84
  Start-Sleep -Seconds 10
66
- $chromeRecheck = Get-Process -Name "chrome" -ErrorAction SilentlyContinue
67
- if (-not $chromeRecheck) {
68
- Write-GuardianLog "Chrome not running. Launching via VBS..."
85
+ $browserRecheck = Get-Process -Name $browserProcess -ErrorAction SilentlyContinue
86
+ if (-not $browserRecheck) {
87
+ Write-GuardianLog "Browser '$browserProcess' not running. Launching via VBS..."
69
88
  Start-Process "wscript.exe" -ArgumentList """$vbsPath""" -WindowStyle Hidden
70
89
  }
71
90
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # LIGHTMAN Agent — Linux Installer
3
3
  # Run as root:
4
- # sudo bash install-linux.sh --slug f-av01 --server http://192.168.1.100:3401
4
+ # sudo bash install-linux.sh --slug f-av01 --server http://192.168.10.100:3401
5
5
  set -euo pipefail
6
6
 
7
7
  INSTALL_DIR="/opt/lightman/agent"
@@ -22,7 +22,7 @@ while [[ $# -gt 0 ]]; do
22
22
  --server) SERVER="$2"; shift 2 ;;
23
23
  --timezone) TIMEZONE="$2"; shift 2 ;;
24
24
  -h|--help)
25
- echo "Usage: sudo bash install-linux.sh --slug f-av01 --server http://192.168.1.100:3401 [--timezone Asia/Kolkata]"
25
+ echo "Usage: sudo bash install-linux.sh --slug f-av01 --server http://192.168.10.100:3401 [--timezone Asia/Kolkata]"
26
26
  exit 0
27
27
  ;;
28
28
  *) echo "Unknown option: $1"; exit 1 ;;
@@ -32,13 +32,13 @@ done
32
32
  # ── Validate required args ──
33
33
  if [[ -z "$SLUG" ]]; then
34
34
  echo "Error: --slug is required"
35
- echo "Usage: sudo bash install-linux.sh --slug f-av01 --server http://192.168.1.100:3401"
35
+ echo "Usage: sudo bash install-linux.sh --slug f-av01 --server http://192.168.10.100:3401"
36
36
  exit 1
37
37
  fi
38
38
 
39
39
  if [[ -z "$SERVER" ]]; then
40
40
  echo "Error: --server is required"
41
- echo "Usage: sudo bash install-linux.sh --slug f-av01 --server http://192.168.1.100:3401"
41
+ echo "Usage: sudo bash install-linux.sh --slug f-av01 --server http://192.168.10.100:3401"
42
42
  exit 1
43
43
  fi
44
44
 
@@ -3,10 +3,10 @@
3
3
  # Cleans up any previous installation automatically before installing.
4
4
  #
5
5
  # Run as Administrator:
6
- # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://192.168.1.180:3401"
6
+ # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://192.168.10.100:3401"
7
7
  #
8
8
  # Shell Replacement mode (RECOMMENDED for kiosk machines):
9
- # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://..." -ShellReplace
9
+ # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://192.168.10.100:3401" -ShellReplace
10
10
  #Requires -RunAsAdministrator
11
11
 
12
12
  param(
@@ -14,7 +14,8 @@ param(
14
14
  [Parameter(Mandatory=$true)] [string]$Server,
15
15
  [string]$Timezone = "Asia/Kolkata",
16
16
  [string]$Username = "",
17
- [switch]$ShellReplace = $false
17
+ [switch]$ShellReplace = $false,
18
+ [int]$PairingTimeoutSeconds = 900
18
19
  )
19
20
 
20
21
  $ErrorActionPreference = "Stop"
@@ -25,38 +26,88 @@ $ChromeData = "C:\ProgramData\Lightman\chrome-kiosk"
25
26
  $NssmDir = "C:\ProgramData\Lightman\nssm"
26
27
  $NssmExe = "$NssmDir\nssm.exe"
27
28
  $ServiceName = "LightmanAgent"
28
- $GuardianTask = "LIGHTMAN Guardian"
29
- $KioskTask = "LIGHTMAN Kiosk Browser"
30
- $AgentTask = "LIGHTMAN Agent"
31
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
32
- $AgentDir = Split-Path -Parent $ScriptDir
33
-
34
- function Ensure-PathContains {
35
- param(
36
- [Parameter(Mandatory=$true)][ValidateSet('Machine','User')] [string]$Scope,
37
- [Parameter(Mandatory=$true)] [string]$Entry
38
- )
39
- $current = [System.Environment]::GetEnvironmentVariable("Path", $Scope)
40
- if (-not $current) { $current = "" }
41
- $parts = $current -split ';' | Where-Object { $_ -and $_.Trim() -ne "" }
42
- $normalizedEntry = $Entry.TrimEnd('\')
43
- $exists = $false
44
- foreach ($p in $parts) {
45
- if ($p.TrimEnd('\') -ieq $normalizedEntry) {
46
- $exists = $true
47
- break
48
- }
49
- }
50
- if (-not $exists) {
51
- $newPath = if ($current -and $current.Trim() -ne "") { "$current;$Entry" } else { $Entry }
52
- [System.Environment]::SetEnvironmentVariable("Path", $newPath, $Scope)
53
- return $true
54
- }
55
- return $false
56
- }
29
+ $GuardianTask = "LIGHTMAN Guardian"
30
+ $KioskTask = "LIGHTMAN Kiosk Browser"
31
+ $AgentTask = "LIGHTMAN Agent"
32
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
33
+ $AgentDir = Split-Path -Parent $ScriptDir
57
34
 
58
35
  if (-not $Username) { $Username = $env:USERNAME }
59
36
 
37
+ function Get-ChromePath {
38
+ $candidates = @(
39
+ "C:\Program Files\Google\Chrome\Application\chrome.exe",
40
+ "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
41
+ (Join-Path $env:LOCALAPPDATA "Google\Chrome\Application\chrome.exe")
42
+ )
43
+
44
+ foreach ($candidate in $candidates) {
45
+ if ($candidate -and (Test-Path $candidate)) {
46
+ return $candidate
47
+ }
48
+ }
49
+
50
+ return $null
51
+ }
52
+
53
+ function Install-ChromeFromMsi {
54
+ $is64Bit = [Environment]::Is64BitOperatingSystem
55
+ $downloadUrl = if ($is64Bit) {
56
+ "https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi"
57
+ } else {
58
+ "https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise.msi"
59
+ }
60
+ $installerName = if ($is64Bit) { "googlechromestandaloneenterprise64.msi" } else { "googlechromestandaloneenterprise.msi" }
61
+ $installerPath = Join-Path $env:TEMP $installerName
62
+
63
+ Write-Host " Chrome not found. Downloading official Google Chrome MSI..." -ForegroundColor Yellow
64
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
65
+ Invoke-WebRequest -Uri $downloadUrl -OutFile $installerPath -UseBasicParsing -TimeoutSec 180
66
+
67
+ if (-not (Test-Path $installerPath)) {
68
+ throw "Chrome MSI was not downloaded."
69
+ }
70
+
71
+ $msiArgs = "/i `"$installerPath`" /qn /norestart"
72
+ Start-Process msiexec.exe -ArgumentList $msiArgs -Wait -NoNewWindow
73
+ Remove-Item $installerPath -Force -ErrorAction SilentlyContinue
74
+ }
75
+
76
+ function Install-ChromeFromWinget {
77
+ if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
78
+ throw "winget is not available."
79
+ }
80
+
81
+ Write-Host " MSI install failed. Trying winget for Google Chrome..." -ForegroundColor Yellow
82
+ & winget install --id Google.Chrome --exact --silent --accept-package-agreements --accept-source-agreements 2>&1 | Out-Host
83
+ if ($LASTEXITCODE -ne 0) {
84
+ throw "winget install failed with exit code $LASTEXITCODE."
85
+ }
86
+ }
87
+
88
+ function Ensure-ChromeInstalled {
89
+ $existingChrome = Get-ChromePath
90
+ if ($existingChrome) {
91
+ Write-Host " Found Chrome: $existingChrome" -ForegroundColor Green
92
+ return $existingChrome
93
+ }
94
+
95
+ try {
96
+ Install-ChromeFromMsi
97
+ } catch {
98
+ Write-Host " Chrome MSI install failed: $($_.Exception.Message)" -ForegroundColor DarkYellow
99
+ Install-ChromeFromWinget
100
+ }
101
+
102
+ $installedChrome = Get-ChromePath
103
+ if (-not $installedChrome) {
104
+ throw "Chrome install did not produce chrome.exe."
105
+ }
106
+
107
+ Write-Host " Chrome installed: $installedChrome" -ForegroundColor Green
108
+ return $installedChrome
109
+ }
110
+
60
111
  Write-Host ""
61
112
  Write-Host "=============================================" -ForegroundColor Cyan
62
113
  Write-Host " LIGHTMAN Agent - Complete Windows Installer" -ForegroundColor Cyan
@@ -93,9 +144,23 @@ foreach ($tn in @($AgentTask, $KioskTask, $GuardianTask)) {
93
144
  }
94
145
 
95
146
  # Kill processes
96
- Write-Host "[0c] Killing node.exe and Chrome..." -ForegroundColor Yellow
97
- Get-Process -Name "node" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
147
+ Write-Host "[0c] Killing node.exe and kiosk browser..." -ForegroundColor Yellow
148
+ # IMPORTANT:
149
+ # If install-windows.ps1 is launched from the npm CLI wrapper (node.exe),
150
+ # killing all node.exe would terminate the installer mid-run.
151
+ $parentPid = $null
152
+ try {
153
+ $parentPid = (Get-CimInstance Win32_Process -Filter "ProcessId=$PID" -ErrorAction SilentlyContinue).ParentProcessId
154
+ } catch { }
155
+ Get-Process -Name "node" -ErrorAction SilentlyContinue | ForEach-Object {
156
+ if ($parentPid -and $_.Id -eq $parentPid) {
157
+ Write-Host " Keeping installer parent node.exe (PID $($_.Id))" -ForegroundColor DarkGray
158
+ } else {
159
+ Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
160
+ }
161
+ }
98
162
  Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
163
+ Get-Process -Name "msedge" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
99
164
  Start-Sleep -Seconds 2
100
165
 
101
166
  # Remove old files (keep NSSM and logs)
@@ -115,9 +180,9 @@ Write-Host ""
115
180
  # PART 1: BUILD & INSTALL
116
181
  # ============================================================
117
182
 
118
- # --- 1. Node.js ---
119
- Write-Host "[1/19] Checking Node.js..." -ForegroundColor Yellow
120
- try {
183
+ # --- 1. Node.js ---
184
+ Write-Host "[1/19] Checking Node.js..." -ForegroundColor Yellow
185
+ try {
121
186
  $nodeVersion = (node -v) -replace 'v', ''
122
187
  if ([int]($nodeVersion.Split('.')[0]) -lt 20) { throw "old" }
123
188
  Write-Host " Found Node.js v$nodeVersion"
@@ -130,19 +195,7 @@ try {
130
195
  $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
131
196
  if (-not (Get-Command node -ErrorAction SilentlyContinue)) { Write-Host " FATAL: Node.js install failed!" -ForegroundColor Red; exit 1 }
132
197
  Write-Host " Node.js installed" -ForegroundColor Green
133
- }
134
-
135
- # Always ensure Node path remains available after kiosk conversion
136
- $nodePath = (Get-Command node).Source
137
- $nodeDir = Split-Path -Parent $nodePath
138
- $addedMachine = Ensure-PathContains -Scope "Machine" -Entry $nodeDir
139
- $addedUser = Ensure-PathContains -Scope "User" -Entry $nodeDir
140
- $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
141
- if ($addedMachine -or $addedUser) {
142
- Write-Host " Ensured Node.js path in PATH: $nodeDir" -ForegroundColor Green
143
- } else {
144
- Write-Host " Node.js path already present in PATH"
145
- }
198
+ }
146
199
 
147
200
  # --- 2. Build ---
148
201
  Write-Host "[2/19] Building agent..." -ForegroundColor Yellow
@@ -166,6 +219,7 @@ Copy-Item "$AgentDir\package.json" "$InstallDir\package.json" -Force
166
219
  if (Test-Path "$AgentDir\package-lock.json") { Copy-Item "$AgentDir\package-lock.json" "$InstallDir\package-lock.json" -Force }
167
220
  Copy-Item "$AgentDir\agent.config.template.json" "$InstallDir\agent.config.template.json" -Force
168
221
  if (Test-Path "$AgentDir\public") { Copy-Item "$AgentDir\public" "$InstallDir\public" -Recurse -Force }
222
+ if (Test-Path "$AgentDir\scripts") { Copy-Item "$AgentDir\scripts" "$InstallDir\scripts" -Recurse -Force }
169
223
 
170
224
  # --- 5. Install deps ---
171
225
  Write-Host "[5/19] Installing dependencies..." -ForegroundColor Yellow
@@ -176,6 +230,16 @@ if ($LASTEXITCODE -ne 0) { & npm install --omit=dev --ignore-scripts 2>&1 | Out-
176
230
  $ErrorActionPreference = "Stop"
177
231
  Pop-Location
178
232
 
233
+ # --- 5b. Chrome ---
234
+ Write-Host "[5b/19] Ensuring Google Chrome is installed..." -ForegroundColor Yellow
235
+ try {
236
+ $chromePath = Ensure-ChromeInstalled
237
+ } catch {
238
+ Write-Host " FATAL: $($_.Exception.Message)" -ForegroundColor Red
239
+ Write-Host " Install Google Chrome manually, then re-run the installer." -ForegroundColor Yellow
240
+ exit 1
241
+ }
242
+
179
243
  # --- 6. Generate config ---
180
244
  Write-Host "[6/19] Generating config..." -ForegroundColor Yellow
181
245
  if ($ShellReplace) {
@@ -326,6 +390,49 @@ for ($i = 0; $i -lt 10; $i++) {
326
390
  if ($portUp) { Write-Host " Port 3403 LISTENING" -ForegroundColor Green }
327
391
  else { Write-Host " Port 3403 not yet up (may take a moment)" -ForegroundColor Yellow }
328
392
 
393
+ # Wait until provisioning is complete (auto-provision or manual pairing)
394
+ Write-Host "[11b/19] Waiting for device provisioning/pairing..." -ForegroundColor Yellow
395
+ $identityPath = Join-Path $InstallDir ".lightman-identity.json"
396
+ $deadline = if ($PairingTimeoutSeconds -gt 0) { (Get-Date).AddSeconds($PairingTimeoutSeconds) } else { $null }
397
+ $paired = $false
398
+ $lastHint = ""
399
+
400
+ while (-not $paired) {
401
+ if (Test-Path $identityPath) {
402
+ try {
403
+ $identity = Get-Content $identityPath -Raw | ConvertFrom-Json
404
+ if ($identity.deviceId -and $identity.apiKey) {
405
+ $paired = $true
406
+ break
407
+ }
408
+ } catch {
409
+ # File may be mid-write, retry
410
+ }
411
+ }
412
+
413
+ $logPath = Join-Path $LogDir "service-stdout.log"
414
+ if (Test-Path $logPath) {
415
+ try {
416
+ $hint = Get-Content $logPath -Tail 40 | Where-Object {
417
+ $_ -match "Pairing required|Waiting for admin to approve pairing|Auto-provisioned|Pairing complete"
418
+ } | Select-Object -Last 1
419
+ if ($hint -and $hint -ne $lastHint) {
420
+ Write-Host " Agent: $hint" -ForegroundColor DarkGray
421
+ $lastHint = $hint
422
+ }
423
+ } catch { }
424
+ }
425
+
426
+ if ($deadline -and (Get-Date) -ge $deadline) {
427
+ Write-Host " FATAL: Pairing timed out after $PairingTimeoutSeconds seconds." -ForegroundColor Red
428
+ Write-Host " Check server pairing UI, then re-run installer (or increase -PairingTimeoutSeconds)." -ForegroundColor Yellow
429
+ exit 1
430
+ }
431
+
432
+ Start-Sleep -Seconds 5
433
+ }
434
+ Write-Host " Provisioning/pairing complete" -ForegroundColor Green
435
+
329
436
  # --- 12. Firewall ---
330
437
  Write-Host "[12/19] Configuring firewall..." -ForegroundColor Yellow
331
438
  $ErrorActionPreference = "Continue"
@@ -529,7 +636,7 @@ $svcStatus = if ($finalSvc) { "$($finalSvc.Status)" } else { "NOT FOUND" }
529
636
  $cfgOk = $false
530
637
  try {
531
638
  Push-Location $InstallDir
532
- $cfgResult = & node -e "const c=JSON.parse(require('fs').readFileSync('agent.config.json','utf8'));console.log(JSON.stringify({slug:c.deviceSlug,shell:c.kiosk&&c.kiosk.shellMode||false}))" 2>&1
639
+ $cfgResult = & node -e "const c=JSON.parse(require('fs').readFileSync('agent.config.json','utf8'));console.log(JSON.stringify({slug:c.deviceSlug,shell:c.kiosk&&c.kiosk.shellMode||false,browser:c.kiosk&&c.kiosk.browserPath||''}))" 2>&1
533
640
  $cfgData = $cfgResult | ConvertFrom-Json
534
641
  Pop-Location
535
642
  $cfgOk = $true
@@ -550,6 +657,7 @@ Write-Host " Service : $svcStatus" -ForegroundColor $(if ($svcStatus -eq 'Ru
550
657
  if ($cfgOk) {
551
658
  Write-Host " Config slug: $($cfgData.slug)" -ForegroundColor $(if ($cfgData.slug -eq $Slug) { 'Green' } else { 'Red' })
552
659
  Write-Host " Shell mode : $($cfgData.shell)" -ForegroundColor $(if ($cfgData.shell -eq $ShellReplace.IsPresent) { 'Green' } else { 'Red' })
660
+ Write-Host " Browser : $($cfgData.browser)" -ForegroundColor Green
553
661
  }
554
662
  Write-Host ""
555
663
  Write-Host " Manage:" -ForegroundColor DarkGray
@@ -26,6 +26,11 @@ objFile.Close
26
26
 
27
27
  browserPath = ExtractJsonValue(jsonText, "browserPath")
28
28
  defaultUrl = ExtractJsonValue(jsonText, "defaultUrl")
29
+ browserExeName = LCase(ExtractFileName(browserPath))
30
+
31
+ If browserExeName = "" Then
32
+ browserExeName = "chrome.exe"
33
+ End If
29
34
 
30
35
  If browserPath = "" Or defaultUrl = "" Then
31
36
  WScript.Quit 1
@@ -35,11 +40,11 @@ End If
35
40
  ' Try to ping the server (extract hostname from URL)
36
41
  waitedMs = 0
37
42
  Do While waitedMs < (maxWaitSeconds * 1000)
38
- ' Check if Chrome is already running (agent's KioskManager may have launched it)
43
+ ' Check if the browser is already running (agent's KioskManager may have launched it)
39
44
  Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
40
- Set colProcesses = objWMI.ExecQuery("SELECT ProcessId FROM Win32_Process WHERE Name = 'chrome.exe'")
45
+ Set colProcesses = objWMI.ExecQuery("SELECT ProcessId FROM Win32_Process WHERE Name = '" & browserExeName & "'")
41
46
  If colProcesses.Count > 0 Then
42
- ' Chrome already running - agent is handling it. Exit gracefully.
47
+ ' Browser already running - agent is handling it. Exit gracefully.
43
48
  WScript.Quit 0
44
49
  End If
45
50
 
@@ -56,14 +61,14 @@ Do While waitedMs < (maxWaitSeconds * 1000)
56
61
  ' Service is running - give it a few more seconds to launch Chrome itself
57
62
  WScript.Sleep 15000
58
63
 
59
- ' Re-check if Chrome appeared (agent launched it)
60
- Set colProcesses2 = objWMI.ExecQuery("SELECT ProcessId FROM Win32_Process WHERE Name = 'chrome.exe'")
64
+ ' Re-check if the browser appeared (agent launched it)
65
+ Set colProcesses2 = objWMI.ExecQuery("SELECT ProcessId FROM Win32_Process WHERE Name = '" & browserExeName & "'")
61
66
  If colProcesses2.Count > 0 Then
62
- ' Agent launched Chrome successfully. Exit.
67
+ ' Agent launched the browser successfully. Exit.
63
68
  WScript.Quit 0
64
69
  End If
65
70
 
66
- ' Agent is running but hasn't launched Chrome yet - we'll do it
71
+ ' Agent is running but hasn't launched the browser yet - we'll do it
67
72
  Exit Do
68
73
  End If
69
74
 
@@ -99,3 +104,14 @@ Function ExtractJsonValue(json, key)
99
104
  If endPos = 0 Then Exit Function
100
105
  ExtractJsonValue = Mid(json, pos, endPos - pos)
101
106
  End Function
107
+
108
+ Function ExtractFileName(path)
109
+ ExtractFileName = ""
110
+ If path = "" Then Exit Function
111
+ On Error Resume Next
112
+ ExtractFileName = objFSO.GetFileName(path)
113
+ If ExtractFileName = "" Then
114
+ ExtractFileName = path
115
+ End If
116
+ On Error GoTo 0
117
+ End Function
@@ -15,6 +15,7 @@ REM ================================================================
15
15
 
16
16
  set INSTALL_DIR=C:\Program Files\Lightman\Agent
17
17
  set CONFIG_FILE=%INSTALL_DIR%\agent.config.json
18
+ set URL_SIDECAR=C:\ProgramData\Lightman\kiosk-url.txt
18
19
  set CHROME_DATA=C:\ProgramData\Lightman\chrome-kiosk
19
20
  set LOG_FILE=C:\ProgramData\Lightman\logs\shell.log
20
21
 
@@ -68,14 +69,27 @@ if not "%DEVICE_SLUG%"=="" (
68
69
  echo [%date% %time%] WARNING: No slug in config! >> "%LOG_FILE%"
69
70
  )
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
+ )
80
+
71
81
  REM Fallback browser
72
82
  if "%BROWSER%"=="" (
73
83
  if exist "C:\Program Files\Google\Chrome\Application\chrome.exe" (
74
84
  set "BROWSER=C:\Program Files\Google\Chrome\Application\chrome.exe"
75
85
  ) else if exist "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" (
76
86
  set "BROWSER=C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
87
+ ) else if exist "C:\Program Files\Microsoft\Edge\Application\msedge.exe" (
88
+ set "BROWSER=C:\Program Files\Microsoft\Edge\Application\msedge.exe"
89
+ ) else if exist "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" (
90
+ set "BROWSER=C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
77
91
  ) else (
78
- echo [%date% %time%] ERROR: Chrome not found! >> "%LOG_FILE%"
92
+ echo [%date% %time%] ERROR: No supported kiosk browser found! >> "%LOG_FILE%"
79
93
  start explorer.exe
80
94
  exit /b 1
81
95
  )
@@ -109,20 +123,27 @@ REM ----------------------------------------------------------------
109
123
  REM Infinite Chrome loop
110
124
  REM ----------------------------------------------------------------
111
125
  :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
126
+ REM Prefer sidecar URL for auth params/device routing; fallback to slug URL.
127
+ set SIDE_URL=
128
+ if exist "%URL_SIDECAR%" (
129
+ for /f "usebackq delims=" %%u in ("%URL_SIDECAR%") do set SIDE_URL=%%u
130
+ )
131
+ if not "%SIDE_URL%"=="" (
132
+ set URL=%SIDE_URL%
133
+ ) else (
134
+ REM Re-read slug from config on every loop iteration.
135
+ if exist "%CONFIG_FILE%" (
136
+ 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 (
137
+ if not "%%a"=="" set URL=http://localhost:3403/display/%%a
138
+ )
118
139
  )
119
140
  )
120
141
 
121
- echo [%date% %time%] Launching Chrome: %URL% >> "%LOG_FILE%"
142
+ echo [%date% %time%] Launching browser: %URL% >> "%LOG_FILE%"
122
143
 
123
144
  start /wait "" "%BROWSER%" --kiosk --noerrdialogs --disable-infobars --disable-session-crashed-bubble --no-first-run --no-default-browser-check --start-fullscreen --disable-translate --disable-extensions --autoplay-policy=no-user-gesture-required --disable-features=TranslateUI --user-data-dir="%CHROME_DATA%" "%URL%"
124
145
 
125
- echo [%date% %time%] Chrome exited (code: %errorlevel%). Restarting in 3s... >> "%LOG_FILE%"
146
+ echo [%date% %time%] Browser exited (code: %errorlevel%). Restarting in 3s... >> "%LOG_FILE%"
126
147
  timeout /t 3 /nobreak >nul
127
148
 
128
149
  goto loop