dominds 1.25.19 → 1.26.1
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/README.md +17 -0
- package/README.zh.md +17 -0
- package/dist/apps/runtime.d.ts +2 -4
- package/dist/apps-host/client.d.ts +2 -5
- package/dist/apps-host/ipc-types.d.ts +2 -5
- package/dist/apps-host/ipc-types.js +5 -1
- package/dist/bootstrap/rtws-cli.d.ts +1 -0
- package/dist/bootstrap/rtws-cli.js +8 -8
- package/dist/cli/cert.d.ts +4 -0
- package/dist/cli/cert.js +247 -0
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.js +1 -1
- package/dist/cli/read.js +1 -1
- package/dist/cli/tui.js +1 -1
- package/dist/cli/webui.d.ts +1 -1
- package/dist/cli/webui.js +35 -5
- package/dist/cli-runner.d.ts +31 -0
- package/dist/cli-runner.js +349 -0
- package/dist/cli.d.ts +0 -30
- package/dist/cli.js +540 -292
- package/dist/docs/cli-usage.md +40 -3
- package/dist/docs/cli-usage.zh.md +40 -3
- package/dist/docs/dialog-persistence.md +1 -1
- package/dist/docs/dominds-terminology.md +2 -2
- package/dist/llm/kernel-driver/drive.js +19 -14
- package/dist/runtime/driver-messages.d.ts +2 -0
- package/dist/runtime/driver-messages.js +24 -4
- package/dist/server/auth.d.ts +7 -0
- package/dist/server/auth.js +15 -4
- package/dist/server/certificates.d.ts +61 -0
- package/dist/server/certificates.js +418 -0
- package/dist/server/dominds-self-update.js +33 -121
- package/dist/server/network-hosts.d.ts +5 -0
- package/dist/server/network-hosts.js +277 -0
- package/dist/server/server-core.d.ts +12 -1
- package/dist/server/server-core.js +26 -4
- package/dist/server/websocket-handler.d.ts +3 -2
- package/dist/server/websocket-handler.js +15 -8
- package/dist/server-debug.d.ts +2 -0
- package/dist/server-debug.js +79 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +101 -126
- package/dist/supervisor-protocol.d.ts +15 -0
- package/dist/supervisor-protocol.js +4 -0
- package/package.json +6 -5
- package/dist/server/dominds-self-update-restart-helper.d.ts +0 -15
- package/dist/server/dominds-self-update-restart-helper.js +0 -305
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DEFAULT_SELF_SIGNED_CERT_DAYS = void 0;
|
|
37
|
+
exports.getDefaultDomindsCertsDir = getDefaultDomindsCertsDir;
|
|
38
|
+
exports.findAutoHttpsCertificateForHost = findAutoHttpsCertificateForHost;
|
|
39
|
+
exports.createSelfSignedCertificate = createSelfSignedCertificate;
|
|
40
|
+
const node_child_process_1 = require("node:child_process");
|
|
41
|
+
const node_crypto_1 = require("node:crypto");
|
|
42
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
43
|
+
const net = __importStar(require("node:net"));
|
|
44
|
+
const os = __importStar(require("node:os"));
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
46
|
+
const tls = __importStar(require("node:tls"));
|
|
47
|
+
const node_util_1 = require("node:util");
|
|
48
|
+
const network_hosts_1 = require("./network-hosts");
|
|
49
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
50
|
+
const CERT_EXTENSIONS = ['.crt', '.cert', '.pem'];
|
|
51
|
+
const KEY_SUFFIXES = ['.key.pem', '.key', '-key.pem'];
|
|
52
|
+
exports.DEFAULT_SELF_SIGNED_CERT_DAYS = 3650;
|
|
53
|
+
function getDefaultDomindsCertsDir() {
|
|
54
|
+
return path.join(os.homedir(), '.dominds', 'certs');
|
|
55
|
+
}
|
|
56
|
+
async function findAutoHttpsCertificateForHost(params) {
|
|
57
|
+
const host = (0, network_hosts_1.normalizeNetworkHost)(params.host);
|
|
58
|
+
const matchHosts = await (0, network_hosts_1.resolveLanHttpsHostsForBindHost)(host);
|
|
59
|
+
const endpoint = { bindHost: host, urlHost: matchHosts[0] ?? host };
|
|
60
|
+
const certsDirAbs = params.certsDirAbs ?? getDefaultDomindsCertsDir();
|
|
61
|
+
const now = params.now ?? new Date();
|
|
62
|
+
const diagnostics = [];
|
|
63
|
+
if (matchHosts.length === 0) {
|
|
64
|
+
return { kind: 'not_found', certsDirAbs, endpoint, diagnostics };
|
|
65
|
+
}
|
|
66
|
+
let entries;
|
|
67
|
+
try {
|
|
68
|
+
entries = await fs.readdir(certsDirAbs);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (isNodeErrorWithCode(error, 'ENOENT')) {
|
|
72
|
+
return { kind: 'not_found', certsDirAbs, endpoint, diagnostics };
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
const matches = [];
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (!isCertificateFilename(entry))
|
|
79
|
+
continue;
|
|
80
|
+
const certPath = path.join(certsDirAbs, entry);
|
|
81
|
+
const keyPath = await findKeyPathForCert({ certsDirAbs, certEntry: entry });
|
|
82
|
+
if (keyPath === null) {
|
|
83
|
+
diagnostics.push({
|
|
84
|
+
level: 'warn',
|
|
85
|
+
code: 'missing_key_file',
|
|
86
|
+
message: `Certificate has no matching private key: ${certPath}`,
|
|
87
|
+
certPath,
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const loaded = await loadAndValidateCandidate({
|
|
92
|
+
matchHosts,
|
|
93
|
+
now,
|
|
94
|
+
certPath,
|
|
95
|
+
keyPath,
|
|
96
|
+
});
|
|
97
|
+
if (loaded.kind === 'match') {
|
|
98
|
+
matches.push(loaded.certificate);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (loaded.kind === 'diagnostic') {
|
|
102
|
+
diagnostics.push(loaded.diagnostic);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (matches.length === 0) {
|
|
106
|
+
return { kind: 'not_found', certsDirAbs, endpoint, diagnostics };
|
|
107
|
+
}
|
|
108
|
+
matches.sort((a, b) => a.matchedHostPriority - b.matchedHostPriority || b.validTo.getTime() - a.validTo.getTime());
|
|
109
|
+
if (matches.length > 1) {
|
|
110
|
+
diagnostics.push({
|
|
111
|
+
level: 'warn',
|
|
112
|
+
code: 'multiple_matching_certs',
|
|
113
|
+
message: `Multiple valid certificates match host '${host}'; using the best-priority match with the latest expiry.`,
|
|
114
|
+
host,
|
|
115
|
+
certPaths: matches.map((m) => m.certPath),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
kind: 'found',
|
|
120
|
+
certsDirAbs,
|
|
121
|
+
endpoint: { bindHost: host, urlHost: matches[0].matchedHost },
|
|
122
|
+
certificate: matches[0],
|
|
123
|
+
diagnostics,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function createSelfSignedCertificate(params) {
|
|
127
|
+
const hosts = normalizeCertificateHostsForCreate(params.host === undefined
|
|
128
|
+
? await (0, network_hosts_1.resolveDefaultLanHttpsHosts)()
|
|
129
|
+
: [params.host, ...(params.altHosts ?? [])], { skipInvalidHosts: params.host === undefined });
|
|
130
|
+
const host = hosts[0];
|
|
131
|
+
const days = normalizeDays(params.days ?? exports.DEFAULT_SELF_SIGNED_CERT_DAYS);
|
|
132
|
+
const certsDirAbs = params.certsDirAbs ?? getDefaultDomindsCertsDir();
|
|
133
|
+
const basename = certificateBasenameForHosts(hosts);
|
|
134
|
+
const certPath = path.join(certsDirAbs, `${basename}.crt.pem`);
|
|
135
|
+
const keyPath = path.join(certsDirAbs, `${basename}.key.pem`);
|
|
136
|
+
const metadataPath = path.join(certsDirAbs, `${basename}.json`);
|
|
137
|
+
await fs.mkdir(certsDirAbs, { recursive: true, mode: 0o700 });
|
|
138
|
+
if (params.force !== true) {
|
|
139
|
+
await assertPathDoesNotExist(certPath);
|
|
140
|
+
await assertPathDoesNotExist(keyPath);
|
|
141
|
+
await assertPathDoesNotExist(metadataPath);
|
|
142
|
+
}
|
|
143
|
+
const san = buildSubjectAltName(hosts);
|
|
144
|
+
await execFileAsync('openssl', [
|
|
145
|
+
'req',
|
|
146
|
+
'-x509',
|
|
147
|
+
'-newkey',
|
|
148
|
+
'rsa:2048',
|
|
149
|
+
'-sha256',
|
|
150
|
+
'-nodes',
|
|
151
|
+
'-days',
|
|
152
|
+
String(days),
|
|
153
|
+
'-keyout',
|
|
154
|
+
keyPath,
|
|
155
|
+
'-out',
|
|
156
|
+
certPath,
|
|
157
|
+
'-subj',
|
|
158
|
+
`/CN=${escapeOpenSslSubjectValue(host)}`,
|
|
159
|
+
'-addext',
|
|
160
|
+
`subjectAltName=${san}`,
|
|
161
|
+
], { maxBuffer: 1024 * 1024 });
|
|
162
|
+
await fs.chmod(keyPath, 0o600);
|
|
163
|
+
await fs.chmod(certPath, 0o644);
|
|
164
|
+
const metadata = {
|
|
165
|
+
kind: 'dominds_self_signed_cert',
|
|
166
|
+
host,
|
|
167
|
+
hosts,
|
|
168
|
+
days,
|
|
169
|
+
certPath,
|
|
170
|
+
keyPath,
|
|
171
|
+
createdAt: new Date().toISOString(),
|
|
172
|
+
subjectAltName: san,
|
|
173
|
+
};
|
|
174
|
+
await fs.writeFile(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8');
|
|
175
|
+
await fs.chmod(metadataPath, 0o644);
|
|
176
|
+
return { host, hosts, days, certsDirAbs, certPath, keyPath, metadataPath };
|
|
177
|
+
}
|
|
178
|
+
function normalizeCertificateHostsForCreate(rawHosts, options) {
|
|
179
|
+
const hosts = [];
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
for (const rawHost of rawHosts) {
|
|
182
|
+
const hostCandidates = getCertificateMatchHostsForCreateHost(rawHost);
|
|
183
|
+
for (const host of hostCandidates) {
|
|
184
|
+
const validation = validateCertificateHost(host);
|
|
185
|
+
if (validation.kind === 'invalid') {
|
|
186
|
+
if (options.skipInvalidHosts)
|
|
187
|
+
continue;
|
|
188
|
+
throw new Error(validation.message);
|
|
189
|
+
}
|
|
190
|
+
const key = host.toLowerCase();
|
|
191
|
+
if (seen.has(key))
|
|
192
|
+
continue;
|
|
193
|
+
seen.add(key);
|
|
194
|
+
hosts.push(host);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (hosts.length === 0) {
|
|
198
|
+
throw new Error(options.skipInvalidHosts
|
|
199
|
+
? 'No certificate-valid non-loopback LAN host was detected; provide --host explicitly'
|
|
200
|
+
: '--host must be provided at least once');
|
|
201
|
+
}
|
|
202
|
+
return hosts;
|
|
203
|
+
}
|
|
204
|
+
function getCertificateMatchHostsForCreateHost(rawHost) {
|
|
205
|
+
const host = (0, network_hosts_1.normalizeNetworkHost)(rawHost);
|
|
206
|
+
return [host];
|
|
207
|
+
}
|
|
208
|
+
function normalizeDays(days) {
|
|
209
|
+
if (!Number.isInteger(days) || days < 1 || days > 36500) {
|
|
210
|
+
throw new Error('--days must be an integer in [1, 36500]');
|
|
211
|
+
}
|
|
212
|
+
return days;
|
|
213
|
+
}
|
|
214
|
+
function validateCertificateHost(host) {
|
|
215
|
+
if (host === '') {
|
|
216
|
+
return { kind: 'invalid', message: '--host must not be empty' };
|
|
217
|
+
}
|
|
218
|
+
if (host.includes('/')) {
|
|
219
|
+
return {
|
|
220
|
+
kind: 'invalid',
|
|
221
|
+
message: '--host must be an exact DNS name or IP address; CIDR/IP ranges are not valid',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (!(0, network_hosts_1.isLanHttpsHost)(host)) {
|
|
225
|
+
return {
|
|
226
|
+
kind: 'invalid',
|
|
227
|
+
message: '--host must not be localhost, loopback, 127.0.0.0/8, 169.254.0.0/16, ::1, fe80::/10, 0.0.0.0, or ::',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (net.isIP(host) !== 0)
|
|
231
|
+
return { kind: 'valid' };
|
|
232
|
+
if (!isValidDnsHost(host)) {
|
|
233
|
+
return {
|
|
234
|
+
kind: 'invalid',
|
|
235
|
+
message: '--host must be a DNS name, an IPv4 address, or an IPv6 address',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return { kind: 'valid' };
|
|
239
|
+
}
|
|
240
|
+
function isValidDnsHost(host) {
|
|
241
|
+
if (host.length > 253)
|
|
242
|
+
return false;
|
|
243
|
+
const trimmed = host.endsWith('.') ? host.slice(0, -1) : host;
|
|
244
|
+
if (trimmed === '')
|
|
245
|
+
return false;
|
|
246
|
+
const labels = trimmed.split('.');
|
|
247
|
+
for (const label of labels) {
|
|
248
|
+
if (label.length < 1 || label.length > 63)
|
|
249
|
+
return false;
|
|
250
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/.test(label))
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
async function assertPathDoesNotExist(filePathAbs) {
|
|
256
|
+
try {
|
|
257
|
+
await fs.access(filePathAbs);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (isNodeErrorWithCode(error, 'ENOENT'))
|
|
261
|
+
return;
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
throw new Error(`Refusing to overwrite existing certificate file: ${filePathAbs}`);
|
|
265
|
+
}
|
|
266
|
+
function certificateBasenameForHosts(hosts) {
|
|
267
|
+
const host = hosts[0];
|
|
268
|
+
const safeHost = host
|
|
269
|
+
.toLowerCase()
|
|
270
|
+
.replace(/[^a-z0-9.-]+/g, '_')
|
|
271
|
+
.replace(/^_+|_+$/g, '');
|
|
272
|
+
const digest = (0, node_crypto_1.createHash)('sha256').update(hosts.join('\0')).digest('hex').slice(0, 8);
|
|
273
|
+
return `dominds-${safeHost || 'host'}-${digest}`;
|
|
274
|
+
}
|
|
275
|
+
function buildSubjectAltName(hosts) {
|
|
276
|
+
return hosts
|
|
277
|
+
.map((host) => {
|
|
278
|
+
const ipVersion = net.isIP(host);
|
|
279
|
+
return ipVersion !== 0 ? `IP:${host}` : `DNS:${host}`;
|
|
280
|
+
})
|
|
281
|
+
.join(',');
|
|
282
|
+
}
|
|
283
|
+
function escapeOpenSslSubjectValue(value) {
|
|
284
|
+
return value.replace(/\\/g, '\\\\').replace(/\//g, '\\/');
|
|
285
|
+
}
|
|
286
|
+
function isCertificateFilename(filename) {
|
|
287
|
+
return CERT_EXTENSIONS.some((ext) => filename.endsWith(ext)) && !isKeyFilename(filename);
|
|
288
|
+
}
|
|
289
|
+
function isKeyFilename(filename) {
|
|
290
|
+
return KEY_SUFFIXES.some((suffix) => filename.endsWith(suffix));
|
|
291
|
+
}
|
|
292
|
+
async function findKeyPathForCert(params) {
|
|
293
|
+
const baseNames = certBaseNameVariants(params.certEntry);
|
|
294
|
+
for (const baseName of baseNames) {
|
|
295
|
+
for (const suffix of KEY_SUFFIXES) {
|
|
296
|
+
const keyPath = path.join(params.certsDirAbs, `${baseName}${suffix}`);
|
|
297
|
+
try {
|
|
298
|
+
await fs.access(keyPath);
|
|
299
|
+
return keyPath;
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
if (isNodeErrorWithCode(error, 'ENOENT'))
|
|
303
|
+
continue;
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
function certBaseNameVariants(certEntry) {
|
|
311
|
+
const variants = [];
|
|
312
|
+
for (const ext of CERT_EXTENSIONS) {
|
|
313
|
+
if (certEntry.endsWith(ext)) {
|
|
314
|
+
variants.push(certEntry.slice(0, -ext.length));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (certEntry.endsWith('.crt.pem')) {
|
|
318
|
+
variants.push(certEntry.slice(0, -'.crt.pem'.length));
|
|
319
|
+
}
|
|
320
|
+
return Array.from(new Set(variants));
|
|
321
|
+
}
|
|
322
|
+
async function loadAndValidateCandidate(params) {
|
|
323
|
+
let certPem;
|
|
324
|
+
let keyPem;
|
|
325
|
+
let cert;
|
|
326
|
+
try {
|
|
327
|
+
certPem = await fs.readFile(params.certPath, 'utf8');
|
|
328
|
+
keyPem = await fs.readFile(params.keyPath, 'utf8');
|
|
329
|
+
cert = new node_crypto_1.X509Certificate(certPem);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
return {
|
|
333
|
+
kind: 'diagnostic',
|
|
334
|
+
diagnostic: {
|
|
335
|
+
level: 'warn',
|
|
336
|
+
code: 'invalid_cert_file',
|
|
337
|
+
message: `Invalid certificate file '${params.certPath}': ${formatUnknownError(error)}`,
|
|
338
|
+
certPath: params.certPath,
|
|
339
|
+
keyPath: params.keyPath,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const matchedHost = findCertificateMatchedHost(cert, params.matchHosts);
|
|
344
|
+
if (matchedHost === null) {
|
|
345
|
+
return { kind: 'no_match' };
|
|
346
|
+
}
|
|
347
|
+
if (!certificateIsCurrentlyValid(cert, params.now)) {
|
|
348
|
+
return {
|
|
349
|
+
kind: 'diagnostic',
|
|
350
|
+
diagnostic: {
|
|
351
|
+
level: 'warn',
|
|
352
|
+
code: 'expired_or_not_yet_valid',
|
|
353
|
+
message: `Certificate is not currently valid: ${params.certPath}`,
|
|
354
|
+
certPath: params.certPath,
|
|
355
|
+
keyPath: params.keyPath,
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
tls.createSecureContext({ cert: certPem, key: keyPem });
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
return {
|
|
364
|
+
kind: 'diagnostic',
|
|
365
|
+
diagnostic: {
|
|
366
|
+
level: 'warn',
|
|
367
|
+
code: 'invalid_key_pair',
|
|
368
|
+
message: `Certificate and private key do not form a valid TLS pair: ${formatUnknownError(error)}`,
|
|
369
|
+
certPath: params.certPath,
|
|
370
|
+
keyPath: params.keyPath,
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
kind: 'match',
|
|
376
|
+
certificate: {
|
|
377
|
+
cert: certPem,
|
|
378
|
+
key: keyPem,
|
|
379
|
+
certPath: params.certPath,
|
|
380
|
+
keyPath: params.keyPath,
|
|
381
|
+
matchedHost: matchedHost.host,
|
|
382
|
+
matchedHostPriority: matchedHost.priority,
|
|
383
|
+
validTo: new Date(Date.parse(cert.validTo)),
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function certificateIsCurrentlyValid(cert, now) {
|
|
388
|
+
const validFromMs = Date.parse(cert.validFrom);
|
|
389
|
+
const validToMs = Date.parse(cert.validTo);
|
|
390
|
+
if (!Number.isFinite(validFromMs) || !Number.isFinite(validToMs)) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
const nowMs = now.getTime();
|
|
394
|
+
return validFromMs <= nowMs && nowMs <= validToMs;
|
|
395
|
+
}
|
|
396
|
+
function findCertificateMatchedHost(cert, matchHosts) {
|
|
397
|
+
for (let i = 0; i < matchHosts.length; i += 1) {
|
|
398
|
+
const host = matchHosts[i];
|
|
399
|
+
if (certificateMatchesHost(cert, host)) {
|
|
400
|
+
return { host, priority: i };
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
function certificateMatchesHost(cert, host) {
|
|
406
|
+
const normalizedHost = (0, network_hosts_1.normalizeNetworkHost)(host);
|
|
407
|
+
const ipVersion = net.isIP(normalizedHost);
|
|
408
|
+
if (ipVersion !== 0) {
|
|
409
|
+
return cert.checkIP(normalizedHost) !== undefined;
|
|
410
|
+
}
|
|
411
|
+
return cert.checkHost(normalizedHost) !== undefined;
|
|
412
|
+
}
|
|
413
|
+
function isNodeErrorWithCode(error, code) {
|
|
414
|
+
return error instanceof Error && error.code === code;
|
|
415
|
+
}
|
|
416
|
+
function formatUnknownError(error) {
|
|
417
|
+
return error instanceof Error ? error.message : String(error);
|
|
418
|
+
}
|
|
@@ -15,14 +15,11 @@ const promises_1 = __importDefault(require("fs/promises"));
|
|
|
15
15
|
const path_1 = __importDefault(require("path"));
|
|
16
16
|
const time_1 = require("@longrun-ai/kernel/utils/time");
|
|
17
17
|
const log_1 = require("../log");
|
|
18
|
+
const supervisor_protocol_1 = require("../supervisor-protocol");
|
|
18
19
|
const dominds_running_version_1 = require("./dominds-running-version");
|
|
19
20
|
const log = (0, log_1.createLogger)('dominds-self-update');
|
|
20
21
|
const BACKGROUND_CHECK_INTERVAL_MS = 30 * 60 * 1000;
|
|
21
22
|
const LATEST_VERSION_CHECK_TIMEOUT_MS = 60000;
|
|
22
|
-
const RESTART_PORT_RELEASE_TIMEOUT_MS = 15000;
|
|
23
|
-
const RESTART_PORT_PROBE_INTERVAL_MS = 150;
|
|
24
|
-
const RESTART_EXIT_GRACE_MS = 1000;
|
|
25
|
-
const RESTART_FORCE_KILL_AFTER_MS = 30000;
|
|
26
23
|
const COMMAND_OUTPUT_LOG_LIMIT = 2000;
|
|
27
24
|
const PROXY_URL_ENV_KEYS = new Set([
|
|
28
25
|
'HTTP_PROXY',
|
|
@@ -34,7 +31,6 @@ const PROXY_URL_ENV_KEYS = new Set([
|
|
|
34
31
|
]);
|
|
35
32
|
const IDLE_RESTART_STATE = { kind: 'idle' };
|
|
36
33
|
const PROCESS_START_ARGV = [...process.argv];
|
|
37
|
-
const PROCESS_START_EXEC_ARGV = [...process.execArgv];
|
|
38
34
|
const PROCESS_START_CWD = process.cwd();
|
|
39
35
|
let runtimeConfig = null;
|
|
40
36
|
let latestObservation = { kind: 'unknown' };
|
|
@@ -86,19 +82,6 @@ function detectRunKind(mode) {
|
|
|
86
82
|
}
|
|
87
83
|
return 'global';
|
|
88
84
|
}
|
|
89
|
-
function hasInteractiveConsole() {
|
|
90
|
-
return Boolean(process.stdin.isTTY || process.stdout.isTTY || process.stderr.isTTY);
|
|
91
|
-
}
|
|
92
|
-
function getRestartHelperStdio() {
|
|
93
|
-
return hasInteractiveConsole() ? 'inherit' : 'ignore';
|
|
94
|
-
}
|
|
95
|
-
function getRestartPortProbeHost(host) {
|
|
96
|
-
if (host === '0.0.0.0')
|
|
97
|
-
return '127.0.0.1';
|
|
98
|
-
if (host === '::')
|
|
99
|
-
return '::1';
|
|
100
|
-
return host;
|
|
101
|
-
}
|
|
102
85
|
function normalizeComparablePath(value) {
|
|
103
86
|
const normalized = path_1.default.normalize(value);
|
|
104
87
|
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
@@ -118,9 +101,6 @@ function buildRestartTraceContext() {
|
|
|
118
101
|
].join('-'));
|
|
119
102
|
return { debugDir, traceFile };
|
|
120
103
|
}
|
|
121
|
-
function getRestartHelperEntrypoint() {
|
|
122
|
-
return path_1.default.resolve(__dirname, 'dominds-self-update-restart-helper.js');
|
|
123
|
-
}
|
|
124
104
|
async function appendRestartTrace(trace, event, details = {}) {
|
|
125
105
|
const payload = {
|
|
126
106
|
...details,
|
|
@@ -170,13 +150,6 @@ function getCurrentProcessEntrypoint() {
|
|
|
170
150
|
}
|
|
171
151
|
return entrypoint;
|
|
172
152
|
}
|
|
173
|
-
function buildCurrentProcessRestartArgs() {
|
|
174
|
-
getCurrentProcessEntrypoint();
|
|
175
|
-
return [...PROCESS_START_EXEC_ARGV, ...PROCESS_START_ARGV.slice(1)];
|
|
176
|
-
}
|
|
177
|
-
function buildCurrentDomindsCliArgs() {
|
|
178
|
-
return PROCESS_START_ARGV.slice(2);
|
|
179
|
-
}
|
|
180
153
|
function getNpxWorkspaceDirAbs() {
|
|
181
154
|
const entrypoint = getCurrentProcessEntrypoint().replace(/\\/g, '/');
|
|
182
155
|
const marker = '/node_modules/dominds/';
|
|
@@ -223,28 +196,12 @@ async function readNpxDomindsSpec() {
|
|
|
223
196
|
}
|
|
224
197
|
return null;
|
|
225
198
|
}
|
|
226
|
-
async function buildNpxLatestRestartLaunchSpec() {
|
|
227
|
-
const npm = await resolveNpmCommandSpec();
|
|
228
|
-
return {
|
|
229
|
-
command: npm.command,
|
|
230
|
-
args: [...npm.args, 'exec', '-y', '--', 'dominds@latest', ...buildCurrentDomindsCliArgs()],
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
function buildCurrentProcessRestartLaunchSpec() {
|
|
234
|
-
return {
|
|
235
|
-
command: process.execPath,
|
|
236
|
-
args: buildCurrentProcessRestartArgs(),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
199
|
function truncateCommandOutput(value) {
|
|
240
200
|
const raw = typeof value === 'string' ? value.trim() : '';
|
|
241
201
|
if (raw.length <= COMMAND_OUTPUT_LOG_LIMIT)
|
|
242
202
|
return raw;
|
|
243
203
|
return `${raw.slice(0, COMMAND_OUTPUT_LOG_LIMIT)}...[truncated ${raw.length - COMMAND_OUTPUT_LOG_LIMIT} chars]`;
|
|
244
204
|
}
|
|
245
|
-
function delayMs(ms) {
|
|
246
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
247
|
-
}
|
|
248
205
|
function formatPathEnvExcerpt(pathEnv) {
|
|
249
206
|
if (pathEnv === null || pathEnv.trim() === '')
|
|
250
207
|
return null;
|
|
@@ -1382,57 +1339,31 @@ async function installLatestDominds() {
|
|
|
1382
1339
|
publishStatusUpdateSoon();
|
|
1383
1340
|
return await getDomindsSelfUpdateStatus();
|
|
1384
1341
|
}
|
|
1385
|
-
async function
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
cwd:
|
|
1393
|
-
host:
|
|
1394
|
-
port:
|
|
1395
|
-
retiringPid: process.pid,
|
|
1396
|
-
forceKillAfterMs: RESTART_FORCE_KILL_AFTER_MS,
|
|
1397
|
-
probeIntervalMs: RESTART_PORT_PROBE_INTERVAL_MS,
|
|
1398
|
-
portReleaseTimeoutMs: RESTART_PORT_RELEASE_TIMEOUT_MS,
|
|
1399
|
-
stdioMode,
|
|
1342
|
+
async function requestSupervisorRestart(params) {
|
|
1343
|
+
if (typeof process.send !== 'function') {
|
|
1344
|
+
throw new Error('Dominds restart requires the dominds supervisor process; this runner has no supervisor IPC channel');
|
|
1345
|
+
}
|
|
1346
|
+
const cfg = assertRuntimeConfig();
|
|
1347
|
+
const message = {
|
|
1348
|
+
type: supervisor_protocol_1.DOMINDS_SUPERVISOR_RESTART_WEBUI,
|
|
1349
|
+
cwd: process.cwd(),
|
|
1350
|
+
host: cfg.host,
|
|
1351
|
+
port: cfg.port,
|
|
1400
1352
|
traceFile: params.trace.traceFile,
|
|
1401
1353
|
debugDir: params.trace.debugDir,
|
|
1354
|
+
currentVersion: params.currentVersion,
|
|
1355
|
+
targetVersion: params.targetVersion,
|
|
1356
|
+
runKind: params.runKind,
|
|
1402
1357
|
};
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
const helperPid = typeof helper.pid === 'number' ? helper.pid : null;
|
|
1411
|
-
const helperSpawned = new Promise((resolve, reject) => {
|
|
1412
|
-
helper.once('spawn', () => {
|
|
1413
|
-
appendRestartTraceSoon(params.trace, 'parent.helper_spawn_event', {
|
|
1414
|
-
helperPid,
|
|
1415
|
-
});
|
|
1416
|
-
resolve(helperPid);
|
|
1417
|
-
});
|
|
1418
|
-
helper.once('error', (error) => {
|
|
1419
|
-
appendRestartTraceSoon(params.trace, 'parent.helper_error_event', {
|
|
1420
|
-
message: error.message,
|
|
1421
|
-
stack: error.stack ?? null,
|
|
1422
|
-
});
|
|
1423
|
-
reject(error);
|
|
1424
|
-
});
|
|
1425
|
-
});
|
|
1426
|
-
helper.once('exit', (code, signal) => {
|
|
1427
|
-
appendRestartTraceSoon(params.trace, 'parent.helper_exit_event', {
|
|
1428
|
-
code,
|
|
1429
|
-
signal,
|
|
1358
|
+
await new Promise((resolve, reject) => {
|
|
1359
|
+
process.send?.(message, (error) => {
|
|
1360
|
+
if (error !== null) {
|
|
1361
|
+
reject(error);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
resolve();
|
|
1430
1365
|
});
|
|
1431
1366
|
});
|
|
1432
|
-
if (stdioMode !== 'inherit') {
|
|
1433
|
-
helper.unref();
|
|
1434
|
-
}
|
|
1435
|
-
return await helperSpawned;
|
|
1436
1367
|
}
|
|
1437
1368
|
async function stopAndExitForRestart() {
|
|
1438
1369
|
const cfg = assertRuntimeConfig();
|
|
@@ -1444,7 +1375,6 @@ async function stopAndExitForRestart() {
|
|
|
1444
1375
|
await appendRestartTraceBestEffort(trace, 'parent.stop_and_exit.start', {
|
|
1445
1376
|
host: cfg.host,
|
|
1446
1377
|
port: cfg.port,
|
|
1447
|
-
exitGraceMs: RESTART_EXIT_GRACE_MS,
|
|
1448
1378
|
});
|
|
1449
1379
|
}
|
|
1450
1380
|
const stopPromise = cfg
|
|
@@ -1481,18 +1411,8 @@ async function stopAndExitForRestart() {
|
|
|
1481
1411
|
return undefined;
|
|
1482
1412
|
});
|
|
1483
1413
|
cfg.closeWebSocketClients();
|
|
1484
|
-
await
|
|
1485
|
-
if (!stopSettled) {
|
|
1486
|
-
log.warn('Exiting Dominds process before graceful HTTP server stop completed during restart', undefined, {
|
|
1487
|
-
host: cfg.host,
|
|
1488
|
-
port: cfg.port,
|
|
1489
|
-
graceMs: RESTART_EXIT_GRACE_MS,
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1414
|
+
await stopPromise;
|
|
1492
1415
|
if (trace !== null) {
|
|
1493
|
-
if (stopSettled) {
|
|
1494
|
-
await stopPromise;
|
|
1495
|
-
}
|
|
1496
1416
|
await appendRestartTraceBestEffort(trace, 'parent.process_exit', {
|
|
1497
1417
|
host: cfg.host,
|
|
1498
1418
|
port: cfg.port,
|
|
@@ -1519,19 +1439,17 @@ async function restartDomindsIntoLatest() {
|
|
|
1519
1439
|
throw new Error('No restartable Dominds update is currently available');
|
|
1520
1440
|
}
|
|
1521
1441
|
const runKind = detectRunKind(cfg.mode);
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
if (previousRestartRequiredState !== null) {
|
|
1525
|
-
launchSpec = buildCurrentProcessRestartLaunchSpec();
|
|
1442
|
+
if (runKind === 'disabled') {
|
|
1443
|
+
throw new Error('Dominds restart requires a production run kind');
|
|
1526
1444
|
}
|
|
1527
|
-
|
|
1445
|
+
const previousRestartRequiredState = restartState.kind === 'restart_required' ? restartState : null;
|
|
1446
|
+
if (previousRestartRequiredState === null && runKind === 'npx') {
|
|
1528
1447
|
const npxSpec = await readNpxDomindsSpec();
|
|
1529
1448
|
if (npxSpec?.kind !== 'latest') {
|
|
1530
1449
|
throw new Error('Dominds npx restart requires launching Dominds with dominds@latest');
|
|
1531
1450
|
}
|
|
1532
|
-
launchSpec = await buildNpxLatestRestartLaunchSpec();
|
|
1533
1451
|
}
|
|
1534
|
-
else {
|
|
1452
|
+
else if (previousRestartRequiredState === null) {
|
|
1535
1453
|
throw new Error('Dominds restart requires a completed install or a restartable session');
|
|
1536
1454
|
}
|
|
1537
1455
|
restartState = { kind: 'restarting', targetVersion: status.targetVersion };
|
|
@@ -1544,29 +1462,23 @@ async function restartDomindsIntoLatest() {
|
|
|
1544
1462
|
runKind,
|
|
1545
1463
|
currentVersion: status.currentVersion,
|
|
1546
1464
|
targetVersion: status.targetVersion,
|
|
1547
|
-
launchCommand: launchSpec.command,
|
|
1548
|
-
launchArgs: launchSpec.args,
|
|
1549
1465
|
restartCwd,
|
|
1550
1466
|
host: cfg.host,
|
|
1551
1467
|
port: cfg.port,
|
|
1552
1468
|
traceFile: trace.traceFile,
|
|
1553
1469
|
});
|
|
1554
|
-
|
|
1555
|
-
command: launchSpec.command,
|
|
1556
|
-
args: launchSpec.args,
|
|
1557
|
-
cwd: restartCwd,
|
|
1558
|
-
host: cfg.host,
|
|
1559
|
-
port: cfg.port,
|
|
1470
|
+
await requestSupervisorRestart({
|
|
1560
1471
|
trace,
|
|
1472
|
+
runKind,
|
|
1473
|
+
currentVersion: status.currentVersion,
|
|
1474
|
+
targetVersion: status.targetVersion,
|
|
1561
1475
|
});
|
|
1562
|
-
await appendRestartTraceBestEffort(trace, 'parent.
|
|
1563
|
-
helperPid,
|
|
1476
|
+
await appendRestartTraceBestEffort(trace, 'parent.supervisor_restart_requested', {
|
|
1564
1477
|
retiringPid: process.pid,
|
|
1565
1478
|
host: cfg.host,
|
|
1566
1479
|
port: cfg.port,
|
|
1567
1480
|
});
|
|
1568
|
-
log.info('Dominds restart
|
|
1569
|
-
helperPid,
|
|
1481
|
+
log.info('Dominds supervisor restart requested', undefined, {
|
|
1570
1482
|
traceFile: trace.traceFile,
|
|
1571
1483
|
});
|
|
1572
1484
|
setImmediate(() => {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function normalizeNetworkHost(host: string): string;
|
|
2
|
+
export declare function isLanHttpsHost(host: string): boolean;
|
|
3
|
+
export declare function resolveHttpUrlHostForBindHost(bindHost: string): string;
|
|
4
|
+
export declare function resolveLanHttpsHostsForBindHost(bindHost: string): Promise<readonly string[]>;
|
|
5
|
+
export declare function resolveDefaultLanHttpsHosts(): Promise<readonly string[]>;
|