lightman-agent 1.0.4 → 1.0.6

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.
Files changed (115) hide show
  1. package/agent.config.template.json +30 -30
  2. package/package.json +52 -52
  3. package/public/assets/index-CcBNCz6h.css +1 -1
  4. package/public/assets/index-D9QHMG8k.js +1 -0
  5. package/public/assets/index-H-8HDl46.js +1 -1
  6. package/public/assets/index-YodeiCia.css +1 -0
  7. package/public/assets/index-legacy-DWtNM8y7.js +41 -0
  8. package/public/assets/museum-map-CwVDA2z1.svg +4182 -0
  9. package/public/assets/polyfills-legacy-DyVYWHbW.js +4 -0
  10. package/public/index.html +7 -2
  11. package/public/templates/custom08/elements/back-button.svg +20 -0
  12. package/public/templates/custom08/elements/base-map-background.svg +37 -0
  13. package/public/templates/custom08/elements/base-map.svg +1191 -0
  14. package/public/templates/custom08/elements/gallery-1-2-3-info-panel.svg +236 -0
  15. package/public/templates/custom08/elements/gallery-4-5-6-7-info-panel.svg +266 -0
  16. package/public/templates/custom08/elements/gallery-8-9-info-panel.svg +274 -0
  17. package/public/templates/custom08/elements/gallery-labels/_nav-map-styles.css +554 -0
  18. package/public/templates/custom08/elements/gallery-labels/_styles.css +556 -0
  19. package/public/templates/custom08/elements/gallery-labels/gallery-1.svg +35 -0
  20. package/public/templates/custom08/elements/gallery-labels/gallery-2.svg +34 -0
  21. package/public/templates/custom08/elements/gallery-labels/gallery-3.svg +34 -0
  22. package/public/templates/custom08/elements/gallery-labels/gallery-4.svg +37 -0
  23. package/public/templates/custom08/elements/gallery-labels/gallery-5.svg +34 -0
  24. package/public/templates/custom08/elements/gallery-labels/gallery-6.svg +34 -0
  25. package/public/templates/custom08/elements/gallery-labels/gallery-7.svg +34 -0
  26. package/public/templates/custom08/elements/gallery-labels/gallery-8.svg +37 -0
  27. package/public/templates/custom08/elements/gallery-labels/gallery-9.svg +34 -0
  28. package/public/templates/custom08/elements/hand-hint.png +0 -0
  29. package/public/templates/custom08/elements/idle-screen-bg.svg +5 -0
  30. package/public/templates/custom08/elements/idle-screen-map.svg +627 -0
  31. package/public/templates/custom08/elements/idle-screen-text.svg +350 -0
  32. package/public/templates/custom08/elements/key-map-1.svg +986 -0
  33. package/public/templates/custom08/elements/key-map-2.svg +1018 -0
  34. package/public/templates/custom08/elements/key-map-3.svg +1019 -0
  35. package/public/templates/custom08/elements/key-map-combined.svg +1001 -0
  36. package/public/templates/custom08/elements/map-highlight-marker.svg +11 -0
  37. package/public/templates/custom08/elements/map-pin-marker.svg +15 -0
  38. package/public/templates/custom08/elements/map-teardrop-star-marker.svg +13 -0
  39. package/public/templates/custom08/elements/nav-circle-galleries-1-3.svg +21 -0
  40. package/public/templates/custom08/elements/nav-circle-galleries-4-7.svg +24 -0
  41. package/public/templates/custom08/elements/nav-circle-galleries-8-9.svg +20 -0
  42. package/public/templates/custom08/elements/section1-map.svg +1435 -0
  43. package/public/templates/custom08/elements/section2-map.svg +1724 -0
  44. package/public/templates/custom08/elements/section3-map.svg +1295 -0
  45. package/public/templates/custom08/fonts/CabinetGrotesk-Variable.ttf +0 -0
  46. package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.23.12_PM.png +0 -0
  47. package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.23.56_PM.png +0 -0
  48. package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.24.24_PM.png +0 -0
  49. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.31.58_PM.jpg +0 -0
  50. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.11_PM.jpg +0 -0
  51. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.36_PM.jpg +0 -0
  52. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.48_PM.jpg +0 -0
  53. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.59_PM.jpg +0 -0
  54. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.15_PM.jpg +0 -0
  55. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.27_PM.jpg +0 -0
  56. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.34_PM.jpg +0 -0
  57. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.42_PM.jpg +0 -0
  58. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.50_PM.jpg +0 -0
  59. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.58_PM.jpg +0 -0
  60. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.04_PM.jpg +0 -0
  61. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.11_PM.jpg +0 -0
  62. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.20_PM.jpg +0 -0
  63. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.57_PM.jpg +0 -0
  64. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.03_PM.jpg +0 -0
  65. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.16_PM.jpg +0 -0
  66. package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.23_PM.jpg +0 -0
  67. package/public/templates/custom08/images/highlights/prologue-highlight.png +0 -0
  68. package/scripts/guardian.ps1 +75 -75
  69. package/scripts/install-linux.sh +134 -134
  70. package/scripts/install-rpi.sh +117 -117
  71. package/scripts/install-windows.ps1 +513 -512
  72. package/scripts/launch-kiosk.vbs +101 -101
  73. package/scripts/lightman-agent.logrotate +12 -12
  74. package/scripts/lightman-agent.service +38 -38
  75. package/scripts/lightman-shell.bat +107 -107
  76. package/scripts/reinstall-windows.ps1 +26 -26
  77. package/scripts/restore-desktop.ps1 +32 -32
  78. package/scripts/setup.ps1 +116 -116
  79. package/scripts/setup.sh +115 -115
  80. package/scripts/sync-display.mjs +20 -0
  81. package/scripts/uninstall-linux.sh +50 -50
  82. package/scripts/uninstall-windows.ps1 +54 -54
  83. package/src/commands/display.ts +177 -177
  84. package/src/commands/kiosk.ts +113 -113
  85. package/src/commands/maintenance.ts +106 -106
  86. package/src/commands/network.ts +129 -129
  87. package/src/commands/power.ts +163 -163
  88. package/src/commands/rpi.ts +45 -45
  89. package/src/commands/screenshot.ts +166 -166
  90. package/src/commands/serial.ts +17 -17
  91. package/src/commands/update.ts +124 -124
  92. package/src/index.ts +652 -652
  93. package/src/lib/config.ts +69 -69
  94. package/src/lib/identity.ts +40 -40
  95. package/src/lib/logger.ts +137 -137
  96. package/src/lib/platform.ts +10 -10
  97. package/src/lib/rpi.ts +180 -180
  98. package/src/lib/screens.ts +128 -128
  99. package/src/lib/types.ts +176 -176
  100. package/src/services/commands.ts +107 -107
  101. package/src/services/health.ts +161 -161
  102. package/src/services/kiosk.ts +384 -384
  103. package/src/services/localEvents.ts +60 -60
  104. package/src/services/logForwarder.ts +72 -72
  105. package/src/services/multiScreenKiosk.ts +324 -324
  106. package/src/services/oscBridge.ts +186 -186
  107. package/src/services/powerScheduler.ts +260 -260
  108. package/src/services/provisioning.ts +120 -120
  109. package/src/services/serialBridge.ts +230 -230
  110. package/src/services/serviceLauncher.ts +183 -183
  111. package/src/services/staticServer.ts +226 -226
  112. package/src/services/updater.ts +249 -249
  113. package/src/services/watchdog.ts +310 -310
  114. package/src/services/websocket.ts +152 -152
  115. package/tsconfig.json +28 -28
