buckyos 0.7.119 → 0.7.121
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/cli/README.md +24 -3
- package/cli/core/app.ts +28 -0
- package/cli/deno.json +2 -1
- package/cli/dist/cli.mjs +777 -169
- package/cli/dist/cli.mjs.map +1 -1
- package/cli/modules/app.ts +213 -94
- package/cli/modules/provision.ts +690 -0
- package/cli/runtime/host.ts +25 -2
- package/cli/runtime/host_deno.ts +4 -0
- package/cli/runtime/host_node.ts +4 -0
- package/cli/version.ts +1 -1
- package/dist/browser.cjs +1 -1
- package/dist/browser.mjs +124 -124
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +124 -124
- package/dist/{ndm_proxy-c00bc561.mjs → ndm_proxy-8f547002.mjs} +113 -216
- package/dist/ndm_proxy-8f547002.mjs.map +1 -0
- package/dist/ndm_proxy-93a8869b.js +13 -0
- package/dist/ndm_proxy-93a8869b.js.map +1 -0
- package/dist/ndn_types-44c69036.js +2 -0
- package/dist/ndn_types-44c69036.js.map +1 -0
- package/dist/{ndn_types-f6c08d20.mjs → ndn_types-d768245f.mjs} +149 -39
- package/dist/ndn_types-d768245f.mjs.map +1 -0
- package/dist/node.cjs +1 -1
- package/dist/node.mjs +124 -124
- package/dist/provision.cjs +6 -6
- package/dist/provision.cjs.map +1 -1
- package/dist/provision.d.ts +2 -0
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.mjs +1084 -35
- package/dist/provision.mjs.map +1 -1
- package/dist/provision_activate.d.ts +146 -0
- package/dist/provision_activate.d.ts.map +1 -0
- package/dist/{tus_client-573066b3.mjs → tus_client-b1c35e8c.mjs} +2 -2
- package/dist/{tus_client-573066b3.mjs.map → tus_client-b1c35e8c.mjs.map} +1 -1
- package/dist/{tus_client-83ad0f4f.js → tus_client-d0798623.js} +2 -2
- package/dist/{tus_client-83ad0f4f.js.map → tus_client-d0798623.js.map} +1 -1
- package/package.json +1 -1
- package/dist/ndm_proxy-68354a89.js +0 -13
- package/dist/ndm_proxy-68354a89.js.map +0 -1
- package/dist/ndm_proxy-c00bc561.mjs.map +0 -1
- package/dist/ndn_types-859bbd48.js +0 -2
- package/dist/ndn_types-859bbd48.js.map +0 -1
- package/dist/ndn_types-f6c08d20.mjs.map +0 -1
package/dist/provision.mjs
CHANGED
|
@@ -5,13 +5,13 @@ var __publicField = (obj, key, value) => {
|
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
7
|
var _a;
|
|
8
|
-
import { N as NODE_IDENTITY_SCHEMA_V2, v as DID,
|
|
8
|
+
import { N as NODE_IDENTITY_SCHEMA_V2, v as DID, H as newDeviceDocumentByJwk, I as verifyJwtEdDSA, J as deviceDocumentToOrderedJson, K as decodeJwtClaimWithoutVerify, L as commonjsGlobal, M as DEFAULT_EXPIRE_TIME, h as hashPassword, P as buckyosGetUnixTimestamp, Q as getPublicKeyXFromPrivatePem, R as getXFromJwk, T as createJwkByX, U as newOwnerDocument, V as ownerDocumentSetDefaultZoneDid, W as ownerDocumentToOrderedJson, X as oodDescriptionToString, Y as parseOODDescription, Z as encodeZoneBootDocument, _ as newZoneBootDocument, $ as encodeDeviceDocument, a0 as newDeviceMiniDocumentByDeviceDocument, a1 as deviceMiniDocumentToJwt, a2 as newZoneDocument, a3 as encodeZoneDocument, a4 as zoneDocumentToOrderedJson, a5 as newDeviceMiniDocument, a6 as newDeviceDocumentByMiniDocument, a7 as buildNamedObjectByJson } from "./ndn_types-d768245f.mjs";
|
|
9
9
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
10
10
|
import { pbkdf2Sync, createPublicKey, createPrivateKey, createECDH, createHmac } from "node:crypto";
|
|
11
11
|
const DEVICE_DOC_JWT_FILE_NAME = "device_doc.jwt";
|
|
12
12
|
const DEVICE_MINI_DOC_JWT_FILE_NAME = "device_mini_doc.jwt";
|
|
13
13
|
const NODE_GATEWAY_PARAMS_FILE_NAME = "node_gateway_params.json";
|
|
14
|
-
function requireNode$
|
|
14
|
+
function requireNode$3(moduleName) {
|
|
15
15
|
const proc = globalThis.process;
|
|
16
16
|
if (typeof (proc == null ? void 0 : proc.getBuiltinModule) === "function") {
|
|
17
17
|
const builtin = proc.getBuiltinModule(moduleName);
|
|
@@ -28,8 +28,8 @@ function asDid(value) {
|
|
|
28
28
|
return value instanceof DID ? value : DID.fromStr(value);
|
|
29
29
|
}
|
|
30
30
|
function writeJsonPretty(filePath, value) {
|
|
31
|
-
const fs = requireNode$
|
|
32
|
-
const path = requireNode$
|
|
31
|
+
const fs = requireNode$3("node:fs");
|
|
32
|
+
const path = requireNode$3("node:path");
|
|
33
33
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
34
34
|
fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
|
|
35
35
|
}
|
|
@@ -45,7 +45,7 @@ function newLocalNodeIdentityConfig(params) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
function loadLocalNodeIdentityConfig(filePath) {
|
|
48
|
-
const fs = requireNode$
|
|
48
|
+
const fs = requireNode$3("node:fs");
|
|
49
49
|
const config = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
50
50
|
if (config.schema !== NODE_IDENTITY_SCHEMA_V2) {
|
|
51
51
|
throw new Error(
|
|
@@ -55,7 +55,7 @@ function loadLocalNodeIdentityConfig(filePath) {
|
|
|
55
55
|
return config;
|
|
56
56
|
}
|
|
57
57
|
function deviceIdentityPathsForRoots(roots, deviceDid) {
|
|
58
|
-
const path = requireNode$
|
|
58
|
+
const path = requireNode$3("node:path");
|
|
59
59
|
const deviceDidStr = asDid(deviceDid).toString();
|
|
60
60
|
const publicDir = roots.publicDir(deviceDidStr);
|
|
61
61
|
return {
|
|
@@ -94,11 +94,11 @@ function newDeviceDocumentByJwkWithDid(name, publicKeyJwk, deviceDid, now) {
|
|
|
94
94
|
return bindDeviceDocumentDid(newDeviceDocumentByJwk(name, publicKeyJwk, now), deviceDid);
|
|
95
95
|
}
|
|
96
96
|
function loadDeviceDocJwtForRoots(roots, deviceDid) {
|
|
97
|
-
const fs = requireNode$
|
|
97
|
+
const fs = requireNode$3("node:fs");
|
|
98
98
|
return fs.readFileSync(deviceIdentityPathsForRoots(roots, deviceDid).deviceDocJwt, "utf8");
|
|
99
99
|
}
|
|
100
100
|
function loadDeviceMiniDocJwtForRoots(roots, deviceDid) {
|
|
101
|
-
const fs = requireNode$
|
|
101
|
+
const fs = requireNode$3("node:fs");
|
|
102
102
|
return fs.readFileSync(deviceIdentityPathsForRoots(roots, deviceDid).deviceMiniDocJwt, "utf8");
|
|
103
103
|
}
|
|
104
104
|
async function loadLocalDeviceDocumentForRoots(roots, nodeIdentity, verify) {
|
|
@@ -112,7 +112,7 @@ async function loadLocalDeviceDocumentForRoots(roots, nodeIdentity, verify) {
|
|
|
112
112
|
return [deviceDocJwt, deviceDoc];
|
|
113
113
|
}
|
|
114
114
|
function saveNodeGatewayParams(etcDir, deviceDid) {
|
|
115
|
-
const path = requireNode$
|
|
115
|
+
const path = requireNode$3("node:path");
|
|
116
116
|
writeJsonPretty(path.join(etcDir, NODE_GATEWAY_PARAMS_FILE_NAME), {
|
|
117
117
|
params: {
|
|
118
118
|
device_did: asDid(deviceDid).toString()
|
|
@@ -120,8 +120,8 @@ function saveNodeGatewayParams(etcDir, deviceDid) {
|
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
function saveLocalDeviceIdentityForRoots(etcDir, roots, nodeIdentity, deviceDoc, deviceDocJwt, deviceMiniDocJwt, devicePrivateKeyPem) {
|
|
123
|
-
const fs = requireNode$
|
|
124
|
-
const path = requireNode$
|
|
123
|
+
const fs = requireNode$3("node:fs");
|
|
124
|
+
const path = requireNode$3("node:path");
|
|
125
125
|
const paths = deviceIdentityPathsForRoots(roots, nodeIdentity.device_did);
|
|
126
126
|
fs.mkdirSync(paths.publicDir, { recursive: true });
|
|
127
127
|
fs.mkdirSync(paths.securityDir, { recursive: true });
|
|
@@ -13157,7 +13157,7 @@ AsnEcSignatureFormatter.namedCurveSize.set("P-256", 32);
|
|
|
13157
13157
|
AsnEcSignatureFormatter.namedCurveSize.set("K-256", 32);
|
|
13158
13158
|
AsnEcSignatureFormatter.namedCurveSize.set("P-384", 48);
|
|
13159
13159
|
AsnEcSignatureFormatter.namedCurveSize.set("P-521", 66);
|
|
13160
|
-
function requireNode$
|
|
13160
|
+
function requireNode$2(moduleName) {
|
|
13161
13161
|
const proc = globalThis.process;
|
|
13162
13162
|
if (typeof (proc == null ? void 0 : proc.getBuiltinModule) === "function") {
|
|
13163
13163
|
const builtin = proc.getBuiltinModule(moduleName);
|
|
@@ -13175,7 +13175,7 @@ function getCrypto() {
|
|
|
13175
13175
|
if (webcrypto == null ? void 0 : webcrypto.subtle) {
|
|
13176
13176
|
return webcrypto;
|
|
13177
13177
|
}
|
|
13178
|
-
const nodeCrypto = requireNode$
|
|
13178
|
+
const nodeCrypto = requireNode$2("node:crypto");
|
|
13179
13179
|
return nodeCrypto.webcrypto;
|
|
13180
13180
|
}
|
|
13181
13181
|
const CA_SIGNING_ALG = {
|
|
@@ -13239,7 +13239,7 @@ function encodeUtf8(value) {
|
|
|
13239
13239
|
if (typeof TextEncoder !== "undefined") {
|
|
13240
13240
|
return new TextEncoder().encode(value);
|
|
13241
13241
|
}
|
|
13242
|
-
const buffer = requireNode$
|
|
13242
|
+
const buffer = requireNode$2("node:buffer").Buffer;
|
|
13243
13243
|
return new Uint8Array(buffer.from(value, "utf8"));
|
|
13244
13244
|
}
|
|
13245
13245
|
function encodeIdentityDirName(rawHostUri) {
|
|
@@ -13343,7 +13343,7 @@ function trimToNull(value) {
|
|
|
13343
13343
|
return trimmed.length > 0 ? trimmed : null;
|
|
13344
13344
|
}
|
|
13345
13345
|
function x509PathsForDirs(publicDir, securityDir, usage) {
|
|
13346
|
-
const path = requireNode$
|
|
13346
|
+
const path = requireNode$2("node:path");
|
|
13347
13347
|
return {
|
|
13348
13348
|
cert: path.join(publicDir, identityFileName(usage, "cert")),
|
|
13349
13349
|
chain: path.join(publicDir, identityFileName(usage, "chain")),
|
|
@@ -13356,12 +13356,12 @@ function x509PathsForDirs(publicDir, securityDir, usage) {
|
|
|
13356
13356
|
}
|
|
13357
13357
|
class IdentityRoots {
|
|
13358
13358
|
constructor(publicRoot, securityRoot) {
|
|
13359
|
-
const path = requireNode$
|
|
13359
|
+
const path = requireNode$2("node:path");
|
|
13360
13360
|
this.publicRoot = path.resolve(publicRoot);
|
|
13361
13361
|
this.securityRoot = path.resolve(securityRoot);
|
|
13362
13362
|
}
|
|
13363
13363
|
static fromEnvOrBuckyosRoot(options = {}) {
|
|
13364
|
-
const path = requireNode$
|
|
13364
|
+
const path = requireNode$2("node:path");
|
|
13365
13365
|
const env = getProcessEnv();
|
|
13366
13366
|
const buckyosRoot = trimToNull(options.buckyosRoot) ?? trimToNull(env.BUCKYOS_ROOT) ?? DEFAULT_BUCKYOS_ROOT;
|
|
13367
13367
|
const publicRoot = trimToNull(options.publicRoot) ?? trimToNull(env.BUCKYOS_IDENTITY_ROOT) ?? path.join(buckyosRoot, "local", "identity");
|
|
@@ -13375,19 +13375,19 @@ class IdentityRoots {
|
|
|
13375
13375
|
return identityDirName(didOrHostname);
|
|
13376
13376
|
}
|
|
13377
13377
|
publicDir(didOrHostname) {
|
|
13378
|
-
const path = requireNode$
|
|
13378
|
+
const path = requireNode$2("node:path");
|
|
13379
13379
|
return path.join(this.publicRoot, this.dirName(didOrHostname));
|
|
13380
13380
|
}
|
|
13381
13381
|
securityDir(didOrHostname) {
|
|
13382
|
-
const path = requireNode$
|
|
13382
|
+
const path = requireNode$2("node:path");
|
|
13383
13383
|
return path.join(this.securityRoot, this.dirName(didOrHostname));
|
|
13384
13384
|
}
|
|
13385
13385
|
publicFile(didOrHostname, usage, material) {
|
|
13386
|
-
const path = requireNode$
|
|
13386
|
+
const path = requireNode$2("node:path");
|
|
13387
13387
|
return path.join(this.publicDir(didOrHostname), identityFileName(usage, material));
|
|
13388
13388
|
}
|
|
13389
13389
|
securityFile(didOrHostname, usage, material) {
|
|
13390
|
-
const path = requireNode$
|
|
13390
|
+
const path = requireNode$2("node:path");
|
|
13391
13391
|
return path.join(this.securityDir(didOrHostname), identityFileName(usage, material));
|
|
13392
13392
|
}
|
|
13393
13393
|
x509Paths(didOrHostname, usage = "server") {
|
|
@@ -13406,7 +13406,7 @@ class IdentityRoots {
|
|
|
13406
13406
|
};
|
|
13407
13407
|
}
|
|
13408
13408
|
findX509Paths(didOrHostname, usage = "server") {
|
|
13409
|
-
const fs = requireNode$
|
|
13409
|
+
const fs = requireNode$2("node:fs");
|
|
13410
13410
|
const exact = this.identityDirMatch(didOrHostname);
|
|
13411
13411
|
const exactPaths = x509PathsForDirs(exact.publicDir, exact.securityDir, usage);
|
|
13412
13412
|
if (fs.existsSync(exactPaths.fullchain) || fs.existsSync(exactPaths.cert)) {
|
|
@@ -13423,7 +13423,7 @@ class IdentityRoots {
|
|
|
13423
13423
|
}
|
|
13424
13424
|
const wildcardHost = `_.${labels.slice(1).join(".")}`;
|
|
13425
13425
|
const wildcardDirName = encodeIdentityDirName(wildcardHost);
|
|
13426
|
-
const path = requireNode$
|
|
13426
|
+
const path = requireNode$2("node:path");
|
|
13427
13427
|
const publicDir = path.join(this.publicRoot, wildcardDirName);
|
|
13428
13428
|
const securityDir = path.join(this.securityRoot, wildcardDirName);
|
|
13429
13429
|
const wildcardPaths = x509PathsForDirs(publicDir, securityDir, usage);
|
|
@@ -13516,8 +13516,8 @@ function getCaPemValidationFailure(pem) {
|
|
|
13516
13516
|
}
|
|
13517
13517
|
}
|
|
13518
13518
|
async function createCa(outputDir, name = "devtest") {
|
|
13519
|
-
const fs = requireNode$
|
|
13520
|
-
const path = requireNode$
|
|
13519
|
+
const fs = requireNode$2("node:fs");
|
|
13520
|
+
const path = requireNode$2("node:path");
|
|
13521
13521
|
const crypto2 = getCrypto();
|
|
13522
13522
|
cryptoProvider.set(crypto2);
|
|
13523
13523
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -13548,8 +13548,8 @@ async function createCa(outputDir, name = "devtest") {
|
|
|
13548
13548
|
return { caCertPath, caKeyPath };
|
|
13549
13549
|
}
|
|
13550
13550
|
async function ensureCa(caDir, name = "devtest") {
|
|
13551
|
-
const fs = requireNode$
|
|
13552
|
-
const path = requireNode$
|
|
13551
|
+
const fs = requireNode$2("node:fs");
|
|
13552
|
+
const path = requireNode$2("node:path");
|
|
13553
13553
|
const caCertPath = path.join(caDir, `${name}_ca_cert.pem`);
|
|
13554
13554
|
const caKeyPath = path.join(caDir, `${name}_ca_key.pem`);
|
|
13555
13555
|
if (fs.existsSync(caCertPath) && fs.existsSync(caKeyPath)) {
|
|
@@ -13564,8 +13564,8 @@ async function ensureCa(caDir, name = "devtest") {
|
|
|
13564
13564
|
return createCa(caDir, name);
|
|
13565
13565
|
}
|
|
13566
13566
|
function findCaFiles(caDir) {
|
|
13567
|
-
const fs = requireNode$
|
|
13568
|
-
const path = requireNode$
|
|
13567
|
+
const fs = requireNode$2("node:fs");
|
|
13568
|
+
const path = requireNode$2("node:path");
|
|
13569
13569
|
const certFiles = fs.readdirSync(caDir).filter((f) => f.endsWith("_ca_cert.pem"));
|
|
13570
13570
|
if (certFiles.length === 0) {
|
|
13571
13571
|
throw new Error(`No CA certificate found matching *_ca_cert.pem pattern in ${caDir}`);
|
|
@@ -13581,7 +13581,7 @@ function findCaFiles(caDir) {
|
|
|
13581
13581
|
return { caCertPath, caKeyPath };
|
|
13582
13582
|
}
|
|
13583
13583
|
async function issueX509CertFromCa(caDir, dnsNames, uriSans = [], usage = "server") {
|
|
13584
|
-
const fs = requireNode$
|
|
13584
|
+
const fs = requireNode$2("node:fs");
|
|
13585
13585
|
const crypto2 = getCrypto();
|
|
13586
13586
|
cryptoProvider.set(crypto2);
|
|
13587
13587
|
const { caCertPath, caKeyPath } = findCaFiles(caDir);
|
|
@@ -13634,8 +13634,8 @@ async function issueX509CertFromCa(caDir, dnsNames, uriSans = [], usage = "serve
|
|
|
13634
13634
|
};
|
|
13635
13635
|
}
|
|
13636
13636
|
async function createCertFromCa(caDir, hostname, targetDir, hostnames) {
|
|
13637
|
-
const fs = requireNode$
|
|
13638
|
-
const path = requireNode$
|
|
13637
|
+
const fs = requireNode$2("node:fs");
|
|
13638
|
+
const path = requireNode$2("node:path");
|
|
13639
13639
|
const dnsNames = hostnames && hostnames.length > 0 ? hostnames : [hostname];
|
|
13640
13640
|
const commonName = dnsNames[0];
|
|
13641
13641
|
const safeHostname = commonName.replace(/\*/g, "wildcard").replace(/\./g, "_");
|
|
@@ -13653,8 +13653,8 @@ function normalizePem(pem) {
|
|
|
13653
13653
|
`;
|
|
13654
13654
|
}
|
|
13655
13655
|
function writeFileAtomicSync(filePath, content, mode) {
|
|
13656
|
-
const fs = requireNode$
|
|
13657
|
-
const path = requireNode$
|
|
13656
|
+
const fs = requireNode$2("node:fs");
|
|
13657
|
+
const path = requireNode$2("node:path");
|
|
13658
13658
|
const dir = path.dirname(filePath);
|
|
13659
13659
|
const tempPath = path.join(dir, `.${path.basename(filePath)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
13660
13660
|
try {
|
|
@@ -13753,7 +13753,7 @@ async function buildX509Metadata(did, usage, rawHostUri, dirName, issued) {
|
|
|
13753
13753
|
};
|
|
13754
13754
|
}
|
|
13755
13755
|
async function createIdentityCertFromCa(caDir, didOrHostname, rootsInput, options = {}) {
|
|
13756
|
-
const fs = requireNode$
|
|
13756
|
+
const fs = requireNode$2("node:fs");
|
|
13757
13757
|
const roots = resolveIdentityRoots(rootsInput);
|
|
13758
13758
|
const usage = options.usage ?? "server";
|
|
13759
13759
|
const did = canonicalIdentityDid(didOrHostname);
|
|
@@ -14111,6 +14111,1035 @@ function getDevTestEvmAccountByUsername(username) {
|
|
|
14111
14111
|
}
|
|
14112
14112
|
return account;
|
|
14113
14113
|
}
|
|
14114
|
+
const ACTIVATION_DEVICE_NAME = "ood1";
|
|
14115
|
+
const ACTIVATION_NET_ID = "wan";
|
|
14116
|
+
const ACTIVATION_DEFAULT_RTCP_PORT = 2980;
|
|
14117
|
+
const ACTIVATION_DOCUMENT_VALIDITY_SECONDS = DEFAULT_EXPIRE_TIME;
|
|
14118
|
+
const ACTIVATION_MIN_PASSWORD_LENGTH = 8;
|
|
14119
|
+
const ACTIVATION_LOCK_FILE_NAME = "provision_activation.lock";
|
|
14120
|
+
const ZONE_DNS_RECORDS_FILE_NAME = "zone_dns_records.json";
|
|
14121
|
+
const ZONE_DOCUMENT_JWT_FILE_NAME = "zone_document.jwt";
|
|
14122
|
+
const UNACTIVATED_GATEWAY_DEVICE_DID = "did:bns:unactivated.local";
|
|
14123
|
+
const MAX_INLINE_DOCUMENT_BYTES = 4096;
|
|
14124
|
+
const ACTIVATION_LOCK_SCHEMA_VERSION = 1;
|
|
14125
|
+
class ActivationError extends Error {
|
|
14126
|
+
constructor(code, message, details = {}) {
|
|
14127
|
+
super(message);
|
|
14128
|
+
this.name = "ActivationError";
|
|
14129
|
+
this.code = code;
|
|
14130
|
+
this.details = details;
|
|
14131
|
+
}
|
|
14132
|
+
}
|
|
14133
|
+
function requireNode$1(moduleName) {
|
|
14134
|
+
const proc = globalThis.process;
|
|
14135
|
+
if (typeof (proc == null ? void 0 : proc.getBuiltinModule) === "function") {
|
|
14136
|
+
const builtin = proc.getBuiltinModule(moduleName);
|
|
14137
|
+
if (builtin) {
|
|
14138
|
+
return builtin;
|
|
14139
|
+
}
|
|
14140
|
+
}
|
|
14141
|
+
if (typeof require === "function") {
|
|
14142
|
+
return require(moduleName);
|
|
14143
|
+
}
|
|
14144
|
+
throw new ActivationError(
|
|
14145
|
+
"RUNTIME_UNSUPPORTED",
|
|
14146
|
+
`buckyos provision cannot load builtin module ${moduleName} in this runtime (Node >= 22.13 or Deno >= 2.2 is required)`
|
|
14147
|
+
);
|
|
14148
|
+
}
|
|
14149
|
+
function isWindows() {
|
|
14150
|
+
var _a3;
|
|
14151
|
+
return ((_a3 = globalThis.process) == null ? void 0 : _a3.platform) === "win32";
|
|
14152
|
+
}
|
|
14153
|
+
function toPosixRelative(rootDir, filePath) {
|
|
14154
|
+
const path = requireNode$1("node:path");
|
|
14155
|
+
return path.relative(rootDir, filePath).split(path.sep).join("/");
|
|
14156
|
+
}
|
|
14157
|
+
function sha256Hex(content) {
|
|
14158
|
+
const crypto2 = requireNode$1("node:crypto");
|
|
14159
|
+
return crypto2.createHash("sha256").update(content, "utf8").digest("hex");
|
|
14160
|
+
}
|
|
14161
|
+
function randomSuffix() {
|
|
14162
|
+
const crypto2 = requireNode$1("node:crypto");
|
|
14163
|
+
return crypto2.randomBytes(6).toString("hex");
|
|
14164
|
+
}
|
|
14165
|
+
function lstatOrNull(filePath) {
|
|
14166
|
+
const fs = requireNode$1("node:fs");
|
|
14167
|
+
try {
|
|
14168
|
+
return fs.lstatSync(filePath);
|
|
14169
|
+
} catch (error) {
|
|
14170
|
+
const code = error.code;
|
|
14171
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
14172
|
+
return null;
|
|
14173
|
+
throw error;
|
|
14174
|
+
}
|
|
14175
|
+
}
|
|
14176
|
+
function statOrNull(filePath) {
|
|
14177
|
+
const fs = requireNode$1("node:fs");
|
|
14178
|
+
try {
|
|
14179
|
+
return fs.statSync(filePath);
|
|
14180
|
+
} catch (error) {
|
|
14181
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") {
|
|
14182
|
+
return null;
|
|
14183
|
+
}
|
|
14184
|
+
throw error;
|
|
14185
|
+
}
|
|
14186
|
+
}
|
|
14187
|
+
function isDirectory(filePath) {
|
|
14188
|
+
var _a3;
|
|
14189
|
+
return ((_a3 = statOrNull(filePath)) == null ? void 0 : _a3.isDirectory()) === true;
|
|
14190
|
+
}
|
|
14191
|
+
function isFile(filePath) {
|
|
14192
|
+
var _a3;
|
|
14193
|
+
return ((_a3 = statOrNull(filePath)) == null ? void 0 : _a3.isFile()) === true;
|
|
14194
|
+
}
|
|
14195
|
+
function readJsonObjectOrThrow(filePath) {
|
|
14196
|
+
const fs = requireNode$1("node:fs");
|
|
14197
|
+
const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
14198
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
14199
|
+
throw new Error(`${filePath} is not a JSON object`);
|
|
14200
|
+
}
|
|
14201
|
+
return value;
|
|
14202
|
+
}
|
|
14203
|
+
function jsonPretty(value) {
|
|
14204
|
+
return JSON.stringify(value, null, 2);
|
|
14205
|
+
}
|
|
14206
|
+
function commitFile(filePath, content, mode, dirMode) {
|
|
14207
|
+
const fs = requireNode$1("node:fs");
|
|
14208
|
+
const path = requireNode$1("node:path");
|
|
14209
|
+
const directory = path.dirname(filePath);
|
|
14210
|
+
fs.mkdirSync(directory, { recursive: true, mode: dirMode });
|
|
14211
|
+
if (!isWindows()) {
|
|
14212
|
+
fs.chmodSync(directory, dirMode);
|
|
14213
|
+
}
|
|
14214
|
+
if (lstatOrNull(filePath)) {
|
|
14215
|
+
throw new Error(`refusing to overwrite existing file: ${filePath}`);
|
|
14216
|
+
}
|
|
14217
|
+
const temporary = path.join(directory, `.${path.basename(filePath)}.provision-${randomSuffix()}`);
|
|
14218
|
+
try {
|
|
14219
|
+
fs.writeFileSync(temporary, content, { mode, flag: "wx" });
|
|
14220
|
+
if (!isWindows()) {
|
|
14221
|
+
fs.chmodSync(temporary, mode);
|
|
14222
|
+
}
|
|
14223
|
+
fs.renameSync(temporary, filePath);
|
|
14224
|
+
} finally {
|
|
14225
|
+
if (lstatOrNull(temporary)) {
|
|
14226
|
+
fs.rmSync(temporary, { force: true });
|
|
14227
|
+
}
|
|
14228
|
+
}
|
|
14229
|
+
}
|
|
14230
|
+
function normalizeDomain(value) {
|
|
14231
|
+
return value.trim().toLowerCase().replace(/\.$/, "");
|
|
14232
|
+
}
|
|
14233
|
+
function validateZoneDomain(value) {
|
|
14234
|
+
const domain = normalizeDomain(String(value ?? ""));
|
|
14235
|
+
if (domain.length === 0 || domain.length > 253 || !domain.includes(".") || domain.split(".").some(
|
|
14236
|
+
(label) => label.length === 0 || label.length > 63 || label.startsWith("-") || label.endsWith("-") || !/^[a-z0-9-]+$/.test(label)
|
|
14237
|
+
)) {
|
|
14238
|
+
throw new ActivationError("INVALID_ARGUMENT", `invalid did:web domain: ${value}`, { field: "domain" });
|
|
14239
|
+
}
|
|
14240
|
+
return domain;
|
|
14241
|
+
}
|
|
14242
|
+
function validateOwnerName(value) {
|
|
14243
|
+
const name = String(value ?? "").trim().toLowerCase();
|
|
14244
|
+
if (name.length === 0 || name.length > 63 || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(name)) {
|
|
14245
|
+
throw new ActivationError(
|
|
14246
|
+
"INVALID_ARGUMENT",
|
|
14247
|
+
"owner name must be a lowercase DNS label (letters, digits, and hyphens)",
|
|
14248
|
+
{ field: "owner_name" }
|
|
14249
|
+
);
|
|
14250
|
+
}
|
|
14251
|
+
return name;
|
|
14252
|
+
}
|
|
14253
|
+
function validateRtcpPort(value) {
|
|
14254
|
+
const port = value ?? ACTIVATION_DEFAULT_RTCP_PORT;
|
|
14255
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
14256
|
+
throw new ActivationError("INVALID_ARGUMENT", `invalid RTCP port: ${String(value)}`, { field: "rtcp_port" });
|
|
14257
|
+
}
|
|
14258
|
+
return port;
|
|
14259
|
+
}
|
|
14260
|
+
function validatePublicIp(value) {
|
|
14261
|
+
const publicIp = (value == null ? void 0 : value.trim()) || void 0;
|
|
14262
|
+
if (publicIp === void 0)
|
|
14263
|
+
return void 0;
|
|
14264
|
+
const net = requireNode$1("node:net");
|
|
14265
|
+
if (net.isIP(publicIp) === 0) {
|
|
14266
|
+
throw new ActivationError("INVALID_ARGUMENT", `invalid public IP address: ${publicIp}`, { field: "public_ip" });
|
|
14267
|
+
}
|
|
14268
|
+
return publicIp;
|
|
14269
|
+
}
|
|
14270
|
+
function validateAdminPassword(value) {
|
|
14271
|
+
if (typeof value !== "string" || value.length < ACTIVATION_MIN_PASSWORD_LENGTH) {
|
|
14272
|
+
throw new ActivationError(
|
|
14273
|
+
"INVALID_ARGUMENT",
|
|
14274
|
+
`administrator password must contain at least ${ACTIVATION_MIN_PASSWORD_LENGTH} characters`,
|
|
14275
|
+
{ field: "admin_password" }
|
|
14276
|
+
);
|
|
14277
|
+
}
|
|
14278
|
+
return value;
|
|
14279
|
+
}
|
|
14280
|
+
function hashAdminPassword(ownerName, password) {
|
|
14281
|
+
return hashPassword(ownerName, password, null);
|
|
14282
|
+
}
|
|
14283
|
+
function buildActivationDnsRecords(domain, publicIp) {
|
|
14284
|
+
const records = [];
|
|
14285
|
+
if (publicIp) {
|
|
14286
|
+
const net = requireNode$1("node:net");
|
|
14287
|
+
records.push({ type: net.isIP(publicIp) === 6 ? "AAAA" : "A", name: domain, value: publicIp });
|
|
14288
|
+
}
|
|
14289
|
+
return records;
|
|
14290
|
+
}
|
|
14291
|
+
function generateProvisionKeyPair() {
|
|
14292
|
+
const crypto2 = requireNode$1("node:crypto");
|
|
14293
|
+
const { privateKey, publicKey } = crypto2.generateKeyPairSync("ed25519");
|
|
14294
|
+
const jwk = publicKey.export({ format: "jwk" });
|
|
14295
|
+
if (typeof jwk.x !== "string" || jwk.x.length === 0) {
|
|
14296
|
+
throw new ActivationError("KEY_GENERATION_FAILED", "generated Ed25519 public key has no x coordinate");
|
|
14297
|
+
}
|
|
14298
|
+
return {
|
|
14299
|
+
privateKeyPem: privateKey.export({ format: "pem", type: "pkcs8" }).toString(),
|
|
14300
|
+
publicKeyX: jwk.x
|
|
14301
|
+
};
|
|
14302
|
+
}
|
|
14303
|
+
function normalizeActivationParams(options) {
|
|
14304
|
+
const path = requireNode$1("node:path");
|
|
14305
|
+
if (typeof options.rootDir !== "string" || options.rootDir.trim().length === 0) {
|
|
14306
|
+
throw new ActivationError("INVALID_ARGUMENT", "target root is required", { field: "root" });
|
|
14307
|
+
}
|
|
14308
|
+
if (typeof options.ownerKeyBackupPath !== "string" || options.ownerKeyBackupPath.trim().length === 0) {
|
|
14309
|
+
throw new ActivationError("INVALID_ARGUMENT", "owner key backup path is required", { field: "owner_key_backup" });
|
|
14310
|
+
}
|
|
14311
|
+
const domain = validateZoneDomain(options.domain);
|
|
14312
|
+
const zoneDid = new DID("web", domain);
|
|
14313
|
+
return {
|
|
14314
|
+
rootDir: path.resolve(options.rootDir),
|
|
14315
|
+
domain,
|
|
14316
|
+
ownerName: validateOwnerName(options.ownerName),
|
|
14317
|
+
ownerKeyBackupPath: path.resolve(options.ownerKeyBackupPath),
|
|
14318
|
+
rtcpPort: validateRtcpPort(options.rtcpPort),
|
|
14319
|
+
guestAccess: options.guestAccess ?? false,
|
|
14320
|
+
publicIp: validatePublicIp(options.publicIp),
|
|
14321
|
+
ownerDid: zoneDid,
|
|
14322
|
+
zoneDid,
|
|
14323
|
+
deviceDid: buildDeviceDid(ACTIVATION_DEVICE_NAME, zoneDid)
|
|
14324
|
+
};
|
|
14325
|
+
}
|
|
14326
|
+
function identityRootsFor(rootDir) {
|
|
14327
|
+
const path = requireNode$1("node:path");
|
|
14328
|
+
return new IdentityRoots(path.join(rootDir, "local", "identity"), path.join(rootDir, "security"));
|
|
14329
|
+
}
|
|
14330
|
+
function layoutFor(rootDir, zoneDid, deviceDid) {
|
|
14331
|
+
const path = requireNode$1("node:path");
|
|
14332
|
+
const etcDir = path.join(rootDir, "etc");
|
|
14333
|
+
const paths = deviceIdentityPathsForRoots(identityRootsFor(rootDir), deviceDid);
|
|
14334
|
+
return {
|
|
14335
|
+
etcDir,
|
|
14336
|
+
lockPath: path.join(etcDir, ACTIVATION_LOCK_FILE_NAME),
|
|
14337
|
+
nodeIdentityPath: path.join(etcDir, "node_identity.json"),
|
|
14338
|
+
startConfigPath: path.join(etcDir, "start_config.json"),
|
|
14339
|
+
zoneDocumentJwtPath: path.join(etcDir, ZONE_DOCUMENT_JWT_FILE_NAME),
|
|
14340
|
+
gatewayParamsPath: path.join(etcDir, "node_gateway_params.json"),
|
|
14341
|
+
dnsRecordsPath: path.join(etcDir, ZONE_DNS_RECORDS_FILE_NAME),
|
|
14342
|
+
zoneBootOverridePath: path.join(etcDir, `${zoneDid.toRawHostName()}.zone.json`),
|
|
14343
|
+
didJsonPath: paths.didJson,
|
|
14344
|
+
deviceDocJwtPath: paths.deviceDocJwt,
|
|
14345
|
+
deviceMiniDocJwtPath: paths.deviceMiniDocJwt,
|
|
14346
|
+
devicePrivateKeyPath: paths.authenticationPrivateKey,
|
|
14347
|
+
publicDir: paths.publicDir,
|
|
14348
|
+
securityDir: paths.securityDir
|
|
14349
|
+
};
|
|
14350
|
+
}
|
|
14351
|
+
function plannedFiles(rootDir, layout) {
|
|
14352
|
+
const entry = (absolutePath, role, required, secret) => ({
|
|
14353
|
+
path: toPosixRelative(rootDir, absolutePath),
|
|
14354
|
+
role,
|
|
14355
|
+
required,
|
|
14356
|
+
secret,
|
|
14357
|
+
absolutePath,
|
|
14358
|
+
mode: secret ? 384 : 420,
|
|
14359
|
+
dirMode: secret ? 448 : 493
|
|
14360
|
+
});
|
|
14361
|
+
return [
|
|
14362
|
+
entry(layout.devicePrivateKeyPath, "device_private_key", true, true),
|
|
14363
|
+
entry(layout.didJsonPath, "device_document", true, false),
|
|
14364
|
+
entry(layout.deviceDocJwtPath, "device_document_jwt", true, false),
|
|
14365
|
+
entry(layout.deviceMiniDocJwtPath, "device_mini_document_jwt", true, false),
|
|
14366
|
+
entry(layout.zoneBootOverridePath, "zone_boot_override", true, false),
|
|
14367
|
+
entry(layout.zoneDocumentJwtPath, "zone_document_jwt", true, false),
|
|
14368
|
+
entry(layout.dnsRecordsPath, "dns_records", false, false),
|
|
14369
|
+
entry(layout.startConfigPath, "start_config", true, false),
|
|
14370
|
+
entry(layout.gatewayParamsPath, "gateway_params", true, false),
|
|
14371
|
+
entry(layout.nodeIdentityPath, "node_identity", true, false)
|
|
14372
|
+
];
|
|
14373
|
+
}
|
|
14374
|
+
function publicEntry(file) {
|
|
14375
|
+
return { path: file.path, role: file.role, required: file.required, secret: file.secret };
|
|
14376
|
+
}
|
|
14377
|
+
function readLock(lockPath) {
|
|
14378
|
+
if (!lstatOrNull(lockPath))
|
|
14379
|
+
return null;
|
|
14380
|
+
const base = {
|
|
14381
|
+
path: lockPath,
|
|
14382
|
+
schemaVersion: null,
|
|
14383
|
+
traceId: null,
|
|
14384
|
+
startedAt: null,
|
|
14385
|
+
domain: null,
|
|
14386
|
+
stage: null,
|
|
14387
|
+
committedFiles: [],
|
|
14388
|
+
ownerKeyBackupPath: null,
|
|
14389
|
+
corrupt: false
|
|
14390
|
+
};
|
|
14391
|
+
try {
|
|
14392
|
+
const value = readJsonObjectOrThrow(lockPath);
|
|
14393
|
+
const stage = value.stage;
|
|
14394
|
+
return {
|
|
14395
|
+
...base,
|
|
14396
|
+
schemaVersion: typeof value.schema_version === "number" ? value.schema_version : null,
|
|
14397
|
+
traceId: typeof value.trace_id === "string" ? value.trace_id : null,
|
|
14398
|
+
startedAt: typeof value.started_at === "number" ? value.started_at : null,
|
|
14399
|
+
domain: typeof value.domain === "string" ? value.domain : null,
|
|
14400
|
+
stage: stage === "locked" || stage === "backup_saved" || stage === "committing" || stage === "committed" ? stage : null,
|
|
14401
|
+
committedFiles: Array.isArray(value.committed) ? value.committed.filter((item) => typeof item === "string") : [],
|
|
14402
|
+
ownerKeyBackupPath: typeof value.owner_key_backup === "string" ? value.owner_key_backup : null
|
|
14403
|
+
};
|
|
14404
|
+
} catch {
|
|
14405
|
+
return { ...base, corrupt: true };
|
|
14406
|
+
}
|
|
14407
|
+
}
|
|
14408
|
+
function acquireLock(lockPath, record) {
|
|
14409
|
+
const fs = requireNode$1("node:fs");
|
|
14410
|
+
try {
|
|
14411
|
+
fs.writeFileSync(lockPath, jsonPretty(record), { mode: 384, flag: "wx" });
|
|
14412
|
+
} catch (error) {
|
|
14413
|
+
if (error.code === "EEXIST") {
|
|
14414
|
+
throw new ActivationError(
|
|
14415
|
+
"ACTIVATION_IN_PROGRESS",
|
|
14416
|
+
`another activation holds the lock ${lockPath}; inspect it with provision status`,
|
|
14417
|
+
{ lock_file: lockPath, lock: readLock(lockPath) }
|
|
14418
|
+
);
|
|
14419
|
+
}
|
|
14420
|
+
throw error;
|
|
14421
|
+
}
|
|
14422
|
+
}
|
|
14423
|
+
function updateLock(lockPath, record) {
|
|
14424
|
+
const fs = requireNode$1("node:fs");
|
|
14425
|
+
const path = requireNode$1("node:path");
|
|
14426
|
+
const temporary = path.join(path.dirname(lockPath), `.${ACTIVATION_LOCK_FILE_NAME}.${randomSuffix()}`);
|
|
14427
|
+
try {
|
|
14428
|
+
fs.writeFileSync(temporary, jsonPretty(record), { mode: 384, flag: "wx" });
|
|
14429
|
+
fs.renameSync(temporary, lockPath);
|
|
14430
|
+
} finally {
|
|
14431
|
+
if (lstatOrNull(temporary)) {
|
|
14432
|
+
fs.rmSync(temporary, { force: true });
|
|
14433
|
+
}
|
|
14434
|
+
}
|
|
14435
|
+
}
|
|
14436
|
+
function listSubdirectories(dirPath) {
|
|
14437
|
+
const fs = requireNode$1("node:fs");
|
|
14438
|
+
if (!isDirectory(dirPath))
|
|
14439
|
+
return [];
|
|
14440
|
+
return fs.readdirSync(dirPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
14441
|
+
}
|
|
14442
|
+
function listFilesMatching(dirPath, predicate) {
|
|
14443
|
+
const fs = requireNode$1("node:fs");
|
|
14444
|
+
if (!isDirectory(dirPath))
|
|
14445
|
+
return [];
|
|
14446
|
+
return fs.readdirSync(dirPath, { withFileTypes: true }).filter((entry) => entry.isFile() && predicate(entry.name)).map((entry) => entry.name).sort();
|
|
14447
|
+
}
|
|
14448
|
+
function observeIdentity(nodeIdentityPath, problems) {
|
|
14449
|
+
if (!isFile(nodeIdentityPath))
|
|
14450
|
+
return { nodeIdentity: null, zoneDid: null, deviceDid: null };
|
|
14451
|
+
try {
|
|
14452
|
+
const value = readJsonObjectOrThrow(nodeIdentityPath);
|
|
14453
|
+
if (value.schema !== NODE_IDENTITY_SCHEMA_V2) {
|
|
14454
|
+
problems.push({
|
|
14455
|
+
code: "NODE_IDENTITY_SCHEMA",
|
|
14456
|
+
message: `etc/node_identity.json schema '${String(value.schema)}' is not ${NODE_IDENTITY_SCHEMA_V2}`
|
|
14457
|
+
});
|
|
14458
|
+
return { nodeIdentity: value, zoneDid: null, deviceDid: null };
|
|
14459
|
+
}
|
|
14460
|
+
const zoneDid = DID.fromStr(String(value.zone_did));
|
|
14461
|
+
const deviceDid = DID.fromStr(String(value.device_did));
|
|
14462
|
+
return { nodeIdentity: value, zoneDid, deviceDid };
|
|
14463
|
+
} catch (error) {
|
|
14464
|
+
problems.push({
|
|
14465
|
+
code: "NODE_IDENTITY_UNREADABLE",
|
|
14466
|
+
message: `etc/node_identity.json cannot be parsed: ${error instanceof Error ? error.message : String(error)}`
|
|
14467
|
+
});
|
|
14468
|
+
return { nodeIdentity: null, zoneDid: null, deviceDid: null };
|
|
14469
|
+
}
|
|
14470
|
+
}
|
|
14471
|
+
function gatewayParamsIsPlaceholder(gatewayParamsPath) {
|
|
14472
|
+
try {
|
|
14473
|
+
const value = readJsonObjectOrThrow(gatewayParamsPath);
|
|
14474
|
+
const params = value.params;
|
|
14475
|
+
return !!params && typeof params === "object" && params.device_did === UNACTIVATED_GATEWAY_DEVICE_DID;
|
|
14476
|
+
} catch {
|
|
14477
|
+
return false;
|
|
14478
|
+
}
|
|
14479
|
+
}
|
|
14480
|
+
async function validateActivationMaterials(rootDir, layout, identity, problems) {
|
|
14481
|
+
var _a3, _b, _c;
|
|
14482
|
+
const fs = requireNode$1("node:fs");
|
|
14483
|
+
const nodeIdentity = identity.nodeIdentity;
|
|
14484
|
+
if (!nodeIdentity || !identity.zoneDid || !identity.deviceDid) {
|
|
14485
|
+
return { ownerDid: null, accessHostname: null };
|
|
14486
|
+
}
|
|
14487
|
+
const problem = (code, message) => problems.push({ code, message });
|
|
14488
|
+
const zoneDid = identity.zoneDid;
|
|
14489
|
+
const deviceDid = identity.deviceDid;
|
|
14490
|
+
const ownerDid = String(nodeIdentity.owner_did);
|
|
14491
|
+
const ownerKey = nodeIdentity.owner_public_key;
|
|
14492
|
+
if (zoneDid.method !== "web") {
|
|
14493
|
+
problem("IDENTITY_NOT_DID_WEB", `zone DID ${zoneDid.toString()} is not a did:web identity`);
|
|
14494
|
+
}
|
|
14495
|
+
if (ownerDid !== zoneDid.toString()) {
|
|
14496
|
+
problem("OWNER_ZONE_MISMATCH", `owner DID ${ownerDid} differs from zone DID ${zoneDid.toString()}`);
|
|
14497
|
+
}
|
|
14498
|
+
const expectedDeviceDid = buildDeviceDid(String(nodeIdentity.device_name), zoneDid).toString();
|
|
14499
|
+
if (deviceDid.toString() !== expectedDeviceDid) {
|
|
14500
|
+
problem("DEVICE_DID_MISMATCH", `device DID ${deviceDid.toString()} is not ${expectedDeviceDid}`);
|
|
14501
|
+
}
|
|
14502
|
+
if (!ownerKey || typeof ownerKey !== "object" || typeof ownerKey.x !== "string") {
|
|
14503
|
+
problem("OWNER_KEY_MISSING", "etc/node_identity.json owner_public_key is not an Ed25519 JWK");
|
|
14504
|
+
return { ownerDid, accessHostname: null };
|
|
14505
|
+
}
|
|
14506
|
+
let startConfig = null;
|
|
14507
|
+
try {
|
|
14508
|
+
startConfig = readJsonObjectOrThrow(layout.startConfigPath);
|
|
14509
|
+
} catch (error) {
|
|
14510
|
+
problem("START_CONFIG_UNREADABLE", `etc/start_config.json cannot be parsed: ${error instanceof Error ? error.message : String(error)}`);
|
|
14511
|
+
}
|
|
14512
|
+
let accessHostname = null;
|
|
14513
|
+
const jwts = {};
|
|
14514
|
+
if (startConfig) {
|
|
14515
|
+
for (const key of ["user_name", "zone_name", "access_hostname", "admin_password_hash"]) {
|
|
14516
|
+
if (typeof startConfig[key] !== "string" || startConfig[key].length === 0) {
|
|
14517
|
+
problem("START_CONFIG_FIELD", `etc/start_config.json is missing ${key}`);
|
|
14518
|
+
}
|
|
14519
|
+
}
|
|
14520
|
+
for (const key of ["boot_config_jwt", "device_doc_jwt", "device_mini_doc_jwt", "zone_document_jwt", "ood_jwt"]) {
|
|
14521
|
+
if (typeof startConfig[key] === "string" && startConfig[key].length > 0) {
|
|
14522
|
+
jwts[key] = startConfig[key];
|
|
14523
|
+
} else {
|
|
14524
|
+
problem("START_CONFIG_FIELD", `etc/start_config.json is missing ${key}`);
|
|
14525
|
+
}
|
|
14526
|
+
}
|
|
14527
|
+
if (startConfig.zone_name !== zoneDid.toString()) {
|
|
14528
|
+
problem("START_CONFIG_ZONE", `etc/start_config.json zone_name is not ${zoneDid.toString()}`);
|
|
14529
|
+
}
|
|
14530
|
+
if (typeof startConfig.access_hostname === "string") {
|
|
14531
|
+
accessHostname = startConfig.access_hostname;
|
|
14532
|
+
if (accessHostname !== zoneDid.toRawHostName()) {
|
|
14533
|
+
problem("START_CONFIG_HOSTNAME", `etc/start_config.json access_hostname is not ${zoneDid.toRawHostName()}`);
|
|
14534
|
+
}
|
|
14535
|
+
}
|
|
14536
|
+
const ownerDocument = startConfig.owner_document;
|
|
14537
|
+
if (!ownerDocument || typeof ownerDocument !== "object") {
|
|
14538
|
+
problem("START_CONFIG_OWNER", "etc/start_config.json is missing owner_document");
|
|
14539
|
+
} else {
|
|
14540
|
+
if (ownerDocument.id !== ownerDid) {
|
|
14541
|
+
problem("START_CONFIG_OWNER", `owner_document id ${String(ownerDocument.id)} is not ${ownerDid}`);
|
|
14542
|
+
}
|
|
14543
|
+
const method = Array.isArray(ownerDocument.verificationMethod) ? ownerDocument.verificationMethod.find((item) => item && item.id === "#main_key") : void 0;
|
|
14544
|
+
if (!method || getXFromJwkSafe(method.publicKeyJwk) !== ownerKey.x) {
|
|
14545
|
+
problem("START_CONFIG_OWNER_KEY", "owner_document #main_key differs from node_identity owner_public_key");
|
|
14546
|
+
}
|
|
14547
|
+
}
|
|
14548
|
+
if (jwts.ood_jwt && jwts.device_doc_jwt && jwts.ood_jwt !== jwts.device_doc_jwt) {
|
|
14549
|
+
problem("START_CONFIG_OOD_JWT", "etc/start_config.json ood_jwt differs from device_doc_jwt");
|
|
14550
|
+
}
|
|
14551
|
+
for (const [key, secret] of [["private_key", true], ["device_private_key", true], ["sn_access_token", true], ["bns_evm_private_key", true]]) {
|
|
14552
|
+
if (secret && key in startConfig)
|
|
14553
|
+
problem("START_CONFIG_SECRET", `etc/start_config.json must not contain ${key}`);
|
|
14554
|
+
}
|
|
14555
|
+
}
|
|
14556
|
+
const readText = (filePath, label) => {
|
|
14557
|
+
try {
|
|
14558
|
+
return String(fs.readFileSync(filePath, "utf8"));
|
|
14559
|
+
} catch (error) {
|
|
14560
|
+
problem("FILE_UNREADABLE", `${label} cannot be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
14561
|
+
return null;
|
|
14562
|
+
}
|
|
14563
|
+
};
|
|
14564
|
+
const zoneDocumentJwt = ((_a3 = readText(layout.zoneDocumentJwtPath, "etc/zone_document.jwt")) == null ? void 0 : _a3.trim()) ?? null;
|
|
14565
|
+
const deviceDocJwt = ((_b = readText(layout.deviceDocJwtPath, toPosixRelative(rootDir, layout.deviceDocJwtPath))) == null ? void 0 : _b.trim()) ?? null;
|
|
14566
|
+
const deviceMiniDocJwt = ((_c = readText(layout.deviceMiniDocJwtPath, toPosixRelative(rootDir, layout.deviceMiniDocJwtPath))) == null ? void 0 : _c.trim()) ?? null;
|
|
14567
|
+
if (zoneDocumentJwt && jwts.zone_document_jwt && zoneDocumentJwt !== jwts.zone_document_jwt) {
|
|
14568
|
+
problem("ZONE_DOCUMENT_MISMATCH", "etc/zone_document.jwt differs from start_config zone_document_jwt");
|
|
14569
|
+
}
|
|
14570
|
+
if (deviceDocJwt && jwts.device_doc_jwt && deviceDocJwt !== jwts.device_doc_jwt) {
|
|
14571
|
+
problem("DEVICE_DOCUMENT_MISMATCH", "device_doc.jwt differs from start_config device_doc_jwt");
|
|
14572
|
+
}
|
|
14573
|
+
if (deviceMiniDocJwt && jwts.device_mini_doc_jwt && deviceMiniDocJwt !== jwts.device_mini_doc_jwt) {
|
|
14574
|
+
problem("DEVICE_MINI_DOCUMENT_MISMATCH", "device_mini_doc.jwt differs from start_config device_mini_doc_jwt");
|
|
14575
|
+
}
|
|
14576
|
+
const verify = async (jwt, label) => {
|
|
14577
|
+
if (!jwt)
|
|
14578
|
+
return null;
|
|
14579
|
+
try {
|
|
14580
|
+
return await verifyJwtEdDSA(jwt, ownerKey);
|
|
14581
|
+
} catch (error) {
|
|
14582
|
+
problem("JWT_SIGNATURE", `${label} is not signed by the owner key: ${error instanceof Error ? error.message : String(error)}`);
|
|
14583
|
+
return null;
|
|
14584
|
+
}
|
|
14585
|
+
};
|
|
14586
|
+
const boot = await verify(jwts.boot_config_jwt ?? null, "boot_config_jwt");
|
|
14587
|
+
const device = await verify(deviceDocJwt ?? jwts.device_doc_jwt ?? null, "device_doc.jwt");
|
|
14588
|
+
const mini = await verify(deviceMiniDocJwt ?? jwts.device_mini_doc_jwt ?? null, "device_mini_doc.jwt");
|
|
14589
|
+
const zone = await verify(zoneDocumentJwt ?? jwts.zone_document_jwt ?? null, "zone_document.jwt");
|
|
14590
|
+
const now = buckyosGetUnixTimestamp();
|
|
14591
|
+
if (boot) {
|
|
14592
|
+
if (boot.id !== zoneDid.toString())
|
|
14593
|
+
problem("BOOT_DOCUMENT", "boot document id differs from zone DID");
|
|
14594
|
+
if (!Array.isArray(boot.oods) || boot.oods.length === 0)
|
|
14595
|
+
problem("BOOT_DOCUMENT", "boot document has no oods");
|
|
14596
|
+
if ("sn" in boot)
|
|
14597
|
+
problem("BOOT_DOCUMENT", "boot document must not reference an SN in offline mode");
|
|
14598
|
+
if (typeof boot.exp !== "number" || boot.exp <= now)
|
|
14599
|
+
problem("BOOT_DOCUMENT", "boot document has expired");
|
|
14600
|
+
try {
|
|
14601
|
+
const override = readJsonObjectOrThrow(layout.zoneBootOverridePath);
|
|
14602
|
+
if (JSON.stringify(override) !== JSON.stringify(boot)) {
|
|
14603
|
+
problem("BOOT_OVERRIDE_MISMATCH", `${toPosixRelative(rootDir, layout.zoneBootOverridePath)} differs from boot_config_jwt`);
|
|
14604
|
+
}
|
|
14605
|
+
} catch (error) {
|
|
14606
|
+
problem("BOOT_OVERRIDE_UNREADABLE", `local boot override cannot be parsed: ${error instanceof Error ? error.message : String(error)}`);
|
|
14607
|
+
}
|
|
14608
|
+
}
|
|
14609
|
+
let deviceKeyX = null;
|
|
14610
|
+
if (device) {
|
|
14611
|
+
if (device.id !== deviceDid.toString())
|
|
14612
|
+
problem("DEVICE_DOCUMENT", "device document id differs from node_identity device_did");
|
|
14613
|
+
if (device.owner !== ownerDid)
|
|
14614
|
+
problem("DEVICE_DOCUMENT", "device document owner differs from owner DID");
|
|
14615
|
+
if (device.zone_did !== zoneDid.toString())
|
|
14616
|
+
problem("DEVICE_DOCUMENT", "device document zone_did differs from zone DID");
|
|
14617
|
+
if (typeof device.exp !== "number" || device.exp <= now)
|
|
14618
|
+
problem("DEVICE_DOCUMENT", "device document has expired");
|
|
14619
|
+
const method = Array.isArray(device.verificationMethod) ? device.verificationMethod.find((item) => item && item.id === "#main_key") : void 0;
|
|
14620
|
+
deviceKeyX = method ? getXFromJwkSafe(method.publicKeyJwk) : null;
|
|
14621
|
+
if (!deviceKeyX)
|
|
14622
|
+
problem("DEVICE_DOCUMENT", "device document has no #main_key");
|
|
14623
|
+
try {
|
|
14624
|
+
const didJson = readJsonObjectOrThrow(layout.didJsonPath);
|
|
14625
|
+
if (didJson.id !== deviceDid.toString()) {
|
|
14626
|
+
problem("DEVICE_DID_JSON", `${toPosixRelative(rootDir, layout.didJsonPath)} id differs from node_identity device_did`);
|
|
14627
|
+
}
|
|
14628
|
+
} catch (error) {
|
|
14629
|
+
problem("DEVICE_DID_JSON", `device did.json cannot be parsed: ${error instanceof Error ? error.message : String(error)}`);
|
|
14630
|
+
}
|
|
14631
|
+
}
|
|
14632
|
+
if (mini && deviceKeyX && mini.x !== deviceKeyX) {
|
|
14633
|
+
problem("DEVICE_MINI_DOCUMENT", "device mini document key differs from device document key");
|
|
14634
|
+
}
|
|
14635
|
+
if (zone) {
|
|
14636
|
+
if (zone.id !== zoneDid.toString())
|
|
14637
|
+
problem("ZONE_DOCUMENT", "zone document id differs from zone DID");
|
|
14638
|
+
if (zone.owner !== ownerDid)
|
|
14639
|
+
problem("ZONE_DOCUMENT", "zone document owner differs from owner DID");
|
|
14640
|
+
if (jwts.boot_config_jwt && zone.boot_jwt !== jwts.boot_config_jwt) {
|
|
14641
|
+
problem("ZONE_DOCUMENT", "zone document boot_jwt differs from start_config boot_config_jwt");
|
|
14642
|
+
}
|
|
14643
|
+
if ("sn" in zone)
|
|
14644
|
+
problem("ZONE_DOCUMENT", "zone document must not reference an SN in offline mode");
|
|
14645
|
+
}
|
|
14646
|
+
if (deviceKeyX) {
|
|
14647
|
+
try {
|
|
14648
|
+
const pem = String(fs.readFileSync(layout.devicePrivateKeyPath, "utf8"));
|
|
14649
|
+
const x = await getPublicKeyXFromPrivatePem(pem);
|
|
14650
|
+
if (x !== deviceKeyX)
|
|
14651
|
+
problem("DEVICE_PRIVATE_KEY", "device private key does not match the device document key");
|
|
14652
|
+
} catch (error) {
|
|
14653
|
+
problem("DEVICE_PRIVATE_KEY", `device private key cannot be loaded: ${error instanceof Error ? error.message : String(error)}`);
|
|
14654
|
+
}
|
|
14655
|
+
if (!isWindows()) {
|
|
14656
|
+
const stats = lstatOrNull(layout.devicePrivateKeyPath);
|
|
14657
|
+
if (stats && (stats.mode & 63) !== 0) {
|
|
14658
|
+
problem("DEVICE_PRIVATE_KEY_MODE", "device private key is readable by other users");
|
|
14659
|
+
}
|
|
14660
|
+
}
|
|
14661
|
+
}
|
|
14662
|
+
return { ownerDid, accessHostname };
|
|
14663
|
+
}
|
|
14664
|
+
function getXFromJwkSafe(jwk) {
|
|
14665
|
+
try {
|
|
14666
|
+
return getXFromJwk(jwk);
|
|
14667
|
+
} catch {
|
|
14668
|
+
return null;
|
|
14669
|
+
}
|
|
14670
|
+
}
|
|
14671
|
+
async function inspectActivationRoot(rootDir) {
|
|
14672
|
+
const path = requireNode$1("node:path");
|
|
14673
|
+
const root = path.resolve(String(rootDir ?? ""));
|
|
14674
|
+
const observedAt = buckyosGetUnixTimestamp();
|
|
14675
|
+
const problems = [];
|
|
14676
|
+
const warnings = [];
|
|
14677
|
+
const base = (state) => ({
|
|
14678
|
+
rootDir: root,
|
|
14679
|
+
state,
|
|
14680
|
+
startupRequired: state === "configured",
|
|
14681
|
+
observedAt,
|
|
14682
|
+
zoneDid: null,
|
|
14683
|
+
ownerDid: null,
|
|
14684
|
+
deviceDid: null,
|
|
14685
|
+
deviceName: null,
|
|
14686
|
+
accessHostname: null,
|
|
14687
|
+
files: [],
|
|
14688
|
+
problems,
|
|
14689
|
+
warnings,
|
|
14690
|
+
lock: null
|
|
14691
|
+
});
|
|
14692
|
+
if (!isDirectory(root)) {
|
|
14693
|
+
problems.push({ code: "ROOT_MISSING", message: `BUCKYOS_ROOT does not exist: ${root}` });
|
|
14694
|
+
return base("not_installed");
|
|
14695
|
+
}
|
|
14696
|
+
const missingDirs = ["bin", "etc"].filter((name) => !isDirectory(path.join(root, name)));
|
|
14697
|
+
if (missingDirs.length > 0) {
|
|
14698
|
+
for (const name of missingDirs) {
|
|
14699
|
+
problems.push({ code: "INSTALL_INCOMPLETE", message: `installed BuckyOS directory is missing: ${path.join(root, name)}` });
|
|
14700
|
+
}
|
|
14701
|
+
return base("not_installed");
|
|
14702
|
+
}
|
|
14703
|
+
const etcDir = path.join(root, "etc");
|
|
14704
|
+
const lock = readLock(path.join(etcDir, ACTIVATION_LOCK_FILE_NAME));
|
|
14705
|
+
const identity = observeIdentity(path.join(etcDir, "node_identity.json"), problems);
|
|
14706
|
+
let zoneDid = identity.zoneDid;
|
|
14707
|
+
let deviceDid = identity.deviceDid;
|
|
14708
|
+
const publicDirs = listSubdirectories(path.join(root, "local", "identity"));
|
|
14709
|
+
const securityDirs = listSubdirectories(path.join(root, "security"));
|
|
14710
|
+
const zoneJsonFiles = listFilesMatching(etcDir, (name) => name.endsWith(".zone.json"));
|
|
14711
|
+
if (!zoneDid && zoneJsonFiles.length > 0) {
|
|
14712
|
+
const host = zoneJsonFiles[0].slice(0, -".zone.json".length);
|
|
14713
|
+
if (!host.endsWith(".did"))
|
|
14714
|
+
zoneDid = new DID("web", host);
|
|
14715
|
+
}
|
|
14716
|
+
if (!deviceDid && zoneDid)
|
|
14717
|
+
deviceDid = buildDeviceDid(ACTIVATION_DEVICE_NAME, zoneDid);
|
|
14718
|
+
const strayIdentityDirs = [...publicDirs, ...securityDirs].filter(
|
|
14719
|
+
(name) => !deviceDid || name !== path.basename(deviceIdentityPathsForRoots(identityRootsFor(root), deviceDid).publicDir)
|
|
14720
|
+
);
|
|
14721
|
+
const files = [];
|
|
14722
|
+
let layout = null;
|
|
14723
|
+
if (zoneDid && deviceDid) {
|
|
14724
|
+
layout = layoutFor(root, zoneDid, deviceDid);
|
|
14725
|
+
for (const planned of plannedFiles(root, layout)) {
|
|
14726
|
+
files.push({ ...publicEntry(planned), present: isFile(planned.absolutePath) });
|
|
14727
|
+
}
|
|
14728
|
+
} else {
|
|
14729
|
+
for (const [name, role, required] of [
|
|
14730
|
+
["node_identity.json", "node_identity", true],
|
|
14731
|
+
["start_config.json", "start_config", true],
|
|
14732
|
+
[ZONE_DOCUMENT_JWT_FILE_NAME, "zone_document_jwt", true],
|
|
14733
|
+
["node_gateway_params.json", "gateway_params", true],
|
|
14734
|
+
[ZONE_DNS_RECORDS_FILE_NAME, "dns_records", false]
|
|
14735
|
+
]) {
|
|
14736
|
+
files.push({ path: `etc/${name}`, role, required, secret: false, present: isFile(path.join(etcDir, name)) });
|
|
14737
|
+
}
|
|
14738
|
+
for (const name of zoneJsonFiles) {
|
|
14739
|
+
files.push({ path: `etc/${name}`, role: "zone_boot_override", required: true, secret: false, present: true });
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14742
|
+
const gatewayEntry = files.find((file) => file.role === "gateway_params");
|
|
14743
|
+
const gatewayPlaceholder = (gatewayEntry == null ? void 0 : gatewayEntry.present) === true && gatewayParamsIsPlaceholder(path.join(etcDir, "node_gateway_params.json"));
|
|
14744
|
+
if (gatewayEntry && gatewayPlaceholder)
|
|
14745
|
+
gatewayEntry.present = false;
|
|
14746
|
+
const markers = files.filter((file) => file.present && file.role !== "dns_records");
|
|
14747
|
+
const requiredMissing = files.filter((file) => file.required && !file.present);
|
|
14748
|
+
const status = base("unactivated");
|
|
14749
|
+
status.files = files;
|
|
14750
|
+
status.lock = lock;
|
|
14751
|
+
status.zoneDid = (zoneDid == null ? void 0 : zoneDid.toString()) ?? null;
|
|
14752
|
+
status.deviceDid = (deviceDid == null ? void 0 : deviceDid.toString()) ?? null;
|
|
14753
|
+
status.deviceName = identity.nodeIdentity ? String(identity.nodeIdentity.device_name) : deviceDid ? ACTIVATION_DEVICE_NAME : null;
|
|
14754
|
+
status.ownerDid = identity.nodeIdentity ? String(identity.nodeIdentity.owner_did) : null;
|
|
14755
|
+
if (lock) {
|
|
14756
|
+
warnings.push(
|
|
14757
|
+
lock.corrupt ? `activation lock ${lock.path} exists but cannot be parsed` : `activation lock ${lock.path} exists (stage=${lock.stage ?? "unknown"}, committed=${lock.committedFiles.length})`
|
|
14758
|
+
);
|
|
14759
|
+
}
|
|
14760
|
+
for (const name of strayIdentityDirs) {
|
|
14761
|
+
warnings.push(`unexpected identity directory: ${name}`);
|
|
14762
|
+
}
|
|
14763
|
+
if (markers.length === 0 && strayIdentityDirs.length === 0 && !lock && problems.length === 0) {
|
|
14764
|
+
status.state = "unactivated";
|
|
14765
|
+
return status;
|
|
14766
|
+
}
|
|
14767
|
+
if (requiredMissing.length > 0 || !layout || strayIdentityDirs.length > 0 && markers.length === 0) {
|
|
14768
|
+
for (const file of requiredMissing) {
|
|
14769
|
+
problems.push({ code: "FILE_MISSING", message: `${file.path} is missing` });
|
|
14770
|
+
}
|
|
14771
|
+
status.state = "partial";
|
|
14772
|
+
return status;
|
|
14773
|
+
}
|
|
14774
|
+
const validated = await validateActivationMaterials(root, layout, identity, problems);
|
|
14775
|
+
status.ownerDid = validated.ownerDid ?? status.ownerDid;
|
|
14776
|
+
status.accessHostname = validated.accessHostname ?? (zoneDid ? zoneDid.toRawHostName() : null);
|
|
14777
|
+
if (lock && lock.stage !== "committed") {
|
|
14778
|
+
problems.push({
|
|
14779
|
+
code: "ACTIVATION_INTERRUPTED",
|
|
14780
|
+
message: `activation lock is still at stage ${lock.stage ?? "unknown"}; the commit did not finish cleanly`
|
|
14781
|
+
});
|
|
14782
|
+
}
|
|
14783
|
+
status.state = problems.length === 0 ? "configured" : "invalid";
|
|
14784
|
+
status.startupRequired = status.state === "configured";
|
|
14785
|
+
return status;
|
|
14786
|
+
}
|
|
14787
|
+
function inspectOwnerKeyBackupTarget(backupPath, problems) {
|
|
14788
|
+
const path = requireNode$1("node:path");
|
|
14789
|
+
const directory = path.dirname(backupPath);
|
|
14790
|
+
const directoryExists = isDirectory(directory);
|
|
14791
|
+
const exists = lstatOrNull(backupPath) !== null;
|
|
14792
|
+
if (!directoryExists) {
|
|
14793
|
+
problems.push({
|
|
14794
|
+
code: "OWNER_KEY_BACKUP_DIRECTORY_MISSING",
|
|
14795
|
+
message: `owner key backup directory does not exist: ${directory}`
|
|
14796
|
+
});
|
|
14797
|
+
}
|
|
14798
|
+
if (exists) {
|
|
14799
|
+
problems.push({ code: "OWNER_KEY_BACKUP_EXISTS", message: `owner key backup already exists: ${backupPath}` });
|
|
14800
|
+
}
|
|
14801
|
+
return { path: backupPath, directoryExists, exists };
|
|
14802
|
+
}
|
|
14803
|
+
function targetStateProblem(status) {
|
|
14804
|
+
if (status.lock) {
|
|
14805
|
+
return {
|
|
14806
|
+
code: "ACTIVATION_IN_PROGRESS",
|
|
14807
|
+
message: `activation lock exists: ${status.lock.path} (stage=${status.lock.stage ?? "unknown"})`
|
|
14808
|
+
};
|
|
14809
|
+
}
|
|
14810
|
+
switch (status.state) {
|
|
14811
|
+
case "unactivated":
|
|
14812
|
+
return null;
|
|
14813
|
+
case "not_installed":
|
|
14814
|
+
return { code: "TARGET_NOT_INSTALLED", message: `BuckyOS is not installed at ${status.rootDir}` };
|
|
14815
|
+
case "configured":
|
|
14816
|
+
return { code: "TARGET_ALREADY_ACTIVATED", message: `BuckyOS is already activated at ${status.rootDir}` };
|
|
14817
|
+
case "invalid":
|
|
14818
|
+
return {
|
|
14819
|
+
code: "TARGET_ALREADY_ACTIVATED",
|
|
14820
|
+
message: `BuckyOS activation material exists but is invalid at ${status.rootDir}; it is not overwritten`
|
|
14821
|
+
};
|
|
14822
|
+
case "partial":
|
|
14823
|
+
return {
|
|
14824
|
+
code: "TARGET_PARTIALLY_ACTIVATED",
|
|
14825
|
+
message: `BuckyOS is partially activated at ${status.rootDir}; remove the partial material or reinstall`
|
|
14826
|
+
};
|
|
14827
|
+
}
|
|
14828
|
+
}
|
|
14829
|
+
async function checkOfflineActivation(options) {
|
|
14830
|
+
const params = normalizeActivationParams(options);
|
|
14831
|
+
const status = await inspectActivationRoot(params.rootDir);
|
|
14832
|
+
const problems = [];
|
|
14833
|
+
const stateProblem = targetStateProblem(status);
|
|
14834
|
+
if (stateProblem)
|
|
14835
|
+
problems.push(stateProblem);
|
|
14836
|
+
const ownerKeyBackup = inspectOwnerKeyBackupTarget(params.ownerKeyBackupPath, problems);
|
|
14837
|
+
const layout = layoutFor(params.rootDir, params.zoneDid, params.deviceDid);
|
|
14838
|
+
return {
|
|
14839
|
+
ready: problems.length === 0,
|
|
14840
|
+
rootDir: params.rootDir,
|
|
14841
|
+
state: status.state,
|
|
14842
|
+
domain: params.domain,
|
|
14843
|
+
ownerName: params.ownerName,
|
|
14844
|
+
ownerDid: params.ownerDid.toString(),
|
|
14845
|
+
zoneDid: params.zoneDid.toString(),
|
|
14846
|
+
deviceDid: params.deviceDid.toString(),
|
|
14847
|
+
deviceName: ACTIVATION_DEVICE_NAME,
|
|
14848
|
+
accessHostname: params.domain,
|
|
14849
|
+
rtcpPort: params.rtcpPort,
|
|
14850
|
+
guestAccess: params.guestAccess,
|
|
14851
|
+
publicIp: params.publicIp ?? null,
|
|
14852
|
+
dnsRecords: buildActivationDnsRecords(params.domain, params.publicIp),
|
|
14853
|
+
ownerKeyBackup,
|
|
14854
|
+
plannedFiles: plannedFiles(params.rootDir, layout).map(publicEntry),
|
|
14855
|
+
problems,
|
|
14856
|
+
status
|
|
14857
|
+
};
|
|
14858
|
+
}
|
|
14859
|
+
function precheckError(precheck) {
|
|
14860
|
+
const first = precheck.problems[0];
|
|
14861
|
+
return new ActivationError(
|
|
14862
|
+
precheck.problems.length === 1 ? first.code : "ACTIVATION_PRECHECK_FAILED",
|
|
14863
|
+
precheck.problems.length === 1 ? first.message : `activation precheck failed: ${precheck.problems.map((item) => item.message).join("; ")}`,
|
|
14864
|
+
{ state: precheck.state, problems: precheck.problems }
|
|
14865
|
+
);
|
|
14866
|
+
}
|
|
14867
|
+
async function generateMaterial(params, adminPassword, ownerKeyPair, deviceKeyPair) {
|
|
14868
|
+
const now = buckyosGetUnixTimestamp();
|
|
14869
|
+
const exp = now + ACTIVATION_DOCUMENT_VALIDITY_SECONDS;
|
|
14870
|
+
const ownerJwk = createJwkByX(ownerKeyPair.publicKeyX);
|
|
14871
|
+
const deviceJwk = createJwkByX(deviceKeyPair.publicKeyX);
|
|
14872
|
+
const ownerDidStr = params.ownerDid.toString();
|
|
14873
|
+
const zoneDidStr = params.zoneDid.toString();
|
|
14874
|
+
const deviceDidStr = params.deviceDid.toString();
|
|
14875
|
+
const ownerDoc = newOwnerDocument({
|
|
14876
|
+
did: params.ownerDid,
|
|
14877
|
+
name: params.ownerName,
|
|
14878
|
+
displayName: params.ownerName,
|
|
14879
|
+
publicKeyJwk: ownerJwk,
|
|
14880
|
+
now
|
|
14881
|
+
});
|
|
14882
|
+
ownerDoc.exp = exp;
|
|
14883
|
+
ownerDocumentSetDefaultZoneDid(ownerDoc, params.zoneDid);
|
|
14884
|
+
const ownerDocument = ownerDocumentToOrderedJson(ownerDoc);
|
|
14885
|
+
const oodString = oodDescriptionToString(parseOODDescription(`${ACTIVATION_DEVICE_NAME}@${ACTIVATION_NET_ID}`));
|
|
14886
|
+
const bootJwt = await encodeZoneBootDocument(
|
|
14887
|
+
newZoneBootDocument({ id: params.zoneDid, oods: [oodString], exp }),
|
|
14888
|
+
ownerKeyPair.privateKeyPem
|
|
14889
|
+
);
|
|
14890
|
+
const bootDocument = decodeJwtClaimWithoutVerify(bootJwt);
|
|
14891
|
+
const deviceDoc = newDeviceDocumentByJwkWithDid(
|
|
14892
|
+
ACTIVATION_DEVICE_NAME,
|
|
14893
|
+
deviceJwk,
|
|
14894
|
+
params.deviceDid,
|
|
14895
|
+
now
|
|
14896
|
+
);
|
|
14897
|
+
delete deviceDoc.support_container;
|
|
14898
|
+
deviceDoc.owner = ownerDidStr;
|
|
14899
|
+
deviceDoc.zone_did = zoneDidStr;
|
|
14900
|
+
deviceDoc.net_id = ACTIVATION_NET_ID;
|
|
14901
|
+
deviceDoc.rtcp_port = params.rtcpPort;
|
|
14902
|
+
deviceDoc.iat = now;
|
|
14903
|
+
deviceDoc.exp = exp;
|
|
14904
|
+
deviceDoc.version_seq = 0;
|
|
14905
|
+
const deviceDocument = deviceDocumentToOrderedJson(deviceDoc);
|
|
14906
|
+
const deviceDocJwt = await encodeDeviceDocument(deviceDoc, ownerKeyPair.privateKeyPem);
|
|
14907
|
+
const miniDoc = newDeviceMiniDocumentByDeviceDocument(deviceDoc);
|
|
14908
|
+
miniDoc.iat = now;
|
|
14909
|
+
const deviceMiniDocJwt = await deviceMiniDocumentToJwt(miniDoc, ownerKeyPair.privateKeyPem);
|
|
14910
|
+
const zoneDoc = newZoneDocument({
|
|
14911
|
+
id: params.zoneDid,
|
|
14912
|
+
ownerDid: params.ownerDid,
|
|
14913
|
+
publicKeyJwk: ownerJwk,
|
|
14914
|
+
now
|
|
14915
|
+
});
|
|
14916
|
+
zoneDoc.hostname = params.domain;
|
|
14917
|
+
zoneDoc.owner = ownerDidStr;
|
|
14918
|
+
zoneDoc.oods = [oodString];
|
|
14919
|
+
zoneDoc.boot_jwt = bootJwt;
|
|
14920
|
+
zoneDoc.devices = { [ACTIVATION_DEVICE_NAME]: deviceDocument };
|
|
14921
|
+
zoneDoc.mini_device_jwts = { [ACTIVATION_DEVICE_NAME]: deviceMiniDocJwt };
|
|
14922
|
+
zoneDoc.iat = now;
|
|
14923
|
+
zoneDoc.exp = exp;
|
|
14924
|
+
zoneDoc.version_seq = 0;
|
|
14925
|
+
delete zoneDoc.sn;
|
|
14926
|
+
const zoneDocumentJwt = await encodeZoneDocument(zoneDoc, ownerKeyPair.privateKeyPem);
|
|
14927
|
+
if (new TextEncoder().encode(zoneDocumentJwt).length >= MAX_INLINE_DOCUMENT_BYTES) {
|
|
14928
|
+
throw new ActivationError(
|
|
14929
|
+
"ZONE_DOCUMENT_TOO_LARGE",
|
|
14930
|
+
`zone document JWT exceeds the ${MAX_INLINE_DOCUMENT_BYTES} byte inline limit`
|
|
14931
|
+
);
|
|
14932
|
+
}
|
|
14933
|
+
const nodeIdentity = newLocalNodeIdentityConfig({
|
|
14934
|
+
zoneDid: params.zoneDid,
|
|
14935
|
+
ownerDid: params.ownerDid,
|
|
14936
|
+
ownerPublicKey: ownerJwk,
|
|
14937
|
+
deviceName: ACTIVATION_DEVICE_NAME,
|
|
14938
|
+
deviceDid: params.deviceDid,
|
|
14939
|
+
zoneIat: now
|
|
14940
|
+
});
|
|
14941
|
+
const startConfig = {
|
|
14942
|
+
user_name: params.ownerName,
|
|
14943
|
+
owner_document: ownerDocument,
|
|
14944
|
+
zone_name: zoneDidStr,
|
|
14945
|
+
access_hostname: params.domain,
|
|
14946
|
+
zone_document_jwt: zoneDocumentJwt,
|
|
14947
|
+
boot_config_jwt: bootJwt,
|
|
14948
|
+
device_doc_jwt: deviceDocJwt,
|
|
14949
|
+
device_mini_doc_jwt: deviceMiniDocJwt,
|
|
14950
|
+
ood_jwt: deviceDocJwt,
|
|
14951
|
+
admin_password_hash: hashAdminPassword(params.ownerName, adminPassword),
|
|
14952
|
+
guest_access: params.guestAccess,
|
|
14953
|
+
friend_passcode: "",
|
|
14954
|
+
enabled_features: {},
|
|
14955
|
+
ai_provider_config: {},
|
|
14956
|
+
jarvis_msg_tunnel_config: {}
|
|
14957
|
+
};
|
|
14958
|
+
return {
|
|
14959
|
+
documentIat: now,
|
|
14960
|
+
documentExp: exp,
|
|
14961
|
+
ownerDocument,
|
|
14962
|
+
bootDocument,
|
|
14963
|
+
bootJwt,
|
|
14964
|
+
deviceDocument,
|
|
14965
|
+
deviceDocJwt,
|
|
14966
|
+
deviceMiniDocJwt,
|
|
14967
|
+
zoneDocumentJwt,
|
|
14968
|
+
nodeIdentity,
|
|
14969
|
+
startConfig,
|
|
14970
|
+
gatewayParams: { params: { device_did: deviceDidStr } },
|
|
14971
|
+
dnsRecords: buildActivationDnsRecords(params.domain, params.publicIp)
|
|
14972
|
+
};
|
|
14973
|
+
}
|
|
14974
|
+
function fileContent(file, material, deviceKeyPair, domain) {
|
|
14975
|
+
switch (file.role) {
|
|
14976
|
+
case "device_private_key":
|
|
14977
|
+
return deviceKeyPair.privateKeyPem;
|
|
14978
|
+
case "device_document":
|
|
14979
|
+
return jsonPretty(material.deviceDocument);
|
|
14980
|
+
case "device_document_jwt":
|
|
14981
|
+
return material.deviceDocJwt;
|
|
14982
|
+
case "device_mini_document_jwt":
|
|
14983
|
+
return material.deviceMiniDocJwt;
|
|
14984
|
+
case "zone_boot_override":
|
|
14985
|
+
return jsonPretty(material.bootDocument);
|
|
14986
|
+
case "zone_document_jwt":
|
|
14987
|
+
return material.zoneDocumentJwt;
|
|
14988
|
+
case "dns_records":
|
|
14989
|
+
return jsonPretty({ hostname: domain, records: material.dnsRecords });
|
|
14990
|
+
case "start_config":
|
|
14991
|
+
return jsonPretty(material.startConfig);
|
|
14992
|
+
case "gateway_params":
|
|
14993
|
+
return jsonPretty(material.gatewayParams);
|
|
14994
|
+
case "node_identity":
|
|
14995
|
+
return jsonPretty(material.nodeIdentity);
|
|
14996
|
+
}
|
|
14997
|
+
}
|
|
14998
|
+
function saveOwnerKeyBackup(backupPath, privateKeyPem) {
|
|
14999
|
+
const fs = requireNode$1("node:fs");
|
|
15000
|
+
fs.writeFileSync(backupPath, privateKeyPem, { mode: 384, flag: "wx" });
|
|
15001
|
+
if (!isWindows()) {
|
|
15002
|
+
fs.chmodSync(backupPath, 384);
|
|
15003
|
+
}
|
|
15004
|
+
}
|
|
15005
|
+
function recoverySteps(lockPath, backupPath, committed) {
|
|
15006
|
+
return [
|
|
15007
|
+
`inspect the target with: buckyos provision status --root <root>`,
|
|
15008
|
+
committed.length > 0 ? `the following files were committed and are left in place: ${committed.join(", ")}` : "no activation file was committed to the target root",
|
|
15009
|
+
`the owner private key backup was saved at ${backupPath}; keep it or delete it before retrying (the backup path must not exist)`,
|
|
15010
|
+
`remove the committed files and the lock file ${lockPath} (or reinstall BuckyOS), then run provision check again`,
|
|
15011
|
+
"the generated identity is not deleted or regenerated automatically"
|
|
15012
|
+
];
|
|
15013
|
+
}
|
|
15014
|
+
async function activateOfflineZone(options) {
|
|
15015
|
+
const fs = requireNode$1("node:fs");
|
|
15016
|
+
const params = normalizeActivationParams(options);
|
|
15017
|
+
const adminPassword = validateAdminPassword(options.adminPassword);
|
|
15018
|
+
const precheck = await checkOfflineActivation(options);
|
|
15019
|
+
if (!precheck.ready)
|
|
15020
|
+
throw precheckError(precheck);
|
|
15021
|
+
const ownerKeyPair = options.ownerKeyPair ?? generateProvisionKeyPair();
|
|
15022
|
+
const deviceKeyPair = options.deviceKeyPair ?? generateProvisionKeyPair();
|
|
15023
|
+
const material = await generateMaterial(params, adminPassword, ownerKeyPair, deviceKeyPair);
|
|
15024
|
+
const layout = layoutFor(params.rootDir, params.zoneDid, params.deviceDid);
|
|
15025
|
+
const files = plannedFiles(params.rootDir, layout);
|
|
15026
|
+
const warnings = [];
|
|
15027
|
+
const lockRecord = {
|
|
15028
|
+
schema_version: ACTIVATION_LOCK_SCHEMA_VERSION,
|
|
15029
|
+
trace_id: options.traceId ?? null,
|
|
15030
|
+
started_at: buckyosGetUnixTimestamp(),
|
|
15031
|
+
domain: params.domain,
|
|
15032
|
+
owner_key_backup: params.ownerKeyBackupPath,
|
|
15033
|
+
stage: "locked",
|
|
15034
|
+
committed: []
|
|
15035
|
+
};
|
|
15036
|
+
acquireLock(layout.lockPath, lockRecord);
|
|
15037
|
+
const releaseLock = () => {
|
|
15038
|
+
try {
|
|
15039
|
+
fs.rmSync(layout.lockPath, { force: true });
|
|
15040
|
+
} catch {
|
|
15041
|
+
}
|
|
15042
|
+
};
|
|
15043
|
+
let recheck;
|
|
15044
|
+
try {
|
|
15045
|
+
recheck = await checkOfflineActivation(options);
|
|
15046
|
+
} catch (error) {
|
|
15047
|
+
releaseLock();
|
|
15048
|
+
throw error;
|
|
15049
|
+
}
|
|
15050
|
+
const recheckProblems = recheck.problems.filter((problem) => problem.code !== "ACTIVATION_IN_PROGRESS");
|
|
15051
|
+
if (recheckProblems.length > 0) {
|
|
15052
|
+
releaseLock();
|
|
15053
|
+
throw precheckError({ ...recheck, problems: recheckProblems });
|
|
15054
|
+
}
|
|
15055
|
+
for (const file of files) {
|
|
15056
|
+
if (lstatOrNull(file.absolutePath)) {
|
|
15057
|
+
releaseLock();
|
|
15058
|
+
throw new ActivationError(
|
|
15059
|
+
"TARGET_PARTIALLY_ACTIVATED",
|
|
15060
|
+
`activation file already exists: ${file.path}`,
|
|
15061
|
+
{ state: "partial", problems: [{ code: "FILE_EXISTS", message: `${file.path} already exists` }] }
|
|
15062
|
+
);
|
|
15063
|
+
}
|
|
15064
|
+
}
|
|
15065
|
+
try {
|
|
15066
|
+
saveOwnerKeyBackup(params.ownerKeyBackupPath, ownerKeyPair.privateKeyPem);
|
|
15067
|
+
} catch (error) {
|
|
15068
|
+
releaseLock();
|
|
15069
|
+
const code = error.code;
|
|
15070
|
+
throw new ActivationError(
|
|
15071
|
+
code === "EEXIST" ? "OWNER_KEY_BACKUP_EXISTS" : "OWNER_KEY_BACKUP_FAILED",
|
|
15072
|
+
`owner key backup could not be saved: ${error instanceof Error ? error.message : String(error)}`,
|
|
15073
|
+
{ owner_key_backup: { path: params.ownerKeyBackupPath, saved: false }, committed_files: [] }
|
|
15074
|
+
);
|
|
15075
|
+
}
|
|
15076
|
+
lockRecord.stage = "backup_saved";
|
|
15077
|
+
const committed = [];
|
|
15078
|
+
const failCommit = (error, stage) => {
|
|
15079
|
+
throw new ActivationError(
|
|
15080
|
+
"ACTIVATION_COMMIT_FAILED",
|
|
15081
|
+
`activation commit failed at ${stage}: ${error instanceof Error ? error.message : String(error)}`,
|
|
15082
|
+
{
|
|
15083
|
+
stage,
|
|
15084
|
+
owner_key_backup: { path: params.ownerKeyBackupPath, saved: true },
|
|
15085
|
+
committed_files: committed,
|
|
15086
|
+
lock_file: layout.lockPath,
|
|
15087
|
+
recovery: recoverySteps(layout.lockPath, params.ownerKeyBackupPath, committed.map((file) => file.path))
|
|
15088
|
+
}
|
|
15089
|
+
);
|
|
15090
|
+
};
|
|
15091
|
+
try {
|
|
15092
|
+
updateLock(layout.lockPath, lockRecord);
|
|
15093
|
+
} catch (error) {
|
|
15094
|
+
failCommit(error, "lock:backup_saved");
|
|
15095
|
+
}
|
|
15096
|
+
lockRecord.stage = "committing";
|
|
15097
|
+
for (const file of files) {
|
|
15098
|
+
const content = fileContent(file, material, deviceKeyPair, params.domain);
|
|
15099
|
+
try {
|
|
15100
|
+
commitFile(file.absolutePath, content, file.mode, file.dirMode);
|
|
15101
|
+
} catch (error) {
|
|
15102
|
+
failCommit(error, file.path);
|
|
15103
|
+
}
|
|
15104
|
+
committed.push({ ...publicEntry(file), sha256: file.secret ? null : sha256Hex(content) });
|
|
15105
|
+
lockRecord.committed = committed.map((item) => item.path);
|
|
15106
|
+
try {
|
|
15107
|
+
updateLock(layout.lockPath, lockRecord);
|
|
15108
|
+
} catch (error) {
|
|
15109
|
+
failCommit(error, `lock:${file.path}`);
|
|
15110
|
+
}
|
|
15111
|
+
}
|
|
15112
|
+
lockRecord.stage = "committed";
|
|
15113
|
+
try {
|
|
15114
|
+
updateLock(layout.lockPath, lockRecord);
|
|
15115
|
+
fs.rmSync(layout.lockPath, { force: true });
|
|
15116
|
+
} catch (error) {
|
|
15117
|
+
warnings.push(
|
|
15118
|
+
`activation completed but the lock ${layout.lockPath} could not be removed: ${error instanceof Error ? error.message : String(error)}`
|
|
15119
|
+
);
|
|
15120
|
+
}
|
|
15121
|
+
return {
|
|
15122
|
+
rootDir: params.rootDir,
|
|
15123
|
+
state: "configured",
|
|
15124
|
+
startupRequired: true,
|
|
15125
|
+
domain: params.domain,
|
|
15126
|
+
ownerName: params.ownerName,
|
|
15127
|
+
ownerDid: params.ownerDid.toString(),
|
|
15128
|
+
zoneDid: params.zoneDid.toString(),
|
|
15129
|
+
deviceDid: params.deviceDid.toString(),
|
|
15130
|
+
deviceName: ACTIVATION_DEVICE_NAME,
|
|
15131
|
+
accessHostname: params.domain,
|
|
15132
|
+
rtcpPort: params.rtcpPort,
|
|
15133
|
+
guestAccess: params.guestAccess,
|
|
15134
|
+
publicIp: params.publicIp ?? null,
|
|
15135
|
+
documentIat: material.documentIat,
|
|
15136
|
+
documentExp: material.documentExp,
|
|
15137
|
+
ownerKeyBackup: { path: params.ownerKeyBackupPath, saved: true },
|
|
15138
|
+
dnsRecords: material.dnsRecords,
|
|
15139
|
+
committedFiles: committed,
|
|
15140
|
+
warnings
|
|
15141
|
+
};
|
|
15142
|
+
}
|
|
14114
15143
|
const PROVISION_BASE_TIME = 1743478939;
|
|
14115
15144
|
const DEFAULT_EXP_YEARS = 10;
|
|
14116
15145
|
const PROVISION_DEFAULT_EXP = PROVISION_BASE_TIME + 3600 * 24 * 365 * DEFAULT_EXP_YEARS;
|
|
@@ -14831,6 +15860,13 @@ function buildDidDocs(outputDir, options) {
|
|
|
14831
15860
|
return written;
|
|
14832
15861
|
}
|
|
14833
15862
|
export {
|
|
15863
|
+
ACTIVATION_DEFAULT_RTCP_PORT,
|
|
15864
|
+
ACTIVATION_DEVICE_NAME,
|
|
15865
|
+
ACTIVATION_DOCUMENT_VALIDITY_SECONDS,
|
|
15866
|
+
ACTIVATION_LOCK_FILE_NAME,
|
|
15867
|
+
ACTIVATION_MIN_PASSWORD_LENGTH,
|
|
15868
|
+
ACTIVATION_NET_ID,
|
|
15869
|
+
ActivationError,
|
|
14834
15870
|
DEVICE_DOC_JWT_FILE_NAME,
|
|
14835
15871
|
DEVICE_MINI_DOC_JWT_FILE_NAME,
|
|
14836
15872
|
DEV_TEST_EVM_ACCOUNTS,
|
|
@@ -14850,11 +15886,16 @@ export {
|
|
|
14850
15886
|
NODE_IDENTITY_SCHEMA_V2,
|
|
14851
15887
|
PROVISION_BASE_TIME,
|
|
14852
15888
|
PROVISION_DEFAULT_EXP,
|
|
15889
|
+
ZONE_DNS_RECORDS_FILE_NAME,
|
|
15890
|
+
ZONE_DOCUMENT_JWT_FILE_NAME,
|
|
15891
|
+
activateOfflineZone,
|
|
14853
15892
|
assertProvisionRuntime,
|
|
14854
15893
|
bindDeviceDocumentDid,
|
|
15894
|
+
buildActivationDnsRecords,
|
|
14855
15895
|
buildDeviceDid,
|
|
14856
15896
|
buildDidDocs,
|
|
14857
15897
|
calcPkgMetaObjId,
|
|
15898
|
+
checkOfflineActivation,
|
|
14858
15899
|
createCa,
|
|
14859
15900
|
createCertFromCa,
|
|
14860
15901
|
createIdentityCertFromCa,
|
|
@@ -14871,13 +15912,16 @@ export {
|
|
|
14871
15912
|
didWebDocumentUrl,
|
|
14872
15913
|
encodeIdentityDirName,
|
|
14873
15914
|
ensureCa,
|
|
15915
|
+
generateProvisionKeyPair,
|
|
14874
15916
|
getDevTestEvmAccountByIndex,
|
|
14875
15917
|
getDevTestEvmAccountByUsername,
|
|
14876
15918
|
getDevTestKeyPairById,
|
|
14877
15919
|
getDevTestKeyPairByIndex,
|
|
15920
|
+
hashAdminPassword,
|
|
14878
15921
|
identityDirName,
|
|
14879
15922
|
identityFileName,
|
|
14880
15923
|
identityRawHostUri,
|
|
15924
|
+
inspectActivationRoot,
|
|
14881
15925
|
loadDeviceDocJwtForRoots,
|
|
14882
15926
|
loadDeviceMiniDocJwtForRoots,
|
|
14883
15927
|
loadLocalDeviceDocumentForRoots,
|
|
@@ -14892,6 +15936,11 @@ export {
|
|
|
14892
15936
|
saveNodeGatewayParams,
|
|
14893
15937
|
setPkgMeta,
|
|
14894
15938
|
uniqueNameToDid,
|
|
15939
|
+
validateAdminPassword,
|
|
15940
|
+
validateOwnerName,
|
|
15941
|
+
validatePublicIp,
|
|
15942
|
+
validateRtcpPort,
|
|
15943
|
+
validateZoneDomain,
|
|
14895
15944
|
versionToInt
|
|
14896
15945
|
};
|
|
14897
15946
|
//# sourceMappingURL=provision.mjs.map
|