camstack 1.2.36 → 1.2.38

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.
@@ -14522,7 +14522,7 @@ function date4(params) {
14522
14522
  // ../../node_modules/zod/v4/classic/external.js
14523
14523
  config(en_default());
14524
14524
 
14525
- // ../types/dist/sleep-zMxKWD0M.mjs
14525
+ // ../types/dist/sleep-B8EIZt4h.mjs
14526
14526
  var WELL_KNOWN_TABS = [
14527
14527
  {
14528
14528
  id: "overview",
@@ -27017,6 +27017,13 @@ var CameraStatusSchema = external_exports.object({
27017
27017
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
27018
27018
  fetchedAt: external_exports.number()
27019
27019
  });
27020
+ var INFERENCE_DEVICE_EXCLUSION_REASONS = [
27021
+ "disabled",
27022
+ "unavailable",
27023
+ "cannot-host-camera-root",
27024
+ "accelerator-preferred"
27025
+ ];
27026
+ var InferenceDeviceExclusionReasonSchema = external_exports.enum(INFERENCE_DEVICE_EXCLUSION_REASONS);
27020
27027
  var NodeInferenceDeviceSchema = external_exports.object({
27021
27028
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
27022
27029
  key: external_exports.string(),
@@ -27047,7 +27054,17 @@ var NodeInferenceDeviceSchema = external_exports.object({
27047
27054
  * available per format; this is the stored selection that becomes the
27048
27055
  * default for EVERY camera landing on this accelerator.
27049
27056
  */
27050
- steps: external_exports.record(external_exports.string(), DeviceStepConfigSchema).optional()
27057
+ steps: external_exports.record(external_exports.string(), DeviceStepConfigSchema).optional(),
27058
+ /**
27059
+ * `null` when the device IS a camera-root candidate on this node; otherwise
27060
+ * the reason the dispatcher drops it. Computed by the SAME
27061
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
27062
+ * never disagree with the election — deriving it in the UI from
27063
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
27064
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
27065
+ * "an accelerator is serving" predicate).
27066
+ */
27067
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
27051
27068
  });
27052
27069
  var NodeInferenceDevicesSchema = external_exports.object({
27053
27070
  nodeId: external_exports.string(),
@@ -32057,7 +32074,35 @@ var ConnectionEndpointSchema = external_exports.object({
32057
32074
  */
32058
32075
  priority: external_exports.number()
32059
32076
  });
32060
- var GetConnectionEndpointsResultSchema = external_exports.object({ endpoints: external_exports.array(ConnectionEndpointSchema).readonly() });
32077
+ var LocalPortSourceEnum = external_exports.enum([
32078
+ "server-config",
32079
+ "server-env",
32080
+ "caller-hint",
32081
+ "default"
32082
+ ]);
32083
+ var AdvertisedLocalPortSchema = external_exports.object({
32084
+ port: external_exports.number().int().min(1).max(65535),
32085
+ source: LocalPortSourceEnum
32086
+ });
32087
+ var GetConnectionEndpointsResultSchema = external_exports.object({
32088
+ endpoints: external_exports.array(ConnectionEndpointSchema).readonly(),
32089
+ /**
32090
+ * The port the hub built the LAN/loopback URLs with, and where that number
32091
+ * came from.
32092
+ *
32093
+ * Returned rather than merely applied, because "the URL is right" and "the
32094
+ * client can KNOW the URL is right" are different properties. A client that
32095
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
32096
+ * a hub that echoed the port the client sent, so it cannot decide whether to
32097
+ * race the candidate or discard it. With `source` it can: anything but
32098
+ * `caller-hint` is the hub's own socket.
32099
+ *
32100
+ * Absent on hubs predating this field — a client that finds it missing is
32101
+ * talking to an echoing hub and must degrade exactly as it does for
32102
+ * `caller-hint`.
32103
+ */
32104
+ localPort: AdvertisedLocalPortSchema
32105
+ });
32061
32106
  var NotificationEndpointSchema = external_exports.object({
32062
32107
  /** The operator's explicit choice, or null for AUTO. */
32063
32108
  baseUrl: external_exports.string().nullable(),
@@ -32099,10 +32144,35 @@ var localNetworkCapability = {
32099
32144
  * Honours `getAllowedAddresses()` when set — addresses outside
32100
32145
  * the allowlist are dropped (the public tunnel + loopback are
32101
32146
  * always included as escape hatches).
32147
+ *
32148
+ * **The port is the hub's, not the caller's** (D62 — a function's fact
32149
+ * belongs to whoever already owns it). This method used to take a `port`
32150
+ * and echo it onto every LAN and loopback URL it advertised, which made
32151
+ * every client a second authority on a socket only the hub binds. A viewer
32152
+ * configured with a port-less public URL (`https://camstack.example.top`)
32153
+ * infers 443 from the scheme, asks with 443, and was handed
32154
+ * `https://192.168.1.9:443` for a hub listening on 4443. That candidate
32155
+ * cannot ever answer, and losing its race reads in the log exactly like
32156
+ * "the LAN was tried and it was slower" — so an operator sitting on his own
32157
+ * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
32158
+ * dead on every single connect.
32159
+ *
32160
+ * The hub now resolves its own listen port and reports it as `localPort`,
32161
+ * with the SOURCE of the number, so a client can tell a fact from an echo.
32162
+ *
32163
+ * Out of scope on purpose: a LAN URL that is not the hub's own socket (a
32164
+ * reverse proxy in front of it on another port) is not this method's to
32165
+ * invent either — pin it with `setNotificationEndpoint` / a configured
32166
+ * origin, which is an operator statement rather than a guess.
32102
32167
  */
32103
32168
  getConnectionEndpoints: method(external_exports.object({
32104
- /** Local hub HTTP port to use in base URLs. */
32105
- port: external_exports.number().int().min(1).max(65535),
32169
+ /**
32170
+ * LEGACY HINT — do not send from new code. Kept optional so clients
32171
+ * written against the echoing contract keep working; the hub uses it
32172
+ * only when it cannot read its own port, and says so via
32173
+ * `localPort.source === 'caller-hint'`.
32174
+ */
32175
+ port: external_exports.number().int().min(1).max(65535).optional(),
32106
32176
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
32107
32177
  * candidate. Default `true`. */
32108
32178
  includeLoopback: external_exports.boolean().optional(),
@@ -36593,6 +36663,7 @@ var BATTERY_DEVICE_PROFILE = {
36593
36663
  settings: {}
36594
36664
  };
36595
36665
  var BATTERY_UNREACHABLE_AFTER_MS = 360 * 6e4;
36666
+ var OPERATOR_WRITTEN_STALE_MS = 10 * 6e4;
36596
36667
  var METHOD_ACCESS_MAP = Object.freeze({
36597
36668
  "accessories.setChildHidden": {
36598
36669
  capName: "accessories",
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDiscover
4
- } from "./chunk-V75HN7AE.js";
4
+ } from "./chunk-UYHYTS5M.js";
5
5
  import "./chunk-LMMQX4CK.js";
6
6
 
7
7
  // src/cli.ts
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-OXQ3W5WV.js");
41
+ await import("./launcher-SCKDFMNR.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-OXQ3W5WV.js");
86
+ await import("./launcher-SCKDFMNR.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -1130,7 +1130,7 @@ function isUnknown(_value) {
1130
1130
  return true;
1131
1131
  }
1132
1132
  async function resolveServerInteractive(presetNamespace) {
1133
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-FXX2XNDX.js");
1133
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-5NNX7OG3.js");
1134
1134
  if (presetNamespace) {
1135
1135
  const spinner4 = clack.spinner();
1136
1136
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -5,7 +5,7 @@ import {
5
5
  filterHubNodes,
6
6
  resolveHubFromDiscovered,
7
7
  runDiscover
8
- } from "./chunk-V75HN7AE.js";
8
+ } from "./chunk-UYHYTS5M.js";
9
9
  import "./chunk-LMMQX4CK.js";
10
10
  export {
11
11
  DEFAULT_HUB_HTTPS_PORT,
@@ -23631,9 +23631,9 @@ var require_zod = __commonJS({
23631
23631
  }
23632
23632
  });
23633
23633
 
23634
- // ../system/dist/dist-B2CtwVW2.js
23635
- var require_dist_B2CtwVW2 = __commonJS({
23636
- "../system/dist/dist-B2CtwVW2.js"(exports) {
23634
+ // ../system/dist/dist-iAwSA2_f.js
23635
+ var require_dist_iAwSA2_f = __commonJS({
23636
+ "../system/dist/dist-iAwSA2_f.js"(exports) {
23637
23637
  "use strict";
23638
23638
  var zod = require_zod();
23639
23639
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -23641,6 +23641,7 @@ var require_dist_B2CtwVW2 = __commonJS({
23641
23641
  EventCategory2["SystemAddonsReady"] = "system.addons-ready";
23642
23642
  EventCategory2["SystemRestarting"] = "system.restarting";
23643
23643
  EventCategory2["SystemRestartCompleted"] = "system.restart-completed";
23644
+ EventCategory2["SystemTlsCertChanged"] = "system.tls-cert-changed";
23644
23645
  EventCategory2["UpdateAvailable"] = "update.available";
23645
23646
  EventCategory2["SystemReadyState"] = "system.ready-state";
23646
23647
  EventCategory2["AddonStarted"] = "addon.started";
@@ -37300,6 +37301,12 @@ var require_dist_B2CtwVW2 = __commonJS({
37300
37301
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
37301
37302
  fetchedAt: zod.z.number()
37302
37303
  });
37304
+ var InferenceDeviceExclusionReasonSchema = zod.z.enum([
37305
+ "disabled",
37306
+ "unavailable",
37307
+ "cannot-host-camera-root",
37308
+ "accelerator-preferred"
37309
+ ]);
37303
37310
  var NodeInferenceDeviceSchema = zod.z.object({
37304
37311
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
37305
37312
  key: zod.z.string(),
@@ -37330,7 +37337,17 @@ var require_dist_B2CtwVW2 = __commonJS({
37330
37337
  * available per format; this is the stored selection that becomes the
37331
37338
  * default for EVERY camera landing on this accelerator.
37332
37339
  */
37333
- steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional()
37340
+ steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional(),
37341
+ /**
37342
+ * `null` when the device IS a camera-root candidate on this node; otherwise
37343
+ * the reason the dispatcher drops it. Computed by the SAME
37344
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
37345
+ * never disagree with the election — deriving it in the UI from
37346
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
37347
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
37348
+ * "an accelerator is serving" predicate).
37349
+ */
37350
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
37334
37351
  });
37335
37352
  var NodeInferenceDevicesSchema = zod.z.object({
37336
37353
  nodeId: zod.z.string(),
@@ -42339,7 +42356,35 @@ var require_dist_B2CtwVW2 = __commonJS({
42339
42356
  */
42340
42357
  priority: zod.z.number()
42341
42358
  });
42342
- var GetConnectionEndpointsResultSchema = zod.z.object({ endpoints: zod.z.array(ConnectionEndpointSchema).readonly() });
42359
+ var LocalPortSourceEnum = zod.z.enum([
42360
+ "server-config",
42361
+ "server-env",
42362
+ "caller-hint",
42363
+ "default"
42364
+ ]);
42365
+ var AdvertisedLocalPortSchema = zod.z.object({
42366
+ port: zod.z.number().int().min(1).max(65535),
42367
+ source: LocalPortSourceEnum
42368
+ });
42369
+ var GetConnectionEndpointsResultSchema = zod.z.object({
42370
+ endpoints: zod.z.array(ConnectionEndpointSchema).readonly(),
42371
+ /**
42372
+ * The port the hub built the LAN/loopback URLs with, and where that number
42373
+ * came from.
42374
+ *
42375
+ * Returned rather than merely applied, because "the URL is right" and "the
42376
+ * client can KNOW the URL is right" are different properties. A client that
42377
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
42378
+ * a hub that echoed the port the client sent, so it cannot decide whether to
42379
+ * race the candidate or discard it. With `source` it can: anything but
42380
+ * `caller-hint` is the hub's own socket.
42381
+ *
42382
+ * Absent on hubs predating this field — a client that finds it missing is
42383
+ * talking to an echoing hub and must degrade exactly as it does for
42384
+ * `caller-hint`.
42385
+ */
42386
+ localPort: AdvertisedLocalPortSchema
42387
+ });
42343
42388
  var NotificationEndpointSchema = zod.z.object({
42344
42389
  /** The operator's explicit choice, or null for AUTO. */
42345
42390
  baseUrl: zod.z.string().nullable(),
@@ -42381,10 +42426,35 @@ var require_dist_B2CtwVW2 = __commonJS({
42381
42426
  * Honours `getAllowedAddresses()` when set — addresses outside
42382
42427
  * the allowlist are dropped (the public tunnel + loopback are
42383
42428
  * always included as escape hatches).
42429
+ *
42430
+ * **The port is the hub's, not the caller's** (D62 — a function's fact
42431
+ * belongs to whoever already owns it). This method used to take a `port`
42432
+ * and echo it onto every LAN and loopback URL it advertised, which made
42433
+ * every client a second authority on a socket only the hub binds. A viewer
42434
+ * configured with a port-less public URL (`https://camstack.example.top`)
42435
+ * infers 443 from the scheme, asks with 443, and was handed
42436
+ * `https://192.168.1.9:443` for a hub listening on 4443. That candidate
42437
+ * cannot ever answer, and losing its race reads in the log exactly like
42438
+ * "the LAN was tried and it was slower" — so an operator sitting on his own
42439
+ * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
42440
+ * dead on every single connect.
42441
+ *
42442
+ * The hub now resolves its own listen port and reports it as `localPort`,
42443
+ * with the SOURCE of the number, so a client can tell a fact from an echo.
42444
+ *
42445
+ * Out of scope on purpose: a LAN URL that is not the hub's own socket (a
42446
+ * reverse proxy in front of it on another port) is not this method's to
42447
+ * invent either — pin it with `setNotificationEndpoint` / a configured
42448
+ * origin, which is an operator statement rather than a guess.
42384
42449
  */
42385
42450
  getConnectionEndpoints: method(zod.z.object({
42386
- /** Local hub HTTP port to use in base URLs. */
42387
- port: zod.z.number().int().min(1).max(65535),
42451
+ /**
42452
+ * LEGACY HINT — do not send from new code. Kept optional so clients
42453
+ * written against the echoing contract keep working; the hub uses it
42454
+ * only when it cannot read its own port, and says so via
42455
+ * `localPort.source === 'caller-hint'`.
42456
+ */
42457
+ port: zod.z.number().int().min(1).max(65535).optional(),
42388
42458
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
42389
42459
  * candidate. Default `true`. */
42390
42460
  includeLoopback: zod.z.boolean().optional(),
@@ -56186,7 +56256,7 @@ var require_alerts_addon = __commonJS({
56186
56256
  [Symbol.toStringTag]: { value: "Module" }
56187
56257
  });
56188
56258
  require_chunk_Cek0wNdY();
56189
- var require_dist10 = require_dist_B2CtwVW2();
56259
+ var require_dist10 = require_dist_iAwSA2_f();
56190
56260
  function selectExpired(alerts, cutoffMs) {
56191
56261
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
56192
56262
  }
@@ -56238,6 +56308,10 @@ var require_alerts_addon = __commonJS({
56238
56308
  severity: "warning",
56239
56309
  titleTemplate: "System restarting"
56240
56310
  },
56311
+ "system.tls-cert-changed": {
56312
+ severity: "warning",
56313
+ titleTemplate: "TLS certificate changed"
56314
+ },
56241
56315
  "device.registered": {
56242
56316
  severity: "info",
56243
56317
  titleTemplate: "Device registered"
@@ -56726,6 +56800,8 @@ var require_alerts_addon = __commonJS({
56726
56800
  return `Storage critical for device "${deviceId ?? "unknown"}"`;
56727
56801
  case "system.boot":
56728
56802
  return `System booted in "${data.mode ?? "unknown"}" mode`;
56803
+ case "system.tls-cert-changed":
56804
+ return data.caRotated === true ? `TLS certificate reissued (${String(data.reason)}) \u2014 the local CA also changed. Re-install ${String(data.caCertPath)} on any device that trusted the old one.` : `TLS certificate reissued (${String(data.reason)}) \u2014 the local CA is unchanged, previously trusted devices keep working.`;
56729
56805
  case "process.crashed":
56730
56806
  return `Process "${processId ?? "unknown"}" crashed`;
56731
56807
  default:
@@ -56999,7 +57075,7 @@ var require_console_logging = __commonJS({
56999
57075
  [Symbol.toStringTag]: { value: "Module" }
57000
57076
  });
57001
57077
  require_chunk_Cek0wNdY();
57002
- var require_dist10 = require_dist_B2CtwVW2();
57078
+ var require_dist10 = require_dist_iAwSA2_f();
57003
57079
  var require_formatter = require_formatter_DqAKDlvN();
57004
57080
  var LEVEL_RANK = {
57005
57081
  debug: 0,
@@ -57093,7 +57169,7 @@ var require_core_blocks_addon = __commonJS({
57093
57169
  "use strict";
57094
57170
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
57095
57171
  var require_chunk = require_chunk_Cek0wNdY();
57096
- var require_dist10 = require_dist_B2CtwVW2();
57172
+ var require_dist10 = require_dist_iAwSA2_f();
57097
57173
  var node_crypto = __require("crypto");
57098
57174
  var node_fs_promises = __require("fs/promises");
57099
57175
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -57990,11 +58066,11 @@ var require_core_blocks = __commonJS({
57990
58066
  }
57991
58067
  });
57992
58068
 
57993
- // ../system/dist/retired-settings-keys-CYDGGkyM.js
57994
- var require_retired_settings_keys_CYDGGkyM = __commonJS({
57995
- "../system/dist/retired-settings-keys-CYDGGkyM.js"(exports) {
58069
+ // ../system/dist/retired-settings-keys-OfhqQio4.js
58070
+ var require_retired_settings_keys_OfhqQio4 = __commonJS({
58071
+ "../system/dist/retired-settings-keys-OfhqQio4.js"(exports) {
57996
58072
  "use strict";
57997
- var require_dist10 = require_dist_B2CtwVW2();
58073
+ var require_dist10 = require_dist_iAwSA2_f();
57998
58074
  function settingsStoreIsAuthoritativeHere(env) {
57999
58075
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
58000
58076
  return raw === "" || raw === "hub";
@@ -60037,8 +60113,8 @@ var require_device_manager_addon = __commonJS({
60037
60113
  [Symbol.toStringTag]: { value: "Module" }
60038
60114
  });
60039
60115
  require_chunk_Cek0wNdY();
60040
- var require_dist10 = require_dist_B2CtwVW2();
60041
- var require_retired_settings_keys = require_retired_settings_keys_CYDGGkyM();
60116
+ var require_dist10 = require_dist_iAwSA2_f();
60117
+ var require_retired_settings_keys = require_retired_settings_keys_OfhqQio4();
60042
60118
  var node_crypto = __require("crypto");
60043
60119
  var _camstack_types_node = require_node();
60044
60120
  var JOB_HISTORY = 20;
@@ -64365,7 +64441,7 @@ var require_hub_forwarder = __commonJS({
64365
64441
  [Symbol.toStringTag]: { value: "Module" }
64366
64442
  });
64367
64443
  require_chunk_Cek0wNdY();
64368
- var require_dist10 = require_dist_B2CtwVW2();
64444
+ var require_dist10 = require_dist_iAwSA2_f();
64369
64445
  var require_formatter = require_formatter_DqAKDlvN();
64370
64446
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
64371
64447
  var HubForwarderDestination = class {
@@ -64502,7 +64578,7 @@ var require_liveness_monitor_addon = __commonJS({
64502
64578
  "use strict";
64503
64579
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
64504
64580
  require_chunk_Cek0wNdY();
64505
- var require_dist10 = require_dist_B2CtwVW2();
64581
+ var require_dist10 = require_dist_iAwSA2_f();
64506
64582
  var NO_DEVICES = "liveness:no-devices";
64507
64583
  var ALL_OFFLINE = "liveness:all-devices-offline";
64508
64584
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -64692,7 +64768,7 @@ var require_local_auth_addon = __commonJS({
64692
64768
  [Symbol.toStringTag]: { value: "Module" }
64693
64769
  });
64694
64770
  var require_chunk = require_chunk_Cek0wNdY();
64695
- var require_dist10 = require_dist_B2CtwVW2();
64771
+ var require_dist10 = require_dist_iAwSA2_f();
64696
64772
  var node_crypto = __require("crypto");
64697
64773
  node_crypto = require_chunk.__toESM(node_crypto);
64698
64774
  var crypto$1 = __require("crypto");
@@ -72376,7 +72452,7 @@ var require_loki_logging = __commonJS({
72376
72452
  [Symbol.toStringTag]: { value: "Module" }
72377
72453
  });
72378
72454
  require_chunk_Cek0wNdY();
72379
- var require_dist10 = require_dist_B2CtwVW2();
72455
+ var require_dist10 = require_dist_iAwSA2_f();
72380
72456
  function sanitizeLabelName(raw) {
72381
72457
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
72382
72458
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -72941,7 +73017,7 @@ var require_native_metrics_addon = __commonJS({
72941
73017
  [Symbol.toStringTag]: { value: "Module" }
72942
73018
  });
72943
73019
  var require_chunk = require_chunk_Cek0wNdY();
72944
- var require_dist10 = require_dist_B2CtwVW2();
73020
+ var require_dist10 = require_dist_iAwSA2_f();
72945
73021
  var node_child_process = __require("child_process");
72946
73022
  var node_util = __require("util");
72947
73023
  var node_os = __require("os");
@@ -73883,7 +73959,7 @@ var require_filesystem_storage_addon = __commonJS({
73883
73959
  [Symbol.toStringTag]: { value: "Module" }
73884
73960
  });
73885
73961
  var require_chunk = require_chunk_Cek0wNdY();
73886
- var require_dist10 = require_dist_B2CtwVW2();
73962
+ var require_dist10 = require_dist_iAwSA2_f();
73887
73963
  var node_crypto = __require("crypto");
73888
73964
  var node_fs_promises = __require("fs/promises");
73889
73965
  var node_path = __require("path");
@@ -74999,8 +75075,8 @@ var require_sqlite_settings_addon = __commonJS({
74999
75075
  [Symbol.toStringTag]: { value: "Module" }
75000
75076
  });
75001
75077
  var require_chunk = require_chunk_Cek0wNdY();
75002
- var require_dist10 = require_dist_B2CtwVW2();
75003
- var require_retired_settings_keys = require_retired_settings_keys_CYDGGkyM();
75078
+ var require_dist10 = require_dist_iAwSA2_f();
75079
+ var require_retired_settings_keys = require_retired_settings_keys_OfhqQio4();
75004
75080
  var node_crypto = __require("crypto");
75005
75081
  var node_fs = __require("fs");
75006
75082
  var node_module = __require("module");
@@ -76544,7 +76620,7 @@ var require_storage_orchestrator_addon = __commonJS({
76544
76620
  [Symbol.toStringTag]: { value: "Module" }
76545
76621
  });
76546
76622
  var require_chunk = require_chunk_Cek0wNdY();
76547
- var require_dist10 = require_dist_B2CtwVW2();
76623
+ var require_dist10 = require_dist_iAwSA2_f();
76548
76624
  var node_crypto = __require("crypto");
76549
76625
  var node_fs_promises = __require("fs/promises");
76550
76626
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -78423,7 +78499,7 @@ var require_system_config_addon = __commonJS({
78423
78499
  [Symbol.toStringTag]: { value: "Module" }
78424
78500
  });
78425
78501
  require_chunk_Cek0wNdY();
78426
- var require_dist10 = require_dist_B2CtwVW2();
78502
+ var require_dist10 = require_dist_iAwSA2_f();
78427
78503
  var SECTION_TITLES = {
78428
78504
  server: "Server",
78429
78505
  auth: "Authentication"
@@ -96484,7 +96560,7 @@ var require_winston_logging = __commonJS({
96484
96560
  [Symbol.toStringTag]: { value: "Module" }
96485
96561
  });
96486
96562
  var require_chunk = require_chunk_Cek0wNdY();
96487
- var require_dist10 = require_dist_B2CtwVW2();
96563
+ var require_dist10 = require_dist_iAwSA2_f();
96488
96564
  var require_formatter = require_formatter_DqAKDlvN();
96489
96565
  var node_path = __require("path");
96490
96566
  node_path = require_chunk.__toESM(node_path);
@@ -97472,15 +97548,16 @@ var require_file_data_plane_DUHPHa_Y = __commonJS({
97472
97548
  }
97473
97549
  });
97474
97550
 
97475
- // ../types/dist/event-category-CRPORAAz.js
97476
- var require_event_category_CRPORAAz = __commonJS({
97477
- "../types/dist/event-category-CRPORAAz.js"(exports) {
97551
+ // ../types/dist/event-category-EY0GNjV9.js
97552
+ var require_event_category_EY0GNjV9 = __commonJS({
97553
+ "../types/dist/event-category-EY0GNjV9.js"(exports) {
97478
97554
  "use strict";
97479
97555
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
97480
97556
  EventCategory2["SystemBoot"] = "system.boot";
97481
97557
  EventCategory2["SystemAddonsReady"] = "system.addons-ready";
97482
97558
  EventCategory2["SystemRestarting"] = "system.restarting";
97483
97559
  EventCategory2["SystemRestartCompleted"] = "system.restart-completed";
97560
+ EventCategory2["SystemTlsCertChanged"] = "system.tls-cert-changed";
97484
97561
  EventCategory2["UpdateAvailable"] = "update.available";
97485
97562
  EventCategory2["SystemReadyState"] = "system.ready-state";
97486
97563
  EventCategory2["AddonStarted"] = "addon.started";
@@ -97638,11 +97715,11 @@ var require_event_category_CRPORAAz = __commonJS({
97638
97715
  }
97639
97716
  });
97640
97717
 
97641
- // ../types/dist/sleep-CMRLJj2e.js
97642
- var require_sleep_CMRLJj2e = __commonJS({
97643
- "../types/dist/sleep-CMRLJj2e.js"(exports) {
97718
+ // ../types/dist/sleep-C2XhJhkd.js
97719
+ var require_sleep_C2XhJhkd = __commonJS({
97720
+ "../types/dist/sleep-C2XhJhkd.js"(exports) {
97644
97721
  "use strict";
97645
- var require_event_category = require_event_category_CRPORAAz();
97722
+ var require_event_category = require_event_category_EY0GNjV9();
97646
97723
  var zod = require_zod();
97647
97724
  var WELL_KNOWN_TABS = [
97648
97725
  {
@@ -101079,8 +101156,8 @@ var require_addon = __commonJS({
101079
101156
  "../types/dist/addon.js"(exports) {
101080
101157
  "use strict";
101081
101158
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
101082
- var require_event_category = require_event_category_CRPORAAz();
101083
- var require_sleep = require_sleep_CMRLJj2e();
101159
+ var require_event_category = require_event_category_EY0GNjV9();
101160
+ var require_sleep = require_sleep_C2XhJhkd();
101084
101161
  var require_err_msg = require_err_msg_COpsHMw2();
101085
101162
  var CAP_INPUT_DEFAULTS = Object.freeze({
101086
101163
  "addons": { "getLogs": { "limit": 100 } },
@@ -107954,9 +108031,9 @@ var require_dist2 = __commonJS({
107954
108031
  }
107955
108032
  });
107956
108033
 
107957
- // ../system/dist/manifest-python-deps-MA_BZGBz.js
107958
- var require_manifest_python_deps_MA_BZGBz = __commonJS({
107959
- "../system/dist/manifest-python-deps-MA_BZGBz.js"(exports) {
108034
+ // ../system/dist/manifest-python-deps-CwBbX4Ut.js
108035
+ var require_manifest_python_deps_CwBbX4Ut = __commonJS({
108036
+ "../system/dist/manifest-python-deps-CwBbX4Ut.js"(exports) {
107960
108037
  "use strict";
107961
108038
  var require_chunk = require_chunk_Cek0wNdY();
107962
108039
  var node_crypto = __require("crypto");
@@ -107977,8 +108054,8 @@ var require_manifest_python_deps_MA_BZGBz = __commonJS({
107977
108054
  var node_vm = __require("vm");
107978
108055
  node_vm = require_chunk.__toESM(node_vm);
107979
108056
  var _camstack_types_addon = require_addon();
107980
- var _trpc_client = require_dist2();
107981
108057
  var node_net = __require("net");
108058
+ var _trpc_client = require_dist2();
107982
108059
  var HEAP_WATCH_INTERVAL_MS = 6e4;
107983
108060
  var HEAP_WATCH_WARN_RATIO = 0.8;
107984
108061
  var HEAP_WATCH_ESCALATE_RATIO = 0.7;
@@ -118760,7 +118837,7 @@ var require_dist3 = __commonJS({
118760
118837
  "use strict";
118761
118838
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
118762
118839
  var require_chunk = require_chunk_Cek0wNdY();
118763
- var require_dist10 = require_dist_B2CtwVW2();
118840
+ var require_dist10 = require_dist_iAwSA2_f();
118764
118841
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
118765
118842
  require_alerts();
118766
118843
  var require_formatter = require_formatter_DqAKDlvN();
@@ -118785,7 +118862,7 @@ var require_dist3 = __commonJS({
118785
118862
  require_system_config();
118786
118863
  var require_builtins_winston_logging_index = require_winston_logging();
118787
118864
  var require_file_data_plane = require_file_data_plane_DUHPHa_Y();
118788
- var require_manifest_python_deps = require_manifest_python_deps_MA_BZGBz();
118865
+ var require_manifest_python_deps = require_manifest_python_deps_CwBbX4Ut();
118789
118866
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
118790
118867
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
118791
118868
  var zod = require_zod();
@@ -118806,6 +118883,7 @@ var require_dist3 = __commonJS({
118806
118883
  var node_vm = __require("vm");
118807
118884
  node_vm = require_chunk.__toESM(node_vm);
118808
118885
  var _camstack_types_addon = require_addon();
118886
+ var node_net = __require("net");
118809
118887
  var node_url = __require("url");
118810
118888
  var node_events = __require("events");
118811
118889
  var node_stream = __require("stream");
@@ -120570,63 +120648,330 @@ var require_dist3 = __commonJS({
120570
120648
  };
120571
120649
  }
120572
120650
  };
120573
- var execFileAsync$3 = (0, node_util.promisify)(node_child_process.execFile);
120574
- async function ensureTlsCert(dataDir, options) {
120575
- const tlsDir = (0, node_path.join)(dataDir, "tls");
120576
- const certPath = (0, node_path.join)(tlsDir, "camstack.crt");
120577
- const keyPath = (0, node_path.join)(tlsDir, "camstack.key");
120578
- if ((0, node_fs.existsSync)(certPath) && (0, node_fs.existsSync)(keyPath)) try {
120579
- const x509 = new node_crypto.X509Certificate((0, node_fs.readFileSync)(certPath));
120580
- if (new Date(x509.validTo) > /* @__PURE__ */ new Date()) return {
120581
- certPath,
120582
- keyPath,
120583
- generated: false
120584
- };
120651
+ var SERVER_AUTH_OID = "1.3.6.1.5.5.7.3.1";
120652
+ var MAX_LEAF_VALIDITY_DAYS = 397;
120653
+ var LEAF_RENEWAL_WINDOW_DAYS = 30;
120654
+ var CA_VALIDITY_DAYS = 3650;
120655
+ var MS_PER_DAY = 864e5;
120656
+ var REASONS_REQUIRING_NEW_CA = [
120657
+ "missing",
120658
+ "unreadable",
120659
+ "no-local-ca",
120660
+ "ca-expiring"
120661
+ ];
120662
+ function reasonRequiresNewCa(reason) {
120663
+ return REASONS_REQUIRING_NEW_CA.includes(reason);
120664
+ }
120665
+ function parse$1(pem) {
120666
+ try {
120667
+ return new node_crypto.X509Certificate(pem);
120585
120668
  } catch {
120669
+ return null;
120586
120670
  }
120587
- (0, node_fs.mkdirSync)(tlsDir, { recursive: true });
120588
- const cn = options?.commonName ?? "camstack.local";
120589
- const validDays = options?.validDays ?? 825;
120590
- const sanDns = /* @__PURE__ */ new Set([
120671
+ }
120672
+ function daysBetween(from, to) {
120673
+ return (to.getTime() - from.getTime()) / MS_PER_DAY;
120674
+ }
120675
+ function hasServerAuthEku(leaf) {
120676
+ const eku = leaf.keyUsage;
120677
+ return eku !== void 0 && eku.length === 1 && eku[0] === "1.3.6.1.5.5.7.3.1";
120678
+ }
120679
+ function keyMatches(leaf, keyPem) {
120680
+ try {
120681
+ return leaf.checkPrivateKey((0, node_crypto.createPrivateKey)(keyPem));
120682
+ } catch {
120683
+ return false;
120684
+ }
120685
+ }
120686
+ function coversIdentity(leaf, identity) {
120687
+ for (const name2 of identity.requiredDnsNames) if (leaf.checkHost(name2) === void 0) return false;
120688
+ for (const ip of identity.requiredIpAddresses) if (leaf.checkIP(ip) === void 0) return false;
120689
+ return true;
120690
+ }
120691
+ function evaluateExistingCert(input) {
120692
+ const leaf = parse$1(input.chainPem);
120693
+ const ca = parse$1(input.caPem);
120694
+ if (leaf === null || ca === null) return "unreadable";
120695
+ if (daysBetween(input.now, new Date(ca.validTo)) < 427) return "ca-expiring";
120696
+ if (!keyMatches(leaf, input.keyPem)) return "key-mismatch";
120697
+ if (daysBetween(input.now, new Date(leaf.validTo)) < 30) return "expiring";
120698
+ if (!hasServerAuthEku(leaf)) return "missing-server-auth-eku";
120699
+ if (leaf.ca || !leaf.checkIssued(ca) || !leaf.verify(ca.publicKey)) return "not-issued-by-local-ca";
120700
+ if (daysBetween(new Date(leaf.validFrom), new Date(leaf.validTo)) > 398) return "validity-too-long";
120701
+ if (!coversIdentity(leaf, input.identity)) return "san-coverage-gap";
120702
+ return null;
120703
+ }
120704
+ var VOLATILE_INTERFACE_PREFIXES = [
120705
+ "docker",
120706
+ "br-",
120707
+ "veth",
120708
+ "virbr",
120709
+ "cni",
120710
+ "flannel",
120711
+ "tun",
120712
+ "utun",
120713
+ "tap",
120714
+ "wg",
120715
+ "zt",
120716
+ "tailscale",
120717
+ "ppp",
120718
+ "awdl",
120719
+ "llw"
120720
+ ];
120721
+ var DNS_NAME_PATTERN = /^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$/;
120722
+ function isIpv4(value) {
120723
+ return (0, node_net.isIP)(value) === 4;
120724
+ }
120725
+ function isIpv6(value) {
120726
+ return (0, node_net.isIP)(value) === 6;
120727
+ }
120728
+ function isLinkLocal(value) {
120729
+ if (isIpv4(value)) return value.startsWith("169.254.");
120730
+ if (!isIpv6(value)) return false;
120731
+ const firstGroup = value.split(":")[0] ?? "";
120732
+ if (firstGroup === "") return false;
120733
+ const parsed = Number.parseInt(firstGroup, 16);
120734
+ return Number.isFinite(parsed) && (parsed & 65472) === 65152;
120735
+ }
120736
+ function isUniqueLocalIpv6(value) {
120737
+ if (!isIpv6(value)) return false;
120738
+ const firstGroup = value.split(":")[0] ?? "";
120739
+ const parsed = Number.parseInt(firstGroup, 16);
120740
+ return Number.isFinite(parsed) && (parsed & 65024) === 64512;
120741
+ }
120742
+ function isVolatileInterface(name2) {
120743
+ const lower = name2.toLowerCase();
120744
+ return VOLATILE_INTERFACE_PREFIXES.some((prefix) => lower.startsWith(prefix));
120745
+ }
120746
+ function isStableGateAddress(address) {
120747
+ if (isLinkLocal(address)) return false;
120748
+ if (isIpv4(address)) return true;
120749
+ return isUniqueLocalIpv6(address);
120750
+ }
120751
+ function addSan(dns, ips, value) {
120752
+ const trimmed = value.trim();
120753
+ if (trimmed === "") return;
120754
+ if ((0, node_net.isIP)(trimmed) !== 0) {
120755
+ if (!isLinkLocal(trimmed)) ips.add(trimmed);
120756
+ return;
120757
+ }
120758
+ if (DNS_NAME_PATTERN.test(trimmed)) dns.add(trimmed);
120759
+ }
120760
+ function collectCertIdentity(options) {
120761
+ const dnsNames = /* @__PURE__ */ new Set();
120762
+ const ipAddresses = /* @__PURE__ */ new Set(["127.0.0.1", "::1"]);
120763
+ const requiredDnsNames = /* @__PURE__ */ new Set();
120764
+ const requiredIpAddresses = /* @__PURE__ */ new Set(["127.0.0.1"]);
120765
+ for (const name2 of [
120591
120766
  "localhost",
120592
- cn,
120767
+ options.commonName,
120593
120768
  node_os.hostname()
120594
- ]);
120595
- const sanIps = /* @__PURE__ */ new Set(["127.0.0.1", "::1"]);
120596
- const interfaces = node_os.networkInterfaces();
120597
- for (const addrs of Object.values(interfaces)) {
120598
- if (!addrs) continue;
120599
- for (const addr of addrs) if (!addr.internal) sanIps.add(addr.address);
120600
- }
120601
- for (const san of options?.extraSans ?? []) if (san.match(/^\d+\.\d+\.\d+\.\d+$/) || san.includes(":")) sanIps.add(san);
120602
- else sanDns.add(san);
120603
- const sanParts = [];
120604
- for (const dns of sanDns) sanParts.push(`DNS:${dns}`);
120605
- for (const ip of sanIps) sanParts.push(`IP:${ip}`);
120606
- const sanString = sanParts.join(",");
120769
+ ]) {
120770
+ addSan(dnsNames, ipAddresses, name2);
120771
+ addSan(requiredDnsNames, requiredIpAddresses, name2);
120772
+ }
120773
+ for (const [ifaceName, addrs] of Object.entries(node_os.networkInterfaces())) for (const addr of addrs ?? []) {
120774
+ if (addr.internal) continue;
120775
+ if (isLinkLocal(addr.address)) continue;
120776
+ ipAddresses.add(addr.address);
120777
+ if (isVolatileInterface(ifaceName)) continue;
120778
+ if (isStableGateAddress(addr.address)) requiredIpAddresses.add(addr.address);
120779
+ }
120780
+ for (const san of options.extraSans) {
120781
+ addSan(dnsNames, ipAddresses, san);
120782
+ addSan(requiredDnsNames, requiredIpAddresses, san);
120783
+ }
120784
+ return {
120785
+ dnsNames: [...dnsNames],
120786
+ ipAddresses: [...ipAddresses],
120787
+ requiredDnsNames: [...requiredDnsNames],
120788
+ requiredIpAddresses: [...requiredIpAddresses]
120789
+ };
120790
+ }
120791
+ var execFileAsync$3 = (0, node_util.promisify)(node_child_process.execFile);
120792
+ var CA_COMMON_NAME = "CamStack Local CA";
120793
+ function sanValue(identity) {
120794
+ const parts = [];
120795
+ for (const dns of identity.dnsNames) parts.push(`DNS:${dns}`);
120796
+ for (const ip of identity.ipAddresses) if ((0, node_net.isIP)(ip) !== 0) parts.push(`IP:${ip}`);
120797
+ return parts.join(",");
120798
+ }
120799
+ async function generateCa(tmpDir) {
120800
+ const caCertPath = (0, node_path.join)(tmpDir, "ca.crt");
120801
+ const caKeyPath = (0, node_path.join)(tmpDir, "ca.key");
120607
120802
  await execFileAsync$3("openssl", [
120608
120803
  "req",
120609
120804
  "-x509",
120610
120805
  "-newkey",
120611
120806
  "rsa:2048",
120807
+ "-nodes",
120808
+ "-sha256",
120809
+ "-days",
120810
+ String(CA_VALIDITY_DAYS),
120811
+ "-keyout",
120812
+ caKeyPath,
120813
+ "-out",
120814
+ caCertPath,
120815
+ "-subj",
120816
+ `/CN=${CA_COMMON_NAME}`,
120817
+ "-addext",
120818
+ "basicConstraints=critical,CA:TRUE,pathlen:0",
120819
+ "-addext",
120820
+ "keyUsage=critical,keyCertSign,cRLSign",
120821
+ "-addext",
120822
+ "subjectKeyIdentifier=hash"
120823
+ ]);
120824
+ await (0, node_fs_promises.chmod)(caKeyPath, 384);
120825
+ return {
120826
+ caCertPath,
120827
+ caKeyPath
120828
+ };
120829
+ }
120830
+ async function issueLeaf(tmpDir, ca, identity, commonName, validDays) {
120831
+ const keyPath = (0, node_path.join)(tmpDir, "leaf.key");
120832
+ const csrPath = (0, node_path.join)(tmpDir, "leaf.csr");
120833
+ const certPath = (0, node_path.join)(tmpDir, "leaf.crt");
120834
+ const extPath = (0, node_path.join)(tmpDir, "leaf.ext");
120835
+ await (0, node_fs_promises.writeFile)(extPath, [
120836
+ "basicConstraints=critical,CA:FALSE",
120837
+ "keyUsage=critical,digitalSignature,keyEncipherment",
120838
+ "extendedKeyUsage=serverAuth",
120839
+ "subjectKeyIdentifier=hash",
120840
+ "authorityKeyIdentifier=keyid,issuer",
120841
+ `subjectAltName=${sanValue(identity)}`,
120842
+ ""
120843
+ ].join("\n"), "utf-8");
120844
+ await execFileAsync$3("openssl", [
120845
+ "req",
120846
+ "-new",
120847
+ "-newkey",
120848
+ "rsa:2048",
120849
+ "-nodes",
120850
+ "-sha256",
120612
120851
  "-keyout",
120613
120852
  keyPath,
120614
120853
  "-out",
120615
- certPath,
120854
+ csrPath,
120855
+ "-subj",
120856
+ `/CN=${commonName}`
120857
+ ]);
120858
+ await execFileAsync$3("openssl", [
120859
+ "x509",
120860
+ "-req",
120861
+ "-in",
120862
+ csrPath,
120863
+ "-CA",
120864
+ ca.caCertPath,
120865
+ "-CAkey",
120866
+ ca.caKeyPath,
120867
+ "-set_serial",
120868
+ `0x00${(0, node_crypto.randomBytes)(16).toString("hex")}`,
120616
120869
  "-days",
120617
120870
  String(validDays),
120618
- "-nodes",
120619
- "-subj",
120620
- `/CN=${cn}`,
120621
- "-addext",
120622
- `subjectAltName=${sanString}`
120871
+ "-sha256",
120872
+ "-extfile",
120873
+ extPath,
120874
+ "-out",
120875
+ certPath
120623
120876
  ]);
120624
- const { chmod } = await import("fs/promises");
120625
- await chmod(keyPath, 384);
120877
+ await (0, node_fs_promises.chmod)(keyPath, 384);
120878
+ await (0, node_fs_promises.rm)(csrPath, { force: true });
120879
+ await (0, node_fs_promises.rm)(extPath, { force: true });
120626
120880
  return {
120627
120881
  certPath,
120628
- keyPath,
120629
- generated: true
120882
+ keyPath
120883
+ };
120884
+ }
120885
+ var DEFAULT_COMMON_NAME = "camstack.local";
120886
+ async function ensureTlsCert(dataDir, options) {
120887
+ const tlsDir = (0, node_path.join)(dataDir, "tls");
120888
+ const paths = {
120889
+ certPath: (0, node_path.join)(tlsDir, "camstack.crt"),
120890
+ keyPath: (0, node_path.join)(tlsDir, "camstack.key"),
120891
+ caCertPath: (0, node_path.join)(tlsDir, "camstack-ca.crt"),
120892
+ caKeyPath: (0, node_path.join)(tlsDir, "camstack-ca.key")
120893
+ };
120894
+ const commonName = options?.commonName ?? DEFAULT_COMMON_NAME;
120895
+ const validDays = Math.min(options?.validDays ?? 397, 397);
120896
+ const identity = collectCertIdentity({
120897
+ commonName,
120898
+ extraSans: options?.extraSans ?? []
120899
+ });
120900
+ const reason = decideRegeneration(paths, identity);
120901
+ if (reason === null) return describe(paths, false, null, false, null);
120902
+ const previousFingerprint = readFingerprint(paths.certPath);
120903
+ const caRotated = reasonRequiresNewCa(reason);
120904
+ (0, node_fs.mkdirSync)(tlsDir, { recursive: true });
120905
+ await regenerate(paths, identity, commonName, validDays, caRotated);
120906
+ return describe(paths, true, reason, caRotated, previousFingerprint);
120907
+ }
120908
+ function readFingerprint(certPath) {
120909
+ try {
120910
+ return new node_crypto.X509Certificate((0, node_fs.readFileSync)(certPath)).fingerprint256;
120911
+ } catch {
120912
+ return null;
120913
+ }
120914
+ }
120915
+ function decideRegeneration(paths, identity) {
120916
+ if (!(0, node_fs.existsSync)(paths.certPath) || !(0, node_fs.existsSync)(paths.keyPath)) return "missing";
120917
+ if (!(0, node_fs.existsSync)(paths.caCertPath) || !(0, node_fs.existsSync)(paths.caKeyPath)) return "no-local-ca";
120918
+ try {
120919
+ return evaluateExistingCert({
120920
+ chainPem: (0, node_fs.readFileSync)(paths.certPath, "utf-8"),
120921
+ keyPem: (0, node_fs.readFileSync)(paths.keyPath, "utf-8"),
120922
+ caPem: (0, node_fs.readFileSync)(paths.caCertPath, "utf-8"),
120923
+ identity,
120924
+ now: /* @__PURE__ */ new Date()
120925
+ });
120926
+ } catch {
120927
+ return "unreadable";
120928
+ }
120929
+ }
120930
+ async function regenerate(paths, identity, commonName, validDays, newCa) {
120931
+ const scratch = await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "camstack-tls-"));
120932
+ try {
120933
+ const ca = newCa ? await generateCa(scratch) : {
120934
+ caCertPath: paths.caCertPath,
120935
+ caKeyPath: paths.caKeyPath
120936
+ };
120937
+ const leaf = await issueLeaf(scratch, ca, identity, commonName, validDays);
120938
+ const chainPath = (0, node_path.join)(scratch, "chain.crt");
120939
+ await (0, node_fs_promises.writeFile)(chainPath, `${(0, node_fs.readFileSync)(leaf.certPath, "utf-8").trimEnd()}
120940
+ ${(0, node_fs.readFileSync)(ca.caCertPath, "utf-8").trimEnd()}
120941
+ `, "utf-8");
120942
+ if (newCa) {
120943
+ await (0, node_fs_promises.copyFile)(ca.caCertPath, `${paths.caCertPath}.new`);
120944
+ await (0, node_fs_promises.copyFile)(ca.caKeyPath, `${paths.caKeyPath}.new`);
120945
+ (0, node_fs.renameSync)(`${paths.caCertPath}.new`, paths.caCertPath);
120946
+ (0, node_fs.renameSync)(`${paths.caKeyPath}.new`, paths.caKeyPath);
120947
+ await (0, node_fs_promises.chmod)(paths.caKeyPath, 384);
120948
+ }
120949
+ await (0, node_fs_promises.copyFile)(leaf.keyPath, `${paths.keyPath}.new`);
120950
+ await (0, node_fs_promises.copyFile)(chainPath, `${paths.certPath}.new`);
120951
+ (0, node_fs.renameSync)(`${paths.keyPath}.new`, paths.keyPath);
120952
+ (0, node_fs.renameSync)(`${paths.certPath}.new`, paths.certPath);
120953
+ await (0, node_fs_promises.chmod)(paths.keyPath, 384);
120954
+ } finally {
120955
+ await (0, node_fs_promises.rm)(scratch, {
120956
+ recursive: true,
120957
+ force: true
120958
+ });
120959
+ }
120960
+ }
120961
+ function describe(paths, generated, reason, caRotated, previousFingerprintSha256) {
120962
+ const leaf = new node_crypto.X509Certificate((0, node_fs.readFileSync)(paths.certPath));
120963
+ const ca = new node_crypto.X509Certificate((0, node_fs.readFileSync)(paths.caCertPath));
120964
+ const san = leaf.subjectAltName ?? "";
120965
+ return {
120966
+ ...paths,
120967
+ generated,
120968
+ reason,
120969
+ caRotated,
120970
+ fingerprintSha256: leaf.fingerprint256,
120971
+ previousFingerprintSha256,
120972
+ caFingerprintSha256: ca.fingerprint256,
120973
+ validTo: new Date(leaf.validTo).toISOString(),
120974
+ sans: san === "" ? [] : san.split(", ")
120630
120975
  };
120631
120976
  }
120632
120977
  function loadTlsCert(certPath, keyPath) {
@@ -198906,6 +199251,8 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
198906
199251
  exports.AlertCenterAddon = require_builtins_alerts_alerts_addon.AlertCenterAddon;
198907
199252
  exports.ApiKeyManager = require_builtins_local_auth_local_auth_addon.ApiKeyManager;
198908
199253
  exports.AuthManager = require_builtins_local_auth_local_auth_addon.AuthManager;
199254
+ exports.CA_COMMON_NAME = CA_COMMON_NAME;
199255
+ exports.CA_VALIDITY_DAYS = CA_VALIDITY_DAYS;
198909
199256
  exports.CLUSTER_SECRET_MISMATCH_TYPE = CLUSTER_SECRET_MISMATCH_TYPE;
198910
199257
  exports.CLUSTER_SECRET_REJECTED_EXIT_CODE = CLUSTER_SECRET_REJECTED_EXIT_CODE;
198911
199258
  exports.CORE_CAP_SERVICE_NAME = CORE_CAP_SERVICE_NAME;
@@ -198952,6 +199299,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
198952
199299
  exports.INFRA_CAPABILITIES = INFRA_CAPABILITIES;
198953
199300
  exports.IntegrationRegistry = IntegrationRegistry;
198954
199301
  exports.JobJournal = JobJournal;
199302
+ exports.LEAF_RENEWAL_WINDOW_DAYS = LEAF_RENEWAL_WINDOW_DAYS;
198955
199303
  exports.LifecycleJobEngine = LifecycleJobEngine;
198956
199304
  exports.LifecycleStateMachine = LifecycleStateMachine;
198957
199305
  exports.LivenessMonitorAddon = require_builtins_liveness_monitor_liveness_monitor_addon.LivenessMonitorAddon;
@@ -198962,6 +199310,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
198962
199310
  exports.LogRingBuffer = LogRingBuffer;
198963
199311
  exports.LokiDestination = require_builtins_loki_logging_index.LokiDestination$1;
198964
199312
  exports.LokiLoggingAddon = require_builtins_loki_logging_index.LokiLoggingAddon$1;
199313
+ exports.MAX_LEAF_VALIDITY_DAYS = MAX_LEAF_VALIDITY_DAYS;
198965
199314
  exports.METHOD_ACCESS_MAP = require_dist10.METHOD_ACCESS_MAP;
198966
199315
  exports.ModelDownloadService = require_file_data_plane.ModelDownloadService;
198967
199316
  exports.NATIVE_PROVIDER_SERVICE_INFIX = require_manifest_python_deps.NATIVE_PROVIDER_SERVICE_INFIX;
@@ -198987,6 +199336,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
198987
199336
  exports.ReadinessTimeoutError = require_dist10.ReadinessTimeoutError;
198988
199337
  exports.ReplEngine = ReplEngine;
198989
199338
  exports.RingBuffer = RingBuffer;
199339
+ exports.SERVER_AUTH_OID = SERVER_AUTH_OID;
198990
199340
  exports.ScopedLogger = ScopedLogger;
198991
199341
  exports.ScopedTokenManager = require_builtins_local_auth_local_auth_addon.ScopedTokenManager;
198992
199342
  exports.SocketChannel = require_manifest_python_deps.SocketChannel;
@@ -199038,6 +199388,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
199038
199388
  exports.clearPendingRestart = clearPendingRestart;
199039
199389
  exports.clusterEventTopic = require_manifest_python_deps.clusterEventTopic;
199040
199390
  exports.clusterSecretMatches = clusterSecretMatches;
199391
+ exports.collectCertIdentity = collectCertIdentity;
199041
199392
  exports.collectModelFiles = require_file_data_plane.collectModelFiles;
199042
199393
  exports.contentTypeFor = require_file_data_plane.contentTypeFor;
199043
199394
  exports.copyDirRecursive = copyDirRecursive;
@@ -199104,6 +199455,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
199104
199455
  }
199105
199456
  });
199106
199457
  exports.ensureTlsCert = ensureTlsCert;
199458
+ exports.evaluateExistingCert = evaluateExistingCert;
199107
199459
  exports.expandCapMethods = require_dist10.expandCapMethods;
199108
199460
  exports.fetchJson = require_file_data_plane.fetchJson;
199109
199461
  Object.defineProperty(exports, "findInPath", {
@@ -237867,7 +238219,7 @@ var require_enums = __commonJS({
237867
238219
  "../types/dist/enums.js"(exports) {
237868
238220
  "use strict";
237869
238221
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
237870
- var require_event_category = require_event_category_CRPORAAz();
238222
+ var require_event_category = require_event_category_EY0GNjV9();
237871
238223
  var EventSourceType = /* @__PURE__ */ (function(EventSourceType2) {
237872
238224
  EventSourceType2["Addon"] = "addon";
237873
238225
  EventSourceType2["Core"] = "core";
@@ -237887,8 +238239,8 @@ var require_dist9 = __commonJS({
237887
238239
  "../types/dist/index.js"(exports) {
237888
238240
  "use strict";
237889
238241
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
237890
- var require_event_category = require_event_category_CRPORAAz();
237891
- var require_sleep = require_sleep_CMRLJj2e();
238242
+ var require_event_category = require_event_category_EY0GNjV9();
238243
+ var require_sleep = require_sleep_C2XhJhkd();
237892
238244
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
237893
238245
  var require_enums2 = require_enums();
237894
238246
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -252258,6 +252610,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252258
252610
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
252259
252611
  fetchedAt: zod.z.number()
252260
252612
  });
252613
+ var INFERENCE_DEVICE_EXCLUSION_REASONS = [
252614
+ "disabled",
252615
+ "unavailable",
252616
+ "cannot-host-camera-root",
252617
+ "accelerator-preferred"
252618
+ ];
252619
+ var InferenceDeviceExclusionReasonSchema = zod.z.enum(INFERENCE_DEVICE_EXCLUSION_REASONS);
252261
252620
  var NodeInferenceDeviceSchema = zod.z.object({
252262
252621
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
252263
252622
  key: zod.z.string(),
@@ -252288,7 +252647,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252288
252647
  * available per format; this is the stored selection that becomes the
252289
252648
  * default for EVERY camera landing on this accelerator.
252290
252649
  */
252291
- steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional()
252650
+ steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional(),
252651
+ /**
252652
+ * `null` when the device IS a camera-root candidate on this node; otherwise
252653
+ * the reason the dispatcher drops it. Computed by the SAME
252654
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
252655
+ * never disagree with the election — deriving it in the UI from
252656
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
252657
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
252658
+ * "an accelerator is serving" predicate).
252659
+ */
252660
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
252292
252661
  });
252293
252662
  var NodeInferenceDevicesSchema = zod.z.object({
252294
252663
  nodeId: zod.z.string(),
@@ -257320,7 +257689,35 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
257320
257689
  */
257321
257690
  priority: zod.z.number()
257322
257691
  });
257323
- var GetConnectionEndpointsResultSchema = zod.z.object({ endpoints: zod.z.array(ConnectionEndpointSchema).readonly() });
257692
+ var LocalPortSourceEnum = zod.z.enum([
257693
+ "server-config",
257694
+ "server-env",
257695
+ "caller-hint",
257696
+ "default"
257697
+ ]);
257698
+ var AdvertisedLocalPortSchema = zod.z.object({
257699
+ port: zod.z.number().int().min(1).max(65535),
257700
+ source: LocalPortSourceEnum
257701
+ });
257702
+ var GetConnectionEndpointsResultSchema = zod.z.object({
257703
+ endpoints: zod.z.array(ConnectionEndpointSchema).readonly(),
257704
+ /**
257705
+ * The port the hub built the LAN/loopback URLs with, and where that number
257706
+ * came from.
257707
+ *
257708
+ * Returned rather than merely applied, because "the URL is right" and "the
257709
+ * client can KNOW the URL is right" are different properties. A client that
257710
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
257711
+ * a hub that echoed the port the client sent, so it cannot decide whether to
257712
+ * race the candidate or discard it. With `source` it can: anything but
257713
+ * `caller-hint` is the hub's own socket.
257714
+ *
257715
+ * Absent on hubs predating this field — a client that finds it missing is
257716
+ * talking to an echoing hub and must degrade exactly as it does for
257717
+ * `caller-hint`.
257718
+ */
257719
+ localPort: AdvertisedLocalPortSchema
257720
+ });
257324
257721
  var NotificationEndpointSchema = zod.z.object({
257325
257722
  /** The operator's explicit choice, or null for AUTO. */
257326
257723
  baseUrl: zod.z.string().nullable(),
@@ -257362,10 +257759,35 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
257362
257759
  * Honours `getAllowedAddresses()` when set — addresses outside
257363
257760
  * the allowlist are dropped (the public tunnel + loopback are
257364
257761
  * always included as escape hatches).
257762
+ *
257763
+ * **The port is the hub's, not the caller's** (D62 — a function's fact
257764
+ * belongs to whoever already owns it). This method used to take a `port`
257765
+ * and echo it onto every LAN and loopback URL it advertised, which made
257766
+ * every client a second authority on a socket only the hub binds. A viewer
257767
+ * configured with a port-less public URL (`https://camstack.example.top`)
257768
+ * infers 443 from the scheme, asks with 443, and was handed
257769
+ * `https://192.168.1.9:443` for a hub listening on 4443. That candidate
257770
+ * cannot ever answer, and losing its race reads in the log exactly like
257771
+ * "the LAN was tried and it was slower" — so an operator sitting on his own
257772
+ * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
257773
+ * dead on every single connect.
257774
+ *
257775
+ * The hub now resolves its own listen port and reports it as `localPort`,
257776
+ * with the SOURCE of the number, so a client can tell a fact from an echo.
257777
+ *
257778
+ * Out of scope on purpose: a LAN URL that is not the hub's own socket (a
257779
+ * reverse proxy in front of it on another port) is not this method's to
257780
+ * invent either — pin it with `setNotificationEndpoint` / a configured
257781
+ * origin, which is an operator statement rather than a guess.
257365
257782
  */
257366
257783
  getConnectionEndpoints: require_sleep.method(zod.z.object({
257367
- /** Local hub HTTP port to use in base URLs. */
257368
- port: zod.z.number().int().min(1).max(65535),
257784
+ /**
257785
+ * LEGACY HINT — do not send from new code. Kept optional so clients
257786
+ * written against the echoing contract keep working; the hub uses it
257787
+ * only when it cannot read its own port, and says so via
257788
+ * `localPort.source === 'caller-hint'`.
257789
+ */
257790
+ port: zod.z.number().int().min(1).max(65535).optional(),
257369
257791
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
257370
257792
  * candidate. Default `true`. */
257371
257793
  includeLoopback: zod.z.boolean().optional(),
@@ -263609,10 +264031,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263609
264031
  } };
263610
264032
  }
263611
264033
  var LAST_FETCHED_FIELD = "lastFetchedAt";
264034
+ var RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS = 6e4;
264035
+ var OPERATOR_WRITTEN_STALE_MS = 10 * 6e4;
263612
264036
  function createRuntimeStateBridge(params) {
263613
264037
  const { runtimeState, cap, ownDeviceId, refresh, staleMs, empty, logger } = params;
263614
264038
  const missCooldownMs = params.refreshMissCooldownMs ?? 6e4;
264039
+ const staleRead = params.staleRead ?? "await-refresh";
263615
264040
  let missCooldownUntil = 0;
264041
+ let refreshInFlight = null;
263616
264042
  const readFetchedAt = () => {
263617
264043
  const value = runtimeState.getCapState(cap.name)?.[LAST_FETCHED_FIELD];
263618
264044
  return typeof value === "number" ? value : 0;
@@ -263627,14 +264053,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263627
264053
  }
263628
264054
  });
263629
264055
  };
263630
- const ensureFresh = async () => {
263631
- const slice = runtimeState.getCapState(cap.name);
263632
- const fetchedAt = readFetchedAt();
263633
- if (slice && Date.now() - fetchedAt <= staleMs) {
263634
- missCooldownUntil = 0;
263635
- return;
263636
- }
263637
- if (Date.now() < missCooldownUntil) return;
264056
+ const runRefresh = async (fetchedAt) => {
263638
264057
  try {
263639
264058
  await refresh();
263640
264059
  } catch (err) {
@@ -263650,6 +264069,34 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263650
264069
  }
263651
264070
  openMissCooldown(void 0);
263652
264071
  };
264072
+ const startRefresh = (fetchedAt) => {
264073
+ const existing = refreshInFlight;
264074
+ if (existing !== null) return existing;
264075
+ const started = runRefresh(fetchedAt).catch(() => void 0).finally(() => {
264076
+ refreshInFlight = null;
264077
+ });
264078
+ refreshInFlight = started;
264079
+ return started;
264080
+ };
264081
+ const ensureFresh = async () => {
264082
+ const slice = runtimeState.getCapState(cap.name);
264083
+ const fetchedAt = readFetchedAt();
264084
+ if (slice && Date.now() - fetchedAt <= staleMs) {
264085
+ missCooldownUntil = 0;
264086
+ return;
264087
+ }
264088
+ if (Date.now() < missCooldownUntil) return;
264089
+ if (staleRead === "serve-and-revalidate") {
264090
+ if (refreshInFlight !== null) return;
264091
+ if (slice && fetchedAt > 0) {
264092
+ startRefresh(fetchedAt);
264093
+ return;
264094
+ }
264095
+ await startRefresh(fetchedAt);
264096
+ return;
264097
+ }
264098
+ await runRefresh(fetchedAt);
264099
+ };
263653
264100
  const projectStatus = () => {
263654
264101
  const slice = runtimeState.getCapState(cap.name);
263655
264102
  if (!slice) return empty();
@@ -277087,6 +277534,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
277087
277534
  exports.HumidifierStatusSchema = HumidifierStatusSchema;
277088
277535
  exports.HumiditySensorStatusSchema = HumiditySensorStatusSchema;
277089
277536
  exports.HvacModeSchema = HvacModeSchema;
277537
+ exports.INFERENCE_DEVICE_EXCLUSION_REASONS = INFERENCE_DEVICE_EXCLUSION_REASONS;
277090
277538
  exports.ImageContractSchema = ImageContractSchema;
277091
277539
  exports.ImageContractStateSchema = ImageContractStateSchema;
277092
277540
  exports.ImageRotateSchema = ImageRotateSchema;
@@ -277094,6 +277542,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
277094
277542
  exports.ImageSettingsPatchSchema = ImageSettingsPatchSchema;
277095
277543
  exports.ImageSettingsStatusSchema = ImageSettingsStatusSchema;
277096
277544
  exports.ImageStatusSchema = ImageStatusSchema;
277545
+ exports.InferenceDeviceExclusionReasonSchema = InferenceDeviceExclusionReasonSchema;
277097
277546
  exports.IngestOwnerSchema = IngestOwnerSchema;
277098
277547
  exports.InstalledPackageSchema = InstalledPackageSchema;
277099
277548
  exports.IntegrationLiteSchema = IntegrationLiteSchema;
@@ -277306,6 +277755,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
277306
277755
  exports.NotificationSchema = NotificationSchema;
277307
277756
  exports.NotifierStatusSchema = NotifierStatusSchema;
277308
277757
  exports.NumericSensorStatusSchema = NumericSensorStatusSchema;
277758
+ exports.OPERATOR_WRITTEN_STALE_MS = OPERATOR_WRITTEN_STALE_MS;
277309
277759
  exports.OPS_LOG_DEFAULT_LIMIT = OPS_LOG_DEFAULT_LIMIT;
277310
277760
  exports.OPS_LOG_RING_DEFAULT_MAX = OPS_LOG_RING_DEFAULT_MAX;
277311
277761
  exports.OauthIntegrationDescriptorSchema = OauthIntegrationDescriptorSchema;
@@ -277388,6 +277838,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
277388
277838
  exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
277389
277839
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
277390
277840
  exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
277841
+ exports.RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS = RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS;
277391
277842
  exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
277392
277843
  exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
277393
277844
  exports.ReadGopBytesResultSchema = ReadGopBytesResultSchema;
@@ -399226,7 +399677,7 @@ var require_post_boot_service = __commonJS({
399226
399677
  return entry.payload;
399227
399678
  }
399228
399679
  async run(context) {
399229
- const { port, host, dataPath, trpcRegistered } = context;
399680
+ const { port, host, dataPath, trpcRegistered, tlsCert } = context;
399230
399681
  this.eventBus.emit({
399231
399682
  id: (0, node_crypto_1.randomUUID)(),
399232
399683
  timestamp: /* @__PURE__ */ new Date(),
@@ -399235,6 +399686,7 @@ var require_post_boot_service = __commonJS({
399235
399686
  data: { port, host, trpcRegistered, dataPath }
399236
399687
  });
399237
399688
  this.emitRestartCompletedIfPending(dataPath);
399689
+ this.emitTlsCertChangedIfReissued(tlsCert);
399238
399690
  await this.reconcileLifecycleJobsIfAny();
399239
399691
  }
399240
399692
  async reconcileLifecycleJobsIfAny() {
@@ -399243,6 +399695,27 @@ var require_post_boot_service = __commonJS({
399243
399695
  this.logger.info("Lifecycle jobs reconciled at boot", { meta: { resumed, failed } });
399244
399696
  }
399245
399697
  }
399698
+ emitTlsCertChangedIfReissued(tlsCert) {
399699
+ if (tlsCert === void 0 || !tlsCert.generated)
399700
+ return;
399701
+ const payload = {
399702
+ reason: tlsCert.reason ?? "unknown",
399703
+ fingerprintSha256: tlsCert.fingerprintSha256,
399704
+ caFingerprintSha256: tlsCert.caFingerprintSha256,
399705
+ caRotated: tlsCert.caRotated,
399706
+ caCertPath: tlsCert.caCertPath,
399707
+ validTo: tlsCert.validTo,
399708
+ sans: tlsCert.sans
399709
+ };
399710
+ this.logger.warn("TLS certificate reissued", { meta: payload });
399711
+ this.eventBus.emit({
399712
+ id: (0, node_crypto_1.randomUUID)(),
399713
+ timestamp: /* @__PURE__ */ new Date(),
399714
+ source: { type: "core", id: "system" },
399715
+ category: types_1.EventCategory.SystemTlsCertChanged,
399716
+ data: payload
399717
+ });
399718
+ }
399246
399719
  emitRestartCompletedIfPending(dataDir) {
399247
399720
  const marker = (0, system_1.readPendingRestart)(dataDir);
399248
399721
  if (marker === null)
@@ -401812,11 +402285,11 @@ var require_boot_config = __commonJS({
401812
402285
  exports.loadBootstrapConfig = loadBootstrapConfig;
401813
402286
  exports.autoGenerateJwtSecret = autoGenerateJwtSecret;
401814
402287
  exports.setupInfra = setupInfra;
402288
+ var node_crypto_1 = __require("crypto");
401815
402289
  var fs = __importStar(__require("fs"));
401816
402290
  var path = __importStar(__require("path"));
401817
- var yaml = __importStar(require_js_yaml());
401818
- var node_crypto_1 = __require("crypto");
401819
402291
  var types_1 = require_dist9();
402292
+ var yaml = __importStar(require_js_yaml());
401820
402293
  var config_schema_1 = require_config_schema();
401821
402294
  var storage_location_manager_1 = require_storage_location_manager();
401822
402295
  var CONFIG_DEFAULTS = {
@@ -401936,6 +402409,7 @@ var require_boot_config = __commonJS({
401936
402409
  console.log(`[Phase2] Location "${name}": ${available ? "OK" : "UNAVAILABLE"} \u2192 ${locPath}`);
401937
402410
  }
401938
402411
  let tlsOptions;
402412
+ let tlsCert;
401939
402413
  if (config.tls.enabled) {
401940
402414
  const core = require_dist3();
401941
402415
  const { ensureTlsCert, loadTlsCert } = core;
@@ -401945,11 +402419,8 @@ var require_boot_config = __commonJS({
401945
402419
  tlsOptions = { key: pair.key, cert: pair.cert };
401946
402420
  } else {
401947
402421
  const tlsResult = await ensureTlsCert(dataPath);
401948
- if (tlsResult.generated) {
401949
- console.log(`[Phase2c] Generated self-signed TLS cert at ${tlsResult.certPath}`);
401950
- } else {
401951
- console.log(`[Phase2c] Using existing TLS cert at ${tlsResult.certPath}`);
401952
- }
402422
+ tlsCert = tlsResult;
402423
+ logTlsCert(tlsResult);
401953
402424
  const pair = loadTlsCert(tlsResult.certPath, tlsResult.keyPath);
401954
402425
  tlsOptions = { key: pair.key, cert: pair.cert };
401955
402426
  }
@@ -401958,9 +402429,20 @@ var require_boot_config = __commonJS({
401958
402429
  bootstrapConfig: config,
401959
402430
  dataPath,
401960
402431
  locationManager,
401961
- tlsOptions
402432
+ tlsOptions,
402433
+ tlsCert
401962
402434
  };
401963
402435
  }
402436
+ function logTlsCert(result) {
402437
+ if (!result.generated) {
402438
+ console.log(`[Phase2c] TLS cert unchanged \u2014 ${result.certPath} (leaf ${result.fingerprintSha256}, valid to ${result.validTo})`);
402439
+ return;
402440
+ }
402441
+ console.log(`[Phase2c] TLS CERTIFICATE REISSUED \u2014 reason=${result.reason}, caRotated=${result.caRotated}`);
402442
+ console.log(`[Phase2c] leaf ${result.previousFingerprintSha256 ?? "(none)"} \u2192 ${result.fingerprintSha256}`);
402443
+ console.log(`[Phase2c] valid to ${result.validTo}, names: ${result.sans.join(", ")}`);
402444
+ console.log(result.caRotated ? `[Phase2c] ACTION REQUIRED: the local CA changed. Install ${result.caCertPath} (SHA-256 ${result.caFingerprintSha256}) on every device that trusted the previous certificate.` : `[Phase2c] Local CA unchanged (${result.caFingerprintSha256}) \u2014 devices that trust ${result.caCertPath} keep working.`);
402445
+ }
401964
402446
  }
401965
402447
  });
401966
402448
 
@@ -409443,7 +409925,7 @@ var require_main4 = __commonJS({
409443
409925
  const configPath = process.env.CONFIG_PATH ?? path.join(process.env.CAMSTACK_DATA ?? path.join(process.cwd(), "camstack-data"), "config.yaml");
409444
409926
  const bootstrapConfig = (0, boot_config_1.loadBootstrapConfig)(configPath);
409445
409927
  const infra = await (0, boot_config_1.setupInfra)(configPath, bootstrapConfig);
409446
- const { dataPath, tlsOptions } = infra;
409928
+ const { dataPath, tlsOptions, tlsCert } = infra;
409447
409929
  const port = infra.bootstrapConfig.server.port;
409448
409930
  const host = infra.bootstrapConfig.server.host;
409449
409931
  const fastifyOpts = tlsOptions ? { https: tlsOptions } : {};
@@ -410192,7 +410674,7 @@ var require_main4 = __commonJS({
410192
410674
  logger.info("CamStack server listening", { meta: { protocol, host, port, trpcRegistered } });
410193
410675
  void Promise.all([resolveAdminUi(), resolveViewerUi()]);
410194
410676
  const postBoot = app.get(post_boot_service_1.PostBootService);
410195
- await postBoot.run({ port, host, dataPath, trpcRegistered });
410677
+ await postBoot.run({ port, host, dataPath, trpcRegistered, tlsCert });
410196
410678
  try {
410197
410679
  const confirm = app.get(server_update_service_1.ServerUpdateService).confirmBootHealthy();
410198
410680
  if (confirm.promoted !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.36",
3
+ "version": "1.2.38",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",