sporades 0.9.21 → 0.9.23

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 (67) hide show
  1. package/CHANGES.md +13 -4
  2. package/README.md +11 -0
  3. package/bin/sporades-host-helper.js +1049 -442
  4. package/bin/sporades.js +2998 -1542
  5. package/dist/bundle-pipeline.d.ts +2 -0
  6. package/dist/bundle-pipeline.d.ts.map +1 -1
  7. package/dist/bundle-pipeline.js +3 -0
  8. package/dist/bundle-pipeline.js.map +1 -1
  9. package/dist/cli/cli-help.d.ts +3 -3
  10. package/dist/cli/cli-help.d.ts.map +1 -1
  11. package/dist/cli/cli-help.js +5 -1
  12. package/dist/cli/cli-help.js.map +1 -1
  13. package/dist/cli/cli-version.d.ts +1 -1
  14. package/dist/cli/cli-version.js +1 -1
  15. package/dist/cli/host-domain-aliases.d.ts +5 -0
  16. package/dist/cli/host-domain-aliases.d.ts.map +1 -0
  17. package/dist/cli/host-domain-aliases.js +69 -0
  18. package/dist/cli/host-domain-aliases.js.map +1 -0
  19. package/dist/cli/host-helper-release-files.d.ts +1 -1
  20. package/dist/cli/host-helper-release-files.d.ts.map +1 -1
  21. package/dist/cli/host-helper-release-files.js +4 -3
  22. package/dist/cli/host-helper-release-files.js.map +1 -1
  23. package/dist/cli/host-helper-requests.d.ts +5 -2
  24. package/dist/cli/host-helper-requests.d.ts.map +1 -1
  25. package/dist/cli/host-helper-validation.d.ts.map +1 -1
  26. package/dist/cli/host-helper-validation.js +7 -1
  27. package/dist/cli/host-helper-validation.js.map +1 -1
  28. package/dist/cli/host-request-builders.d.ts +2 -1
  29. package/dist/cli/host-request-builders.d.ts.map +1 -1
  30. package/dist/cli/host-request-builders.js +4 -2
  31. package/dist/cli/host-request-builders.js.map +1 -1
  32. package/dist/cli/hosted-capsule-contract.d.ts +2 -0
  33. package/dist/cli/hosted-capsule-contract.d.ts.map +1 -1
  34. package/dist/cli/sporades-host-helper.js +280 -25
  35. package/dist/cli/sporades-host-helper.js.map +1 -1
  36. package/dist/cli/sporades.js +251 -18
  37. package/dist/cli/sporades.js.map +1 -1
  38. package/dist/database-runtime.d.ts.map +1 -1
  39. package/dist/database-runtime.js +10 -0
  40. package/dist/database-runtime.js.map +1 -1
  41. package/dist/deploy-files.d.ts +47 -0
  42. package/dist/deploy-files.d.ts.map +1 -0
  43. package/dist/deploy-files.js +399 -0
  44. package/dist/deploy-files.js.map +1 -0
  45. package/dist/file-storage-runtime.d.ts +12 -1
  46. package/dist/file-storage-runtime.d.ts.map +1 -1
  47. package/dist/file-storage-runtime.js +685 -5
  48. package/dist/file-storage-runtime.js.map +1 -1
  49. package/dist/generated-source-manifest.json +12 -2
  50. package/dist/http-runtime.d.ts +4 -1
  51. package/dist/http-runtime.d.ts.map +1 -1
  52. package/dist/http-runtime.js +43 -3
  53. package/dist/http-runtime.js.map +1 -1
  54. package/dist/mail-runtime.d.ts.map +1 -1
  55. package/dist/mail-runtime.js +6 -3
  56. package/dist/mail-runtime.js.map +1 -1
  57. package/dist/server-runtime-source.d.ts.map +1 -1
  58. package/dist/server-runtime-source.js +167 -70
  59. package/dist/server-runtime-source.js.map +1 -1
  60. package/dist/templates/client-runtime-template.d.ts.map +1 -1
  61. package/dist/templates/client-runtime-template.js +127 -5
  62. package/dist/templates/client-runtime-template.js.map +1 -1
  63. package/dist/templates/server-bundle-entry.d.ts.map +1 -1
  64. package/dist/templates/server-bundle-entry.js +12 -3
  65. package/dist/templates/server-bundle-entry.js.map +1 -1
  66. package/package.json +1 -1
  67. package/src/types/server.d.ts +27 -8
@@ -5147,9 +5147,9 @@ var require_pako = __commonJS({
5147
5147
  var assign = require_common().assign;
5148
5148
  var deflate = require_deflate2();
5149
5149
  var inflate = require_inflate2();
5150
- var constants = require_constants();
5150
+ var constants2 = require_constants();
5151
5151
  var pako = {};
5152
- assign(pako, deflate, inflate, constants);
5152
+ assign(pako, deflate, inflate, constants2);
5153
5153
  module.exports = pako;
5154
5154
  }
5155
5155
  });
