lightman-agent 1.0.18 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent.config.json +22 -23
- package/agent.config.template.json +30 -31
- package/bin/cms-agent.js +269 -248
- package/package.json +1 -1
- package/public/assets/index-CcBNCz6h.css +1 -1
- package/public/assets/index-D9QHMG8k.js +1 -1
- package/public/assets/index-H-8HDl46.js +1 -1
- package/public/assets/index-YodeiCia.css +1 -1
- package/public/assets/index-legacy-DWtNM8y7.js +41 -41
- package/public/assets/polyfills-legacy-DyVYWHbW.js +4 -4
- package/scripts/guardian.ps1 +50 -124
- package/scripts/install-windows.ps1 +60 -116
- package/scripts/lightman-agent.logrotate +12 -12
- package/scripts/lightman-agent.service +38 -38
- package/scripts/reinstall-windows.ps1 +26 -26
- package/scripts/restore-desktop.ps1 +32 -32
- package/scripts/setup.ps1 +17 -22
- package/scripts/sync-display.mjs +20 -20
- package/scripts/uninstall-windows.ps1 +54 -54
- package/src/commands/display.ts +177 -177
- package/src/commands/kiosk.ts +113 -113
- package/src/commands/maintenance.ts +106 -106
- package/src/commands/network.ts +129 -129
- package/src/commands/power.ts +163 -163
- package/src/commands/rpi.ts +45 -45
- package/src/commands/screenshot.ts +166 -166
- package/src/commands/serial.ts +17 -17
- package/src/commands/update.ts +124 -124
- package/src/index.ts +173 -90
- package/src/lib/config.ts +2 -3
- package/src/lib/identity.ts +40 -40
- package/src/lib/logger.ts +137 -137
- package/src/lib/platform.ts +10 -10
- package/src/lib/rpi.ts +180 -180
- package/src/lib/screenMap.ts +135 -0
- package/src/lib/screens.ts +128 -128
- package/src/lib/types.ts +176 -177
- package/src/services/commands.ts +107 -107
- package/src/services/health.ts +161 -161
- package/src/services/localEvents.ts +60 -60
- package/src/services/logForwarder.ts +72 -72
- package/src/services/multiScreenKiosk.ts +116 -83
- package/src/services/oscBridge.ts +186 -186
- package/src/services/powerScheduler.ts +260 -260
- package/src/services/provisioning.ts +120 -122
- package/src/services/serialBridge.ts +230 -230
- package/src/services/serviceLauncher.ts +183 -183
- package/src/services/staticServer.ts +226 -226
- package/src/services/updater.ts +249 -249
- package/src/services/watchdog.ts +310 -310
- package/src/services/websocket.ts +152 -152
- package/tsconfig.json +28 -28
package/agent.config.json
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"serverUrl": "http://192.168.10.100:3401",
|
|
3
|
-
"deviceSlug": "a-av03",
|
|
4
|
-
"healthIntervalMs": 60000,
|
|
5
|
-
"logLevel": "debug",
|
|
6
|
-
"logFile": "agent.log",
|
|
7
|
-
"identityFile": ".lightman-identity.json",
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"serverUrl": "http://192.168.10.100:3401",
|
|
3
|
+
"deviceSlug": "a-av03",
|
|
4
|
+
"healthIntervalMs": 60000,
|
|
5
|
+
"logLevel": "debug",
|
|
6
|
+
"logFile": "agent.log",
|
|
7
|
+
"identityFile": ".lightman-identity.json",
|
|
8
|
+
"kiosk": {
|
|
9
|
+
"browserPath": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
10
|
+
"defaultUrl": "http://localhost:3403/display/a-av03",
|
|
11
|
+
"extraArgs": ["--start-fullscreen", "--disable-translate", "--disable-extensions", "--user-data-dir=C:\\ProgramData\\Lightman\\chrome-kiosk"],
|
|
12
|
+
"pollIntervalMs": 10000,
|
|
13
|
+
"maxCrashesInWindow": 10,
|
|
14
|
+
"crashWindowMs": 300000
|
|
15
|
+
},
|
|
16
|
+
"powerSchedule": {
|
|
17
|
+
"shutdownCron": "0 19 * * *",
|
|
18
|
+
"startupCron": "0 8 * * *",
|
|
19
|
+
"timezone": "Asia/Kolkata",
|
|
20
|
+
"shutdownWarningSeconds": 60
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
{
|
|
2
|
-
"serverUrl": "__SERVER_URL__",
|
|
3
|
-
"deviceSlug": "__DEVICE_SLUG__",
|
|
4
|
-
"healthIntervalMs": 60000,
|
|
5
|
-
"logLevel": "info",
|
|
6
|
-
"logFile": "agent.log",
|
|
7
|
-
"identityFile": ".lightman-identity.json",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
"--
|
|
15
|
-
"--disable-
|
|
16
|
-
"--
|
|
17
|
-
"--
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"serverUrl": "__SERVER_URL__",
|
|
3
|
+
"deviceSlug": "__DEVICE_SLUG__",
|
|
4
|
+
"healthIntervalMs": 60000,
|
|
5
|
+
"logLevel": "info",
|
|
6
|
+
"logFile": "agent.log",
|
|
7
|
+
"identityFile": ".lightman-identity.json",
|
|
8
|
+
"localServices": false,
|
|
9
|
+
"kiosk": {
|
|
10
|
+
"browserPath": "__BROWSER_PATH__",
|
|
11
|
+
"defaultUrl": "__KIOSK_URL__",
|
|
12
|
+
"extraArgs": [
|
|
13
|
+
"--start-fullscreen",
|
|
14
|
+
"--disable-translate",
|
|
15
|
+
"--disable-extensions",
|
|
16
|
+
"--autoplay-policy=no-user-gesture-required",
|
|
17
|
+
"--user-data-dir=__CHROME_DATA_DIR__"
|
|
18
|
+
],
|
|
19
|
+
"pollIntervalMs": 10000,
|
|
20
|
+
"maxCrashesInWindow": 10,
|
|
21
|
+
"crashWindowMs": 300000,
|
|
22
|
+
"shellMode": false
|
|
23
|
+
},
|
|
24
|
+
"powerSchedule": {
|
|
25
|
+
"shutdownCron": "0 19 * * *",
|
|
26
|
+
"startupCron": "0 8 * * *",
|
|
27
|
+
"timezone": "Asia/Kolkata",
|
|
28
|
+
"shutdownWarningSeconds": 60
|
|
29
|
+
}
|
|
30
|
+
}
|
package/bin/cms-agent.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { existsSync, readFileSync } from 'fs';
|
|
4
|
-
import { networkInterfaces } from 'os';
|
|
5
|
-
import { resolve, dirname } from 'path';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
import { spawnSync } from 'child_process';
|
|
8
|
-
import { createInterface } from 'readline/promises';
|
|
9
|
-
import { stdin as input, stdout as output, cwd, platform, exit } from 'process';
|
|
10
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { networkInterfaces } from 'os';
|
|
5
|
+
import { resolve, dirname } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { spawnSync } from 'child_process';
|
|
8
|
+
import { createInterface } from 'readline/promises';
|
|
9
|
+
import { stdin as input, stdout as output, cwd, platform, exit } from 'process';
|
|
10
|
+
|
|
11
11
|
const DEFAULT_SERVER = 'http://192.168.10.100:3401';
|
|
12
12
|
const INSTALL_CONFIG_PATH = 'C:\\Program Files\\Lightman\\Agent\\agent.config.json';
|
|
13
|
+
const INSTALL_PACKAGE_PATH = 'C:\\Program Files\\Lightman\\Agent\\package.json';
|
|
13
14
|
|
|
14
15
|
function printUsage() {
|
|
15
16
|
console.log(`
|
|
@@ -19,6 +20,7 @@ Commands:
|
|
|
19
20
|
install Prompt slug and server IP, install agent with ShellReplace, reboot
|
|
20
21
|
setup Alias of install
|
|
21
22
|
update Reinstall/update using installed config, reboot
|
|
23
|
+
version Show CLI package version and installed agent version
|
|
22
24
|
|
|
23
25
|
Options:
|
|
24
26
|
--slug <value> Device slug (example: C-AV01)
|
|
@@ -26,242 +28,256 @@ Options:
|
|
|
26
28
|
--timezone <tz> Timezone override (default: Asia/Kolkata)
|
|
27
29
|
--pair-timeout <s> Wait time for pairing in seconds (default: 900, 0 = no timeout)
|
|
28
30
|
--no-restart Skip reboot after successful install/update
|
|
31
|
+
-v, --version Show version
|
|
29
32
|
-h, --help Show help
|
|
30
33
|
`);
|
|
31
34
|
}
|
|
32
|
-
|
|
33
|
-
function parseArgs(argv) {
|
|
34
|
-
const args = {};
|
|
35
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
36
|
-
const part = argv[i];
|
|
37
|
-
if (!part.startsWith('-')) {
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (part === '--no-restart') {
|
|
41
|
-
args.noRestart = true;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (part === '--help' || part === '-h') {
|
|
45
|
-
args.help = true;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
const next = argv[i + 1];
|
|
49
|
-
if (!next || next.startsWith('-')) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (part === '--slug') {
|
|
53
|
-
args.slug = next.trim();
|
|
54
|
-
i += 1;
|
|
55
|
-
} else if (part === '--server') {
|
|
56
|
-
args.server = next.trim();
|
|
57
|
-
i += 1;
|
|
58
|
-
} else if (part === '--timezone') {
|
|
59
|
-
args.timezone = next.trim();
|
|
60
|
-
i += 1;
|
|
61
|
-
} else if (part === '--pair-timeout') {
|
|
62
|
-
args.pairTimeout = next.trim();
|
|
63
|
-
i += 1;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return args;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function safeReadJson(path) {
|
|
70
|
-
try {
|
|
71
|
-
if (!existsSync(path)) return null;
|
|
72
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
73
|
-
} catch {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function isValidSlug(slug) {
|
|
79
|
-
return /^[A-Za-z0-9][A-Za-z0-9-]{0,62}$/.test(slug);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function normalizeServer(server) {
|
|
83
|
-
const value = (server || '').trim();
|
|
84
|
-
if (!value) {
|
|
85
|
-
return '';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const hasProtocol = /^[a-z]+:\/\//i.test(value);
|
|
89
|
-
const candidate = hasProtocol ? value : `http://${value}`;
|
|
90
|
-
|
|
91
|
-
let parsed;
|
|
92
|
-
try {
|
|
93
|
-
parsed = new URL(candidate);
|
|
94
|
-
} catch {
|
|
95
|
-
throw new Error('Invalid server IP/URL. Use an IP like 192.168.10.100 or a URL like http://192.168.10.100:3401.');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!parsed.port) {
|
|
99
|
-
parsed.port = '3401';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (parsed.pathname === '/') {
|
|
103
|
-
parsed.pathname = '';
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return parsed.toString().replace(/\/$/, '');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function collectMacAddresses() {
|
|
110
|
-
const nets = networkInterfaces();
|
|
111
|
-
const macs = new Set();
|
|
112
|
-
for (const netName of Object.keys(nets)) {
|
|
113
|
-
const ifaceList = nets[netName] || [];
|
|
114
|
-
for (const iface of ifaceList) {
|
|
115
|
-
const mac = (iface.mac || '').trim().toUpperCase();
|
|
116
|
-
if (!iface.internal && mac && mac !== '00:00:00:00:00:00') {
|
|
117
|
-
macs.add(`${netName}: ${mac}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return [...macs];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function isWindowsAdmin() {
|
|
125
|
-
const cmd = '[bool]([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)';
|
|
126
|
-
const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', cmd], { encoding: 'utf8' });
|
|
127
|
-
return result.status === 0 && result.stdout.trim().toLowerCase() === 'true';
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function runOrFail(command, args) {
|
|
131
|
-
const result = spawnSync(command, args, { stdio: 'inherit', shell: false });
|
|
132
|
-
if (result.error) {
|
|
133
|
-
throw result.error;
|
|
134
|
-
}
|
|
135
|
-
if (result.status !== 0) {
|
|
136
|
-
throw new Error(`Command failed with exit code ${result.status}: ${command} ${args.join(' ')}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async function promptSlug(defaultSlug) {
|
|
141
|
-
const rl = createInterface({ input, output });
|
|
142
|
-
try {
|
|
143
|
-
while (true) {
|
|
144
|
-
const prompt = defaultSlug
|
|
145
|
-
? `Enter device slug [${defaultSlug}]: `
|
|
146
|
-
: 'Enter device slug (example C-AV01): ';
|
|
147
|
-
const answer = (await rl.question(prompt)).trim();
|
|
148
|
-
const slug = answer || defaultSlug || '';
|
|
149
|
-
if (isValidSlug(slug)) {
|
|
150
|
-
return slug;
|
|
151
|
-
}
|
|
152
|
-
console.error('Invalid slug. Use letters, numbers, and hyphens only.');
|
|
153
|
-
}
|
|
154
|
-
} finally {
|
|
155
|
-
rl.close();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function promptServer(defaultServer) {
|
|
160
|
-
const rl = createInterface({ input, output });
|
|
161
|
-
try {
|
|
162
|
-
while (true) {
|
|
163
|
-
const prompt = defaultServer
|
|
164
|
-
? `Enter server IP or URL [${defaultServer}]: `
|
|
165
|
-
: 'Enter server IP or URL (example 192.168.10.100 or http://192.168.10.100:3401): ';
|
|
166
|
-
const answer = (await rl.question(prompt)).trim();
|
|
167
|
-
const server = answer || defaultServer || '';
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
return normalizeServer(server);
|
|
171
|
-
} catch (error) {
|
|
172
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
} finally {
|
|
176
|
-
rl.close();
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
35
|
+
|
|
36
|
+
function parseArgs(argv) {
|
|
37
|
+
const args = {};
|
|
38
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
39
|
+
const part = argv[i];
|
|
40
|
+
if (!part.startsWith('-')) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (part === '--no-restart') {
|
|
44
|
+
args.noRestart = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (part === '--help' || part === '-h') {
|
|
48
|
+
args.help = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const next = argv[i + 1];
|
|
52
|
+
if (!next || next.startsWith('-')) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (part === '--slug') {
|
|
56
|
+
args.slug = next.trim();
|
|
57
|
+
i += 1;
|
|
58
|
+
} else if (part === '--server') {
|
|
59
|
+
args.server = next.trim();
|
|
60
|
+
i += 1;
|
|
61
|
+
} else if (part === '--timezone') {
|
|
62
|
+
args.timezone = next.trim();
|
|
63
|
+
i += 1;
|
|
64
|
+
} else if (part === '--pair-timeout') {
|
|
65
|
+
args.pairTimeout = next.trim();
|
|
66
|
+
i += 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return args;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function safeReadJson(path) {
|
|
73
|
+
try {
|
|
74
|
+
if (!existsSync(path)) return null;
|
|
75
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isValidSlug(slug) {
|
|
82
|
+
return /^[A-Za-z0-9][A-Za-z0-9-]{0,62}$/.test(slug);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeServer(server) {
|
|
86
|
+
const value = (server || '').trim();
|
|
87
|
+
if (!value) {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const hasProtocol = /^[a-z]+:\/\//i.test(value);
|
|
92
|
+
const candidate = hasProtocol ? value : `http://${value}`;
|
|
93
|
+
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = new URL(candidate);
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error('Invalid server IP/URL. Use an IP like 192.168.10.100 or a URL like http://192.168.10.100:3401.');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!parsed.port) {
|
|
102
|
+
parsed.port = '3401';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (parsed.pathname === '/') {
|
|
106
|
+
parsed.pathname = '';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return parsed.toString().replace(/\/$/, '');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function collectMacAddresses() {
|
|
113
|
+
const nets = networkInterfaces();
|
|
114
|
+
const macs = new Set();
|
|
115
|
+
for (const netName of Object.keys(nets)) {
|
|
116
|
+
const ifaceList = nets[netName] || [];
|
|
117
|
+
for (const iface of ifaceList) {
|
|
118
|
+
const mac = (iface.mac || '').trim().toUpperCase();
|
|
119
|
+
if (!iface.internal && mac && mac !== '00:00:00:00:00:00') {
|
|
120
|
+
macs.add(`${netName}: ${mac}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return [...macs];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isWindowsAdmin() {
|
|
128
|
+
const cmd = '[bool]([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)';
|
|
129
|
+
const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', cmd], { encoding: 'utf8' });
|
|
130
|
+
return result.status === 0 && result.stdout.trim().toLowerCase() === 'true';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function runOrFail(command, args) {
|
|
134
|
+
const result = spawnSync(command, args, { stdio: 'inherit', shell: false });
|
|
135
|
+
if (result.error) {
|
|
136
|
+
throw result.error;
|
|
137
|
+
}
|
|
138
|
+
if (result.status !== 0) {
|
|
139
|
+
throw new Error(`Command failed with exit code ${result.status}: ${command} ${args.join(' ')}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function promptSlug(defaultSlug) {
|
|
144
|
+
const rl = createInterface({ input, output });
|
|
145
|
+
try {
|
|
146
|
+
while (true) {
|
|
147
|
+
const prompt = defaultSlug
|
|
148
|
+
? `Enter device slug [${defaultSlug}]: `
|
|
149
|
+
: 'Enter device slug (example C-AV01): ';
|
|
150
|
+
const answer = (await rl.question(prompt)).trim();
|
|
151
|
+
const slug = answer || defaultSlug || '';
|
|
152
|
+
if (isValidSlug(slug)) {
|
|
153
|
+
return slug;
|
|
154
|
+
}
|
|
155
|
+
console.error('Invalid slug. Use letters, numbers, and hyphens only.');
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
rl.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function promptServer(defaultServer) {
|
|
163
|
+
const rl = createInterface({ input, output });
|
|
164
|
+
try {
|
|
165
|
+
while (true) {
|
|
166
|
+
const prompt = defaultServer
|
|
167
|
+
? `Enter server IP or URL [${defaultServer}]: `
|
|
168
|
+
: 'Enter server IP or URL (example 192.168.10.100 or http://192.168.10.100:3401): ';
|
|
169
|
+
const answer = (await rl.question(prompt)).trim();
|
|
170
|
+
const server = answer || defaultServer || '';
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
return normalizeServer(server);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} finally {
|
|
179
|
+
rl.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
180
183
|
function resolveInstallScript() {
|
|
181
184
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
182
185
|
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
|
+
const localRepo = resolve(cwd(), 'scripts/install-windows.ps1');
|
|
187
|
+
if (existsSync(packaged)) return packaged;
|
|
188
|
+
if (existsSync(localRepo)) return localRepo;
|
|
186
189
|
throw new Error('install-windows.ps1 not found. Expected in package scripts/ or current folder scripts/.');
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
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);
|
|
199
|
-
|
|
200
|
-
if (!noRestart) {
|
|
201
|
-
console.log('Installation completed. Rebooting now...');
|
|
202
|
-
runOrFail('shutdown.exe', ['/r', '/t', '0', '/c', 'CMS Agent installation complete']);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async function runInstall(opts) {
|
|
207
|
-
if (platform !== 'win32') {
|
|
208
|
-
throw new Error('cms-agent install is currently supported on Windows only.');
|
|
209
|
-
}
|
|
210
|
-
if (!isWindowsAdmin()) {
|
|
211
|
-
throw new Error('Please run this command from an Administrator PowerShell or CMD.');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const localConfig = safeReadJson(resolve(cwd(), 'agent.config.json')) || {};
|
|
215
|
-
const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
|
|
216
|
-
const defaultSlug = opts.slug || localConfig.deviceSlug || installedConfig.deviceSlug || '';
|
|
217
|
-
const defaultServer = localConfig.serverUrl || installedConfig.serverUrl || DEFAULT_SERVER;
|
|
218
|
-
const timezone = opts.timezone || localConfig?.powerSchedule?.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
|
|
219
|
-
const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
|
|
220
|
-
const noRestart = Boolean(opts.noRestart);
|
|
221
|
-
|
|
222
|
-
console.log('Detected MAC addresses:');
|
|
223
|
-
const macs = collectMacAddresses();
|
|
224
|
-
if (macs.length === 0) {
|
|
225
|
-
console.log(' (no active external interface found)');
|
|
226
|
-
} else {
|
|
227
|
-
for (const item of macs) {
|
|
228
|
-
console.log(` ${item}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
console.log('');
|
|
232
|
-
|
|
233
|
-
const slug = opts.slug || await promptSlug(defaultSlug);
|
|
234
|
-
const server = opts.server ? normalizeServer(opts.server) : await promptServer(defaultServer);
|
|
235
|
-
const scriptPath = resolveInstallScript();
|
|
236
|
-
|
|
237
|
-
console.log(`Installing with slug=${slug}, server=${server}, shellReplace=true`);
|
|
238
|
-
installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async function runUpdate(opts) {
|
|
242
|
-
if (platform !== 'win32') {
|
|
243
|
-
throw new Error('cms-agent update is currently supported on Windows only.');
|
|
244
|
-
}
|
|
245
|
-
if (!isWindowsAdmin()) {
|
|
246
|
-
throw new Error('Please run this command from an Administrator PowerShell or CMD.');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
|
|
250
|
-
const slug = opts.slug || installedConfig.deviceSlug;
|
|
251
|
-
const server = normalizeServer(opts.server || installedConfig.serverUrl || DEFAULT_SERVER);
|
|
252
|
-
const timezone = opts.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
|
|
253
|
-
const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
|
|
254
|
-
const noRestart = Boolean(opts.noRestart);
|
|
255
|
-
const scriptPath = resolveInstallScript();
|
|
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) || {};
|
|
256
196
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
197
|
+
const cliVersion = typeof bundledPkg.version === 'string' ? bundledPkg.version : 'unknown';
|
|
198
|
+
const installedVersion = typeof installedPkg.version === 'string' ? installedPkg.version : 'not-installed';
|
|
260
199
|
|
|
261
|
-
console.log(`
|
|
262
|
-
|
|
200
|
+
console.log(`lightman-agent cli: ${cliVersion}`);
|
|
201
|
+
console.log(`lightman-agent installed: ${installedVersion}`);
|
|
202
|
+
console.log(`node: ${process.version}`);
|
|
263
203
|
}
|
|
264
|
-
|
|
204
|
+
|
|
205
|
+
function installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart }) {
|
|
206
|
+
console.log(`powershell -ExecutionPolicy Bypass -File scripts\\install-windows.ps1 -Slug "${slug}" -Server "${server}" -ShellReplace`);
|
|
207
|
+
const args = ['-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Slug', slug, '-Server', server, '-ShellReplace'];
|
|
208
|
+
if (timezone) {
|
|
209
|
+
args.push('-Timezone', timezone);
|
|
210
|
+
}
|
|
211
|
+
if (Number.isInteger(pairingTimeoutSeconds) && pairingTimeoutSeconds >= 0) {
|
|
212
|
+
args.push('-PairingTimeoutSeconds', String(pairingTimeoutSeconds));
|
|
213
|
+
}
|
|
214
|
+
runOrFail('powershell.exe', args);
|
|
215
|
+
|
|
216
|
+
if (!noRestart) {
|
|
217
|
+
console.log('Installation completed. Rebooting now...');
|
|
218
|
+
runOrFail('shutdown.exe', ['/r', '/t', '0', '/c', 'CMS Agent installation complete']);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function runInstall(opts) {
|
|
223
|
+
if (platform !== 'win32') {
|
|
224
|
+
throw new Error('cms-agent install is currently supported on Windows only.');
|
|
225
|
+
}
|
|
226
|
+
if (!isWindowsAdmin()) {
|
|
227
|
+
throw new Error('Please run this command from an Administrator PowerShell or CMD.');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const localConfig = safeReadJson(resolve(cwd(), 'agent.config.json')) || {};
|
|
231
|
+
const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
|
|
232
|
+
const defaultSlug = opts.slug || localConfig.deviceSlug || installedConfig.deviceSlug || '';
|
|
233
|
+
const defaultServer = localConfig.serverUrl || installedConfig.serverUrl || DEFAULT_SERVER;
|
|
234
|
+
const timezone = opts.timezone || localConfig?.powerSchedule?.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
|
|
235
|
+
const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
|
|
236
|
+
const noRestart = Boolean(opts.noRestart);
|
|
237
|
+
|
|
238
|
+
console.log('Detected MAC addresses:');
|
|
239
|
+
const macs = collectMacAddresses();
|
|
240
|
+
if (macs.length === 0) {
|
|
241
|
+
console.log(' (no active external interface found)');
|
|
242
|
+
} else {
|
|
243
|
+
for (const item of macs) {
|
|
244
|
+
console.log(` ${item}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
console.log('');
|
|
248
|
+
|
|
249
|
+
const slug = opts.slug || await promptSlug(defaultSlug);
|
|
250
|
+
const server = opts.server ? normalizeServer(opts.server) : await promptServer(defaultServer);
|
|
251
|
+
const scriptPath = resolveInstallScript();
|
|
252
|
+
|
|
253
|
+
console.log(`Installing with slug=${slug}, server=${server}, shellReplace=true`);
|
|
254
|
+
installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function runUpdate(opts) {
|
|
258
|
+
if (platform !== 'win32') {
|
|
259
|
+
throw new Error('cms-agent update is currently supported on Windows only.');
|
|
260
|
+
}
|
|
261
|
+
if (!isWindowsAdmin()) {
|
|
262
|
+
throw new Error('Please run this command from an Administrator PowerShell or CMD.');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const installedConfig = safeReadJson(INSTALL_CONFIG_PATH) || {};
|
|
266
|
+
const slug = opts.slug || installedConfig.deviceSlug;
|
|
267
|
+
const server = normalizeServer(opts.server || installedConfig.serverUrl || DEFAULT_SERVER);
|
|
268
|
+
const timezone = opts.timezone || installedConfig?.powerSchedule?.timezone || 'Asia/Kolkata';
|
|
269
|
+
const pairingTimeoutSeconds = Number.isFinite(Number(opts.pairTimeout)) ? Number.parseInt(String(opts.pairTimeout), 10) : 900;
|
|
270
|
+
const noRestart = Boolean(opts.noRestart);
|
|
271
|
+
const scriptPath = resolveInstallScript();
|
|
272
|
+
|
|
273
|
+
if (!slug) {
|
|
274
|
+
throw new Error('No installed slug found. Use cms-agent install first or pass --slug.');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log(`Updating with slug=${slug}, server=${server}, shellReplace=true`);
|
|
278
|
+
installUsingPowerShell({ scriptPath, slug, server, timezone, pairingTimeoutSeconds, noRestart });
|
|
279
|
+
}
|
|
280
|
+
|
|
265
281
|
async function main() {
|
|
266
282
|
const [, , commandRaw, ...rest] = process.argv;
|
|
267
283
|
const command = commandRaw || '';
|
|
@@ -272,20 +288,25 @@ async function main() {
|
|
|
272
288
|
return;
|
|
273
289
|
}
|
|
274
290
|
|
|
275
|
-
if (command === '
|
|
276
|
-
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (command === 'update') {
|
|
280
|
-
await runUpdate(opts);
|
|
291
|
+
if (command === 'version' || command === '--version' || command === '-v') {
|
|
292
|
+
runVersion();
|
|
281
293
|
return;
|
|
282
294
|
}
|
|
283
295
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
296
|
+
if (command === 'install' || command === 'setup') {
|
|
297
|
+
await runInstall(opts);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (command === 'update') {
|
|
301
|
+
await runUpdate(opts);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
printUsage();
|
|
306
|
+
throw new Error(`Unknown command: ${command}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
main().catch((err) => {
|
|
310
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
311
|
+
exit(1);
|
|
312
|
+
});
|