lightman-agent 1.0.15 → 1.0.17

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/agent.config.json CHANGED
@@ -5,6 +5,7 @@
5
5
  "logLevel": "debug",
6
6
  "logFile": "agent.log",
7
7
  "identityFile": ".lightman-identity.json",
8
+ "pairingTimeoutSeconds": 900,
8
9
  "kiosk": {
9
10
  "browserPath": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
10
11
  "defaultUrl": "http://localhost:3403/display/a-av03",
@@ -19,4 +20,4 @@
19
20
  "timezone": "Asia/Kolkata",
20
21
  "shutdownWarningSeconds": 60
21
22
  }
22
- }
23
+ }
@@ -5,6 +5,7 @@
5
5
  "logLevel": "info",
6
6
  "logFile": "agent.log",
7
7
  "identityFile": ".lightman-identity.json",
8
+ "pairingTimeoutSeconds": __PAIRING_TIMEOUT_SECONDS__,
8
9
  "localServices": false,
9
10
  "kiosk": {
10
11
  "browserPath": "__BROWSER_PATH__",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightman-agent",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "LIGHTMAN Agent - System-level daemon for museum display machines",
5
5
  "private": false,
6
6
  "type": "module",
@@ -2,30 +2,88 @@
2
2
  # Runs every 5 minutes via Task Scheduler.
3
3
  # Restarts the NSSM service if it's down. Checks Chrome kiosk health.
4
4
 
5
- $LogDir = "C:\ProgramData\Lightman\logs"
6
- $LogFile = Join-Path $LogDir "guardian.log"
7
- $ServiceName = "LightmanAgent"
8
- $NssmExe = "C:\ProgramData\Lightman\nssm\nssm.exe"
9
-
10
- function Write-GuardianLog($msg) {
11
- $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
12
- try {
13
- if (-not (Test-Path $LogDir)) { New-Item -ItemType Directory -Force -Path $LogDir | Out-Null }
5
+ $LogDir = "C:\ProgramData\Lightman\logs"
6
+ $LogFile = Join-Path $LogDir "guardian.log"
7
+ $ServiceName = "LightmanAgent"
8
+ $NssmExe = "C:\ProgramData\Lightman\nssm\nssm.exe"
9
+ $InstallDir = "C:\Program Files\Lightman\Agent"
10
+ $ChromeDataDir = "C:\ProgramData\Lightman\chrome-kiosk"
11
+
12
+ function Write-GuardianLog($msg) {
13
+ $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
14
+ try {
15
+ if (-not (Test-Path $LogDir)) { New-Item -ItemType Directory -Force -Path $LogDir | Out-Null }
14
16
  Add-Content -Path $LogFile -Value "[$ts] $msg" -ErrorAction SilentlyContinue
15
17
  if ((Get-Item $LogFile -ErrorAction SilentlyContinue).Length -gt 1MB) {
16
18
  $rotated = "$LogFile.old"
17
19
  if (Test-Path $rotated) { Remove-Item $rotated -Force }
18
20
  Rename-Item $LogFile $rotated -Force
19
21
  }
20
- } catch { }
21
- }
22
-
23
- try {
24
- # 1. Check LIGHTMAN service
25
- $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
26
- if (-not $svc) {
27
- $svc = Get-Service -DisplayName "LIGHTMAN*" -ErrorAction SilentlyContinue | Select-Object -First 1
28
- }
22
+ } catch { }
23
+ }
24
+
25
+ function Get-LightmanNodeProcesses {
26
+ try {
27
+ @(Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" -ErrorAction SilentlyContinue | Where-Object {
28
+ $_.CommandLine -and (
29
+ $_.CommandLine -like "*$InstallDir*" -or
30
+ $_.CommandLine -match 'dist\\index\.js|src\\index\.ts|cms-agent\.js'
31
+ )
32
+ })
33
+ } catch {
34
+ @()
35
+ }
36
+ }
37
+
38
+ function Stop-LightmanNodeProcesses {
39
+ $procs = Get-LightmanNodeProcesses
40
+ if (-not $procs -or $procs.Count -eq 0) {
41
+ Write-GuardianLog "No LIGHTMAN-owned node.exe process found"
42
+ return
43
+ }
44
+
45
+ foreach ($proc in $procs) {
46
+ try {
47
+ Stop-Process -Id $proc.ProcessId -Force -ErrorAction Stop
48
+ Write-GuardianLog "Stopped LIGHTMAN node.exe PID $($proc.ProcessId)"
49
+ } catch {
50
+ Write-GuardianLog "Failed to stop LIGHTMAN node.exe PID $($proc.ProcessId): $_"
51
+ }
52
+ }
53
+ }
54
+
55
+ function Get-LightmanShellMode {
56
+ $configPath = Join-Path $InstallDir "agent.config.json"
57
+ try {
58
+ if (-not (Test-Path $configPath)) { return $false }
59
+ $config = Get-Content $configPath -Raw | ConvertFrom-Json
60
+ return [bool]($config.kiosk.shellMode)
61
+ } catch {
62
+ return $false
63
+ }
64
+ }
65
+
66
+ function Get-LightmanChromeProcesses {
67
+ try {
68
+ @(Get-CimInstance Win32_Process -Filter "Name = 'chrome.exe'" -ErrorAction SilentlyContinue | Where-Object {
69
+ $_.CommandLine -and (
70
+ $_.CommandLine -like "*$ChromeDataDir*" -or
71
+ $_.CommandLine -like "*chrome-kiosk*"
72
+ )
73
+ })
74
+ } catch {
75
+ @()
76
+ }
77
+ }
78
+
79
+ try {
80
+ $shellMode = Get-LightmanShellMode
81
+
82
+ # 1. Check LIGHTMAN service
83
+ $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
84
+ if (-not $svc) {
85
+ $svc = Get-Service -DisplayName "LIGHTMAN*" -ErrorAction SilentlyContinue | Select-Object -First 1
86
+ }
29
87
 
30
88
  if (-not $svc) {
31
89
  Write-GuardianLog "CRITICAL: Service not found!"
@@ -41,35 +99,51 @@ try {
41
99
  } else {
42
100
  Start-Service -Name $svc.Name -ErrorAction SilentlyContinue
43
101
  }
44
- Start-Sleep -Seconds 5
45
- $svc.Refresh()
46
- Write-GuardianLog "After restart: $($svc.Status)"
47
- }
48
- elseif ($svc.Status -in @('StartPending', 'StopPending')) {
49
- Write-GuardianLog "Service stuck in $($svc.Status). Force killing node.exe..."
50
- Get-Process -Name "node" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
51
- Start-Sleep -Seconds 3
52
- if (Test-Path $NssmExe) { & $NssmExe start $ServiceName 2>$null }
53
- else { Start-Service -Name $svc.Name -ErrorAction SilentlyContinue }
54
- Start-Sleep -Seconds 5
55
- $svc.Refresh()
56
- Write-GuardianLog "After force restart: $($svc.Status)"
57
- }
58
- }
59
-
60
- # 2. Check Chrome kiosk
61
- $chrome = Get-Process -Name "chrome" -ErrorAction SilentlyContinue
62
- if (-not $chrome) {
63
- $vbsPath = "C:\Program Files\Lightman\Agent\launch-kiosk.vbs"
64
- if (Test-Path $vbsPath) {
65
- 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..."
69
- Start-Process "wscript.exe" -ArgumentList """$vbsPath""" -WindowStyle Hidden
70
- }
71
- }
72
- }
73
- } catch {
74
- Write-GuardianLog "Guardian error: $_"
75
- }
102
+ Start-Sleep -Seconds 5
103
+ $svc.Refresh()
104
+ Write-GuardianLog "After restart: $($svc.Status)"
105
+ }
106
+ elseif ($svc.Status -in @('StartPending', 'StopPending')) {
107
+ Write-GuardianLog "Service is $($svc.Status). Waiting before targeted restart..."
108
+ Start-Sleep -Seconds 15
109
+ $svc.Refresh()
110
+ if ($svc.Status -in @('StartPending', 'StopPending')) {
111
+ Write-GuardianLog "Service still stuck in $($svc.Status). Restarting LIGHTMAN service only."
112
+ Stop-LightmanNodeProcesses
113
+ Start-Sleep -Seconds 3
114
+ if (Test-Path $NssmExe) {
115
+ & $NssmExe restart $ServiceName 2>$null
116
+ } else {
117
+ Stop-Service -Name $svc.Name -Force -ErrorAction SilentlyContinue
118
+ Start-Sleep -Seconds 2
119
+ Start-Service -Name $svc.Name -ErrorAction SilentlyContinue
120
+ }
121
+ Start-Sleep -Seconds 5
122
+ $svc.Refresh()
123
+ Write-GuardianLog "After targeted restart: $($svc.Status)"
124
+ } else {
125
+ Write-GuardianLog "Service recovered during wait: $($svc.Status)"
126
+ }
127
+ }
128
+ }
129
+
130
+ # 2. Check Chrome kiosk
131
+ $chrome = Get-LightmanChromeProcesses
132
+ if (-not $chrome) {
133
+ if ($shellMode) {
134
+ Write-GuardianLog "LIGHTMAN kiosk Chrome not running, but shell mode is enabled. Shell will relaunch it."
135
+ } else {
136
+ $vbsPath = Join-Path $InstallDir "launch-kiosk.vbs"
137
+ if (Test-Path $vbsPath) {
138
+ Start-Sleep -Seconds 10
139
+ $chromeRecheck = Get-LightmanChromeProcesses
140
+ if (-not $chromeRecheck) {
141
+ Write-GuardianLog "LIGHTMAN Chrome not running. Launching via VBS..."
142
+ Start-Process "wscript.exe" -ArgumentList """$vbsPath""" -WindowStyle Hidden
143
+ }
144
+ }
145
+ }
146
+ }
147
+ } catch {
148
+ Write-GuardianLog "Guardian error: $_"
149
+ }
@@ -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
+ [int]$PairingTimeoutSeconds = 900,
17
+ [string]$Username = "",
18
+ [switch]$ShellReplace = $false
19
+ )
19
20
 
