lightman-agent 1.0.22 → 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,51 +177,25 @@ 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');
186
- const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
187
- if (existsSync(packaged)) return packaged;
188
- 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 scriptSupportsPairingTimeout(scriptPath) {
193
- try {
194
- const script = readFileSync(scriptPath, 'utf8');
195
- return script.includes('PairingTimeoutSeconds');
196
- } catch {
197
- return false;
198
- }
199
- }
200
-
201
- function runVersion() {
202
- const here = dirname(fileURLToPath(import.meta.url));
203
- const bundledPkg = safeReadJson(resolve(here, '../package.json')) || {};
204
- const installedPkg = safeReadJson(INSTALL_PACKAGE_PATH) || {};
205
-
206
- const cliVersion = typeof bundledPkg.version === 'string' ? bundledPkg.version : 'unknown';
207
- const installedVersion = typeof installedPkg.version === 'string' ? installedPkg.version : 'not-installed';
208
-
209
- console.log(`lightman-agent cli: ${cliVersion}`);
210
- console.log(`lightman-agent installed: ${installedVersion}`);
211
- console.log(`node: ${process.version}`);
212
- }
213
-
214
- function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
215
- console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
216
- const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
217
- if (timezone) {
218
- args.push('-Timezone', timezone);
219
- }
220
- if (
221
- Number.isInteger(pairingTimeoutSeconds)
222
- && pairingTimeoutSeconds >= 0
223
- && scriptSupportsPairingTimeout(scriptPath)
224
- ) {
225
- args.push('-PairingTimeoutSeconds', String(pairingTimeoutSeconds));
226
- }
227
- runOrFail('powershell.exe', args);
180
+ function resolveInstallScript() {
181
+ const here = dirname(fileURLToPath(import.meta.url));
182
+ const packaged = resolve(here, '../scripts/install-windows.ps1');
183
+ const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
184
+ if (existsSync(packaged)) return packaged;
185
+ if (existsSync(localRepo)) return localRepo;
186
+ throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
187
+ }
188
+
189
+ function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
190
+ console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
191
+ const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
192
+ if (timezone) {
193
+ args.push('-Timezone', timezone);
194
+ }
195
+ if (Number.isInteger(pairingTimeoutSeconds) && pairingTimeoutSeconds >= 0) {
196
+ args.push('-PairingTimeoutSeconds', String(pairingTimeoutSeconds));
197
+ }
198
+ runOrFail('powershell.exe', args);
228
199
 
