@camstack/system 1.2.356 → 1.2.358
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/auth/user-manager.d.ts +43 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +2 -2
- package/dist/builtins/autotrack/index.mjs +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +8 -8
- package/dist/builtins/composer/composer.addon.mjs +8 -8
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.js +9 -9
- package/dist/builtins/device-manager/device-manager.addon.mjs +9 -9
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/admin-account-boot.d.ts +27 -0
- package/dist/builtins/local-auth/auth-schema.d.ts +3 -2
- package/dist/builtins/local-auth/local-auth.addon.js +281 -8
- package/dist/builtins/local-auth/local-auth.addon.mjs +291 -22
- package/dist/builtins/local-network/local-network.addon.d.ts +26 -0
- package/dist/builtins/local-network/local-network.addon.js +74 -25
- package/dist/builtins/local-network/local-network.addon.mjs +74 -25
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-wbIQJctP.js → child-cap-dispatch-BWk99kmA.js} +2 -2
- package/dist/{child-cap-dispatch-BcRHQBKW.mjs → child-cap-dispatch-C2dFiu87.mjs} +2 -2
- package/dist/{composition-sources-CeO0K2cl.js → composition-sources-BaHPH661.js} +1 -1
- package/dist/{composition-sources-BBfqepyT.mjs → composition-sources-D8Hapyav.mjs} +1 -1
- package/dist/{dist-B7WRgEZY.mjs → dist-D3bhbMY3.mjs} +321 -65
- package/dist/{dist-CFVOy8VT.js → dist-DzAHit0a.js} +332 -64
- package/dist/index.js +19 -143
- package/dist/index.mjs +6 -142
- package/dist/kernel/config-manager.d.ts +1 -1
- package/dist/kernel/config-schema.d.ts +6 -3
- package/dist/{retired-settings-keys-DKRjfz0U.mjs → retired-settings-keys-B6AFmtlO.mjs} +1 -1
- package/dist/{retired-settings-keys-CRlTzxaS.js → retired-settings-keys-CkXoPjKy.js} +1 -1
- package/dist/tls/boot-tls.d.ts +29 -0
- package/dist/tls/https-listener.d.ts +30 -0
- package/dist/tls/index.d.ts +4 -1
- package/dist/tls/tls-actions.d.ts +29 -0
- package/dist/tls/tls-status.d.ts +12 -1
- package/dist/{tls-ue_Xefmv.js → tls-CCMX7MjX.js} +319 -54
- package/dist/{tls-DTlVWwAI.mjs → tls-CJHogwTl.mjs} +246 -53
- package/package.json +3 -3
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { r as __exportAll, t as __commonJSMin } from "./chunk-CNf5ZN-e.mjs";
|
|
2
2
|
import { X509Certificate, createPrivateKey, randomBytes, webcrypto } from "node:crypto";
|
|
3
|
-
import { chmod, writeFile } from "node:fs/promises";
|
|
3
|
+
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import * as os from "node:os";
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { createServer } from "node:http";
|
|
8
8
|
import { isIP } from "node:net";
|
|
9
9
|
//#region src/http/lan-http-bind.ts
|
|
@@ -11861,45 +11861,6 @@ function loadTlsCert(certPath, keyPath) {
|
|
|
11861
11861
|
};
|
|
11862
11862
|
}
|
|
11863
11863
|
//#endregion
|
|
11864
|
-
//#region src/tls/uploaded-cert.ts
|
|
11865
|
-
/**
|
|
11866
|
-
* Operator-supplied material must parse, match its key, and carry
|
|
11867
|
-
* `extendedKeyUsage = serverAuth` — the same leaf defects D227 refused
|
|
11868
|
-
* to keep generating.
|
|
11869
|
-
*/
|
|
11870
|
-
function validateUploadedTls(certPem, keyPem) {
|
|
11871
|
-
let leaf;
|
|
11872
|
-
try {
|
|
11873
|
-
leaf = new X509Certificate(certPem);
|
|
11874
|
-
} catch {
|
|
11875
|
-
return {
|
|
11876
|
-
ok: false,
|
|
11877
|
-
error: "Certificate PEM could not be parsed."
|
|
11878
|
-
};
|
|
11879
|
-
}
|
|
11880
|
-
try {
|
|
11881
|
-
if (!leaf.checkPrivateKey(createPrivateKey(keyPem))) return {
|
|
11882
|
-
ok: false,
|
|
11883
|
-
error: "Private key does not match the certificate."
|
|
11884
|
-
};
|
|
11885
|
-
} catch {
|
|
11886
|
-
return {
|
|
11887
|
-
ok: false,
|
|
11888
|
-
error: "Private key PEM could not be parsed."
|
|
11889
|
-
};
|
|
11890
|
-
}
|
|
11891
|
-
const eku = leaf.keyUsage;
|
|
11892
|
-
if (eku === void 0 || !eku.includes("1.3.6.1.5.5.7.3.1")) return {
|
|
11893
|
-
ok: false,
|
|
11894
|
-
error: "Certificate is missing extendedKeyUsage = serverAuth."
|
|
11895
|
-
};
|
|
11896
|
-
return {
|
|
11897
|
-
ok: true,
|
|
11898
|
-
fingerprintSha256: leaf.fingerprint256,
|
|
11899
|
-
validTo: new Date(leaf.validTo).toISOString()
|
|
11900
|
-
};
|
|
11901
|
-
}
|
|
11902
|
-
//#endregion
|
|
11903
11864
|
//#region src/tls/tls-status.ts
|
|
11904
11865
|
function tlsDir(dataDir) {
|
|
11905
11866
|
return join(dataDir, "tls");
|
|
@@ -11932,10 +11893,51 @@ function writeExtraSans(dataDir, sans) {
|
|
|
11932
11893
|
mkdirSync(tlsDir(dataDir), { recursive: true });
|
|
11933
11894
|
writeFileSync(join(tlsDir(dataDir), "extra-sans.json"), `${JSON.stringify(sans)}\n`, "utf-8");
|
|
11934
11895
|
}
|
|
11935
|
-
|
|
11896
|
+
/** Where an upload's own CA is kept — never over the local CA's `camstack-ca.crt`. */
|
|
11897
|
+
var UPLOADED_CA_FILE = "uploaded-ca.crt";
|
|
11898
|
+
/** Every certificate in a PEM bundle, in order. */
|
|
11899
|
+
function splitPemCertificates(pem) {
|
|
11900
|
+
const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g);
|
|
11901
|
+
return blocks === null ? [] : blocks.map((block) => `${block}\n`);
|
|
11902
|
+
}
|
|
11903
|
+
/**
|
|
11904
|
+
* The certificate that signed `leaf`, from the upload itself: the CA the
|
|
11905
|
+
* operator supplied, else the rest of the uploaded chain. `null` when the
|
|
11906
|
+
* upload did not include it — and then nothing may stand in for it: the
|
|
11907
|
+
* local CA did not sign an uploaded leaf, so handing it out as "the CA" would
|
|
11908
|
+
* tell a client to trust a root that verifies nothing it is served.
|
|
11909
|
+
*/
|
|
11910
|
+
function uploadedIssuer(dir, leaf, chainPem) {
|
|
11911
|
+
const caPath = join(dir, UPLOADED_CA_FILE);
|
|
11912
|
+
const candidates = [...existsSync(caPath) ? splitPemCertificates(readFileSync(caPath, "utf-8")) : [], ...splitPemCertificates(chainPem).slice(1)];
|
|
11913
|
+
for (const pem of candidates) try {
|
|
11914
|
+
const cert = new X509Certificate(pem);
|
|
11915
|
+
if (leaf.checkIssued(cert) && leaf.verify(cert.publicKey)) return {
|
|
11916
|
+
pem,
|
|
11917
|
+
cert
|
|
11918
|
+
};
|
|
11919
|
+
} catch {}
|
|
11920
|
+
return null;
|
|
11921
|
+
}
|
|
11922
|
+
function generatedIssuer(dir) {
|
|
11923
|
+
const caPath = join(dir, "camstack-ca.crt");
|
|
11924
|
+
if (!existsSync(caPath)) return null;
|
|
11925
|
+
const pem = readFileSync(caPath, "utf-8");
|
|
11926
|
+
return {
|
|
11927
|
+
pem,
|
|
11928
|
+
cert: new X509Certificate(pem)
|
|
11929
|
+
};
|
|
11930
|
+
}
|
|
11931
|
+
/**
|
|
11932
|
+
* What the node's certificate is, and whether the listener serves it.
|
|
11933
|
+
* `servedLeafFingerprint` is the leaf the HTTPS listener presents right now
|
|
11934
|
+
* (`servedHttpsLeafFingerprint()`), or `null` when unknown; a restart is
|
|
11935
|
+
* required exactly when the listener serves a different leaf than the one
|
|
11936
|
+
* on disk — a measured fact, not a flag a mutation remembered to set.
|
|
11937
|
+
*/
|
|
11938
|
+
function readTlsAccessStatus(dataDir, servedLeafFingerprint) {
|
|
11936
11939
|
const dir = tlsDir(dataDir);
|
|
11937
11940
|
const certPath = join(dir, "camstack.crt");
|
|
11938
|
-
const caPath = join(dir, "camstack-ca.crt");
|
|
11939
11941
|
const mode = readTlsMode(dataDir);
|
|
11940
11942
|
if (!existsSync(certPath)) return {
|
|
11941
11943
|
mode: "disabled",
|
|
@@ -11943,24 +11945,24 @@ function readTlsAccessStatus(dataDir, restartRequired = false) {
|
|
|
11943
11945
|
caFingerprintSha256: null,
|
|
11944
11946
|
validTo: null,
|
|
11945
11947
|
sans: [],
|
|
11946
|
-
caCertPem:
|
|
11948
|
+
caCertPem: (mode === "uploaded" ? null : generatedIssuer(dir))?.pem ?? null,
|
|
11947
11949
|
reissueError: null,
|
|
11948
|
-
restartRequired
|
|
11950
|
+
restartRequired: false
|
|
11949
11951
|
};
|
|
11950
11952
|
try {
|
|
11951
|
-
const
|
|
11952
|
-
const
|
|
11953
|
-
const ca =
|
|
11953
|
+
const chainPem = readFileSync(certPath, "utf-8");
|
|
11954
|
+
const leaf = new X509Certificate(chainPem);
|
|
11955
|
+
const ca = mode === "uploaded" ? uploadedIssuer(dir, leaf, chainPem) : generatedIssuer(dir);
|
|
11954
11956
|
const san = leaf.subjectAltName ?? "";
|
|
11955
11957
|
return {
|
|
11956
11958
|
mode,
|
|
11957
11959
|
leafFingerprintSha256: leaf.fingerprint256,
|
|
11958
|
-
caFingerprintSha256: ca?.fingerprint256 ?? null,
|
|
11960
|
+
caFingerprintSha256: ca?.cert.fingerprint256 ?? null,
|
|
11959
11961
|
validTo: new Date(leaf.validTo).toISOString(),
|
|
11960
11962
|
sans: san === "" ? [] : san.split(", "),
|
|
11961
|
-
caCertPem:
|
|
11963
|
+
caCertPem: ca?.pem ?? null,
|
|
11962
11964
|
reissueError: null,
|
|
11963
|
-
restartRequired
|
|
11965
|
+
restartRequired: servedLeafFingerprint !== null && servedLeafFingerprint !== leaf.fingerprint256
|
|
11964
11966
|
};
|
|
11965
11967
|
} catch {
|
|
11966
11968
|
return {
|
|
@@ -11971,9 +11973,200 @@ function readTlsAccessStatus(dataDir, restartRequired = false) {
|
|
|
11971
11973
|
sans: [],
|
|
11972
11974
|
caCertPem: null,
|
|
11973
11975
|
reissueError: "Certificate on disk could not be parsed.",
|
|
11974
|
-
restartRequired
|
|
11976
|
+
restartRequired: false
|
|
11975
11977
|
};
|
|
11976
11978
|
}
|
|
11977
11979
|
}
|
|
11978
11980
|
//#endregion
|
|
11979
|
-
|
|
11981
|
+
//#region src/tls/boot-tls.ts
|
|
11982
|
+
/**
|
|
11983
|
+
* Decide what the listener serves at boot. The admin UI (Network → Local
|
|
11984
|
+
* access) is the only way to set a certificate (D726): it either leaves the
|
|
11985
|
+
* hub on its generated local-CA leaf (D227) or uploads one, recorded by the
|
|
11986
|
+
* `tls/mode` marker. An uploaded certificate is served verbatim — running it
|
|
11987
|
+
* through `ensureTlsCert` would replace it on the first restart, because a
|
|
11988
|
+
* leaf the local root did not issue is one of that rule's reissue triggers.
|
|
11989
|
+
*/
|
|
11990
|
+
async function prepareBootTls(dataDir, options) {
|
|
11991
|
+
const certPath = join(dataDir, "tls", "camstack.crt");
|
|
11992
|
+
const keyPath = join(dataDir, "tls", "camstack.key");
|
|
11993
|
+
const marked = readTlsMode(dataDir);
|
|
11994
|
+
if (marked === "uploaded" && existsSync(certPath) && existsSync(keyPath)) return {
|
|
11995
|
+
mode: "uploaded",
|
|
11996
|
+
pair: loadTlsCert(certPath, keyPath),
|
|
11997
|
+
ensured: void 0,
|
|
11998
|
+
uploadedMissing: false
|
|
11999
|
+
};
|
|
12000
|
+
const uploadedMissing = marked === "uploaded";
|
|
12001
|
+
if (uploadedMissing) writeTlsMode(dataDir, "generated");
|
|
12002
|
+
const ensured = await ensureTlsCert(dataDir, options);
|
|
12003
|
+
return {
|
|
12004
|
+
mode: "generated",
|
|
12005
|
+
pair: loadTlsCert(ensured.certPath, ensured.keyPath),
|
|
12006
|
+
ensured,
|
|
12007
|
+
uploadedMissing
|
|
12008
|
+
};
|
|
12009
|
+
}
|
|
12010
|
+
//#endregion
|
|
12011
|
+
//#region src/tls/https-listener.ts
|
|
12012
|
+
var SLOT_KEY = Symbol.for("camstack.tls.https-listener");
|
|
12013
|
+
var EMPTY_SLOT = {
|
|
12014
|
+
reload: null,
|
|
12015
|
+
servedLeafFingerprint: null
|
|
12016
|
+
};
|
|
12017
|
+
function isSlot(value) {
|
|
12018
|
+
if (typeof value !== "object" || value === null) return false;
|
|
12019
|
+
if (!("reload" in value) || !("servedLeafFingerprint" in value)) return false;
|
|
12020
|
+
const reload = value.reload;
|
|
12021
|
+
const served = value.servedLeafFingerprint;
|
|
12022
|
+
return (reload === null || typeof reload === "function") && (served === null || typeof served === "string");
|
|
12023
|
+
}
|
|
12024
|
+
function readSlot() {
|
|
12025
|
+
const current = Reflect.get(globalThis, SLOT_KEY);
|
|
12026
|
+
return isSlot(current) ? current : EMPTY_SLOT;
|
|
12027
|
+
}
|
|
12028
|
+
function writeSlot(next) {
|
|
12029
|
+
Reflect.set(globalThis, SLOT_KEY, next);
|
|
12030
|
+
}
|
|
12031
|
+
/** SHA-256 fingerprint of the first certificate (the leaf) in a PEM chain. */
|
|
12032
|
+
function leafFingerprintOf(chain) {
|
|
12033
|
+
return new X509Certificate(chain).fingerprint256;
|
|
12034
|
+
}
|
|
12035
|
+
/**
|
|
12036
|
+
* Called once by the server after its HTTPS listener is up, with the pair it
|
|
12037
|
+
* was started on. From here on a certificate action swaps the served
|
|
12038
|
+
* certificate in place (`setSecureContext`) — new handshakes get the new
|
|
12039
|
+
* leaf, open connections keep theirs, nothing restarts.
|
|
12040
|
+
*/
|
|
12041
|
+
function registerHttpsListener(server, pair) {
|
|
12042
|
+
writeSlot({
|
|
12043
|
+
reload: (next) => server.setSecureContext({
|
|
12044
|
+
cert: next.cert,
|
|
12045
|
+
key: next.key
|
|
12046
|
+
}),
|
|
12047
|
+
servedLeafFingerprint: leafFingerprintOf(pair.cert)
|
|
12048
|
+
});
|
|
12049
|
+
}
|
|
12050
|
+
/** Forget the listener — on shutdown, and between specs. */
|
|
12051
|
+
function clearHttpsListener() {
|
|
12052
|
+
writeSlot(EMPTY_SLOT);
|
|
12053
|
+
}
|
|
12054
|
+
/**
|
|
12055
|
+
* The leaf the HTTPS listener serves now, or `null` when this process has no
|
|
12056
|
+
* registered listener (TLS off, or a process that does not own one) — in
|
|
12057
|
+
* which case nobody can say, and `null` is the honest answer.
|
|
12058
|
+
*/
|
|
12059
|
+
function servedHttpsLeafFingerprint() {
|
|
12060
|
+
return readSlot().servedLeafFingerprint;
|
|
12061
|
+
}
|
|
12062
|
+
/** Serve `pair` from now on. Never throws: the outcome says what happened. */
|
|
12063
|
+
function reloadHttpsListener(pair) {
|
|
12064
|
+
const slot = readSlot();
|
|
12065
|
+
if (slot.reload === null) return { kind: "no-listener" };
|
|
12066
|
+
try {
|
|
12067
|
+
const servedLeafFingerprint = leafFingerprintOf(pair.cert);
|
|
12068
|
+
slot.reload(pair);
|
|
12069
|
+
writeSlot({
|
|
12070
|
+
reload: slot.reload,
|
|
12071
|
+
servedLeafFingerprint
|
|
12072
|
+
});
|
|
12073
|
+
return {
|
|
12074
|
+
kind: "reloaded",
|
|
12075
|
+
servedLeafFingerprint
|
|
12076
|
+
};
|
|
12077
|
+
} catch (err) {
|
|
12078
|
+
return {
|
|
12079
|
+
kind: "failed",
|
|
12080
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12081
|
+
};
|
|
12082
|
+
}
|
|
12083
|
+
}
|
|
12084
|
+
//#endregion
|
|
12085
|
+
//#region src/tls/uploaded-cert.ts
|
|
12086
|
+
/**
|
|
12087
|
+
* Operator-supplied material must parse, match its key, and carry
|
|
12088
|
+
* `extendedKeyUsage = serverAuth` — the same leaf defects D227 refused
|
|
12089
|
+
* to keep generating.
|
|
12090
|
+
*/
|
|
12091
|
+
function validateUploadedTls(certPem, keyPem) {
|
|
12092
|
+
let leaf;
|
|
12093
|
+
try {
|
|
12094
|
+
leaf = new X509Certificate(certPem);
|
|
12095
|
+
} catch {
|
|
12096
|
+
return {
|
|
12097
|
+
ok: false,
|
|
12098
|
+
error: "Certificate PEM could not be parsed."
|
|
12099
|
+
};
|
|
12100
|
+
}
|
|
12101
|
+
try {
|
|
12102
|
+
if (!leaf.checkPrivateKey(createPrivateKey(keyPem))) return {
|
|
12103
|
+
ok: false,
|
|
12104
|
+
error: "Private key does not match the certificate."
|
|
12105
|
+
};
|
|
12106
|
+
} catch {
|
|
12107
|
+
return {
|
|
12108
|
+
ok: false,
|
|
12109
|
+
error: "Private key PEM could not be parsed."
|
|
12110
|
+
};
|
|
12111
|
+
}
|
|
12112
|
+
const eku = leaf.keyUsage;
|
|
12113
|
+
if (eku === void 0 || !eku.includes("1.3.6.1.5.5.7.3.1")) return {
|
|
12114
|
+
ok: false,
|
|
12115
|
+
error: "Certificate is missing extendedKeyUsage = serverAuth."
|
|
12116
|
+
};
|
|
12117
|
+
return {
|
|
12118
|
+
ok: true,
|
|
12119
|
+
fingerprintSha256: leaf.fingerprint256,
|
|
12120
|
+
validTo: new Date(leaf.validTo).toISOString()
|
|
12121
|
+
};
|
|
12122
|
+
}
|
|
12123
|
+
//#endregion
|
|
12124
|
+
//#region src/tls/tls-actions.ts
|
|
12125
|
+
function withNewline(pem) {
|
|
12126
|
+
return pem.endsWith("\n") ? pem : `${pem}\n`;
|
|
12127
|
+
}
|
|
12128
|
+
/**
|
|
12129
|
+
* Store an operator's certificate. The leaf (or chain) and key replace the
|
|
12130
|
+
* served pair; a CA, when given, goes to its own file. The local CA
|
|
12131
|
+
* (`camstack-ca.crt` + key) is never touched — it did not sign the upload,
|
|
12132
|
+
* and it is what "Revert to generated" and every device that trusts it
|
|
12133
|
+
* still need.
|
|
12134
|
+
*/
|
|
12135
|
+
async function storeUploadedCertificate(dataDir, input) {
|
|
12136
|
+
const valid = validateUploadedTls(input.certPem, input.keyPem);
|
|
12137
|
+
if (!valid.ok) throw new Error(valid.error);
|
|
12138
|
+
const dir = join(dataDir, "tls");
|
|
12139
|
+
await mkdir(dir, { recursive: true });
|
|
12140
|
+
await writeFile(join(dir, "camstack.crt"), withNewline(input.certPem));
|
|
12141
|
+
await writeFile(join(dir, "camstack.key"), withNewline(input.keyPem), { mode: 384 });
|
|
12142
|
+
const caPath = join(dir, UPLOADED_CA_FILE);
|
|
12143
|
+
if (input.caPem !== void 0 && input.caPem.trim() !== "") await writeFile(caPath, withNewline(input.caPem));
|
|
12144
|
+
else rmSync(caPath, { force: true });
|
|
12145
|
+
writeTlsMode(dataDir, "uploaded");
|
|
12146
|
+
}
|
|
12147
|
+
/** Issue a new generated leaf under the local CA. Refused while an upload is served. */
|
|
12148
|
+
async function regenerateCertificate(dataDir, options) {
|
|
12149
|
+
if (readTlsMode(dataDir) === "uploaded") throw new Error("Cannot regenerate an uploaded certificate — revert to generated first.");
|
|
12150
|
+
await reissueTlsLeaf(dataDir, options);
|
|
12151
|
+
}
|
|
12152
|
+
/** Drop the upload and serve the generated local-CA leaf again (D227). */
|
|
12153
|
+
async function revertToGeneratedCertificate(dataDir, options) {
|
|
12154
|
+
writeTlsMode(dataDir, "generated");
|
|
12155
|
+
rmSync(join(dataDir, "tls", UPLOADED_CA_FILE), { force: true });
|
|
12156
|
+
await ensureTlsCert(dataDir, options);
|
|
12157
|
+
}
|
|
12158
|
+
/**
|
|
12159
|
+
* Put the pair on disk on the HTTPS listener. Called after every action, so
|
|
12160
|
+
* "the certificate changed" and "clients see it" are the same moment.
|
|
12161
|
+
*/
|
|
12162
|
+
function applyCertificateToListener(dataDir) {
|
|
12163
|
+
const certPath = join(dataDir, "tls", "camstack.crt");
|
|
12164
|
+
const keyPath = join(dataDir, "tls", "camstack.key");
|
|
12165
|
+
if (!existsSync(certPath) || !existsSync(keyPath)) return {
|
|
12166
|
+
kind: "failed",
|
|
12167
|
+
error: `No certificate pair under ${join(dataDir, "tls")}`
|
|
12168
|
+
};
|
|
12169
|
+
return reloadHttpsListener(loadTlsCert(certPath, keyPath));
|
|
12170
|
+
}
|
|
12171
|
+
//#endregion
|
|
12172
|
+
export { allFamiliesListenHost as A, collectCertIdentity as C, SERVER_AUTH_OID as D, MAX_LEAF_VALIDITY_DAYS as E, registerLanHttpHandler as F, bindPendingLanHttp as M, closeLanHttp as N, evaluateExistingCert as O, readLanHttpState as P, CA_COMMON_NAME as S, LEAF_RENEWAL_WINDOW_DAYS as T, writeExtraSans as _, validateUploadedTls as a, loadTlsCert as b, registerHttpsListener as c, prepareBootTls as d, UPLOADED_CA_FILE as f, splitPemCertificates as g, readTlsMode as h, storeUploadedCertificate as i, applyLanHttp as j, DEFAULT_LAN_HTTP_PORT as k, reloadHttpsListener as l, readTlsAccessStatus as m, regenerateCertificate as n, clearHttpsListener as o, readExtraSans as p, revertToGeneratedCertificate as r, leafFingerprintOf as s, applyCertificateToListener as t, servedHttpsLeafFingerprint as u, writeTlsMode as v, CA_VALIDITY_DAYS as w, reissueTlsLeaf as x, ensureTlsCert as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/system",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.358",
|
|
4
4
|
"description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"category": "system",
|
|
54
54
|
"name": "SQLite Settings",
|
|
55
55
|
"entry": "./dist/builtins/sqlite-storage/sqlite-settings.addon.js",
|
|
56
|
-
"description": "The relational ENGINE behind the data door (SQLite WAL). Registers `data-store-provider`, an internal collection cap; the public `settings-store` door is owned by `storage-orchestrator`, which dispatches here. Callers never reach this addon directly — that is what makes the engine replaceable. It ALSO registers `vector-store` (sqlite-vec) from the same database file and the same open handle, and `database-snapshot`: the verified copy of its database that the nightly backup archives in place of the live files, written by this runner on a thread over its own connection
|
|
56
|
+
"description": "The relational ENGINE behind the data door (SQLite WAL). Registers `data-store-provider`, an internal collection cap; the public `settings-store` door is owned by `storage-orchestrator`, which dispatches here. Callers never reach this addon directly — that is what makes the engine replaceable. It ALSO registers `vector-store` (sqlite-vec) from the same database file and the same open handle, and `database-snapshot`: the verified copy of its database that the nightly backup archives in place of the live files, written by this runner on a thread over its own connection. Runs in its OWN process (`execution.isolate`) — `better-sqlite3` `Statement.run` does not belong on the event loop that serves the API.",
|
|
57
57
|
"execution": {
|
|
58
58
|
"isolate": true,
|
|
59
59
|
"heapProfile": "heavy",
|
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
"id": "composer",
|
|
364
364
|
"category": "system",
|
|
365
365
|
"name": "Composer",
|
|
366
|
-
"description": "Runs every DECLARATIVE composition (a core block whose source is `composition`): declares the composed device under the shared Blocks integration, registers its caps natively, and derives each field from other devices' state by `from` / expression — event-driven, no polling. Hub-only. Runs in its OWN runner (`execution.isolate` + `group: composer`): frame-free and low-crash, grouped with no device provider or data plane
|
|
366
|
+
"description": "Runs every DECLARATIVE composition (a core block whose source is `composition`): declares the composed device under the shared Blocks integration, registers its caps natively, and derives each field from other devices' state by `from` / expression — event-driven, no polling. Hub-only. Runs in its OWN runner (`execution.isolate` + `group: composer`): frame-free and low-crash, grouped with no device provider or data plane.",
|
|
367
367
|
"entry": "./dist/builtins/composer/index.js",
|
|
368
368
|
"protected": true,
|
|
369
369
|
"execution": {
|