sporades 0.9.21 → 0.9.22

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.
Files changed (36) hide show
  1. package/CHANGES.md +24 -4
  2. package/bin/sporades-host-helper.js +443 -273
  3. package/bin/sporades.js +708 -659
  4. package/dist/cli/cli-help.d.ts +2 -2
  5. package/dist/cli/cli-help.d.ts.map +1 -1
  6. package/dist/cli/cli-help.js +2 -0
  7. package/dist/cli/cli-help.js.map +1 -1
  8. package/dist/cli/cli-version.d.ts +1 -1
  9. package/dist/cli/cli-version.js +1 -1
  10. package/dist/cli/host-domain-aliases.d.ts +5 -0
  11. package/dist/cli/host-domain-aliases.d.ts.map +1 -0
  12. package/dist/cli/host-domain-aliases.js +69 -0
  13. package/dist/cli/host-domain-aliases.js.map +1 -0
  14. package/dist/cli/host-helper-requests.d.ts +1 -0
  15. package/dist/cli/host-helper-requests.d.ts.map +1 -1
  16. package/dist/cli/host-helper-validation.d.ts.map +1 -1
  17. package/dist/cli/host-helper-validation.js +2 -0
  18. package/dist/cli/host-helper-validation.js.map +1 -1
  19. package/dist/cli/host-request-builders.d.ts +2 -1
  20. package/dist/cli/host-request-builders.d.ts.map +1 -1
  21. package/dist/cli/host-request-builders.js +2 -1
  22. package/dist/cli/host-request-builders.js.map +1 -1
  23. package/dist/cli/hosted-capsule-contract.d.ts +2 -0
  24. package/dist/cli/hosted-capsule-contract.d.ts.map +1 -1
  25. package/dist/cli/sporades-host-helper.js +124 -15
  26. package/dist/cli/sporades-host-helper.js.map +1 -1
  27. package/dist/cli/sporades.js +16 -2
  28. package/dist/cli/sporades.js.map +1 -1
  29. package/dist/generated-source-manifest.json +7 -2
  30. package/dist/http-runtime.d.ts +2 -1
  31. package/dist/http-runtime.d.ts.map +1 -1
  32. package/dist/http-runtime.js +16 -3
  33. package/dist/http-runtime.js.map +1 -1
  34. package/dist/templates/server-bundle-entry.js +1 -0
  35. package/dist/templates/server-bundle-entry.js.map +1 -1
  36. package/package.json +1 -1
@@ -16678,14 +16678,14 @@ var require_svgPath = __commonJS({
16678
16678
  ["Z", 0],
16679
16679
  ["z", 0]
16680
16680
  ]);