20
21
  $ErrorActionPreference = "Stop"
21
22
 
@@ -27,21 +28,83 @@ $NssmExe = "$NssmDir\nssm.exe"
27
28
  $ServiceName = "LightmanAgent"
28
29
  $GuardianTask = "LIGHTMAN Guardian"
29
30
  $KioskTask = "LIGHTMAN Kiosk Browser"
30
- $AgentTask = "LIGHTMAN Agent"
31
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
32
- $AgentDir = Split-Path -Parent $ScriptDir
33
-
34
- if (-not $Username) { $Username = $env:USERNAME }
31
+ $AgentTask = "LIGHTMAN Agent"
32
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
33
+ $AgentDir = Split-Path -Parent $ScriptDir
34
+
35
+ if (-not $Username) { $Username = $env:USERNAME }
36
+
37
+ function Get-LightmanNodeProcesses {
38
+ try {
39
+ @(Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" -ErrorAction SilentlyContinue | Where-Object {
40
+ $_.CommandLine -and (
41
+ $_.CommandLine -like "*$InstallDir*" -or
42
+ $_.CommandLine -like "*$AgentDir*" -or
43
+ $_.CommandLine -match 'dist\\index\.js|src\\index\.ts|cms-agent\.js'
44
+ )
45
+ })
46
+ } catch {
47
+ @()
48
+ }
49
+ }
50
+
51
+ function Stop-LightmanNodeProcesses {
52
+ $procs = Get-LightmanNodeProcesses
53
+ if (-not $procs -or $procs.Count -eq 0) {
54
+ Write-Host " No LIGHTMAN-owned node.exe processes found" -ForegroundColor DarkGray
55
+ return
56
+ }
57
+
58
+ foreach ($proc in $procs) {
59
+ try {
60
+ Stop-Process -Id $proc.ProcessId -Force -ErrorAction Stop
61
+ Write-Host " Stopped LIGHTMAN node.exe PID $($proc.ProcessId)" -ForegroundColor DarkGray
62
+ } catch {
63
+ Write-Host " Failed to stop LIGHTMAN node.exe PID $($proc.ProcessId): $_" -ForegroundColor Yellow
64
+ }
65
+ }
66
+ }
67
+
68
+ function Get-LightmanChromeProcesses {
69
+ try {
70
+ @(Get-CimInstance Win32_Process -Filter "Name = 'chrome.exe'" -ErrorAction SilentlyContinue | Where-Object {
71
+ $_.CommandLine -and (
72
+ $_.CommandLine -like "*$ChromeData*" -or
73
+ $_.CommandLine -like "*chrome-kiosk*"
74
+ )
75
+ })
76
+ } catch {
77
+ @()
78
+ }
79
+ }
80
+
81
+ function Stop-LightmanChromeProcesses {
82
+ $procs = Get-LightmanChromeProcesses
83
+ if (-not $procs -or $procs.Count -eq 0) {
84
+ Write-Host " No LIGHTMAN kiosk Chrome processes found" -ForegroundColor DarkGray
85
+ return
86
+ }
87
+
88
+ foreach ($proc in $procs) {
89
+ try {
90
+ Stop-Process -Id $proc.ProcessId -Force -ErrorAction Stop
91
+ Write-Host " Stopped LIGHTMAN Chrome PID $($proc.ProcessId)" -ForegroundColor DarkGray
92
+ } catch {
93
+ Write-Host " Failed to stop LIGHTMAN Chrome PID $($proc.ProcessId): $_" -ForegroundColor Yellow
94
+ }
95
+ }
96
+ }
35
97
 