@@ -1,14 +1,14 @@
1
- # LIGHTMAN Agent - Complete Windows Installer
2
- # Uses NSSM for rock-solid Windows Service. Shell replacement for kiosk.
3
- # Cleans up any previous installation automatically before installing.
4
- #
5
- # Run as Administrator:
6
- # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://192.168.1.180:3401"
7
- #
8
- # Shell Replacement mode (RECOMMENDED for kiosk machines):
9
- # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://..." -ShellReplace
10
- #Requires -RunAsAdministrator
11
-
1
+ # LIGHTMAN Agent - Complete Windows Installer
2
+ # Uses NSSM for rock-solid Windows Service. Shell replacement for kiosk.
3
+ # Cleans up any previous installation automatically before installing.
4
+ #
5
+ # Run as Administrator:
6
+ # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://192.168.1.180:3401"
7
+ #
8
+ # Shell Replacement mode (RECOMMENDED for kiosk machines):
9
+ # powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://..." -ShellReplace
10
+ #Requires -RunAsAdministrator
11
+
12
12
  param(
13
13
  [Parameter(Mandatory=$true)] [string]$Slug,
14
14
  [Parameter(Mandatory=$true)] [string]$Server,
@@ -17,58 +17,58 @@ param(
17
17
  [switch]$ShellReplace = $false,
18
18
  [int]$PairingTimeoutSeconds = 900
19
19
  )
20
-
21
- $ErrorActionPreference = "Stop"
22
-
23
- $InstallDir = "C:\Program Files\Lightman\Agent"
24
- $LogDir = "C:\ProgramData\Lightman\logs"
25
- $ChromeData = "C:\ProgramData\Lightman\chrome-kiosk"
26
- $NssmDir = "C:\ProgramData\Lightman\nssm"
27
- $NssmExe = "$NssmDir\nssm.exe"
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
- if (-not $Username) { $Username = $env:USERNAME }
36
-
37
- Write-Host ""
38
- Write-Host "=============================================" -ForegroundColor Cyan
39
- Write-Host " LIGHTMAN Agent - Complete Windows Installer" -ForegroundColor Cyan
40
- Write-Host "=============================================" -ForegroundColor Cyan
41
- Write-Host " Device slug : $Slug"
42
- Write-Host " Server URL : $Server"
43
- Write-Host " Username : $Username"
44
- Write-Host " Mode : $(if ($ShellReplace) { 'Shell Replacement' } else { 'Standard' })"
45
- Write-Host ""
46
-
47
- # ============================================================
48
- # PHASE 0: NUKE EVERYTHING FROM PREVIOUS INSTALLS
49
- # ============================================================
50
- Write-Host "--- Phase 0: Cleaning previous installation ---" -ForegroundColor Cyan
51
- $ErrorActionPreference = "Continue"
52
-
53
- # Stop and remove NSSM service
54
- Write-Host "[0a] Removing old services..." -ForegroundColor Yellow
55
- if (Test-Path $NssmExe) {
56
- & $NssmExe stop $ServiceName 2>$null
57
- & $NssmExe remove $ServiceName confirm 2>$null
58
- }
59
- foreach ($sn in @($ServiceName, "lightmanagent.exe", "LightmanAgent.exe")) {
60
- sc.exe stop $sn 2>$null; sc.exe delete $sn 2>$null
61
- }
62
- $oldSvc = Get-Service -DisplayName "LIGHTMAN*" -ErrorAction SilentlyContinue
63
- if ($oldSvc) { Stop-Service -Name $oldSvc.Name -Force -ErrorAction SilentlyContinue; sc.exe delete $oldSvc.Name 2>$null }
64
-
65
- # Remove scheduled tasks (from previous task-scheduler-based installs)
66
- Write-Host "[0b] Removing old scheduled tasks..." -ForegroundColor Yellow
67
- foreach ($tn in @($AgentTask, $KioskTask, $GuardianTask)) {
68
- $t = Get-ScheduledTask -TaskName $tn -ErrorAction SilentlyContinue
69
- if ($t) { Stop-ScheduledTask -TaskName $tn -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName $tn -Confirm:$false -ErrorAction SilentlyContinue }
70
- }
71
-
20
+
21
+ $ErrorActionPreference = "Stop"
22
+
23
+ $InstallDir = "C:\Program Files\Lightman\Agent"
24
+ $LogDir = "C:\ProgramData\Lightman\logs"
25
+ $ChromeData = "C:\ProgramData\Lightman\chrome-kiosk"
26
+ $NssmDir = "C:\ProgramData\Lightman\nssm"
27
+ $NssmExe = "$NssmDir\nssm.exe"
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
+ if (-not $Username) { $Username = $env:USERNAME }
36
+
37
+ Write-Host ""
38
+ Write-Host "=============================================" -ForegroundColor Cyan
39
+ Write-Host " LIGHTMAN Agent - Complete Windows Installer" -ForegroundColor Cyan
40
+ Write-Host "=============================================" -ForegroundColor Cyan
41
+ Write-Host " Device slug : $Slug"
42
+ Write-Host " Server URL : $Server"
43
+ Write-Host " Username : $Username"
44
+ Write-Host " Mode : $(if ($ShellReplace) { 'Shell Replacement' } else { 'Standard' })"
45
+ Write-Host ""
46
+
47
+ # ============================================================
48
+ # PHASE 0: NUKE EVERYTHING FROM PREVIOUS INSTALLS
49
+ # ============================================================
50
+ Write-Host "--- Phase 0: Cleaning previous installation ---" -ForegroundColor Cyan
51
+ $ErrorActionPreference = "Continue"
52
+
53
+ # Stop and remove NSSM service
54
+ Write-Host "[0a] Removing old services..." -ForegroundColor Yellow
55
+ if (Test-Path $NssmExe) {
56
+ & $NssmExe stop $ServiceName 2>$null
57
+ & $NssmExe remove $ServiceName confirm 2>$null
58
+ }
59
+ foreach ($sn in @($ServiceName, "lightmanagent.exe", "LightmanAgent.exe")) {
60
+ sc.exe stop $sn 2>$null; sc.exe delete $sn 2>$null
61
+ }
62
+ $oldSvc = Get-Service -DisplayName "LIGHTMAN*" -ErrorAction SilentlyContinue
63
+ if ($oldSvc) { Stop-Service -Name $oldSvc.Name -Force -ErrorAction SilentlyContinue; sc.exe delete $oldSvc.Name 2>$null }
64
+
65
+ # Remove scheduled tasks (from previous task-scheduler-based installs)
66
+ Write-Host "[0b] Removing old scheduled tasks..." -ForegroundColor Yellow
67
+ foreach ($tn in @($AgentTask, $KioskTask, $GuardianTask)) {
68
+ $t = Get-ScheduledTask -TaskName $tn -ErrorAction SilentlyContinue
69
+ if ($t) { Stop-ScheduledTask -TaskName $tn -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName $tn -Confirm:$false -ErrorAction SilentlyContinue }
70
+ }
71
+
72
72
  # Kill processes
73
73
  Write-Host "[0c] Killing node.exe and Chrome..." -ForegroundColor Yellow
74
74
  # IMPORTANT:
@@ -87,220 +87,221 @@ Get-Process -Name "node" -ErrorAction SilentlyContinue | ForEach-Object {
87
87
  }
88
88
  Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
89
89
  Start-Sleep -Seconds 2
90
-
91
- # Remove old files (keep NSSM and logs)
92
- Write-Host "[0d] Removing old agent files..." -ForegroundColor Yellow
93
- Remove-Item -Path $InstallDir -Recurse -Force -ErrorAction SilentlyContinue
94
- Remove-Item -Path "C:\ProgramData\Lightman\kiosk-url.txt" -Force -ErrorAction SilentlyContinue
95
-
96
- # Remove firewall rule
97
- Remove-NetFirewallRule -DisplayName "LIGHTMAN Agent WebSocket" -ErrorAction SilentlyContinue
98
-
99
- $ErrorActionPreference = "Stop"
100
- Start-Sleep -Seconds 2
101
- Write-Host " Clean slate" -ForegroundColor Green
102
- Write-Host ""
103
-
104
- # ============================================================
105
- # PART 1: BUILD & INSTALL
106
- # ============================================================
107
-
108
- # --- 1. Node.js ---
109
- Write-Host "[1/19] Checking Node.js..." -ForegroundColor Yellow
110
- try {
111
- $nodeVersion = (node -v) -replace 'v', ''
112
- if ([int]($nodeVersion.Split('.')[0]) -lt 20) { throw "old" }
113
- Write-Host " Found Node.js v$nodeVersion"
114
- } catch {
115
- Write-Host " Installing Node.js v20.18.0..." -ForegroundColor Yellow
116
- $installer = "$env:TEMP\node-setup.msi"
117
- Invoke-WebRequest -Uri "https://nodejs.org/dist/v20.18.0/node-v20.18.0-x64.msi" -OutFile $installer -UseBasicParsing
118
- Start-Process msiexec.exe -ArgumentList "/i `"$installer`" /qn /norestart" -Wait -NoNewWindow
119
- Remove-Item $installer -Force -ErrorAction SilentlyContinue
120
- $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
121
- if (-not (Get-Command node -ErrorAction SilentlyContinue)) { Write-Host " FATAL: Node.js install failed!" -ForegroundColor Red; exit 1 }
122
- Write-Host " Node.js installed" -ForegroundColor Green
123
- }
124
-
125
- # --- 2. Build ---
126
- Write-Host "[2/19] Building agent..." -ForegroundColor Yellow
127
- Push-Location $AgentDir
128
- $ErrorActionPreference = "Continue"
129
- & npm install 2>&1 | Out-Host
130
- & npm run build 2>&1 | Out-Host
131
- $ErrorActionPreference = "Stop"
132
- if (-not (Test-Path "$AgentDir\dist\index.js")) { Write-Host " FATAL: Build failed!" -ForegroundColor Red; exit 1 }
133
- Pop-Location
134
- Write-Host " Build successful"
135
-
136
- # --- 3. Directories ---
137
- Write-Host "[3/19] Creating directories..." -ForegroundColor Yellow
138
- foreach ($d in @($InstallDir, $LogDir, $ChromeData, $NssmDir)) { New-Item -ItemType Directory -Force -Path $d | Out-Null }
139
-
140
- # --- 4. Copy files ---
141
- Write-Host "[4/19] Copying agent files..." -ForegroundColor Yellow
142
- Copy-Item "$AgentDir\dist" "$InstallDir\dist" -Recurse -Force
143
- Copy-Item "$AgentDir\package.json" "$InstallDir\package.json" -Force
144
- if (Test-Path "$AgentDir\package-lock.json") { Copy-Item "$AgentDir\package-lock.json" "$InstallDir\package-lock.json" -Force }
145
- Copy-Item "$AgentDir\agent.config.template.json" "$InstallDir\agent.config.template.json" -Force
146
- if (Test-Path "$AgentDir\public") { Copy-Item "$AgentDir\public" "$InstallDir\public" -Recurse -Force }
147
-
148
- # --- 5. Install deps ---
149
- Write-Host "[5/19] Installing dependencies..." -ForegroundColor Yellow
150
- Push-Location $InstallDir
151
- $ErrorActionPreference = "Continue"
152
- & npm ci --omit=dev --ignore-scripts 2>&1 | Out-Host
153
- if ($LASTEXITCODE -ne 0) { & npm install --omit=dev --ignore-scripts 2>&1 | Out-Host }
154
- $ErrorActionPreference = "Stop"
155
- Pop-Location
156
-
157
- # --- 6. Generate config ---
158
- Write-Host "[6/19] Generating config..." -ForegroundColor Yellow
159
- if ($ShellReplace) {
160
- & "$ScriptDir\setup.ps1" -Slug $Slug -Server $Server -Timezone $Timezone -InstallDir $InstallDir -ShellMode
161
- } else {
162
- & "$ScriptDir\setup.ps1" -Slug $Slug -Server $Server -Timezone $Timezone -InstallDir $InstallDir
163
- }
164
-
165
- # --- 7. Fix BOM ---
166
- Write-Host "[7/19] Fixing config encoding..." -ForegroundColor Yellow
167
- $configPath = Join-Path $InstallDir "agent.config.json"
168
- if (-not (Test-Path $configPath)) { Write-Host " FATAL: config not created!" -ForegroundColor Red; exit 1 }
169
- $raw = [System.IO.File]::ReadAllText($configPath)
170
- [System.IO.File]::WriteAllText($configPath, $raw.TrimStart([char]0xFEFF), [System.Text.UTF8Encoding]::new($false))
171
-
172
- # --- 8. Verify config ---
173
- Write-Host "[8/19] Verifying config..." -ForegroundColor Yellow
174
- Push-Location $InstallDir
175
- $ErrorActionPreference = "Continue"
176
- $jsonCheck = & node -e "try{const c=JSON.parse(require('fs').readFileSync('agent.config.json','utf8'));console.log('OK slug='+c.deviceSlug+' shellMode='+(c.kiosk&&c.kiosk.shellMode||false))}catch(e){console.log('FAIL: '+e.message);process.exit(1)}" 2>&1
177
- $ErrorActionPreference = "Stop"
178
- if ($LASTEXITCODE -ne 0) { Write-Host " FATAL: invalid config: $jsonCheck" -ForegroundColor Red; Pop-Location; exit 1 }
179
- Pop-Location
180
- Write-Host " $jsonCheck"
181
-
182
- # --- 9. Download NSSM ---
183
- Write-Host "[9/19] Setting up NSSM..." -ForegroundColor Yellow
184
- if (-not (Test-Path $NssmExe)) {
185
- # Check bundled copy first (fastest, no internet needed)
186
- $bundled = Join-Path $AgentDir "nssm\nssm.exe"
187
- if (Test-Path $bundled) {
188
- Copy-Item $bundled $NssmExe -Force
189
- Write-Host " Using bundled NSSM"
190
- } else {
191
- # Download from multiple sources
192
- $nssmZip = "$env:TEMP\nssm.zip"
193
- $downloaded = $false
194
- $urls = @(
195
- "https://nssm.cc/release/nssm-2.24.zip",
196
- "https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip"
197
- )
198
- foreach ($url in $urls) {
199
- if ($downloaded) { break }
200
- Write-Host " Downloading from $url ..."
201
- try {
202
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
203
- Invoke-WebRequest -Uri $url -OutFile $nssmZip -UseBasicParsing -TimeoutSec 60
204
- if ((Test-Path $nssmZip) -and (Get-Item $nssmZip).Length -gt 10000) {
205
- $downloaded = $true
206
- }
207
- } catch {
208
- Write-Host " Failed: $_" -ForegroundColor DarkYellow
209
- }
210
- }
211
- if ($downloaded) {
212
- Expand-Archive -Path $nssmZip -DestinationPath "$env:TEMP\nssm-extract" -Force
213
- # Find nssm.exe in extracted folder (handles different zip structures)
214
- $found = Get-ChildItem "$env:TEMP\nssm-extract" -Recurse -Filter "nssm.exe" | Where-Object { $_.DirectoryName -like "*win64*" } | Select-Object -First 1
215
- if (-not $found) { $found = Get-ChildItem "$env:TEMP\nssm-extract" -Recurse -Filter "nssm.exe" | Select-Object -First 1 }
216
- if ($found) { Copy-Item $found.FullName $NssmExe -Force }
217
- Remove-Item $nssmZip -Force -ErrorAction SilentlyContinue
218
- Remove-Item "$env:TEMP\nssm-extract" -Recurse -Force -ErrorAction SilentlyContinue
219
- }
220
- }
221
- }
222
- if (-not (Test-Path $NssmExe)) {
223
- Write-Host ""
224
- Write-Host " NSSM download failed. Manual fix:" -ForegroundColor Red
225
- Write-Host " 1. Download nssm-2.24.zip from https://nssm.cc/release/nssm-2.24.zip" -ForegroundColor Yellow
226
- Write-Host " 2. Extract win64\nssm.exe to: $NssmExe" -ForegroundColor Yellow
227
- Write-Host " 3. Re-run this script" -ForegroundColor Yellow
228
- Write-Host ""
229
- Write-Host " OR bundle it in the repo:" -ForegroundColor Yellow
230
- Write-Host " Copy nssm.exe to: $AgentDir\nssm\nssm.exe" -ForegroundColor Yellow
231
- exit 1
232
- }
233
- Write-Host " NSSM ready: $NssmExe"
234
-
235
- # --- 10. Install Windows Service via NSSM ---
236
- Write-Host "[10/19] Installing Windows Service..." -ForegroundColor Yellow
237
-
238
- # Clean slate
239
- $ErrorActionPreference = "Continue"
240
- & $NssmExe stop $ServiceName 2>$null
241
- & $NssmExe remove $ServiceName confirm 2>$null
242
- sc.exe delete $ServiceName 2>$null
243
- Start-Sleep -Seconds 2
244
- $ErrorActionPreference = "Stop"
245
-
246
- $nodePath = (Get-Command node).Source
247
-
248
- # Install
249
- & $NssmExe install $ServiceName $nodePath "dist\index.js"
250
- if ($LASTEXITCODE -ne 0) { Write-Host " FATAL: NSSM install failed!" -ForegroundColor Red; exit 1 }
251
-
252
- # Configure
253
- & $NssmExe set $ServiceName AppDirectory $InstallDir
254
- & $NssmExe set $ServiceName DisplayName "LIGHTMAN Agent"
255
- & $NssmExe set $ServiceName Description "LIGHTMAN kiosk agent - display management and monitoring"
256
- & $NssmExe set $ServiceName Start SERVICE_AUTO_START
257
- & $NssmExe set $ServiceName AppStdout "$LogDir\service-stdout.log"
258
- & $NssmExe set $ServiceName AppStderr "$LogDir\service-stderr.log"
259
- & $NssmExe set $ServiceName AppStdoutCreationDisposition 4
260
- & $NssmExe set $ServiceName AppStderrCreationDisposition 4
261
- & $NssmExe set $ServiceName AppRotateFiles 1
262
- & $NssmExe set $ServiceName AppRotateBytes 5242880
263
- & $NssmExe set $ServiceName AppRestartDelay 10000
264
- & $NssmExe set $ServiceName AppExit Default Restart
265
-
266
- # Verify service was created
267
- Start-Sleep -Seconds 2
268
- $svcCheck = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
269
- if (-not $svcCheck) {
270
- $svcCheck = Get-Service -DisplayName "LIGHTMAN*" -ErrorAction SilentlyContinue | Select-Object -First 1
271
- }
272
- if (-not $svcCheck) {
273
- Write-Host " FATAL: Service was not created!" -ForegroundColor Red
274
- exit 1
275
- }
276
- Write-Host " Service installed: $($svcCheck.Name)" -ForegroundColor Green
277
-
278
- # Recovery policy
279
- sc.exe failure $svcCheck.Name reset= 86400 actions= restart/5000/restart/10000/restart/30000 2>$null
280
-
281
- # --- 11. Start service ---
282
- Write-Host "[11/19] Starting service..." -ForegroundColor Yellow
283
- Start-Service -Name $svcCheck.Name -ErrorAction SilentlyContinue
284
- Start-Sleep -Seconds 5
285
- $svcCheck.Refresh()
286
-
287
- if ($svcCheck.Status -eq 'Running') {
288
- Write-Host " Service is RUNNING" -ForegroundColor Green
289
- } else {
290
- Write-Host " Service status: $($svcCheck.Status) - check $LogDir" -ForegroundColor Yellow
291
- Start-Sleep -Seconds 3
292
- Start-Service -Name $svcCheck.Name -ErrorAction SilentlyContinue
293
- }
294
-
295
- # Wait for port 3403
296
- $portUp = $false
297
- for ($i = 0; $i -lt 10; $i++) {
298
- $ErrorActionPreference = "Continue"
299
- $n = netstat -an 2>$null | findstr ":3403.*LISTENING" 2>$null
300
- $ErrorActionPreference = "Stop"
301
- if ($n) { $portUp = $true; break }
302
- Start-Sleep -Seconds 2
303
- }
90
+
91
+ # Remove old files (keep NSSM and logs)
92
+ Write-Host "[0d] Removing old agent files..." -ForegroundColor Yellow
93
+ Remove-Item -Path $InstallDir -Recurse -Force -ErrorAction SilentlyContinue
94
+ Remove-Item -Path "C:\ProgramData\Lightman\kiosk-url.txt" -Force -ErrorAction SilentlyContinue
95
+
96
+ # Remove firewall rule
97
+ Remove-NetFirewallRule -DisplayName "LIGHTMAN Agent WebSocket" -ErrorAction SilentlyContinue
98
+
99
+ $ErrorActionPreference = "Stop"
100
+ Start-Sleep -Seconds 2
101
+ Write-Host " Clean slate" -ForegroundColor Green
102
+ Write-Host ""
103
+
104
+ # ============================================================
105
+ # PART 1: BUILD & INSTALL
106
+ # ============================================================
107
+
108
+ # --- 1. Node.js ---
109
+ Write-Host "[1/19] Checking Node.js..." -ForegroundColor Yellow
110
+ try {
111
+ $nodeVersion = (node -v) -replace 'v', ''
112
+ if ([int]($nodeVersion.Split('.')[0]) -lt 20) { throw "old" }
113
+ Write-Host " Found Node.js v$nodeVersion"
114
+ } catch {
115
+ Write-Host " Installing Node.js v20.18.0..." -ForegroundColor Yellow
116
+ $installer = "$env:TEMP\node-setup.msi"
117
+ Invoke-WebRequest -Uri "https://nodejs.org/dist/v20.18.0/node-v20.18.0-x64.msi" -OutFile $installer -UseBasicParsing
118
+ Start-Process msiexec.exe -ArgumentList "/i `"$installer`" /qn /norestart" -Wait -NoNewWindow
119
+ Remove-Item $installer -Force -ErrorAction SilentlyContinue
120
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
121
+ if (-not (Get-Command node -ErrorAction SilentlyContinue)) { Write-Host " FATAL: Node.js install failed!" -ForegroundColor Red; exit 1 }
122
+ Write-Host " Node.js installed" -ForegroundColor Green
123
+ }
124
+
125
+ # --- 2. Build ---
126
+ Write-Host "[2/19] Building agent..." -ForegroundColor Yellow
127
+ Push-Location $AgentDir
128
+ $ErrorActionPreference = "Continue"
129
+ & npm install 2>&1 | Out-Host
130
+ & npm run build 2>&1 | Out-Host
131
+ $ErrorActionPreference = "Stop"
132
+ if (-not (Test-Path "$AgentDir\dist\index.js")) { Write-Host " FATAL: Build failed!" -ForegroundColor Red; exit 1 }
133
+ Pop-Location
134
+ Write-Host " Build successful"
135
+
136
+ # --- 3. Directories ---
137
+ Write-Host "[3/19] Creating directories..." -ForegroundColor Yellow
138
+ foreach ($d in @($InstallDir, $LogDir, $ChromeData, $NssmDir)) { New-Item -ItemType Directory -Force -Path $d | Out-Null }
139
+
140
+ # --- 4. Copy files ---
141
+ Write-Host "[4/19] Copying agent files..." -ForegroundColor Yellow
142
+ Copy-Item "$AgentDir\dist" "$InstallDir\dist" -Recurse -Force
143
+ Copy-Item "$AgentDir\package.json" "$InstallDir\package.json" -Force
144
+ if (Test-Path "$AgentDir\package-lock.json") { Copy-Item "$AgentDir\package-lock.json" "$InstallDir\package-lock.json" -Force }
145
+ Copy-Item "$AgentDir\agent.config.template.json" "$InstallDir\agent.config.template.json" -Force
146
+ if (Test-Path "$AgentDir\public") { Copy-Item "$AgentDir\public" "$InstallDir\public" -Recurse -Force }
147
+ if (Test-Path "$AgentDir\scripts") { Copy-Item "$AgentDir\scripts" "$InstallDir\scripts" -Recurse -Force }
148
+
149
+ # --- 5. Install deps ---
150
+ Write-Host "[5/19] Installing dependencies..." -ForegroundColor Yellow
151
+ Push-Location $InstallDir
152
+ $ErrorActionPreference = "Continue"
153
+ & npm ci --omit=dev --ignore-scripts 2>&1 | Out-Host
154
+ if ($LASTEXITCODE -ne 0) { & npm install --omit=dev --ignore-scripts 2>&1 | Out-Host }
155
+ $ErrorActionPreference = "Stop"
156
+ Pop-Location
157
+
158
+ # --- 6. Generate config ---
159
+ Write-Host "[6/19] Generating config..." -ForegroundColor Yellow
160
+ if ($ShellReplace) {
161
+ & "$ScriptDir\setup.ps1" -Slug $Slug -Server $Server -Timezone $Timezone -InstallDir $InstallDir -ShellMode
162
+ } else {
163
+ & "$ScriptDir\setup.ps1" -Slug $Slug -Server $Server -Timezone $Timezone -InstallDir $InstallDir
164
+ }
165
+
166
+ # --- 7. Fix BOM ---
167
+ Write-Host "[7/19] Fixing config encoding..." -ForegroundColor Yellow
168
+ $configPath = Join-Path $InstallDir "agent.config.json"
169
+ if (-not (Test-Path $configPath)) { Write-Host " FATAL: config not created!" -ForegroundColor Red; exit 1 }
170
+ $raw = [System.IO.File]::ReadAllText($configPath)
171
+ [System.IO.File]::WriteAllText($configPath, $raw.TrimStart([char]0xFEFF), [System.Text.UTF8Encoding]::new($false))
172
+
173
+ # --- 8. Verify config ---
174
+ Write-Host "[8/19] Verifying config..." -ForegroundColor Yellow
175
+ Push-Location $InstallDir
176
+ $ErrorActionPreference = "Continue"
177
+ $jsonCheck = & node -e "try{const c=JSON.parse(require('fs').readFileSync('agent.config.json','utf8'));console.log('OK slug='+c.deviceSlug+' shellMode='+(c.kiosk&&c.kiosk.shellMode||false))}catch(e){console.log('FAIL: '+e.message);process.exit(1)}" 2>&1
178
+ $ErrorActionPreference = "Stop"
179
+ if ($LASTEXITCODE -ne 0) { Write-Host " FATAL: invalid config: $jsonCheck" -ForegroundColor Red; Pop-Location; exit 1 }
180
+ Pop-Location
181
+ Write-Host " $jsonCheck"
182
+
183
+ # --- 9. Download NSSM ---
184
+ Write-Host "[9/19] Setting up NSSM..." -ForegroundColor Yellow
185
+ if (-not (Test-Path $NssmExe)) {
186
+ # Check bundled copy first (fastest, no internet needed)
187
+ $bundled = Join-Path $AgentDir "nssm\nssm.exe"
188
+ if (Test-Path $bundled) {
189
+ Copy-Item $bundled $NssmExe -Force
190
+ Write-Host " Using bundled NSSM"
191
+ } else {
192
+ # Download from multiple sources
193
+ $nssmZip = "$env:TEMP\nssm.zip"
194
+ $downloaded = $false
195
+ $urls = @(
196
+ "https://nssm.cc/release/nssm-2.24.zip",
197
+ "https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip"
198
+ )
199
+ foreach ($url in $urls) {
200
+ if ($downloaded) { break }
201
+ Write-Host " Downloading from $url ..."
202
+ try {
203
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
204
+ Invoke-WebRequest -Uri $url -OutFile $nssmZip -UseBasicParsing -TimeoutSec 60
205
+ if ((Test-Path $nssmZip) -and (Get-Item $nssmZip).Length -gt 10000) {
206
+ $downloaded = $true
207
+ }
208
+ } catch {
209
+ Write-Host " Failed: $_" -ForegroundColor DarkYellow
210
+ }
211
+ }
212
+ if ($downloaded) {
213
+ Expand-Archive -Path $nssmZip -DestinationPath "$env:TEMP\nssm-extract" -Force
214
+ # Find nssm.exe in extracted folder (handles different zip structures)
215
+ $found = Get-ChildItem "$env:TEMP\nssm-extract" -Recurse -Filter "nssm.exe" | Where-Object { $_.DirectoryName -like "*win64*" } | Select-Object -First 1
216
+ if (-not $found) { $found = Get-ChildItem "$env:TEMP\nssm-extract" -Recurse -Filter "nssm.exe" | Select-Object -First 1 }
217
+ if ($found) { Copy-Item $found.FullName $NssmExe -Force }
218
+ Remove-Item $nssmZip -Force -ErrorAction SilentlyContinue
219
+ Remove-Item "$env:TEMP\nssm-extract" -Recurse -Force -ErrorAction SilentlyContinue
220
+ }
221
+ }
222
+ }
223
+ if (-not (Test-Path $NssmExe)) {
224
+ Write-Host ""
225
+ Write-Host " NSSM download failed. Manual fix:" -ForegroundColor Red
226
+ Write-Host " 1. Download nssm-2.24.zip from https://nssm.cc/release/nssm-2.24.zip" -ForegroundColor Yellow
227
+ Write-Host " 2. Extract win64\nssm.exe to: $NssmExe" -ForegroundColor Yellow
228
+ Write-Host " 3. Re-run this script" -ForegroundColor Yellow
229
+ Write-Host ""
230
+ Write-Host " OR bundle it in the repo:" -ForegroundColor Yellow
231
+ Write-Host " Copy nssm.exe to: $AgentDir\nssm\nssm.exe" -ForegroundColor Yellow
232
+ exit 1
233
+ }
234
+ Write-Host " NSSM ready: $NssmExe"
235
+
236
+ # --- 10. Install Windows Service via NSSM ---
237
+ Write-Host "[10/19] Installing Windows Service..." -ForegroundColor Yellow
238
+
239
+ # Clean slate
240
+ $ErrorActionPreference = "Continue"
241
+ & $NssmExe stop $ServiceName 2>$null
242
+ & $NssmExe remove $ServiceName confirm 2>$null
243
+ sc.exe delete $ServiceName 2>$null
244
+ Start-Sleep -Seconds 2
245
+ $ErrorActionPreference = "Stop"
246
+
247
+ $nodePath = (Get-Command node).Source
248
+
249
+ # Install
250
+ & $NssmExe install $ServiceName $nodePath "dist\index.js"
251
+ if ($LASTEXITCODE -ne 0) { Write-Host " FATAL: NSSM install failed!" -ForegroundColor Red; exit 1 }
252
+
253
+ # Configure
254
+ & $NssmExe set $ServiceName AppDirectory $InstallDir
255
+ & $NssmExe set $ServiceName DisplayName "LIGHTMAN Agent"
256
+ & $NssmExe set $ServiceName Description "LIGHTMAN kiosk agent - display management and monitoring"
257
+ & $NssmExe set $ServiceName Start SERVICE_AUTO_START
258
+ & $NssmExe set $ServiceName AppStdout "$LogDir\service-stdout.log"
259
+ & $NssmExe set $ServiceName AppStderr "$LogDir\service-stderr.log"
260
+ & $NssmExe set $ServiceName AppStdoutCreationDisposition 4
261
+ & $NssmExe set $ServiceName AppStderrCreationDisposition 4
262
+ & $NssmExe set $ServiceName AppRotateFiles 1
263
+ & $NssmExe set $ServiceName AppRotateBytes 5242880
264
+ & $NssmExe set $ServiceName AppRestartDelay 10000
265
+ & $NssmExe set $ServiceName AppExit Default Restart
266
+
267
+ # Verify service was created
268
+ Start-Sleep -Seconds 2
269
+ $svcCheck = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
270
+ if (-not $svcCheck) {
271
+ $svcCheck = Get-Service -DisplayName "LIGHTMAN*" -ErrorAction SilentlyContinue | Select-Object -First 1
272
+ }
273
+ if (-not $svcCheck) {
274
+ Write-Host " FATAL: Service was not created!" -ForegroundColor Red
275
+ exit 1
276
+ }
277
+ Write-Host " Service installed: $($svcCheck.Name)" -ForegroundColor Green
278
+
279
+ # Recovery policy
280
+ sc.exe failure $svcCheck.Name reset= 86400 actions= restart/5000/restart/10000/restart/30000 2>$null
281
+
282
+ # --- 11. Start service ---
283
+ Write-Host "[11/19] Starting service..." -ForegroundColor Yellow
284
+ Start-Service -Name $svcCheck.Name -ErrorAction SilentlyContinue
285
+ Start-Sleep -Seconds 5
286
+ $svcCheck.Refresh()
287
+
288
+ if ($svcCheck.Status -eq 'Running') {
289
+ Write-Host " Service is RUNNING" -ForegroundColor Green
290
+ } else {
291
+ Write-Host " Service status: $($svcCheck.Status) - check $LogDir" -ForegroundColor Yellow
292
+ Start-Sleep -Seconds 3
293
+ Start-Service -Name $svcCheck.Name -ErrorAction SilentlyContinue
294
+ }
295
+
296
+ # Wait for port 3403
297
+ $portUp = $false
298
+ for ($i = 0; $i -lt 10; $i++) {
299
+ $ErrorActionPreference = "Continue"
300
+ $n = netstat -an 2>$null | findstr ":3403.*LISTENING" 2>$null
301
+ $ErrorActionPreference = "Stop"
302
+ if ($n) { $portUp = $true; break }
303
+ Start-Sleep -Seconds 2
304
+ }
304
305
  if ($portUp) { Write-Host " Port 3403 LISTENING" -ForegroundColor Green }
305
306
  else { Write-Host " Port 3403 not yet up (may take a moment)" -ForegroundColor Yellow }
306
307
 
@@ -349,238 +350,238 @@ Write-Host " Provisioning/pairing complete" -ForegroundColor Green
349
350
 
350
351
  # --- 12. Firewall ---
351
352
  Write-Host "[12/19] Configuring firewall..." -ForegroundColor Yellow
352
- $ErrorActionPreference = "Continue"
353
- if (-not (Get-NetFirewallRule -DisplayName "LIGHTMAN Agent WebSocket" -ErrorAction SilentlyContinue)) {
354
- New-NetFirewallRule -DisplayName "LIGHTMAN Agent WebSocket" -Direction Outbound -Action Allow -Protocol TCP -RemotePort 3001 -Description "LIGHTMAN Agent" | Out-Null
355
- Write-Host " Created"
356
- } else { Write-Host " Already exists" }
357
-
358
- # ============================================================
359
- # PART 2: KIOSK CONFIGURATION
360
- # ============================================================
361
- $ErrorActionPreference = "Continue"
362
- Write-Host ""
363
- Write-Host "--- Configuring Kiosk Mode ---" -ForegroundColor Cyan
364
- Write-Host ""
365
-
366
- # --- 13. Auto-login ---
367
- Write-Host "[13/19] Enabling auto-login..." -ForegroundColor Yellow
368
- $RegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
369
- $targetUser = Get-LocalUser -Name $Username -ErrorAction SilentlyContinue
370
- $isMsAccount = $targetUser -and $targetUser.PrincipalSource -eq 'MicrosoftAccount'
371
-
372
- if ($isMsAccount) {
373
- $KioskUser = "kiosk"
374
- $existingKiosk = Get-LocalUser -Name $KioskUser -ErrorAction SilentlyContinue
375
- if (-not $existingKiosk) { net user $KioskUser "" /add 2>$null; net localgroup Administrators $KioskUser /add 2>$null }
376
- else { net user $KioskUser "" 2>$null }
377
- $HidePath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"
378
- if (-not (Test-Path $HidePath)) { New-Item -Path $HidePath -Force | Out-Null }
379
- Set-ItemProperty -Path $HidePath -Name $Username -Value 0
380
- $Username = $KioskUser
381
- Write-Host " Created kiosk account, auto-login: $Username" -ForegroundColor Green
382
- } else {
383
- net user $Username "" 2>$null
384
- }
385
-
386
- $PwdLess = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device"
387
- if (Test-Path $PwdLess) { Set-ItemProperty -Path $PwdLess -Name "DevicePasswordLessBuildVersion" -Value 0 }
388
-
389
- $Passport = "HKLM:\SOFTWARE\Policies\Microsoft\PassportForWork"
390
- if (-not (Test-Path $Passport)) { New-Item -Path $Passport -Force | Out-Null }
391
- Set-ItemProperty -Path $Passport -Name "Enabled" -Value 0
392
-
393
- Set-ItemProperty -Path $RegPath -Name "AutoAdminLogon" -Value "1"
394
- Set-ItemProperty -Path $RegPath -Name "DefaultUserName" -Value $Username
395
- Set-ItemProperty -Path $RegPath -Name "DefaultPassword" -Value ""
396
- Set-ItemProperty -Path $RegPath -Name "DefaultDomainName" -Value ""
397
- Set-ItemProperty -Path $RegPath -Name "DisableCAD" -Value 1
398
- Set-ItemProperty -Path $RegPath -Name "AutoRestartShell" -Value 1
399
- Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "DisableAutomaticRestartSignOn" -Value 0
400
- $OOBE = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OOBE"
401
- if (-not (Test-Path $OOBE)) { New-Item -Path $OOBE -Force | Out-Null }
402
- Set-ItemProperty -Path $OOBE -Name "DisablePrivacyExperience" -Value 1
403
- Write-Host " Auto-login enabled for: $Username"
404
-
405
- # --- 14. Lock screen ---
406
- Write-Host "[14/19] Removing lock screen..." -ForegroundColor Yellow
407
- $LP = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization"
408
- if (-not (Test-Path $LP)) { New-Item -Path $LP -Force | Out-Null }
409
- Set-ItemProperty -Path $LP -Name "NoLockScreen" -Value 1
410
-
411
- $SD = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\SessionData"
412
- if (Test-Path $SD) { Set-ItemProperty -Path $SD -Name "AllowLockScreen" -Value 0 -ErrorAction SilentlyContinue }
413
-
414
- $CC = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent"
415
- if (-not (Test-Path $CC)) { New-Item -Path $CC -Force | Out-Null }
416
- Set-ItemProperty -Path $CC -Name "DisableWindowsConsumerFeatures" -Value 1
417
- Set-ItemProperty -Path $CC -Name "DisableCloudOptimizedContent" -Value 1
418
- $CCU = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\CloudContent"
419
- if (-not (Test-Path $CCU)) { New-Item -Path $CCU -Force | Out-Null }
420
- Set-ItemProperty -Path $CCU -Name "DisableWindowsSpotlightFeatures" -Value 1
421
- Set-ItemProperty -Path $CCU -Name "DisableTailoredExperiencesWithDiagnosticData" -Value 1
422
-
423
- Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "EnableFirstLogonAnimation" -Value 0
424
- $SP = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
425
- Set-ItemProperty -Path $SP -Name "DisableLockWorkstation" -Value 1
426
- Set-ItemProperty -Path $SP -Name "HideFastUserSwitching" -Value 1
427
-
428
- $DL = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
429
- if (-not (Test-Path $DL)) { New-Item -Path $DL -Force | Out-Null }
430
- Set-ItemProperty -Path $DL -Name "EnableGoodbye" -Value 0
431
-
432
- $PS = "HKLM:\SOFTWARE\Policies\Microsoft\Power\PowerSettings\0e796bdb-100d-47d6-a2d5-f7d2daa51f51"
433
- if (-not (Test-Path $PS)) { New-Item -Path $PS -Force | Out-Null }
434
- Set-ItemProperty -Path $PS -Name "ACSettingIndex" -Value 0
435
- Set-ItemProperty -Path $PS -Name "DCSettingIndex" -Value 0
436
- powercfg /SETACVALUEINDEX SCHEME_CURRENT SUB_NONE CONSOLELOCK 0 2>&1 | Out-Null
437
- powercfg /SETDCVALUEINDEX SCHEME_CURRENT SUB_NONE CONSOLELOCK 0 2>&1 | Out-Null
438
- powercfg /SETACTIVE SCHEME_CURRENT 2>&1 | Out-Null
439
-
440
- Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name "ScreenSaverIsSecure" -Value "0"
441
- Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name "ScreenSaveActive" -Value "0"
442
- Set-ItemProperty -Path $SP -Name "InactivityTimeoutSecs" -Value 0 -ErrorAction SilentlyContinue
443
- try { Disable-ScheduledTask -TaskName "\Microsoft\Windows\Shell\CreateObjectTask" -ErrorAction SilentlyContinue | Out-Null } catch { }
444
- Write-Host " Lock screen fully disabled"
445
-
446
- # --- 15. Sleep ---
447
- Write-Host "[15/19] Disabling sleep..." -ForegroundColor Yellow
448
- powercfg /change monitor-timeout-ac 0 2>&1 | Out-Null
449
- powercfg /change standby-timeout-ac 0 2>&1 | Out-Null
450
- powercfg /change hibernate-timeout-ac 0 2>&1 | Out-Null
451
-
452
- # --- 16. Harden ---
453
- Write-Host "[16/19] Hardening Windows..." -ForegroundColor Yellow
454
- $WU = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
455
- if (-not (Test-Path $WU)) { New-Item -Path $WU -Force | Out-Null }
456
- Set-ItemProperty -Path $WU -Name "NoAutoRebootWithLoggedOnUsers" -Value 1
457
- Set-ItemProperty -Path $WU -Name "AUOptions" -Value 2
458
- $WUM = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
459
- if (-not (Test-Path $WUM)) { New-Item -Path $WUM -Force | Out-Null }
460
- Set-ItemProperty -Path $WUM -Name "SetAutoRestartNotificationDisable" -Value 1
461
- Set-ItemProperty -Path $WUM -Name "SetActiveHours" -Value 1
462
- Set-ItemProperty -Path $WUM -Name "ActiveHoursStart" -Value 0
463
- Set-ItemProperty -Path $WUM -Name "ActiveHoursEnd" -Value 23
464
-
465
- $NP = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer"
466
- if (-not (Test-Path $NP)) { New-Item -Path $NP -Force | Out-Null }
467
- Set-ItemProperty -Path $NP -Name "DisableNotificationCenter" -Value 1
468
- $TP = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\PushNotifications"
469
- if (-not (Test-Path $TP)) { New-Item -Path $TP -Force | Out-Null }
470
- Set-ItemProperty -Path $TP -Name "ToastEnabled" -Value 0
471
-
472
- $WER = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting"
473
- if (-not (Test-Path $WER)) { New-Item -Path $WER -Force | Out-Null }
474
- Set-ItemProperty -Path $WER -Name "DontShowUI" -Value 1
475
- Set-ItemProperty -Path $WER -Name "Disabled" -Value 1
476
- Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Windows" -Name "ErrorMode" -Value 2 -ErrorAction SilentlyContinue
477
-
478
- $SR = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search"
479
- if (-not (Test-Path $SR)) { New-Item -Path $SR -Force | Out-Null }
480
- Set-ItemProperty -Path $SR -Name "AllowCortana" -Value 0
481
- Write-Host " Done"
482
-
483
- # --- 17. Kiosk Chrome ---
484
- if ($ShellReplace) {
485
- Write-Host "[17/19] SHELL REPLACEMENT..." -ForegroundColor Magenta
486
-
487
- # Copy shell BAT (reads slug from agent.config.json - single source of truth)
488
- $shellSource = Join-Path $ScriptDir "lightman-shell.bat"
489
- $shellTarget = Join-Path $InstallDir "lightman-shell.bat"
490
- if (Test-Path $shellSource) { Copy-Item $shellSource $shellTarget -Force }
491
-
492
- # No sidecar file needed - shell BAT reads directly from agent.config.json
493
-
494
- # Replace shell
495
- $ShellReg = "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon"
496
- if (-not (Test-Path $ShellReg)) { New-Item -Path $ShellReg -Force | Out-Null }
497
- Set-ItemProperty -Path $ShellReg -Name "Shell" -Value """$shellTarget"""
498
- $HKLMShell = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
499
- $orig = (Get-ItemProperty -Path $HKLMShell -Name "Shell" -ErrorAction SilentlyContinue).Shell
500
- if ($orig -and $orig -notlike "*lightman*") { Set-ItemProperty -Path $HKLMShell -Name "Shell_Original" -Value $orig }
501
- Set-ItemProperty -Path $HKLMShell -Name "Shell" -Value """$shellTarget"""
502
-
503
- Write-Host " Shell replaced -> lightman-shell.bat" -ForegroundColor Green
504
- Write-Host " Recovery: scripts\restore-desktop.ps1" -ForegroundColor Yellow
505
-
506
- # Remove kiosk task if exists
507
- $kt = Get-ScheduledTask -TaskName $KioskTask -ErrorAction SilentlyContinue
508
- if ($kt) { Unregister-ScheduledTask -TaskName $KioskTask -Confirm:$false }
509
- } else {
510
- Write-Host "[17/19] Standard mode - kiosk browser task..." -ForegroundColor Yellow
511
- $vbs = Join-Path $ScriptDir "launch-kiosk.vbs"
512
- $vbsT = Join-Path $InstallDir "launch-kiosk.vbs"
513
- if (Test-Path $vbs) { Copy-Item $vbs $vbsT -Force }
514
- $kt = Get-ScheduledTask -TaskName $KioskTask -ErrorAction SilentlyContinue
515
- if ($kt) { Unregister-ScheduledTask -TaskName $KioskTask -Confirm:$false }
516
- $kA = New-ScheduledTaskAction -Execute "wscript.exe" -Argument """$vbsT""" -WorkingDirectory $InstallDir
517
- $kT1 = New-ScheduledTaskTrigger -AtLogOn -User $Username
518
- $kT2 = New-ScheduledTaskTrigger -AtStartup
519
- $kS = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
520
- Register-ScheduledTask -TaskName $KioskTask -Action $kA -Trigger @($kT1,$kT2) -Settings $kS -RunLevel Highest -Description "Chrome kiosk at logon/startup" -Force | Out-Null
521
- Write-Host " Kiosk browser task registered"
522
- }
523
-
524
- # --- 18. Guardian ---
525
- Write-Host "[18/19] Registering Guardian..." -ForegroundColor Yellow
526
- $gSrc = Join-Path $ScriptDir "guardian.ps1"
527
- $gDst = Join-Path $InstallDir "guardian.ps1"
528
- if (Test-Path $gSrc) { Copy-Item $gSrc $gDst -Force }
529
- $gt = Get-ScheduledTask -TaskName $GuardianTask -ErrorAction SilentlyContinue
530
- if ($gt) { Unregister-ScheduledTask -TaskName $GuardianTask -Confirm:$false }
531
- $gA = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File ""$gDst""" -WorkingDirectory $InstallDir
532
- $gT = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 365)
533
- $gS = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 2)
534
- $gP = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
535
- Register-ScheduledTask -TaskName $GuardianTask -Action $gA -Trigger $gT -Settings $gS -Principal $gP -Description "LIGHTMAN health check every 5 min" -Force | Out-Null
536
-
537
- foreach ($task in @("\Microsoft\Windows\UpdateOrchestrator\Reboot","\Microsoft\Windows\UpdateOrchestrator\Schedule Retry Scan","\Microsoft\Windows\WindowsUpdate\Scheduled Start")) {
538
- try { Disable-ScheduledTask -TaskName $task -ErrorAction SilentlyContinue | Out-Null } catch { }
539
- }
540
- Write-Host " Guardian registered"
541
-
542
- # --- 19. Final verification ---
543
- Write-Host "[19/19] Verification..." -ForegroundColor Yellow
544
- Start-Sleep -Seconds 3
545
-
546
- $finalSvc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
547
- if (-not $finalSvc) { $finalSvc = Get-Service -DisplayName "LIGHTMAN*" -ErrorAction SilentlyContinue | Select-Object -First 1 }
548
- $svcStatus = if ($finalSvc) { "$($finalSvc.Status)" } else { "NOT FOUND" }
549
-
550
- $cfgOk = $false
551
- try {
552
- Push-Location $InstallDir
553
- $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
554
- $cfgData = $cfgResult | ConvertFrom-Json
555
- Pop-Location
556
- $cfgOk = $true
557
- } catch { Pop-Location }
558
-
559
- Write-Host ""
560
- Write-Host "=============================================" -ForegroundColor Green
561
- Write-Host " INSTALLATION COMPLETE" -ForegroundColor Green
562
- Write-Host "=============================================" -ForegroundColor Green
563
- Write-Host ""
564
- Write-Host " Slug : $Slug"
565
- Write-Host " Server : $Server"
566
- Write-Host " Install : $InstallDir"
567
- Write-Host " Logs : $LogDir"
568
- Write-Host " User : $Username"
569
- Write-Host ""
570
- Write-Host " Service : $svcStatus" -ForegroundColor $(if ($svcStatus -eq 'Running') { 'Green' } else { 'Red' })
571
- if ($cfgOk) {
572
- Write-Host " Config slug: $($cfgData.slug)" -ForegroundColor $(if ($cfgData.slug -eq $Slug) { 'Green' } else { 'Red' })
573
- Write-Host " Shell mode : $($cfgData.shell)" -ForegroundColor $(if ($cfgData.shell -eq $ShellReplace.IsPresent) { 'Green' } else { 'Red' })
574
- }
575
- Write-Host ""
576
- Write-Host " Manage:" -ForegroundColor DarkGray
577
- Write-Host " $NssmExe stop $ServiceName" -ForegroundColor DarkGray
578
- Write-Host " $NssmExe start $ServiceName" -ForegroundColor DarkGray
579
- Write-Host " $NssmExe restart $ServiceName" -ForegroundColor DarkGray
580
- Write-Host ""
581
- Write-Host " BIOS (manual):" -ForegroundColor Red
582
- Write-Host " After Power Loss = Power On" -ForegroundColor Red
583
- Write-Host " Wake-on-LAN = Enabled" -ForegroundColor Red
584
- Write-Host ""
585
- Write-Host " REBOOT NOW: Restart-Computer" -ForegroundColor Yellow
586
- Write-Host ""
353
+ $ErrorActionPreference = "Continue"
354
+ if (-not (Get-NetFirewallRule -DisplayName "LIGHTMAN Agent WebSocket" -ErrorAction SilentlyContinue)) {
355
+ New-NetFirewallRule -DisplayName "LIGHTMAN Agent WebSocket" -Direction Outbound -Action Allow -Protocol TCP -RemotePort 3001 -Description "LIGHTMAN Agent" | Out-Null
356
+ Write-Host " Created"
357
+ } else { Write-Host " Already exists" }
358
+
359
+ # ============================================================
360
+ # PART 2: KIOSK CONFIGURATION
361
+ # ============================================================
362
+ $ErrorActionPreference = "Continue"
363
+ Write-Host ""
364
+ Write-Host "--- Configuring Kiosk Mode ---" -ForegroundColor Cyan
365
+ Write-Host ""
366
+
367
+ # --- 13. Auto-login ---
368
+ Write-Host "[13/19] Enabling auto-login..." -ForegroundColor Yellow
369
+ $RegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
370
+ $targetUser = Get-LocalUser -Name $Username -ErrorAction SilentlyContinue
371
+ $isMsAccount = $targetUser -and $targetUser.PrincipalSource -eq 'MicrosoftAccount'
372
+
373
+ if ($isMsAccount) {
374
+ $KioskUser = "kiosk"
375
+ $existingKiosk = Get-LocalUser -Name $KioskUser -ErrorAction SilentlyContinue
376
+ if (-not $existingKiosk) { net user $KioskUser "" /add 2>$null; net localgroup Administrators $KioskUser /add 2>$null }
377
+ else { net user $KioskUser "" 2>$null }
378
+ $HidePath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"
379
+ if (-not (Test-Path $HidePath)) { New-Item -Path $HidePath -Force | Out-Null }
380
+ Set-ItemProperty -Path $HidePath -Name $Username -Value 0
381
+ $Username = $KioskUser
382
+ Write-Host " Created kiosk account, auto-login: $Username" -ForegroundColor Green
383
+ } else {
384
+ net user $Username "" 2>$null
385
+ }
386
+
387
+ $PwdLess = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device"
388
+ if (Test-Path $PwdLess) { Set-ItemProperty -Path $PwdLess -Name "DevicePasswordLessBuildVersion" -Value 0 }
389
+
390
+ $Passport = "HKLM:\SOFTWARE\Policies\Microsoft\PassportForWork"
391
+ if (-not (Test-Path $Passport)) { New-Item -Path $Passport -Force | Out-Null }
392
+ Set-ItemProperty -Path $Passport -Name "Enabled" -Value 0
393
+
394
+ Set-ItemProperty -Path $RegPath -Name "AutoAdminLogon" -Value "1"
395
+ Set-ItemProperty -Path $RegPath -Name "DefaultUserName" -Value $Username
396
+ Set-ItemProperty -Path $RegPath -Name "DefaultPassword" -Value ""
397
+ Set-ItemProperty -Path $RegPath -Name "DefaultDomainName" -Value ""
398
+ Set-ItemProperty -Path $RegPath -Name "DisableCAD" -Value 1
399
+ Set-ItemProperty -Path $RegPath -Name "AutoRestartShell" -Value 1
400
+ Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "DisableAutomaticRestartSignOn" -Value 0
401
+ $OOBE = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OOBE"
402
+ if (-not (Test-Path $OOBE)) { New-Item -Path $OOBE -Force | Out-Null }
403
+ Set-ItemProperty -Path $OOBE -Name "DisablePrivacyExperience" -Value 1
404
+ Write-Host " Auto-login enabled for: $Username"
405
+
406
+ # --- 14. Lock screen ---
407
+ Write-Host "[14/19] Removing lock screen..." -ForegroundColor Yellow
408
+ $LP = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization"
409
+ if (-not (Test-Path $LP)) { New-Item -Path $LP -Force | Out-Null }
410
+ Set-ItemProperty -Path $LP -Name "NoLockScreen" -Value 1
411
+
412
+ $SD = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\SessionData"
413
+ if (Test-Path $SD) { Set-ItemProperty -Path $SD -Name "AllowLockScreen" -Value 0 -ErrorAction SilentlyContinue }
414
+
415
+ $CC = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent"
416
+ if (-not (Test-Path $CC)) { New-Item -Path $CC -Force | Out-Null }
417
+ Set-ItemProperty -Path $CC -Name "DisableWindowsConsumerFeatures" -Value 1
418
+ Set-ItemProperty -Path $CC -Name "DisableCloudOptimizedContent" -Value 1
419
+ $CCU = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\CloudContent"
420
+ if (-not (Test-Path $CCU)) { New-Item -Path $CCU -Force | Out-Null }
421
+ Set-ItemProperty -Path $CCU -Name "DisableWindowsSpotlightFeatures" -Value 1
422
+ Set-ItemProperty -Path $CCU -Name "DisableTailoredExperiencesWithDiagnosticData" -Value 1
423
+
424
+ Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "EnableFirstLogonAnimation" -Value 0
425
+ $SP = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
426
+ Set-ItemProperty -Path $SP -Name "DisableLockWorkstation" -Value 1
427
+ Set-ItemProperty -Path $SP -Name "HideFastUserSwitching" -Value 1
428
+
429
+ $DL = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
430
+ if (-not (Test-Path $DL)) { New-Item -Path $DL -Force | Out-Null }
431
+ Set-ItemProperty -Path $DL -Name "EnableGoodbye" -Value 0
432
+
433
+ $PS = "HKLM:\SOFTWARE\Policies\Microsoft\Power\PowerSettings\0e796bdb-100d-47d6-a2d5-f7d2daa51f51"
434
+ if (-not (Test-Path $PS)) { New-Item -Path $PS -Force | Out-Null }
435
+ Set-ItemProperty -Path $PS -Name "ACSettingIndex" -Value 0
436
+ Set-ItemProperty -Path $PS -Name "DCSettingIndex" -Value 0
437
+ powercfg /SETACVALUEINDEX SCHEME_CURRENT SUB_NONE CONSOLELOCK 0 2>&1 | Out-Null
438
+ powercfg /SETDCVALUEINDEX SCHEME_CURRENT SUB_NONE CONSOLELOCK 0 2>&1 | Out-Null
439
+ powercfg /SETACTIVE SCHEME_CURRENT 2>&1 | Out-Null
440
+
441
+ Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name "ScreenSaverIsSecure" -Value "0"
442
+ Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name "ScreenSaveActive" -Value "0"
443
+ Set-ItemProperty -Path $SP -Name "InactivityTimeoutSecs" -Value 0 -ErrorAction SilentlyContinue
444
+ try { Disable-ScheduledTask -TaskName "\Microsoft\Windows\Shell\CreateObjectTask" -ErrorAction SilentlyContinue | Out-Null } catch { }
445
+ Write-Host " Lock screen fully disabled"
446
+
447
+ # --- 15. Sleep ---
448
+ Write-Host "[15/19] Disabling sleep..." -ForegroundColor Yellow
449
+ powercfg /change monitor-timeout-ac 0 2>&1 | Out-Null
450
+ powercfg /change standby-timeout-ac 0 2>&1 | Out-Null
451
+ powercfg /change hibernate-timeout-ac 0 2>&1 | Out-Null
452
+
453
+ # --- 16. Harden ---
454
+ Write-Host "[16/19] Hardening Windows..." -ForegroundColor Yellow
455
+ $WU = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
456
+ if (-not (Test-Path $WU)) { New-Item -Path $WU -Force | Out-Null }
457
+ Set-ItemProperty -Path $WU -Name "NoAutoRebootWithLoggedOnUsers" -Value 1
458
+ Set-ItemProperty -Path $WU -Name "AUOptions" -Value 2
459
+ $WUM = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
460
+ if (-not (Test-Path $WUM)) { New-Item -Path $WUM -Force | Out-Null }
461
+ Set-ItemProperty -Path $WUM -Name "SetAutoRestartNotificationDisable" -Value 1
462
+ Set-ItemProperty -Path $WUM -Name "SetActiveHours" -Value 1
463
+ Set-ItemProperty -Path $WUM -Name "ActiveHoursStart" -Value 0
464
+ Set-ItemProperty -Path $WUM -Name "ActiveHoursEnd" -Value 23
465
+
466
+ $NP = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\Explorer"
467
+ if (-not (Test-Path $NP)) { New-Item -Path $NP -Force | Out-Null }
468
+ Set-ItemProperty -Path $NP -Name "DisableNotificationCenter" -Value 1
469
+ $TP = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\PushNotifications"
470
+ if (-not (Test-Path $TP)) { New-Item -Path $TP -Force | Out-Null }
471
+ Set-ItemProperty -Path $TP -Name "ToastEnabled" -Value 0
472
+
473
+ $WER = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting"
474
+ if (-not (Test-Path $WER)) { New-Item -Path $WER -Force | Out-Null }
475
+ Set-ItemProperty -Path $WER -Name "DontShowUI" -Value 1
476
+ Set-ItemProperty -Path $WER -Name "Disabled" -Value 1
477
+ Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Windows" -Name "ErrorMode" -Value 2 -ErrorAction SilentlyContinue
478
+
479
+ $SR = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search"
480
+ if (-not (Test-Path $SR)) { New-Item -Path $SR -Force | Out-Null }
481
+ Set-ItemProperty -Path $SR -Name "AllowCortana" -Value 0
482
+ Write-Host " Done"
483
+
484
+ # --- 17. Kiosk Chrome ---
485
+ if ($ShellReplace) {
486
+ Write-Host "[17/19] SHELL REPLACEMENT..." -ForegroundColor Magenta
487
+
488
+ # Copy shell BAT (reads slug from agent.config.json - single source of truth)
489
+ $shellSource = Join-Path $ScriptDir "lightman-shell.bat"
490
+ $shellTarget = Join-Path $InstallDir "lightman-shell.bat"
491
+ if (Test-Path $shellSource) { Copy-Item $shellSource $shellTarget -Force }
492
+
493
+ # No sidecar file needed - shell BAT reads directly from agent.config.json
494
+
495
+ # Replace shell
496
+ $ShellReg = "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon"
497
+ if (-not (Test-Path $ShellReg)) { New-Item -Path $ShellReg -Force | Out-Null }
498
+ Set-ItemProperty -Path $ShellReg -Name "Shell" -Value """$shellTarget"""
499
+ $HKLMShell = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
500
+ $orig = (Get-ItemProperty -Path $HKLMShell -Name "Shell" -ErrorAction SilentlyContinue).Shell
501
+ if ($orig -and $orig -notlike "*lightman*") { Set-ItemProperty -Path $HKLMShell -Name "Shell_Original" -Value $orig }
502
+ Set-ItemProperty -Path $HKLMShell -Name "Shell" -Value """$shellTarget"""
503
+
504
+ Write-Host " Shell replaced -> lightman-shell.bat" -ForegroundColor Green
505
+ Write-Host " Recovery: scripts\restore-desktop.ps1" -ForegroundColor Yellow
506
+
507
+ # Remove kiosk task if exists
508
+ $kt = Get-ScheduledTask -TaskName $KioskTask -ErrorAction SilentlyContinue
509
+ if ($kt) { Unregister-ScheduledTask -TaskName $KioskTask -Confirm:$false }
510
+ } else {
511
+ Write-Host "[17/19] Standard mode - kiosk browser task..." -ForegroundColor Yellow
512
+ $vbs = Join-Path $ScriptDir "launch-kiosk.vbs"
513
+ $vbsT = Join-Path $InstallDir "launch-kiosk.vbs"
514
+ if (Test-Path $vbs) { Copy-Item $vbs $vbsT -Force }
515
+ $kt = Get-ScheduledTask -TaskName $KioskTask -ErrorAction SilentlyContinue
516
+ if ($kt) { Unregister-ScheduledTask -TaskName $KioskTask -Confirm:$false }
517
+ $kA = New-ScheduledTaskAction -Execute "wscript.exe" -Argument """$vbsT""" -WorkingDirectory $InstallDir
518
+ $kT1 = New-ScheduledTaskTrigger -AtLogOn -User $Username
519
+ $kT2 = New-ScheduledTaskTrigger -AtStartup
520
+ $kS = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
521
+ Register-ScheduledTask -TaskName $KioskTask -Action $kA -Trigger @($kT1,$kT2) -Settings $kS -RunLevel Highest -Description "Chrome kiosk at logon/startup" -Force | Out-Null
522
+ Write-Host " Kiosk browser task registered"
523
+ }
524
+
525
+ # --- 18. Guardian ---
526
+ Write-Host "[18/19] Registering Guardian..." -ForegroundColor Yellow
527
+ $gSrc = Join-Path $ScriptDir "guardian.ps1"
528
+ $gDst = Join-Path $InstallDir "guardian.ps1"
529
+ if (Test-Path $gSrc) { Copy-Item $gSrc $gDst -Force }
530
+ $gt = Get-ScheduledTask -TaskName $GuardianTask -ErrorAction SilentlyContinue
531
+ if ($gt) { Unregister-ScheduledTask -TaskName $GuardianTask -Confirm:$false }
532
+ $gA = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File ""$gDst""" -WorkingDirectory $InstallDir
533
+ $gT = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 365)
534
+ $gS = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 2)
535
+ $gP = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
536
+ Register-ScheduledTask -TaskName $GuardianTask -Action $gA -Trigger $gT -Settings $gS -Principal $gP -Description "LIGHTMAN health check every 5 min" -Force | Out-Null
537
+
538
+ foreach ($task in @("\Microsoft\Windows\UpdateOrchestrator\Reboot","\Microsoft\Windows\UpdateOrchestrator\Schedule Retry Scan","\Microsoft\Windows\WindowsUpdate\Scheduled Start")) {
539
+ try { Disable-ScheduledTask -TaskName $task -ErrorAction SilentlyContinue | Out-Null } catch { }
540
+ }
541
+ Write-Host " Guardian registered"
542
+
543
+ # --- 19. Final verification ---
544
+ Write-Host "[19/19] Verification..." -ForegroundColor Yellow
545
+ Start-Sleep -Seconds 3
546
+
547
+ $finalSvc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
548
+ if (-not $finalSvc) { $finalSvc = Get-Service -DisplayName "LIGHTMAN*" -ErrorAction SilentlyContinue | Select-Object -First 1 }
549
+ $svcStatus = if ($finalSvc) { "$($finalSvc.Status)" } else { "NOT FOUND" }
550
+
551
+ $cfgOk = $false
552
+ try {
553
+ Push-Location $InstallDir
554
+ $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
555
+ $cfgData = $cfgResult | ConvertFrom-Json
556
+ Pop-Location
557
+ $cfgOk = $true
558
+ } catch { Pop-Location }
559
+
560
+ Write-Host ""
561
+ Write-Host "=============================================" -ForegroundColor Green
562
+ Write-Host " INSTALLATION COMPLETE" -ForegroundColor Green
563
+ Write-Host "=============================================" -ForegroundColor Green
564
+ Write-Host ""
565
+ Write-Host " Slug : $Slug"
566
+ Write-Host " Server : $Server"
567
+ Write-Host " Install : $InstallDir"
568
+ Write-Host " Logs : $LogDir"
569
+ Write-Host " User : $Username"
570
+ Write-Host ""
571
+ Write-Host " Service : $svcStatus" -ForegroundColor $(if ($svcStatus -eq 'Running') { 'Green' } else { 'Red' })
572
+ if ($cfgOk) {
573
+ Write-Host " Config slug: $($cfgData.slug)" -ForegroundColor $(if ($cfgData.slug -eq $Slug) { 'Green' } else { 'Red' })
574
+ Write-Host " Shell mode : $($cfgData.shell)" -ForegroundColor $(if ($cfgData.shell -eq $ShellReplace.IsPresent) { 'Green' } else { 'Red' })
575
+ }
576
+ Write-Host ""
577
+ Write-Host " Manage:" -ForegroundColor DarkGray
578
+ Write-Host " $NssmExe stop $ServiceName" -ForegroundColor DarkGray
579
+ Write-Host " $NssmExe start $ServiceName" -ForegroundColor DarkGray
580
+ Write-Host " $NssmExe restart $ServiceName" -ForegroundColor DarkGray
581
+ Write-Host ""
582
+ Write-Host " BIOS (manual):" -ForegroundColor Red
583
+ Write-Host " After Power Loss = Power On" -ForegroundColor Red
584
+ Write-Host " Wake-on-LAN = Enabled" -ForegroundColor Red
585
+ Write-Host ""
586
+ Write-Host " REBOOT NOW: Restart-Computer" -ForegroundColor Yellow
587
+ Write-Host ""