@threadbase-sh/streamer 1.60.0 → 1.61.0
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/cli.cjs +152 -67
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +16 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -31270,7 +31270,7 @@ var require_websocket = __commonJS({
|
|
|
31270
31270
|
var http = require("http");
|
|
31271
31271
|
var net = require("net");
|
|
31272
31272
|
var tls = require("tls");
|
|
31273
|
-
var { randomBytes: randomBytes5, createHash:
|
|
31273
|
+
var { randomBytes: randomBytes5, createHash: createHash9 } = require("crypto");
|
|
31274
31274
|
var { Duplex, Readable: Readable2 } = require("stream");
|
|
31275
31275
|
var { URL: URL2 } = require("url");
|
|
31276
31276
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -31938,7 +31938,7 @@ var require_websocket = __commonJS({
|
|
|
31938
31938
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
31939
31939
|
return;
|
|
31940
31940
|
}
|
|
31941
|
-
const digest =
|
|
31941
|
+
const digest = createHash9("sha1").update(key + GUID).digest("base64");
|
|
31942
31942
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
31943
31943
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
31944
31944
|
return;
|
|
@@ -32307,7 +32307,7 @@ var require_websocket_server = __commonJS({
|
|
|
32307
32307
|
var EventEmitter5 = require("events");
|
|
32308
32308
|
var http = require("http");
|
|
32309
32309
|
var { Duplex } = require("stream");
|
|
32310
|
-
var { createHash:
|
|
32310
|
+
var { createHash: createHash9 } = require("crypto");
|
|
32311
32311
|
var extension2 = require_extension();
|
|
32312
32312
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
32313
32313
|
var subprotocol2 = require_subprotocol();
|
|
@@ -32614,7 +32614,7 @@ var require_websocket_server = __commonJS({
|
|
|
32614
32614
|
);
|
|
32615
32615
|
}
|
|
32616
32616
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
32617
|
-
const digest =
|
|
32617
|
+
const digest = createHash9("sha1").update(key + GUID).digest("base64");
|
|
32618
32618
|
const headers = [
|
|
32619
32619
|
"HTTP/1.1 101 Switching Protocols",
|
|
32620
32620
|
"Upgrade: websocket",
|
|
@@ -97360,6 +97360,81 @@ var init_platform = __esm({
|
|
|
97360
97360
|
}
|
|
97361
97361
|
});
|
|
97362
97362
|
|
|
97363
|
+
// src/server-identity.ts
|
|
97364
|
+
function serverIdentityKeyPath() {
|
|
97365
|
+
const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path3.join)((0, import_os5.homedir)(), ".threadbase");
|
|
97366
|
+
return (0, import_path3.join)(dir, "keys", "server-identity.key");
|
|
97367
|
+
}
|
|
97368
|
+
function loadOrCreateServerIdentity() {
|
|
97369
|
+
const path2 = serverIdentityKeyPath();
|
|
97370
|
+
let raw2;
|
|
97371
|
+
try {
|
|
97372
|
+
raw2 = (0, import_fs4.readFileSync)(path2, "utf-8");
|
|
97373
|
+
} catch (err) {
|
|
97374
|
+
if (err.code !== "ENOENT") throw err;
|
|
97375
|
+
return generateIdentity(path2);
|
|
97376
|
+
}
|
|
97377
|
+
let privateKey;
|
|
97378
|
+
try {
|
|
97379
|
+
privateKey = (0, import_crypto4.createPrivateKey)({ key: JSON.parse(raw2).key, format: "jwk" });
|
|
97380
|
+
} catch {
|
|
97381
|
+
throw new Error(
|
|
97382
|
+
`Server identity key at ${path2} could not be read. Refusing to generate a new one \u2014 that would invalidate every paired device. Repair or delete the file deliberately.`
|
|
97383
|
+
);
|
|
97384
|
+
}
|
|
97385
|
+
return { publicKey: publicKeyOf(privateKey), privateKey };
|
|
97386
|
+
}
|
|
97387
|
+
function serverIdentityPublicKey() {
|
|
97388
|
+
return loadOrCreateServerIdentity().publicKey;
|
|
97389
|
+
}
|
|
97390
|
+
function serverIdentityFingerprint(publicKeyBase64url) {
|
|
97391
|
+
const raw2 = Buffer.from(publicKeyBase64url, "base64url");
|
|
97392
|
+
const hex3 = (0, import_crypto4.createHash)("sha256").update(raw2).digest().subarray(0, 16).toString("hex");
|
|
97393
|
+
return hex3.match(/.{4}/g)?.join(" ") ?? hex3;
|
|
97394
|
+
}
|
|
97395
|
+
function currentServerIdentityFingerprint() {
|
|
97396
|
+
return serverIdentityFingerprint(serverIdentityPublicKey());
|
|
97397
|
+
}
|
|
97398
|
+
function generateIdentity(path2) {
|
|
97399
|
+
const { privateKey } = (0, import_crypto4.generateKeyPairSync)("x25519");
|
|
97400
|
+
const file2 = {
|
|
97401
|
+
v: IDENTITY_FILE_VERSION,
|
|
97402
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
97403
|
+
key: privateKey.export({ format: "jwk" })
|
|
97404
|
+
};
|
|
97405
|
+
(0, import_fs4.mkdirSync)((0, import_path3.dirname)(path2), { recursive: true, mode: 448 });
|
|
97406
|
+
const tmp = `${path2}.tmp`;
|
|
97407
|
+
(0, import_fs4.writeFileSync)(tmp, `${JSON.stringify(file2)}
|
|
97408
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
97409
|
+
(0, import_fs4.chmodSync)(tmp, 384);
|
|
97410
|
+
(0, import_fs4.renameSync)(tmp, path2);
|
|
97411
|
+
const publicKey = publicKeyOf(privateKey);
|
|
97412
|
+
getLogger("identity").info(`Generated server identity key ${publicKey}`, {
|
|
97413
|
+
event: "identity.key_generated",
|
|
97414
|
+
path: path2
|
|
97415
|
+
});
|
|
97416
|
+
return { publicKey, privateKey };
|
|
97417
|
+
}
|
|
97418
|
+
function publicKeyOf(privateKey) {
|
|
97419
|
+
const jwk = (0, import_crypto4.createPublicKey)(privateKey).export({ format: "jwk" });
|
|
97420
|
+
if (jwk.crv !== "X25519" || typeof jwk.x !== "string") {
|
|
97421
|
+
throw new Error(`Server identity key at ${serverIdentityKeyPath()} is not an X25519 key`);
|
|
97422
|
+
}
|
|
97423
|
+
return jwk.x;
|
|
97424
|
+
}
|
|
97425
|
+
var import_crypto4, import_fs4, import_os5, import_path3, IDENTITY_FILE_VERSION;
|
|
97426
|
+
var init_server_identity = __esm({
|
|
97427
|
+
"src/server-identity.ts"() {
|
|
97428
|
+
"use strict";
|
|
97429
|
+
import_crypto4 = require("crypto");
|
|
97430
|
+
import_fs4 = require("fs");
|
|
97431
|
+
import_os5 = require("os");
|
|
97432
|
+
import_path3 = require("path");
|
|
97433
|
+
init_logger();
|
|
97434
|
+
IDENTITY_FILE_VERSION = 1;
|
|
97435
|
+
}
|
|
97436
|
+
});
|
|
97437
|
+
|
|
97363
97438
|
// node_modules/fast-glob/out/utils/array.js
|
|
97364
97439
|
var require_array = __commonJS({
|
|
97365
97440
|
"node_modules/fast-glob/out/utils/array.js"(exports2) {
|
|
@@ -128273,6 +128348,43 @@ var init_repo = __esm({
|
|
|
128273
128348
|
}
|
|
128274
128349
|
});
|
|
128275
128350
|
|
|
128351
|
+
// cli/identity.ts
|
|
128352
|
+
var identity_exports = {};
|
|
128353
|
+
__export(identity_exports, {
|
|
128354
|
+
formatIdentityBanner: () => formatIdentityBanner,
|
|
128355
|
+
runIdentity: () => runIdentity
|
|
128356
|
+
});
|
|
128357
|
+
function runIdentity(deps) {
|
|
128358
|
+
const keyPath = (deps.keyPath ?? serverIdentityKeyPath)();
|
|
128359
|
+
const fingerprint2 = deps.fingerprint ?? currentServerIdentityFingerprint;
|
|
128360
|
+
let value;
|
|
128361
|
+
try {
|
|
128362
|
+
value = fingerprint2();
|
|
128363
|
+
} catch (err) {
|
|
128364
|
+
deps.log.error(
|
|
128365
|
+
`Could not read the server identity key: ${err instanceof Error ? err.message : String(err)}`
|
|
128366
|
+
);
|
|
128367
|
+
return 1;
|
|
128368
|
+
}
|
|
128369
|
+
deps.log.info(formatIdentityBanner(value, keyPath));
|
|
128370
|
+
return 0;
|
|
128371
|
+
}
|
|
128372
|
+
function formatIdentityBanner(fingerprint2, keyPath) {
|
|
128373
|
+
return [
|
|
128374
|
+
`Server identity (X25519, ${keyPath})`,
|
|
128375
|
+
"",
|
|
128376
|
+
` ${fingerprint2}`,
|
|
128377
|
+
"",
|
|
128378
|
+
"Compare this with the fingerprint your phone shows when you add this server."
|
|
128379
|
+
].join("\n");
|
|
128380
|
+
}
|
|
128381
|
+
var init_identity = __esm({
|
|
128382
|
+
"cli/identity.ts"() {
|
|
128383
|
+
"use strict";
|
|
128384
|
+
init_server_identity();
|
|
128385
|
+
}
|
|
128386
|
+
});
|
|
128387
|
+
|
|
128276
128388
|
// cli/setKey.ts
|
|
128277
128389
|
var setKey_exports = {};
|
|
128278
128390
|
__export(setKey_exports, {
|
|
@@ -135627,67 +135739,7 @@ var PushRepository = class {
|
|
|
135627
135739
|
|
|
135628
135740
|
// src/api/routes/misc.routes.ts
|
|
135629
135741
|
init_logger();
|
|
135630
|
-
|
|
135631
|
-
// src/server-identity.ts
|
|
135632
|
-
var import_crypto4 = require("crypto");
|
|
135633
|
-
var import_fs4 = require("fs");
|
|
135634
|
-
var import_os5 = require("os");
|
|
135635
|
-
var import_path3 = require("path");
|
|
135636
|
-
init_logger();
|
|
135637
|
-
var IDENTITY_FILE_VERSION = 1;
|
|
135638
|
-
function serverIdentityKeyPath() {
|
|
135639
|
-
const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path3.join)((0, import_os5.homedir)(), ".threadbase");
|
|
135640
|
-
return (0, import_path3.join)(dir, "keys", "server-identity.key");
|
|
135641
|
-
}
|
|
135642
|
-
function loadOrCreateServerIdentity() {
|
|
135643
|
-
const path2 = serverIdentityKeyPath();
|
|
135644
|
-
let raw2;
|
|
135645
|
-
try {
|
|
135646
|
-
raw2 = (0, import_fs4.readFileSync)(path2, "utf-8");
|
|
135647
|
-
} catch (err) {
|
|
135648
|
-
if (err.code !== "ENOENT") throw err;
|
|
135649
|
-
return generateIdentity(path2);
|
|
135650
|
-
}
|
|
135651
|
-
let privateKey;
|
|
135652
|
-
try {
|
|
135653
|
-
privateKey = (0, import_crypto4.createPrivateKey)({ key: JSON.parse(raw2).key, format: "jwk" });
|
|
135654
|
-
} catch {
|
|
135655
|
-
throw new Error(
|
|
135656
|
-
`Server identity key at ${path2} could not be read. Refusing to generate a new one \u2014 that would invalidate every paired device. Repair or delete the file deliberately.`
|
|
135657
|
-
);
|
|
135658
|
-
}
|
|
135659
|
-
return { publicKey: publicKeyOf(privateKey), privateKey };
|
|
135660
|
-
}
|
|
135661
|
-
function serverIdentityPublicKey() {
|
|
135662
|
-
return loadOrCreateServerIdentity().publicKey;
|
|
135663
|
-
}
|
|
135664
|
-
function generateIdentity(path2) {
|
|
135665
|
-
const { privateKey } = (0, import_crypto4.generateKeyPairSync)("x25519");
|
|
135666
|
-
const file2 = {
|
|
135667
|
-
v: IDENTITY_FILE_VERSION,
|
|
135668
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
135669
|
-
key: privateKey.export({ format: "jwk" })
|
|
135670
|
-
};
|
|
135671
|
-
(0, import_fs4.mkdirSync)((0, import_path3.dirname)(path2), { recursive: true, mode: 448 });
|
|
135672
|
-
const tmp = `${path2}.tmp`;
|
|
135673
|
-
(0, import_fs4.writeFileSync)(tmp, `${JSON.stringify(file2)}
|
|
135674
|
-
`, { encoding: "utf-8", mode: 384 });
|
|
135675
|
-
(0, import_fs4.chmodSync)(tmp, 384);
|
|
135676
|
-
(0, import_fs4.renameSync)(tmp, path2);
|
|
135677
|
-
const publicKey = publicKeyOf(privateKey);
|
|
135678
|
-
getLogger("identity").info(`Generated server identity key ${publicKey}`, {
|
|
135679
|
-
event: "identity.key_generated",
|
|
135680
|
-
path: path2
|
|
135681
|
-
});
|
|
135682
|
-
return { publicKey, privateKey };
|
|
135683
|
-
}
|
|
135684
|
-
function publicKeyOf(privateKey) {
|
|
135685
|
-
const jwk = (0, import_crypto4.createPublicKey)(privateKey).export({ format: "jwk" });
|
|
135686
|
-
if (jwk.crv !== "X25519" || typeof jwk.x !== "string") {
|
|
135687
|
-
throw new Error(`Server identity key at ${serverIdentityKeyPath()} is not an X25519 key`);
|
|
135688
|
-
}
|
|
135689
|
-
return jwk.x;
|
|
135690
|
-
}
|
|
135742
|
+
init_server_identity();
|
|
135691
135743
|
|
|
135692
135744
|
// src/services/push/apnsClient.ts
|
|
135693
135745
|
var import_node_crypto = require("crypto");
|
|
@@ -150263,6 +150315,9 @@ function seal(plaintext, recipientPublicKeyBase64) {
|
|
|
150263
150315
|
};
|
|
150264
150316
|
}
|
|
150265
150317
|
|
|
150318
|
+
// src/server.ts
|
|
150319
|
+
init_server_identity();
|
|
150320
|
+
|
|
150266
150321
|
// src/server-wiring.ts
|
|
150267
150322
|
var import_fs31 = require("fs");
|
|
150268
150323
|
|
|
@@ -155134,6 +155189,9 @@ function parseDirScanDebounceEnv(raw2) {
|
|
|
155134
155189
|
return Number.isNaN(parsed) || parsed < 0 ? void 0 : parsed;
|
|
155135
155190
|
}
|
|
155136
155191
|
|
|
155192
|
+
// cli/index.ts
|
|
155193
|
+
init_server_identity();
|
|
155194
|
+
|
|
155137
155195
|
// src/updater/check-update.ts
|
|
155138
155196
|
var import_semver = __toESM(require_semver2(), 1);
|
|
155139
155197
|
|
|
@@ -158775,6 +158833,7 @@ init_platform2();
|
|
|
158775
158833
|
init_logger();
|
|
158776
158834
|
init_protocol();
|
|
158777
158835
|
init_socket();
|
|
158836
|
+
init_server_identity();
|
|
158778
158837
|
var log11 = getLogger("prod");
|
|
158779
158838
|
function clearSupervisorLogs() {
|
|
158780
158839
|
let paths;
|
|
@@ -158827,6 +158886,7 @@ async function runProdStatus() {
|
|
|
158827
158886
|
async function runProdDoctor(opts, deps = {}) {
|
|
158828
158887
|
const findings = [];
|
|
158829
158888
|
const repairs = [];
|
|
158889
|
+
let identity;
|
|
158830
158890
|
let ptyHost;
|
|
158831
158891
|
const marker = readMarker();
|
|
158832
158892
|
if (marker && !marker.userHeld && !isPidAlive(marker.devPid)) {
|
|
@@ -158839,6 +158899,15 @@ async function runProdDoctor(opts, deps = {}) {
|
|
|
158839
158899
|
if (!getSupervisor().isAgentLoaded()) {
|
|
158840
158900
|
findings.push("launchd agent is not loaded \u2014 prod is fully down");
|
|
158841
158901
|
}
|
|
158902
|
+
if (deps.serverIdentityFingerprint) {
|
|
158903
|
+
try {
|
|
158904
|
+
identity = deps.serverIdentityFingerprint();
|
|
158905
|
+
} catch (err) {
|
|
158906
|
+
findings.push(
|
|
158907
|
+
`server identity key at ${serverIdentityKeyPath()} is unreadable: ` + (err instanceof Error ? err.message : String(err))
|
|
158908
|
+
);
|
|
158909
|
+
}
|
|
158910
|
+
}
|
|
158842
158911
|
const conflicts = detectConflictingAgents();
|
|
158843
158912
|
for (const c of conflicts) {
|
|
158844
158913
|
if (c.resolution === "uninstall-homebrew") {
|
|
@@ -158867,7 +158936,7 @@ async function runProdDoctor(opts, deps = {}) {
|
|
|
158867
158936
|
);
|
|
158868
158937
|
}
|
|
158869
158938
|
}
|
|
158870
|
-
return { findings, repairs, ...ptyHost ? { ptyHost } : {} };
|
|
158939
|
+
return { findings, repairs, ...identity ? { identity } : {}, ...ptyHost ? { ptyHost } : {} };
|
|
158871
158940
|
}
|
|
158872
158941
|
function toPowerShellLiteral(value) {
|
|
158873
158942
|
return `'${value.replace(/'/g, "''")}'`;
|
|
@@ -159016,7 +159085,13 @@ function registerProdCommands(program3) {
|
|
|
159016
159085
|
log11.info(what, void 0, "console");
|
|
159017
159086
|
});
|
|
159018
159087
|
prod.command("doctor").description("Detect stale markers, missing agents, and pty-host health").option("--fix", "Apply repairs (default is dry-run)", false).action(async (opts) => {
|
|
159019
|
-
const r = await runProdDoctor(
|
|
159088
|
+
const r = await runProdDoctor(
|
|
159089
|
+
{ fix: opts.fix === true },
|
|
159090
|
+
{ serverIdentityFingerprint: currentServerIdentityFingerprint }
|
|
159091
|
+
);
|
|
159092
|
+
if (r.identity) {
|
|
159093
|
+
log11.info(`identity: ${r.identity}`, void 0, "console");
|
|
159094
|
+
}
|
|
159020
159095
|
if (r.ptyHost) {
|
|
159021
159096
|
log11.info(
|
|
159022
159097
|
r.ptyHost.reachable ? `pty-host: reachable, protocol=${r.ptyHost.protocolVersion}, sessions=${r.ptyHost.sessionCount}` : `pty-host: unreachable (${r.ptyHost.error})`,
|
|
@@ -159408,6 +159483,16 @@ program2.command("pair").description("Print a pairing QR code (server must alrea
|
|
|
159408
159483
|
const publicUrl = loadPublicUrl() ?? null;
|
|
159409
159484
|
await printServerBanner({ port, apiKey, publicUrl, includeQr: true });
|
|
159410
159485
|
});
|
|
159486
|
+
program2.command("identity").description("Print this server's identity fingerprint for out-of-band verification").action(async () => {
|
|
159487
|
+
const { runIdentity: runIdentity2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
|
|
159488
|
+
const code = runIdentity2({
|
|
159489
|
+
log: {
|
|
159490
|
+
info: (msg) => log14.info(msg, void 0, "console"),
|
|
159491
|
+
error: (msg) => log14.error(msg, void 0, "console")
|
|
159492
|
+
}
|
|
159493
|
+
});
|
|
159494
|
+
process.exit(code);
|
|
159495
|
+
});
|
|
159411
159496
|
program2.command("set-key [key]").description("Set the streamer API key in ~/.threadbase/server.yaml").action(async (key) => {
|
|
159412
159497
|
const { runSetKey: runSetKey2 } = await Promise.resolve().then(() => (init_setKey(), setKey_exports));
|
|
159413
159498
|
const code = await runSetKey2(
|