36
98
  Write-Host ""
37
99
  Write-Host "=============================================" -ForegroundColor Cyan
38
100
  Write-Host " LIGHTMAN Agent - Complete Windows Installer" -ForegroundColor Cyan
39
101
  Write-Host "=============================================" -ForegroundColor Cyan
40
- Write-Host " Device slug : $Slug"
41
- Write-Host " Server URL : $Server"
42
- Write-Host " Username : $Username"
43
- Write-Host " Mode : $(if ($ShellReplace) { 'Shell Replacement' } else { 'Standard' })"
44
- Write-Host ""
102
+ Write-Host " Device slug : $Slug"
103
+ Write-Host " Server URL : $Server"
104
+ Write-Host " Username : $Username"
105
+ Write-Host " Mode : $(if ($ShellReplace) { 'Shell Replacement' } else { 'Standard' })"
106
+ Write-Host " Pair timeout: $(if ($PairingTimeoutSeconds -eq 0) { 'Disabled' } else { "$PairingTimeoutSeconds s" })"
107
+ Write-Host ""
45
108
 
46
109
  # ============================================================
47
110
  # PHASE 0: NUKE EVERYTHING FROM PREVIOUS INSTALLS
@@ -68,11 +131,11 @@ foreach ($tn in @($AgentTask, $KioskTask, $GuardianTask)) {
68
131
  if ($t) { Stop-ScheduledTask -TaskName $tn -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName $tn -Confirm:$false -ErrorAction SilentlyContinue }
69
132
  }
