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