229
200
  if (!noRestart) {
230
201
  console.log('Installation completed. Rebooting now...');
@@ -291,24 +262,19 @@ async function runUpdate(opts) {
291
262
  installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
292
263
  }
293
264
 
294
- async function main() {
295
- const [, , commandRaw, ...rest] = process.argv;
296
- const command = commandRaw || '';
297
- const opts = parseArgs(rest);
298
-
299
- if (!command || opts.help || command === 'help' || command === '--help' || command === '-h') {
300
- printUsage();
301
- return;
302
- }
303
-
304
- if (command === 'version' || command === '--version' || command === '-v') {
305
- runVersion();
306
- return;
307
- }
308
-
309
- if (command === 'install' || command === 'setup') {
310
- await runInstall(opts);
311
- 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;
312
278
  }
313
279
  if (command === 'update') {
314
280
  await runUpdate(opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightman-agent",
3
- "version": "1.0.22",
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,20 +3,20 @@
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
- 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
- )
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
+ )
20
20
 
21
21
  $ErrorActionPreference = "Stop"
22
22
 
@@ -26,38 +26,88 @@ $ChromeData = "C:\ProgramData\Lightman\chrome-kiosk"
26
26
  $NssmDir = "C:\ProgramData\Lightman\nssm"
27
27
  $NssmExe = "$NssmDir\nssm.exe"
28
28
  $ServiceName = "LightmanAgent"
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
34
-
35
- function Ensure-PathContains {
36
- param(
37
- [Parameter(Mandatory=$true)][ValidateSet('Machine','User')] [string]$Scope,
38
- [Parameter(Mandatory=$true)] [string]$Entry
39
- )
40
- $current = [System.Environment]::GetEnvironmentVariable("Path", $Scope)
41
- if (-not $current) { $current = "" }
42
- $parts = $current -split ';' | Where-Object { $_ -and $_.Trim() -ne "" }
43
- $normalizedEntry = $Entry.TrimEnd('\')
44
- $exists = $false
45
- foreach ($p in $parts) {
46
- if ($p.TrimEnd('\') -ieq $normalizedEntry) {
47
- $exists = $true
48
- break
49
- }
50
- }
51
- if (-not $exists) {
52
- $newPath = if ($current -and $current.Trim() -ne "") { "$current;$Entry" } else { $Entry }
53
- [System.Environment]::SetEnvironmentVariable("Path", $newPath, $Scope)
54
- return $true
55
- }
56
- return $false
57
- }
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
58
34
 
59
35
  if (-not $Username) { $Username = $env:USERNAME }
60
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
+
61
111
  Write-Host ""
62
112
  Write-Host "=============================================" -ForegroundColor Cyan
63
113
  Write-Host " LIGHTMAN Agent - Complete Windows Installer" -ForegroundColor Cyan
@@ -94,9 +144,23 @@ foreach ($tn in @($AgentTask, $KioskTask, $GuardianTask)) {
94
144
  }
95
145
 
96
146
  # Kill processes
97
- Write-Host "[0c] Killing node.exe and Chrome..." -ForegroundColor Yellow
98
- 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
+ }
99
162
  Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
163
+ Get-Process -Name "msedge" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
100
164
  Start-Sleep -Seconds 2
101
165
 
102
166
  # Remove old files (keep NSSM and logs)
@@ -116,9 +180,9 @@ Write-Host ""
116
180
  # PART 1: BUILD & INSTALL
117
181
  # ============================================================
118
182
 
119
- # --- 1. Node.js ---
120
- Write-Host "[1/19] Checking Node.js..." -ForegroundColor Yellow
121
- try {
183
+ # --- 1. Node.js ---
184
+ Write-Host "[1/19] Checking Node.js..." -ForegroundColor Yellow
185
+ try {
122
186
  $nodeVersion = (node -v) -replace 'v', ''
123
187
  if ([int]($nodeVersion.Split('.')[0]) -lt 20) { throw "old" }
124
188
  Write-Host " Found Node.js v$nodeVersion"
@@ -131,19 +195,7 @@ try {
131
195
  $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
132
196
  if (-not (Get-Command node -ErrorAction SilentlyContinue)) { Write-Host " FATAL: Node.js install failed!" -ForegroundColor Red; exit 1 }
133
197
  Write-Host " Node.js installed" -ForegroundColor Green
134
- }
135
-
136
- # Always ensure Node path remains available after kiosk conversion
137
- $nodePath = (Get-Command node).Source
138
- $nodeDir = Split-Path -Parent $nodePath
139
- $addedMachine = Ensure-PathContains -Scope "Machine" -Entry $nodeDir
140
- $addedUser = Ensure-PathContains -Scope "User" -Entry $nodeDir
141
- $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
142
- if ($addedMachine -or $addedUser) {
143
- Write-Host " Ensured Node.js path in PATH: $nodeDir" -ForegroundColor Green
144
- } else {
145
- Write-Host " Node.js path already present in PATH"
146
- }
198
+ }
147
199
 
148
200
  # --- 2. Build ---
149
201
  Write-Host "[2/19] Building agent..." -ForegroundColor Yellow
@@ -167,6 +219,7 @@ Copy-Item "$AgentDir\package.json" "$InstallDir\package.json" -Force
167
219
  if (Test-Path "$AgentDir\package-lock.json") { Copy-Item "$AgentDir\package-lock.json" "$InstallDir\package-lock.json" -Force }
168
220
  Copy-Item "$AgentDir\agent.config.template.json" "$InstallDir\agent.config.template.json" -Force
169
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 }
170
223
 
171
224
  # --- 5. Install deps ---
172
225
  Write-Host "[5/19] Installing dependencies..." -ForegroundColor Yellow
@@ -177,6 +230,16 @@ if ($LASTEXITCODE -ne 0) { & npm install --omit=dev --ignore-scripts 2>&1 | Out-
177
230
  $ErrorActionPreference = "Stop"
178
231
  Pop-Location
179
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
+
180
243
  # --- 6. Generate config ---
181
244
  Write-Host "[6/19] Generating config..." -ForegroundColor Yellow
182
245
  if ($ShellReplace) {
@@ -327,6 +390,49 @@ for ($i = 0; $i -lt 10; $i++) {
327
390
  if ($portUp) { Write-Host " Port 3403 LISTENING" -ForegroundColor Green }
328
391
  else { Write-Host " Port 3403 not yet up (may take a moment)" -ForegroundColor Yellow }
329
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
+
330
436
  # --- 12. Firewall ---
331
437
  Write-Host "[12/19] Configuring firewall..." -ForegroundColor Yellow
332
438
  $ErrorActionPreference = "Continue"
@@ -530,7 +636,7 @@ $svcStatus = if ($finalSvc) { "$($finalSvc.Status)" } else { "NOT FOUND" }
530
636
  $cfgOk = $false
531
637
  try {
532
638
  Push-Location $InstallDir
533
- $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
534
640
  $cfgData = $cfgResult | ConvertFrom-Json
535
641
  Pop-Location
536
642
  $cfgOk = $true
@@ -551,6 +657,7 @@ Write-Host " Service : $svcStatus" -ForegroundColor $(if ($svcStatus -eq 'Ru
551
657
  if ($cfgOk) {
552
658
  Write-Host " Config slug: $($cfgData.slug)" -ForegroundColor $(if ($cfgData.slug -eq $Slug) { 'Green' } else { 'Red' })
553
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
554
661
  }
555
662
  Write-Host ""
556
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