70
133
 
71
- # Kill processes
72
- Write-Host "[0c] Killing node.exe and Chrome..." -ForegroundColor Yellow
73
- Get-Process -Name "node" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
74
- Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
75
- Start-Sleep -Seconds 2
134
+ # Kill only LIGHTMAN-owned processes
135
+ Write-Host "[0c] Stopping LIGHTMAN node/chrome processes only..." -ForegroundColor Yellow
136
+ Stop-LightmanNodeProcesses
137
+ Stop-LightmanChromeProcesses
138
+ Start-Sleep -Seconds 2
76
139
 
77
140
  # Remove old files (keep NSSM and logs)
78
141
  Write-Host "[0d] Removing old agent files..." -ForegroundColor Yellow
@@ -142,11 +205,11 @@ Pop-Location
142
205
 
143
206
  # --- 6. Generate config ---
144
207
  Write-Host "[6/19] Generating config..." -ForegroundColor Yellow
145
- if ($ShellReplace) {
146
- & "$ScriptDir\setup.ps1" -Slug $Slug -Server $Server -Timezone $Timezone -InstallDir $InstallDir -ShellMode
147
- } else {
148
- & "$ScriptDir\setup.ps1" -Slug $Slug -Server $Server -Timezone $Timezone -InstallDir $InstallDir
149
- }
208
+ if ($ShellReplace) {
209
+ & "$ScriptDir\setup.ps1" -Slug $Slug -Server $Server -Timezone $Timezone -PairingTimeoutSeconds $PairingTimeoutSeconds -InstallDir $InstallDir -ShellMode
210
+ } else {
211
+ & "$ScriptDir\setup.ps1" -Slug $Slug -Server $Server -Timezone $Timezone -PairingTimeoutSeconds $PairingTimeoutSeconds -InstallDir $InstallDir
212
+ }
150
213
 
151
214
  # --- 7. Fix BOM ---
152
215
  Write-Host "[7/19] Fixing config encoding..." -ForegroundColor Yellow