@@ -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(path6) {
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 = path6; _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(path6) {
17003
+ return apply(parse5(path6));
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(path6, 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(path6), [
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(path6, 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(path6, "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(path6, {
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(path6) {
23609
23609
  var xhr = new XMLHttpRequest();
23610
- xhr.open("GET", path4, true);
23610
+ xhr.open("GET", path6, true);
23611
23611
  xhr.responseType = "arraybuffer";
23612
23612
  xhr.onload = (function() {
23613
23613
  var data2 = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
@@ -24660,7 +24660,7 @@ var require_crc = __commonJS({
24660
24660
  var require_parser = __commonJS({
24661
24661
  "node_modules/pngjs/lib/parser.js"(exports, module) {
24662
24662
  "use strict";
24663
- var constants = require_constants2();
24663
+ var constants2 = require_constants2();
24664
24664
  var CrcCalculator = require_crc();
24665
24665
  var Parser5 = module.exports = function(options, dependencies) {
24666
24666
  this._options = options;
@@ -24671,12 +24671,12 @@ var require_parser = __commonJS({
24671
24671
  this._palette = [];
24672
24672
  this._colorType = 0;
24673
24673
  this._chunks = {};
24674
- this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
24675
- this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
24676
- this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
24677
- this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
24678
- this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
24679
- this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
24674
+ this._chunks[constants2.TYPE_IHDR] = this._handleIHDR.bind(this);
24675
+ this._chunks[constants2.TYPE_IEND] = this._handleIEND.bind(this);
24676
+ this._chunks[constants2.TYPE_IDAT] = this._handleIDAT.bind(this);
24677
+ this._chunks[constants2.TYPE_PLTE] = this._handlePLTE.bind(this);
24678
+ this._chunks[constants2.TYPE_tRNS] = this._handleTRNS.bind(this);
24679
+ this._chunks[constants2.TYPE_gAMA] = this._handleGAMA.bind(this);
24680
24680
  this.read = dependencies.read;
24681
24681
  this.error = dependencies.error;
24682
24682
  this.metadata = dependencies.metadata;
@@ -24691,10 +24691,10 @@ var require_parser = __commonJS({
24691
24691
  };
24692
24692
  };
24693
24693
  Parser5.prototype.start = function() {
24694
- this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this));
24694
+ this.read(constants2.PNG_SIGNATURE.length, this._parseSignature.bind(this));
24695
24695
  };
24696
24696
  Parser5.prototype._parseSignature = function(data2) {
24697
- let signature = constants.PNG_SIGNATURE;
24697
+ let signature = constants2.PNG_SIGNATURE;
24698
24698
  for (let i = 0; i < signature.length; i++) {
24699
24699
  if (data2[i] !== signature[i]) {
24700
24700
  this.error(new Error("Invalid file signature"));
@@ -24711,7 +24711,7 @@ var require_parser = __commonJS({
24711
24711
  name2 += String.fromCharCode(data2[i]);
24712
24712
  }
24713
24713
  let ancillary = Boolean(data2[4] & 32);
24714
- if (!this._hasIHDR && type !== constants.TYPE_IHDR) {
24714
+ if (!this._hasIHDR && type !== constants2.TYPE_IHDR) {
24715
24715
  this.error(new Error("Expected IHDR on beggining"));
24716
24716
  return;
24717
24717
  }
@@ -24759,7 +24759,7 @@ var require_parser = __commonJS({
24759
24759
  this.error(new Error("Unsupported bit depth " + depth));
24760
24760
  return;
24761
24761
  }
24762
- if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) {
24762
+ if (!(colorType in constants2.COLORTYPE_TO_BPP_MAP)) {
24763
24763
  this.error(new Error("Unsupported color type"));
24764
24764
  return;
24765
24765
  }
@@ -24776,16 +24776,16 @@ var require_parser = __commonJS({
24776
24776
  return;
24777
24777
  }
24778
24778
  this._colorType = colorType;
24779
- let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];
24779
+ let bpp = constants2.COLORTYPE_TO_BPP_MAP[this._colorType];
24780
24780
  this._hasIHDR = true;
24781
24781
  this.metadata({
24782
24782
  width,
24783
24783
  height,
24784
24784
  depth,
24785
24785
  interlace: Boolean(interlace),
24786
- palette: Boolean(colorType & constants.COLORTYPE_PALETTE),
24787
- color: Boolean(colorType & constants.COLORTYPE_COLOR),
24788
- alpha: Boolean(colorType & constants.COLORTYPE_ALPHA),
24786
+ palette: Boolean(colorType & constants2.COLORTYPE_PALETTE),
24787
+ color: Boolean(colorType & constants2.COLORTYPE_COLOR),
24788
+ alpha: Boolean(colorType & constants2.COLORTYPE_ALPHA),
24789
24789
  bpp,
24790
24790
  colorType
24791
24791
  });
@@ -24809,7 +24809,7 @@ var require_parser = __commonJS({
24809
24809
  };
24810
24810
  Parser5.prototype._parseTRNS = function(data2) {
24811
24811
  this._crc.write(data2);
24812
- if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) {
24812
+ if (this._colorType === constants2.COLORTYPE_PALETTE_COLOR) {
24813
24813
  if (this._palette.length === 0) {
24814
24814
  this.error(new Error("Transparency chunk must be after palette"));
24815
24815
  return;
@@ -24823,10 +24823,10 @@ var require_parser = __commonJS({
24823
24823
  }
24824
24824
  this.palette(this._palette);
24825
24825
  }
24826
- if (this._colorType === constants.COLORTYPE_GRAYSCALE) {
24826
+ if (this._colorType === constants2.COLORTYPE_GRAYSCALE) {
24827
24827
  this.transColor([data2.readUInt16BE(0)]);
24828
24828
  }
24829
- if (this._colorType === constants.COLORTYPE_COLOR) {
24829
+ if (this._colorType === constants2.COLORTYPE_COLOR) {
24830
24830
  this.transColor([
24831
24831
  data2.readUInt16BE(0),
24832
24832
  data2.readUInt16BE(2),
@@ -24840,7 +24840,7 @@ var require_parser = __commonJS({
24840
24840
  };
24841
24841
  Parser5.prototype._parseGAMA = function(data2) {
24842
24842
  this._crc.write(data2);
24843
- this.gamma(data2.readUInt32BE(0) / constants.GAMMA_DIVISION);
24843
+ this.gamma(data2.readUInt32BE(0) / constants2.GAMMA_DIVISION);
24844
24844
  this._handleChunkEnd();
24845
24845
  };
24846
24846
  Parser5.prototype._handleIDAT = function(length) {
@@ -24852,7 +24852,7 @@ var require_parser = __commonJS({
24852
24852
  };
24853
24853
  Parser5.prototype._parseIDAT = function(length, data2) {
24854
24854
  this._crc.write(data2);
24855
- if (this._colorType === constants.COLORTYPE_PALETTE_COLOR && this._palette.length === 0) {
24855
+ if (this._colorType === constants2.COLORTYPE_PALETTE_COLOR && this._palette.length === 0) {
24856
24856
  throw new Error("Expected palette not found");
24857
24857
  }
24858
24858
  this.inflateData(data2);
@@ -25340,9 +25340,9 @@ var require_parser_async = __commonJS({
25340
25340
  var require_bitpacker = __commonJS({
25341
25341
  "node_modules/pngjs/lib/bitpacker.js"(exports, module) {
25342
25342
  "use strict";
25343
- var constants = require_constants2();
25343
+ var constants2 = require_constants2();
25344
25344
  module.exports = function(dataIn, width, height, options) {
25345
- let outHasAlpha = [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf(
25345
+ let outHasAlpha = [constants2.COLORTYPE_COLOR_ALPHA, constants2.COLORTYPE_ALPHA].indexOf(
25346
25346
  options.colorType
25347
25347
  ) !== -1;
25348
25348
  if (options.colorType === options.inputColorType) {
@@ -25362,11 +25362,11 @@ var require_bitpacker = __commonJS({
25362
25362
  }
25363
25363
  let data2 = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
25364
25364
  let maxValue = 255;
25365
- let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType];
25365
+ let inBpp = constants2.COLORTYPE_TO_BPP_MAP[options.inputColorType];
25366
25366
  if (inBpp === 4 && !options.inputHasAlpha) {
25367
25367
  inBpp = 3;
25368
25368
  }
25369
- let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType];
25369
+ let outBpp = constants2.COLORTYPE_TO_BPP_MAP[options.colorType];
25370
25370
  if (options.bitDepth === 16) {
25371
25371
  maxValue = 65535;
25372
25372
  outBpp *= 2;
@@ -25390,24 +25390,24 @@ var require_bitpacker = __commonJS({
25390
25390
  let blue;
25391
25391
  let alpha = maxValue;
25392
25392
  switch (options.inputColorType) {
25393
- case constants.COLORTYPE_COLOR_ALPHA:
25393
+ case constants2.COLORTYPE_COLOR_ALPHA:
25394
25394
  alpha = data2[inIndex + 3];
25395
25395
  red = data2[inIndex];
25396
25396
  green = data2[inIndex + 1];
25397
25397
  blue = data2[inIndex + 2];
25398
25398
  break;
25399
- case constants.COLORTYPE_COLOR:
25399
+ case constants2.COLORTYPE_COLOR:
25400
25400
  red = data2[inIndex];
25401
25401
  green = data2[inIndex + 1];
25402
25402
  blue = data2[inIndex + 2];
25403
25403
  break;
25404
- case constants.COLORTYPE_ALPHA:
25404
+ case constants2.COLORTYPE_ALPHA:
25405
25405
  alpha = data2[inIndex + 1];
25406
25406
  red = data2[inIndex];
25407
25407
  green = red;
25408
25408
  blue = red;
25409
25409
  break;
25410
- case constants.COLORTYPE_GRAYSCALE:
25410
+ case constants2.COLORTYPE_GRAYSCALE:
25411
25411
  red = data2[inIndex];
25412
25412
  green = red;
25413
25413
  blue = red;
@@ -25440,8 +25440,8 @@ var require_bitpacker = __commonJS({
25440
25440
  for (let x = 0; x < width; x++) {
25441
25441
  let rgba = getRGBA(data2, inIndex);
25442
25442
  switch (options.colorType) {
25443
- case constants.COLORTYPE_COLOR_ALPHA:
25444
- case constants.COLORTYPE_COLOR:
25443
+ case constants2.COLORTYPE_COLOR_ALPHA:
25444
+ case constants2.COLORTYPE_COLOR:
25445
25445
  if (options.bitDepth === 8) {
25446
25446
  outData[outIndex] = rgba.red;
25447
25447
  outData[outIndex + 1] = rgba.green;
@@ -25458,8 +25458,8 @@ var require_bitpacker = __commonJS({
25458
25458
  }
25459
25459
  }
25460
25460
  break;
25461
- case constants.COLORTYPE_ALPHA:
25462
- case constants.COLORTYPE_GRAYSCALE: {
25461
+ case constants2.COLORTYPE_ALPHA:
25462
+ case constants2.COLORTYPE_GRAYSCALE: {
25463
25463
  let grayscale = (rgba.red + rgba.green + rgba.blue) / 3;
25464
25464
  if (options.bitDepth === 8) {
25465
25465
  outData[outIndex] = grayscale;
@@ -25632,7 +25632,7 @@ var require_filter_pack = __commonJS({
25632
25632
  var require_packer = __commonJS({
25633
25633
  "node_modules/pngjs/lib/packer.js"(exports, module) {
25634
25634
  "use strict";
25635
- var constants = require_constants2();
25635
+ var constants2 = require_constants2();
25636
25636
  var CrcStream = require_crc();
25637
25637
  var bitPacker = require_bitpacker();
25638
25638
  var filter = require_filter_pack();
@@ -25645,23 +25645,23 @@ var require_packer = __commonJS({
25645
25645
  options.inputHasAlpha = options.inputHasAlpha != null ? options.inputHasAlpha : true;
25646
25646
  options.deflateFactory = options.deflateFactory || zlib2.createDeflate;
25647
25647
  options.bitDepth = options.bitDepth || 8;
25648
- options.colorType = typeof options.colorType === "number" ? options.colorType : constants.COLORTYPE_COLOR_ALPHA;
25649
- options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType : constants.COLORTYPE_COLOR_ALPHA;
25648
+ options.colorType = typeof options.colorType === "number" ? options.colorType : constants2.COLORTYPE_COLOR_ALPHA;
25649
+ options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType : constants2.COLORTYPE_COLOR_ALPHA;
25650
25650
  if ([
25651
- constants.COLORTYPE_GRAYSCALE,
25652
- constants.COLORTYPE_COLOR,
25653
- constants.COLORTYPE_COLOR_ALPHA,
25654
- constants.COLORTYPE_ALPHA
25651
+ constants2.COLORTYPE_GRAYSCALE,
25652
+ constants2.COLORTYPE_COLOR,
25653
+ constants2.COLORTYPE_COLOR_ALPHA,
25654
+ constants2.COLORTYPE_ALPHA
25655
25655
  ].indexOf(options.colorType) === -1) {
25656
25656
  throw new Error(
25657
25657
  "option color type:" + options.colorType + " is not supported at present"
25658
25658
  );
25659
25659
  }
25660
25660
  if ([
25661
- constants.COLORTYPE_GRAYSCALE,
25662
- constants.COLORTYPE_COLOR,
25663
- constants.COLORTYPE_COLOR_ALPHA,
25664
- constants.COLORTYPE_ALPHA
25661
+ constants2.COLORTYPE_GRAYSCALE,
25662
+ constants2.COLORTYPE_COLOR,
25663
+ constants2.COLORTYPE_COLOR_ALPHA,
25664
+ constants2.COLORTYPE_ALPHA
25665
25665
  ].indexOf(options.inputColorType) === -1) {
25666
25666
  throw new Error(
25667
25667
  "option input color type:" + options.inputColorType + " is not supported at present"
@@ -25685,7 +25685,7 @@ var require_packer = __commonJS({
25685
25685
  };
25686
25686
  Packer.prototype.filterData = function(data2, width, height) {
25687
25687
  let packedData = bitPacker(data2, width, height, this._options);
25688
- let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType];
25688
+ let bpp = constants2.COLORTYPE_TO_BPP_MAP[this._options.colorType];
25689
25689
  let filteredData = filter(packedData, width, height, this._options, bpp);
25690
25690
  return filteredData;
25691
25691
  };
@@ -25705,8 +25705,8 @@ var require_packer = __commonJS({
25705
25705
  };
25706
25706
  Packer.prototype.packGAMA = function(gamma) {
25707
25707
  let buf = Buffer.alloc(4);
25708
- buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0);
25709
- return this._packChunk(constants.TYPE_gAMA, buf);
25708
+ buf.writeUInt32BE(Math.floor(gamma * constants2.GAMMA_DIVISION), 0);
25709
+ return this._packChunk(constants2.TYPE_gAMA, buf);
25710
25710
  };
25711
25711
  Packer.prototype.packIHDR = function(width, height) {
25712
25712
  let buf = Buffer.alloc(13);
@@ -25717,13 +25717,13 @@ var require_packer = __commonJS({
25717
25717
  buf[10] = 0;
25718
25718
  buf[11] = 0;
25719
25719
  buf[12] = 0;
25720
- return this._packChunk(constants.TYPE_IHDR, buf);
25720
+ return this._packChunk(constants2.TYPE_IHDR, buf);
25721
25721
  };
25722
25722
  Packer.prototype.packIDAT = function(data2) {
25723
- return this._packChunk(constants.TYPE_IDAT, data2);
25723
+ return this._packChunk(constants2.TYPE_IDAT, data2);
25724
25724
  };
25725
25725
  Packer.prototype.packIEND = function() {
25726
- return this._packChunk(constants.TYPE_IEND, null);
25726
+ return this._packChunk(constants2.TYPE_IEND, null);
25727
25727
  };
25728
25728
  }
25729
25729
  });
@@ -25734,7 +25734,7 @@ var require_packer_async = __commonJS({
25734
25734
  "use strict";
25735
25735
  var util = __require("util");
25736
25736
  var Stream = __require("stream");
25737
- var constants = require_constants2();
25737
+ var constants2 = require_constants2();
25738
25738
  var Packer = require_packer();
25739
25739
  var PackerAsync = module.exports = function(opt) {
25740
25740
  Stream.call(this);
@@ -25745,7 +25745,7 @@ var require_packer_async = __commonJS({
25745
25745
  };
25746
25746
  util.inherits(PackerAsync, Stream);
25747
25747
  PackerAsync.prototype.pack = function(data2, width, height, gamma) {
25748
- this.emit("data", Buffer.from(constants.PNG_SIGNATURE));
25748
+ this.emit("data", Buffer.from(constants2.PNG_SIGNATURE));
25749
25749
  this.emit("data", this._packer.packIHDR(width, height));
25750
25750
  if (gamma) {
25751
25751
  this.emit("data", this._packer.packGAMA(gamma));
@@ -26073,7 +26073,7 @@ var require_packer_sync = __commonJS({
26073
26073
  if (!zlib2.deflateSync) {
26074
26074
  hasSyncZlib = false;
26075
26075
  }
26076
- var constants = require_constants2();
26076
+ var constants2 = require_constants2();
26077
26077
  var Packer = require_packer();
26078
26078
  module.exports = function(metaData, opt) {
26079
26079
  if (!hasSyncZlib) {
@@ -26084,7 +26084,7 @@ var require_packer_sync = __commonJS({
26084
26084
  let options = opt || {};
26085
26085
  let packer = new Packer(options);
26086
26086
  let chunks = [];
26087
- chunks.push(Buffer.from(constants.PNG_SIGNATURE));
26087
+ chunks.push(Buffer.from(constants2.PNG_SIGNATURE));
26088
26088
  chunks.push(packer.packIHDR(metaData.width, metaData.height));
26089
26089
  if (metaData.gamma) {
26090
26090
  chunks.push(packer.packGAMA(metaData.gamma));
@@ -26261,13 +26261,421 @@ var require_png2 = __commonJS({
26261
26261
  }
26262
26262
  });
26263
26263
 
26264
+ // src/deploy-files.ts
26265
+ import path from "node:path";
26266
+ import { createHash, randomUUID } from "node:crypto";
26267
+ import { constants } from "node:fs";
26268
+ import { lstat, mkdir, open, readFile, readdir, link, rename, rm, realpath } from "node:fs/promises";
26269
+ var RESERVED = [".sporades", "public", "data", "server.mjs", "client.js", "index.html", "sporades.json", ".env.sporades.server"];
26270
+ function resolveDeployFiles(value) {
26271
+ if (value === void 0) return [];
26272
+ if (!Array.isArray(value)) throw new Error("deploy.files must be an array.");
26273
+ const root = path.resolve("/app");
26274
+ const files = value.map((entry) => {
26275
+ if (!entry || typeof entry !== "object" || typeof entry.path !== "string" || !entry.path || path.isAbsolute(entry.path) || /[\\\x00-\x1f:]/.test(entry.path)) {
26276
+ throw new Error("Invalid deploy.files path: use a relative file path under the app root.");
26277
+ }
26278
+ const resolved = path.resolve(root, entry.path);
26279
+ const relative = path.relative(root, resolved);
26280
+ if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
26281
+ throw new Error(`deploy.files path escapes the app root: ${entry.path}`);
26282
+ }
26283
+ const reservedCandidate = resolved.toLowerCase();
26284
+ for (const reserved of RESERVED) {
26285
+ const target = path.resolve(root, reserved);
26286
+ if (reservedCandidate === target || reservedCandidate.startsWith(`${target}${path.sep}`) || target.startsWith(`${reservedCandidate}${path.sep}`)) {
26287
+ throw new Error(`deploy.files path collides with Sporades-managed files: ${entry.path}`);
26288
+ }
26289
+ }
26290
+ const normalized = relative.split(path.sep).join("/");
26291
+ if (normalized.split("/").some((part) => part.startsWith("-") || part.startsWith("._") || part === "__MACOSX")) {
26292
+ throw new Error(`Unsupported deploy.files path: ${entry.path}`);
26293
+ }
26294
+ const update = entry.update === void 0 ? "replace" : entry.update;
26295
+ if (update !== "replace" && update !== "preserve") throw new Error(`Invalid deploy.files update for ${entry.path}: use replace or preserve.`);
26296
+ return { path: normalized, update };
26297
+ });
26298
+ const seen = [];
26299
+ for (const file of files) {
26300
+ const name2 = file.path.normalize("NFC");
26301
+ if (seen.some((other) => name2 === other || name2.startsWith(`${other}/`) || other.startsWith(`${name2}/`))) {
26302
+ throw new Error(`Conflicting deploy.files paths: ${file.path}`);
26303
+ }
26304
+ seen.push(name2);
26305
+ }
26306
+ return files;
26307
+ }
26308
+ async function assertDeployFile(root, relative, recoverSeed = false) {
26309
+ const rootInfo = await lstat(root);
26310
+ if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error(`Unsafe deploy.files root: ${root}`);
26311
+ let current2 = root;
26312
+ const parts = relative.split("/");
26313
+ for (let index = 0; index < parts.length; index++) {
26314
+ current2 = path.join(current2, parts[index]);
26315
+ let info = await lstat(current2);
26316
+ if (recoverSeed && index === parts.length - 1 && info.isFile() && info.nlink === 2) {
26317
+ for (const entry of await readdir(path.dirname(current2))) {
26318
+ if (!/^\.(?:seed|rollback)-[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(entry)) continue;
26319
+ const seed = path.join(path.dirname(current2), entry);
26320
+ const candidate = await lstat(seed).catch((error) => {
26321
+ if (error.code !== "ENOENT") throw error;
26322
+ return null;
26323
+ });
26324
+ if (candidate?.isFile() && candidate.dev === info.dev && candidate.ino === info.ino) {
26325
+ await rm(seed, { force: true });
26326
+ info = await lstat(current2);
26327
+ break;
26328
+ }
26329
+ }
26330
+ }
26331
+ if (info.isSymbolicLink() || (index < parts.length - 1 ? !info.isDirectory() : !info.isFile() || recoverSeed && info.nlink !== 1)) {
26332
+ throw new Error(`deploy.files requires regular files without symlinks: ${relative}`);
26333
+ }
26334
+ }
26335
+ return current2;
26336
+ }
26337
+ function preservedDeployFilePath(root, relative) {
26338
+ const key = createHash("sha256").update(relative.normalize("NFC")).digest("hex");
26339
+ return path.join(root, `${key}.file`);
26340
+ }
26341
+ async function assertPreservedDeployFile(root, relative) {
26342
+ return assertDeployFile(root, path.basename(preservedDeployFilePath(root, relative)), true);
26343
+ }
26344
+ async function readDeployFile(root, relative) {
26345
+ root = path.resolve(root);
26346
+ const rootHandle = await open(root, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
26347
+ const handles = [rootHandle];
26348
+ try {
26349
+ const identity = await rootHandle.stat();
26350
+ await assertDeployFile(root, relative);
26351
+ const rootNow = await lstat(root);
26352
+ if (rootNow.isSymbolicLink() || rootNow.dev !== identity.dev || rootNow.ino !== identity.ino) throw new Error("deploy.files project root changed during the build.");
26353
+ let file;
26354
+ let checkRoot;
26355
+ if (process.platform === "darwin") {
26356
+ const canonicalRoot = path.join(await realpath(path.dirname(root)), path.basename(root));
26357
+ checkRoot = async () => {
26358
+ const current2 = await lstat(canonicalRoot);
26359
+ if (current2.isSymbolicLink() || current2.dev !== identity.dev || current2.ino !== identity.ino) throw new Error("deploy.files project root changed during the build.");
26360
+ };
26361
+ await checkRoot();
26362
+ const O_NOFOLLOW_ANY = 536870912;
26363
+ file = await open(path.join(canonicalRoot, relative), constants.O_RDONLY | constants.O_NONBLOCK | O_NOFOLLOW_ANY);
26364
+ } else if (process.platform === "linux") {
26365
+ let directory = rootHandle;
26366
+ const parts = relative.split("/");
26367
+ for (const part of parts.slice(0, -1)) {
26368
+ directory = await open(`/proc/self/fd/${directory.fd}/${part}`, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
26369
+ handles.push(directory);
26370
+ }
26371
+ file = await open(`/proc/self/fd/${directory.fd}/${parts.at(-1)}`, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
26372
+ } else {
26373
+ const target = path.join(root, relative);
26374
+ file = await open(target, constants.O_RDONLY | constants.O_NONBLOCK);
26375
+ handles.push(file);
26376
+ const opened = await file.stat();
26377
+ await assertDeployFile(root, relative);
26378
+ const named = await lstat(target);
26379
+ if (!opened.isFile() || named.dev !== opened.dev || named.ino !== opened.ino) throw new Error(`deploy.files source changed during the build: ${relative}`);
26380
+ return await file.readFile();
26381
+ }
26382
+ handles.push(file);
26383
+ await checkRoot?.();
26384
+ if (!(await file.stat()).isFile()) throw new Error(`deploy.files requires a regular file: ${relative}`);
26385
+ return await file.readFile();
26386
+ } finally {
26387
+ for (const handle of handles.reverse()) await handle.close();
26388
+ }
26389
+ }
26390
+ function deployFileMounts(files, releaseRoot, preservedRoot) {
26391
+ return files.map((file) => ({
26392
+ host: file.update === "preserve" ? preservedDeployFilePath(preservedRoot, file.path) : path.join(releaseRoot, file.path),
26393
+ container: `/app/${file.path}`,
26394
+ mode: file.update === "preserve" ? "rw" : "ro"
26395
+ }));
26396
+ }
26397
+ function attemptJournalPath(preservedRoot) {
26398
+ return path.join(path.dirname(preservedRoot), "deploy-file-attempt.jsonl");
26399
+ }
26400
+ function interruptedAttemptError(journal) {
26401
+ return new Error(`Interrupted deploy.files attempt requires recovery: ${journal}. Run the matching reconcile command before retrying.`);
26402
+ }
26403
+ async function assertNoPreservedFileAttempt(preservedRoot) {
26404
+ const journal = attemptJournalPath(preservedRoot);
26405
+ try {
26406
+ await lstat(journal);
26407
+ } catch (error) {
26408
+ if (error.code === "ENOENT") return;
26409
+ throw error;
26410
+ }
26411
+ throw interruptedAttemptError(journal);
26412
+ }
26413
+ async function beginPreservedFileAttempt(preservedRoot, release, needed) {
26414
+ if (!needed) {
26415
+ await assertNoPreservedFileAttempt(preservedRoot);
26416
+ return void 0;
26417
+ }
26418
+ const journal = attemptJournalPath(preservedRoot);
26419
+ let handle;
26420
+ try {
26421
+ handle = await open(journal, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
26422
+ } catch (error) {
26423
+ if (error.code === "EEXIST") throw interruptedAttemptError(journal);
26424
+ throw error;
26425
+ }
26426
+ try {
26427
+ await handle.writeFile(JSON.stringify({ release, preservedRoot }) + "\n");
26428
+ await handle.sync();
26429
+ } finally {
26430
+ await handle.close();
26431
+ }
26432
+ return journal;
26433
+ }
26434
+ async function readPreservedFileAttempt(journal) {
26435
+ const handle = await open(journal, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch((error) => {
26436
+ if (error.code !== "ENOENT") throw error;
26437
+ return null;
26438
+ });
26439
+ if (!handle) return null;
26440
+ let records;
26441
+ try {
26442
+ if (!(await handle.stat()).isFile()) throw new Error("Unsafe deploy.files journal.");
26443
+ const contents = (await handle.readFile("utf8")).trim();
26444
+ records = contents ? contents.split("\n").map((line) => JSON.parse(line)) : [];
26445
+ } finally {
26446
+ await handle.close();
26447
+ }
26448
+ const header = records[0] ?? {};
26449
+ const release = typeof header.release === "string" ? header.release : "";
26450
+ const preservedRoot = typeof header.preservedRoot === "string" ? header.preservedRoot : path.join(path.dirname(journal), "preserved-files");
26451
+ const seeds = records.filter((record) => typeof record.root === "string" && typeof record.path === "string" && typeof record.dev === "number" && typeof record.ino === "number" && typeof record.sha256 === "string");
26452
+ const temporaries = records.map((record) => record.temporary).filter((value) => typeof value === "string");
26453
+ return { journal, release, preservedRoot, seeds: seeds.map((seed) => ({ ...seed })), temporaries, records };
26454
+ }
26455
+ async function recordPreservedFileAttempt(journal, entry) {
26456
+ if (!journal) return;
26457
+ const handle = await open(journal, constants.O_WRONLY | constants.O_APPEND | constants.O_NOFOLLOW);
26458
+ try {
26459
+ await handle.writeFile(JSON.stringify(entry) + "\n");
26460
+ await handle.sync();
26461
+ } finally {
26462
+ await handle.close();
26463
+ }
26464
+ }
26465
+ async function finishPreservedFileAttempt(journal) {
26466
+ if (!journal) return;
26467
+ const attempt = await readPreservedFileAttempt(journal);
26468
+ if (!attempt) return;
26469
+ const root = path.join(path.dirname(journal), "preserved-files");
26470
+ for (const relative of attempt.temporaries) {
26471
+ const resolved = path.resolve(root, relative);
26472
+ if (path.isAbsolute(relative) || relative === ".." || relative.startsWith(`..${path.sep}`) || path.relative(root, resolved) !== relative || !/^\.seed-[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(path.basename(relative))) {
26473
+ throw new Error("Unsafe temporary seed path in deploy.files journal.");
26474
+ }
26475
+ try {
26476
+ await rm(await assertDeployFile(root, relative), { force: true });
26477
+ } catch (error) {
26478
+ if (error.code !== "ENOENT") throw error;
26479
+ }
26480
+ }
26481
+ await rm(journal, { force: true });
26482
+ }
26483
+ async function preparePreservedFiles(files, releaseRoot, preservedRoot, owner, created = [], journal) {
26484
+ for (const file of files.filter((entry) => entry.update === "preserve")) {
26485
+ await mkdir(preservedRoot, { mode: 448 }).catch((error) => {
26486
+ if (error.code !== "EEXIST") throw error;
26487
+ });
26488
+ const directory = await lstat(preservedRoot);
26489
+ if (!directory.isDirectory() || directory.isSymbolicLink()) throw new Error(`Unsafe preserved deploy.files directory: ${file.path}`);
26490
+ const rootHandle = await open(preservedRoot, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
26491
+ try {
26492
+ await rootHandle.chmod(448);
26493
+ } finally {
26494
+ await rootHandle.close();
26495
+ }
26496
+ const destination = preservedDeployFilePath(preservedRoot, file.path);
26497
+ try {
26498
+ await assertPreservedDeployFile(preservedRoot, file.path);
26499
+ continue;
26500
+ } catch (error) {
26501
+ if (error.code !== "ENOENT") throw error;
26502
+ }
26503
+ let handle;
26504
+ const temporary = path.join(path.dirname(destination), `.seed-${randomUUID()}`);
26505
+ try {
26506
+ await recordPreservedFileAttempt(journal, { temporary: path.relative(preservedRoot, temporary) });
26507
+ const contents = await readDeployFile(releaseRoot, file.path);
26508
+ handle = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
26509
+ await handle.writeFile(contents);
26510
+ if (owner) await owner(handle, destination, await handle.stat());
26511
+ const identity = await handle.stat();
26512
+ const seed = { root: preservedRoot, path: file.path, storagePath: destination, dev: identity.dev, ino: identity.ino, sha256: createHash("sha256").update(contents).digest("hex") };
26513
+ await recordPreservedFileAttempt(journal, seed);
26514
+ await link(temporary, destination);
26515
+ created.push(seed);
26516
+ } catch (error) {
26517
+ if (error.code !== "EEXIST") throw error;
26518
+ } finally {
26519
+ await handle?.close();
26520
+ await rm(temporary, { force: true });
26521
+ }
26522
+ await assertPreservedDeployFile(preservedRoot, file.path);
26523
+ }
26524
+ }
26525
+ async function rollbackPreservedFiles(created, hooks = {}) {
26526
+ for (const seed of [...created].reverse()) {
26527
+ let handle;
26528
+ try {
26529
+ const target = await assertPreservedDeployFile(seed.root, seed.path);
26530
+ handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
26531
+ const info = await handle.stat();
26532
+ if (info.dev !== seed.dev || info.ino !== seed.ino || info.nlink !== 1) continue;
26533
+ if (createHash("sha256").update(await handle.readFile()).digest("hex") !== seed.sha256) continue;
26534
+ await hooks.beforeClaim?.(target);
26535
+ const claimed = path.join(seed.root, `.rollback-${randomUUID()}`);
26536
+ await rename(target, claimed);
26537
+ const captured = await lstat(claimed);
26538
+ const sameSeed = captured.isFile() && captured.dev === info.dev && captured.ino === info.ino && createHash("sha256").update(await readFile(claimed)).digest("hex") === seed.sha256;
26539
+ if (!sameSeed) {
26540
+ try {
26541
+ await link(claimed, target);
26542
+ await rm(claimed);
26543
+ } catch (error) {
26544
+ if (error.code !== "EEXIST") throw error;
26545
+ }
26546
+ }
26547
+ } catch (error) {
26548
+ if (error.code !== "ENOENT") throw error;
26549
+ } finally {
26550
+ await handle?.close();
26551
+ }
26552
+ }
26553
+ }
26554
+ async function rethrowAfterDeployCleanup(error, cleanups) {
26555
+ const failures = [];
26556
+ for (const cleanup of cleanups) {
26557
+ try {
26558
+ await cleanup();
26559
+ } catch (failure) {
26560
+ failures.push(failure);
26561
+ }
26562
+ }
26563
+ if (failures.length) throw new AggregateError([error, ...failures], "Deployment failed and cleanup is incomplete.");
26564
+ throw error;
26565
+ }
26566
+
26567
+ // src/cli/host-domain-aliases.ts
26568
+ import { readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
26569
+ import path2 from "node:path";
26570
+
26571
+ // src/cli/cli-support.ts
26572
+ function errorDetails(error) {
26573
+ if (error === null || error === void 0) {
26574
+ return {};
26575
+ }
26576
+ return typeof error === "object" ? error : { message: String(error) };
26577
+ }
26578
+ function helperError(message, hint, diagnostics = null) {
26579
+ const error = new Error(message);
26580
+ error.hint = hint;
26581
+ if (diagnostics) {
26582
+ error.diagnostics = diagnostics;
26583
+ }
26584
+ return error;
26585
+ }
26586
+ function readStdin() {
26587
+ return new Promise((resolve, reject) => {
26588
+ let stdin = "";
26589
+ process.stdin.setEncoding("utf8");
26590
+ process.stdin.on("data", (chunk) => {
26591
+ stdin += chunk;
26592
+ });
26593
+ process.stdin.on("end", () => resolve(stdin));
26594
+ process.stdin.on("error", reject);
26595
+ });
26596
+ }
26597
+ function delay(ms) {
26598
+ return new Promise((resolve) => setTimeout(resolve, ms));
26599
+ }
26600
+ function writeResult(result, failed = false) {
26601
+ process.stdout.write(`${JSON.stringify(result)}
26602
+ `);
26603
+ if (failed) {
26604
+ process.exitCode = 1;
26605
+ }
26606
+ }
26607
+ function writeEnvelope(result, failed = false) {
26608
+ writeResult(result, failed);
26609
+ }
26610
+
26611
+ // src/cli/host-domain-aliases.ts
26612
+ function validateAliasDomains(value) {
26613
+ if (value === void 0) return [];
26614
+ 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)))) {
26615
+ throw helperError(
26616
+ "Invalid Hosted Capsule alias domains.",
26617
+ "Supply at most 20 unique lowercase DNS hostnames, without schemes, ports, paths, wildcards, or IP addresses."
26618
+ );
26619
+ }
26620
+ if (new Set(value).size !== value.length) {
26621
+ throw helperError("Duplicate Hosted Capsule alias domain.", "Supply each alias domain once.");
26622
+ }
26623
+ return [...value];
26624
+ }
26625
+ async function assertHostnamesAvailable(remoteRoot, hostnames, owner) {
26626
+ const wanted = new Set(hostnames.map((hostname) => hostname.replace(/:\d+$/, "")));
26627
+ if (wanted.size !== hostnames.length) {
26628
+ throw helperError("Duplicate Hosted Capsule hostname.", "An alias must differ from the Capsule subdomain.");
26629
+ }
26630
+ const assertUnclaimed = (hostname, claimant) => {
26631
+ if (claimant !== owner && wanted.has(hostname.replace(/:\d+$/, ""))) {
26632
+ throw helperError(
26633
+ "Hosted Capsule hostname is already reserved.",
26634
+ `Choose another hostname; ${hostname} belongs to ${claimant}.`
26635
+ );
26636
+ }
26637
+ };
26638
+ const entries = async (directory) => readdir2(directory, { withFileTypes: true }).catch((error) => {
26639
+ if (error.code === "ENOENT") return [];
26640
+ throw error;
26641
+ });
26642
+ for (const entry of await entries(path2.join(remoteRoot, "caddy", "hosts"))) {
26643
+ if (entry.name.endsWith(".caddy")) {
26644
+ const domain = entry.name.slice(0, -6);
26645
+ assertUnclaimed(`host.${domain}`, `health:${domain}`);
26646
+ }
26647
+ }
26648
+ for (const domain of await entries(path2.join(remoteRoot, "hosts"))) {
26649
+ if (domain.isSymbolicLink()) throw helperError("Cannot inspect Hosted domain ownership.", "Replace symbolic links in the Host registry with canonical directories.");
26650
+ if (!domain.isDirectory()) continue;
26651
+ for (const collection of ["capsules", "registration-claims"]) {
26652
+ const directory = path2.join(remoteRoot, "hosts", domain.name, "registry", collection);
26653
+ for (const entry of await entries(directory)) {
26654
+ if (!entry.name.endsWith(".json")) continue;
26655
+ if (!entry.isFile()) throw helperError("Cannot inspect Hosted Capsule ownership.", "Repair the Host registry before registering domains.");
26656
+ const record = JSON.parse(await readFile2(path2.join(directory, entry.name), "utf8"));
26657
+ if (record.domain !== domain.name || record.subname !== entry.name.slice(0, -5)) {
26658
+ throw helperError("Invalid Hosted Capsule registry identity.", "Repair the Host registry before registering domains.");
26659
+ }
26660
+ if (collection === "capsules" && record.status === "unregistered") continue;
26661
+ const claimant = `${record.domain}/${record.subname}`;
26662
+ const aliases = validateAliasDomains(record.aliasDomains);
26663
+ const previousAliases = collection === "registration-claims" ? validateAliasDomains(record.previousAliasDomains) : [];
26664
+ for (const hostname of [`${record.subname}.${record.domain}`, ...aliases, ...previousAliases]) {
26665
+ assertUnclaimed(hostname, claimant);
26666
+ }
26667
+ }
26668
+ }
26669
+ }
26670
+ }
26671
+
26264
26672
  // src/cli/sporades-host-helper.ts
26265
26673
  import { spawnSync as spawnSync2 } from "node:child_process";
26266
26674
  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";
26268
- import { createHash, generateKeyPairSync, randomBytes } from "node:crypto";
26675
+ import { access, chmod, lstat as lstat2, mkdir as mkdir2, open as open2, opendir, readdir as readdir3, readFile as readFile4, readlink, rename as rename2, rm as rm2, stat, statfs, symlink, writeFile } from "node:fs/promises";
26676
+ import { createHash as createHash2, generateKeyPairSync, randomBytes } from "node:crypto";
26269
26677
  import { freemem, loadavg, totalmem } from "node:os";
26270
- import path3 from "node:path";
26678
+ import path5 from "node:path";
26271
26679
  import { isDeepStrictEqual } from "node:util";
26272
26680
 
26273
26681
  // src/base-image.ts
@@ -26422,7 +26830,7 @@ function validatePublicTreeFileSet(files) {
26422
26830
  }
26423
26831
 
26424
26832
  // src/log-envelope.ts
26425
- import { randomUUID } from "node:crypto";
26833
+ import { randomUUID as randomUUID2 } from "node:crypto";
26426
26834
  function uncappedLogEnvelope(input) {
26427
26835
  const config = input.config ?? {};
26428
26836
  const capsuleName = String(config.name ?? "unknown");
@@ -26439,7 +26847,7 @@ function uncappedLogEnvelope(input) {
26439
26847
  },
26440
26848
  release: input.release ?? config.release ?? null,
26441
26849
  request: input.request ? {
26442
- id: input.request.id ?? randomUUID(),
26850
+ id: input.request.id ?? randomUUID2(),
26443
26851
  method: input.request.method ?? null,
26444
26852
  path: input.request.path ?? null
26445
26853
  } : null,
@@ -26567,6 +26975,7 @@ var ACL_HELPER_STATE = Symbol("sporades.aclHelperState");
26567
26975
 
26568
26976
  // src/file-storage-runtime.ts
26569
26977
  var nodeCryptoModule2 = process.getBuiltinModule("node:crypto");
26978
+ var nodePromiseHooks = process.getBuiltinModule("node:v8")?.promiseHooks;
26570
26979
 
26571
26980
  // src/file-ingress-runtime.ts
26572
26981
  var import_pdf_lib = __toESM(require_cjs(), 1);
@@ -40519,17 +40928,17 @@ var Lexer = class _Lexer {
40519
40928
  if (next < end && this.src.charCodeAt(next) === CH_LBRACE) {
40520
40929
  close = this.findClosingBrace(next + 1, end);
40521
40930
  } else {
40522
- const open2 = skipLineContinuations(this.src, next, end);
40523
- if (open2 >= end || this.src.charCodeAt(open2) !== CH_LPAREN)
40931
+ const open3 = skipLineContinuations(this.src, next, end);
40932
+ if (open3 >= end || this.src.charCodeAt(open3) !== CH_LPAREN)
40524
40933
  return start;
40525
- close = this.findClosingParenthesis(open2 + 1, end);
40934
+ close = this.findClosingParenthesis(open3 + 1, end);
40526
40935
  }
40527
40936
  } else {
40528
- const open2 = start + 1;
40529
- if (quoted || ch !== CH_LT && ch !== CH_GT || open2 >= end || this.src.charCodeAt(open2) !== CH_LPAREN) {
40937
+ const open3 = start + 1;
40938
+ if (quoted || ch !== CH_LT && ch !== CH_GT || open3 >= end || this.src.charCodeAt(open3) !== CH_LPAREN) {
40530
40939
  return start;
40531
40940
  }
40532
- close = this.findClosingParenthesis(open2 + 1, end);
40941
+ close = this.findClosingParenthesis(open3 + 1, end);
40533
40942
  }
40534
40943
  return close === -1 ? end : close + 1;
40535
40944
  }
@@ -42546,48 +42955,8 @@ var serviceUserMutationAuthority = Object.freeze({ kind: "service-user-mutation-
42546
42955
  var MutationExecutionStorage = process.getBuiltinModule("node:async_hooks").AsyncLocalStorage;
42547
42956
  var mutationExecution = new MutationExecutionStorage();
42548
42957
 
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
42958
  // src/cli/cli-version.ts
42590
- var CLI_VERSION = "0.9.21";
42959
+ var CLI_VERSION = "0.9.23";
42591
42960
 
42592
42961
  // src/cli/schedule-inspection-envelope.ts
42593
42962
  var SCHEDULE_DIAGNOSTIC_FIELDS = /* @__PURE__ */ new Set([
@@ -42668,7 +43037,7 @@ import { spawnSync } from "node:child_process";
42668
43037
  // src/cli/host-helper-release-files.ts
42669
43038
  function expectedReleaseFiles(release) {
42670
43039
  const publicFiles = Array.isArray(release.files) ? release.files.filter((file) => typeof file === "string" && file.startsWith("public/")) : [];
42671
- const files = ["server.mjs", "sporades.json", ...publicFiles];
43040
+ const files = ["server.mjs", "sporades.json", ...publicFiles, ...resolveDeployFiles(release.deployFiles).map((file) => file.path)];
42672
43041
  if (release.serverEnvIncluded) {
42673
43042
  files.push(".env.sporades.server");
42674
43043
  }
@@ -42680,8 +43049,8 @@ function expectedReleaseFiles(release) {
42680
43049
  }
42681
43050
  return files;
42682
43051
  }
42683
- function isExpectedClaimedReleaseFile(file) {
42684
- return typeof file === "string" && (file.startsWith("public/") || [
43052
+ function isExpectedClaimedReleaseFile(file, deployFiles = []) {
43053
+ return typeof file === "string" && (file.startsWith("public/") || deployFiles.includes(file) || [
42685
43054
  "server.mjs",
42686
43055
  "sporades.json",
42687
43056
  ".env.sporades.server",
@@ -42833,8 +43202,8 @@ function validatePublicArchiveBounds(entries) {
42833
43202
  }
42834
43203
 
42835
43204
  // src/cli/host-helper-config.ts
42836
- import { readFile } from "node:fs/promises";
42837
- import path from "node:path";
43205
+ import { readFile as readFile3 } from "node:fs/promises";
43206
+ import path3 from "node:path";
42838
43207
  var HOST_HELPER_CONFIG_FILE = "sporades-host-helper.json";
42839
43208
  var DEFAULT_HOSTED_CAPSULE_DOCKER_IMAGE = SPORADES_BASE_IMAGE.image;
42840
43209
  var DEFAULT_HOSTED_CAPSULE_DOCKER_NETWORK = "sporades-hosted-capsules";
@@ -42862,7 +43231,7 @@ async function loadHostHelperConfig(request) {
42862
43231
  }
42863
43232
  let contents;
42864
43233
  try {
42865
- contents = await readFile(configPath, "utf8");
43234
+ contents = await readFile3(configPath, "utf8");
42866
43235
  } catch (error) {
42867
43236
  if (errorDetails(error).code === "ENOENT" && !process.env.SPORADES_HOST_HELPER_CONFIG) {
42868
43237
  return loaded;
@@ -42890,7 +43259,7 @@ function hostHelperConfigPath(request) {
42890
43259
  if (typeof request.host?.remoteRoot !== "string" || request.host.remoteRoot.length === 0) {
42891
43260
  return null;
42892
43261
  }
42893
- return path.join(request.host.remoteRoot, HOST_HELPER_CONFIG_FILE);
43262
+ return path3.join(request.host.remoteRoot, HOST_HELPER_CONFIG_FILE);
42894
43263
  }
42895
43264
  function applyHostHelperConfig(loaded, config, configPath) {
42896
43265
  assertPlainObject(config, "Host helper config", configPath);
@@ -42962,7 +43331,7 @@ function readConfigPositiveInteger(value, key, configPath) {
42962
43331
  }
42963
43332
 
42964
43333
  // src/cli/host-helper-validation.ts
42965
- import path2 from "node:path";
43334
+ import path4 from "node:path";
42966
43335
  function missingCapsuleHint(request, purpose) {
42967
43336
  if (purpose === "push") {
42968
43337
  return `Run \`sporades host register ${request.capsule.subname} --host ${request.host.alias}\` before pushing a release.`;
@@ -43110,7 +43479,7 @@ function validateListRequest(request) {
43110
43479
  }
43111
43480
  function validateListRegistryRecord(request, record, recordPath) {
43112
43481
  const capsuleRecord = record;
43113
- const expectedSubname = path2.basename(recordPath, ".json");
43482
+ const expectedSubname = path4.basename(recordPath, ".json");
43114
43483
  const expectedRemoteCapsuleId = `${request.host.domain}/${typeof capsuleRecord?.subname === "string" ? capsuleRecord.subname : expectedSubname}`;
43115
43484
  const valid = capsuleRecord && typeof capsuleRecord.subname === "string" && capsuleRecord.subname.length > 0 && capsuleRecord.subname === expectedSubname && capsuleRecord.domain === request.host.domain && (capsuleRecord.remoteCapsuleId ?? expectedRemoteCapsuleId) === expectedRemoteCapsuleId;
43116
43485
  if (!valid) {
@@ -43148,6 +43517,7 @@ function validateRegisterRequest(request) {
43148
43517
  throw helperError("Invalid Hosted Capsule registration request.", "Update the Sporades CLI and retry `sporades host register`.");
43149
43518
  }
43150
43519
  const registration = request.registration ?? {};
43520
+ validateAliasDomains(registration.aliasDomains);
43151
43521
  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
43522
  if (mismatchedIdentity) {
43153
43523
  throw helperError(
@@ -43222,7 +43592,9 @@ function validateInstallRequest(request) {
43222
43592
  if (!/^\d{8}T\d{6}Z-[a-f0-9]{8}$/.test(release.id)) {
43223
43593
  throw helperError("Invalid Hosted Capsule release ID.", "Push again to generate a fresh UTC-sortable release ID.");
43224
43594
  }
43225
- if (!Array.isArray(release.files) || release.files.some((file) => !isExpectedClaimedReleaseFile(file))) {
43595
+ const deployFiles = resolveDeployFiles(release.deployFiles);
43596
+ if (JSON.stringify(release.deployFiles ?? []) !== JSON.stringify(deployFiles)) throw helperError("Invalid release deploy.files manifest.", "Rebuild with canonical relative paths and retry.");
43597
+ if (!Array.isArray(release.files) || release.files.some((file) => !isExpectedClaimedReleaseFile(file, deployFiles.map((entry) => entry.path)))) {
43226
43598
  throw helperError("Invalid Hosted Capsule release file list.", "Update the Sporades CLI and retry `sporades host push`.");
43227
43599
  }
43228
43600
  validateClaimedReleaseFiles(release.files);
@@ -43236,8 +43608,8 @@ function validateInstallRequest(request) {
43236
43608
  if (!directories?.releases || !directories.release) {
43237
43609
  throw helperError("Invalid Hosted Capsule release directory.", "Update the Sporades CLI and retry `sporades host push`.");
43238
43610
  }
43239
- const expectedReleaseDirectory = path2.join(directories.releases, release.id);
43240
- if (path2.resolve(directories.release) !== path2.resolve(expectedReleaseDirectory)) {
43611
+ const expectedReleaseDirectory = path4.join(directories.releases, release.id);
43612
+ if (path4.resolve(directories.release) !== path4.resolve(expectedReleaseDirectory)) {
43241
43613
  throw helperError("Invalid Hosted Capsule release directory.", "Update the Sporades CLI and retry `sporades host push`.");
43242
43614
  }
43243
43615
  }
@@ -43356,12 +43728,12 @@ async function runHostHelperInstaller() {
43356
43728
  const internal = process.argv[2] === "--install-host-helper-internal";
43357
43729
  const target = process.argv[3];
43358
43730
  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 ?? "")) {
43731
+ if (process.argv.length !== 5 || typeof target !== "string" || !path5.isAbsolute(target) || path5.basename(target) !== "sporades-host-helper" || !/^[a-f0-9]{64}$/.test(expectedChecksum ?? "")) {
43360
43732
  throw helperError("Invalid Host helper upgrade request.", "Retry with `sporades host upgrade --host <alias>`.");
43361
43733
  }
43362
43734
  const stage = process.argv[1];
43363
- const directory = path3.dirname(target);
43364
- const expectedStage = path3.join(directory, `.sporades-host-helper-stage-${expectedChecksum}.mjs`);
43735
+ const directory = path5.dirname(target);
43736
+ const expectedStage = path5.join(directory, `.sporades-host-helper-stage-${expectedChecksum}.mjs`);
43365
43737
  if (stage !== expectedStage) {
43366
43738
  throw helperError("Invalid staged Host helper path.", "Retry with `sporades host upgrade --host <alias>`.");
43367
43739
  }
@@ -43375,7 +43747,7 @@ async function runHostHelperInstaller() {
43375
43747
  "--conflict-exit-code",
43376
43748
  "75",
43377
43749
  "--no-fork",
43378
- path3.join(directory, ".sporades-host-helper.upgrade.lock"),
43750
+ path5.join(directory, ".sporades-host-helper.upgrade.lock"),
43379
43751
  process.execPath,
43380
43752
  stage,
43381
43753
  "--install-host-helper-internal",
@@ -43416,14 +43788,14 @@ function hostHelperUpgradeTimeoutMs(name2, fallback) {
43416
43788
  return value;
43417
43789
  }
43418
43790
  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");
43791
+ const directory = path5.dirname(target);
43792
+ const pointer = path5.join(directory, ".sporades-host-helper.active");
43793
+ const blocked = path5.join(directory, ".sporades-host-helper.upgrade-blocked");
43794
+ const needsDrain = path5.join(directory, ".sporades-host-helper.needs-drain");
43423
43795
  const newPayloadName = `.sporades-host-helper-payload-${expectedChecksum}.mjs`;
43424
- const newPayload = path3.join(directory, newPayloadName);
43425
- await mkdir(directory, { recursive: true });
43426
- if (createHash("sha256").update(await readFile2(stage)).digest("hex") !== expectedChecksum) {
43796
+ const newPayload = path5.join(directory, newPayloadName);
43797
+ await mkdir2(directory, { recursive: true });
43798
+ if (createHash2("sha256").update(await readFile4(stage)).digest("hex") !== expectedChecksum) {
43427
43799
  throw helperError("Staged Host helper checksum did not match.", "Upload the immutable Host helper again, then retry the upgrade.");
43428
43800
  }
43429
43801
  await publishHostHelperFile(stage, newPayload, 493);
@@ -43431,18 +43803,18 @@ async function installHostHelperPayload(stage, target, expectedChecksum) {
43431
43803
  let firstCooperativeUpgrade = true;
43432
43804
  let currentTarget;
43433
43805
  try {
43434
- currentTarget = await readFile2(target);
43806
+ currentTarget = await readFile4(target);
43435
43807
  } catch {
43436
43808
  throw helperError("Current Host helper was not found.", "Install the current Host helper before retrying the upgrade.");
43437
43809
  }
43438
43810
  if (currentTarget.toString("utf8").includes(HOST_HELPER_DISPATCHER_MARKER)) {
43439
43811
  firstCooperativeUpgrade = false;
43440
- previousPayloadName = (await readFile2(pointer, "utf8")).trim();
43812
+ previousPayloadName = (await readFile4(pointer, "utf8")).trim();
43441
43813
  await validateHostHelperPayload(directory, previousPayloadName);
43442
43814
  } else {
43443
- const previousChecksum = createHash("sha256").update(currentTarget).digest("hex");
43815
+ const previousChecksum = createHash2("sha256").update(currentTarget).digest("hex");
43444
43816
  previousPayloadName = `.sporades-host-helper-payload-${previousChecksum}.mjs`;
43445
- await publishHostHelperFile(target, path3.join(directory, previousPayloadName), 493);
43817
+ await publishHostHelperFile(target, path5.join(directory, previousPayloadName), 493);
43446
43818
  await writeHostHelperPointer(pointer, previousPayloadName);
43447
43819
  await writeFile(needsDrain, "legacy-helper-drain-required\n", { mode: 384 });
43448
43820
  }
@@ -43452,16 +43824,16 @@ async function installHostHelperPayload(stage, target, expectedChecksum) {
43452
43824
  try {
43453
43825
  if (firstCooperativeUpgrade || await pathExists(needsDrain)) await drainUncooperativeHostHelpers(target);
43454
43826
  await writeHostHelperPointer(pointer, newPayloadName);
43455
- await rm(needsDrain, { force: true });
43456
- await rm(blocked, { force: true });
43457
- await rm(stage, { force: true });
43827
+ await rm2(needsDrain, { force: true });
43828
+ await rm2(blocked, { force: true });
43829
+ await rm2(stage, { force: true });
43458
43830
  } catch (error) {
43459
43831
  await writeFile(blocked, "upgrade-recovery-required\n", { mode: 384 });
43460
43832
  throw error;
43461
43833
  }
43462
43834
  }
43463
43835
  async function publishHostHelperFile(source, target, mode) {
43464
- const contents = await readFile2(source);
43836
+ const contents = await readFile4(source);
43465
43837
  await publishHostHelperBytes(contents, target, mode);
43466
43838
  }
43467
43839
  async function publishHostHelperBytes(contents, target, mode) {
@@ -43469,21 +43841,21 @@ async function publishHostHelperBytes(contents, target, mode) {
43469
43841
  try {
43470
43842
  await writeFile(temporary, contents, { flag: "wx", mode });
43471
43843
  await chmod(temporary, mode);
43472
- await rename(temporary, target);
43844
+ await rename2(temporary, target);
43473
43845
  } finally {
43474
- await rm(temporary, { force: true });
43846
+ await rm2(temporary, { force: true });
43475
43847
  }
43476
43848
  }
43477
43849
  async function writeHostHelperPointer(pointer, payloadName) {
43478
- await validateHostHelperPayload(path3.dirname(pointer), payloadName);
43850
+ await validateHostHelperPayload(path5.dirname(pointer), payloadName);
43479
43851
  await publishHostHelperBytes(Buffer.from(`${payloadName}
43480
43852
  `, "utf8"), pointer, 384);
43481
43853
  }
43482
43854
  async function validateHostHelperPayload(directory, payloadName) {
43483
43855
  const match = /^\.sporades-host-helper-payload-([a-f0-9]{64})\.mjs$/.exec(payloadName);
43484
43856
  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");
43857
+ const payload = path5.join(directory, payloadName);
43858
+ const actual = createHash2("sha256").update(await readFile4(payload)).digest("hex");
43487
43859
  if (actual !== match[1]) throw helperError("Host helper payload checksum did not match.", "Retry the Host helper upgrade.");
43488
43860
  }
43489
43861
  async function drainUncooperativeHostHelpers(target) {
@@ -43553,11 +43925,11 @@ async function findUncooperativeHostHelperProcesses(target, deadline) {
43553
43925
  );
43554
43926
  }
43555
43927
  try {
43556
- const processDir = path3.join(procRoot, entry.name);
43557
- const argv = (await readFile2(path3.join(processDir, "cmdline"))).toString("utf8").split("\0").filter(Boolean);
43928
+ const processDir = path5.join(procRoot, entry.name);
43929
+ const argv = (await readFile4(path5.join(processDir, "cmdline"))).toString("utf8").split("\0").filter(Boolean);
43558
43930
  assertHostHelperDrainDeadline(deadline);
43559
43931
  if (argv[1] !== target) continue;
43560
- const environment = (await readFile2(path3.join(processDir, "environ"))).toString("utf8").split("\0");
43932
+ const environment = (await readFile4(path5.join(processDir, "environ"))).toString("utf8").split("\0");
43561
43933
  assertHostHelperDrainDeadline(deadline);
43562
43934
  if (environment.includes("SPORADES_HOST_DISPATCH_LOCK_HELD=1")) continue;
43563
43935
  active.push(Number(entry.name));
@@ -43614,7 +43986,7 @@ async function runManagedRouteActionProcess(request, input, lockIdentity) {
43614
43986
  process.argv[1]
43615
43987
  ] : [process.execPath, process.argv[1]];
43616
43988
  const result = spawnSync2(flock, [
43617
- lockIdentity.routeLockFile ? "--shared" : "--exclusive",
43989
+ lockIdentity.routeLockFile && request.action !== "capsule.register" ? "--shared" : "--exclusive",
43618
43990
  "--timeout",
43619
43991
  String(timeoutMs / 1e3),
43620
43992
  "--conflict-exit-code",
@@ -43671,6 +44043,7 @@ function managedRouteMutationLockIdentity(request) {
43671
44043
  case "capsule.release.rollback":
43672
44044
  validateRollbackRequest(request);
43673
44045
  break;
44046
+ case "capsule.release.reconcile":
43674
44047
  case "capsule.start":
43675
44048
  case "capsule.stop":
43676
44049
  case "capsule.restart":
@@ -43689,7 +44062,7 @@ function managedRouteMutationLockIdentity(request) {
43689
44062
  const domainDirectory = canonicalManagedRouteDomainDirectory(request, remoteRoot2);
43690
44063
  const bootstrapTrust = bootstrapTrustManifest(request);
43691
44064
  return {
43692
- globalLockFile: path3.join(remoteRoot2, "bin", ".sporades-host-helper.host-route.lock"),
44065
+ globalLockFile: path5.join(remoteRoot2, "bin", ".sporades-host-helper.host-route.lock"),
43693
44066
  routeLockFile: null,
43694
44067
  domainDirectory,
43695
44068
  bootstrapTrust
@@ -43700,7 +44073,7 @@ function managedRouteMutationLockIdentity(request) {
43700
44073
  }
43701
44074
  const remoteRoot = validateCanonicalHostRouteRoot(request);
43702
44075
  return {
43703
- globalLockFile: path3.join(remoteRoot, "bin", ".sporades-host-helper.host-route.lock"),
44076
+ globalLockFile: path5.join(remoteRoot, "bin", ".sporades-host-helper.host-route.lock"),
43704
44077
  routeLockFile: `${canonicalManagedRouteFile(request, remoteRoot)}.lock`,
43705
44078
  domainDirectory: canonicalManagedRouteDomainDirectory(request, remoteRoot),
43706
44079
  ...actionCanProvisionCapsuleHttpLog(request.action) ? { routeLogTrust: capsuleHttpLogTrustManifest(request, remoteRoot) } : {}
@@ -43711,7 +44084,7 @@ function actionCanProvisionCapsuleHttpLog(action) {
43711
44084
  }
43712
44085
  function validateCanonicalHostRouteRoot(request) {
43713
44086
  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) {
44087
+ if (typeof remoteRoot !== "string" || remoteRoot.length < 2 || remoteRoot.length > 4096 || !path5.isAbsolute(remoteRoot) || /[\0\r\n]/.test(remoteRoot) || path5.normalize(remoteRoot) !== remoteRoot || remoteRoot === path5.parse(remoteRoot).root) {
43715
44088
  throw helperError("Invalid Hosted Capsule route identity.", "Use a bounded absolute canonical Host remote root and retry.");
43716
44089
  }
43717
44090
  const domain = request.host.domain;
@@ -43730,22 +44103,22 @@ function canonicalManagedRouteFile(request, validatedRemoteRoot = validateCanoni
43730
44103
  throw helperError("Invalid Hosted Capsule route identity.", "Use a canonical DNS-safe Capsule subname and retry.");
43731
44104
  }
43732
44105
  const domainDirectory = canonicalManagedRouteDomainDirectory(request, validatedRemoteRoot);
43733
- const routeFile = path3.resolve(domainDirectory, `${subname}.caddy`);
43734
- if (path3.dirname(routeFile) !== domainDirectory) {
44106
+ const routeFile = path5.resolve(domainDirectory, `${subname}.caddy`);
44107
+ if (path5.dirname(routeFile) !== domainDirectory) {
43735
44108
  throw helperError("Invalid Hosted Capsule route identity.", "Use a route identity within the configured Hosted domain directory and retry.");
43736
44109
  }
43737
44110
  return routeFile;
43738
44111
  }
43739
44112
  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) {
44113
+ const hostsDirectory = path5.resolve(validatedRemoteRoot, "caddy", "hosts");
44114
+ const domainDirectory = path5.resolve(hostsDirectory, request.host.domain);
44115
+ if (path5.dirname(domainDirectory) !== hostsDirectory) {
43743
44116
  throw helperError("Invalid Hosted Capsule route identity.", "Use a route identity within the configured Hosted domain directory and retry.");
43744
44117
  }
43745
44118
  return domainDirectory;
43746
44119
  }
43747
44120
  async function captureManagedRouteTrust(lockIdentity, createMissing) {
43748
- const remoteRoot = path3.dirname(path3.dirname(lockIdentity.globalLockFile));
44121
+ const remoteRoot = path5.dirname(path5.dirname(lockIdentity.globalLockFile));
43749
44122
  const canonicalRemoteRoot = await canonicalTrustedTarget(remoteRoot);
43750
44123
  const directoryIdentities = /* @__PURE__ */ new Map();
43751
44124
  const captureDirectory = async (directory, caddyOwned = false) => {
@@ -43755,18 +44128,18 @@ async function captureManagedRouteTrust(lockIdentity, createMissing) {
43755
44128
  }
43756
44129
  };
43757
44130
  await captureDirectory(remoteRoot);
43758
- await captureDirectory(path3.join(canonicalRemoteRoot, "bin"));
44131
+ await captureDirectory(path5.join(canonicalRemoteRoot, "bin"));
43759
44132
  let routeFile = null;
43760
44133
  if (lockIdentity.routeLockFile) {
43761
44134
  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"));
44135
+ const domainDirectory = path5.dirname(routeFile);
44136
+ await captureDirectory(path5.join(canonicalRemoteRoot, "caddy"));
44137
+ await captureDirectory(path5.join(canonicalRemoteRoot, "caddy", "hosts"));
43765
44138
  await captureDirectory(await canonicalTrustedTarget(domainDirectory));
43766
44139
  await assertTrustedRegularFileIfExists(routeFile);
43767
44140
  } else {
43768
- await captureDirectory(path3.join(canonicalRemoteRoot, "caddy"));
43769
- await captureDirectory(path3.join(canonicalRemoteRoot, "caddy", "hosts"));
44141
+ await captureDirectory(path5.join(canonicalRemoteRoot, "caddy"));
44142
+ await captureDirectory(path5.join(canonicalRemoteRoot, "caddy", "hosts"));
43770
44143
  await captureDirectory(await canonicalTrustedTarget(lockIdentity.domainDirectory));
43771
44144
  for (const directory of lockIdentity.bootstrapTrust?.directories ?? []) await captureDirectory(directory.path, directory.caddyOwned === true);
43772
44145
  }
@@ -43790,34 +44163,34 @@ async function captureManagedRouteTrust(lockIdentity, createMissing) {
43790
44163
  return { directories: [...directoryIdentities.values()], managedRoot: canonicalRemoteRoot, finalFiles, globalLockFile: lockIdentity.globalLockFile, routeLockFile: lockIdentity.routeLockFile, routeFile };
43791
44164
  }
43792
44165
  async function canonicalTrustedTarget(target) {
43793
- if (!path3.isAbsolute(target) || path3.normalize(target) !== target) throw routeTrustError();
44166
+ if (!path5.isAbsolute(target) || path5.normalize(target) !== target) throw routeTrustError();
43794
44167
  return target;
43795
44168
  }
43796
44169
  async function trustedDirectoryChain(target, createMissing, managedRoot, targetOwner) {
43797
44170
  const canonicalTarget = await canonicalTrustedTarget(target);
43798
- const parsed = path3.parse(canonicalTarget);
43799
- const components = canonicalTarget.slice(parsed.root.length).split(path3.sep).filter(Boolean);
44171
+ const parsed = path5.parse(canonicalTarget);
44172
+ const components = canonicalTarget.slice(parsed.root.length).split(path5.sep).filter(Boolean);
43800
44173
  const identities = [];
43801
44174
  let current2 = parsed.root;
43802
44175
  for (const component of [null, ...components]) {
43803
- if (component !== null) current2 = path3.join(current2, component);
44176
+ if (component !== null) current2 = path5.join(current2, component);
43804
44177
  let details;
43805
44178
  try {
43806
- details = await lstat(current2);
44179
+ details = await lstat2(current2);
43807
44180
  } catch (error) {
43808
44181
  if (!createMissing || errorDetails(error).code !== "ENOENT") throw routeTrustError();
43809
44182
  try {
43810
- await mkdir(current2, { mode: 493 });
44183
+ await mkdir2(current2, { mode: 493 });
43811
44184
  } catch (mkdirError) {
43812
44185
  if (errorDetails(mkdirError).code !== "EEXIST") throw routeTrustError();
43813
44186
  }
43814
44187
  try {
43815
- details = await lstat(current2);
44188
+ details = await lstat2(current2);
43816
44189
  } catch {
43817
44190
  throw routeTrustError();
43818
44191
  }
43819
44192
  }
43820
- const managed = current2 === managedRoot || current2.startsWith(`${managedRoot}${path3.sep}`);
44193
+ const managed = current2 === managedRoot || current2.startsWith(`${managedRoot}${path5.sep}`);
43821
44194
  const expectedOwner = current2 === canonicalTarget ? targetOwner : void 0;
43822
44195
  if (!details.isDirectory() || details.isSymbolicLink() || !(expectedOwner ? trustedExactOwnerMetadata(details, expectedOwner) : managed ? trustedHostPathMetadata(details) : trustedAnchorPathMetadata(details))) throw routeTrustError();
43823
44196
  identities.push({ path: current2, dev: details.dev, ino: details.ino, ...expectedOwner ? { expectedOwner } : {} });
@@ -43830,7 +44203,7 @@ function trustedExactOwnerMetadata(details, owner) {
43830
44203
  async function expectedCaddyOwnerForExistingPath(target) {
43831
44204
  let details;
43832
44205
  try {
43833
- details = await lstat(target);
44206
+ details = await lstat2(target);
43834
44207
  } catch (error) {
43835
44208
  if (errorDetails(error).code === "ENOENT") return void 0;
43836
44209
  throw routeTrustError();
@@ -43860,7 +44233,7 @@ function trustedAnchorPathMetadata(details) {
43860
44233
  async function assertTrustedRegularFileIfExists(file, expectedOwner) {
43861
44234
  let details;
43862
44235
  try {
43863
- details = await lstat(file);
44236
+ details = await lstat2(file);
43864
44237
  } catch (error) {
43865
44238
  if (errorDetails(error).code === "ENOENT") return null;
43866
44239
  throw routeTrustError();
@@ -43874,11 +44247,11 @@ async function assertActiveManagedRouteTrust(routeFile) {
43874
44247
  for (const expected of trust.directories) {
43875
44248
  let current2;
43876
44249
  try {
43877
- current2 = await lstat(expected.path);
44250
+ current2 = await lstat2(expected.path);
43878
44251
  } catch {
43879
44252
  throw routeTrustError();
43880
44253
  }
43881
- const managed = expected.path === trust.managedRoot || expected.path.startsWith(`${trust.managedRoot}${path3.sep}`);
44254
+ const managed = expected.path === trust.managedRoot || expected.path.startsWith(`${trust.managedRoot}${path5.sep}`);
43882
44255
  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
44256
  }
43884
44257
  await assertTrustedRegularFileIfExists(trust.globalLockFile);
@@ -43926,6 +44299,10 @@ async function main(request) {
43926
44299
  await rollbackRelease(request);
43927
44300
  return;
43928
44301
  }
44302
+ if (request.action === "capsule.release.reconcile") {
44303
+ await reconcileReleaseAttempt(request);
44304
+ return;
44305
+ }
43929
44306
  if (request.action === "capsule.start") {
43930
44307
  await startCapsule(request);
43931
44308
  return;
@@ -44049,6 +44426,7 @@ function versionHost(request) {
44049
44426
  async function bootstrapHost(request) {
44050
44427
  validateBootstrapRequest(request);
44051
44428
  const bootstrap = normaliseBootstrap(request);
44429
+ await assertHostnamesAvailable(request.host.remoteRoot, [`host.${request.host.domain}`], `health:${request.host.domain}`);
44052
44430
  await ensureBootstrapDirectories(bootstrap);
44053
44431
  await validateBootstrapTls(request, bootstrap);
44054
44432
  const network = ensureDockerNetwork(bootstrap.network);
@@ -44073,26 +44451,60 @@ async function bootstrapHost(request) {
44073
44451
  }
44074
44452
  async function registerCapsule(request) {
44075
44453
  validateRegisterRequest(request);
44076
- const registration = normaliseRegistration(request);
44454
+ const claimPath = path5.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", "registration-claims", `${request.capsule.subname}.json`);
44455
+ const pendingClaim = await readFile4(claimPath, "utf8").then((contents) => JSON.parse(contents)).catch((error) => {
44456
+ if (errorDetails(error).code === "ENOENT") return null;
44457
+ throw error;
44458
+ });
44459
+ if (pendingClaim) {
44460
+ const aliases = validateAliasDomains(pendingClaim.aliasDomains);
44461
+ if (pendingClaim.domain !== request.host.domain || pendingClaim.subname !== request.capsule.subname || request.registration?.aliasDomains !== void 0 && !isDeepStrictEqual([...request.registration.aliasDomains].sort(), [...aliases].sort())) {
44462
+ throw helperError("Hosted Capsule registration recovery is required.", "Repair Caddy and retry the original registration with the same alias domains before changing its aliases.");
44463
+ }
44464
+ request = { ...request, registration: { ...request.registration, aliasDomains: aliases } };
44465
+ }
44466
+ let registration = normaliseRegistration(request);
44077
44467
  await ensureHostedDomainBootstrapped(request, registration);
44078
44468
  let reactivated = false;
44079
44469
  let sealedServerEnv = null;
44080
44470
  let priorRuntime = null;
44081
44471
  let admissionError = null;
44472
+ let registrationCommitted = false;
44473
+ const recoveryErrors = [];
44474
+ let priorRoute = null;
44475
+ let routeAttempted = false;
44476
+ let claimWritten = Boolean(pendingClaim);
44477
+ const reserveClaim = async (previous = null) => {
44478
+ if (claimWritten) return;
44479
+ await mkdir2(path5.dirname(claimPath), { recursive: true });
44480
+ await publishHostHelperBytes(Buffer.from(JSON.stringify({
44481
+ domain: registration.domain,
44482
+ subname: registration.subname,
44483
+ aliasDomains: registration.aliasDomains,
44484
+ previousAliasDomains: validateAliasDomains(previous?.aliasDomains)
44485
+ })), claimPath, 384);
44486
+ claimWritten = true;
44487
+ };
44082
44488
  try {
44083
- await mkdir(path3.dirname(registryLockPath(request)), { recursive: true });
44489
+ await mkdir2(path5.dirname(registryLockPath(request)), { recursive: true });
44084
44490
  await withRegistryLock(request, async () => {
44085
44491
  if (await pathExists(registration.registryRecord)) {
44086
44492
  const existing = await readRegistryRecordForCapsule(request, "register");
44087
44493
  assertRegistryRecordMatchesRequest(request, existing);
44088
- if (existing.status === "unregistered") {
44494
+ if (existing.status === "unregistered" || pendingClaim) {
44495
+ registration = normaliseRegistration(request, existing);
44496
+ await assertHostnamesAvailable(request.host.remoteRoot, [registration.route.hostname, ...registration.aliasDomains], registration.remoteCapsuleId);
44497
+ priorRoute = await captureReleaseInstallRoute(request, existing);
44498
+ await reserveClaim(existing);
44089
44499
  priorRuntime = captureCapsuleRuntimeSettlement(request, existing);
44090
44500
  quiesceCapsuleRuntime(priorRuntime);
44091
- await mkdir(path3.dirname(registration.registryRecord), { recursive: true });
44092
- await mkdir(registration.directories.releases, { recursive: true });
44501
+ await mkdir2(path5.dirname(registration.registryRecord), { recursive: true });
44502
+ await mkdir2(registration.directories.releases, { recursive: true });
44503
+ routeAttempted = true;
44093
44504
  await writeUnavailableRoute(registration.lifecycle);
44094
44505
  sealedServerEnv = await ensureHostSealedEnvKeyPair(registration, existing);
44095
- await writeRegistryRecordAtomic(registration.registryRecord, reactivateRegistrationRecord(existing, sealedServerEnv));
44506
+ await writeRegistryRecordAtomic(registration.registryRecord, { ...reactivateRegistrationRecord(existing, sealedServerEnv), aliasDomains: registration.aliasDomains, route: { tls: registration.route.tls } });
44507
+ registrationCommitted = true;
44096
44508
  reactivated = true;
44097
44509
  return;
44098
44510
  }
@@ -44101,17 +44513,50 @@ async function registerCapsule(request) {
44101
44513
  `Choose a different Capsule subname for ${request.host.domain}.`
44102
44514
  );
44103
44515
  }
44104
- await mkdir(path3.dirname(registration.registryRecord), { recursive: true });
44105
- await mkdir(registration.directories.releases, { recursive: true });
44106
- await mkdir(registration.directories.logs, { recursive: true });
44516
+ await assertHostnamesAvailable(request.host.remoteRoot, [registration.route.hostname, ...registration.aliasDomains], registration.remoteCapsuleId);
44517
+ priorRoute = await captureReleaseInstallRoute(request, null);
44518
+ await reserveClaim();
44519
+ await mkdir2(path5.dirname(registration.registryRecord), { recursive: true });
44520
+ await mkdir2(registration.directories.releases, { recursive: true });
44521
+ await mkdir2(registration.directories.logs, { recursive: true });
44522
+ routeAttempted = true;
44107
44523
  await writeUnavailableRoute(registration.lifecycle);
44108
44524
  sealedServerEnv = await ensureHostSealedEnvKeyPair(registration);
44109
44525
  await writeRegistryRecordAtomic(registration.registryRecord, createRegistrationRecord(registration, sealedServerEnv));
44526
+ registrationCommitted = true;
44110
44527
  });
44111
44528
  } catch (error) {
44112
- admissionError = error;
44529
+ if (registrationCommitted) {
44530
+ recoveryErrors.push(`Registry lock cleanup: ${errorDetails(error).message}`);
44531
+ } else {
44532
+ admissionError = error;
44533
+ }
44534
+ }
44535
+ if (admissionError && routeAttempted && priorRoute) {
44536
+ try {
44537
+ await restoreReleaseInstallRoute(priorRoute, true);
44538
+ } catch (error) {
44539
+ recoveryErrors.push(`Route rollback: ${errorDetails(error).message}`);
44540
+ }
44541
+ }
44542
+ try {
44543
+ await settleCapsuleRuntime(request, priorRuntime, admissionError);
44544
+ } catch (error) {
44545
+ recoveryErrors.push(`Runtime settlement: ${errorDetails(error).message}`);
44546
+ }
44547
+ if (claimWritten && recoveryErrors.length === 0 && (!admissionError || !pendingClaim)) {
44548
+ try {
44549
+ await rm2(claimPath);
44550
+ } catch (error) {
44551
+ recoveryErrors.push(`Reservation cleanup: ${errorDetails(error).message}`);
44552
+ }
44553
+ }
44554
+ if (recoveryErrors.length) {
44555
+ throw helperError(
44556
+ "Hosted Capsule registration recovery failed.",
44557
+ `${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.`
44558
+ );
44113
44559
  }
44114
- await settleCapsuleRuntime(request, priorRuntime, admissionError);
44115
44560
  if (admissionError) throw admissionError;
44116
44561
  writeEnvelope({
44117
44562
  ok: true,
@@ -44123,6 +44568,8 @@ async function registerCapsule(request) {
44123
44568
  subname: registration.subname,
44124
44569
  domain: registration.domain,
44125
44570
  hostedUrl: registration.hostedUrl,
44571
+ aliasDomains: registration.aliasDomains,
44572
+ aliasUrls: registration.aliasDomains.map((hostname) => `https://${hostname}`),
44126
44573
  remoteCapsuleId: registration.remoteCapsuleId
44127
44574
  },
44128
44575
  registryRecord: registration.registryRecord,
@@ -44135,7 +44582,7 @@ async function registerCapsule(request) {
44135
44582
  }
44136
44583
  async function rotateCapsuleSealedEnvKey(request) {
44137
44584
  validateSealedEnvRotationRequest(request);
44138
- await mkdir(path3.dirname(registryLockPath(request)), { recursive: true });
44585
+ await mkdir2(path5.dirname(registryLockPath(request)), { recursive: true });
44139
44586
  let data2;
44140
44587
  let priorRuntime = null;
44141
44588
  let rotationError = null;
@@ -44151,7 +44598,7 @@ async function rotateCapsuleSealedEnvKey(request) {
44151
44598
  }
44152
44599
  priorRuntime = captureCapsuleRuntimeSettlement(request, record);
44153
44600
  quiesceCapsuleRuntime(priorRuntime);
44154
- const dataDirectory = path3.join(request.host.remoteRoot, "hosts", request.host.domain, "capsules", request.capsule.subname, "data");
44601
+ const dataDirectory = path5.join(request.host.remoteRoot, "hosts", request.host.domain, "capsules", request.capsule.subname, "data");
44155
44602
  const previousPublicKeyFingerprint = record.sealedServerEnv?.currentKeyFingerprint ?? null;
44156
44603
  const sealedServerEnv = await generateHostSealedEnvKeyPair(dataDirectory);
44157
44604
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -44192,10 +44639,24 @@ async function rotateCapsuleSealedEnvKey(request) {
44192
44639
  if (rotationError) throw rotationError;
44193
44640
  writeEnvelope({ ok: true, data: data2, error: null });
44194
44641
  }
44642
+ async function assertRegistrationRecoveryComplete(request) {
44643
+ const claimPath = path5.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", "registration-claims", `${request.capsule.subname}.json`);
44644
+ const pending = await lstat2(claimPath).catch((error) => {
44645
+ if (errorDetails(error).code === "ENOENT") return null;
44646
+ throw error;
44647
+ });
44648
+ if (pending) {
44649
+ throw helperError(
44650
+ "Hosted Capsule registration recovery is required before teardown.",
44651
+ "Repair the reported Host services or storage problem and retry the original registration with the same alias domains before unregistering or deleting this Capsule."
44652
+ );
44653
+ }
44654
+ }
44195
44655
  async function unregisterCapsule(request) {
44196
44656
  validateUnregisterRequest(request);
44657
+ await assertRegistrationRecoveryComplete(request);
44197
44658
  const unregister = normaliseUnregister(request);
44198
- await mkdir(path3.dirname(registryLockPath(request)), { recursive: true });
44659
+ await mkdir2(path5.dirname(registryLockPath(request)), { recursive: true });
44199
44660
  let data2;
44200
44661
  await withRegistryLock(request, async () => {
44201
44662
  const record = await readRegistryRecordForCapsule(request, "unregister");
@@ -44231,8 +44692,9 @@ async function unregisterCapsule(request) {
44231
44692
  }
44232
44693
  async function deleteCapsule(request) {
44233
44694
  validateDeleteRequest(request);
44695
+ await assertRegistrationRecoveryComplete(request);
44234
44696
  const deletion = normaliseDeletion(request);
44235
- await mkdir(path3.dirname(registryLockPath(request)), { recursive: true });
44697
+ await mkdir2(path5.dirname(registryLockPath(request)), { recursive: true });
44236
44698
  let data2;
44237
44699
  await withRegistryLock(request, async () => {
44238
44700
  const record = await readOptionalRegistryRecordForCapsule(request);
@@ -44265,6 +44727,7 @@ function deletionRequiresUnregisterError(request) {
44265
44727
  `Run \`sporades host unregister ${request.capsule.subname} --host ${request.host.alias}\` before deleting Hosted Capsule storage.`
44266
44728
  );
44267
44729
  }
44730
+ var activePreservedAttempts = /* @__PURE__ */ new Set();
44268
44731
  async function installRelease(request) {
44269
44732
  validateInstallRequest(request);
44270
44733
  const previousRecord = await verifyRegisteredCapsule(request);
@@ -44277,14 +44740,15 @@ async function installRelease(request) {
44277
44740
  try {
44278
44741
  await installClaimedRelease(request, previousRecord, { ...paths, release: paths.release }, claimedArchive);
44279
44742
  } finally {
44280
- await rm(claimedArchive.path, { force: true });
44281
- await rm(request.release.remoteArchive, { force: true });
44743
+ activePreservedAttempts.delete(attemptJournalPath(hostedPreservedFilesRoot(paths)));
44744
+ await rm2(claimedArchive.path, { force: true });
44745
+ await rm2(request.release.remoteArchive, { force: true });
44282
44746
  }
44283
44747
  }
44284
44748
  async function installClaimedRelease(request, previousRecord, paths, claimedArchive) {
44285
44749
  const release = request.release;
44286
44750
  const previousCurrentRelease = previousRecord.currentRelease?.id ? { id: previousRecord.currentRelease.id } : null;
44287
- const previousRegistryContents = await readFile2(registryPath(request), "utf8");
44751
+ const previousRegistryContents = await readFile4(registryPath(request), "utf8");
44288
44752
  const previousCurrentTarget = await readlink(paths.currentLink).catch((error) => {
44289
44753
  if (errorDetails(error).code === "ENOENT") return null;
44290
44754
  throw error;
@@ -44296,20 +44760,20 @@ async function installClaimedRelease(request, previousRecord, paths, claimedArch
44296
44760
  throw helperError("Hosted Capsule release archive ownership changed.", "Upload the release again so the Host helper can claim immutable archive bytes.");
44297
44761
  }
44298
44762
  validateSealedServerEnvPrivateKeyPath(release, paths);
44299
- await mkdir(paths.releases, { recursive: true });
44763
+ await mkdir2(paths.releases, { recursive: true });
44300
44764
  const dataRoot = await openCanonicalRuntimeDataDirectory(paths.data, true);
44301
44765
  await dataRoot.close();
44302
- await mkdir(paths.logs, { recursive: true });
44766
+ await mkdir2(paths.logs, { recursive: true });
44303
44767
  const tempReleaseDirectory = `${paths.release}.tmp-${process.pid}`;
44304
44768
  const tempCurrentLink = `${paths.currentLink}.tmp-${process.pid}`;
44305
- await rm(tempReleaseDirectory, { recursive: true, force: true });
44306
- await rm(tempCurrentLink, { force: true });
44307
- await mkdir(tempReleaseDirectory, { recursive: true });
44769
+ await rm2(tempReleaseDirectory, { recursive: true, force: true });
44770
+ await rm2(tempCurrentLink, { force: true });
44771
+ await mkdir2(tempReleaseDirectory, { recursive: true });
44308
44772
  const extract = spawnSync2("tar", ["-xzf", claimedArchive.path, "-C", tempReleaseDirectory], {
44309
44773
  encoding: "utf8"
44310
44774
  });
44311
44775
  if (extract.error || extract.status !== 0) {
44312
- await rm(tempReleaseDirectory, { recursive: true, force: true });
44776
+ await rm2(tempReleaseDirectory, { recursive: true, force: true });
44313
44777
  throw helperError(
44314
44778
  "Failed to extract Hosted Capsule release archive.",
44315
44779
  "Upload the release again with `sporades host push` and check that tar is installed on the Host server."
@@ -44318,17 +44782,23 @@ async function installClaimedRelease(request, previousRecord, paths, claimedArch
44318
44782
  let installedInventory;
44319
44783
  try {
44320
44784
  installedInventory = await validateExtractedReleaseTree(tempReleaseDirectory, validatedArchive.files);
44785
+ for (const file of resolveDeployFiles(release.deployFiles)) {
44786
+ await prepareHostedRuntimeFileAccess(path5.join(tempReleaseDirectory, file.path), 256, {
44787
+ message: "Unsafe additional release file.",
44788
+ hint: "Upload regular deployment files."
44789
+ });
44790
+ }
44321
44791
  if (await releaseArchiveSha256(claimedArchive.path) !== claimedArchive.sha256) {
44322
44792
  throw helperError("Hosted Capsule release archive ownership changed.", "Upload the release again so the Host helper can claim immutable archive bytes.");
44323
44793
  }
44324
44794
  } catch (error) {
44325
- await rm(tempReleaseDirectory, { recursive: true, force: true });
44795
+ await rm2(tempReleaseDirectory, { recursive: true, force: true });
44326
44796
  throw error;
44327
44797
  }
44328
44798
  try {
44329
- await rename(tempReleaseDirectory, paths.release);
44799
+ await rename2(tempReleaseDirectory, paths.release);
44330
44800
  } catch (error) {
44331
- await rm(tempReleaseDirectory, { recursive: true, force: true });
44801
+ await rm2(tempReleaseDirectory, { recursive: true, force: true });
44332
44802
  const details = errorDetails(error);
44333
44803
  if (details.code === "EEXIST" || details.code === "ENOTEMPTY") {
44334
44804
  throw helperError(
@@ -44342,23 +44812,50 @@ async function installClaimedRelease(request, previousRecord, paths, claimedArch
44342
44812
  try {
44343
44813
  await installSealedServerEnvPrivateKey(release, paths);
44344
44814
  } catch (error) {
44345
- await rm(paths.release, { recursive: true, force: true });
44815
+ await rm2(paths.release, { recursive: true, force: true });
44346
44816
  throw error;
44347
44817
  }
44348
44818
  }
44819
+ const createdSeeds = [];
44820
+ let seedJournal;
44349
44821
  try {
44350
- await symlink(paths.release, tempCurrentLink);
44351
- await rename(tempCurrentLink, paths.currentLink);
44352
- await recordReleaseUploaded(request, release, installedInventory);
44822
+ seedJournal = await beginPreservedFileAttempt(hostedPreservedFilesRoot(paths), release.id, resolveDeployFiles(release.deployFiles).length > 0);
44823
+ if (seedJournal) activePreservedAttempts.add(seedJournal);
44353
44824
  } catch (error) {
44354
- await restoreCurrentReleasePointerTarget(paths.currentLink, previousCurrentTarget);
44355
44825
  await removeInstalledReleasePrivateKey(release, paths);
44356
- await rm(paths.release, { recursive: true, force: true });
44826
+ await rm2(paths.release, { recursive: true, force: true });
44357
44827
  throw error;
44358
44828
  }
44829
+ try {
44830
+ await preparePreservedFiles(resolveDeployFiles(release.deployFiles), paths.release, hostedPreservedFilesRoot(paths), prepareRuntimeDataOwnershipHandle, createdSeeds, seedJournal);
44831
+ await symlink(paths.release, tempCurrentLink);
44832
+ await rename2(tempCurrentLink, paths.currentLink);
44833
+ await recordReleaseUploaded(request, release, installedInventory);
44834
+ } catch (error) {
44835
+ let pointerRestored = false;
44836
+ await rethrowAfterDeployCleanup(error, [
44837
+ async () => {
44838
+ await restoreCurrentReleasePointerTarget(paths.currentLink, previousCurrentTarget);
44839
+ pointerRestored = true;
44840
+ },
44841
+ async () => {
44842
+ if (pointerRestored) await removeInstalledReleasePrivateKey(release, paths);
44843
+ },
44844
+ async () => {
44845
+ if (pointerRestored) await rm2(paths.release, { recursive: true, force: true });
44846
+ },
44847
+ async () => {
44848
+ if (pointerRestored) {
44849
+ await rollbackPreservedFiles(createdSeeds);
44850
+ await finishPreservedFileAttempt(seedJournal);
44851
+ }
44852
+ }
44853
+ ]);
44854
+ }
44359
44855
  let restartResult = null;
44360
44856
  let restartError = null;
44361
44857
  let installRolledBack = false;
44858
+ let seedCleanupError = null;
44362
44859
  const priorRuntime = release.restart ? captureCapsuleRuntimeSettlement(request, previousRecord) : null;
44363
44860
  if (release.restart) {
44364
44861
  try {
@@ -44382,11 +44879,18 @@ async function installClaimedRelease(request, previousRecord, paths, claimedArch
44382
44879
  release
44383
44880
  );
44384
44881
  installRolledBack = true;
44882
+ try {
44883
+ await rollbackPreservedFiles(createdSeeds);
44884
+ await finishPreservedFileAttempt(seedJournal);
44885
+ } catch (error) {
44886
+ seedCleanupError = error;
44887
+ }
44385
44888
  } catch (error) {
44386
44889
  restartError = error;
44387
44890
  }
44388
44891
  }
44389
44892
  }
44893
+ if (!release.restart || restartResult) await finishPreservedFileAttempt(seedJournal);
44390
44894
  const data2 = {
44391
44895
  installed: !installRolledBack,
44392
44896
  restartRequested: Boolean(release.restart),
@@ -44408,6 +44912,9 @@ async function installClaimedRelease(request, previousRecord, paths, claimedArch
44408
44912
  if (restartResult) {
44409
44913
  data2.lifecycle = restartResult;
44410
44914
  }
44915
+ if (seedCleanupError) {
44916
+ data2.cleanup = { complete: false, reason: "preserved-seed-cleanup-failed", message: errorDetails(seedCleanupError).message };
44917
+ }
44411
44918
  if (installRolledBack) {
44412
44919
  data2.rollback = { applied: true, previousCurrentRelease };
44413
44920
  }
@@ -44454,34 +44961,34 @@ async function installClaimedRelease(request, previousRecord, paths, claimedArch
44454
44961
  writeEnvelope({ ok: true, data: data2, error: null });
44455
44962
  }
44456
44963
  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)) {
44964
+ const expectedIncoming = path5.join(request.host.remoteRoot, "incoming", `${request.release.id}.tar.gz`);
44965
+ if (path5.resolve(request.release.remoteArchive) !== path5.resolve(expectedIncoming)) {
44459
44966
  throw helperError("Invalid release install request.", "Upload the release to the canonical Host incoming path and retry `sporades host push`.");
44460
44967
  }
44461
- const claimsDirectory = path3.join(request.host.remoteRoot, ".release-claims");
44462
- await mkdir(claimsDirectory, { recursive: true, mode: 448 });
44463
- const claimsStats = await lstat(claimsDirectory);
44968
+ const claimsDirectory = path5.join(request.host.remoteRoot, ".release-claims");
44969
+ await mkdir2(claimsDirectory, { recursive: true, mode: 448 });
44970
+ const claimsStats = await lstat2(claimsDirectory);
44464
44971
  if (!claimsStats.isDirectory() || claimsStats.isSymbolicLink() || typeof process.getuid === "function" && claimsStats.uid !== process.getuid()) {
44465
44972
  throw helperError("Hosted Capsule release claim directory is unsafe.", "Repair Host helper ownership of the release claim directory and retry.");
44466
44973
  }
44467
44974
  await chmod(claimsDirectory, 448);
44468
- const claimedPath = path3.join(claimsDirectory, `${request.release.id}-${process.pid}-${randomBytes(16).toString("hex")}.tar.gz`);
44469
- await rename(request.release.remoteArchive, claimedPath);
44975
+ const claimedPath = path5.join(claimsDirectory, `${request.release.id}-${process.pid}-${randomBytes(16).toString("hex")}.tar.gz`);
44976
+ await rename2(request.release.remoteArchive, claimedPath);
44470
44977
  try {
44471
- const stats = await lstat(claimedPath);
44978
+ const stats = await lstat2(claimedPath);
44472
44979
  if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1 || stats.size > HOST_RELEASE_ARCHIVE_LIMITS.compressedBytes) {
44473
44980
  throw helperError("Hosted Capsule release archive is unsafe.", "Upload one bounded regular archive file and retry `sporades host push`.");
44474
44981
  }
44475
44982
  await chmod(claimedPath, 384);
44476
44983
  return { path: claimedPath, sha256: await releaseArchiveSha256(claimedPath) };
44477
44984
  } catch (error) {
44478
- await rm(claimedPath, { force: true });
44985
+ await rm2(claimedPath, { force: true });
44479
44986
  throw error;
44480
44987
  }
44481
44988
  }
44482
44989
  async function releaseArchiveSha256(archivePath) {
44483
44990
  return new Promise((resolve, reject) => {
44484
- const hash2 = createHash("sha256");
44991
+ const hash2 = createHash2("sha256");
44485
44992
  const stream = createReadStream(archivePath);
44486
44993
  stream.on("data", (chunk) => hash2.update(chunk));
44487
44994
  stream.on("error", reject);
@@ -44491,7 +44998,7 @@ async function releaseArchiveSha256(archivePath) {
44491
44998
  async function maybeSwapUnclaimedArchiveForTest(release) {
44492
44999
  const replacement = process.env.SPORADES_TEST_HOST_ARCHIVE_SWAP_PATH;
44493
45000
  if (!replacement) return;
44494
- await rename(replacement, release.remoteArchive);
45001
+ await rename2(replacement, release.remoteArchive);
44495
45002
  }
44496
45003
  async function validateExtractedReleaseTree(root, expectedFiles) {
44497
45004
  const expected = new Map(expectedFiles.map((file) => [file.path, file]));
@@ -44500,16 +45007,16 @@ async function validateExtractedReleaseTree(root, expectedFiles) {
44500
45007
  let totalBytes = 0;
44501
45008
  const publicClaims = [];
44502
45009
  async function visit(directory, prefix = "") {
44503
- for (const entry of await readdir(directory, { withFileTypes: true })) {
45010
+ for (const entry of await readdir3(directory, { withFileTypes: true })) {
44504
45011
  const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
44505
45012
  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 !== "..");
45013
+ const safe = relative.length > 0 && !relative.startsWith("/") && !relative.includes("\\") && !relative.includes("\0") && path5.posix.normalize(relative) === relative && Buffer.byteLength(relative, "utf8") <= HOST_RELEASE_ARCHIVE_LIMITS.pathBytes && relative.split("/").every((segment) => segment && segment !== "." && segment !== "..");
44507
45014
  if (!safe || canonical.has(normalized)) {
44508
45015
  throw helperError("Extracted Hosted Capsule release is unsafe.", "Upload a release with unique bounded relative paths.");
44509
45016
  }
44510
45017
  canonical.add(normalized);
44511
- const entryPath = path3.join(directory, entry.name);
44512
- const stats = await lstat(entryPath);
45018
+ const entryPath = path5.join(directory, entry.name);
45019
+ const stats = await lstat2(entryPath);
44513
45020
  if (stats.isSymbolicLink()) {
44514
45021
  throw helperError("Extracted Hosted Capsule release is unsafe.", "Upload regular release files without symbolic links.");
44515
45022
  }
@@ -44538,7 +45045,7 @@ async function validateExtractedReleaseTree(root, expectedFiles) {
44538
45045
  }
44539
45046
  publicClaims.push({ path: publicPath, size: stats.size });
44540
45047
  }
44541
- actual.push({ path: relative, size: stats.size, sha256: createHash("sha256").update(await readFile2(entryPath)).digest("hex") });
45048
+ actual.push({ path: relative, size: stats.size, sha256: createHash2("sha256").update(await readFile4(entryPath)).digest("hex") });
44542
45049
  }
44543
45050
  }
44544
45051
  await visit(root);
@@ -44679,7 +45186,7 @@ function readVerificationHealthTimeoutMs(request) {
44679
45186
  return Math.min(value, 18e4);
44680
45187
  }
44681
45188
  async function routeVerifiedFailureToUnavailable(request, releaseId, message) {
44682
- const lifecycle = normaliseLifecycle(request);
45189
+ const lifecycle = normaliseLifecycle(request, await readRegistryRecordForCapsule(request, "lifecycle"));
44683
45190
  stopAndRemoveContainer(lifecycle.container.name);
44684
45191
  try {
44685
45192
  await writeUnavailableRoute(lifecycle);
@@ -44731,9 +45238,9 @@ async function maybeFallbackToPreviousRelease(request, failedReleaseId, previous
44731
45238
  }
44732
45239
  async function switchCurrentReleaseLink(currentLink, releaseDirectory) {
44733
45240
  const tempCurrentLink = `${currentLink}.tmp-${process.pid}`;
44734
- await rm(tempCurrentLink, { force: true });
45241
+ await rm2(tempCurrentLink, { force: true });
44735
45242
  await symlink(releaseDirectory, tempCurrentLink);
44736
- await rename(tempCurrentLink, currentLink);
45243
+ await rename2(tempCurrentLink, currentLink);
44737
45244
  }
44738
45245
  async function restoreFailedReleaseAfterFallbackRestartFailure(request, failedReleaseId, fallbackReleaseId, reason, restartError) {
44739
45246
  const failedPaths = canonicalRollbackPaths(request, failedReleaseId);
@@ -44749,7 +45256,7 @@ async function restoreFailedReleaseAfterFallbackRestartFailure(request, failedRe
44749
45256
  reason
44750
45257
  );
44751
45258
  try {
44752
- await writeUnavailableRoute(normaliseLifecycle(request));
45259
+ await writeUnavailableRoute(normaliseLifecycle(request, await readRegistryRecordForCapsule(request, "lifecycle")));
44753
45260
  } catch {
44754
45261
  }
44755
45262
  }
@@ -44850,8 +45357,10 @@ async function startCapsule(request, options = {}) {
44850
45357
  const lifecycle = normaliseLifecycle(
44851
45358
  request,
44852
45359
  registryRecord,
44853
- options.trustedRegistryLifecycle === true ? { ignoreProvidedLifecycle: true } : {}
45360
+ { ...options.trustedRegistryLifecycle === true ? { ignoreProvidedLifecycle: true } : {}, releaseId }
44854
45361
  );
45362
+ const recordedRelease = normaliseReleaseHistory(registryRecord).find((entry) => entry.id === releaseId);
45363
+ await preparePreservedReleaseFiles(request, recordedRelease);
44855
45364
  if (options.containerQuiesced !== true) stopAndRemoveContainer(lifecycle.container.name);
44856
45365
  if (options.dataPrepared !== true) await prepareWritableDataPath(paths.data);
44857
45366
  await recordReleaseStartAttempt(request, releaseId);
@@ -44985,7 +45494,7 @@ function releaseIncludesSealedServerEnvPrivateKey(release) {
44985
45494
  return Boolean(release.sealedServerEnvIncluded && privateKey && privateKeyPath);
44986
45495
  }
44987
45496
  function releasePrivateKeyPath(paths, releaseId) {
44988
- return path3.join(paths.data, "sealed-server-env", "releases", `${releaseId}.private.pem`);
45497
+ return path5.join(paths.data, "sealed-server-env", "releases", `${releaseId}.private.pem`);
44989
45498
  }
44990
45499
  async function installSealedServerEnvPrivateKey(release, paths) {
44991
45500
  if (!releaseIncludesSealedServerEnvPrivateKey(release)) return;
@@ -44993,9 +45502,9 @@ async function installSealedServerEnvPrivateKey(release, paths) {
44993
45502
  const privateKeyPath = releasePrivateKeyPath(paths, release.id);
44994
45503
  const dataHandle = await openCanonicalRuntimeDataDirectory(paths.data, true);
44995
45504
  try {
44996
- const rootHandle = await openOrCreateRuntimeDirectory(dataHandle, path3.join(paths.data, "sealed-server-env"));
45505
+ const rootHandle = await openOrCreateRuntimeDirectory(dataHandle, path5.join(paths.data, "sealed-server-env"));
44997
45506
  try {
44998
- const releasesHandle = await openOrCreateRuntimeDirectory(rootHandle, path3.dirname(privateKeyPath));
45507
+ const releasesHandle = await openOrCreateRuntimeDirectory(rootHandle, path5.dirname(privateKeyPath));
44999
45508
  try {
45000
45509
  await publishRuntimeFile(releasesHandle, privateKeyPath, privateKey, 384, "release-private-key-publish");
45001
45510
  } finally {
@@ -45010,15 +45519,18 @@ async function installSealedServerEnvPrivateKey(release, paths) {
45010
45519
  }
45011
45520
  async function removeInstalledReleasePrivateKey(release, paths) {
45012
45521
  if (!releaseIncludesSealedServerEnvPrivateKey(release)) return;
45013
- const privateKeyPath = releasePrivateKeyPath(paths, release.id);
45522
+ await removeReleasePrivateKeyIfPresent(paths, release.id);
45523
+ }
45524
+ async function removeReleasePrivateKeyIfPresent(paths, releaseId) {
45525
+ const privateKeyPath = releasePrivateKeyPath(paths, releaseId);
45014
45526
  const dataHandle = await openCanonicalRuntimeDataDirectory(paths.data, false);
45015
45527
  try {
45016
- const rootHandle = await openOrCreateRuntimeDirectory(dataHandle, path3.join(paths.data, "sealed-server-env"));
45528
+ const rootHandle = await openOrCreateRuntimeDirectory(dataHandle, path5.join(paths.data, "sealed-server-env"));
45017
45529
  try {
45018
- const releasesHandle = await openOrCreateRuntimeDirectory(rootHandle, path3.dirname(privateKeyPath));
45530
+ const releasesHandle = await openOrCreateRuntimeDirectory(rootHandle, path5.dirname(privateKeyPath));
45019
45531
  try {
45020
- const descriptorPath = descriptorChildPath(releasesHandle.fd, path3.basename(privateKeyPath), privateKeyPath);
45021
- const retained = await open(descriptorPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW).catch((error) => {
45532
+ const descriptorPath = descriptorChildPath(releasesHandle.fd, path5.basename(privateKeyPath), privateKeyPath);
45533
+ const retained = await open2(descriptorPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW).catch((error) => {
45022
45534
  if (errorDetails(error).code === "ENOENT") return null;
45023
45535
  throw runtimeDataTrustError(privateKeyPath);
45024
45536
  });
@@ -45027,7 +45539,7 @@ async function removeInstalledReleasePrivateKey(release, paths) {
45027
45539
  const identity = await retained.stat();
45028
45540
  if (!identity.isFile()) throw runtimeDataTrustError(privateKeyPath);
45029
45541
  await assertRuntimeDataPathIdentity(privateKeyPath, { dev: identity.dev, ino: identity.ino }, false);
45030
- await rm(descriptorPath, { force: true });
45542
+ await rm2(descriptorPath, { force: true });
45031
45543
  } finally {
45032
45544
  await retained.close();
45033
45545
  }
@@ -45044,7 +45556,7 @@ async function removeInstalledReleasePrivateKey(release, paths) {
45044
45556
  async function captureReleaseInstallRoute(request, previousRecord) {
45045
45557
  const lifecycle = normaliseLifecycle(request, previousRecord, { ignoreProvidedLifecycle: true });
45046
45558
  const routeFile = lifecycle.routes.running.routeFile;
45047
- const contents = await readFile2(routeFile, "utf8").catch((error) => {
45559
+ const contents = await readFile4(routeFile, "utf8").catch((error) => {
45048
45560
  if (errorDetails(error).code === "ENOENT") return null;
45049
45561
  throw error;
45050
45562
  });
@@ -45052,21 +45564,22 @@ async function captureReleaseInstallRoute(request, previousRecord) {
45052
45564
  }
45053
45565
  async function restoreCurrentReleasePointerTarget(currentLink, previousTarget) {
45054
45566
  const temporary = `${currentLink}.restore-${process.pid}-${randomBytes(8).toString("hex")}`;
45055
- await rm(temporary, { force: true });
45567
+ await rm2(temporary, { force: true });
45056
45568
  if (!previousTarget) {
45057
- await rm(currentLink, { force: true });
45569
+ await rm2(currentLink, { force: true });
45058
45570
  return;
45059
45571
  }
45060
45572
  await symlink(previousTarget, temporary);
45061
- await rename(temporary, currentLink);
45573
+ await rename2(temporary, currentLink);
45062
45574
  }
45063
- async function restoreReleaseInstallRoute(snapshot) {
45575
+ async function restoreReleaseInstallRoute(snapshot, reloadWhenAbsent = false) {
45064
45576
  if (snapshot.contents !== null) {
45065
45577
  await applyManagedRoute(snapshot.lifecycle, snapshot.routeFile, snapshot.contents);
45066
45578
  return;
45067
45579
  }
45068
45580
  await withManagedRouteLock(snapshot.routeFile, async () => {
45069
45581
  const removed = await removeManagedRouteLocked(snapshot.lifecycle, snapshot.routeFile);
45582
+ if (reloadWhenAbsent && !removed.removed) reloadCaddy(snapshot.lifecycle);
45070
45583
  await finalizeRemovedRouteLocked(removed);
45071
45584
  });
45072
45585
  }
@@ -45096,7 +45609,7 @@ async function restoreFailedReleaseInstall(request, paths, previousRecord, previ
45096
45609
  if (!runningRouteRestored && !missingRunningRuntimeSettled) await restoreReleaseInstallRoute(previousRoute);
45097
45610
  if (!missingRunningRuntimeSettled) await writeRegistryContentsAtomic(registryPath(request), previousRegistryContents);
45098
45611
  await removeInstalledReleasePrivateKey(release, paths);
45099
- await rm(paths.release, { recursive: true, force: true });
45612
+ await rm2(paths.release, { recursive: true, force: true });
45100
45613
  } catch {
45101
45614
  throw helperError(
45102
45615
  "Hosted Capsule runtime restoration failed.",
@@ -45120,11 +45633,11 @@ function validateSealedServerEnvPrivateKeyPath(release, paths) {
45120
45633
  );
45121
45634
  }
45122
45635
  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`))] : []
45636
+ path5.resolve(releasePrivateKeyPath(paths, release.id)),
45637
+ path5.resolve(path5.join(paths.data, "sealed-server-env", "server-env.private.pem")),
45638
+ ...fingerprint ? [path5.resolve(path5.join(paths.data, "sealed-server-env", "keys", `${fingerprint}.private.pem`))] : []
45126
45639
  ]);
45127
- if (typeof privateKeyPath !== "string" || !compatiblePrivateKeyPaths.has(path3.resolve(privateKeyPath))) {
45640
+ if (typeof privateKeyPath !== "string" || !compatiblePrivateKeyPaths.has(path5.resolve(privateKeyPath))) {
45128
45641
  throw helperError(
45129
45642
  "Invalid Sealed Server env private key path.",
45130
45643
  "Update the Sporades CLI and retry `sporades host push`."
@@ -45343,7 +45856,7 @@ async function routeRuntimeExhaustionToUnavailable(request, record, health) {
45343
45856
  async function stopCapsule(request, options = {}) {
45344
45857
  validateLifecycleRequest(request);
45345
45858
  await verifyRegisteredCapsule(request, "lifecycle");
45346
- const lifecycle = normaliseLifecycle(request);
45859
+ const lifecycle = normaliseLifecycle(request, await readRegistryRecordForCapsule(request, "lifecycle"));
45347
45860
  stopAndRemoveContainer(lifecycle.container.name);
45348
45861
  await writeUnavailableRoute(lifecycle);
45349
45862
  await updateRegistryStatus(request, "stopped");
@@ -45366,10 +45879,9 @@ async function restartCapsule(request, options = {}) {
45366
45879
  registryRecord,
45367
45880
  options.trustedRegistryLifecycle === true ? { ignoreProvidedLifecycle: true } : {}
45368
45881
  );
45369
- if (options.containerQuiesced !== true) stopAndRemoveContainer(lifecycle.container.name);
45370
45882
  const startResult = await startCapsule(request, {
45371
45883
  write: false,
45372
- containerQuiesced: true,
45884
+ containerQuiesced: options.containerQuiesced === true,
45373
45885
  dataPrepared: options.dataPrepared === true,
45374
45886
  trustedRegistryLifecycle: true
45375
45887
  });
@@ -45518,6 +46030,7 @@ async function listReleases(request) {
45518
46030
  subname: record.subname,
45519
46031
  domain: record.domain,
45520
46032
  hostedUrl: record.hostedUrl ?? `${request.host.scheme ?? "https"}://${record.subname}.${request.host.domain}`,
46033
+ aliasDomains: validateAliasDomains(record.aliasDomains),
45521
46034
  remoteCapsuleId: record.remoteCapsuleId ?? `${request.host.domain}/${record.subname}`
45522
46035
  },
45523
46036
  currentRelease: publicCurrentRelease(record),
@@ -45558,9 +46071,9 @@ async function rollbackRelease(request) {
45558
46071
  await assertRollbackReleaseFiles(request, paths.release, selectedRelease);
45559
46072
  const previousCurrentRelease = record.currentRelease ?? null;
45560
46073
  const tempCurrentLink = `${paths.currentLink}.tmp-${process.pid}`;
45561
- await rm(tempCurrentLink, { force: true });
46074
+ await rm2(tempCurrentLink, { force: true });
45562
46075
  await symlink(paths.release, tempCurrentLink);
45563
- await rename(tempCurrentLink, paths.currentLink);
46076
+ await rename2(tempCurrentLink, paths.currentLink);
45564
46077
  await recordReleaseRollbackSelected(request, releaseId);
45565
46078
  let lifecycle = null;
45566
46079
  let restartError = null;
@@ -45586,7 +46099,7 @@ async function rollbackRelease(request) {
45586
46099
  return;
45587
46100
  }
45588
46101
  try {
45589
- await writeUnavailableRoute(normaliseLifecycle(request));
46102
+ await writeUnavailableRoute(normaliseLifecycle(request, await readRegistryRecordForCapsule(request, "lifecycle")));
45590
46103
  } catch (error) {
45591
46104
  restartError = restartError ?? error;
45592
46105
  }
@@ -45665,6 +46178,7 @@ async function listCapsules(request) {
45665
46178
  subname: record.subname,
45666
46179
  domain: record.domain,
45667
46180
  hostedUrl: record.hostedUrl ?? `${request.host.scheme ?? "https"}://${record.subname}.${request.host.domain}`,
46181
+ aliasDomains: validateAliasDomains(record.aliasDomains),
45668
46182
  registry: {
45669
46183
  remoteCapsuleId: record.remoteCapsuleId ?? `${request.host.domain}/${record.subname}`,
45670
46184
  createdAt: record.createdAt ?? null,
@@ -45709,7 +46223,7 @@ function createUnregisterResult(request, unregister, record, idempotent, route =
45709
46223
  registryRecord: unregister.registryRecord,
45710
46224
  directories: unregister.directories,
45711
46225
  preserved: {
45712
- releases: record.currentRelease?.id ? path3.join(unregister.directories.releases, record.currentRelease.id) : unregister.directories.releases,
46226
+ releases: record.currentRelease?.id ? path5.join(unregister.directories.releases, record.currentRelease.id) : unregister.directories.releases,
45713
46227
  data: unregister.directories.data
45714
46228
  },
45715
46229
  deleteAfter: record.deleteAfter ?? null,
@@ -45758,28 +46272,28 @@ function createDeleteResult(request, deletion, removals) {
45758
46272
  };
45759
46273
  }
45760
46274
  function canonicalReleasePaths(request) {
45761
- const capsule = path3.join(
46275
+ const capsule = path5.join(
45762
46276
  request.host.remoteRoot,
45763
46277
  "hosts",
45764
46278
  request.host.domain,
45765
46279
  "capsules",
45766
46280
  request.capsule.subname
45767
46281
  );
45768
- const releases = path3.join(capsule, "releases");
46282
+ const releases = path5.join(capsule, "releases");
45769
46283
  return {
45770
46284
  capsule,
45771
46285
  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")
46286
+ release: request.release?.id ? path5.join(releases, request.release.id) : null,
46287
+ data: path5.join(capsule, "data"),
46288
+ logs: path5.join(capsule, "logs"),
46289
+ currentLink: path5.join(capsule, "current")
45776
46290
  };
45777
46291
  }
45778
46292
  function canonicalRollbackPaths(request, releaseId) {
45779
46293
  const paths = canonicalReleasePaths({ ...request, release: { id: releaseId, remoteArchive: "", files: [] } });
45780
46294
  return {
45781
46295
  ...paths,
45782
- release: path3.join(paths.releases, releaseId)
46296
+ release: path5.join(paths.releases, releaseId)
45783
46297
  };
45784
46298
  }
45785
46299
  function normaliseLifecycle(request, registryRecord = null, options = {}) {
@@ -45806,12 +46320,12 @@ function normaliseLifecycle(request, registryRecord = null, options = {}) {
45806
46320
  };
45807
46321
  const defaultMounts = {
45808
46322
  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 },
46323
+ { host: path5.join(currentLink, "server.mjs"), container: "/app/server.mjs", mode: "ro" },
46324
+ { host: path5.join(currentLink, "public"), container: "/app/public", mode: "ro" },
46325
+ { host: path5.join(currentLink, "sporades.json"), container: "/app/sporades.json", mode: "ro" },
46326
+ { host: path5.join(currentLink, ".env.sporades.server"), container: "/app/.env.sporades.server", mode: "ro", optional: true },
45813
46327
  {
45814
- host: path3.join(currentLink, ".sporades", "sealed-server-env", "server-env.sealed.json"),
46328
+ host: path5.join(currentLink, ".sporades", "sealed-server-env", "server-env.sealed.json"),
45815
46329
  container: "/app/.sporades/sealed-server-env/server-env.sealed.json",
45816
46330
  mode: "ro",
45817
46331
  optional: true
@@ -45827,6 +46341,8 @@ function normaliseLifecycle(request, registryRecord = null, options = {}) {
45827
46341
  ],
45828
46342
  data: { host: paths.data, container: "/app/data", mode: "rw" }
45829
46343
  };
46344
+ const deployRelease = normaliseReleaseHistory(registryRecord).find((entry) => entry.id === (options.releaseId ?? registryRecord?.currentRelease?.id));
46345
+ const additionalMounts = deployFileMounts(resolveDeployFiles(deployRelease?.source?.deployFiles), currentLink, path5.join(paths.capsule, "preserved-files"));
45830
46346
  const fileMounts = authoritativeSshAuthorizedKeysMount(
45831
46347
  authoritativeSealedServerEnvPrivateKeyMount(
45832
46348
  provided.mounts?.files ?? defaultMounts.files,
@@ -45863,9 +46379,11 @@ function normaliseLifecycle(request, registryRecord = null, options = {}) {
45863
46379
  ...baseImageLabels(helperPackageBaseImage.updatePolicy.mode)
45864
46380
  }
45865
46381
  };
46382
+ const aliasDomains = validateAliasDomains(registryRecord?.aliasDomains);
45866
46383
  const canonicalRoutes = {
45867
46384
  running: {
45868
46385
  hostname: `${subname}.${domain}`,
46386
+ ...aliasDomains.length ? { aliasDomains } : {},
45869
46387
  target: "container",
45870
46388
  containerName,
45871
46389
  port: 4e3,
@@ -45875,6 +46393,7 @@ function normaliseLifecycle(request, registryRecord = null, options = {}) {
45875
46393
  },
45876
46394
  unavailable: {
45877
46395
  hostname: `${subname}.${domain}`,
46396
+ ...aliasDomains.length ? { aliasDomains } : {},
45878
46397
  target: "hosted-capsule-unavailable",
45879
46398
  statusCode: 503,
45880
46399
  routeFile,
@@ -45909,7 +46428,7 @@ function normaliseLifecycle(request, registryRecord = null, options = {}) {
45909
46428
  },
45910
46429
  remoteRoot: request.host.remoteRoot,
45911
46430
  mounts: {
45912
- files: fileMounts,
46431
+ files: [...fileMounts, ...additionalMounts],
45913
46432
  data: defaultMounts.data
45914
46433
  },
45915
46434
  container: canonicalContainer,
@@ -45923,12 +46442,12 @@ function canonicalLifecycleRouteTls(request, registryRecord, provided) {
45923
46442
  if (!(/* @__PURE__ */ new Set(["automatic", "cloudflare-origin"])).has(mode) || suppliedModes.some((value) => value !== mode)) {
45924
46443
  throw invalidLifecycleAuthorityError();
45925
46444
  }
45926
- const directory = path3.join(request.host.remoteRoot, "hosts", request.host.domain, "tls");
46445
+ const directory = path5.join(request.host.remoteRoot, "hosts", request.host.domain, "tls");
45927
46446
  return {
45928
46447
  mode,
45929
46448
  directory,
45930
- certificate: mode === "cloudflare-origin" ? path3.join(directory, "origin.crt") : null,
45931
- key: mode === "cloudflare-origin" ? path3.join(directory, "origin.key") : null
46449
+ certificate: mode === "cloudflare-origin" ? path5.join(directory, "origin.crt") : null,
46450
+ key: mode === "cloudflare-origin" ? path5.join(directory, "origin.key") : null
45932
46451
  };
45933
46452
  }
45934
46453
  function invalidLifecycleAuthorityError() {
@@ -45971,14 +46490,14 @@ function assertCanonicalLifecycleAuthority(provided, canonical) {
45971
46490
  const allowedByContainer = new Map(canonical.defaultMounts.files.map((mount) => [mount.container, mount]));
45972
46491
  const privateKeyContainerPath = "/app/.sporades/sealed-server-env/server-env.private.pem";
45973
46492
  const legacySealedServerEnvPrivateKeyMount = {
45974
- host: path3.join(canonical.paths.data, "sealed-server-env", "server-env.private.pem"),
46493
+ host: path5.join(canonical.paths.data, "sealed-server-env", "server-env.private.pem"),
45975
46494
  container: privateKeyContainerPath,
45976
46495
  mode: "ro",
45977
46496
  optional: true
45978
46497
  };
45979
46498
  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" }
46499
+ { host: path5.join(canonical.currentLink, "client.js"), container: "/app/client.js", mode: "ro" },
46500
+ { host: path5.join(canonical.currentLink, "index.html"), container: "/app/index.html", mode: "ro" }
45982
46501
  ];
45983
46502
  for (const mount of legacyAllowed) allowedByContainer.set(mount.container, mount);
45984
46503
  const seen = /* @__PURE__ */ new Set();
@@ -46260,10 +46779,10 @@ function inspectContainerLifecycle(containerName) {
46260
46779
  };
46261
46780
  }
46262
46781
  async function readCapsuleRegistryRecords(request) {
46263
- const registryDirectory = path3.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", "capsules");
46782
+ const registryDirectory = path5.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", "capsules");
46264
46783
  let entries;
46265
46784
  try {
46266
- entries = await readdir(registryDirectory, { withFileTypes: true });
46785
+ entries = await readdir3(registryDirectory, { withFileTypes: true });
46267
46786
  } catch (error) {
46268
46787
  if (errorDetails(error).code === "ENOENT") {
46269
46788
  return [];
@@ -46273,10 +46792,10 @@ async function readCapsuleRegistryRecords(request) {
46273
46792
  const records = [];
46274
46793
  const files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort();
46275
46794
  for (const file of files) {
46276
- const recordPath = path3.join(registryDirectory, file);
46795
+ const recordPath = path5.join(registryDirectory, file);
46277
46796
  let record;
46278
46797
  try {
46279
- record = JSON.parse(await readFile2(recordPath, "utf8"));
46798
+ record = JSON.parse(await readFile4(recordPath, "utf8"));
46280
46799
  } catch (error) {
46281
46800
  if (error instanceof SyntaxError) {
46282
46801
  throw helperError(
@@ -46404,19 +46923,21 @@ function normaliseProvidedBaseImage(value) {
46404
46923
  version: provided.version ?? SPORADES_BASE_IMAGE.version
46405
46924
  };
46406
46925
  }
46407
- function normaliseRegistration(request) {
46926
+ function normaliseRegistration(request, existing = null) {
46927
+ const aliasDomains = validateAliasDomains(request.registration?.aliasDomains ?? existing?.aliasDomains);
46408
46928
  const subname = request.capsule.subname;
46409
46929
  const domain = request.host.domain;
46410
46930
  const remoteRoot = request.host.remoteRoot;
46411
46931
  const scheme = request.host.scheme ?? "https";
46412
46932
  const hostedUrl = `${scheme}://${subname}.${domain}`;
46413
46933
  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`);
46934
+ const capsuleDirectory = path5.join(remoteRoot, "hosts", domain, "capsules", subname);
46935
+ const routeFile = path5.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`);
46416
46936
  const routeTls = normaliseRegistrationTls(request);
46417
46937
  const accessLog = canonicalCapsuleHttpLogPath(request, remoteRoot);
46418
46938
  const route = {
46419
46939
  hostname: `${subname}.${domain}`,
46940
+ ...aliasDomains.length ? { aliasDomains } : {},
46420
46941
  target: "hosted-capsule-unavailable",
46421
46942
  statusCode: 503,
46422
46943
  routeFile,
@@ -46429,12 +46950,13 @@ function normaliseRegistration(request) {
46429
46950
  remoteRoot,
46430
46951
  hostedUrl,
46431
46952
  remoteCapsuleId,
46432
- registryRecord: path3.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46953
+ aliasDomains,
46954
+ registryRecord: path5.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46433
46955
  directories: {
46434
46956
  capsule: capsuleDirectory,
46435
- releases: path3.join(capsuleDirectory, "releases"),
46436
- data: path3.join(capsuleDirectory, "data"),
46437
- logs: path3.join(capsuleDirectory, "logs")
46957
+ releases: path5.join(capsuleDirectory, "releases"),
46958
+ data: path5.join(capsuleDirectory, "data"),
46959
+ logs: path5.join(capsuleDirectory, "logs")
46438
46960
  },
46439
46961
  baseImage: normaliseProvidedBaseImage(request.registration?.baseImage),
46440
46962
  route,
@@ -46452,19 +46974,19 @@ function normaliseUnregister(request) {
46452
46974
  const scheme = request.host.scheme ?? "https";
46453
46975
  const hostedUrl = `${scheme}://${subname}.${domain}`;
46454
46976
  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`);
46977
+ const capsuleDirectory = path5.join(remoteRoot, "hosts", domain, "capsules", subname);
46978
+ const routeFile = path5.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`);
46457
46979
  const containerName = createHostedContainerName(domain, subname);
46458
46980
  return {
46459
46981
  subname,
46460
46982
  domain,
46461
46983
  hostedUrl,
46462
46984
  remoteCapsuleId,
46463
- registryRecord: path3.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46985
+ registryRecord: path5.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46464
46986
  directories: {
46465
46987
  capsule: capsuleDirectory,
46466
- releases: path3.join(capsuleDirectory, "releases"),
46467
- data: path3.join(capsuleDirectory, "data")
46988
+ releases: path5.join(capsuleDirectory, "releases"),
46989
+ data: path5.join(capsuleDirectory, "data")
46468
46990
  },
46469
46991
  container: {
46470
46992
  name: containerName
@@ -46485,21 +47007,21 @@ function normaliseDeletion(request) {
46485
47007
  const domain = request.host.domain;
46486
47008
  const remoteRoot = request.host.remoteRoot;
46487
47009
  const scheme = request.host.scheme ?? "https";
46488
- const capsuleDirectory = path3.join(remoteRoot, "hosts", domain, "capsules", subname);
47010
+ const capsuleDirectory = path5.join(remoteRoot, "hosts", domain, "capsules", subname);
46489
47011
  return {
46490
47012
  subname,
46491
47013
  domain,
46492
47014
  hostedUrl: `${scheme}://${subname}.${domain}`,
46493
47015
  remoteCapsuleId: `${domain}/${subname}`,
46494
- registryRecord: path3.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
47016
+ registryRecord: path5.join(remoteRoot, "hosts", domain, "registry", "capsules", `${subname}.json`),
46495
47017
  directories: {
46496
47018
  capsule: capsuleDirectory,
46497
- releases: path3.join(capsuleDirectory, "releases"),
46498
- data: path3.join(capsuleDirectory, "data")
47019
+ releases: path5.join(capsuleDirectory, "releases"),
47020
+ data: path5.join(capsuleDirectory, "data")
46499
47021
  },
46500
47022
  route: {
46501
47023
  hostname: `${subname}.${domain}`,
46502
- routeFile: path3.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`)
47024
+ routeFile: path5.join(remoteRoot, "caddy", "hosts", domain, `${subname}.caddy`)
46503
47025
  },
46504
47026
  lifecycle: {
46505
47027
  remoteRoot
@@ -46510,17 +47032,17 @@ function normaliseRegistrationTls(request) {
46510
47032
  const remoteRoot = request.host.remoteRoot;
46511
47033
  const domain = request.host.domain;
46512
47034
  const tlsMode = request.registration?.bootstrap?.tls?.mode ?? request.bootstrap?.tls?.mode ?? "automatic";
46513
- const tlsDirectory = path3.join(remoteRoot, "hosts", domain, "tls");
47035
+ const tlsDirectory = path5.join(remoteRoot, "hosts", domain, "tls");
46514
47036
  return {
46515
47037
  mode: tlsMode,
46516
47038
  directory: tlsDirectory,
46517
- certificate: tlsMode === "cloudflare-origin" ? path3.join(tlsDirectory, "origin.crt") : null,
46518
- key: tlsMode === "cloudflare-origin" ? path3.join(tlsDirectory, "origin.key") : null
47039
+ certificate: tlsMode === "cloudflare-origin" ? path5.join(tlsDirectory, "origin.crt") : null,
47040
+ key: tlsMode === "cloudflare-origin" ? path5.join(tlsDirectory, "origin.key") : null
46519
47041
  };
46520
47042
  }
46521
47043
  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`);
47044
+ const caddyfile = path5.join(request.host.remoteRoot, "caddy", "Caddyfile");
47045
+ const domainInclude = path5.join(request.host.remoteRoot, "caddy", "hosts", `${request.host.domain}.caddy`);
46524
47046
  const bootstrapped = await pathExists(caddyfile) && await pathExists(domainInclude);
46525
47047
  if (bootstrapped) {
46526
47048
  return;
@@ -46539,6 +47061,7 @@ function createRegistrationRecord(registration, sealedServerEnv = null) {
46539
47061
  domain: registration.domain,
46540
47062
  remoteCapsuleId: registration.remoteCapsuleId,
46541
47063
  hostedUrl: registration.hostedUrl,
47064
+ aliasDomains: registration.aliasDomains,
46542
47065
  status: "registered",
46543
47066
  createdAt: now,
46544
47067
  updatedAt: now,
@@ -46598,11 +47121,11 @@ async function generateHostSealedEnvKeyPair(dataDirectory) {
46598
47121
  };
46599
47122
  }
46600
47123
  async function openOrCreateRuntimeDirectory(parentHandle, targetPath) {
46601
- const descriptorPath = descriptorChildPath(parentHandle.fd, path3.basename(targetPath), targetPath);
46602
- await mkdir(descriptorPath, { mode: 448 }).catch((error) => {
47124
+ const descriptorPath = descriptorChildPath(parentHandle.fd, path5.basename(targetPath), targetPath);
47125
+ await mkdir2(descriptorPath, { mode: 448 }).catch((error) => {
46603
47126
  if (errorDetails(error).code !== "EEXIST") throw runtimeDataTrustError(targetPath);
46604
47127
  });
46605
- const handle = await open(
47128
+ const handle = await open2(
46606
47129
  descriptorPath,
46607
47130
  fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | (fsConstants.O_DIRECTORY ?? 0)
46608
47131
  ).catch(() => {
@@ -46619,8 +47142,8 @@ async function openOrCreateRuntimeDirectory(parentHandle, targetPath) {
46619
47142
  return handle;
46620
47143
  }
46621
47144
  async function writeExclusiveRuntimeFile(parentHandle, targetPath, contents, mode) {
46622
- const descriptorPath = descriptorChildPath(parentHandle.fd, path3.basename(targetPath), targetPath);
46623
- const handle = await open(
47145
+ const descriptorPath = descriptorChildPath(parentHandle.fd, path5.basename(targetPath), targetPath);
47146
+ const handle = await open2(
46624
47147
  descriptorPath,
46625
47148
  fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW,
46626
47149
  mode
@@ -46642,24 +47165,24 @@ async function writeExclusiveRuntimeFile(parentHandle, targetPath, contents, mod
46642
47165
  }
46643
47166
  }
46644
47167
  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")}`);
47168
+ const temporaryPath = path5.join(path5.dirname(targetPath), `.${path5.basename(targetPath)}.tmp-${process.pid}-${randomBytes(8).toString("hex")}`);
46646
47169
  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);
47170
+ const temporaryDescriptor = descriptorChildPath(parentHandle.fd, path5.basename(temporaryPath), temporaryPath);
47171
+ const targetDescriptor = descriptorChildPath(parentHandle.fd, path5.basename(targetPath), targetPath);
46649
47172
  try {
46650
47173
  await pauseRuntimeTreePublication(boundary, targetPath);
46651
47174
  const parentIdentity = await parentHandle.stat();
46652
- await assertRuntimeDataPathIdentity(path3.dirname(targetPath), { dev: parentIdentity.dev, ino: parentIdentity.ino }, true);
47175
+ await assertRuntimeDataPathIdentity(path5.dirname(targetPath), { dev: parentIdentity.dev, ino: parentIdentity.ino }, true);
46653
47176
  try {
46654
- const existing = await lstat(targetPath);
47177
+ const existing = await lstat2(targetPath);
46655
47178
  if (!existing.isFile() || existing.isSymbolicLink()) throw runtimeDataTrustError(targetPath);
46656
47179
  } catch (error) {
46657
47180
  if (errorDetails(error).code !== "ENOENT") throw error;
46658
47181
  }
46659
- await rename(temporaryDescriptor, targetDescriptor).catch(() => {
47182
+ await rename2(temporaryDescriptor, targetDescriptor).catch(() => {
46660
47183
  throw runtimeDataTrustError(targetPath);
46661
47184
  });
46662
- const installed = await open(targetDescriptor, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW).catch(() => {
47185
+ const installed = await open2(targetDescriptor, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW).catch(() => {
46663
47186
  throw runtimeDataTrustError(targetPath);
46664
47187
  });
46665
47188
  try {
@@ -46670,7 +47193,7 @@ async function publishRuntimeFile(parentHandle, targetPath, contents, mode, boun
46670
47193
  await installed.close();
46671
47194
  }
46672
47195
  } finally {
46673
- await rm(temporaryDescriptor, { force: true }).catch(() => {
47196
+ await rm2(temporaryDescriptor, { force: true }).catch(() => {
46674
47197
  });
46675
47198
  }
46676
47199
  }
@@ -46692,13 +47215,13 @@ async function cleanupUnreferencedHostSealedEnvKeys(dataDirectory, referencedFin
46692
47215
  try {
46693
47216
  const keysIdentity = await keysHandle.stat();
46694
47217
  const descriptorDirectory = process.platform === "linux" ? `/proc/self/fd/${keysHandle.fd}` : paths.keys;
46695
- const entries = await readdir(descriptorDirectory);
47218
+ const entries = await readdir3(descriptorDirectory);
46696
47219
  for (const entry of entries) {
46697
47220
  const match = /^([a-f0-9]{16})\.(private|public)\.pem$/.exec(entry);
46698
47221
  if (!match || referencedFingerprints.has(match[1])) continue;
46699
- const targetPath = path3.join(paths.keys, entry);
47222
+ const targetPath = path5.join(paths.keys, entry);
46700
47223
  const descriptorPath = descriptorChildPath(keysHandle.fd, entry, targetPath);
46701
- const retained = await open(descriptorPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW).catch(() => {
47224
+ const retained = await open2(descriptorPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW).catch(() => {
46702
47225
  throw runtimeDataTrustError(targetPath);
46703
47226
  });
46704
47227
  try {
@@ -46707,7 +47230,7 @@ async function cleanupUnreferencedHostSealedEnvKeys(dataDirectory, referencedFin
46707
47230
  await pauseRuntimeTreePublication("sealed-key-cleanup", targetPath);
46708
47231
  await assertRuntimeDataPathIdentity(paths.keys, { dev: keysIdentity.dev, ino: keysIdentity.ino }, true);
46709
47232
  await assertRuntimeDataPathIdentity(targetPath, { dev: details.dev, ino: details.ino }, false);
46710
- await rm(descriptorPath, { force: true });
47233
+ await rm2(descriptorPath, { force: true });
46711
47234
  } finally {
46712
47235
  await retained.close();
46713
47236
  }
@@ -46754,7 +47277,7 @@ async function inspectHostSealedEnvKey(record, remoteRoot) {
46754
47277
  if (typeof publicKeyFingerprint !== "string" || publicKeyFingerprint.length === 0) {
46755
47278
  return null;
46756
47279
  }
46757
- const dataDirectory = path3.join(remoteRoot, "hosts", record.domain, "capsules", record.subname, "data");
47280
+ const dataDirectory = path5.join(remoteRoot, "hosts", record.domain, "capsules", record.subname, "data");
46758
47281
  const paths = hostSealedEnvKeyPaths(dataDirectory, publicKeyFingerprint);
46759
47282
  const [publicKeyReadable, privateKeyReadable] = await Promise.all([
46760
47283
  pathReadable(paths.publicKey),
@@ -46774,7 +47297,7 @@ async function inspectHostSealedEnvKey(record, remoteRoot) {
46774
47297
  try {
46775
47298
  return {
46776
47299
  ...base,
46777
- publicKey: await readFile2(paths.publicKey, "utf8")
47300
+ publicKey: await readFile4(paths.publicKey, "utf8")
46778
47301
  };
46779
47302
  } catch (error) {
46780
47303
  if (errorDetails(error).code === "ENOENT") {
@@ -46821,17 +47344,17 @@ function currentReleaseSealedServerEnvFingerprint(record) {
46821
47344
  return typeof fingerprint === "string" ? fingerprint : null;
46822
47345
  }
46823
47346
  function hostSealedEnvKeyPaths(dataDirectory, fingerprint) {
46824
- const root = path3.join(dataDirectory, "sealed-server-env");
46825
- const keys = path3.join(root, "keys");
47347
+ const root = path5.join(dataDirectory, "sealed-server-env");
47348
+ const keys = path5.join(root, "keys");
46826
47349
  return {
46827
47350
  root,
46828
47351
  keys,
46829
- privateKey: path3.join(keys, `${fingerprint}.private.pem`),
46830
- publicKey: path3.join(keys, `${fingerprint}.public.pem`)
47352
+ privateKey: path5.join(keys, `${fingerprint}.private.pem`),
47353
+ publicKey: path5.join(keys, `${fingerprint}.public.pem`)
46831
47354
  };
46832
47355
  }
46833
47356
  function fingerprintPublicKey(publicKey) {
46834
- return createHash("sha256").update(publicKey).digest("hex").slice(0, 16);
47357
+ return createHash2("sha256").update(publicKey).digest("hex").slice(0, 16);
46835
47358
  }
46836
47359
  function reactivateRegistrationRecord(record, sealedServerEnv = null) {
46837
47360
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -46887,20 +47410,20 @@ function normaliseBootstrap(request) {
46887
47410
  const provided = request.bootstrap ?? {};
46888
47411
  const remoteRoot = request.host.remoteRoot;
46889
47412
  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");
47413
+ const caddyDirectory = path5.join(remoteRoot, "caddy");
47414
+ const domainDirectory = path5.join(remoteRoot, "hosts", domain);
47415
+ const tlsDirectory = path5.join(domainDirectory, "tls");
46893
47416
  const expectedDirectories = {
46894
47417
  remoteRoot,
46895
- bin: path3.join(remoteRoot, "bin"),
46896
- incoming: path3.join(remoteRoot, "incoming"),
47418
+ bin: path5.join(remoteRoot, "bin"),
47419
+ incoming: path5.join(remoteRoot, "incoming"),
46897
47420
  caddy: caddyDirectory,
46898
- caddyHosts: path3.join(caddyDirectory, "hosts"),
46899
- hosts: path3.join(remoteRoot, "hosts"),
47421
+ caddyHosts: path5.join(caddyDirectory, "hosts"),
47422
+ hosts: path5.join(remoteRoot, "hosts"),
46900
47423
  domain: domainDirectory,
46901
47424
  tls: tlsDirectory,
46902
- registry: path3.join(domainDirectory, "registry"),
46903
- capsules: path3.join(domainDirectory, "capsules")
47425
+ registry: path5.join(domainDirectory, "registry"),
47426
+ capsules: path5.join(domainDirectory, "capsules")
46904
47427
  };
46905
47428
  for (const [name2, value] of Object.entries(provided.directories ?? {})) {
46906
47429
  if (!(name2 in expectedDirectories) || value !== expectedDirectories[name2]) throw bootstrapPathError();
@@ -46910,8 +47433,8 @@ function normaliseBootstrap(request) {
46910
47433
  const directories = {
46911
47434
  ...expectedDirectories
46912
47435
  };
46913
- const expectedCertificate = path3.join(directories.tls, "origin.crt");
46914
- const expectedKey = path3.join(directories.tls, "origin.key");
47436
+ const expectedCertificate = path5.join(directories.tls, "origin.crt");
47437
+ const expectedKey = path5.join(directories.tls, "origin.key");
46915
47438
  if (provided.tls?.directory !== void 0 && provided.tls.directory !== directories.tls) throw bootstrapPathError();
46916
47439
  if (provided.tls?.certificate != null && provided.tls.certificate !== expectedCertificate) throw bootstrapPathError();
46917
47440
  if (provided.tls?.key != null && provided.tls.key !== expectedKey) throw bootstrapPathError();
@@ -46921,8 +47444,8 @@ function normaliseBootstrap(request) {
46921
47444
  certificate: tlsMode === "cloudflare-origin" ? expectedCertificate : null,
46922
47445
  key: tlsMode === "cloudflare-origin" ? expectedKey : null
46923
47446
  };
46924
- const expectedManagedInclude = path3.join(directories.caddy, "sporades-hosted-domains.caddy");
46925
- const expectedDomainInclude = path3.join(directories.caddyHosts, `${domain}.caddy`);
47447
+ const expectedManagedInclude = path5.join(directories.caddy, "sporades-hosted-domains.caddy");
47448
+ const expectedDomainInclude = path5.join(directories.caddyHosts, `${domain}.caddy`);
46926
47449
  const expectedAccessLog = defaultCaddyAccessLogPath(remoteRoot);
46927
47450
  if (provided.caddy?.managedInclude !== void 0 && provided.caddy.managedInclude !== expectedManagedInclude) throw bootstrapPathError();
46928
47451
  if (provided.caddy?.domainInclude !== void 0 && provided.caddy.domainInclude !== expectedDomainInclude) throw bootstrapPathError();
@@ -46937,11 +47460,11 @@ function normaliseBootstrap(request) {
46937
47460
  tls,
46938
47461
  network: provided.network ?? hostHelperConfig.hostedCapsule.dockerNetwork,
46939
47462
  caddy: {
46940
- caddyfile: path3.join(directories.caddy, "Caddyfile"),
47463
+ caddyfile: path5.join(directories.caddy, "Caddyfile"),
46941
47464
  managedInclude: expectedManagedInclude,
46942
47465
  domainInclude: expectedDomainInclude,
46943
- routesDirectory: path3.join(directories.caddyHosts, domain),
46944
- healthRoute: path3.join(directories.caddyHosts, domain, "host.caddy"),
47466
+ routesDirectory: path5.join(directories.caddyHosts, domain),
47467
+ healthRoute: path5.join(directories.caddyHosts, domain, "host.caddy"),
46945
47468
  accessLog: expectedAccessLog
46946
47469
  }
46947
47470
  };
@@ -46954,7 +47477,7 @@ function bootstrapPathError() {
46954
47477
  }
46955
47478
  function bootstrapTrustManifest(request) {
46956
47479
  const bootstrap = normaliseBootstrap(request);
46957
- const placeholder = path3.join(bootstrap.caddy.routesDirectory, ".sporades-placeholder.caddy");
47480
+ const placeholder = path5.join(bootstrap.caddy.routesDirectory, ".sporades-placeholder.caddy");
46958
47481
  const finalFiles = [
46959
47482
  { path: bootstrap.caddy.caddyfile },
46960
47483
  { path: bootstrap.caddy.managedInclude },
@@ -46969,8 +47492,8 @@ function bootstrapTrustManifest(request) {
46969
47492
  return {
46970
47493
  directories: [
46971
47494
  ...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") },
47495
+ { path: path5.dirname(bootstrap.caddy.accessLog), caddyOwned: true },
47496
+ { path: path5.join(bootstrap.directories.registry, "capsules") },
46974
47497
  { path: bootstrap.caddy.routesDirectory }
46975
47498
  ],
46976
47499
  finalFiles
@@ -46982,24 +47505,24 @@ async function ensureBootstrapDirectories(bootstrap) {
46982
47505
  bootstrap.directories.bin,
46983
47506
  bootstrap.directories.incoming,
46984
47507
  bootstrap.directories.caddy,
46985
- path3.dirname(bootstrap.caddy.accessLog),
47508
+ path5.dirname(bootstrap.caddy.accessLog),
46986
47509
  bootstrap.directories.caddyHosts,
46987
47510
  bootstrap.directories.hosts,
46988
47511
  bootstrap.directories.domain,
46989
47512
  bootstrap.directories.tls,
46990
47513
  bootstrap.directories.registry,
46991
- path3.join(bootstrap.directories.registry, "capsules"),
47514
+ path5.join(bootstrap.directories.registry, "capsules"),
46992
47515
  bootstrap.directories.capsules,
46993
47516
  bootstrap.caddy.routesDirectory
46994
47517
  ];
46995
47518
  for (const directory of directories) {
46996
- const details = await lstat(directory);
47519
+ const details = await lstat2(directory);
46997
47520
  if (!details.isDirectory() || details.isSymbolicLink()) throw routeTrustError();
46998
47521
  }
46999
47522
  }
47000
47523
  async function provisionCaddyAccessLog(request, bootstrap) {
47001
47524
  const logFile = bootstrap.caddy.accessLog;
47002
- const logDirectory = path3.dirname(logFile);
47525
+ const logDirectory = path5.dirname(logFile);
47003
47526
  const caddyUser = resolveCaddyServiceUser();
47004
47527
  if (!caddyUser) {
47005
47528
  throw helperError(
@@ -47034,7 +47557,7 @@ async function provisionCaddyOwnedLogDescriptors(options) {
47034
47557
  const fileIdentity = trust.finalFiles.find((entry) => entry.path === logFile && entry.caddyOwned);
47035
47558
  if (!directoryIdentity || !fileIdentity) throw routeTrustError();
47036
47559
  await assertManagedLogMutationBoundary(logFile);
47037
- const directoryHandle = await open(
47560
+ const directoryHandle = await open2(
47038
47561
  logDirectory,
47039
47562
  fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | (fsConstants.O_DIRECTORY ?? 0)
47040
47563
  ).catch(() => {
@@ -47043,17 +47566,17 @@ async function provisionCaddyOwnedLogDescriptors(options) {
47043
47566
  try {
47044
47567
  let directoryDetails = await directoryHandle.stat();
47045
47568
  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;
47569
+ const descriptorFile = process.platform === "linux" ? `/proc/self/fd/${directoryHandle.fd}/${path5.basename(logFile)}` : logFile;
47047
47570
  let fileHandle;
47048
47571
  try {
47049
- fileHandle = await open(
47572
+ fileHandle = await open2(
47050
47573
  descriptorFile,
47051
47574
  fsConstants.O_RDWR | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW,
47052
47575
  416
47053
47576
  );
47054
47577
  } catch (error) {
47055
47578
  if (errorDetails(error).code !== "EEXIST") throw routeTrustError();
47056
- fileHandle = await open(descriptorFile, fsConstants.O_RDWR | fsConstants.O_NOFOLLOW).catch(() => {
47579
+ fileHandle = await open2(descriptorFile, fsConstants.O_RDWR | fsConstants.O_NOFOLLOW).catch(() => {
47057
47580
  throw routeTrustError();
47058
47581
  });
47059
47582
  }
@@ -47085,10 +47608,10 @@ async function provisionCaddyOwnedLogDescriptors(options) {
47085
47608
  directoryDetails = await directoryHandle.stat();
47086
47609
  fileDetails = await fileHandle.stat();
47087
47610
  }
47088
- const directoryPathDetails = await lstat(logDirectory).catch(() => {
47611
+ const directoryPathDetails = await lstat2(logDirectory).catch(() => {
47089
47612
  throw routeTrustError();
47090
47613
  });
47091
- const filePathDetails = await lstat(logFile).catch(() => {
47614
+ const filePathDetails = await lstat2(logFile).catch(() => {
47092
47615
  throw routeTrustError();
47093
47616
  });
47094
47617
  if (!directoryPathDetails.isDirectory() || directoryPathDetails.isSymbolicLink() || !filePathDetails.isFile() || filePathDetails.isSymbolicLink() || directoryPathDetails.dev !== directoryDetails.dev || directoryPathDetails.ino !== directoryDetails.ino || filePathDetails.dev !== fileDetails.dev || filePathDetails.ino !== fileDetails.ino || !trustedExactOwnerMetadata(directoryDetails, owner) || !trustedExactOwnerMetadata(fileDetails, owner) || (Number(directoryDetails.mode) & 511) !== 488 || (Number(fileDetails.mode) & 511) !== 416) throw routeTrustError();
@@ -47120,7 +47643,7 @@ async function provisionRouteLogFile(route, mutationBoundary) {
47120
47643
  if (!logFile) {
47121
47644
  return;
47122
47645
  }
47123
- const logDirectory = path3.dirname(logFile);
47646
+ const logDirectory = path5.dirname(logFile);
47124
47647
  const caddyUser = resolveCaddyServiceUser() ?? {
47125
47648
  uid: String(process.geteuid?.() ?? process.getuid?.() ?? 0),
47126
47649
  gid: String(process.getegid?.() ?? process.getgid?.() ?? 0)
@@ -47188,15 +47711,15 @@ async function installCaddyBootstrapConfig(request, bootstrap) {
47188
47711
  const caddyfile = bootstrap.caddy.caddyfile;
47189
47712
  const managedInclude = bootstrap.caddy.managedInclude;
47190
47713
  const domainInclude = bootstrap.caddy.domainInclude;
47191
- const placeholderRoute = path3.join(bootstrap.caddy.routesDirectory, ".sporades-placeholder.caddy");
47714
+ const placeholderRoute = path5.join(bootstrap.caddy.routesDirectory, ".sporades-placeholder.caddy");
47192
47715
  await atomicPublishBootstrapFile(placeholderRoute, "# Sporades keeps this placeholder so Caddy route imports are valid before Capsules are registered.\n", "bootstrap-placeholder");
47193
47716
  await atomicPublishBootstrapFile(bootstrap.caddy.healthRoute, renderHostHealthRoute(request.host.domain, bootstrap.tls), "bootstrap-health-route");
47194
47717
  await writeManagedCaddyfile(caddyfile, `import ${managedInclude}`);
47195
47718
  await atomicPublishBootstrapFile(managedInclude, `# Sporades-managed Hosted domain include list.
47196
- import ${path3.join(bootstrap.directories.caddyHosts, "*.caddy")}
47719
+ import ${path5.join(bootstrap.directories.caddyHosts, "*.caddy")}
47197
47720
  `, "bootstrap-managed-include");
47198
47721
  await atomicPublishBootstrapFile(domainInclude, `# Sporades-managed routes for ${request.host.domain}.
47199
- import ${path3.join(bootstrap.caddy.routesDirectory, "*.caddy")}
47722
+ import ${path5.join(bootstrap.caddy.routesDirectory, "*.caddy")}
47200
47723
  `, "bootstrap-domain-include");
47201
47724
  await assertActiveManagedRouteTrust(null);
47202
47725
  validateCaddyBootstrap(caddyfile);
@@ -47240,7 +47763,7 @@ ${end}
47240
47763
  let existing = "";
47241
47764
  await assertBootstrapMutationBoundary("bootstrap-caddyfile-read", [caddyfile]);
47242
47765
  try {
47243
- existing = await readFile2(caddyfile, "utf8");
47766
+ existing = await readFile4(caddyfile, "utf8");
47244
47767
  } catch (error) {
47245
47768
  if (errorDetails(error).code !== "ENOENT") {
47246
47769
  throw error;
@@ -47263,11 +47786,11 @@ async function atomicPublishBootstrapFile(target, contents, boundary) {
47263
47786
  await writeFile(temporary, contents, { flag: "wx", mode: 420 });
47264
47787
  try {
47265
47788
  await assertBootstrapMutationBoundary(`${boundary}-publish`, [target, temporary]);
47266
- await rename(temporary, target);
47789
+ await rename2(temporary, target);
47267
47790
  await refreshTrustedBootstrapFinalFileIdentity(target);
47268
47791
  } catch (error) {
47269
47792
  await assertBootstrapMutationBoundary(`${boundary}-cleanup`, [target, temporary]);
47270
- await rm(temporary, { force: true });
47793
+ await rm2(temporary, { force: true });
47271
47794
  throw error;
47272
47795
  }
47273
47796
  }
@@ -47431,6 +47954,8 @@ async function dockerRunArgs(lifecycle, releaseId, runtimeProbe) {
47431
47954
  "--env",
47432
47955
  `SPORADES_PUBLIC_ORIGIN=${lifecycle.hostedUrl}`,
47433
47956
  "--env",
47957
+ `SPORADES_PUBLIC_ALIASES=${JSON.stringify(validateAliasDomains(lifecycle.routes.running.aliasDomains).map((hostname) => `https://${hostname}`))}`,
47958
+ "--env",
47434
47959
  `SPORADES_RELEASE_ID=${releaseId}`
47435
47960
  );
47436
47961
  args.push("--publish", `127.0.0.1::${lifecycle.routes.running.port ?? 4e3}`);
@@ -47509,7 +48034,7 @@ function ensureHostedBaseImage(lifecycle) {
47509
48034
  if (pull.ok) {
47510
48035
  return;
47511
48036
  }
47512
- const dockerfilePath = path3.join(lifecycle.remoteRoot, "Dockerfile.base");
48037
+ const dockerfilePath = path5.join(lifecycle.remoteRoot, "Dockerfile.base");
47513
48038
  if (!pathExistsSync(dockerfilePath)) {
47514
48039
  throw helperError(
47515
48040
  "Unable to prepare the Sporades Base image.",
@@ -47599,7 +48124,7 @@ async function currentReleaseId(currentLink, request) {
47599
48124
  }
47600
48125
  throw error;
47601
48126
  }
47602
- return path3.basename(target);
48127
+ return path5.basename(target);
47603
48128
  }
47604
48129
  function loopbackRunningRoute(route, publishedPort) {
47605
48130
  return {
@@ -47615,7 +48140,7 @@ async function refreshLoopbackRunningRoute(request, registryRecord, containerNam
47615
48140
  return withManagedRouteLock(routeFile, async () => {
47616
48141
  let currentRoute;
47617
48142
  try {
47618
- currentRoute = await readFile2(routeFile, "utf8");
48143
+ currentRoute = await readFile4(routeFile, "utf8");
47619
48144
  } catch (error) {
47620
48145
  if (errorDetails(error).code === "ENOENT") {
47621
48146
  return { ok: true, refreshed: false };
@@ -47642,6 +48167,17 @@ async function refreshLoopbackRunningRoute(request, registryRecord, containerNam
47642
48167
  }
47643
48168
  async function writeRunningRoute(lifecycle, route = lifecycle.routes.running) {
47644
48169
  await provisionRouteLogFile(route, "capsule-running-http-log-descriptor-mutate");
48170
+ await applyManagedRoute(lifecycle, route.routeFile, routeVariants(route).map(renderRunningRoute).join("\n"));
48171
+ }
48172
+ function routeVariants(route) {
48173
+ return [route, ...validateAliasDomains(route.aliasDomains).map((hostname) => ({
48174
+ ...route,
48175
+ hostname,
48176
+ aliasDomains: void 0,
48177
+ tls: { mode: "automatic" }
48178
+ }))];
48179
+ }
48180
+ function renderRunningRoute(route) {
47645
48181
  const cloudflareOrigin = route.tls?.mode === "cloudflare-origin";
47646
48182
  const proxyLine = [
47647
48183
  `reverse_proxy ${route.upstream ?? `${route.containerName}:${route.port ?? 4e3}`} {`,
@@ -47654,11 +48190,7 @@ async function writeRunningRoute(lifecycle, route = lifecycle.routes.running) {
47654
48190
  "respond @sporadesUntrustedCloudflareSource 403",
47655
48191
  routeHandler
47656
48192
  ].join("\n ") : routeHandler;
47657
- await applyManagedRoute(
47658
- lifecycle,
47659
- route.routeFile,
47660
- renderRoute(route, guardedHandler)
47661
- );
48193
+ return renderRoute(route, guardedHandler);
47662
48194
  }
47663
48195
  function renderRunningRouteHandler(route, proxyLine) {
47664
48196
  const probe = route.runtimeProbe;
@@ -47684,7 +48216,7 @@ async function writeUnavailableRoute(lifecycle) {
47684
48216
  await applyManagedRoute(
47685
48217
  lifecycle,
47686
48218
  route.routeFile,
47687
- renderRoute(route, renderUnavailableRouteHandler(route))
48219
+ routeVariants(route).map((variant) => renderRoute(variant, renderUnavailableRouteHandler(variant))).join("\n")
47688
48220
  );
47689
48221
  }
47690
48222
  function renderUnavailableRouteHandler(route) {
@@ -47729,16 +48261,16 @@ async function applyManagedRouteLocked(lifecycle, routeFile, contents) {
47729
48261
  const tempRouteFile = `${routeFile}.tmp`;
47730
48262
  const previousRouteFile = `${routeFile}.previous-${process.pid}`;
47731
48263
  await assertManagedRouteMutationBoundary(routeFile, "apply-remove-temp", [tempRouteFile, previousRouteFile]);
47732
- await rm(tempRouteFile, { force: true });
48264
+ await rm2(tempRouteFile, { force: true });
47733
48265
  await assertManagedRouteMutationBoundary(routeFile, "apply-remove-previous", [previousRouteFile]);
47734
- await rm(previousRouteFile, { force: true });
48266
+ await rm2(previousRouteFile, { force: true });
47735
48267
  await assertManagedRouteMutationBoundary(routeFile, "apply-write-temp", [tempRouteFile]);
47736
48268
  await writeFile(tempRouteFile, contents, { flag: "wx", mode: 420 });
47737
48269
  try {
47738
48270
  validateCaddyRoute(tempRouteFile);
47739
48271
  } catch (error) {
47740
48272
  await assertManagedRouteMutationBoundary(routeFile, "apply-validation-cleanup", [tempRouteFile]);
47741
- await rm(tempRouteFile, { force: true });
48273
+ await rm2(tempRouteFile, { force: true });
47742
48274
  throw error;
47743
48275
  }
47744
48276
  const hadPreviousRoute = await pathExists(routeFile);
@@ -47746,20 +48278,20 @@ async function applyManagedRouteLocked(lifecycle, routeFile, contents) {
47746
48278
  try {
47747
48279
  if (hadPreviousRoute) {
47748
48280
  await assertManagedRouteMutationBoundary(routeFile, "apply-move-current", [previousRouteFile]);
47749
- await rename(routeFile, previousRouteFile);
48281
+ await rename2(routeFile, previousRouteFile);
47750
48282
  previousRouteMoved = true;
47751
48283
  }
47752
48284
  await assertManagedRouteMutationBoundary(routeFile, "apply-publish-temp", [tempRouteFile, previousRouteFile]);
47753
- await rename(tempRouteFile, routeFile);
48285
+ await rename2(tempRouteFile, routeFile);
47754
48286
  reloadCaddy(lifecycle);
47755
48287
  } catch (error) {
47756
48288
  await assertManagedRouteMutationBoundary(routeFile, "apply-rollback-remove-temp", [tempRouteFile, previousRouteFile]);
47757
- await rm(tempRouteFile, { force: true });
48289
+ await rm2(tempRouteFile, { force: true });
47758
48290
  await assertManagedRouteMutationBoundary(routeFile, "apply-rollback-remove-current", [previousRouteFile]);
47759
- await rm(routeFile, { force: true });
48291
+ await rm2(routeFile, { force: true });
47760
48292
  if (previousRouteMoved) {
47761
48293
  await assertManagedRouteMutationBoundary(routeFile, "apply-rollback-restore", [previousRouteFile]);
47762
- await rename(previousRouteFile, routeFile);
48294
+ await rename2(previousRouteFile, routeFile);
47763
48295
  }
47764
48296
  if (previousRouteMoved) {
47765
48297
  try {
@@ -47774,23 +48306,23 @@ async function applyManagedRouteLocked(lifecycle, routeFile, contents) {
47774
48306
  throw error;
47775
48307
  }
47776
48308
  await assertManagedRouteMutationBoundary(routeFile, "apply-finalize-previous", [previousRouteFile]);
47777
- await rm(previousRouteFile, { force: true });
48309
+ await rm2(previousRouteFile, { force: true });
47778
48310
  }
47779
48311
  async function removeManagedRouteLocked(lifecycle, routeFile) {
47780
48312
  const previousRouteFile = `${routeFile}.previous-${process.pid}`;
47781
48313
  await assertManagedRouteMutationBoundary(routeFile, "remove-remove-previous", [previousRouteFile]);
47782
- await rm(previousRouteFile, { force: true });
48314
+ await rm2(previousRouteFile, { force: true });
47783
48315
  const hadRoute = await pathExists(routeFile);
47784
48316
  if (!hadRoute) {
47785
48317
  return { routeFile, removed: false };
47786
48318
  }
47787
48319
  await assertManagedRouteMutationBoundary(routeFile, "remove-move-current", [previousRouteFile]);
47788
- await rename(routeFile, previousRouteFile);
48320
+ await rename2(routeFile, previousRouteFile);
47789
48321
  try {
47790
48322
  reloadCaddy(lifecycle);
47791
48323
  } catch (error) {
47792
48324
  await assertManagedRouteMutationBoundary(routeFile, "remove-rollback-restore", [previousRouteFile]);
47793
- await rename(previousRouteFile, routeFile);
48325
+ await rename2(previousRouteFile, routeFile);
47794
48326
  try {
47795
48327
  reloadCaddy(lifecycle);
47796
48328
  } catch {
@@ -47809,7 +48341,7 @@ async function removeManagedRouteLocked(lifecycle, routeFile) {
47809
48341
  async function finalizeRemovedRouteLocked(route) {
47810
48342
  if (route?.previousRouteFile) {
47811
48343
  await assertManagedRouteMutationBoundary(route.routeFile, "remove-finalize-previous", [route.previousRouteFile]);
47812
- await rm(route.previousRouteFile, { force: true });
48344
+ await rm2(route.previousRouteFile, { force: true });
47813
48345
  }
47814
48346
  }
47815
48347
  async function restoreRemovedRouteLocked(lifecycle, route) {
@@ -47817,9 +48349,9 @@ async function restoreRemovedRouteLocked(lifecycle, route) {
47817
48349
  return;
47818
48350
  }
47819
48351
  await assertManagedRouteMutationBoundary(route.routeFile, "restore-remove-current", [route.previousRouteFile]);
47820
- await rm(route.routeFile, { force: true });
48352
+ await rm2(route.routeFile, { force: true });
47821
48353
  await assertManagedRouteMutationBoundary(route.routeFile, "restore-publish-previous", [route.previousRouteFile]);
47822
- await rename(route.previousRouteFile, route.routeFile);
48354
+ await rename2(route.previousRouteFile, route.routeFile);
47823
48355
  reloadCaddy(lifecycle);
47824
48356
  }
47825
48357
  async function assertManagedRouteMutationBoundary(routeFile, boundary, relatedFiles = []) {
@@ -47865,7 +48397,7 @@ async function processRetainsOsFlock(lockFile) {
47865
48397
  for (const descriptorRoot of ["/proc/self/fd", "/dev/fd"]) {
47866
48398
  let entries;
47867
48399
  try {
47868
- entries = await readdir(descriptorRoot);
48400
+ entries = await readdir3(descriptorRoot);
47869
48401
  } catch {
47870
48402
  continue;
47871
48403
  }
@@ -47877,7 +48409,7 @@ async function processRetainsOsFlock(lockFile) {
47877
48409
  for (const descriptor of descriptorNumbers) {
47878
48410
  for (const descriptorRoot of ["/proc/self/fd", "/dev/fd"]) {
47879
48411
  try {
47880
- const descriptorStat = await stat(path3.join(descriptorRoot, descriptor));
48412
+ const descriptorStat = await stat(path5.join(descriptorRoot, descriptor));
47881
48413
  if (!routeLockFileIdentityMatches(descriptorStat, expected)) continue;
47882
48414
  if (process.platform === "linux") {
47883
48415
  if (descriptorStat.dev !== expected.dev) continue;
@@ -47894,7 +48426,7 @@ async function processRetainsOsFlock(lockFile) {
47894
48426
  async function linuxDescriptorOwnsFlock(descriptor, expected) {
47895
48427
  let fdinfo;
47896
48428
  try {
47897
- fdinfo = await readFile2(`/proc/self/fdinfo/${descriptor}`, "utf8");
48429
+ fdinfo = await readFile4(`/proc/self/fdinfo/${descriptor}`, "utf8");
47898
48430
  } catch {
47899
48431
  return false;
47900
48432
  }
@@ -47952,7 +48484,7 @@ function managedRouteLockTimeoutMs() {
47952
48484
  async function readManagedRouteProtocolOwner(protocolFile) {
47953
48485
  let parsed;
47954
48486
  try {
47955
- parsed = JSON.parse(await readFile2(protocolFile, "utf8"));
48487
+ parsed = JSON.parse(await readFile4(protocolFile, "utf8"));
47956
48488
  } catch (error) {
47957
48489
  if (errorDetails(error).code === "ENOENT") {
47958
48490
  return { state: "absent", owner: null };
@@ -47981,15 +48513,15 @@ function managedRouteProtocolOwnerIsLive(owner) {
47981
48513
  return currentIdentity === null || currentIdentity === owner.processIdentity;
47982
48514
  }
47983
48515
  async function cleanupManagedRouteProtocolArtifacts(lockFile) {
47984
- const directory = path3.dirname(lockFile);
47985
- const base = path3.basename(lockFile);
48516
+ const directory = path5.dirname(lockFile);
48517
+ const base = path5.basename(lockFile);
47986
48518
  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);
48519
+ const entries = (await readdir3(directory)).filter((entry) => artifact.test(entry)).sort().slice(0, 100);
47988
48520
  for (const entry of entries) {
47989
- const artifactPath = path3.join(directory, entry);
48521
+ const artifactPath = path5.join(directory, entry);
47990
48522
  const owner = await readManagedRouteProtocolOwner(artifactPath);
47991
48523
  if (owner.state === "owner" && !managedRouteProtocolOwnerIsLive(owner.owner)) {
47992
- await rm(artifactPath, { force: true });
48524
+ await rm2(artifactPath, { force: true });
47993
48525
  }
47994
48526
  }
47995
48527
  }
@@ -48004,7 +48536,7 @@ async function removePathIfPresent(targetPath, options = {}) {
48004
48536
  if (!existed) {
48005
48537
  return { path: targetPath, removed: false };
48006
48538
  }
48007
- await rm(targetPath, { recursive: Boolean(options.recursive), force: true });
48539
+ await rm2(targetPath, { recursive: Boolean(options.recursive), force: true });
48008
48540
  return { path: targetPath, removed: true };
48009
48541
  }
48010
48542
  async function prepareWritableDataPath(targetPath) {
@@ -48019,11 +48551,11 @@ async function prepareWritableDataPath(targetPath) {
48019
48551
  }
48020
48552
  async function openCanonicalRuntimeDataDirectory(targetPath, createData) {
48021
48553
  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);
48026
- let handle = await open(
48554
+ if (!managedRoot || !path5.isAbsolute(targetPath) || path5.normalize(targetPath) !== targetPath) throw runtimeDataTrustError(targetPath);
48555
+ const relative = path5.relative(managedRoot, targetPath);
48556
+ const components = relative.split(path5.sep).filter(Boolean);
48557
+ if (relative.startsWith("..") || path5.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);
48558
+ let handle = await open2(
48027
48559
  managedRoot,
48028
48560
  fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | (fsConstants.O_DIRECTORY ?? 0)
48029
48561
  ).catch(() => {
@@ -48032,14 +48564,14 @@ async function openCanonicalRuntimeDataDirectory(targetPath, createData) {
48032
48564
  let logical = managedRoot;
48033
48565
  try {
48034
48566
  for (let index = 0; index < components.length; index += 1) {
48035
- logical = path3.join(logical, components[index]);
48567
+ logical = path5.join(logical, components[index]);
48036
48568
  const descriptorPath = descriptorChildPath(handle.fd, components[index], logical);
48037
48569
  if (createData && index === components.length - 1) {
48038
- await mkdir(descriptorPath, { mode: 448 }).catch((error) => {
48570
+ await mkdir2(descriptorPath, { mode: 448 }).catch((error) => {
48039
48571
  if (errorDetails(error).code !== "EEXIST") throw runtimeDataTrustError(logical);
48040
48572
  });
48041
48573
  }
48042
- const child = await open(
48574
+ const child = await open2(
48043
48575
  descriptorPath,
48044
48576
  fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | (fsConstants.O_DIRECTORY ?? 0)
48045
48577
  ).catch(() => {
@@ -48062,7 +48594,7 @@ async function openCanonicalRuntimeDataDirectory(targetPath, createData) {
48062
48594
  }
48063
48595
  }
48064
48596
  function descriptorChildPath(parentFd, name2, fallbackPath) {
48065
- if (name2.length === 0 || name2 === "." || name2 === ".." || name2.includes(path3.sep)) throw runtimeDataTrustError(fallbackPath);
48597
+ if (name2.length === 0 || name2 === "." || name2 === ".." || name2.includes(path5.sep)) throw runtimeDataTrustError(fallbackPath);
48066
48598
  return process.platform === "linux" ? `/proc/self/fd/${parentFd}/${name2}` : fallbackPath;
48067
48599
  }
48068
48600
  async function prepareWritableDataHandle(handle, targetPath, directory) {
@@ -48078,15 +48610,15 @@ async function prepareWritableDataHandle(handle, targetPath, directory) {
48078
48610
  await assertRuntimeDataPathIdentity(targetPath, identity, directory);
48079
48611
  if (!directory) return;
48080
48612
  const descriptorDirectory = process.platform === "linux" ? `/proc/self/fd/${handle.fd}` : targetPath;
48081
- const entries = await readdir(descriptorDirectory, { withFileTypes: true }).catch(() => {
48613
+ const entries = await readdir3(descriptorDirectory, { withFileTypes: true }).catch(() => {
48082
48614
  throw runtimeDataTrustError(targetPath);
48083
48615
  });
48084
48616
  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);
48617
+ if (entry.name === "." || entry.name === ".." || entry.name.includes(path5.sep)) throw runtimeDataTrustError(targetPath);
48618
+ const childPath = path5.join(targetPath, entry.name);
48087
48619
  const descriptorPath = descriptorChildPath(handle.fd, entry.name, childPath);
48088
48620
  const flags = fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | (entry.isDirectory() ? fsConstants.O_DIRECTORY ?? 0 : 0);
48089
- const child = await open(descriptorPath, flags).catch(() => {
48621
+ const child = await open2(descriptorPath, flags).catch(() => {
48090
48622
  throw runtimeDataTrustError(childPath);
48091
48623
  });
48092
48624
  try {
@@ -48097,7 +48629,7 @@ async function prepareWritableDataHandle(handle, targetPath, directory) {
48097
48629
  }
48098
48630
  }
48099
48631
  async function assertRuntimeDataPathIdentity(targetPath, identity, directory) {
48100
- const details = await lstat(targetPath).catch(() => {
48632
+ const details = await lstat2(targetPath).catch(() => {
48101
48633
  throw runtimeDataTrustError(targetPath);
48102
48634
  });
48103
48635
  if (details.isSymbolicLink() || details.dev !== identity.dev || details.ino !== identity.ino || (directory ? !details.isDirectory() : !details.isFile())) throw runtimeDataTrustError(targetPath);
@@ -48151,7 +48683,7 @@ function validateCaddyRoute(routeFile) {
48151
48683
  }
48152
48684
  }
48153
48685
  function reloadCaddy(lifecycle) {
48154
- const configPath = path3.join(lifecycle.remoteRoot, "caddy", "Caddyfile");
48686
+ const configPath = path5.join(lifecycle.remoteRoot, "caddy", "Caddyfile");
48155
48687
  const result = spawnSync2("caddy", ["reload", "--config", configPath, "--adapter", "caddyfile"], { encoding: "utf8" });
48156
48688
  if (result.error || result.status !== 0) {
48157
48689
  throw helperError(
@@ -48195,6 +48727,7 @@ async function recordReleaseUploaded(request, release, fileInventory) {
48195
48727
  hostedUrl: release.hostedUrl ?? entry.source?.hostedUrl ?? null,
48196
48728
  remoteCapsuleId: release.remoteCapsuleId ?? entry.source?.remoteCapsuleId ?? null,
48197
48729
  files: Array.isArray(release.files) ? [...release.files] : [],
48730
+ deployFiles: resolveDeployFiles(release.deployFiles),
48198
48731
  fileInventory: fileInventory.map((file) => ({ ...file })),
48199
48732
  serverEnvIncluded: Boolean(release.serverEnvIncluded),
48200
48733
  inspection: Array.isArray(release.inspection?.requiredInspectors) ? { requiredInspectors: [...release.inspection.requiredInspectors] } : void 0,
@@ -48467,12 +49000,12 @@ function releaseSealedServerEnvPrivateKeyMount(registryRecord, paths) {
48467
49000
  const fingerprint = release?.source?.sealedServerEnv?.publicKeyFingerprint;
48468
49001
  if (typeof fingerprint === "string" && /^[a-f0-9]{16}$/.test(fingerprint)) {
48469
49002
  return {
48470
- host: path3.join(paths.data, "sealed-server-env", "keys", `${fingerprint}.private.pem`),
49003
+ host: path5.join(paths.data, "sealed-server-env", "keys", `${fingerprint}.private.pem`),
48471
49004
  fingerprint
48472
49005
  };
48473
49006
  }
48474
49007
  return {
48475
- host: path3.join(paths.data, "sealed-server-env", "server-env.private.pem"),
49008
+ host: path5.join(paths.data, "sealed-server-env", "server-env.private.pem"),
48476
49009
  fingerprint: null
48477
49010
  };
48478
49011
  }
@@ -48484,7 +49017,7 @@ function releaseSshAuthorizedKeysMount(registryRecord, paths) {
48484
49017
  return null;
48485
49018
  }
48486
49019
  return {
48487
- host: path3.join(paths.currentLink, ".sporades", "ssh", "authorized_keys"),
49020
+ host: path5.join(paths.currentLink, ".sporades", "ssh", "authorized_keys"),
48488
49021
  container: "/run/sporades/ssh/authorized_keys",
48489
49022
  mode: "ro",
48490
49023
  optional: false
@@ -48596,7 +49129,7 @@ function compareReleasesNewestFirst(left, right) {
48596
49129
  async function readRegistryRecordForCapsule(request, purpose) {
48597
49130
  const registryRecordPath = registryPath(request);
48598
49131
  try {
48599
- return JSON.parse(await readFile2(registryRecordPath, "utf8"));
49132
+ return JSON.parse(await readFile4(registryRecordPath, "utf8"));
48600
49133
  } catch (error) {
48601
49134
  if (errorDetails(error).code === "ENOENT") {
48602
49135
  throw helperError(
@@ -48636,7 +49169,7 @@ function assertRegistryRecordMatchesRequest(request, record) {
48636
49169
  async function mutateRegistryRecord(request, mutate) {
48637
49170
  return withRegistryLock(request, async () => {
48638
49171
  const registryRecordPath = registryPath(request);
48639
- const record = JSON.parse(await readFile2(registryRecordPath, "utf8"));
49172
+ const record = JSON.parse(await readFile4(registryRecordPath, "utf8"));
48640
49173
  await writeRegistryRecordAtomic(registryRecordPath, mutate(record));
48641
49174
  });
48642
49175
  }
@@ -48654,9 +49187,9 @@ async function writeRegistryContentsAtomic(registryRecordPath, contents) {
48654
49187
  "Check Host server disk permissions and free space, then retry the command."
48655
49188
  );
48656
49189
  }
48657
- await rename(tempPath, registryRecordPath);
49190
+ await rename2(tempPath, registryRecordPath);
48658
49191
  } catch (error) {
48659
- await rm(tempPath, { force: true });
49192
+ await rm2(tempPath, { force: true });
48660
49193
  throw error;
48661
49194
  }
48662
49195
  }
@@ -48666,7 +49199,7 @@ async function withRegistryLock(request, fn) {
48666
49199
  const startedAt = Date.now();
48667
49200
  while (true) {
48668
49201
  try {
48669
- await mkdir(lockDir, { recursive: false });
49202
+ await mkdir2(lockDir, { recursive: false });
48670
49203
  break;
48671
49204
  } catch (error) {
48672
49205
  if (errorDetails(error).code !== "EEXIST") {
@@ -48684,11 +49217,11 @@ async function withRegistryLock(request, fn) {
48684
49217
  try {
48685
49218
  return await fn();
48686
49219
  } finally {
48687
- await rm(lockDir, { recursive: true, force: true });
49220
+ await rm2(lockDir, { recursive: true, force: true });
48688
49221
  }
48689
49222
  }
48690
49223
  function registryPath(request) {
48691
- return path3.join(
49224
+ return path5.join(
48692
49225
  request.host.remoteRoot,
48693
49226
  "hosts",
48694
49227
  request.host.domain,
@@ -48698,7 +49231,7 @@ function registryPath(request) {
48698
49231
  );
48699
49232
  }
48700
49233
  function registryLockPath(request) {
48701
- return path3.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", ".lock");
49234
+ return path5.join(request.host.remoteRoot, "hosts", request.host.domain, "registry", ".lock");
48702
49235
  }
48703
49236
  function capsuleData(request, lifecycle) {
48704
49237
  return {
@@ -48750,7 +49283,7 @@ async function readManagedCaddyAccessLog(logs) {
48750
49283
  }
48751
49284
  return null;
48752
49285
  }
48753
- const contents = await readFile2(logs.file, "utf8");
49286
+ const contents = await readFile4(logs.file, "utf8");
48754
49287
  return lastLogEntries(contents, logs.lines);
48755
49288
  }
48756
49289
  function readCaddyJournalLogs(logs) {
@@ -48800,14 +49333,14 @@ function createHostedContainerName(domain, subname) {
48800
49333
  return `sporades-${domain.replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase()}-${subname}`;
48801
49334
  }
48802
49335
  function defaultCaddyAccessLogPath(remoteRoot) {
48803
- return path3.join(remoteRoot, "caddy", "logs", "access.log");
49336
+ return path5.join(remoteRoot, "caddy", "logs", "access.log");
48804
49337
  }
48805
49338
  function defaultCapsuleHttpLogPath(remoteRoot, domain, subname) {
48806
- return path3.join(remoteRoot, "hosts", domain, "capsules", subname, "logs", "http.log");
49339
+ return path5.join(remoteRoot, "hosts", domain, "capsules", subname, "logs", "http.log");
48807
49340
  }
48808
49341
  function canonicalCapsuleHttpLogPath(request, validatedRemoteRoot = validateCanonicalHostRouteRoot(request)) {
48809
49342
  canonicalManagedRouteFile(request, validatedRemoteRoot);
48810
- const expectedDirectory = path3.resolve(
49343
+ const expectedDirectory = path5.resolve(
48811
49344
  validatedRemoteRoot,
48812
49345
  "hosts",
48813
49346
  request.host.domain,
@@ -48815,8 +49348,8 @@ function canonicalCapsuleHttpLogPath(request, validatedRemoteRoot = validateCano
48815
49348
  request.capsule.subname,
48816
49349
  "logs"
48817
49350
  );
48818
- const expected = path3.resolve(expectedDirectory, "http.log");
48819
- if (path3.dirname(expected) !== expectedDirectory) throw invalidCapsuleHttpLogPathError();
49351
+ const expected = path5.resolve(expectedDirectory, "http.log");
49352
+ if (path5.dirname(expected) !== expectedDirectory) throw invalidCapsuleHttpLogPathError();
48820
49353
  const lifecycle = request.lifecycle;
48821
49354
  const supplied = [
48822
49355
  request.registration?.route?.log?.file,
@@ -48831,7 +49364,7 @@ function canonicalCapsuleHttpLogPath(request, validatedRemoteRoot = validateCano
48831
49364
  function capsuleHttpLogTrustManifest(request, validatedRemoteRoot) {
48832
49365
  const logFile = canonicalCapsuleHttpLogPath(request, validatedRemoteRoot);
48833
49366
  return {
48834
- directories: [{ path: path3.dirname(logFile), caddyOwned: true }],
49367
+ directories: [{ path: path5.dirname(logFile), caddyOwned: true }],
48835
49368
  finalFiles: [{ path: logFile, caddyOwned: true }]
48836
49369
  };
48837
49370
  }
@@ -48841,7 +49374,81 @@ function invalidCapsuleHttpLogPathError() {
48841
49374
  "Use the canonical Capsule-scoped Host HTTP log path and retry the lifecycle command."
48842
49375
  );
48843
49376
  }
49377
+ function hostedPreservedFilesRoot(paths) {
49378
+ return path5.join(paths.capsule, "preserved-files");
49379
+ }
49380
+ async function prepareHostedRuntimeFileAccess(target, mode, failure) {
49381
+ const handle = await open2(target, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
49382
+ try {
49383
+ const identity = await handle.stat();
49384
+ if (!identity.isFile() || identity.nlink !== 1) throw helperError(failure.message, failure.hint);
49385
+ await prepareRuntimeDataOwnershipHandle(handle, target, identity);
49386
+ await handle.chmod(mode);
49387
+ } finally {
49388
+ await handle.close();
49389
+ }
49390
+ }
49391
+ async function preparePreservedReleaseFiles(request, recordedRelease) {
49392
+ const paths = canonicalReleasePaths(request);
49393
+ const journal = attemptJournalPath(hostedPreservedFilesRoot(paths));
49394
+ const interrupted = !activePreservedAttempts.has(journal) && await pathExists(journal);
49395
+ if (!recordedRelease) {
49396
+ if (interrupted) throw helperError("Current Hosted release is not recorded.", "Run `sporades host reconcile <subname>` to settle the interrupted release install before starting the Capsule.");
49397
+ return;
49398
+ }
49399
+ if (interrupted) {
49400
+ throw helperError("Interrupted deploy.files attempt requires recovery.", "Run `sporades host reconcile <subname>` to settle the interrupted release install before starting, restarting or selecting a release.");
49401
+ }
49402
+ for (const file of resolveDeployFiles(recordedRelease.source?.deployFiles)) {
49403
+ if (file.update !== "preserve") continue;
49404
+ const target = await assertPreservedDeployFile(hostedPreservedFilesRoot(paths), file.path);
49405
+ await prepareHostedRuntimeFileAccess(target, 384, { message: "Unsafe preserved release file.", hint: "Restore a regular preserved file before restarting." });
49406
+ }
49407
+ }
49408
+ async function reconcileReleaseAttempt(request) {
49409
+ validateLifecycleRequest(request);
49410
+ const record = await readRegistryRecordForCapsule(request, "reconcile");
49411
+ assertRegistryRecordMatchesRequest(request, record);
49412
+ const paths = canonicalReleasePaths(request);
49413
+ const journal = attemptJournalPath(hostedPreservedFilesRoot(paths));
49414
+ const attempt = await readPreservedFileAttempt(journal);
49415
+ const lifecycle = normaliseLifecycle(request, record, { ignoreProvidedLifecycle: true });
49416
+ const data2 = { capsule: capsuleData(request, lifecycle), journal, reconciled: false, committed: null, release: null, actions: [] };
49417
+ if (!attempt) {
49418
+ writeEnvelope({ ok: true, data: data2, error: null });
49419
+ return;
49420
+ }
49421
+ if (!/^\d{8}T\d{6}Z-[a-f0-9]{8}$/.test(attempt.release)) {
49422
+ throw helperError("Hosted deploy.files journal names an invalid release.", `Inspect ${journal} before reconciling manually.`);
49423
+ }
49424
+ const recorded = normaliseReleaseHistory(record).some((entry) => entry.id === attempt.release);
49425
+ const committed = recorded && record.currentRelease?.id === attempt.release;
49426
+ const actions = [];
49427
+ if (!recorded) {
49428
+ await rollbackPreservedFiles(attempt.seeds);
49429
+ if (attempt.seeds.length) actions.push("seeds-rolled-back");
49430
+ const recordedTarget = record.currentRelease?.id ? path5.join(paths.releases, record.currentRelease.id) : null;
49431
+ const currentTarget = await readlink(paths.currentLink).catch((error) => {
49432
+ if (errorDetails(error).code === "ENOENT") return null;
49433
+ throw error;
49434
+ });
49435
+ if (currentTarget !== recordedTarget) {
49436
+ await restoreCurrentReleasePointerTarget(paths.currentLink, recordedTarget);
49437
+ actions.push("current-pointer-restored");
49438
+ }
49439
+ const candidate = canonicalRollbackPaths(request, attempt.release);
49440
+ await removeReleasePrivateKeyIfPresent(candidate, attempt.release);
49441
+ if (await pathExists(candidate.release)) {
49442
+ await rm2(candidate.release, { recursive: true, force: true });
49443
+ actions.push("candidate-release-removed");
49444
+ }
49445
+ }
49446
+ await finishPreservedFileAttempt(journal);
49447
+ actions.push("journal-removed");
49448
+ writeEnvelope({ ok: true, data: { ...data2, reconciled: true, committed, release: attempt.release, actions }, error: null });
49449
+ }
48844
49450
  async function assertRollbackReleaseFiles(request, releaseDirectory, recordedRelease = null) {
49451
+ await preparePreservedReleaseFiles(request, recordedRelease);
48845
49452
  try {
48846
49453
  const expected = await recordedReleaseFileClaims(releaseDirectory, recordedRelease);
48847
49454
  const actual = await validateExtractedReleaseTree(releaseDirectory, expected);
@@ -48898,7 +49505,7 @@ async function recordedReleaseFileClaims(releaseDirectory, recordedRelease) {
48898
49505
  const normalized = file.normalize("NFC");
48899
49506
  if (canonical.has(normalized)) throw helperError("Hosted Capsule release inventory is invalid.", "Choose another recorded release or push a replacement.");
48900
49507
  canonical.add(normalized);
48901
- const stats = await lstat(path3.join(releaseDirectory, ...file.split("/")));
49508
+ const stats = await lstat2(path5.join(releaseDirectory, ...file.split("/")));
48902
49509
  if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1) {
48903
49510
  throw helperError("Hosted Capsule release files are missing.", "Choose another complete immutable release.");
48904
49511
  }
@@ -48909,13 +49516,13 @@ async function recordedReleaseFileClaims(releaseDirectory, recordedRelease) {
48909
49516
  async function deriveReleaseFileClaims(root) {
48910
49517
  const claims = [];
48911
49518
  async function visit(directory, prefix = "") {
48912
- for (const entry of await readdir(directory, { withFileTypes: true })) {
49519
+ for (const entry of await readdir3(directory, { withFileTypes: true })) {
48913
49520
  const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
48914
49521
  if (!safeRecordedReleasePath(relative)) {
48915
49522
  throw helperError("Hosted Capsule release inventory is invalid.", "Choose another recorded release or push a replacement.");
48916
49523
  }
48917
- const entryPath = path3.join(directory, entry.name);
48918
- const stats = await lstat(entryPath);
49524
+ const entryPath = path5.join(directory, entry.name);
49525
+ const stats = await lstat2(entryPath);
48919
49526
  if (stats.isSymbolicLink()) {
48920
49527
  throw helperError("Hosted Capsule release inventory is invalid.", "Choose another recorded release or push a replacement.");
48921
49528
  }
@@ -48938,7 +49545,7 @@ function validRecordedReleaseIdentity(file) {
48938
49545
  return file && safeRecordedReleasePath(file.path) && Number.isSafeInteger(file.size) && file.size >= 0 && typeof file.sha256 === "string" && /^[a-f0-9]{64}$/.test(file.sha256);
48939
49546
  }
48940
49547
  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 !== "..");
49548
+ return file.length > 0 && !file.startsWith("/") && !file.includes("\\") && !file.includes("\0") && path5.posix.normalize(file) === file && Buffer.byteLength(file, "utf8") <= HOST_RELEASE_ARCHIVE_LIMITS.pathBytes && file.split("/").every((segment) => segment && segment !== "." && segment !== "..");
48942
49549
  }
48943
49550
  async function verifyRegisteredCapsule(request, purpose = "push") {
48944
49551
  const record = await readRegistryRecordForCapsule(request, purpose);