ftown-bridge 0.19.19 → 0.19.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/dist/index.js +228 -3
- package/dist/index.js.map +1 -1
- package/dist/solo/contract.d.ts +104 -0
- package/dist/solo/contract.js +248 -0
- package/dist/solo/contract.js.map +1 -0
- package/dist/solo/hub-manager.d.ts +85 -0
- package/dist/solo/hub-manager.js +381 -0
- package/dist/solo/hub-manager.js.map +1 -0
- package/dist/solo/panel-manager.d.ts +129 -0
- package/dist/solo/panel-manager.js +715 -0
- package/dist/solo/panel-manager.js.map +1 -0
- package/dist/solo/solo-auth.d.ts +25 -0
- package/dist/solo/solo-auth.js +83 -0
- package/dist/solo/solo-auth.js.map +1 -0
- package/dist/solo/solo-server.d.ts +105 -0
- package/dist/solo/solo-server.js +399 -0
- package/dist/solo/solo-server.js.map +1 -0
- package/dist/solo/ws-proxy.d.ts +55 -0
- package/dist/solo/ws-proxy.js +228 -0
- package/dist/solo/ws-proxy.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solo hub manager — downloads, verifies, configures, spawns and stops the
|
|
3
|
+
* managed Centrifugo child (contract ownership: solo/hub-manager.ts(+test)).
|
|
4
|
+
*
|
|
5
|
+
* Dependency policy (deliberate): node builtins + node:child_process + the
|
|
6
|
+
* SYSTEM `tar` binary only — no new npm deps. tar(1) ships with macOS and
|
|
7
|
+
* Linux, the only platforms resolvePlatformTriple() supports, so archive
|
|
8
|
+
* listing/extraction needs no bundled extraction library.
|
|
9
|
+
*
|
|
10
|
+
* Invariants owned here: S6 (embedded CENTRIFUGO_SHA256 verification before
|
|
11
|
+
* anything is extracted), S7 (0600 config + pidfile), S15 (child argv is
|
|
12
|
+
* exactly [binPath, '-c', configPath]; no secrets in argv/env), S16 (logs
|
|
13
|
+
* never echo secrets — stderr tails are sanitized), L2 (stale pidfile reap
|
|
14
|
+
* under dataDir/solo/ on boot).
|
|
15
|
+
*/
|
|
16
|
+
import { execFile as execFileCb, spawn as nodeSpawn } from 'node:child_process';
|
|
17
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
19
|
+
import { chmod, copyFile, mkdir, realpath, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { promisify } from 'node:util';
|
|
22
|
+
import { CENTRIFUGO_SHA256, CENTRIFUGO_VERSION, HUB_JWT_AUDIENCE, } from './contract.js';
|
|
23
|
+
const execFile = promisify(execFileCb);
|
|
24
|
+
const defaultSpawn = (command, args, options) => nodeSpawn(command, args, options);
|
|
25
|
+
export class UnsupportedPlatformError extends Error {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'UnsupportedPlatformError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class ChecksumError extends Error {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = 'ChecksumError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class ArchiveSafetyError extends Error {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = 'ArchiveSafetyError';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class HubStartError extends Error {
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = 'HubStartError';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// ---------- Platform triple & release asset URL ----------
|
|
50
|
+
const ARCH_ALIASES = {
|
|
51
|
+
x64: 'amd64',
|
|
52
|
+
arm64: 'arm64',
|
|
53
|
+
};
|
|
54
|
+
export function resolvePlatformTriple(platform = process.platform, arch = process.arch) {
|
|
55
|
+
const archAlias = ARCH_ALIASES[arch];
|
|
56
|
+
if ((platform === 'darwin' || platform === 'linux') && archAlias !== undefined) {
|
|
57
|
+
return `${platform}-${archAlias}`;
|
|
58
|
+
}
|
|
59
|
+
throw new UnsupportedPlatformError(`unsupported platform/arch pair: ${platform}/${arch}`);
|
|
60
|
+
}
|
|
61
|
+
export function assetUrl(version, triple) {
|
|
62
|
+
const bare = version.startsWith('v') ? version.slice(1) : version;
|
|
63
|
+
// Release assets use UNDERSCORES between parts (centrifugo_5.4.9_darwin_arm64.tar.gz)
|
|
64
|
+
// while our platform triple is dashed — verified against the v5.4.9 checksums.txt.
|
|
65
|
+
const assetTriple = triple.split('-').join('_');
|
|
66
|
+
return `https://github.com/centrifugal/centrifugo/releases/download/${version}/centrifugo_${bare}_${assetTriple}.tar.gz`;
|
|
67
|
+
}
|
|
68
|
+
// ---------- Binary ensure (S6) ----------
|
|
69
|
+
const MAX_ARCHIVE_ENTRIES = 200;
|
|
70
|
+
const TAIL_LIMIT_BYTES = 8192;
|
|
71
|
+
const SANITIZED_TAIL_LIMIT = 2000;
|
|
72
|
+
function sha256Hex(data) {
|
|
73
|
+
return createHash('sha256').update(data).digest('hex');
|
|
74
|
+
}
|
|
75
|
+
async function sha256File(filePath) {
|
|
76
|
+
return sha256Hex(new Uint8Array(await readFile(filePath)));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validates tar entries against the extraction allowlist (S4/S17):
|
|
80
|
+
* names must be relative without '..' segments; entry types must be regular
|
|
81
|
+
* file ('-') or directory ('d') — symlinks, hardlinks, devices are refused.
|
|
82
|
+
*/
|
|
83
|
+
export function assertSafeTarEntries(names, verboseLines) {
|
|
84
|
+
if (names.length !== verboseLines.length) {
|
|
85
|
+
throw new ArchiveSafetyError('tar listing mismatch between name and verbose output');
|
|
86
|
+
}
|
|
87
|
+
for (const name of names) {
|
|
88
|
+
if (name.startsWith('/')) {
|
|
89
|
+
throw new ArchiveSafetyError(`refusing absolute archive entry path`);
|
|
90
|
+
}
|
|
91
|
+
if (name.split('/').includes('..')) {
|
|
92
|
+
throw new ArchiveSafetyError(`refusing archive entry escaping target dir`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const line of verboseLines) {
|
|
96
|
+
const kind = line.charAt(0);
|
|
97
|
+
if (kind !== '-' && kind !== 'd') {
|
|
98
|
+
throw new ArchiveSafetyError(`refusing non-regular-file archive entry (type "${kind}")`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function locateCentrifugoBinary(root) {
|
|
103
|
+
const found = [];
|
|
104
|
+
const walk = async (dir) => {
|
|
105
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
106
|
+
const full = path.join(dir, entry.name);
|
|
107
|
+
if (entry.isSymbolicLink()) {
|
|
108
|
+
throw new ArchiveSafetyError('symlink present in extracted hub archive');
|
|
109
|
+
}
|
|
110
|
+
if (entry.isDirectory()) {
|
|
111
|
+
await walk(full);
|
|
112
|
+
}
|
|
113
|
+
else if (entry.isFile() && entry.name === 'centrifugo') {
|
|
114
|
+
found.push(full);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
await walk(root);
|
|
119
|
+
if (found.length !== 1) {
|
|
120
|
+
throw new ChecksumError(`unexpected hub archive layout: expected exactly one "centrifugo" entry, found ${found.length}`);
|
|
121
|
+
}
|
|
122
|
+
const rootReal = await realpath(root);
|
|
123
|
+
const binaryReal = await realpath(found[0]);
|
|
124
|
+
if (!binaryReal.startsWith(rootReal + path.sep)) {
|
|
125
|
+
throw new ArchiveSafetyError('extracted hub binary resolved outside its sandbox');
|
|
126
|
+
}
|
|
127
|
+
return found[0];
|
|
128
|
+
}
|
|
129
|
+
export async function ensureHubBinary(opts) {
|
|
130
|
+
const version = opts.version ?? CENTRIFUGO_VERSION;
|
|
131
|
+
const triple = resolvePlatformTriple();
|
|
132
|
+
const expectedSha = (opts.digests ?? CENTRIFUGO_SHA256)[triple];
|
|
133
|
+
if (expectedSha === undefined) {
|
|
134
|
+
throw new ChecksumError(`no embedded sha256 digest for platform triple "${triple}"`);
|
|
135
|
+
}
|
|
136
|
+
const soloDir = path.join(opts.dataDir, 'solo');
|
|
137
|
+
const binDir = path.join(soloDir, 'bin');
|
|
138
|
+
const target = path.join(binDir, `centrifugo-${version}`);
|
|
139
|
+
// The embedded digests describe the RELEASE ARCHIVE, not the unpacked binary,
|
|
140
|
+
// so the cache records the verified archive digest in a sidecar at install
|
|
141
|
+
// time; a cached install is trusted only if that record still matches.
|
|
142
|
+
const targetSidecar = `${target}.sha256`;
|
|
143
|
+
await mkdir(binDir, { recursive: true });
|
|
144
|
+
const sidecarSha = existsSync(targetSidecar)
|
|
145
|
+
? (await readFile(targetSidecar, 'utf8').catch(() => '')).trim()
|
|
146
|
+
: '';
|
|
147
|
+
if (existsSync(target) &&
|
|
148
|
+
(sidecarSha === expectedSha || (await sha256File(target)) === expectedSha)) {
|
|
149
|
+
return target;
|
|
150
|
+
}
|
|
151
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
152
|
+
const url = assetUrl(version, triple);
|
|
153
|
+
const res = await fetchImpl(url);
|
|
154
|
+
if (!res.ok) {
|
|
155
|
+
throw new ChecksumError(`hub binary download failed: HTTP ${res.status} from ${url}`);
|
|
156
|
+
}
|
|
157
|
+
const archive = Buffer.from(await res.arrayBuffer());
|
|
158
|
+
const actualSha = sha256Hex(archive);
|
|
159
|
+
if (actualSha !== expectedSha) {
|
|
160
|
+
// S6: mismatch aborts BEFORE any extraction — nothing is ever unpacked or run.
|
|
161
|
+
throw new ChecksumError(`hub archive checksum mismatch for ${triple}: expected sha256 ${expectedSha}, got ${actualSha}; nothing was extracted`);
|
|
162
|
+
}
|
|
163
|
+
const archivePath = path.join(soloDir, `.download-${randomUUID()}.tar.gz`);
|
|
164
|
+
const extractDir = path.join(soloDir, `.extract-${randomUUID()}`);
|
|
165
|
+
await writeFile(archivePath, archive, { mode: 0o600 });
|
|
166
|
+
await mkdir(extractDir, { recursive: true });
|
|
167
|
+
try {
|
|
168
|
+
const tarOpts = { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 };
|
|
169
|
+
const names = (await execFile('tar', ['-tzf', archivePath], tarOpts)).stdout
|
|
170
|
+
.split('\n')
|
|
171
|
+
.map((line) => line.trim())
|
|
172
|
+
.filter((line) => line.length > 0);
|
|
173
|
+
const verbose = (await execFile('tar', ['-tvzf', archivePath], tarOpts)).stdout
|
|
174
|
+
.split('\n')
|
|
175
|
+
.filter((line) => line.trim().length > 0);
|
|
176
|
+
if (names.length > MAX_ARCHIVE_ENTRIES) {
|
|
177
|
+
throw new ArchiveSafetyError(`hub archive has too many entries (${names.length})`);
|
|
178
|
+
}
|
|
179
|
+
assertSafeTarEntries(names, verbose);
|
|
180
|
+
await execFile('tar', ['-xzf', archivePath, '-C', extractDir], tarOpts);
|
|
181
|
+
const extracted = await locateCentrifugoBinary(extractDir);
|
|
182
|
+
try {
|
|
183
|
+
await rename(extracted, target);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
if (err.code !== 'EXDEV')
|
|
187
|
+
throw err;
|
|
188
|
+
await copyFile(extracted, target);
|
|
189
|
+
}
|
|
190
|
+
await chmod(target, 0o700);
|
|
191
|
+
await writeFile(targetSidecar, `${expectedSha}\n`, { mode: 0o600 });
|
|
192
|
+
return target;
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
await rm(extractDir, { recursive: true, force: true });
|
|
196
|
+
await rm(archivePath, { force: true });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// ---------- Config write (frozen keys, S7) ----------
|
|
200
|
+
/**
|
|
201
|
+
* Frozen solo hub configuration. Security keys mirror the contract block
|
|
202
|
+
* translated to actual centrifugo v5 flat key names (verified against the
|
|
203
|
+
* v5.4.9 source defaults): `admin` disables the admin web UI, `api_disable`
|
|
204
|
+
* disables the server HTTP API, `allow_anonymous_connect_without_token=false`
|
|
205
|
+
* means no anonymous client connections. address/port are operational keys so
|
|
206
|
+
* the child binds loopback on the ephemeral port the integrator assigned.
|
|
207
|
+
*/
|
|
208
|
+
function hubConfigObject(port, secret) {
|
|
209
|
+
return {
|
|
210
|
+
address: '127.0.0.1',
|
|
211
|
+
port,
|
|
212
|
+
token_hmac_secret_key: secret,
|
|
213
|
+
token_audience: HUB_JWT_AUDIENCE,
|
|
214
|
+
allowed_origins: [],
|
|
215
|
+
websocket_compression: false,
|
|
216
|
+
allow_anonymous_connect_without_token: false,
|
|
217
|
+
admin: false,
|
|
218
|
+
api_disable: true,
|
|
219
|
+
health: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export async function writeHubConfig(configPath, opts) {
|
|
223
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
224
|
+
const body = JSON.stringify(hubConfigObject(opts.port, opts.secret), null, 2) + '\n';
|
|
225
|
+
await writeFile(configPath, body, { encoding: 'utf8', mode: 0o600 });
|
|
226
|
+
// Enforce even when overwriting a pre-existing file (mode applies at creation).
|
|
227
|
+
await chmod(configPath, 0o600);
|
|
228
|
+
}
|
|
229
|
+
const DEFAULT_HEALTH_INTERVAL_MS = 500;
|
|
230
|
+
const DEFAULT_HEALTH_TRY_TIMEOUT_MS = 1000;
|
|
231
|
+
const DEFAULT_HEALTH_DEADLINE_MS = 30_000;
|
|
232
|
+
const STOP_GRACE_MS = 3_000;
|
|
233
|
+
const STOP_POLL_MS = 100;
|
|
234
|
+
export function hubPidFilePath(dataDir) {
|
|
235
|
+
return path.join(dataDir, 'solo', 'hub.pid');
|
|
236
|
+
}
|
|
237
|
+
function pidAlive(pid) {
|
|
238
|
+
try {
|
|
239
|
+
process.kill(pid, 0);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
return err.code === 'EPERM';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function sleep(ms) {
|
|
247
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
248
|
+
}
|
|
249
|
+
function errorMessage(err) {
|
|
250
|
+
return err instanceof Error ? err.message : String(err);
|
|
251
|
+
}
|
|
252
|
+
function appendTail(prev, chunk) {
|
|
253
|
+
return (prev + chunk.toString('utf8')).slice(-TAIL_LIMIT_BYTES);
|
|
254
|
+
}
|
|
255
|
+
/** Strips ANSI escapes and redacts the hub secret (S16). */
|
|
256
|
+
function sanitizeTail(tail, secret) {
|
|
257
|
+
let out = tail.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
258
|
+
if (secret.length >= 8) {
|
|
259
|
+
out = out.split(secret).join('[redacted]');
|
|
260
|
+
}
|
|
261
|
+
return out.slice(-SANITIZED_TAIL_LIMIT);
|
|
262
|
+
}
|
|
263
|
+
async function probeHealth(url, timeoutMs, fetchImpl) {
|
|
264
|
+
try {
|
|
265
|
+
const res = await fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
266
|
+
return res.ok;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async function reapStalePidFile(pidFile) {
|
|
273
|
+
const raw = await readFile(pidFile, 'utf8').catch(() => null);
|
|
274
|
+
await rm(pidFile, { force: true });
|
|
275
|
+
if (raw === null)
|
|
276
|
+
return;
|
|
277
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
278
|
+
if (!Number.isInteger(pid) || pid <= 0 || !pidAlive(pid))
|
|
279
|
+
return;
|
|
280
|
+
try {
|
|
281
|
+
process.kill(pid, 'SIGTERM');
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// lost the race — nothing to reap
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
export async function startHub(opts) {
|
|
288
|
+
let port;
|
|
289
|
+
let secret;
|
|
290
|
+
try {
|
|
291
|
+
const parsed = JSON.parse(await readFile(opts.configPath, 'utf8'));
|
|
292
|
+
port = Number(parsed.port);
|
|
293
|
+
secret = typeof parsed.token_hmac_secret_key === 'string' ? parsed.token_hmac_secret_key : '';
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
throw new HubStartError(`cannot read hub config ${opts.configPath}: ${errorMessage(err)}`);
|
|
297
|
+
}
|
|
298
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
299
|
+
throw new HubStartError(`hub config ${opts.configPath} has no usable port`);
|
|
300
|
+
}
|
|
301
|
+
const pidFile = hubPidFilePath(opts.dataDir);
|
|
302
|
+
await mkdir(path.dirname(pidFile), { recursive: true });
|
|
303
|
+
await reapStalePidFile(pidFile);
|
|
304
|
+
const spawn = opts.spawnImpl ?? defaultSpawn;
|
|
305
|
+
let child;
|
|
306
|
+
try {
|
|
307
|
+
// S15: the ONLY argv the child ever gets.
|
|
308
|
+
child = spawn(opts.binPath, ['-c', opts.configPath], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
throw new HubStartError(`failed to spawn hub binary: ${errorMessage(err)}`);
|
|
312
|
+
}
|
|
313
|
+
const state = { exit: null };
|
|
314
|
+
let stderrTail = '';
|
|
315
|
+
child.stderr?.on('data', (chunk) => {
|
|
316
|
+
stderrTail = appendTail(stderrTail, chunk);
|
|
317
|
+
});
|
|
318
|
+
child.stdout?.on('data', () => { });
|
|
319
|
+
// S16: log the failure kind only — never argv, config contents, or secrets.
|
|
320
|
+
child.on('error', (err) => {
|
|
321
|
+
console.error(`[ftown-solo] hub process error: ${err.message}`);
|
|
322
|
+
});
|
|
323
|
+
child.once('exit', (code, signal) => {
|
|
324
|
+
state.exit = { code, signal };
|
|
325
|
+
});
|
|
326
|
+
if (typeof child.pid === 'number') {
|
|
327
|
+
await writeFile(pidFile, `${child.pid}\n`, { mode: 0o600 });
|
|
328
|
+
await chmod(pidFile, 0o600);
|
|
329
|
+
}
|
|
330
|
+
const base = (opts.healthBaseUrl ?? `http://127.0.0.1:${port}`).replace(/\/+$/, '');
|
|
331
|
+
const healthUrl = `${base}/health`;
|
|
332
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
333
|
+
const intervalMs = opts.healthIntervalMs ?? DEFAULT_HEALTH_INTERVAL_MS;
|
|
334
|
+
const tryTimeoutMs = opts.healthTryTimeoutMs ?? DEFAULT_HEALTH_TRY_TIMEOUT_MS;
|
|
335
|
+
const deadlineMs = opts.healthDeadlineMs ?? DEFAULT_HEALTH_DEADLINE_MS;
|
|
336
|
+
const deadline = Date.now() + deadlineMs;
|
|
337
|
+
let healthy = false;
|
|
338
|
+
while (Date.now() < deadline && !state.exit) {
|
|
339
|
+
if (await probeHealth(healthUrl, tryTimeoutMs, fetchImpl)) {
|
|
340
|
+
healthy = true;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
await sleep(intervalMs);
|
|
344
|
+
}
|
|
345
|
+
if (!healthy) {
|
|
346
|
+
const exitBeforeKill = state.exit;
|
|
347
|
+
child.kill('SIGKILL');
|
|
348
|
+
await rm(pidFile, { force: true });
|
|
349
|
+
const reason = exitBeforeKill
|
|
350
|
+
? `hub exited early (code=${exitBeforeKill.code}, signal=${exitBeforeKill.signal})`
|
|
351
|
+
: `hub did not become healthy within ${deadlineMs}ms`;
|
|
352
|
+
throw new HubStartError(`${reason}; last hub stderr:\n${sanitizeTail(stderrTail, secret)}`);
|
|
353
|
+
}
|
|
354
|
+
return { child, pid: child.pid, port };
|
|
355
|
+
}
|
|
356
|
+
/** SIGTERM → 3s grace → SIGKILL; always unlinks the pidfile. Returns whether a live process was stopped. */
|
|
357
|
+
export async function stopHub(dataDir) {
|
|
358
|
+
const pidFile = hubPidFilePath(dataDir);
|
|
359
|
+
const raw = await readFile(pidFile, 'utf8').catch(() => null);
|
|
360
|
+
await rm(pidFile, { force: true });
|
|
361
|
+
if (raw === null)
|
|
362
|
+
return false;
|
|
363
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
364
|
+
if (!Number.isInteger(pid) || pid <= 0 || !pidAlive(pid))
|
|
365
|
+
return false;
|
|
366
|
+
process.kill(pid, 'SIGTERM');
|
|
367
|
+
const deadline = Date.now() + STOP_GRACE_MS;
|
|
368
|
+
while (pidAlive(pid) && Date.now() < deadline) {
|
|
369
|
+
await sleep(STOP_POLL_MS);
|
|
370
|
+
}
|
|
371
|
+
if (pidAlive(pid)) {
|
|
372
|
+
try {
|
|
373
|
+
process.kill(pid, 'SIGKILL');
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// already gone
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
//# sourceMappingURL=hub-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub-manager.js","sourceRoot":"","sources":["../../src/solo/hub-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ftown Solo — panel-manager.
|
|
3
|
+
*
|
|
4
|
+
* Owns the managed Next.js STANDALONE child ("the panel"):
|
|
5
|
+
* - bundle URL templating from PANEL_BUNDLE_URL_TEMPLATE
|
|
6
|
+
* - bundle fetch + sha256 sidecar verification (S6, first-party trust root)
|
|
7
|
+
* - extraction hardened per S4 (zip-slip, entry-type allowlist) and S17
|
|
8
|
+
* (per-entry / total uncompressed / entry-count caps — decompression bombs)
|
|
9
|
+
* - spawn on 127.0.0.1:<port>, pinned via HOSTNAME/PORT env (private binding),
|
|
10
|
+
* argv carries ONLY [interpreter, server.js] — no secrets ever (S15)
|
|
11
|
+
* - lifecycle L2: pidfile under <dataDir>/solo/panel.pid with stale-orphan
|
|
12
|
+
* reap on boot; SIGTERM → 3s → SIGKILL shutdown
|
|
13
|
+
* - pinned health probe: HEAD http://127.0.0.1:<port>/ , status <500 = up,
|
|
14
|
+
* 1s probe timeout, 45s give-up (cold-start budget)
|
|
15
|
+
*
|
|
16
|
+
* EXTERNAL TOOLING CHOICE (documented): the system `tar` binary is used for
|
|
17
|
+
* extraction (`tar -xzf --no-same-owner`) and for the entry-name pre-listing
|
|
18
|
+
* (`tar -tzf`). Structural enforcement (S4 type allowlist, S17 byte caps) is
|
|
19
|
+
* performed by walking the ustar headers of the gunzipped stream with node
|
|
20
|
+
* builtins only: `-t` output is prose whose layout differs between bsdtar and
|
|
21
|
+
* GNU tar (and is ambiguous for filenames containing whitespace), whereas raw
|
|
22
|
+
* ustar headers are exact, portable, and let us abort MID-STREAM on cap
|
|
23
|
+
* breach instead of after extraction. No third-party dependencies.
|
|
24
|
+
*/
|
|
25
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
26
|
+
import type { ChildProcess } from 'node:child_process';
|
|
27
|
+
/** Any panel bundle acquisition/installation failure. */
|
|
28
|
+
export declare class PanelBundleError extends Error {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
/** sha256 sidecar verification failure (S6) — install aborted, nothing ran. */
|
|
32
|
+
export declare class ChecksumError extends PanelBundleError {
|
|
33
|
+
constructor(message: string);
|
|
34
|
+
}
|
|
35
|
+
/** Panel child failed to spawn or never became healthy. */
|
|
36
|
+
export declare class PanelStartError extends Error {
|
|
37
|
+
constructor(message: string);
|
|
38
|
+
}
|
|
39
|
+
/** Per-entry uncompressed size cap (decompression-bomb defense). */
|
|
40
|
+
export declare const MAX_ENTRY_BYTES: number;
|
|
41
|
+
/** Total uncompressed size cap across all entries. */
|
|
42
|
+
export declare const MAX_TOTAL_BYTES: number;
|
|
43
|
+
/** Maximum number of archive entries. */
|
|
44
|
+
export declare const MAX_ENTRIES = 20000;
|
|
45
|
+
/** Pinned panel health-probe parameters (contract: probe table). */
|
|
46
|
+
export declare const PANEL_HEALTH_PROBE_TIMEOUT_MS = 1000;
|
|
47
|
+
export declare const PANEL_HEALTH_MAX_WAIT_MS = 45000;
|
|
48
|
+
/** L2 shutdown: SIGTERM → 3s → SIGKILL. */
|
|
49
|
+
export declare const STOP_GRACE_MS = 3000;
|
|
50
|
+
/** Substitute every `<version>` placeholder in the frozen template. */
|
|
51
|
+
export declare function panelBundleUrl(version: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Normalize a user- or config-supplied panel version into the bare semver
|
|
54
|
+
* expected by {@link panelBundleUrl} (the template already supplies the
|
|
55
|
+
* `v` prefix for the release-tag segment). Trims surrounding whitespace and
|
|
56
|
+
* strips a single leading `v`/`V`, so `v0.19.20`, `V0.19.20`, and `0.19.20`
|
|
57
|
+
* all resolve to the same bundle URL.
|
|
58
|
+
*/
|
|
59
|
+
export declare function normalizePanelVersion(raw: string): string;
|
|
60
|
+
export interface EnsurePanelBundleOptions {
|
|
61
|
+
/** Bridge data dir (~/.ftown/data). Bundle caches under <dataDir>/solo/panel/. */
|
|
62
|
+
dataDir: string;
|
|
63
|
+
/** UI package version identifying the release asset. */
|
|
64
|
+
version: string;
|
|
65
|
+
/** Injectable fetch (tests run offline). Defaults to global fetch. */
|
|
66
|
+
fetchImpl?: typeof fetch;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Ensure the standalone bundle for `version` is extracted under
|
|
70
|
+
* `<dataDir>/solo/panel/<version>/` and return that directory.
|
|
71
|
+
*
|
|
72
|
+
* Flow: cached-marker short-circuit → download tar.gz → download `.sha256`
|
|
73
|
+
* sidecar → lowercase-hex comparison (mismatch ⇒ cleanup + ChecksumError) →
|
|
74
|
+
* pre-scan (`tar -tzf` names + structural ustar walk enforcing S4/S17) →
|
|
75
|
+
* `tar -xzf --no-same-owner` → write `.ok` marker (0600).
|
|
76
|
+
*/
|
|
77
|
+
export declare function ensurePanelBundle(opts: EnsurePanelBundleOptions): Promise<string>;
|
|
78
|
+
/**
|
|
79
|
+
* Locate the Next standalone output root: the directory containing server.js.
|
|
80
|
+
* Searches the extract root first, then one level deep (deterministic order).
|
|
81
|
+
*/
|
|
82
|
+
export declare function findPanelServerDir(extractDir: string): Promise<string>;
|
|
83
|
+
export interface StartPanelOptions {
|
|
84
|
+
/** Extracted bundle directory (findPanelServerDir locates server.js inside). */
|
|
85
|
+
bundleDir: string;
|
|
86
|
+
/** Private loopback port chosen by the CALLER (free-port guarantee upstream). */
|
|
87
|
+
port: number;
|
|
88
|
+
/** Bridge data dir — pidfile lives at <dataDir>/solo/panel.pid. */
|
|
89
|
+
dataDir: string;
|
|
90
|
+
/** Extra environment merged over process.env (never carries secrets, S15). */
|
|
91
|
+
env?: Record<string, string>;
|
|
92
|
+
/** Injectable spawn (tests). Defaults to node:child_process spawn. */
|
|
93
|
+
spawnImpl?: typeof nodeSpawn;
|
|
94
|
+
/** Injectable health-probe fetch (tests run offline). */
|
|
95
|
+
healthFetchImpl?: typeof fetch;
|
|
96
|
+
/** Test seams for the pinned probe budgets (defaults per contract). */
|
|
97
|
+
probeTimeoutMs?: number;
|
|
98
|
+
probeIntervalMs?: number;
|
|
99
|
+
probeMaxWaitMs?: number;
|
|
100
|
+
}
|
|
101
|
+
export interface RunningPanel {
|
|
102
|
+
pid: number;
|
|
103
|
+
proc: ChildProcess;
|
|
104
|
+
/** Directory containing server.js (the standalone output root). */
|
|
105
|
+
serverDir: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Spawn the panel standalone server pinned to 127.0.0.1:<port>.
|
|
109
|
+
*
|
|
110
|
+
* - argv is exactly [process.execPath, <server.js>] — no secrets on the command
|
|
111
|
+
* line (S15). Secrets reach children only through 0600 files, never argv/env.
|
|
112
|
+
* - HOSTNAME=127.0.0.1 pins the standalone server's binding (private port; the
|
|
113
|
+
* front proxies everything).
|
|
114
|
+
* - A stale/orphaned pidfile is detected and reaped before spawn (L2).
|
|
115
|
+
* - Resolves only after the pinned health probe succeeds (HEAD / <500).
|
|
116
|
+
*/
|
|
117
|
+
export declare function startPanel(opts: StartPanelOptions): Promise<RunningPanel>;
|
|
118
|
+
/**
|
|
119
|
+
* L2 stale-pidfile semantics (own implementation, mirroring hub-manager
|
|
120
|
+
* behavior without importing it): if the pidfile names a LIVE process it is an
|
|
121
|
+
* orphan from a previous run — SIGTERM → 3s → SIGKILL — then remove the
|
|
122
|
+
* pidfile. Dead/stale/garbled pidfiles are simply unlinked.
|
|
123
|
+
*/
|
|
124
|
+
export declare function reapOrStopByPidFile(pidfile: string, graceMs?: number): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Stop the panel previously started under `dataDir`: SIGTERM → 3s → SIGKILL,
|
|
127
|
+
* then remove the pidfile. Idempotent; safe when no pidfile exists.
|
|
128
|
+
*/
|
|
129
|
+
export declare function stopPanel(dataDir: string): Promise<void>;
|