lightman-agent 1.0.22 → 1.0.24
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 +53 -87
- package/package.json +1 -1
- package/scripts/guardian.ps1 +25 -6
- package/scripts/install-linux.sh +4 -4
- package/scripts/install-windows.ps1 +165 -58
- 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 +214 -156
- 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,51 +177,25 @@ async function promptServer(defaultServer) {
|
|
|
180
177
|
}
|
|
181
178
|
}
|
|
182
179
|
|
|
183
|
-
function resolveInstallScript() {
|
|
184
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
185
|
-
const packaged = resolve(here, '../scripts/install-windows.ps1');
|
|
186
|
-
const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
|
|
187
|
-
if (existsSync(packaged)) return packaged;
|
|
188
|
-
if (existsSync(localRepo)) return localRepo;
|
|
189
|
-
throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
203
|
-
const bundledPkg = safeReadJson(resolve(here, '../package.json')) || {};
|
|
204
|
-
const installedPkg = safeReadJson(INSTALL_PACKAGE_PATH) || {};
|
|
205
|
-
|
|
206
|
-
const cliVersion = typeof bundledPkg.version === 'string' ? bundledPkg.version : 'unknown';
|
|
207
|
-
const installedVersion = typeof installedPkg.version === 'string' ? installedPkg.version : 'not-installed';
|
|
208
|
-
|
|
209
|
-
console.log(`lightman-agent cli: ${cliVersion}`);
|
|
210
|
-
console.log(`lightman-agent installed: ${installedVersion}`);
|
|
211
|
-
console.log(`node: ${process.version}`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
|
|
215
|
-
console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
|
|
216
|
-
const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
|
|
217
|
-
if (timezone) {
|
|
218
|
-
args.push('-Timezone', timezone);
|
|
219
|
-
}
|
|
220
|
-
if (
|
|
221
|
-
Number.isInteger(pairingTimeoutSeconds)
|
|
222
|
-
&& pairingTimeoutSeconds >= 0
|
|
223
|
-
&& scriptSupportsPairingTimeout(scriptPath)
|
|
224
|
-
) {
|
|
225
|
-
args.push('-PairingTimeoutSeconds', String(pairingTimeoutSeconds));
|
|
226
|
-
}
|
|
227
|
-
runOrFail('powershell.exe', args);
|
|
180
|
+
function resolveInstallScript() {
|
|
181
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
182
|
+
const packaged = resolve(here, '../scripts/install-windows.ps1');
|
|
183
|
+
const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
|
|
184
|
+
if (existsSync(packaged)) return packaged;
|
|
185
|
+
if (existsSync(localRepo)) return localRepo;
|
|
186
|
+
throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
|
|
190
|
+
console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
|
|
191
|
+
const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
|
|
192
|
+
if (timezone) {
|
|
193
|
+
args.push('-Timezone', timezone);
|
|
194
|
+
}
|
|
195
|
+
if (Number.isInteger(pairingTimeoutSeconds) && pairingTimeoutSeconds >= 0) {
|
|
196
|
+
args.push('-PairingTimeoutSeconds', String(pairingTimeoutSeconds));
|
|
197
|
+
}
|
|
198
|
+
runOrFail('powershell.exe', args);
|
|
228
199
|
|
|
229
200
|
if (!noRestart) {
|
|
230
201
|
console.log('Installation completed. Rebooting now...');
|
|
@@ -291,24 +262,19 @@ async function runUpdate(opts) {
|
|
|
291
262
|
installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
|
|
292
263
|
}
|
|
293
264
|
|
|
294
|
-
async function main() {
|
|
295
|
-
const [, , commandRaw, ...rest] = process.argv;
|
|
296
|
-
const command = commandRaw || '';
|
|
297
|
-
const opts = parseArgs(rest);
|
|
298
|
-
|
|
299
|
-
if (!command || opts.help || command === 'help' || command === '--help' || command === '-h') {
|
|
300
|
-
printUsage();
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (command === '
|
|
305
|
-
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (command === 'install' || command === 'setup') {
|
|
310
|
-
await runInstall(opts);
|
|
311
|
-
return;
|
|
265
|
+
async function main() {
|
|
266
|
+
const [, , commandRaw, ...rest] = process.argv;
|
|
267
|
+
const command = commandRaw || '';
|
|
268
|
+
const opts = parseArgs(rest);
|
|
269
|
+
|
|
270
|
+
if (!command || opts.help || command === 'help' || command === '--help' || command === '-h') {
|
|
271
|
+
printUsage();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (command === 'install' || command === 'setup') {
|
|
276
|
+
await runInstall(opts);
|
|
277
|
+
return;
|
|
312
278
|
}
|
|
313
279
|
if (command === 'update') {
|
|
314
280
|
await runUpdate(opts);
|
package/package.json
CHANGED
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,20 +3,20 @@
|
|
|
3
3
|
# Cleans up any previous installation automatically before installing.
|
|
4
4
|
#
|
|
5
5
|
# Run as Administrator:
|
|
6
|
-
# powershell -ExecutionPolicy Bypass -File install-windows.ps1 -Slug "F-AV01" -Server "http://192.168.
|
|
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
|
-
param(
|
|
13
|
-
[Parameter(Mandatory=$true)] [string]$Slug,
|
|
14
|
-
[Parameter(Mandatory=$true)] [string]$Server,
|
|
15
|
-
[string]$Timezone = "Asia/Kolkata",
|
|
16
|
-
[string]$Username = "",
|
|
17
|
-
[switch]$ShellReplace = $false,
|
|
18
|
-
[int]$PairingTimeoutSeconds = 900
|
|
19
|
-
)
|
|
12
|
+
param(
|
|
13
|
+
[Parameter(Mandatory=$true)] [string]$Slug,
|
|
14
|
+
[Parameter(Mandatory=$true)] [string]$Server,
|
|
15
|
+
[string]$Timezone = "Asia/Kolkata",
|
|
16
|
+
[string]$Username = "",
|
|
17
|
+
[switch]$ShellReplace = $false,
|
|
18
|
+
[int]$PairingTimeoutSeconds = 900
|
|
19
|
+
)
|
|
20
20
|
|
|
21
21
|
$ErrorActionPreference = "Stop"
|
|
22
22
|
|
|
@@ -26,38 +26,88 @@ $ChromeData = "C:\ProgramData\Lightman\chrome-kiosk"
|
|
|
26
26
|
$NssmDir = "C:\ProgramData\Lightman\nssm"
|
|
27
27
|
$NssmExe = "$NssmDir\nssm.exe"
|
|
28
28
|
$ServiceName = "LightmanAgent"
|
|
29
|
-
$GuardianTask = "LIGHTMAN Guardian"
|
|
30
|
-
$KioskTask = "LIGHTMAN Kiosk Browser"
|
|
31
|
-
$AgentTask = "LIGHTMAN Agent"
|
|
32
|
-
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
33
|
-
$AgentDir = Split-Path -Parent $ScriptDir
|
|
34
|
-
|
|
35
|
-
function Ensure-PathContains {
|
|
36
|
-
param(
|
|
37
|
-
[Parameter(Mandatory=$true)][ValidateSet('Machine','User')] [string]$Scope,
|
|
38
|
-
[Parameter(Mandatory=$true)] [string]$Entry
|
|
39
|
-
)
|
|
40
|
-
$current = [System.Environment]::GetEnvironmentVariable("Path", $Scope)
|
|
41
|
-
if (-not $current) { $current = "" }
|
|
42
|
-
$parts = $current -split ';' | Where-Object { $_ -and $_.Trim() -ne "" }
|
|
43
|
-
$normalizedEntry = $Entry.TrimEnd('\')
|
|
44
|
-
$exists = $false
|
|
45
|
-
foreach ($p in $parts) {
|
|
46
|
-
if ($p.TrimEnd('\') -ieq $normalizedEntry) {
|
|
47
|
-
$exists = $true
|
|
48
|
-
break
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (-not $exists) {
|
|
52
|
-
$newPath = if ($current -and $current.Trim() -ne "") { "$current;$Entry" } else { $Entry }
|
|
53
|
-
[System.Environment]::SetEnvironmentVariable("Path", $newPath, $Scope)
|
|
54
|
-
return $true
|
|
55
|
-
}
|
|
56
|
-
return $false
|
|
57
|
-
}
|
|
29
|
+
$GuardianTask = "LIGHTMAN Guardian"
|
|
30
|
+
$KioskTask = "LIGHTMAN Kiosk Browser"
|
|
31
|
+
$AgentTask = "LIGHTMAN Agent"
|
|
32
|
+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
33
|
+
$AgentDir = Split-Path -Parent $ScriptDir
|
|
58
34
|
|
|
59
35
|
if (-not $Username) { $Username = $env:USERNAME }
|
|
60
36
|
|
|
37
|
+
function Get-ChromePath {
|
|
38
|
+
$candidates = @(
|
|
39
|
+
"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
|
40
|
+
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
|
|
41
|
+
(Join-Path $env:LOCALAPPDATA "Google\Chrome\Application\chrome.exe")
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
foreach ($candidate in $candidates) {
|
|
45
|
+
if ($candidate -and (Test-Path $candidate)) {
|
|
46
|
+
return $candidate
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return $null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function Install-ChromeFromMsi {
|
|
54
|
+
$is64Bit = [Environment]::Is64BitOperatingSystem
|
|
55
|
+
$downloadUrl = if ($is64Bit) {
|
|
56
|
+
"https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi"
|
|
57
|
+
} else {
|
|
58
|
+
"https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise.msi"
|
|
59
|
+
}
|
|
60
|
+
$installerName = if ($is64Bit) { "googlechromestandaloneenterprise64.msi" } else { "googlechromestandaloneenterprise.msi" }
|
|
61
|
+
$installerPath = Join-Path $env:TEMP $installerName
|
|
62
|
+
|
|
63
|
+
Write-Host " Chrome not found. Downloading official Google Chrome MSI..." -ForegroundColor Yellow
|
|
64
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
65
|
+
Invoke-WebRequest -Uri $downloadUrl -OutFile $installerPath -UseBasicParsing -TimeoutSec 180
|
|
66
|
+
|
|
67
|
+
if (-not (Test-Path $installerPath)) {
|
|
68
|
+
throw "Chrome MSI was not downloaded."
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
$msiArgs = "/i `"$installerPath`" /qn /norestart"
|
|
72
|
+
Start-Process msiexec.exe -ArgumentList $msiArgs -Wait -NoNewWindow
|
|
73
|
+
Remove-Item $installerPath -Force -ErrorAction SilentlyContinue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function Install-ChromeFromWinget {
|
|
77
|
+
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
|
78
|
+
throw "winget is not available."
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Write-Host " MSI install failed. Trying winget for Google Chrome..." -ForegroundColor Yellow
|
|
82
|
+
& winget install --id Google.Chrome --exact --silent --accept-package-agreements --accept-source-agreements 2>&1 | Out-Host
|
|
83
|
+
if ($LASTEXITCODE -ne 0) {
|
|
84
|
+
throw "winget install failed with exit code $LASTEXITCODE."
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function Ensure-ChromeInstalled {
|
|
89
|
+
$existingChrome = Get-ChromePath
|
|
90
|
+
if ($existingChrome) {
|
|
91
|
+
Write-Host " Found Chrome: $existingChrome" -ForegroundColor Green
|
|
92
|
+
return $existingChrome
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
Install-ChromeFromMsi
|
|
97
|
+
} catch {
|
|
98
|
+
Write-Host " Chrome MSI install failed: $($_.Exception.Message)" -ForegroundColor DarkYellow
|
|
99
|
+
Install-ChromeFromWinget
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
$installedChrome = Get-ChromePath
|
|
103
|
+
if (-not $installedChrome) {
|
|
104
|
+
throw "Chrome install did not produce chrome.exe."
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Write-Host " Chrome installed: $installedChrome" -ForegroundColor Green
|
|
108
|
+
return $installedChrome
|
|
109
|
+
}
|
|
110
|
+
|
|
61
111
|
Write-Host ""
|
|
62
112
|
Write-Host "=============================================" -ForegroundColor Cyan
|
|
63
113
|
Write-Host " LIGHTMAN Agent - Complete Windows Installer" -ForegroundColor Cyan
|
|
@@ -94,9 +144,23 @@ foreach ($tn in @($AgentTask, $KioskTask, $GuardianTask)) {
|
|
|
94
144
|
}
|
|
95
145
|
|
|
96
146
|
# Kill processes
|
|
97
|
-
Write-Host "[0c] Killing node.exe and
|
|
98
|
-
|
|
147
|
+
Write-Host "[0c] Killing node.exe and kiosk browser..." -ForegroundColor Yellow
|
|
148
|
+
# IMPORTANT:
|
|
149
|
+
# If install-windows.ps1 is launched from the npm CLI wrapper (node.exe),
|
|
150
|
+
# killing all node.exe would terminate the installer mid-run.
|
|
151
|
+
$parentPid = $null
|
|
152
|
+
try {
|
|
153
|
+
$parentPid = (Get-CimInstance Win32_Process -Filter "ProcessId=$PID" -ErrorAction SilentlyContinue).ParentProcessId
|
|
154
|
+
} catch { }
|
|
155
|
+
Get-Process -Name "node" -ErrorAction SilentlyContinue | ForEach-Object {
|
|
156
|
+
if ($parentPid -and $_.Id -eq $parentPid) {
|
|
157
|
+
Write-Host " Keeping installer parent node.exe (PID $($_.Id))" -ForegroundColor DarkGray
|
|
158
|
+
} else {
|
|
159
|
+
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
|
|
160
|
+
}
|
|
161
|
+
}
|
|
99
162
|
Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
163
|
+
Get-Process -Name "msedge" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
100
164
|
Start-Sleep -Seconds 2
|
|
101
165
|
|
|
102
166
|
# Remove old files (keep NSSM and logs)
|
|
@@ -116,9 +180,9 @@ Write-Host ""
|
|
|
116
180
|
# PART 1: BUILD & INSTALL
|
|
117
181
|
# ============================================================
|
|
118
182
|
|
|
119
|
-
# --- 1. Node.js ---
|
|
120
|
-
Write-Host "[1/19] Checking Node.js..." -ForegroundColor Yellow
|
|
121
|
-
try {
|
|
183
|
+
# --- 1. Node.js ---
|
|
184
|
+
Write-Host "[1/19] Checking Node.js..." -ForegroundColor Yellow
|
|
185
|
+
try {
|
|
122
186
|
$nodeVersion = (node -v) -replace 'v', ''
|
|
123
187
|
if ([int]($nodeVersion.Split('.')[0]) -lt 20) { throw "old" }
|
|
124
188
|
Write-Host " Found Node.js v$nodeVersion"
|
|
@@ -131,19 +195,7 @@ try {
|
|
|
131
195
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
|
132
196
|
if (-not (Get-Command node -ErrorAction SilentlyContinue)) { Write-Host " FATAL: Node.js install failed!" -ForegroundColor Red; exit 1 }
|
|
133
197
|
Write-Host " Node.js installed" -ForegroundColor Green
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
# Always ensure Node path remains available after kiosk conversion
|
|
137
|
-
$nodePath = (Get-Command node).Source
|
|
138
|
-
$nodeDir = Split-Path -Parent $nodePath
|
|
139
|
-
$addedMachine = Ensure-PathContains -Scope "Machine" -Entry $nodeDir
|
|
140
|
-
$addedUser = Ensure-PathContains -Scope "User" -Entry $nodeDir
|
|
141
|
-
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
|
142
|
-
if ($addedMachine -or $addedUser) {
|
|
143
|
-
Write-Host " Ensured Node.js path in PATH: $nodeDir" -ForegroundColor Green
|
|
144
|
-
} else {
|
|
145
|
-
Write-Host " Node.js path already present in PATH"
|
|
146
|
-
}
|
|
198
|
+
}
|
|
147
199
|
|
|
148
200
|
# --- 2. Build ---
|
|
149
201
|
Write-Host "[2/19] Building agent..." -ForegroundColor Yellow
|
|
@@ -167,6 +219,7 @@ Copy-Item "$AgentDir\package.json" "$InstallDir\package.json" -Force
|
|
|
167
219
|
if (Test-Path "$AgentDir\package-lock.json") { Copy-Item "$AgentDir\package-lock.json" "$InstallDir\package-lock.json" -Force }
|
|
168
220
|
Copy-Item "$AgentDir\agent.config.template.json" "$InstallDir\agent.config.template.json" -Force
|
|
169
221
|
if (Test-Path "$AgentDir\public") { Copy-Item "$AgentDir\public" "$InstallDir\public" -Recurse -Force }
|
|
222
|
+
if (Test-Path "$AgentDir\scripts") { Copy-Item "$AgentDir\scripts" "$InstallDir\scripts" -Recurse -Force }
|
|
170
223
|
|
|
171
224
|
# --- 5. Install deps ---
|
|
172
225
|
Write-Host "[5/19] Installing dependencies..." -ForegroundColor Yellow
|
|
@@ -177,6 +230,16 @@ if ($LASTEXITCODE -ne 0) { & npm install --omit=dev --ignore-scripts 2>&1 | Out-
|
|
|
177
230
|
$ErrorActionPreference = "Stop"
|
|
178
231
|
Pop-Location
|
|
179
232
|
|
|
233
|
+
# --- 5b. Chrome ---
|
|
234
|
+
Write-Host "[5b/19] Ensuring Google Chrome is installed..." -ForegroundColor Yellow
|
|
235
|
+
try {
|
|
236
|
+
$chromePath = Ensure-ChromeInstalled
|
|
237
|
+
} catch {
|
|
238
|
+
Write-Host " FATAL: $($_.Exception.Message)" -ForegroundColor Red
|
|
239
|
+
Write-Host " Install Google Chrome manually, then re-run the installer." -ForegroundColor Yellow
|
|
240
|
+
exit 1
|
|
241
|
+
}
|
|
242
|
+
|
|
180
243
|
# --- 6. Generate config ---
|
|
181
244
|
Write-Host "[6/19] Generating config..." -ForegroundColor Yellow
|
|
182
245
|
if ($ShellReplace) {
|
|
@@ -327,6 +390,49 @@ for ($i = 0; $i -lt 10; $i++) {
|
|
|
327
390
|
if ($portUp) { Write-Host " Port 3403 LISTENING" -ForegroundColor Green }
|
|
328
391
|
else { Write-Host " Port 3403 not yet up (may take a moment)" -ForegroundColor Yellow }
|
|
329
392
|
|
|
393
|
+
# Wait until provisioning is complete (auto-provision or manual pairing)
|
|
394
|
+
Write-Host "[11b/19] Waiting for device provisioning/pairing..." -ForegroundColor Yellow
|
|
395
|
+
$identityPath = Join-Path $InstallDir ".lightman-identity.json"
|
|
396
|
+
$deadline = if ($PairingTimeoutSeconds -gt 0) { (Get-Date).AddSeconds($PairingTimeoutSeconds) } else { $null }
|
|
397
|
+
$paired = $false
|
|
398
|
+
$lastHint = ""
|
|
399
|
+
|
|
400
|
+
while (-not $paired) {
|
|
401
|
+
if (Test-Path $identityPath) {
|
|
402
|
+
try {
|
|
403
|
+
$identity = Get-Content $identityPath -Raw | ConvertFrom-Json
|
|
404
|
+
if ($identity.deviceId -and $identity.apiKey) {
|
|
405
|
+
$paired = $true
|
|
406
|
+
break
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
# File may be mid-write, retry
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
$logPath = Join-Path $LogDir "service-stdout.log"
|
|
414
|
+
if (Test-Path $logPath) {
|
|
415
|
+
try {
|
|
416
|
+
$hint = Get-Content $logPath -Tail 40 | Where-Object {
|
|
417
|
+
$_ -match "Pairing required|Waiting for admin to approve pairing|Auto-provisioned|Pairing complete"
|
|
418
|
+
} | Select-Object -Last 1
|
|
419
|
+
if ($hint -and $hint -ne $lastHint) {
|
|
420
|
+
Write-Host " Agent: $hint" -ForegroundColor DarkGray
|
|
421
|
+
$lastHint = $hint
|
|
422
|
+
}
|
|
423
|
+
} catch { }
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if ($deadline -and (Get-Date) -ge $deadline) {
|
|
427
|
+
Write-Host " FATAL: Pairing timed out after $PairingTimeoutSeconds seconds." -ForegroundColor Red
|
|
428
|
+
Write-Host " Check server pairing UI, then re-run installer (or increase -PairingTimeoutSeconds)." -ForegroundColor Yellow
|
|
429
|
+
exit 1
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
Start-Sleep -Seconds 5
|
|
433
|
+
}
|
|
434
|
+
Write-Host " Provisioning/pairing complete" -ForegroundColor Green
|
|
435
|
+
|
|
330
436
|
# --- 12. Firewall ---
|
|
331
437
|
Write-Host "[12/19] Configuring firewall..." -ForegroundColor Yellow
|
|
332
438
|
$ErrorActionPreference = "Continue"
|
|
@@ -530,7 +636,7 @@ $svcStatus = if ($finalSvc) { "$($finalSvc.Status)" } else { "NOT FOUND" }
|
|
|
530
636
|
$cfgOk = $false
|
|
531
637
|
try {
|
|
532
638
|
Push-Location $InstallDir
|
|
533
|
-
$cfgResult = & node -e "const c=JSON.parse(require('fs').readFileSync('agent.config.json','utf8'));console.log(JSON.stringify({slug:c.deviceSlug,shell:c.kiosk&&c.kiosk.shellMode||false}))" 2>&1
|
|
639
|
+
$cfgResult = & node -e "const c=JSON.parse(require('fs').readFileSync('agent.config.json','utf8'));console.log(JSON.stringify({slug:c.deviceSlug,shell:c.kiosk&&c.kiosk.shellMode||false,browser:c.kiosk&&c.kiosk.browserPath||''}))" 2>&1
|
|
534
640
|
$cfgData = $cfgResult | ConvertFrom-Json
|
|
535
641
|
Pop-Location
|
|
536
642
|
$cfgOk = $true
|
|
@@ -551,6 +657,7 @@ Write-Host " Service : $svcStatus" -ForegroundColor $(if ($svcStatus -eq 'Ru
|
|
|
551
657
|
if ($cfgOk) {
|
|
552
658
|
Write-Host " Config slug: $($cfgData.slug)" -ForegroundColor $(if ($cfgData.slug -eq $Slug) { 'Green' } else { 'Red' })
|
|
553
659
|
Write-Host " Shell mode : $($cfgData.shell)" -ForegroundColor $(if ($cfgData.shell -eq $ShellReplace.IsPresent) { 'Green' } else { 'Red' })
|
|
660
|
+
Write-Host " Browser : $($cfgData.browser)" -ForegroundColor Green
|
|
554
661
|
}
|
|
555
662
|
Write-Host ""
|
|
556
663
|
Write-Host " Manage:" -ForegroundColor DarkGray
|
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
|