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
|
@@ -1,122 +1,120 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import type { AgentConfig, Identity } from '../lib/types.js';
|
|
3
|
-
import { readIdentity, writeIdentity } from '../lib/identity.js';
|
|
4
|
-
import type { Logger } from '../lib/logger.js';
|
|
5
|
-
|
|
6
|
-
const provisionResponseSchema = z.object({
|
|
7
|
-
deviceId: z.string().uuid(),
|
|
8
|
-
apiKey: z.string().min(1),
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
interface ProvisionResult {
|
|
12
|
-
identity: Identity;
|
|
13
|
-
fromCache: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Provision the agent: check for cached identity, otherwise call CMS provisioning endpoints.
|
|
18
|
-
* If pairing is required, polls until admin approves (with timeout).
|
|
19
|
-
*/
|
|
20
|
-
export async function provision(
|
|
21
|
-
config: AgentConfig,
|
|
22
|
-
logger: Logger
|
|
23
|
-
): Promise<ProvisionResult> {
|
|
24
|
-
// Check for cached identity first
|
|
25
|
-
const cached = readIdentity(config.identityFile);
|
|
26
|
-
if (cached) {
|
|
27
|
-
logger.info('Using cached identity', { deviceId: cached.deviceId });
|
|
28
|
-
return { identity: cached, fromCache: true };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
logger.info(`Provisioning device: ${config.deviceSlug}`);
|
|
32
|
-
|
|
33
|
-
const baseUrl = `${config.serverUrl}/api/devices/provision/${encodeURIComponent(config.deviceSlug)}`;
|
|
34
|
-
|
|
35
|
-
// Step 1: Request provisioning
|
|
36
|
-
const res = await fetch(baseUrl);
|
|
37
|
-
if (!res.ok) {
|
|
38
|
-
const body = await res.text();
|
|
39
|
-
throw new Error(`Provision request failed (${res.status}): ${body}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const data = await res.json() as Record<string, unknown>;
|
|
43
|
-
|
|
44
|
-
// Auto-provisioned (IP match)
|
|
45
|
-
if (data.deviceId && data.apiKey) {
|
|
46
|
-
const parsed = provisionResponseSchema.safeParse(data);
|
|
47
|
-
if (!parsed.success) {
|
|
48
|
-
throw new Error('Invalid provision response: ' + parsed.error.message);
|
|
49
|
-
}
|
|
50
|
-
const identity: Identity = {
|
|
51
|
-
deviceId: parsed.data.deviceId,
|
|
52
|
-
apiKey: parsed.data.apiKey,
|
|
53
|
-
};
|
|
54
|
-
writeIdentity(config.identityFile, identity);
|
|
55
|
-
logger.info('Auto-provisioned', { deviceId: identity.deviceId });
|
|
56
|
-
return { identity, fromCache: false };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Pairing required — display code and poll
|
|
60
|
-
if (data.requiresPairing && data.code) {
|
|
61
|
-
const code = data.code as string;
|
|
62
|
-
logger.warn(`Pairing required. Enter code in admin UI: ${code}`);
|
|
63
|
-
logger.info('Waiting for admin to approve pairing...');
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
logger.
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
122
|
-
}
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AgentConfig, Identity } from '../lib/types.js';
|
|
3
|
+
import { readIdentity, writeIdentity } from '../lib/identity.js';
|
|
4
|
+
import type { Logger } from '../lib/logger.js';
|
|
5
|
+
|
|
6
|
+
const provisionResponseSchema = z.object({
|
|
7
|
+
deviceId: z.string().uuid(),
|
|
8
|
+
apiKey: z.string().min(1),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
interface ProvisionResult {
|
|
12
|
+
identity: Identity;
|
|
13
|
+
fromCache: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Provision the agent: check for cached identity, otherwise call CMS provisioning endpoints.
|
|
18
|
+
* If pairing is required, polls until admin approves (with timeout).
|
|
19
|
+
*/
|
|
20
|
+
export async function provision(
|
|
21
|
+
config: AgentConfig,
|
|
22
|
+
logger: Logger
|
|
23
|
+
): Promise<ProvisionResult> {
|
|
24
|
+
// Check for cached identity first
|
|
25
|
+
const cached = readIdentity(config.identityFile);
|
|
26
|
+
if (cached) {
|
|
27
|
+
logger.info('Using cached identity', { deviceId: cached.deviceId });
|
|
28
|
+
return { identity: cached, fromCache: true };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
logger.info(`Provisioning device: ${config.deviceSlug}`);
|
|
32
|
+
|
|
33
|
+
const baseUrl = `${config.serverUrl}/api/devices/provision/${encodeURIComponent(config.deviceSlug)}`;
|
|
34
|
+
|
|
35
|
+
// Step 1: Request provisioning
|
|
36
|
+
const res = await fetch(baseUrl);
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const body = await res.text();
|
|
39
|
+
throw new Error(`Provision request failed (${res.status}): ${body}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = await res.json() as Record<string, unknown>;
|
|
43
|
+
|
|
44
|
+
// Auto-provisioned (IP match)
|
|
45
|
+
if (data.deviceId && data.apiKey) {
|
|
46
|
+
const parsed = provisionResponseSchema.safeParse(data);
|
|
47
|
+
if (!parsed.success) {
|
|
48
|
+
throw new Error('Invalid provision response: ' + parsed.error.message);
|
|
49
|
+
}
|
|
50
|
+
const identity: Identity = {
|
|
51
|
+
deviceId: parsed.data.deviceId,
|
|
52
|
+
apiKey: parsed.data.apiKey,
|
|
53
|
+
};
|
|
54
|
+
writeIdentity(config.identityFile, identity);
|
|
55
|
+
logger.info('Auto-provisioned', { deviceId: identity.deviceId });
|
|
56
|
+
return { identity, fromCache: false };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Pairing required — display code and poll
|
|
60
|
+
if (data.requiresPairing && data.code) {
|
|
61
|
+
const code = data.code as string;
|
|
62
|
+
logger.warn(`Pairing required. Enter code in admin UI: ${code}`);
|
|
63
|
+
logger.info('Waiting for admin to approve pairing...');
|
|
64
|
+
|
|
65
|
+
const identity = await pollForPairing(
|
|
66
|
+
`${baseUrl}/status?code=${encodeURIComponent(code)}`,
|
|
67
|
+
logger
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
writeIdentity(config.identityFile, identity);
|
|
71
|
+
logger.info('Pairing complete', { deviceId: identity.deviceId });
|
|
72
|
+
return { identity, fromCache: false };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new Error('Unexpected provision response: ' + JSON.stringify(data));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function pollForPairing(
|
|
79
|
+
statusUrl: string,
|
|
80
|
+
logger: Logger,
|
|
81
|
+
timeoutMs = 600_000,
|
|
82
|
+
intervalMs = 5_000
|
|
83
|
+
): Promise<Identity> {
|
|
84
|
+
const deadline = Date.now() + timeoutMs;
|
|
85
|
+
|
|
86
|
+
while (Date.now() < deadline) {
|
|
87
|
+
await sleep(intervalMs);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(statusUrl);
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
logger.warn(`Pairing poll failed (${res.status}), retrying...`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = await res.json() as Record<string, unknown>;
|
|
97
|
+
|
|
98
|
+
if (data.deviceId && data.apiKey) {
|
|
99
|
+
const parsed = provisionResponseSchema.safeParse(data);
|
|
100
|
+
if (!parsed.success) {
|
|
101
|
+
throw new Error('Invalid pairing response: ' + parsed.error.message);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
deviceId: parsed.data.deviceId,
|
|
105
|
+
apiKey: parsed.data.apiKey,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
logger.debug('Pairing not yet complete, polling again...');
|
|
110
|
+
} catch (err) {
|
|
111
|
+
logger.warn('Pairing poll error, retrying...', err);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new Error('Pairing timed out after ' + (timeoutMs / 1000) + 's');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sleep(ms: number): Promise<void> {
|
|
119
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
120
|
+
}
|