16681
- var parse5 = function(path4) {
16681
+ var parse5 = function(path5) {
16682
16682
  var cmd;
16683
16683
  var ret = [];
16684
16684
  var args = [];
16685
16685
  var curArg = "";
16686
16686
  var foundDecimal = false;
16687
16687
  var params = 0;
16688
- for (var _i = 0, path_1 = path4; _i < path_1.length; _i++) {
16688
+ for (var _i = 0, path_1 = path5; _i < path_1.length; _i++) {
16689
16689
  var c = path_1[_i];
16690
16690
  if (parameters.has(c)) {
16691
16691
  params = parameters.get(c);
@@ -16999,8 +16999,8 @@ var require_svgPath = __commonJS({
16999
16999
  ];
17000
17000
  return result;
17001
17001
  };
17002
- exports.svgPathToOperators = function(path4) {
17003
- return apply(parse5(path4));
17002
+ exports.svgPathToOperators = function(path5) {
17003
+ return apply(parse5(path5));
17004
17004
  };
17005
17005
  }
17006
17006
  });
@@ -17183,7 +17183,7 @@ var require_operations = __commonJS({
17183
17183
  operators_1.popGraphicsState()
17184
17184
  ]).filter(Boolean);
17185
17185
  };
17186
- exports.drawSvgPath = function(path4, options) {
17186
+ exports.drawSvgPath = function(path5, options) {
17187
17187
  var _a, _b, _c;
17188
17188
  return tslib_1.__spreadArrays([
17189
17189
  operators_1.pushGraphicsState(),
@@ -17197,7 +17197,7 @@ var require_operations = __commonJS({
17197
17197
  options.borderWidth && operators_1.setLineWidth(options.borderWidth),
17198
17198
  options.borderLineCap && operators_1.setLineCap(options.borderLineCap),
17199
17199
  operators_1.setDashPattern((_b = options.borderDashArray) !== null && _b !== void 0 ? _b : [], (_c = options.borderDashPhase) !== null && _c !== void 0 ? _c : 0)
17200
- ], svgPath_1.svgPathToOperators(path4), [
17200
+ ], svgPath_1.svgPathToOperators(path5), [
17201
17201
  // prettier-ignore
17202
17202
  options.color && options.borderWidth ? operators_1.fillAndStroke() : options.color ? operators_1.fill() : options.borderColor ? operators_1.stroke() : operators_1.closePath(),
17203
17203
  operators_1.popGraphicsState()
@@ -21495,12 +21495,12 @@ var require_PDFPage = __commonJS({
21495
21495
  graphicsState: graphicsStateKey
21496
21496
  }));
21497
21497
  };
21498
- PDFPage2.prototype.drawSvgPath = function(path4, options) {
21498
+ PDFPage2.prototype.drawSvgPath = function(path5, options) {
21499
21499
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
21500
21500
  if (options === void 0) {
21501
21501
  options = {};
21502
21502
  }
21503
- utils_1.assertIs(path4, "path", ["string"]);
21503
+ utils_1.assertIs(path5, "path", ["string"]);
21504
21504
  utils_1.assertOrUndefined(options.x, "options.x", ["number"]);
21505
21505
  utils_1.assertOrUndefined(options.y, "options.y", ["number"]);
21506
21506
  utils_1.assertOrUndefined(options.scale, "options.scale", ["number"]);
@@ -21529,7 +21529,7 @@ var require_PDFPage = __commonJS({
21529
21529
  options.borderColor = colors_1.rgb(0, 0, 0);
21530
21530
  }
21531
21531
  var contentStream = this.getContentStream();
21532
- contentStream.push.apply(contentStream, operations_1.drawSvgPath(path4, {
21532
+ contentStream.push.apply(contentStream, operations_1.drawSvgPath(path5, {
21533
21533
  x: (_a = options.x) !== null && _a !== void 0 ? _a : this.x,
21534
21534
  y: (_b = options.y) !== null && _b !== void 0 ? _b : this.y,
21535
21535
  scale: options.scale,
@@ -23605,9 +23605,9 @@ var require_decoder = __commonJS({
23605
23605
  return a < 0 ? 0 : a > 255 ? 255 : a;
23606
23606
  }
23607
23607
  constructor.prototype = {
23608
- load: function load(path4) {
23608
+ load: function load(path5) {
23609
23609
  var xhr = new XMLHttpRequest();
23610
- xhr.open("GET", path4, true);
23610
+ xhr.open("GET", path5, true);
23611
23611
  xhr.responseType = "arraybuffer";
23612
23612
  xhr.onload = (function() {
23613
23613
  var data2 = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
@@ -26261,13 +26261,118 @@ var require_png2 = __commonJS({
26261
26261
  }
26262
26262
  });
26263
26263
 
26264
+ // src/cli/host-domain-aliases.ts
26265
+ import { readdir, readFile } from "node:fs/promises";
26266
+ import path from "node:path";
26267
+
26268
+ // src/cli/cli-support.ts
26269
+ function errorDetails(error) {
26270
+ if (error === null || error === void 0) {
26271
+ return {};
26272
+ }
26273
+ return typeof error === "object" ? error : { message: String(error) };
26274
+ }
26275
+ function helperError(message, hint, diagnostics = null) {
26276
+ const error = new Error(message);
26277
+ error.hint = hint;
26278
+ if (diagnostics) {
26279
+ error.diagnostics = diagnostics;
26280
+ }
26281
+ return error;
26282
+ }
26283
+ function readStdin() {
26284
+ return new Promise((resolve, reject) => {
26285
+ let stdin = "";
26286
+ process.stdin.setEncoding("utf8");
26287
+ process.stdin.on("data", (chunk) => {
26288
+ stdin += chunk;
26289
+ });
26290
+ process.stdin.on("end", () => resolve(stdin));
26291
+ process.stdin.on("error", reject);
26292
+ });
26293
+ }
26294
+ function delay(ms) {
26295
+ return new Promise((resolve) => setTimeout(resolve, ms));
26296
+ }
26297
+ function writeResult(result, failed = false) {
26298
+ process.stdout.write(`${JSON.stringify(result)}
26299
+ `);
26300
+ if (failed) {
26301
+ process.exitCode = 1;
26302
+ }
26303
+ }
26304
+ function writeEnvelope(result, failed = false) {
26305
+ writeResult(result, failed);
26306
+ }
26307
+
26308
+ // src/cli/host-domain-aliases.ts
26309
+ function validateAliasDomains(value) {
26310
+ if (value === void 0) return [];
26311
+ if (!Array.isArray(value) || value.length > 20 || value.some((hostname) => typeof hostname !== "string" || hostname.length > 253 || !hostname.includes(".") || /^[0-9.]+$/.test(hostname) || !hostname.split(".").every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)))) {
26312
+ throw helperError(
26313
+ "Invalid Hosted Capsule alias domains.",
26314
+ "Supply at most 20 unique lowercase DNS hostnames, without schemes, ports, paths, wildcards, or IP addresses."
26315
+ );
26316
+ }
26317
+ if (new Set(value).size !== value.length) {
26318
+ throw helperError("Duplicate Hosted Capsule alias domain.", "Supply each alias domain once.");
26319
+ }
26320
+ return [...value];
26321
+ }
26322
+ async function assertHostnamesAvailable(remoteRoot, hostnames, owner) {
26323
+ const wanted = new Set(hostnames.map((hostname) => hostname.replace(/:\d+$/, "")));
26324
+ if (wanted.size !== hostnames.length) {
26325
+ throw helperError("Duplicate Hosted Capsule hostname.", "An alias must differ from the Capsule subdomain.");
26326
+ }
26327
+ const assertUnclaimed = (hostname, claimant) => {
26328
+ if (claimant !== owner && wanted.has(hostname.replace(/:\d+$/, ""))) {
26329
+ throw helperError(
26330
+ "Hosted Capsule hostname is already reserved.",
26331
+ `Choose another hostname; ${hostname} belongs to ${claimant}.`
26332
+ );
26333
+ }
26334
+ };
26335
+ const entries = async (directory) => readdir(directory, { withFileTypes: true }).catch((error) => {
26336
+ if (error.code === "ENOENT") return [];
26337
+ throw error;
26338
+ });
26339
+ for (const entry of await entries(path.join(remoteRoot, "caddy", "hosts"))) {
26340
+ if (entry.name.endsWith(".caddy")) {
26341
+ const domain = entry.name.slice(0, -6);
26342
+ assertUnclaimed(`host.${domain}`, `health:${domain}`);
26343
+ }
26344
+ }
26345
+ for (const domain of await entries(path.join(remoteRoot, "hosts"))) {
26346
+ if (domain.isSymbolicLink()) throw helperError("Cannot inspect Hosted domain ownership.", "Replace symbolic links in the Host registry with canonical directories.");
26347
+ if (!domain.isDirectory()) continue;
26348
+ for (const collection of ["capsules", "registration-claims"]) {
26349
+ const directory = path.join(remoteRoot, "hosts", domain.name, "registry", collection);
26350
+ for (const entry of await entries(directory)) {
26351
+ if (!entry.name.endsWith(".json")) continue;
26352
+ if (!entry.isFile()) throw helperError("Cannot inspect Hosted Capsule ownership.", "Repair the Host registry before registering domains.");
26353
+ const record = JSON.parse(await readFile(path.join(directory, entry.name), "utf8"));
26354
+ if (record.domain !== domain.name || record.subname !== entry.name.slice(0, -5)) {
26355
+ throw helperError("Invalid Hosted Capsule registry identity.", "Repair the Host registry before registering domains.");
26356
+ }
26357
+ if (collection === "capsules" && record.status === "unregistered") continue;
26358
+ const claimant = `${record.domain}/${record.subname}`;
26359
+ const aliases = validateAliasDomains(record.aliasDomains);
26360
+ const previousAliases = collection === "registration-claims" ? validateAliasDomains(record.previousAliasDomains) : [];
26361
+ for (const hostname of [`${record.subname}.${record.domain}`, ...aliases, ...previousAliases]) {
26362
+ assertUnclaimed(hostname, claimant);
26363
+ }
26364
+ }
26365
+ }
26366
+ }
26367
+ }
26368
+
26264
26369
  // src/cli/sporades-host-helper.ts
26265
26370
  import { spawnSync as spawnSync2 } from "node:child_process";
26266
26371
  import { constants as fsConstants, createReadStream, statSync } from "node:fs";
26267
- import { access, chmod, lstat, mkdir, open, opendir, readdir, readFile as readFile2, readlink, rename, rm, stat, statfs, symlink, writeFile } from "node:fs/promises";
26372
+ import { access, chmod, lstat, mkdir, open, opendir, readdir as readdir2, readFile as readFile3, readlink, rename, rm, stat, statfs, symlink, writeFile } from "node:fs/promises";
26268
26373
  import { createHash, generateKeyPairSync, randomBytes } from "node:crypto";
26269
26374
  import { freemem, loadavg, totalmem } from "node:os";
26270
- import path3 from "node:path";
26375
+ import path4 from "node:path";
26271
26376
  import { isDeepStrictEqual } from "node:util";
26272
26377
 
26273
26378
  // src/base-image.ts
@@ -42546,48 +42651,8 @@ var serviceUserMutationAuthority = Object.freeze({ kind: "service-user-mutation-
42546
42651
  var MutationExecutionStorage = process.getBuiltinModule("node:async_hooks").AsyncLocalStorage;
42547
42652
  var mutationExecution = new MutationExecutionStorage();
42548
42653
 
42549
- // src/cli/cli-support.ts
42550
- function errorDetails(error) {
42551
- if (error === null || error === void 0) {
42552
- return {};
42553
- }
42554
- return typeof error === "object" ? error : { message: String(error) };
42555
- }
42556
- function helperError(message, hint, diagnostics = null) {
42557
- const error = new Error(message);
42558
- error.hint = hint;
42559
- if (diagnostics) {
42560
- error.diagnostics = diagnostics;
42561
- }
42562
- return error;
42563
- }
42564
- function readStdin() {
42565
- return new Promise((resolve, reject) => {
42566
- let stdin = "";
42567
- process.stdin.setEncoding("utf8");
42568
- process.stdin.on("data", (chunk) => {
42569
- stdin += chunk;
42570
- });
42571
- process.stdin.on("end", () => resolve(stdin));
42572
- process.stdin.on("error", reject);
42573
- });
42574
- }
42575
- function delay(ms) {
42576
- return new Promise((resolve) => setTimeout(resolve, ms));
42577
- }
42578
- function writeResult(result, failed = false) {
42579
- process.stdout.write(`${JSON.stringify(result)}
42580
- `);
42581
- if (failed) {
42582
- process.exitCode = 1;
42583
- }
42584
- }
42585
- function writeEnvelope(result, failed = false) {
42586
- writeResult(result, failed);
42587
- }
42588
-
42589
42654
  // src/cli/cli-version.ts
42590
- var CLI_VERSION = "0.9.21";
42655
+ var CLI_VERSION = "0.9.22";
42591
42656
 
42592
42657
  // src/cli/schedule-inspection-envelope.ts
42593
42658
  var SCHEDULE_DIAGNOSTIC_FIELDS = /* @__PURE__ */ new Set([
@@ -42833,8 +42898,8 @@ function validatePublicArchiveBounds(entries) {
42833
42898
  }
42834
42899
 
42835
42900
  // src/cli/host-helper-config.ts
42836
- import { readFile } from "node:fs/promises";
42837
- import path from "node:path";
42901
+ import { readFile as readFile2 } from "node:fs/promises";
42902
+ import path2 from "node:path";
42838
42903
  var HOST_HELPER_CONFIG_FILE = "sporades-host-helper.json";
42839
42904
  var DEFAULT_HOSTED_CAPSULE_DOCKER_IMAGE = SPORADES_BASE_IMAGE.image;
42840
42905
  var DEFAULT_HOSTED_CAPSULE_DOCKER_NETWORK = "sporades-hosted-capsules";
@@ -42862,7 +42927,7 @@ async function loadHostHelperConfig(request) {
42862
42927
  }
42863
42928
  let contents;
42864
42929
  try {
42865
- contents = await readFile(configPath, "utf8");
42930
+ contents = await readFile2(configPath, "utf8");
42866
42931
  } catch (error) {
42867
42932
  if (errorDetails(error).code === "ENOENT" && !process.env.SPORADES_HOST_HELPER_CONFIG) {
42868
42933
  return loaded;
@@ -42890,7 +42955,7 @@ function hostHelperConfigPath(request) {
42890
42955
  if (typeof request.host?.remoteRoot !== "string" || request.host.remoteRoot.length === 0) {
42891
42956
  return null;
42892
42957
  }
42893
- return path.join(request.host.remoteRoot, HOST_HELPER_CONFIG_FILE);
42958
+ return path2.join(request.host.remoteRoot, HOST_HELPER_CONFIG_FILE);
42894
42959
  }
42895
42960
  function applyHostHelperConfig(loaded, config, configPath) {
42896
42961
  assertPlainObject(config, "Host helper config", configPath);
@@ -42962,7 +43027,7 @@ function readConfigPositiveInteger(value, key, configPath) {
42962
43027
  }
42963
43028
 
42964
43029
  // src/cli/host-helper-validation.ts
42965
- import path2 from "node:path";
43030
+ import path3 from "node:path";
42966
43031
  function missingCapsuleHint(request, purpose) {
42967
43032
  if (purpose === "push") {
42968
43033
  return `Run \`sporades host register ${request.capsule.subname} --host ${request.host.alias}\` before pushing a release.`;
@@ -43110,7 +43175,7 @@ function validateListRequest(request) {
43110
43175
  }
43111
43176
  function validateListRegistryRecord(request, record, recordPath) {
43112
43177
  const capsuleRecord = record;
43113
- const expectedSubname = path2.basename(recordPath, ".json");
43178
+ const expectedSubname = path3.basename(recordPath, ".json");
43114
43179
  const expectedRemoteCapsuleId = `${request.host.domain}/${typeof capsuleRecord?.subname === "string" ? capsuleRecord.subname : expectedSubname}`;
43115
43180
  const valid = capsuleRecord && typeof capsuleRecord.subname === "string" && capsuleRecord.subname.length > 0 && capsuleRecord.subname === expectedSubname && capsuleRecord.domain === request.host.domain && (capsuleRecord.remoteCapsuleId ?? expectedRemoteCapsuleId) === expectedRemoteCapsuleId;
43116
43181
  if (!valid) {
@@ -43148,6 +43213,7 @@ function validateRegisterRequest(request) {
43148
43213
  throw helperError("Invalid Hosted Capsule registration request.", "Update the Sporades CLI and retry `sporades host register`.");
43149
43214
  }
43150
43215
  const registration = request.registration ?? {};
43216
+ validateAliasDomains(registration.aliasDomains);
43151
43217
  const mismatchedIdentity = registration.subname && registration.subname !== request.capsule.subname || registration.domain && registration.domain !== request.host.domain || registration.remoteCapsuleId && registration.remoteCapsuleId !== `${request.host.domain}/${request.capsule.subname}`;
43152
43218
  if (mismatchedIdentity) {
43153
43219
  throw helperError(
@@ -43236,8 +43302,8 @@ function validateInstallRequest(request) {
43236
43302
  if (!directories?.releases || !directories.release) {
43237
43303
  throw helperError("Invalid Hosted Capsule release directory.", "Update the Sporades CLI and retry `sporades host push`.");
43238
43304
  }
43239
- const expectedReleaseDirectory = path2.join(directories.releases, release.id);
43240
- if (path2.resolve(directories.release) !== path2.resolve(expectedReleaseDirectory)) {
43305
+ const expectedReleaseDirectory = path3.join(directories.releases, release.id);
43306
+ if (path3.resolve(directories.release) !== path3.resolve(expectedReleaseDirectory)) {
43241
43307
  throw helperError("Invalid Hosted Capsule release directory.", "Update the Sporades CLI and retry `sporades host push`.");
43242
43308
  }
43243
43309
  }
@@ -43356,12 +43422,12 @@ async function runHostHelperInstaller() {
43356
43422
  const internal = process.argv[2] === "--install-host-helper-internal";
43357
43423
  const target = process.argv[3];
43358
43424
  const expectedChecksum = process.argv[4];
43359
- if (process.argv.length !== 5 || typeof target !== "string" || !path3.isAbsolute(target) || path3.basename(target) !== "sporades-host-helper" || !/^[a-f0-9]{64}$/.test(expectedChecksum ?? "")) {
43425
+ if (process.argv.length !== 5 || typeof target !== "string" || !path4.isAbsolute(target) || path4.basename(target) !== "sporades-host-helper" || !/^[a-f0-9]{64}$/.test(expectedChecksum ?? "")) {
43360
43426
  throw helperError("Invalid Host helper upgrade request.", "Retry with `sporades host upgrade --host <alias>`.");
43361
43427
  }
43362
43428
  const stage = process.argv[1];
43363
- const directory = path3.dirname(target);
43364
- const expectedStage = path3.join(directory, `.sporades-host-helper-stage-${expectedChecksum}.mjs`);
43429
+ const directory = path4.dirname(target);
43430
+ const expectedStage = path4.join(directory, `.sporades-host-helper-stage-${expectedChecksum}.mjs`);
43365
43431
  if (stage !== expectedStage) {
43366
43432
  throw helperError("Invalid staged Host helper path.", "Retry with `sporades host upgrade --host <alias>`.");
43367
43433
  }
@@ -43375,7 +43441,7 @@ async function runHostHelperInstaller() {
43375
43441
  "--conflict-exit-code",
43376
43442
  "75",
43377
43443
  "--no-fork",
43378
- path3.join(directory, ".sporades-host-helper.upgrade.lock"),
43444
+ path4.join(directory, ".sporades-host-helper.upgrade.lock"),
43379
43445
  process.execPath,
43380
43446
  stage,
43381
43447
  "--install-host-helper-internal",
@@ -43416,14 +43482,14 @@ function hostHelperUpgradeTimeoutMs(name2, fallback) {
43416
43482
  return value;
43417
43483
  }
43418
43484
  async function installHostHelperPayload(stage, target, expectedChecksum) {
43419
- const directory = path3.dirname(target);
43420
- const pointer = path3.join(directory, ".sporades-host-helper.active");
43421
- const blocked = path3.join(directory, ".sporades-host-helper.upgrade-blocked");
43422
- const needsDrain = path3.join(directory, ".sporades-host-helper.needs-drain");
43485
+ const directory = path4.dirname(target);
43486
+ const pointer = path4.join(directory, ".sporades-host-helper.active");
43487
+ const blocked = path4.join(directory, ".sporades-host-helper.upgrade-blocked");
43488
+ const needsDrain = path4.join(directory, ".sporades-host-helper.needs-drain");
43423
43489
  const newPayloadName = `.sporades-host-helper-payload-${expectedChecksum}.mjs`;
43424
- const newPayload = path3.join(directory, newPayloadName);
43490
+ const newPayload = path4.join(directory, newPayloadName);
43425
43491
  await mkdir(directory, { recursive: true });
43426
- if (createHash("sha256").update(await readFile2(stage)).digest("hex") !== expectedChecksum) {
43492
+ if (createHash("sha256").update(await readFile3(stage)).digest("hex") !== expectedChecksum) {
43427
43493
  throw helperError("Staged Host helper checksum did not match.", "Upload the immutable Host helper again, then retry the upgrade.");
43428
43494
  }
43429
43495
  await publishHostHelperFile(stage, newPayload, 493);
@@ -43431,18 +43497,18 @@ async function installHostHelperPayload(stage, target, expectedChecksum) {
43431
43497
  let firstCooperativeUpgrade = true;
43432
43498
  let currentTarget;
43433
43499
  try {
43434
- currentTarget = await readFile2(target);
43500
+ currentTarget = await readFile3(target);
43435
43501
  } catch {
43436
43502
  throw helperError("Current Host helper was not found.", "Install the current Host helper before retrying the upgrade.");
43437
43503
  }
43438
43504
  if (currentTarget.toString("utf8").includes(HOST_HELPER_DISPATCHER_MARKER)) {
43439
43505
  firstCooperativeUpgrade = false;
43440
- previousPayloadName = (await readFile2(pointer, "utf8")).trim();
43506
+ previousPayloadName = (await readFile3(pointer, "utf8")).trim();
43441
43507
  await validateHostHelperPayload(directory, previousPayloadName);
43442
43508
  } else {
43443
43509
  const previousChecksum = createHash("sha256").update(currentTarget).digest("hex");
43444
43510
  previousPayloadName = `.sporades-host-helper-payload-${previousChecksum}.mjs`;
43445
- await publishHostHelperFile(target, path3.join(directory, previousPayloadName), 493);
43511
+ await publishHostHelperFile(target, path4.join(directory, previousPayloadName), 493);
43446
43512
  await writeHostHelperPointer(pointer, previousPayloadName);
43447
43513
  await writeFile(needsDrain, "legacy-helper-drain-required\n", { mode: 384 });
43448
43514
  }
@@ -43461,7 +43527,7 @@ async function installHostHelperPayload(stage, target, expectedChecksum) {
43461
43527
  }
43462
43528
  }
43463
43529
  async function publishHostHelperFile(source, target, mode) {
43464
- const contents = await readFile2(source);
43530
+ const contents = await readFile3(source);
43465
43531
  await publishHostHelperBytes(contents, target, mode);
43466
43532
  }
43467
43533
  async function publishHostHelperBytes(contents, target, mode) {
@@ -43475,15 +43541,15 @@ async function publishHostHelperBytes(contents, target, mode) {
43475
43541
  }
43476
43542
  }
43477
43543
  async function writeHostHelperPointer(pointer, payloadName) {
43478
- await validateHostHelperPayload(path3.dirname(pointer), payloadName);
43544
+ await validateHostHelperPayload(path4.dirname(pointer), payloadName);
43479
43545
  await publishHostHelperBytes(Buffer.from(`${payloadName}
43480
43546
  `, "utf8"), pointer, 384);
43481
43547
  }
43482
43548
  async function validateHostHelperPayload(directory, payloadName) {
43483
43549
  const match = /^\.sporades-host-helper-payload-([a-f0-9]{64})\.mjs$/.exec(payloadName);
43484
43550
  if (!match) throw helperError("Host helper payload pointer was invalid.", "Retry the Host helper upgrade.");
43485
- const payload = path3.join(directory, payloadName);
43486
- const actual = createHash("sha256").update(await readFile2(payload)).digest("hex");
43551
+ const payload = path4.join(directory, payloadName);
43552
+ const actual = createHash("sha256").update(await readFile3(payload)).digest("hex");
43487
43553
  if (actual !== match[1]) throw helperError("Host helper payload checksum did not match.", "Retry the Host helper upgrade.");
43488
43554
  }
43489
43555
  async function drainUncooperativeHostHelpers(target) {
@@ -43553,11 +43619,11 @@ async function findUncooperativeHostHelperProcesses(target, deadline) {
43553
43619
  );
43554
43620
  }
43555
43621
  try {
43556
- const processDir = path3.join(procRoot, entry.name);
43557
- const argv = (await readFile2(path3.join(processDir, "cmdline"))).toString("utf8").split("\0").filter(Boolean);
43622
+ const processDir = path4.join(procRoot, entry.name);
43623
+ const argv = (await readFile3(path4.join(processDir, "cmdline"))).toString("utf8").split("\0").filter(Boolean);
43558
43624
  assertHostHelperDrainDeadline(deadline);
43559
43625
  if (argv[1] !== target) continue;
43560
- const environment = (await readFile2(path3.join(processDir, "environ"))).toString("utf8").split("\0");
43626
+ const environment = (await readFile3(path4.join(processDir, "environ"))).toString("utf8").split("\0");
43561
43627
  assertHostHelperDrainDeadline(deadline);
43562
43628
  if (environment.includes("SPORADES_HOST_DISPATCH_LOCK_HELD=1")) continue;
43563
43629
  active.push(Number(entry.name));
@@ -43614,7 +43680,7 @@ async function runManagedRouteActionProcess(request, input, lockIdentity) {
43614
43680
  process.argv[1]
43615
43681
  ] : [process.execPath, process.argv[1]];
43616
43682
  const result = spawnSync2(flock, [
43617
- lockIdentity.routeLockFile ? "--shared" : "--exclusive",
43683
+ lockIdentity.routeLockFile && request.action !== "capsule.register" ? "--shared" : "--exclusive",
43618
43684
  "--timeout",
43619
43685
  String(timeoutMs / 1e3),
43620
43686
  "--conflict-exit-code",
@@ -43689,7 +43755,7 @@ function managedRouteMutationLockIdentity(request) {
43689
43755
  const domainDirectory = canonicalManagedRouteDomainDirectory(request, remoteRoot2);
43690
43756
  const bootstrapTrust = bootstrapTrustManifest(request);
43691
43757
  return {
43692
- globalLockFile: path3.join(remoteRoot2, "bin", ".sporades-host-helper.host-route.lock"),
43758
+ globalLockFile: path4.join(remoteRoot2, "bin", ".sporades-host-helper.host-route.lock"),
43693
43759
  routeLockFile: null,
43694
43760
  domainDirectory,
43695
43761
  bootstrapTrust
@@ -43700,7 +43766,7 @@ function managedRouteMutationLockIdentity(request) {
43700
43766
  }
43701
43767
  const remoteRoot = validateCanonicalHostRouteRoot(request);
43702
43768
  return {
43703
- globalLockFile: path3.join(remoteRoot, "bin", ".sporades-host-helper.host-route.lock"),
43769
+ globalLockFile: path4.join(remoteRoot, "bin", ".sporades-host-helper.host-route.lock"),
43704
43770
  routeLockFile: `${canonicalManagedRouteFile(request, remoteRoot)}.lock`,
43705
43771
  domainDirectory: canonicalManagedRouteDomainDirectory(request, remoteRoot),
43706
43772
  ...actionCanProvisionCapsuleHttpLog(request.action) ? { routeLogTrust: capsuleHttpLogTrustManifest(request, remoteRoot) } : {}
@@ -43711,7 +43777,7 @@ function actionCanProvisionCapsuleHttpLog(action) {
43711
43777
  }
43712
43778
  function validateCanonicalHostRouteRoot(request) {
43713
43779
  const remoteRoot = request.host.remoteRoot;
43714
- if (typeof remoteRoot !== "string" || remoteRoot.length < 2 || remoteRoot.length > 4096 || !path3.isAbsolute(remoteRoot) || /[\0\r\n]/.test(remoteRoot) || path3.normalize(remoteRoot) !== remoteRoot || remoteRoot === path3.parse(remoteRoot).root) {
43780
+ if (typeof remoteRoot !== "string" || remoteRoot.length < 2 || remoteRoot.length > 4096 || !path4.isAbsolute(remoteRoot) || /[\0\r\n]/.test(remoteRoot) || path4.normalize(remoteRoot) !== remoteRoot || remoteRoot === path4.parse(remoteRoot).root) {
43715
43781
  throw helperError("Invalid Hosted Capsule route identity.", "Use a bounded absolute canonical Host remote root and retry.");
43716
43782
  }
43717
43783
  const domain = request.host.domain;
@@ -43730,22 +43796,22 @@ function canonicalManagedRouteFile(request, validatedRemoteRoot = validateCanoni
43730
43796
  throw helperError("Invalid Hosted Capsule route identity.", "Use a canonical DNS-safe Capsule subname and retry.");
43731
43797
  }
43732
43798
  const domainDirectory = canonicalManagedRouteDomainDirectory(request, validatedRemoteRoot);
43733
- const routeFile = path3.resolve(domainDirectory, `${subname}.caddy`);
43734
- if (path3.dirname(routeFile) !== domainDirectory) {
43799
+ const routeFile = path4.resolve(domainDirectory, `${subname}.caddy`);
43800
+ if (path4.dirname(routeFile) !== domainDirectory) {
43735
43801
  throw helperError("Invalid Hosted Capsule route identity.", "Use a route identity within the configured Hosted domain directory and retry.");
43736
43802
  }
43737
43803
  return routeFile;
43738
43804
  }
43739
43805
  function canonicalManagedRouteDomainDirectory(request, validatedRemoteRoot = validateCanonicalHostRouteRoot(request)) {
43740
- const hostsDirectory = path3.resolve(validatedRemoteRoot, "caddy", "hosts");
43741
- const domainDirectory = path3.resolve(hostsDirectory, request.host.domain);
43742
- if (path3.dirname(domainDirectory) !== hostsDirectory) {
43806
+ const hostsDirectory = path4.resolve(validatedRemoteRoot, "caddy", "hosts");
43807
+ const domainDirectory = path4.resolve(hostsDirectory, request.host.domain);
43808
+ if (path4.dirname(domainDirectory) !== hostsDirectory) {
43743
43809
  throw helperError("Invalid Hosted Capsule route identity.", "Use a route identity within the configured Hosted domain directory and retry.");
43744
43810
  }
43745
43811
  return domainDirectory;
43746
43812
  }
43747
43813
  async function captureManagedRouteTrust(lockIdentity, createMissing) {
43748
- const remoteRoot = path3.dirname(path3.dirname(lockIdentity.globalLockFile));
43814
+ const remoteRoot = path4.dirname(path4.dirname(lockIdentity.globalLockFile));
43749
43815
  const canonicalRemoteRoot = await canonicalTrustedTarget(remoteRoot);
43750
43816
  const directoryIdentities = /* @__PURE__ */ new Map();
43751
43817
  const captureDirectory = async (directory, caddyOwned = false) => {
@@ -43755,18 +43821,18 @@ async function captureManagedRouteTrust(lockIdentity, createMissing) {
43755
43821
  }
43756
43822
  };
43757
43823
  await captureDirectory(remoteRoot);
43758
- await captureDirectory(path3.join(canonicalRemoteRoot, "bin"));
43824
+ await captureDirectory(path4.join(canonicalRemoteRoot, "bin"));
43759
43825
  let routeFile = null;
43760
43826
  if (lockIdentity.routeLockFile) {
43761
43827
  routeFile = lockIdentity.routeLockFile.slice(0, -5);
43762
- const domainDirectory = path3.dirname(routeFile);
43763
- await captureDirectory(path3.join(canonicalRemoteRoot, "caddy"));
43764
- await captureDirectory(path3.join(canonicalRemoteRoot, "caddy", "hosts"));
43828
+ const domainDirectory = path4.dirname(routeFile);
43829
+ await captureDirectory(path4.join(canonicalRemoteRoot, "caddy"));
43830
+ await captureDirectory(path4.join(canonicalRemoteRoot, "caddy", "hosts"));
43765
43831
  await captureDirectory(await canonicalTrustedTarget(domainDirectory));
43766
43832
  await assertTrustedRegularFileIfExists(routeFile);
43767
43833
  } else {
43768
- await captureDirectory(path3.join(canonicalRemoteRoot, "caddy"));
43769
- await captureDirectory(path3.join(canonicalRemoteRoot, "caddy", "hosts"));
43834
+ await captureDirectory(path4.join(canonicalRemoteRoot, "caddy"));
43835
+ await captureDirectory(path4.join(canonicalRemoteRoot, "caddy", "hosts"));
43770
43836
  await captureDirectory(await canonicalTrustedTarget(lockIdentity.domainDirectory));
43771
43837
  for (const directory of lockIdentity.bootstrapTrust?.directories ?? []) await captureDirectory(directory.path, directory.caddyOwned === true);
43772
43838
  }
@@ -43790,17 +43856,17 @@ async function captureManagedRouteTrust(lockIdentity, createMissing) {
43790
43856
  return { directories: [...directoryIdentities.values()], managedRoot: canonicalRemoteRoot, finalFiles, globalLockFile: lockIdentity.globalLockFile, routeLockFile: lockIdentity.routeLockFile, routeFile };
43791
43857
  }
43792
43858
  async function canonicalTrustedTarget(target) {
43793
- if (!path3.isAbsolute(target) || path3.normalize(target) !== target) throw routeTrustError();
43859
+ if (!path4.isAbsolute(target) || path4.normalize(target) !== target) throw routeTrustError();
43794
43860
  return target;
43795
43861
  }
43796
43862
  async function trustedDirectoryChain(target, createMissing, managedRoot, targetOwner) {
43797
43863
  const canonicalTarget = await canonicalTrustedTarget(target);
43798
- const parsed = path3.parse(canonicalTarget);
43799
- const components = canonicalTarget.slice(parsed.root.length).split(path3.sep).filter(Boolean);
43864
+ const parsed = path4.parse(canonicalTarget);
43865
+ const components = canonicalTarget.slice(parsed.root.length).split(path4.sep).filter(Boolean);
43800
43866
  const identities = [];
43801
43867
  let current2 = parsed.root;
43802
43868
  for (const component of [null, ...components]) {
43803
- if (component !== null) current2 = path3.join(current2, component);
43869
+ if (component !== null) current2 = path4.join(current2, component);
43804
43870
  let details;
43805
43871
  try {
43806
43872
  details = await lstat(current2);
@@ -43817,7 +43883,7 @@ async function trustedDirectoryChain(target, createMissing, managedRoot, targetO
43817
43883
  throw routeTrustError();
43818
43884
  }
43819
43885
  }
43820
- const managed = current2 === managedRoot || current2.startsWith(`${managedRoot}${path3.sep}`);
43886
+ const managed = current2 === managedRoot || current2.startsWith(`${managedRoot}${path4.sep}`);
43821
43887
  const expectedOwner = current2 === canonicalTarget ? targetOwner : void 0;
43822
43888
  if (!details.isDirectory() || details.isSymbolicLink() || !(expectedOwner ? trustedExactOwnerMetadata(details, expectedOwner) : managed ? trustedHostPathMetadata(details) : trustedAnchorPathMetadata(details))) throw routeTrustError();
43823
43889
  identities.push({ path: current2, dev: details.dev, ino: details.ino, ...expectedOwner ? { expectedOwner } : {} });
@@ -43878,7 +43944,7 @@ async function assertActiveManagedRouteTrust(routeFile) {
43878
43944
  } catch {
43879
43945
  throw routeTrustError();
43880
43946
  }
43881
- const managed = expected.path === trust.managedRoot || expected.path.startsWith(`${trust.managedRoot}${path3.sep}`);
43947
+ const managed = expected.path === trust.managedRoot || expected.path.startsWith(`${trust.managedRoot}${path4.sep}`);
43882
43948
  if (!current2.isDirectory() || current2.isSymbolicLink() || !(expected.expectedOwner ? trustedExactOwnerMetadata(current2, expected.expectedOwner) : managed ? trustedHostPathMetadata(current2) : trustedAnchorPathMetadata(current2)) || current2.dev !== expected.dev || current2.ino !== expected.ino) throw routeTrustError();
43883
43949
  }
43884
43950
  await assertTrustedRegularFileIfExists(trust.globalLockFile);
@@ -44049,6 +44115,7 @@ function versionHost(request) {
44049
44115
  async function bootstrapHost(request) {
44050
44116
  validateBootstrapRequest(request);
44051
44117
  const bootstrap = normaliseBootstrap(request);
44118
+ await assertHostnamesAvailable(request.host.remoteRoot, [`host.${request.host.domain}`], `health:${request.host.domain}`);
44052
44119
  await ensureBootstrapDirectories(bootstrap);
44053
44120
  await validateBootstrapTls(request, bootstrap);
44054
44121
  const network = ensureDockerNetwork(bootstrap.network);
@@ -44073,26 +44140,60 @@ async function bootstrapHost(request) {
44073
44140
  }
44074
44141
  async function registerCapsule(request) {
44075
44142
  validateRegisterRequest(request);
44076
- const registration = normaliseRegistration(request);
44143
+ const claimPath = path4.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", "registration-claims", `${request.capsule.subname}.json`);
44144
+ const pendingClaim = await readFile3(claimPath, "utf8").then((contents) => JSON.parse(contents)).catch((error) => {
44145
+ if (errorDetails(error).code === "ENOENT") return null;
44146
+ throw error;
44147
+ });
44148
+ if (pendingClaim) {
44149
+ const aliases = validateAliasDomains(pendingClaim.aliasDomains);
44150
+ if (pendingClaim.domain !== request.host.domain || pendingClaim.subname !== request.capsule.subname || request.registration?.aliasDomains !== void 0 && !isDeepStrictEqual([...request.registration.aliasDomains].sort(), [...aliases].sort())) {
44151
+ throw helperError("Hosted Capsule registration recovery is required.", "Repair Caddy and retry the original registration with the same alias domains before changing its aliases.");
44152
+ }
44153
+ request = { ...request, registration: { ...request.registration, aliasDomains: aliases } };
44154
+ }
44155
+ let registration = normaliseRegistration(request);
44077
44156
  await ensureHostedDomainBootstrapped(request, registration);
44078
44157
  let reactivated = false;
44079
44158
  let sealedServerEnv = null;
44080
44159
  let priorRuntime = null;
44081
44160
  let admissionError = null;
44161
+ let registrationCommitted = false;
44162
+ const recoveryErrors = [];
44163
+ let priorRoute = null;
44164
+ let routeAttempted = false;
44165
+ let claimWritten = Boolean(pendingClaim);
44166
+ const reserveClaim = async (previous = null) => {
44167
+ if (claimWritten) return;
44168
+ await mkdir(path4.dirname(claimPath), { recursive: true });
44169
+ await publishHostHelperBytes(Buffer.from(JSON.stringify({
44170
+ domain: registration.domain,
44171
+ subname: registration.subname,
44172
+ aliasDomains: registration.aliasDomains,
44173
+ previousAliasDomains: validateAliasDomains(previous?.aliasDomains)
44174
+ })), claimPath, 384);
44175
+ claimWritten = true;
44176
+ };
44082
44177
  try {
44083
- await mkdir(path3.dirname(registryLockPath(request)), { recursive: true });
44178
+ await mkdir(path4.dirname(registryLockPath(request)), { recursive: true });
44084
44179
  await withRegistryLock(request, async () => {
44085
44180
  if (await pathExists(registration.registryRecord)) {
44086
44181
  const existing = await readRegistryRecordForCapsule(request, "register");
44087
44182
  assertRegistryRecordMatchesRequest(request, existing);
44088
- if (existing.status === "unregistered") {
44183
+ if (existing.status === "unregistered" || pendingClaim) {
44184
+ registration = normaliseRegistration(request, existing);
44185
+ await assertHostnamesAvailable(request.host.remoteRoot, [registration.route.hostname, ...registration.aliasDomains], registration.remoteCapsuleId);
44186
+ priorRoute = await captureReleaseInstallRoute(request, existing);
44187
+ await reserveClaim(existing);
44089
44188
  priorRuntime = captureCapsuleRuntimeSettlement(request, existing);
44090
44189
  quiesceCapsuleRuntime(priorRuntime);
44091
- await mkdir(path3.dirname(registration.registryRecord), { recursive: true });
44190
+ await mkdir(path4.dirname(registration.registryRecord), { recursive: true });
44092
44191
  await mkdir(registration.directories.releases, { recursive: true });
44192
+ routeAttempted = true;
44093
44193
  await writeUnavailableRoute(registration.lifecycle);
44094
44194
  sealedServerEnv = await ensureHostSealedEnvKeyPair(registration, existing);
44095
- await writeRegistryRecordAtomic(registration.registryRecord, reactivateRegistrationRecord(existing, sealedServerEnv));
44195
+ await writeRegistryRecordAtomic(registration.registryRecord, { ...reactivateRegistrationRecord(existing, sealedServerEnv), aliasDomains: registration.aliasDomains, route: { tls: registration.route.tls } });
44196
+ registrationCommitted = true;
44096
44197
  reactivated = true;
44097
44198
  return;
44098
44199
  }
@@ -44101,17 +44202,50 @@ async function registerCapsule(request) {
44101
44202
  `Choose a different Capsule subname for ${request.host.domain}.`
44102
44203
  );
44103
44204
  }
44104
- await mkdir(path3.dirname(registration.registryRecord), { recursive: true });
44205
+ await assertHostnamesAvailable(request.host.remoteRoot, [registration.route.hostname, ...registration.aliasDomains], registration.remoteCapsuleId);
44206
+ priorRoute = await captureReleaseInstallRoute(request, null);
44207
+ await reserveClaim();
44208
+ await mkdir(path4.dirname(registration.registryRecord), { recursive: true });
44105
44209
  await mkdir(registration.directories.releases, { recursive: true });
44106
44210
  await mkdir(registration.directories.logs, { recursive: true });
44211
+ routeAttempted = true;
44107
44212
  await writeUnavailableRoute(registration.lifecycle);
44108
44213
  sealedServerEnv = await ensureHostSealedEnvKeyPair(registration);
44109
44214
  await writeRegistryRecordAtomic(registration.registryRecord, createRegistrationRecord(registration, sealedServerEnv));
44215
+ registrationCommitted = true;
44110
44216
  });
44111
44217
  } catch (error) {
44112
- admissionError = error;
44218
+ if (registrationCommitted) {
44219
+ recoveryErrors.push(`Registry lock cleanup: ${errorDetails(error).message}`);
44220
+ } else {
44221
+ admissionError = error;
44222
+ }
44223
+ }
44224
+ if (admissionError && routeAttempted && priorRoute) {
44225
+ try {
44226
+ await restoreReleaseInstallRoute(priorRoute, true);
44227
+ } catch (error) {
44228
+ recoveryErrors.push(`Route rollback: ${errorDetails(error).message}`);
44229
+ }
44230
+ }
44231
+ try {
44232
+ await settleCapsuleRuntime(request, priorRuntime, admissionError);
44233
+ } catch (error) {
44234
+ recoveryErrors.push(`Runtime settlement: ${errorDetails(error).message}`);
44235
+ }
44236
+ if (claimWritten && recoveryErrors.length === 0 && (!admissionError || !pendingClaim)) {
44237
+ try {
44238
+ await rm(claimPath);
44239
+ } catch (error) {
44240
+ recoveryErrors.push(`Reservation cleanup: ${errorDetails(error).message}`);
44241
+ }
44242
+ }
44243
+ if (recoveryErrors.length) {
44244
+ throw helperError(
44245
+ "Hosted Capsule registration recovery failed.",
44246
+ `${admissionError ? `Registration: ${errorDetails(admissionError).message}` : "Registration committed."} ${recoveryErrors.join(" ")} Hostnames remain reserved; repair the Host services and retry the original registration with the same alias domains.`
44247
+ );
44113
44248
  }
44114
- await settleCapsuleRuntime(request, priorRuntime, admissionError);
44115
44249
  if (admissionError) throw admissionError;
44116
44250
  writeEnvelope({
44117
44251
  ok: true,
@@ -44123,6 +44257,8 @@ async function registerCapsule(request) {
44123
44257
  subname: registration.subname,
44124
44258
  domain: registration.domain,
44125
44259
  hostedUrl: registration.hostedUrl,
44260
+ aliasDomains: registration.aliasDomains,
44261
+ aliasUrls: registration.aliasDomains.map((hostname) => `https://${hostname}`),
44126
44262
  remoteCapsuleId: registration.remoteCapsuleId
44127
44263
  },
44128
44264
  registryRecord: registration.registryRecord,
@@ -44135,7 +44271,7 @@ async function registerCapsule(request) {
44135
44271
  }
44136
44272
  async function rotateCapsuleSealedEnvKey(request) {
44137
44273
  validateSealedEnvRotationRequest(request);
44138
- await mkdir(path3.dirname(registryLockPath(request)), { recursive: true });
44274
+ await mkdir(path4.dirname(registryLockPath(request)), { recursive: true });
44139
44275
  let data2;
44140
44276
  let priorRuntime = null;
44141
44277
  let rotationError = null;
@@ -44151,7 +44287,7 @@ async function rotateCapsuleSealedEnvKey(request) {
44151
44287
  }
44152
44288
  priorRuntime = captureCapsuleRuntimeSettlement(request, record);
44153
44289
  quiesceCapsuleRuntime(priorRuntime);
44154
- const dataDirectory = path3.join(request.host.remoteRoot, "hosts", request.host.domain, "capsules", request.capsule.subname, "data");
44290
+ const dataDirectory = path4.join(request.host.remoteRoot, "hosts", request.host.domain, "capsules", request.capsule.subname, "data");
44155
44291
  const previousPublicKeyFingerprint = record.sealedServerEnv?.currentKeyFingerprint ?? null;
44156
44292
  const sealedServerEnv = await generateHostSealedEnvKeyPair(dataDirectory);
44157
44293
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -44192,10 +44328,24 @@ async function rotateCapsuleSealedEnvKey(request) {
44192
44328
  if (rotationError) throw rotationError;
44193
44329
  writeEnvelope({ ok: true, data: data2, error: null });
44194
44330
  }
44331
+ async function assertRegistrationRecoveryComplete(request) {
44332
+ const claimPath = path4.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", "registration-claims", `${request.capsule.subname}.json`);
44333
+ const pending = await lstat(claimPath).catch((error) => {
44334
+ if (errorDetails(error).code === "ENOENT") return null;
44335
+ throw error;
44336
+ });
44337
+ if (pending) {
44338
+ throw helperError(
44339
+ "Hosted Capsule registration recovery is required before teardown.",
44340
+ "Repair the reported Host services or storage problem and retry the original registration with the same alias domains before unregistering or deleting this Capsule."
44341
+ );
44342
+ }
44343
+ }
44195
44344
  async function unregisterCapsule(request) {
44196
44345
  validateUnregisterRequest(request);
44346
+ await assertRegistrationRecoveryComplete(request);
44197
44347
  const unregister = normaliseUnregister(request);
44198
- await mkdir(path3.dirname(registryLockPath(request)), { recursive: true });
44348
+ await mkdir(path4.dirname(registryLockPath(request)), { recursive: true });
44199
44349
  let data2;
44200
44350
  await withRegistryLock(request, async () => {
44201
44351
  const record = await readRegistryRecordForCapsule(request, "unregister");
@@ -44231,8 +44381,9 @@ async function unregisterCapsule(request) {
44231
44381
  }
44232
44382
  async function deleteCapsule(request) {
44233
44383
  validateDeleteRequest(request);
44384
+ await assertRegistrationRecoveryComplete(request);
44234
44385
  const deletion = normaliseDeletion(request);
44235
- await mkdir(path3.dirname(registryLockPath(request)), { recursive: true });
44386
+ await mkdir(path4.dirname(registryLockPath(request)), { recursive: true });
44236
44387
  let data2;
44237
44388
  await withRegistryLock(request, async () => {
44238
44389
  const record = await readOptionalRegistryRecordForCapsule(request);
@@ -44284,7 +44435,7 @@ async function installRelease(request) {
44284
44435
  async function installClaimedRelease(request, previousRecord, paths, claimedArchive) {
44285
44436
  const release = request.release;
44286
44437
  const previousCurrentRelease = previousRecord.currentRelease?.id ? { id: previousRecord.currentRelease.id } : null;
44287
- const previousRegistryContents = await readFile2(registryPath(request), "utf8");
44438
+ const previousRegistryContents = await readFile3(registryPath(request), "utf8");
44288
44439
  const previousCurrentTarget = await readlink(paths.currentLink).catch((error) => {
44289
44440
  if (errorDetails(error).code === "ENOENT") return null;
44290
44441
  throw error;
@@ -44454,18 +44605,18 @@ async function installClaimedRelease(request, previousRecord, paths, claimedArch
44454
44605
  writeEnvelope({ ok: true, data: data2, error: null });
44455
44606
  }
44456
44607
  async function claimReleaseArchive(request) {
44457
- const expectedIncoming = path3.join(request.host.remoteRoot, "incoming", `${request.release.id}.tar.gz`);
44458
- if (path3.resolve(request.release.remoteArchive) !== path3.resolve(expectedIncoming)) {
44608
+ const expectedIncoming = path4.join(request.host.remoteRoot, "incoming", `${request.release.id}.tar.gz`);
44609
+ if (path4.resolve(request.release.remoteArchive) !== path4.resolve(expectedIncoming)) {
44459
44610
  throw helperError("Invalid release install request.", "Upload the release to the canonical Host incoming path and retry `sporades host push`.");
44460
44611
  }
44461
- const claimsDirectory = path3.join(request.host.remoteRoot, ".release-claims");
44612
+ const claimsDirectory = path4.join(request.host.remoteRoot, ".release-claims");
44462
44613
  await mkdir(claimsDirectory, { recursive: true, mode: 448 });
44463
44614
  const claimsStats = await lstat(claimsDirectory);
44464
44615
  if (!claimsStats.isDirectory() || claimsStats.isSymbolicLink() || typeof process.getuid === "function" && claimsStats.uid !== process.getuid()) {
44465
44616
  throw helperError("Hosted Capsule release claim directory is unsafe.", "Repair Host helper ownership of the release claim directory and retry.");
44466
44617
  }
44467
44618
  await chmod(claimsDirectory, 448);
44468
- const claimedPath = path3.join(claimsDirectory, `${request.release.id}-${process.pid}-${randomBytes(16).toString("hex")}.tar.gz`);
44619
+ const claimedPath = path4.join(claimsDirectory, `${request.release.id}-${process.pid}-${randomBytes(16).toString("hex")}.tar.gz`);
44469
44620
  await rename(request.release.remoteArchive, claimedPath);
44470
44621
  try {
44471
44622
  const stats = await lstat(claimedPath);
@@ -44500,15 +44651,15 @@ async function validateExtractedReleaseTree(root, expectedFiles) {
44500
44651
  let totalBytes = 0;
44501
44652
  const publicClaims = [];
44502
44653
  async function visit(directory, prefix = "") {
44503
- for (const entry of await readdir(directory, { withFileTypes: true })) {
44654
+ for (const entry of await readdir2(directory, { withFileTypes: true })) {
44504
44655
  const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
44505
44656
  const normalized = relative.normalize("NFC");
44506
- const safe = relative.length > 0 && !relative.startsWith("/") && !relative.includes("\\") && !relative.includes("\0") && path3.posix.normalize(relative) === relative && Buffer.byteLength(relative, "utf8") <= HOST_RELEASE_ARCHIVE_LIMITS.pathBytes && relative.split("/").every((segment) => segment && segment !== "." && segment !== "..");
44657
+ const safe = relative.length > 0 && !relative.startsWith("/") && !relative.includes("\\") && !relative.includes("\0") && path4.posix.normalize(relative) === relative && Buffer.byteLength(relative, "utf8") <= HOST_RELEASE_ARCHIVE_LIMITS.pathBytes && relative.split("/").every((segment) => segment && segment !== "." && segment !== "..");
44507
44658
  if (!safe || canonical.has(normalized)) {
44508
44659
  throw helperError("Extracted Hosted Capsule release is unsafe.", "Upload a release with unique bounded relative paths.");
44509
44660
  }
44510
44661
  canonical.add(normalized);
44511
- const entryPath = path3.join(directory, entry.name);
44662
+ const entryPath = path4.join(directory, entry.name);
44512
44663
  const stats = await lstat(entryPath);
44513
44664
  if (stats.isSymbolicLink()) {
44514
44665
  throw helperError("Extracted Hosted Capsule release is unsafe.", "Upload regular release files without symbolic links.");
@@ -44538,7 +44689,7 @@ async function validateExtractedReleaseTree(root, expectedFiles) {
44538
44689
  }
44539
44690
  publicClaims.push({ path: publicPath, size: stats.size });
44540
44691
  }
44541
- actual.push({ path: relative, size: stats.size, sha256: createHash("sha256").update(await readFile2(entryPath)).digest("hex") });
44692
+ actual.push({ path: relative, size: stats.size, sha256: createHash("sha256").update(await readFile3(entryPath)).digest("hex") });
44542
44693
  }
44543
44694
  }
44544
44695
  await visit(root);
@@ -44679,7 +44830,7 @@ function readVerificationHealthTimeoutMs(request) {
44679
44830
  return Math.min(value, 18e4);
44680
44831
  }
44681
44832
  async function routeVerifiedFailureToUnavailable(request, releaseId, message) {
44682
- const lifecycle = normaliseLifecycle(request);
44833
+ const lifecycle = normaliseLifecycle(request, await readRegistryRecordForCapsule(request, "lifecycle"));
44683
44834
  stopAndRemoveContainer(lifecycle.container.name);
44684
44835
  try {
44685
44836
  await writeUnavailableRoute(lifecycle);
@@ -44749,7 +44900,7 @@ async function restoreFailedReleaseAfterFallbackRestartFailure(request, failedRe
44749
44900
  reason
44750
44901
  );
44751
44902
  try {
44752
- await writeUnavailableRoute(normaliseLifecycle(request));
44903
+ await writeUnavailableRoute(normaliseLifecycle(request, await readRegistryRecordForCapsule(request, "lifecycle")));
44753
44904
  } catch {
44754
44905
  }
44755
44906
  }
@@ -44985,7 +45136,7 @@ function releaseIncludesSealedServerEnvPrivateKey(release) {
44985
45136
  return Boolean(release.sealedServerEnvIncluded && privateKey && privateKeyPath);
44986
45137
  }
44987
45138
  function releasePrivateKeyPath(paths, releaseId) {
44988
- return path3.join(paths.data, "sealed-server-env", "releases", `${releaseId}.private.pem`);
45139
+ return path4.join(paths.data, "sealed-server-env", "releases", `${releaseId}.private.pem`);
44989
45140
  }
44990
45141
  async function installSealedServerEnvPrivateKey(release, paths) {
44991
45142
  if (!releaseIncludesSealedServerEnvPrivateKey(release)) return;
@@ -44993,9 +45144,9 @@ async function installSealedServerEnvPrivateKey(release, paths) {
44993
45144
  const privateKeyPath = releasePrivateKeyPath(paths, release.id);
44994
45145
  const dataHandle = await openCanonicalRuntimeDataDirectory(paths.data, true);
44995
45146
  try {
44996
- const rootHandle = await openOrCreateRuntimeDirectory(dataHandle, path3.join(paths.data, "sealed-server-env"));
45147
+ const rootHandle = await openOrCreateRuntimeDirectory(dataHandle, path4.join(paths.data, "sealed-server-env"));
44997
45148
  try {
44998
- const releasesHandle = await openOrCreateRuntimeDirectory(rootHandle, path3.dirname(privateKeyPath));
45149
+ const releasesHandle = await openOrCreateRuntimeDirectory(rootHandle, path4.dirname(privateKeyPath));
44999
45150
  try {
45000
45151
  await publishRuntimeFile(releasesHandle, privateKeyPath, privateKey, 384, "release-private-key-publish");
45001
45152
  } finally {
@@ -45013,11 +45164,11 @@ async function removeInstalledReleasePrivateKey(release, paths) {
45013
45164
  const privateKeyPath = releasePrivateKeyPath(paths, release.id);
45014
45165
  const dataHandle = await openCanonicalRuntimeDataDirectory(paths.data, false);
45015
45166
  try {
45016
- const rootHandle = await openOrCreateRuntimeDirectory(dataHandle, path3.join(paths.data, "sealed-server-env"));
45167
+ const rootHandle = await openOrCreateRuntimeDirectory(dataHandle, path4.join(paths.data, "sealed-server-env"));
45017
45168
  try {
45018
- const releasesHandle = await openOrCreateRuntimeDirectory(rootHandle, path3.dirname(privateKeyPath));
45169
+ const releasesHandle = await openOrCreateRuntimeDirectory(rootHandle, path4.dirname(privateKeyPath));
45019
45170
  try {
45020
- const descriptorPath = descriptorChildPath(releasesHandle.fd, path3.basename(privateKeyPath), privateKeyPath);
45171
+ const descriptorPath = descriptorChildPath(releasesHandle.fd, path4.basename(privateKeyPath), privateKeyPath);
45021
45172
  const retained = await open(descriptorPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW).catch((error) => {
45022
45173
  if (errorDetails(error).code === "ENOENT") return null;
45023
45174
  throw runtimeDataTrustError(privateKeyPath);
@@ -45044,7 +45195,7 @@ async function removeInstalledReleasePrivateKey(release, paths) {
45044
45195
  async function captureReleaseInstallRoute(request, previousRecord) {
45045
45196
  const lifecycle = normaliseLifecycle(request, previousRecord, { ignoreProvidedLifecycle: true });
45046
45197
  const routeFile = lifecycle.routes.running.routeFile;
45047
- const contents = await readFile2(routeFile, "utf8").catch((error) => {
45198
+ const contents = await readFile3(routeFile, "utf8").catch((error) => {
45048
45199
  if (errorDetails(error).code === "ENOENT") return null;
45049
45200
  throw error;
45050
45201
  });
@@ -45060,13 +45211,14 @@ async function restoreCurrentReleasePointerTarget(currentLink, previousTarget) {
45060
45211
  await symlink(previousTarget, temporary);
45061
45212
  await rename(temporary, currentLink);
45062
45213
  }
45063
- async function restoreReleaseInstallRoute(snapshot) {
45214
+ async function restoreReleaseInstallRoute(snapshot, reloadWhenAbsent = false) {
45064
45215
  if (snapshot.contents !== null) {
45065
45216
  await applyManagedRoute(snapshot.lifecycle, snapshot.routeFile, snapshot.contents);
45066
45217
  return;
45067
45218
  }
45068
45219
  await withManagedRouteLock(snapshot.routeFile, async () => {
45069
45220
  const removed = await removeManagedRouteLocked(snapshot.lifecycle, snapshot.routeFile);
45221
+ if (reloadWhenAbsent && !removed.removed) reloadCaddy(snapshot.lifecycle);
45070
45222
  await finalizeRemovedRouteLocked(removed);
45071
45223
  });
45072
45224
  }
@@ -45120,11 +45272,11 @@ function validateSealedServerEnvPrivateKeyPath(release, paths) {
45120
45272
  );
45121
45273
  }
45122
45274
  const compatiblePrivateKeyPaths = /* @__PURE__ */ new Set([
45123
- path3.resolve(releasePrivateKeyPath(paths, release.id)),
45124
- path3.resolve(path3.join(paths.data, "sealed-server-env", "server-env.private.pem")),
45125
- ...fingerprint ? [path3.resolve(path3.join(paths.data, "sealed-server-env", "keys", `${fingerprint}.private.pem`))] : []
45275
+ path4.resolve(releasePrivateKeyPath(paths, release.id)),
45276
+ path4.resolve(path4.join(paths.data, "sealed-server-env", "server-env.private.pem")),
45277
+ ...fingerprint ? [path4.resolve(path4.join(paths.data, "sealed-server-env", "keys", `${fingerprint}.private.pem`))] : []
45126
45278
  ]);
45127
- if (typeof privateKeyPath !== "string" || !compatiblePrivateKeyPaths.has(path3.resolve(privateKeyPath))) {
45279
+ if (typeof privateKeyPath !== "string" || !compatiblePrivateKeyPaths.has(path4.resolve(privateKeyPath))) {
45128
45280
  throw helperError(
45129
45281
  "Invalid Sealed Server env private key path.",
45130
45282
  "Update the Sporades CLI and retry `sporades host push`."
@@ -45343,7 +45495,7 @@ async function routeRuntimeExhaustionToUnavailable(request, record, health) {
45343
45495
  async function stopCapsule(request, options = {}) {
45344
45496
  validateLifecycleRequest(request);
45345
45497
  await verifyRegisteredCapsule(request, "lifecycle");
45346
- const lifecycle = normaliseLifecycle(request);
45498
+ const lifecycle = normaliseLifecycle(request, await readRegistryRecordForCapsule(request, "lifecycle"));
45347
45499
  stopAndRemoveContainer(lifecycle.container.name);
45348
45500
  await writeUnavailableRoute(lifecycle);
45349
45501
  await updateRegistryStatus(request, "stopped");
@@ -45518,6 +45670,7 @@ async function listReleases(request) {
45518
45670
  subname: record.subname,
45519
45671
  domain: record.domain,
45520
45672
  hostedUrl: record.hostedUrl ?? `${request.host.scheme ?? "https"}://${record.subname}.${request.host.domain}`,
45673
+ aliasDomains: validateAliasDomains(record.aliasDomains),
45521
45674
  remoteCapsuleId: record.remoteCapsuleId ?? `${request.host.domain}/${record.subname}`
45522
45675
  },
45523
45676
  currentRelease: publicCurrentRelease(record),
@@ -45586,7 +45739,7 @@ async function rollbackRelease(request) {
45586
45739
  return;
45587
45740
  }
45588
45741
  try {
45589
- await writeUnavailableRoute(normaliseLifecycle(request));
45742
+ await writeUnavailableRoute(normaliseLifecycle(request, await readRegistryRecordForCapsule(request, "lifecycle")));
45590
45743
  } catch (error) {
45591
45744
  restartError = restartError ?? error;
45592
45745
  }
@@ -45665,6 +45818,7 @@ async function listCapsules(request) {
45665
45818
  subname: record.subname,
45666
45819
  domain: record.domain,
45667
45820
  hostedUrl: record.hostedUrl ?? `${request.host.scheme ?? "https"}://${record.subname}.${request.host.domain}`,
45821
+ aliasDomains: validateAliasDomains(record.aliasDomains),
45668
45822
  registry: {
45669
45823
  remoteCapsuleId: record.remoteCapsuleId ?? `${request.host.domain}/${record.subname}`,
45670
45824
  createdAt: record.createdAt ?? null,
@@ -45709,7 +45863,7 @@ function createUnregisterResult(request, unregister, record, idempotent, route =
45709
45863
  registryRecord: unregister.registryRecord,
45710
45864
  directories: unregister.directories,
45711
45865
  preserved: {
45712
- releases: record.currentRelease?.id ? path3.join(unregister.directories.releases, record.currentRelease.id) : unregister.directories.releases,
45866
+ releases: record.currentRelease?.id ? path4.join(unregister.directories.releases, record.currentRelease.id) : unregister.directories.releases,
45713
45867
  data: unregister.directories.data
45714
45868
  },
45715
45869
  deleteAfter: record.deleteAfter ?? null,
@@ -45758,28 +45912,28 @@ function createDeleteResult(request, deletion, removals) {
45758
45912
  };
45759
45913
  }
45760
45914
  function canonicalReleasePaths(request) {
45761
- const capsule = path3.join(
45915
+ const capsule = path4.join(
45762
45916
  request.host.remoteRoot,
45763
45917
  "hosts",
45764
45918
  request.host.domain,
45765
45919
  "capsules",
45766
45920
  request.capsule.subname
45767
45921
  );
45768
- const releases = path3.join(capsule, "releases");
45922
+ const releases = path4.join(capsule, "releases");
45769
45923
  return {
45770
45924
  capsule,
45771
45925
  releases,
45772
- release: request.release?.id ? path3.join(releases, request.release.id) : null,
45773
- data: path3.join(capsule, "data"),
45774
- logs: path3.join(capsule, "logs"),
45775
- currentLink: path3.join(capsule, "current")
45926
+ release: request.release?.id ? path4.join(releases, request.release.id) : null,
45927
+ data: path4.join(capsule, "data"),
45928
+ logs: path4.join(capsule, "logs"),
45929
+ currentLink: path4.join(capsule, "current")
45776
45930
  };
45777
45931
  }
45778
45932
  function canonicalRollbackPaths(request, releaseId) {
45779
45933
  const paths = canonicalReleasePaths({ ...request, release: { id: releaseId, remoteArchive: "", files: [] } });
45780
45934
  return {
45781
45935
  ...paths,
45782
- release: path3.join(paths.releases, releaseId)
45936
+ release: path4.join(paths.releases, releaseId)
45783
45937
  };
45784
45938
  }
45785
45939
  function normaliseLifecycle(request, registryRecord = null, options = {}) {
@@ -45806,12 +45960,12 @@ function normaliseLifecycle(request, registryRecord = null, options = {}) {
45806
45960
  };
45807
45961
  const defaultMounts = {
45808
45962
  files: [
45809
- { host: path3.join(currentLink, "server.mjs"), container: "/app/server.mjs", mode: "ro" },
45810
- { host: path3.join(currentLink, "public"), container: "/app/public", mode: "ro" },
45811
- { host: path3.join(currentLink, "sporades.json"), container: "/app/sporades.json", mode: "ro" },
45812
- { host: path3.join(currentLink, ".env.sporades.server"), container: "/app/.env.sporades.server", mode: "ro", optional: true },
45963
+ { host: path4.join(currentLink, "server.mjs"), container: "/app/server.mjs", mode: "ro" },
45964
+ { host: path4.join(currentLink, "public"), container: "/app/public", mode: "ro" },
45965
+ { host: path4.join(currentLink, "sporades.json"), container: "/app/sporades.json", mode: "ro" },
45966
+ { host: path4.join(currentLink, ".env.sporades.server"), container: "/app/.env.sporades.server", mode: "ro", optional: true },
45813
45967
  {
45814
- host: path3.join(currentLink, ".sporades", "sealed-server-env", "server-env.sealed.json"),
45968
+ host: path4.join(currentLink, ".sporades", "sealed-server-env", "server-env.sealed.json"),
45815
45969
  container: "/app/.sporades/sealed-server-env/server-env.sealed.json",
45816
45970
  mode: "ro",
45817
45971
  optional: true
@@ -45863,9 +46017,11 @@ function normaliseLifecycle(request, registryRecord = null, options = {}) {
45863
46017
  ...baseImageLabels(helperPackageBaseImage.updatePolicy.mode)
45864
46018
  }
45865
46019
  };
46020
+ const aliasDomains = validateAliasDomains(registryRecord?.aliasDomains);
45866
46021
  const canonicalRoutes = {
45867
46022
  running: {
45868
46023
  hostname: `${subname}.${domain}`,
46024
+ ...aliasDomains.length ? { aliasDomains } : {},
45869
46025
  target: "container",
45870
46026
  containerName,
45871
46027
  port: 4e3,
@@ -45875,6 +46031,7 @@ function normaliseLifecycle(request, registryRecord = null, options = {}) {
45875
46031
  },
45876
46032
  unavailable: {
45877
46033
  hostname: `${subname}.${domain}`,
46034
+ ...aliasDomains.length ? { aliasDomains } : {},
45878
46035
  target: "hosted-capsule-unavailable",
45879
46036
  statusCode: 503,
45880
46037
  routeFile,
@@ -45923,12 +46080,12 @@ function canonicalLifecycleRouteTls(request, registryRecord, provided) {
45923
46080
  if (!(/* @__PURE__ */ new Set(["automatic", "cloudflare-origin"])).has(mode) || suppliedModes.some((value) => value !== mode)) {
45924
46081
  throw invalidLifecycleAuthorityError();
45925
46082
  }
45926
- const directory = path3.join(request.host.remoteRoot, "hosts", request.host.domain, "tls");
46083
+ const directory = path4.join(request.host.remoteRoot, "hosts", request.host.domain, "tls");
45927
46084
  return {
45928
46085
  mode,
45929
46086
  directory,
45930
- certificate: mode === "cloudflare-origin" ? path3.join(directory, "origin.crt") : null,
45931
- key: mode === "cloudflare-origin" ? path3.join(directory, "origin.key") : null
46087
+ certificate: mode === "cloudflare-origin" ? path4.join(directory, "origin.crt") : null,
46088
+ key: mode === "cloudflare-origin" ? path4.join(directory, "origin.key") : null
45932
46089
  };
45933
46090
  }
45934
46091
  function invalidLifecycleAuthorityError() {
@@ -45971,14 +46128,14 @@ function assertCanonicalLifecycleAuthority(provided, canonical) {
45971
46128
  const allowedByContainer = new Map(canonical.defaultMounts.files.map((mount) => [mount.container, mount]));
45972
46129
  const privateKeyContainerPath = "/app/.sporades/sealed-server-env/server-env.private.pem";
45973
46130
  const legacySealedServerEnvPrivateKeyMount = {
45974
- host: path3.join(canonical.paths.data, "sealed-server-env", "server-env.private.pem"),
46131
+ host: path4.join(canonical.paths.data, "sealed-server-env", "server-env.private.pem"),
45975
46132
  container: privateKeyContainerPath,
45976
46133
  mode: "ro",
45977
46134
  optional: true
45978
46135
  };
45979
46136
  const legacyAllowed = [
45980
- { host: path3.join(canonical.currentLink, "client.js"), container: "/app/client.js", mode: "ro" },
45981
- { host: path3.join(canonical.currentLink, "index.html"), container: "/app/index.html", mode: "ro" }
46137
+ { host: path4.join(canonical.currentLink, "client.js"), container: "/app/client.js", mode: "ro" },
46138
+ { host: path4.join(canonical.currentLink, "index.html"), container: "/app/index.html", mode: "ro" }
45982
46139
  ];
45983
46140
  for (const mount of legacyAllowed) allowedByContainer.set(mount.container, mount);
45984
46141
  const seen = /* @__PURE__ */ new Set();
@@ -46260,10 +46417,10 @@ function inspectContainerLifecycle(containerName) {
46260
46417
  };
46261
46418
  }
46262
46419
  async function readCapsuleRegistryRecords(request) {
46263
- const registryDirectory = path3.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", "capsules");
46420
+ const registryDirectory = path4.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", "capsules");
46264
46421
  let entries;
46265
46422
  try {
46266
- entries = await readdir(registryDirectory, { withFileTypes: true });
46423
+ entries = await readdir2(registryDirectory, { withFileTypes: true });
46267
46424
  } catch (error) {
46268
46425
  if (errorDetails(error).code === "ENOENT") {
46269
46426
  return [];
@@ -46273,10 +46430,10 @@ async function readCapsuleRegistryRecords(request) {
46273
46430
  const records = [];
46274
46431
  const files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort();
46275
46432
  for (const file of files) {
46276
- const recordPath = path3.join(registryDirectory, file);
46433
+ const recordPath = path4.join(registryDirectory, file);
46277
46434
  let record;
46278
46435
  try {
46279
- record = JSON.parse(await readFile2(recordPath, "utf8"));
46436
+ record = JSON.parse(await readFile3(recordPath, "utf8"));
46280
46437
  } catch (error) {
46281
46438
  if (error instanceof SyntaxError) {
46282
46439
  throw helperError(
@@ -46404,19 +46561,21 @@ function normaliseProvidedBaseImage(value) {
46404
46561
  version: provided.version ?? SPORADES_BASE_IMAGE.version
46405
46562
  };
46406
46563
  }
46407
- function normaliseRegistration(request) {
46564
+ function normaliseRegistration(request, existing = null) {
46565
+ const aliasDomains = validateAliasDomains(request.registration?.aliasDomains ?? existing?.aliasDomains);
46408
46566
  const subname = request.capsule.subname;
46409
46567
  const domain = request.host.domain;
46410
46568
  const remoteRoot = request.host.remoteRoot;
46411
46569
  const scheme = request.host.scheme ?? "https";
46412
46570
  const hostedUrl = `${scheme}://${subname}.${domain}`;
46413
46571
  const remoteCapsuleId = `${domain}/${subname}`;
46414
- const capsuleDirectory = path3.join(remoteRoot, "hosts", domain, "capsules", subname);
46415
- const routeFile = path3.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`);
46572
+ const capsuleDirectory = path4.join(remoteRoot, "hosts", domain, "capsules", subname);
46573
+ const routeFile = path4.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`);
46416
46574
  const routeTls = normaliseRegistrationTls(request);
46417
46575
  const accessLog = canonicalCapsuleHttpLogPath(request, remoteRoot);
46418
46576
  const route = {
46419
46577
  hostname: `${subname}.${domain}`,
46578
+ ...aliasDomains.length ? { aliasDomains } : {},
46420
46579
  target: "hosted-capsule-unavailable",
46421
46580
  statusCode: 503,
46422
46581
  routeFile,
@@ -46429,12 +46588,13 @@ function normaliseRegistration(request) {
46429
46588
  remoteRoot,
46430
46589
  hostedUrl,
46431
46590
  remoteCapsuleId,
46432
- registryRecord: path3.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46591
+ aliasDomains,
46592
+ registryRecord: path4.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46433
46593
  directories: {
46434
46594
  capsule: capsuleDirectory,
46435
- releases: path3.join(capsuleDirectory, "releases"),
46436
- data: path3.join(capsuleDirectory, "data"),
46437
- logs: path3.join(capsuleDirectory, "logs")
46595
+ releases: path4.join(capsuleDirectory, "releases"),
46596
+ data: path4.join(capsuleDirectory, "data"),
46597
+ logs: path4.join(capsuleDirectory, "logs")
46438
46598
  },
46439
46599
  baseImage: normaliseProvidedBaseImage(request.registration?.baseImage),
46440
46600
  route,
@@ -46452,19 +46612,19 @@ function normaliseUnregister(request) {
46452
46612
  const scheme = request.host.scheme ?? "https";
46453
46613
  const hostedUrl = `${scheme}://${subname}.${domain}`;
46454
46614
  const remoteCapsuleId = `${domain}/${subname}`;
46455
- const capsuleDirectory = path3.join(remoteRoot, "hosts", domain, "capsules", subname);
46456
- const routeFile = path3.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`);
46615
+ const capsuleDirectory = path4.join(remoteRoot, "hosts", domain, "capsules", subname);
46616
+ const routeFile = path4.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`);
46457
46617
  const containerName = createHostedContainerName(domain, subname);
46458
46618
  return {
46459
46619
  subname,
46460
46620
  domain,
46461
46621
  hostedUrl,
46462
46622
  remoteCapsuleId,
46463
- registryRecord: path3.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46623
+ registryRecord: path4.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46464
46624
  directories: {
46465
46625
  capsule: capsuleDirectory,
46466
- releases: path3.join(capsuleDirectory, "releases"),
46467
- data: path3.join(capsuleDirectory, "data")
46626
+ releases: path4.join(capsuleDirectory, "releases"),
46627
+ data: path4.join(capsuleDirectory, "data")
46468
46628
  },
46469
46629
  container: {
46470
46630
  name: containerName
@@ -46485,21 +46645,21 @@ function normaliseDeletion(request) {
46485
46645
  const domain = request.host.domain;
46486
46646
  const remoteRoot = request.host.remoteRoot;
46487
46647
  const scheme = request.host.scheme ?? "https";
46488
- const capsuleDirectory = path3.join(remoteRoot, "hosts", domain, "capsules", subname);
46648
+ const capsuleDirectory = path4.join(remoteRoot, "hosts", domain, "capsules", subname);
46489
46649
  return {
46490
46650
  subname,
46491
46651
  domain,
46492
46652
  hostedUrl: `${scheme}://${subname}.${domain}`,
46493
46653
  remoteCapsuleId: `${domain}/${subname}`,
46494
- registryRecord: path3.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46654
+ registryRecord: path4.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46495
46655
  directories: {
46496
46656
  capsule: capsuleDirectory,
46497
- releases: path3.join(capsuleDirectory, "releases"),
46498
- data: path3.join(capsuleDirectory, "data")
46657
+ releases: path4.join(capsuleDirectory, "releases"),
46658
+ data: path4.join(capsuleDirectory, "data")
46499
46659
  },
46500
46660
  route: {
46501
46661
  hostname: `${subname}.${domain}`,
46502
- routeFile: path3.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`)
46662
+ routeFile: path4.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`)
46503
46663
  },
46504
46664
  lifecycle: {
46505
46665
  remoteRoot
@@ -46510,17 +46670,17 @@ function normaliseRegistrationTls(request) {
46510
46670
  const remoteRoot = request.host.remoteRoot;
46511
46671
  const domain = request.host.domain;
46512
46672
  const tlsMode = request.registration?.bootstrap?.tls?.mode ?? request.bootstrap?.tls?.mode ?? "automatic";
46513
- const tlsDirectory = path3.join(remoteRoot, "hosts", domain, "tls");
46673
+ const tlsDirectory = path4.join(remoteRoot, "hosts", domain, "tls");
46514
46674
  return {
46515
46675
  mode: tlsMode,
46516
46676
  directory: tlsDirectory,
46517
- certificate: tlsMode === "cloudflare-origin" ? path3.join(tlsDirectory, "origin.crt") : null,
46518
- key: tlsMode === "cloudflare-origin" ? path3.join(tlsDirectory, "origin.key") : null
46677
+ certificate: tlsMode === "cloudflare-origin" ? path4.join(tlsDirectory, "origin.crt") : null,
46678
+ key: tlsMode === "cloudflare-origin" ? path4.join(tlsDirectory, "origin.key") : null
46519
46679
  };
46520
46680
  }
46521
46681
  async function ensureHostedDomainBootstrapped(request, registration) {
46522
- const caddyfile = path3.join(request.host.remoteRoot, "caddy", "Caddyfile");
46523
- const domainInclude = path3.join(request.host.remoteRoot, "caddy", "hosts", `${request.host.domain}.caddy`);
46682
+ const caddyfile = path4.join(request.host.remoteRoot, "caddy", "Caddyfile");
46683
+ const domainInclude = path4.join(request.host.remoteRoot, "caddy", "hosts", `${request.host.domain}.caddy`);
46524
46684
  const bootstrapped = await pathExists(caddyfile) && await pathExists(domainInclude);
46525
46685
  if (bootstrapped) {
46526
46686
  return;
@@ -46539,6 +46699,7 @@ function createRegistrationRecord(registration, sealedServerEnv = null) {
46539
46699
  domain: registration.domain,
46540
46700
  remoteCapsuleId: registration.remoteCapsuleId,
46541
46701
  hostedUrl: registration.hostedUrl,
46702
+ aliasDomains: registration.aliasDomains,
46542
46703
  status: "registered",
46543
46704
  createdAt: now,
46544
46705
  updatedAt: now,
@@ -46598,7 +46759,7 @@ async function generateHostSealedEnvKeyPair(dataDirectory) {
46598
46759
  };
46599
46760
  }
46600
46761
  async function openOrCreateRuntimeDirectory(parentHandle, targetPath) {
46601
- const descriptorPath = descriptorChildPath(parentHandle.fd, path3.basename(targetPath), targetPath);
46762
+ const descriptorPath = descriptorChildPath(parentHandle.fd, path4.basename(targetPath), targetPath);
46602
46763
  await mkdir(descriptorPath, { mode: 448 }).catch((error) => {
46603
46764
  if (errorDetails(error).code !== "EEXIST") throw runtimeDataTrustError(targetPath);
46604
46765
  });
@@ -46619,7 +46780,7 @@ async function openOrCreateRuntimeDirectory(parentHandle, targetPath) {
46619
46780
  return handle;
46620
46781
  }
46621
46782
  async function writeExclusiveRuntimeFile(parentHandle, targetPath, contents, mode) {
46622
- const descriptorPath = descriptorChildPath(parentHandle.fd, path3.basename(targetPath), targetPath);
46783
+ const descriptorPath = descriptorChildPath(parentHandle.fd, path4.basename(targetPath), targetPath);
46623
46784
  const handle = await open(
46624
46785
  descriptorPath,
46625
46786
  fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW,
@@ -46642,14 +46803,14 @@ async function writeExclusiveRuntimeFile(parentHandle, targetPath, contents, mod
46642
46803
  }
46643
46804
  }
46644
46805
  async function publishRuntimeFile(parentHandle, targetPath, contents, mode, boundary) {
46645
- const temporaryPath = path3.join(path3.dirname(targetPath), `.${path3.basename(targetPath)}.tmp-${process.pid}-${randomBytes(8).toString("hex")}`);
46806
+ const temporaryPath = path4.join(path4.dirname(targetPath), `.${path4.basename(targetPath)}.tmp-${process.pid}-${randomBytes(8).toString("hex")}`);
46646
46807
  await writeExclusiveRuntimeFile(parentHandle, temporaryPath, contents, mode);
46647
- const temporaryDescriptor = descriptorChildPath(parentHandle.fd, path3.basename(temporaryPath), temporaryPath);
46648
- const targetDescriptor = descriptorChildPath(parentHandle.fd, path3.basename(targetPath), targetPath);
46808
+ const temporaryDescriptor = descriptorChildPath(parentHandle.fd, path4.basename(temporaryPath), temporaryPath);
46809
+ const targetDescriptor = descriptorChildPath(parentHandle.fd, path4.basename(targetPath), targetPath);
46649
46810
  try {
46650
46811
  await pauseRuntimeTreePublication(boundary, targetPath);
46651
46812
  const parentIdentity = await parentHandle.stat();
46652
- await assertRuntimeDataPathIdentity(path3.dirname(targetPath), { dev: parentIdentity.dev, ino: parentIdentity.ino }, true);
46813
+ await assertRuntimeDataPathIdentity(path4.dirname(targetPath), { dev: parentIdentity.dev, ino: parentIdentity.ino }, true);
46653
46814
  try {
46654
46815
  const existing = await lstat(targetPath);
46655
46816
  if (!existing.isFile() || existing.isSymbolicLink()) throw runtimeDataTrustError(targetPath);
@@ -46692,11 +46853,11 @@ async function cleanupUnreferencedHostSealedEnvKeys(dataDirectory, referencedFin
46692
46853
  try {
46693
46854
  const keysIdentity = await keysHandle.stat();
46694
46855
  const descriptorDirectory = process.platform === "linux" ? `/proc/self/fd/${keysHandle.fd}` : paths.keys;
46695
- const entries = await readdir(descriptorDirectory);
46856
+ const entries = await readdir2(descriptorDirectory);
46696
46857
  for (const entry of entries) {
46697
46858
  const match = /^([a-f0-9]{16})\.(private|public)\.pem$/.exec(entry);
46698
46859
  if (!match || referencedFingerprints.has(match[1])) continue;
46699
- const targetPath = path3.join(paths.keys, entry);
46860
+ const targetPath = path4.join(paths.keys, entry);
46700
46861
  const descriptorPath = descriptorChildPath(keysHandle.fd, entry, targetPath);
46701
46862
  const retained = await open(descriptorPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW).catch(() => {
46702
46863
  throw runtimeDataTrustError(targetPath);
@@ -46754,7 +46915,7 @@ async function inspectHostSealedEnvKey(record, remoteRoot) {
46754
46915
  if (typeof publicKeyFingerprint !== "string" || publicKeyFingerprint.length === 0) {
46755
46916
  return null;
46756
46917
  }
46757
- const dataDirectory = path3.join(remoteRoot, "hosts", record.domain, "capsules", record.subname, "data");
46918
+ const dataDirectory = path4.join(remoteRoot, "hosts", record.domain, "capsules", record.subname, "data");
46758
46919
  const paths = hostSealedEnvKeyPaths(dataDirectory, publicKeyFingerprint);
46759
46920
  const [publicKeyReadable, privateKeyReadable] = await Promise.all([
46760
46921
  pathReadable(paths.publicKey),
@@ -46774,7 +46935,7 @@ async function inspectHostSealedEnvKey(record, remoteRoot) {
46774
46935
  try {
46775
46936
  return {
46776
46937
  ...base,
46777
- publicKey: await readFile2(paths.publicKey, "utf8")
46938
+ publicKey: await readFile3(paths.publicKey, "utf8")
46778
46939
  };
46779
46940
  } catch (error) {
46780
46941
  if (errorDetails(error).code === "ENOENT") {
@@ -46821,13 +46982,13 @@ function currentReleaseSealedServerEnvFingerprint(record) {
46821
46982
  return typeof fingerprint === "string" ? fingerprint : null;
46822
46983
  }
46823
46984
  function hostSealedEnvKeyPaths(dataDirectory, fingerprint) {
46824
- const root = path3.join(dataDirectory, "sealed-server-env");
46825
- const keys = path3.join(root, "keys");
46985
+ const root = path4.join(dataDirectory, "sealed-server-env");
46986
+ const keys = path4.join(root, "keys");
46826
46987
  return {
46827
46988
  root,
46828
46989
  keys,
46829
- privateKey: path3.join(keys, `${fingerprint}.private.pem`),
46830
- publicKey: path3.join(keys, `${fingerprint}.public.pem`)
46990
+ privateKey: path4.join(keys, `${fingerprint}.private.pem`),
46991
+ publicKey: path4.join(keys, `${fingerprint}.public.pem`)
46831
46992
  };
46832
46993
  }
46833
46994
  function fingerprintPublicKey(publicKey) {
@@ -46887,20 +47048,20 @@ function normaliseBootstrap(request) {
46887
47048
  const provided = request.bootstrap ?? {};
46888
47049
  const remoteRoot = request.host.remoteRoot;
46889
47050
  const domain = request.host.domain;
46890
- const caddyDirectory = path3.join(remoteRoot, "caddy");
46891
- const domainDirectory = path3.join(remoteRoot, "hosts", domain);
46892
- const tlsDirectory = path3.join(domainDirectory, "tls");
47051
+ const caddyDirectory = path4.join(remoteRoot, "caddy");
47052
+ const domainDirectory = path4.join(remoteRoot, "hosts", domain);
47053
+ const tlsDirectory = path4.join(domainDirectory, "tls");
46893
47054
  const expectedDirectories = {
46894
47055
  remoteRoot,
46895
- bin: path3.join(remoteRoot, "bin"),
46896
- incoming: path3.join(remoteRoot, "incoming"),
47056
+ bin: path4.join(remoteRoot, "bin"),
47057
+ incoming: path4.join(remoteRoot, "incoming"),
46897
47058
  caddy: caddyDirectory,
46898
- caddyHosts: path3.join(caddyDirectory, "hosts"),
46899
- hosts: path3.join(remoteRoot, "hosts"),
47059
+ caddyHosts: path4.join(caddyDirectory, "hosts"),
47060
+ hosts: path4.join(remoteRoot, "hosts"),
46900
47061
  domain: domainDirectory,
46901
47062
  tls: tlsDirectory,
46902
- registry: path3.join(domainDirectory, "registry"),
46903
- capsules: path3.join(domainDirectory, "capsules")
47063
+ registry: path4.join(domainDirectory, "registry"),
47064
+ capsules: path4.join(domainDirectory, "capsules")
46904
47065
  };
46905
47066
  for (const [name2, value] of Object.entries(provided.directories ?? {})) {
46906
47067
  if (!(name2 in expectedDirectories) || value !== expectedDirectories[name2]) throw bootstrapPathError();
@@ -46910,8 +47071,8 @@ function normaliseBootstrap(request) {
46910
47071
  const directories = {
46911
47072
  ...expectedDirectories
46912
47073
  };
46913
- const expectedCertificate = path3.join(directories.tls, "origin.crt");
46914
- const expectedKey = path3.join(directories.tls, "origin.key");
47074
+ const expectedCertificate = path4.join(directories.tls, "origin.crt");
47075
+ const expectedKey = path4.join(directories.tls, "origin.key");
46915
47076
  if (provided.tls?.directory !== void 0 && provided.tls.directory !== directories.tls) throw bootstrapPathError();
46916
47077
  if (provided.tls?.certificate != null && provided.tls.certificate !== expectedCertificate) throw bootstrapPathError();
46917
47078
  if (provided.tls?.key != null && provided.tls.key !== expectedKey) throw bootstrapPathError();
@@ -46921,8 +47082,8 @@ function normaliseBootstrap(request) {
46921
47082
  certificate: tlsMode === "cloudflare-origin" ? expectedCertificate : null,
46922
47083
  key: tlsMode === "cloudflare-origin" ? expectedKey : null
46923
47084
  };
46924
- const expectedManagedInclude = path3.join(directories.caddy, "sporades-hosted-domains.caddy");
46925
- const expectedDomainInclude = path3.join(directories.caddyHosts, `${domain}.caddy`);
47085
+ const expectedManagedInclude = path4.join(directories.caddy, "sporades-hosted-domains.caddy");
47086
+ const expectedDomainInclude = path4.join(directories.caddyHosts, `${domain}.caddy`);
46926
47087
  const expectedAccessLog = defaultCaddyAccessLogPath(remoteRoot);
46927
47088
  if (provided.caddy?.managedInclude !== void 0 && provided.caddy.managedInclude !== expectedManagedInclude) throw bootstrapPathError();
46928
47089
  if (provided.caddy?.domainInclude !== void 0 && provided.caddy.domainInclude !== expectedDomainInclude) throw bootstrapPathError();
@@ -46937,11 +47098,11 @@ function normaliseBootstrap(request) {
46937
47098
  tls,
46938
47099
  network: provided.network ?? hostHelperConfig.hostedCapsule.dockerNetwork,
46939
47100
  caddy: {
46940
- caddyfile: path3.join(directories.caddy, "Caddyfile"),
47101
+ caddyfile: path4.join(directories.caddy, "Caddyfile"),
46941
47102
  managedInclude: expectedManagedInclude,
46942
47103
  domainInclude: expectedDomainInclude,
46943
- routesDirectory: path3.join(directories.caddyHosts, domain),
46944
- healthRoute: path3.join(directories.caddyHosts, domain, "host.caddy"),
47104
+ routesDirectory: path4.join(directories.caddyHosts, domain),
47105
+ healthRoute: path4.join(directories.caddyHosts, domain, "host.caddy"),
46945
47106
  accessLog: expectedAccessLog
46946
47107
  }
46947
47108
  };
@@ -46954,7 +47115,7 @@ function bootstrapPathError() {
46954
47115
  }
46955
47116
  function bootstrapTrustManifest(request) {
46956
47117
  const bootstrap = normaliseBootstrap(request);
46957
- const placeholder = path3.join(bootstrap.caddy.routesDirectory, ".sporades-placeholder.caddy");
47118
+ const placeholder = path4.join(bootstrap.caddy.routesDirectory, ".sporades-placeholder.caddy");
46958
47119
  const finalFiles = [
46959
47120
  { path: bootstrap.caddy.caddyfile },
46960
47121
  { path: bootstrap.caddy.managedInclude },
@@ -46969,8 +47130,8 @@ function bootstrapTrustManifest(request) {
46969
47130
  return {
46970
47131
  directories: [
46971
47132
  ...Object.values(bootstrap.directories).map((entry) => ({ path: entry })),
46972
- { path: path3.dirname(bootstrap.caddy.accessLog), caddyOwned: true },
46973
- { path: path3.join(bootstrap.directories.registry, "capsules") },
47133
+ { path: path4.dirname(bootstrap.caddy.accessLog), caddyOwned: true },
47134
+ { path: path4.join(bootstrap.directories.registry, "capsules") },
46974
47135
  { path: bootstrap.caddy.routesDirectory }
46975
47136
  ],
46976
47137
  finalFiles
@@ -46982,13 +47143,13 @@ async function ensureBootstrapDirectories(bootstrap) {
46982
47143
  bootstrap.directories.bin,
46983
47144
  bootstrap.directories.incoming,
46984
47145
  bootstrap.directories.caddy,
46985
- path3.dirname(bootstrap.caddy.accessLog),
47146
+ path4.dirname(bootstrap.caddy.accessLog),
46986
47147
  bootstrap.directories.caddyHosts,
46987
47148
  bootstrap.directories.hosts,
46988
47149
  bootstrap.directories.domain,
46989
47150
  bootstrap.directories.tls,
46990
47151
  bootstrap.directories.registry,
46991
- path3.join(bootstrap.directories.registry, "capsules"),
47152
+ path4.join(bootstrap.directories.registry, "capsules"),
46992
47153
  bootstrap.directories.capsules,
46993
47154
  bootstrap.caddy.routesDirectory
46994
47155
  ];
@@ -46999,7 +47160,7 @@ async function ensureBootstrapDirectories(bootstrap) {
46999
47160
  }
47000
47161
  async function provisionCaddyAccessLog(request, bootstrap) {
47001
47162
  const logFile = bootstrap.caddy.accessLog;
47002
- const logDirectory = path3.dirname(logFile);
47163
+ const logDirectory = path4.dirname(logFile);
47003
47164
  const caddyUser = resolveCaddyServiceUser();
47004
47165
  if (!caddyUser) {
47005
47166
  throw helperError(
@@ -47043,7 +47204,7 @@ async function provisionCaddyOwnedLogDescriptors(options) {
47043
47204
  try {
47044
47205
  let directoryDetails = await directoryHandle.stat();
47045
47206
  if (!directoryDetails.isDirectory() || directoryDetails.dev !== directoryIdentity.dev || directoryDetails.ino !== directoryIdentity.ino || !(directoryIdentity.expectedOwner ? trustedExactOwnerMetadata(directoryDetails, directoryIdentity.expectedOwner) : trustedHostPathMetadata(directoryDetails))) throw routeTrustError();
47046
- const descriptorFile = process.platform === "linux" ? `/proc/self/fd/${directoryHandle.fd}/${path3.basename(logFile)}` : logFile;
47207
+ const descriptorFile = process.platform === "linux" ? `/proc/self/fd/${directoryHandle.fd}/${path4.basename(logFile)}` : logFile;
47047
47208
  let fileHandle;
47048
47209
  try {
47049
47210
  fileHandle = await open(
@@ -47120,7 +47281,7 @@ async function provisionRouteLogFile(route, mutationBoundary) {
47120
47281
  if (!logFile) {
47121
47282
  return;
47122
47283
  }
47123
- const logDirectory = path3.dirname(logFile);
47284
+ const logDirectory = path4.dirname(logFile);
47124
47285
  const caddyUser = resolveCaddyServiceUser() ?? {
47125
47286
  uid: String(process.geteuid?.() ?? process.getuid?.() ?? 0),
47126
47287
  gid: String(process.getegid?.() ?? process.getgid?.() ?? 0)
@@ -47188,15 +47349,15 @@ async function installCaddyBootstrapConfig(request, bootstrap) {
47188
47349
  const caddyfile = bootstrap.caddy.caddyfile;
47189
47350
  const managedInclude = bootstrap.caddy.managedInclude;
47190
47351
  const domainInclude = bootstrap.caddy.domainInclude;
47191
- const placeholderRoute = path3.join(bootstrap.caddy.routesDirectory, ".sporades-placeholder.caddy");
47352
+ const placeholderRoute = path4.join(bootstrap.caddy.routesDirectory, ".sporades-placeholder.caddy");
47192
47353
  await atomicPublishBootstrapFile(placeholderRoute, "# Sporades keeps this placeholder so Caddy route imports are valid before Capsules are registered.\n", "bootstrap-placeholder");
47193
47354
  await atomicPublishBootstrapFile(bootstrap.caddy.healthRoute, renderHostHealthRoute(request.host.domain, bootstrap.tls), "bootstrap-health-route");
47194
47355
  await writeManagedCaddyfile(caddyfile, `import ${managedInclude}`);
47195
47356
  await atomicPublishBootstrapFile(managedInclude, `# Sporades-managed Hosted domain include list.
47196
- import ${path3.join(bootstrap.directories.caddyHosts, "*.caddy")}
47357
+ import ${path4.join(bootstrap.directories.caddyHosts, "*.caddy")}
47197
47358
  `, "bootstrap-managed-include");
47198
47359
  await atomicPublishBootstrapFile(domainInclude, `# Sporades-managed routes for ${request.host.domain}.
47199
- import ${path3.join(bootstrap.caddy.routesDirectory, "*.caddy")}
47360
+ import ${path4.join(bootstrap.caddy.routesDirectory, "*.caddy")}
47200
47361
  `, "bootstrap-domain-include");
47201
47362
  await assertActiveManagedRouteTrust(null);
47202
47363
  validateCaddyBootstrap(caddyfile);
@@ -47240,7 +47401,7 @@ ${end}
47240
47401
  let existing = "";
47241
47402
  await assertBootstrapMutationBoundary("bootstrap-caddyfile-read", [caddyfile]);
47242
47403
  try {
47243
- existing = await readFile2(caddyfile, "utf8");
47404
+ existing = await readFile3(caddyfile, "utf8");
47244
47405
  } catch (error) {
47245
47406
  if (errorDetails(error).code !== "ENOENT") {
47246
47407
  throw error;
@@ -47431,6 +47592,8 @@ async function dockerRunArgs(lifecycle, releaseId, runtimeProbe) {
47431
47592
  "--env",
47432
47593
  `SPORADES_PUBLIC_ORIGIN=${lifecycle.hostedUrl}`,
47433
47594
  "--env",
47595
+ `SPORADES_PUBLIC_ALIASES=${JSON.stringify(validateAliasDomains(lifecycle.routes.running.aliasDomains).map((hostname) => `https://${hostname}`))}`,
47596
+ "--env",
47434
47597
  `SPORADES_RELEASE_ID=${releaseId}`
47435
47598
  );
47436
47599
  args.push("--publish", `127.0.0.1::${lifecycle.routes.running.port ?? 4e3}`);
@@ -47509,7 +47672,7 @@ function ensureHostedBaseImage(lifecycle) {
47509
47672
  if (pull.ok) {
47510
47673
  return;
47511
47674
  }
47512
- const dockerfilePath = path3.join(lifecycle.remoteRoot, "Dockerfile.base");
47675
+ const dockerfilePath = path4.join(lifecycle.remoteRoot, "Dockerfile.base");
47513
47676
  if (!pathExistsSync(dockerfilePath)) {
47514
47677
  throw helperError(
47515
47678
  "Unable to prepare the Sporades Base image.",
@@ -47599,7 +47762,7 @@ async function currentReleaseId(currentLink, request) {
47599
47762
  }
47600
47763
  throw error;
47601
47764
  }
47602
- return path3.basename(target);
47765
+ return path4.basename(target);
47603
47766
  }
47604
47767
  function loopbackRunningRoute(route, publishedPort) {
47605
47768
  return {
@@ -47615,7 +47778,7 @@ async function refreshLoopbackRunningRoute(request, registryRecord, containerNam
47615
47778
  return withManagedRouteLock(routeFile, async () => {
47616
47779
  let currentRoute;
47617
47780
  try {
47618
- currentRoute = await readFile2(routeFile, "utf8");
47781
+ currentRoute = await readFile3(routeFile, "utf8");
47619
47782
  } catch (error) {
47620
47783
  if (errorDetails(error).code === "ENOENT") {
47621
47784
  return { ok: true, refreshed: false };
@@ -47642,6 +47805,17 @@ async function refreshLoopbackRunningRoute(request, registryRecord, containerNam
47642
47805
  }
47643
47806
  async function writeRunningRoute(lifecycle, route = lifecycle.routes.running) {
47644
47807
  await provisionRouteLogFile(route, "capsule-running-http-log-descriptor-mutate");
47808
+ await applyManagedRoute(lifecycle, route.routeFile, routeVariants(route).map(renderRunningRoute).join("\n"));
47809
+ }
47810
+ function routeVariants(route) {
47811
+ return [route, ...validateAliasDomains(route.aliasDomains).map((hostname) => ({
47812
+ ...route,
47813
+ hostname,
47814
+ aliasDomains: void 0,
47815
+ tls: { mode: "automatic" }
47816
+ }))];
47817
+ }
47818
+ function renderRunningRoute(route) {
47645
47819
  const cloudflareOrigin = route.tls?.mode === "cloudflare-origin";
47646
47820
  const proxyLine = [
47647
47821
  `reverse_proxy ${route.upstream ?? `${route.containerName}:${route.port ?? 4e3}`} {`,
@@ -47654,11 +47828,7 @@ async function writeRunningRoute(lifecycle, route = lifecycle.routes.running) {
47654
47828
  "respond @sporadesUntrustedCloudflareSource 403",
47655
47829
  routeHandler
47656
47830
  ].join("\n ") : routeHandler;
47657
- await applyManagedRoute(
47658
- lifecycle,
47659
- route.routeFile,
47660
- renderRoute(route, guardedHandler)
47661
- );
47831
+ return renderRoute(route, guardedHandler);
47662
47832
  }
47663
47833
  function renderRunningRouteHandler(route, proxyLine) {
47664
47834
  const probe = route.runtimeProbe;
@@ -47684,7 +47854,7 @@ async function writeUnavailableRoute(lifecycle) {
47684
47854
  await applyManagedRoute(
47685
47855
  lifecycle,
47686
47856
  route.routeFile,
47687
- renderRoute(route, renderUnavailableRouteHandler(route))
47857
+ routeVariants(route).map((variant) => renderRoute(variant, renderUnavailableRouteHandler(variant))).join("\n")
47688
47858
  );
47689
47859
  }
47690
47860
  function renderUnavailableRouteHandler(route) {
@@ -47865,7 +48035,7 @@ async function processRetainsOsFlock(lockFile) {
47865
48035
  for (const descriptorRoot of ["/proc/self/fd", "/dev/fd"]) {
47866
48036
  let entries;
47867
48037
  try {
47868
- entries = await readdir(descriptorRoot);
48038
+ entries = await readdir2(descriptorRoot);
47869
48039
  } catch {
47870
48040
  continue;
47871
48041
  }
@@ -47877,7 +48047,7 @@ async function processRetainsOsFlock(lockFile) {
47877
48047
  for (const descriptor of descriptorNumbers) {
47878
48048
  for (const descriptorRoot of ["/proc/self/fd", "/dev/fd"]) {
47879
48049
  try {
47880
- const descriptorStat = await stat(path3.join(descriptorRoot, descriptor));
48050
+ const descriptorStat = await stat(path4.join(descriptorRoot, descriptor));
47881
48051
  if (!routeLockFileIdentityMatches(descriptorStat, expected)) continue;
47882
48052
  if (process.platform === "linux") {
47883
48053
  if (descriptorStat.dev !== expected.dev) continue;
@@ -47894,7 +48064,7 @@ async function processRetainsOsFlock(lockFile) {
47894
48064
  async function linuxDescriptorOwnsFlock(descriptor, expected) {
47895
48065
  let fdinfo;
47896
48066
  try {
47897
- fdinfo = await readFile2(`/proc/self/fdinfo/${descriptor}`, "utf8");
48067
+ fdinfo = await readFile3(`/proc/self/fdinfo/${descriptor}`, "utf8");
47898
48068
  } catch {
47899
48069
  return false;
47900
48070
  }
@@ -47952,7 +48122,7 @@ function managedRouteLockTimeoutMs() {
47952
48122
  async function readManagedRouteProtocolOwner(protocolFile) {
47953
48123
  let parsed;
47954
48124
  try {
47955
- parsed = JSON.parse(await readFile2(protocolFile, "utf8"));
48125
+ parsed = JSON.parse(await readFile3(protocolFile, "utf8"));
47956
48126
  } catch (error) {
47957
48127
  if (errorDetails(error).code === "ENOENT") {
47958
48128
  return { state: "absent", owner: null };
@@ -47981,12 +48151,12 @@ function managedRouteProtocolOwnerIsLive(owner) {
47981
48151
  return currentIdentity === null || currentIdentity === owner.processIdentity;
47982
48152
  }
47983
48153
  async function cleanupManagedRouteProtocolArtifacts(lockFile) {
47984
- const directory = path3.dirname(lockFile);
47985
- const base = path3.basename(lockFile);
48154
+ const directory = path4.dirname(lockFile);
48155
+ const base = path4.basename(lockFile);
47986
48156
  const artifact = new RegExp(`^${escapeRegExp(base)}\\.(?:claim|stale|reclaim)-[a-f0-9]{32}$`);
47987
- const entries = (await readdir(directory)).filter((entry) => artifact.test(entry)).sort().slice(0, 100);
48157
+ const entries = (await readdir2(directory)).filter((entry) => artifact.test(entry)).sort().slice(0, 100);
47988
48158
  for (const entry of entries) {
47989
- const artifactPath = path3.join(directory, entry);
48159
+ const artifactPath = path4.join(directory, entry);
47990
48160
  const owner = await readManagedRouteProtocolOwner(artifactPath);
47991
48161
  if (owner.state === "owner" && !managedRouteProtocolOwnerIsLive(owner.owner)) {
47992
48162
  await rm(artifactPath, { force: true });
@@ -48019,10 +48189,10 @@ async function prepareWritableDataPath(targetPath) {
48019
48189
  }
48020
48190
  async function openCanonicalRuntimeDataDirectory(targetPath, createData) {
48021
48191
  const managedRoot = activeManagedRouteTrust?.managedRoot;
48022
- if (!managedRoot || !path3.isAbsolute(targetPath) || path3.normalize(targetPath) !== targetPath) throw runtimeDataTrustError(targetPath);
48023
- const relative = path3.relative(managedRoot, targetPath);
48024
- const components = relative.split(path3.sep).filter(Boolean);
48025
- if (relative.startsWith("..") || path3.isAbsolute(relative) || components.length !== 5 || components[0] !== "hosts" || components[2] !== "capsules" || components[4] !== "data" || !/^[a-z0-9.-]+(?::[1-9][0-9]{0,4})?$/.test(components[1]) || !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(components[3])) throw runtimeDataTrustError(targetPath);
48192
+ if (!managedRoot || !path4.isAbsolute(targetPath) || path4.normalize(targetPath) !== targetPath) throw runtimeDataTrustError(targetPath);
48193
+ const relative = path4.relative(managedRoot, targetPath);
48194
+ const components = relative.split(path4.sep).filter(Boolean);
48195
+ if (relative.startsWith("..") || path4.isAbsolute(relative) || components.length !== 5 || components[0] !== "hosts" || components[2] !== "capsules" || components[4] !== "data" || !/^[a-z0-9.-]+(?::[1-9][0-9]{0,4})?$/.test(components[1]) || !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(components[3])) throw runtimeDataTrustError(targetPath);
48026
48196
  let handle = await open(
48027
48197
  managedRoot,
48028
48198
  fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | (fsConstants.O_DIRECTORY ?? 0)
@@ -48032,7 +48202,7 @@ async function openCanonicalRuntimeDataDirectory(targetPath, createData) {
48032
48202
  let logical = managedRoot;
48033
48203
  try {
48034
48204
  for (let index = 0; index < components.length; index += 1) {
48035
- logical = path3.join(logical, components[index]);
48205
+ logical = path4.join(logical, components[index]);
48036
48206
  const descriptorPath = descriptorChildPath(handle.fd, components[index], logical);
48037
48207
  if (createData && index === components.length - 1) {
48038
48208
  await mkdir(descriptorPath, { mode: 448 }).catch((error) => {
@@ -48062,7 +48232,7 @@ async function openCanonicalRuntimeDataDirectory(targetPath, createData) {
48062
48232
  }
48063
48233
  }
48064
48234
  function descriptorChildPath(parentFd, name2, fallbackPath) {
48065
- if (name2.length === 0 || name2 === "." || name2 === ".." || name2.includes(path3.sep)) throw runtimeDataTrustError(fallbackPath);
48235
+ if (name2.length === 0 || name2 === "." || name2 === ".." || name2.includes(path4.sep)) throw runtimeDataTrustError(fallbackPath);
48066
48236
  return process.platform === "linux" ? `/proc/self/fd/${parentFd}/${name2}` : fallbackPath;
48067
48237
  }
48068
48238
  async function prepareWritableDataHandle(handle, targetPath, directory) {
@@ -48078,12 +48248,12 @@ async function prepareWritableDataHandle(handle, targetPath, directory) {
48078
48248
  await assertRuntimeDataPathIdentity(targetPath, identity, directory);
48079
48249
  if (!directory) return;
48080
48250
  const descriptorDirectory = process.platform === "linux" ? `/proc/self/fd/${handle.fd}` : targetPath;
48081
- const entries = await readdir(descriptorDirectory, { withFileTypes: true }).catch(() => {
48251
+ const entries = await readdir2(descriptorDirectory, { withFileTypes: true }).catch(() => {
48082
48252
  throw runtimeDataTrustError(targetPath);
48083
48253
  });
48084
48254
  for (const entry of entries) {
48085
- if (entry.name === "." || entry.name === ".." || entry.name.includes(path3.sep)) throw runtimeDataTrustError(targetPath);
48086
- const childPath = path3.join(targetPath, entry.name);
48255
+ if (entry.name === "." || entry.name === ".." || entry.name.includes(path4.sep)) throw runtimeDataTrustError(targetPath);
48256
+ const childPath = path4.join(targetPath, entry.name);
48087
48257
  const descriptorPath = descriptorChildPath(handle.fd, entry.name, childPath);
48088
48258
  const flags = fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | (entry.isDirectory() ? fsConstants.O_DIRECTORY ?? 0 : 0);
48089
48259
  const child = await open(descriptorPath, flags).catch(() => {
@@ -48151,7 +48321,7 @@ function validateCaddyRoute(routeFile) {
48151
48321
  }
48152
48322
  }
48153
48323
  function reloadCaddy(lifecycle) {
48154
- const configPath = path3.join(lifecycle.remoteRoot, "caddy", "Caddyfile");
48324
+ const configPath = path4.join(lifecycle.remoteRoot, "caddy", "Caddyfile");
48155
48325
  const result = spawnSync2("caddy", ["reload", "--config", configPath, "--adapter", "caddyfile"], { encoding: "utf8" });
48156
48326
  if (result.error || result.status !== 0) {
48157
48327
  throw helperError(
@@ -48467,12 +48637,12 @@ function releaseSealedServerEnvPrivateKeyMount(registryRecord, paths) {
48467
48637
  const fingerprint = release?.source?.sealedServerEnv?.publicKeyFingerprint;
48468
48638
  if (typeof fingerprint === "string" && /^[a-f0-9]{16}$/.test(fingerprint)) {
48469
48639
  return {
48470
- host: path3.join(paths.data, "sealed-server-env", "keys", `${fingerprint}.private.pem`),
48640
+ host: path4.join(paths.data, "sealed-server-env", "keys", `${fingerprint}.private.pem`),
48471
48641
  fingerprint
48472
48642
  };
48473
48643
  }
48474
48644
  return {
48475
- host: path3.join(paths.data, "sealed-server-env", "server-env.private.pem"),
48645
+ host: path4.join(paths.data, "sealed-server-env", "server-env.private.pem"),
48476
48646
  fingerprint: null
48477
48647
  };
48478
48648
  }
@@ -48484,7 +48654,7 @@ function releaseSshAuthorizedKeysMount(registryRecord, paths) {
48484
48654
  return null;
48485
48655
  }
48486
48656
  return {
48487
- host: path3.join(paths.currentLink, ".sporades", "ssh", "authorized_keys"),
48657
+ host: path4.join(paths.currentLink, ".sporades", "ssh", "authorized_keys"),
48488
48658
  container: "/run/sporades/ssh/authorized_keys",
48489
48659
  mode: "ro",
48490
48660
  optional: false
@@ -48596,7 +48766,7 @@ function compareReleasesNewestFirst(left, right) {
48596
48766
  async function readRegistryRecordForCapsule(request, purpose) {
48597
48767
  const registryRecordPath = registryPath(request);
48598
48768
  try {
48599
- return JSON.parse(await readFile2(registryRecordPath, "utf8"));
48769
+ return JSON.parse(await readFile3(registryRecordPath, "utf8"));
48600
48770
  } catch (error) {
48601
48771
  if (errorDetails(error).code === "ENOENT") {
48602
48772
  throw helperError(
@@ -48636,7 +48806,7 @@ function assertRegistryRecordMatchesRequest(request, record) {
48636
48806
  async function mutateRegistryRecord(request, mutate) {
48637
48807
  return withRegistryLock(request, async () => {
48638
48808
  const registryRecordPath = registryPath(request);
48639
- const record = JSON.parse(await readFile2(registryRecordPath, "utf8"));
48809
+ const record = JSON.parse(await readFile3(registryRecordPath, "utf8"));
48640
48810
  await writeRegistryRecordAtomic(registryRecordPath, mutate(record));
48641
48811
  });
48642
48812
  }
@@ -48688,7 +48858,7 @@ async function withRegistryLock(request, fn) {
48688
48858
  }
48689
48859
  }
48690
48860
  function registryPath(request) {
48691
- return path3.join(
48861
+ return path4.join(
48692
48862
  request.host.remoteRoot,
48693
48863
  "hosts",
48694
48864
  request.host.domain,
@@ -48698,7 +48868,7 @@ function registryPath(request) {
48698
48868
  );
48699
48869
  }
48700
48870
  function registryLockPath(request) {
48701
- return path3.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", ".lock");
48871
+ return path4.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", ".lock");
48702
48872
  }
48703
48873
  function capsuleData(request, lifecycle) {
48704
48874
  return {
@@ -48750,7 +48920,7 @@ async function readManagedCaddyAccessLog(logs) {
48750
48920
  }
48751
48921
  return null;
48752
48922
  }
48753
- const contents = await readFile2(logs.file, "utf8");
48923
+ const contents = await readFile3(logs.file, "utf8");
48754
48924
  return lastLogEntries(contents, logs.lines);
48755
48925
  }
48756
48926
  function readCaddyJournalLogs(logs) {
@@ -48800,14 +48970,14 @@ function createHostedContainerName(domain, subname) {
48800
48970
  return `sporades-${domain.replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase()}-${subname}`;
48801
48971
  }
48802
48972
  function defaultCaddyAccessLogPath(remoteRoot) {
48803
- return path3.join(remoteRoot, "caddy", "logs", "access.log");
48973
+ return path4.join(remoteRoot, "caddy", "logs", "access.log");
48804
48974
  }
48805
48975
  function defaultCapsuleHttpLogPath(remoteRoot, domain, subname) {
48806
- return path3.join(remoteRoot, "hosts", domain, "capsules", subname, "logs", "http.log");
48976
+ return path4.join(remoteRoot, "hosts", domain, "capsules", subname, "logs", "http.log");
48807
48977
  }
48808
48978
  function canonicalCapsuleHttpLogPath(request, validatedRemoteRoot = validateCanonicalHostRouteRoot(request)) {
48809
48979
  canonicalManagedRouteFile(request, validatedRemoteRoot);
48810
- const expectedDirectory = path3.resolve(
48980
+ const expectedDirectory = path4.resolve(
48811
48981
  validatedRemoteRoot,
48812
48982
  "hosts",
48813
48983
  request.host.domain,
@@ -48815,8 +48985,8 @@ function canonicalCapsuleHttpLogPath(request, validatedRemoteRoot = validateCano
48815
48985
  request.capsule.subname,
48816
48986
  "logs"
48817
48987
  );
48818
- const expected = path3.resolve(expectedDirectory, "http.log");
48819
- if (path3.dirname(expected) !== expectedDirectory) throw invalidCapsuleHttpLogPathError();
48988
+ const expected = path4.resolve(expectedDirectory, "http.log");
48989
+ if (path4.dirname(expected) !== expectedDirectory) throw invalidCapsuleHttpLogPathError();
48820
48990
  const lifecycle = request.lifecycle;
48821
48991
  const supplied = [
48822
48992
  request.registration?.route?.log?.file,
@@ -48831,7 +49001,7 @@ function canonicalCapsuleHttpLogPath(request, validatedRemoteRoot = validateCano
48831
49001
  function capsuleHttpLogTrustManifest(request, validatedRemoteRoot) {
48832
49002
  const logFile = canonicalCapsuleHttpLogPath(request, validatedRemoteRoot);
48833
49003
  return {
48834
- directories: [{ path: path3.dirname(logFile), caddyOwned: true }],
49004
+ directories: [{ path: path4.dirname(logFile), caddyOwned: true }],
48835
49005
  finalFiles: [{ path: logFile, caddyOwned: true }]
48836
49006
  };
48837
49007
  }
@@ -48898,7 +49068,7 @@ async function recordedReleaseFileClaims(releaseDirectory, recordedRelease) {
48898
49068
  const normalized = file.normalize("NFC");
48899
49069
  if (canonical.has(normalized)) throw helperError("Hosted Capsule release inventory is invalid.", "Choose another recorded release or push a replacement.");
48900
49070
  canonical.add(normalized);
48901
- const stats = await lstat(path3.join(releaseDirectory, ...file.split("/")));
49071
+ const stats = await lstat(path4.join(releaseDirectory, ...file.split("/")));
48902
49072
  if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1) {
48903
49073
  throw helperError("Hosted Capsule release files are missing.", "Choose another complete immutable release.");
48904
49074
  }
@@ -48909,12 +49079,12 @@ async function recordedReleaseFileClaims(releaseDirectory, recordedRelease) {
48909
49079
  async function deriveReleaseFileClaims(root) {
48910
49080
  const claims = [];
48911
49081
  async function visit(directory, prefix = "") {
48912
- for (const entry of await readdir(directory, { withFileTypes: true })) {
49082
+ for (const entry of await readdir2(directory, { withFileTypes: true })) {
48913
49083
  const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
48914
49084
  if (!safeRecordedReleasePath(relative)) {
48915
49085
  throw helperError("Hosted Capsule release inventory is invalid.", "Choose another recorded release or push a replacement.");
48916
49086
  }
48917
- const entryPath = path3.join(directory, entry.name);
49087
+ const entryPath = path4.join(directory, entry.name);
48918
49088
  const stats = await lstat(entryPath);
48919
49089
  if (stats.isSymbolicLink()) {
48920
49090
  throw helperError("Hosted Capsule release inventory is invalid.", "Choose another recorded release or push a replacement.");
@@ -48938,7 +49108,7 @@ function validRecordedReleaseIdentity(file) {
48938
49108
  return file && safeRecordedReleasePath(file.path) && Number.isSafeInteger(file.size) && file.size >= 0 && typeof file.sha256 === "string" && /^[a-f0-9]{64}$/.test(file.sha256);
48939
49109
  }
48940
49110
  function safeRecordedReleasePath(file) {
48941
- return file.length > 0 && !file.startsWith("/") && !file.includes("\\") && !file.includes("\0") && path3.posix.normalize(file) === file && Buffer.byteLength(file, "utf8") <= HOST_RELEASE_ARCHIVE_LIMITS.pathBytes && file.split("/").every((segment) => segment && segment !== "." && segment !== "..");
49111
+ return file.length > 0 && !file.startsWith("/") && !file.includes("\\") && !file.includes("\0") && path4.posix.normalize(file) === file && Buffer.byteLength(file, "utf8") <= HOST_RELEASE_ARCHIVE_LIMITS.pathBytes && file.split("/").every((segment) => segment && segment !== "." && segment !== "..");
48942
49112
  }
48943
49113
  async function verifyRegisteredCapsule(request, purpose = "push") {
48944
49114
  const record = await readRegistryRecordForCapsule(request, purpose);