package/scripts/setup.ps1 CHANGED
@@ -15,11 +15,14 @@ param(
15
15
  [Parameter(Mandatory=$true)]
16
16
  [string]$Server,
17
17
 
18
- [Parameter(Mandatory=$false)]
19
- [string]$Timezone = "Asia/Kolkata",
20
-
21
- [Parameter(Mandatory=$false)]
22
- [string]$InstallDir = $null,
18
+ [Parameter(Mandatory=$false)]
19
+ [string]$Timezone = "Asia/Kolkata",
20
+
21
+ [Parameter(Mandatory=$false)]
22
+ [int]$PairingTimeoutSeconds = 900,
23
+
24
+ [Parameter(Mandatory=$false)]
25
+ [string]$InstallDir = $null,
23
26
 
24
27
  [Parameter(Mandatory=$false)]
25
28
  [switch]$ShellMode = $false
@@ -36,11 +39,12 @@ if (-not $InstallDir) {
36
39
 
37
40
  Write-Host ""
38
41
  Write-Host "=== LIGHTMAN Agent - Device Setup ===" -ForegroundColor Cyan
39
- Write-Host " Slug: $Slug"
40
- Write-Host " Server: $Server"
41
- Write-Host " Install dir: $InstallDir"
42
- Write-Host " Timezone: $Timezone"
43
- Write-Host ""
42
+ Write-Host " Slug: $Slug"
43
+ Write-Host " Server: $Server"
44
+ Write-Host " Install dir: $InstallDir"
45
+ Write-Host " Timezone: $Timezone"
46
+ Write-Host " Pair timeout: $(if ($PairingTimeoutSeconds -eq 0) { 'Disabled' } else { "$PairingTimeoutSeconds s" })"
47
+ Write-Host ""
44
48
 
45
49
  # 1. Clear cached identity (CRITICAL - prevents old device credentials leaking)
46
50
  $IdentityFile = Join-Path $InstallDir ".lightman-identity.json"
@@ -83,13 +87,14 @@ $Template = Get-Content $TemplatePath -Raw
83
87
  $BrowserEscaped = $BrowserPath -replace '\\', '\\'
84
88
  $ChromeDirEscaped = $ChromeDataDir -replace '\\', '\\'
85
89
 
86
- $Config = $Template `
87
- -replace '__SERVER_URL__', $Server `
88
- -replace '__DEVICE_SLUG__', $Slug `
89
- -replace '__KIOSK_URL__', $KioskUrl `
90
- -replace '__BROWSER_PATH__', $BrowserEscaped `
91
- -replace '__CHROME_DATA_DIR__', $ChromeDirEscaped `
92
- -replace 'Asia/Kolkata', $Timezone
90
+ $Config = $Template `
91
+ -replace '__SERVER_URL__', $Server `
92
+ -replace '__DEVICE_SLUG__', $Slug `
93
+ -replace '__KIOSK_URL__', $KioskUrl `
94
+ -replace '__BROWSER_PATH__', $BrowserEscaped `
95
+ -replace '__CHROME_DATA_DIR__', $ChromeDirEscaped `
96
+ -replace '__PAIRING_TIMEOUT_SECONDS__', $PairingTimeoutSeconds `
97
+ -replace 'Asia/Kolkata', $Timezone
93
98
 
94
99
  # 6. Inject shellMode into kiosk config if requested
95
100
  if ($ShellMode) {
package/src/lib/config.ts CHANGED
@@ -32,8 +32,9 @@ const configSchema = z.object({
32
32
  healthIntervalMs: z.number().int().min(5000).default(60000),
33
33
  logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
34
34
  logFile: z.string().default('agent.log'),
35
- identityFile: z.string().default('.lightman-identity.json'),
36
- localServices: z.boolean().default(true),
35
+ identityFile: z.string().default('.lightman-identity.json'),
36
+ pairingTimeoutSeconds: z.number().int().min(0).default(900),
37
+ localServices: z.boolean().default(true),
37
38
  kiosk: kioskSchema.optional(),
38
39
  screenshot: screenshotSchema.optional(),
39
40
  powerSchedule: powerScheduleSchema.optional(),
package/src/lib/types.ts CHANGED
@@ -6,6 +6,7 @@ export interface AgentConfig {
6
6
  logLevel: 'debug' | 'info' | 'warn' | 'error';
7
7
  logFile: string;
8
8
  identityFile: string;
9
+ pairingTimeoutSeconds?: number;
9
10
  /** When false, agent runs in kiosk-only mode — no local server/display processes. Default: true */
10
11
  localServices: boolean;
11
12
  kiosk?: KioskConfig;
@@ -61,10 +61,12 @@ export async function provision(
61
61
  const code = data.code as string;
62
62
  logger.warn(`Pairing required. Enter code in admin UI: ${code}`);
63
63
  logger.info('Waiting for admin to approve pairing...');
64
+ const pairingTimeoutMs = Math.max(0, config.pairingTimeoutSeconds ?? 900) * 1000;
64
65
 
65
66
  const identity = await pollForPairing(
66
67
  `${baseUrl}/status?code=${encodeURIComponent(code)}`,
67
- logger
68
+ logger,
69
+ pairingTimeoutMs
68
70
  );
69
71
 
70
72
  writeIdentity(config.identityFile, identity);
@@ -81,7 +83,7 @@ async function pollForPairing(
81
83
  timeoutMs = 600_000,
82
84
  intervalMs = 5_000
83
85
  ): Promise<Identity> {
84
- const deadline = Date.now() + timeoutMs;
86
+ const deadline = timeoutMs <= 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
85
87
 
86
88
  while (Date.now() < deadline) {
87
89
  await sleep(intervalMs);