@camstack/system 1.2.378 → 1.2.380

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 (119) hide show
  1. package/assets/addon-pages-aggregator/icon.svg +5 -0
  2. package/assets/addon-widgets-aggregator/icon.svg +6 -0
  3. package/assets/alert-center/icon.svg +5 -0
  4. package/assets/autotrack/icon.svg +5 -0
  5. package/assets/backup-orchestrator/icon.svg +5 -0
  6. package/assets/calendars/icon.svg +5 -0
  7. package/assets/camera-grid/icon.svg +10 -0
  8. package/assets/composer/icon.svg +7 -0
  9. package/assets/console-logging/icon.svg +4 -0
  10. package/assets/core-blocks/icon.svg +6 -0
  11. package/assets/device-manager/icon.svg +6 -0
  12. package/assets/filesystem-storage/icon.svg +6 -0
  13. package/assets/hub-forwarder/icon.svg +7 -0
  14. package/assets/keychain/icon.svg +6 -0
  15. package/assets/liveness-monitor/icon.svg +4 -0
  16. package/assets/local-auth/icon.svg +5 -0
  17. package/assets/local-network/icon.svg +7 -0
  18. package/assets/loki-logging/icon.svg +7 -0
  19. package/assets/metrics-native/icon.svg +3 -0
  20. package/assets/platform-probe/icon.svg +5 -0
  21. package/assets/remote-access-orchestrator/icon.svg +5 -0
  22. package/assets/snapshot/icon.svg +4 -0
  23. package/assets/sqlite-settings/icon.svg +5 -0
  24. package/assets/storage-orchestrator/icon.svg +8 -0
  25. package/assets/system-config/icon.svg +4 -0
  26. package/assets/winston-logging/icon.svg +6 -0
  27. package/dist/addon-runner.js +1 -1
  28. package/dist/addon-runner.mjs +1 -1
  29. package/dist/addon-utils.js +1 -1
  30. package/dist/addon-utils.mjs +1 -1
  31. package/dist/auth/auth-manager.d.ts +10 -2
  32. package/dist/auth/jwt-signing-key.d.ts +42 -0
  33. package/dist/auth/user-manager.d.ts +19 -19
  34. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  35. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  36. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  37. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  38. package/dist/builtins/alerts/alerts.addon.js +1 -1
  39. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  40. package/dist/builtins/autotrack/index.js +1 -1
  41. package/dist/builtins/autotrack/index.mjs +1 -1
  42. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  43. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  44. package/dist/builtins/calendars/calendars.addon.js +1 -1
  45. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  46. package/dist/builtins/camera-grid/index.js +1 -1
  47. package/dist/builtins/camera-grid/index.mjs +1 -1
  48. package/dist/builtins/composer/composer.addon.js +2 -2
  49. package/dist/builtins/composer/composer.addon.mjs +2 -2
  50. package/dist/builtins/console-logging/index.js +1 -1
  51. package/dist/builtins/console-logging/index.mjs +1 -1
  52. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  53. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  54. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  55. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  56. package/dist/builtins/hub-forwarder/index.js +1 -1
  57. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  58. package/dist/builtins/keychain/index.js +34 -4
  59. package/dist/builtins/keychain/index.mjs +34 -4
  60. package/dist/builtins/keychain/keychain-service.d.ts +14 -2
  61. package/dist/builtins/keychain/system-held-secrets.d.ts +11 -0
  62. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  63. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  64. package/dist/builtins/local-auth/admin-account-boot.d.ts +1 -1
  65. package/dist/builtins/local-auth/auth-config-reader.d.ts +9 -7
  66. package/dist/builtins/local-auth/jwt-signing-key-keeper.d.ts +30 -0
  67. package/dist/builtins/local-auth/local-auth.addon.d.ts +2 -6
  68. package/dist/builtins/local-auth/local-auth.addon.js +127 -221
  69. package/dist/builtins/local-auth/local-auth.addon.mjs +126 -220
  70. package/dist/builtins/local-network/local-network.addon.js +1 -1
  71. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  72. package/dist/builtins/loki-logging/index.js +1 -1
  73. package/dist/builtins/loki-logging/index.mjs +1 -1
  74. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  75. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  76. package/dist/builtins/platform-probe/index.js +1 -1
  77. package/dist/builtins/platform-probe/index.mjs +1 -1
  78. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  79. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  80. package/dist/builtins/snapshot/index.js +1 -1
  81. package/dist/builtins/snapshot/index.mjs +1 -1
  82. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  83. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  84. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  85. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  86. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  87. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  88. package/dist/builtins/system-config/cluster-secret-keeper.d.ts +36 -0
  89. package/dist/builtins/system-config/system-config.addon.d.ts +13 -0
  90. package/dist/builtins/system-config/system-config.addon.js +244 -53
  91. package/dist/builtins/system-config/system-config.addon.mjs +244 -54
  92. package/dist/builtins/winston-logging/index.js +1 -1
  93. package/dist/builtins/winston-logging/index.mjs +1 -1
  94. package/dist/{child-cap-dispatch-C9lmrWFT.js → child-cap-dispatch-CZeRUgYF.js} +1 -1
  95. package/dist/{child-cap-dispatch-DpHovtQd.mjs → child-cap-dispatch-CpjohToR.mjs} +1 -1
  96. package/dist/cluster-secret-BDue-uJj.mjs +83 -0
  97. package/dist/cluster-secret-Dg0OOOSr.js +148 -0
  98. package/dist/{composition-sources-CxhGdtdh.js → composition-sources-CHvLMrFI.js} +1 -1
  99. package/dist/{composition-sources-BLBd338N.mjs → composition-sources-DT_uJwI3.mjs} +1 -1
  100. package/dist/{dist-Bvl5JgNt.mjs → dist-B9oFw2jh.mjs} +58 -2
  101. package/dist/{dist-KxsOMQik.js → dist-Cc6KOdY-.js} +69 -1
  102. package/dist/{event-loop-stall-monitor-D8rqSWfW.mjs → event-loop-stall-monitor-anUH2IE1.mjs} +14 -14
  103. package/dist/{event-loop-stall-monitor-CbSmCWkp.js → event-loop-stall-monitor-mk6WGxir.js} +14 -14
  104. package/dist/index.d.ts +4 -2
  105. package/dist/index.js +192 -62
  106. package/dist/index.mjs +171 -55
  107. package/dist/jwt-signing-key-C2rE3wH6.js +174 -0
  108. package/dist/jwt-signing-key-Qhde8r82.mjs +127 -0
  109. package/dist/kernel/config-manager.d.ts +1 -1
  110. package/dist/kernel/config-schema.d.ts +4 -11
  111. package/dist/kernel/index.d.ts +2 -1
  112. package/dist/kernel/moleculer/broker-factory.d.ts +0 -2
  113. package/dist/kernel/moleculer/cluster-secret.d.ts +31 -0
  114. package/dist/kernel/moleculer/hub-service.d.ts +7 -4
  115. package/dist/kernel/moleculer/node-registry.d.ts +1 -1
  116. package/dist/kernel/system-settings-policy.d.ts +7 -9
  117. package/dist/{retired-settings-keys-BAcfvFtm.mjs → retired-settings-keys-DlZ3VV6x.mjs} +1 -1
  118. package/dist/{retired-settings-keys-gTsZfpmP.js → retired-settings-keys-YOOKLA3v.js} +1 -1
  119. package/package.json +55 -3
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-KxsOMQik.js");
7
- let zod = require("zod");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
+ const require_jwt_signing_key = require("../../jwt-signing-key-C2rE3wH6.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  node_crypto = require_chunk.__toESM(node_crypto);
10
10
  let crypto$1 = require("crypto");
@@ -6205,7 +6205,7 @@ var noopLogger = {
6205
6205
  };
6206
6206
  var AuthManager = class {
6207
6207
  config;
6208
- jwtSecret;
6208
+ signingKey;
6209
6209
  logger;
6210
6210
  /**
6211
6211
  * Offending `auth.tokenExpiry` values already reported by this instance.
@@ -6217,17 +6217,19 @@ var AuthManager = class {
6217
6217
  * rest resolve silently to the same default until the value changes.
6218
6218
  */
6219
6219
  reportedInvalidExpiries = /* @__PURE__ */ new Set();
6220
- constructor(config, logger = noopLogger) {
6220
+ /**
6221
+ * @param signingKey where the session signing key comes from — the process
6222
+ * slot `local-auth` fills from the keychain (D797). Never config, never
6223
+ * env; read on every mint and verify, and it THROWS while unavailable.
6224
+ */
6225
+ constructor(config, logger = noopLogger, signingKey = require_jwt_signing_key.processJwtSigningKey) {
6221
6226
  this.config = config;
6227
+ this.signingKey = signingKey;
6222
6228
  this.logger = logger;
6223
- const configured = this.config.get("auth.jwtSecret");
6224
- if (configured) this.jwtSecret = configured;
6225
- else {
6226
- const secret = node_crypto.randomBytes(32).toString("hex");
6227
- this.config.update("auth", { jwtSecret: secret });
6228
- this.logger.info("Generated JWT secret and saved to config.yaml (auth.jwtSecret)");
6229
- this.jwtSecret = secret;
6230
- }
6229
+ }
6230
+ /** The current signing key; throws `JwtSigningKeyUnavailableError` while there is none. */
6231
+ get jwtSecret() {
6232
+ return this.signingKey.current();
6231
6233
  }
6232
6234
  signToken(payload) {
6233
6235
  const configured = this.readConfiguredExpiry();
@@ -6620,17 +6622,23 @@ var ScopedTokenManager = class {
6620
6622
  //#endregion
6621
6623
  //#region src/auth/user-manager.ts
6622
6624
  var USERS_COLLECTION$1 = "users";
6625
+ /**
6626
+ * The account a fresh hub seeds on its first boot (D797). Not configurable —
6627
+ * no env variable, no config.yaml key: the first sign-in with this password
6628
+ * forces a change (admin-ui `mustChangePassword`), and an existing user is
6629
+ * never overwritten (D728).
6630
+ */
6631
+ var FIRST_BOOT_ADMIN_USERNAME = "admin";
6632
+ var FIRST_BOOT_ADMIN_PASSWORD = "changeme";
6623
6633
  function parseUser(data) {
6624
6634
  return parseRecord("user", require_dist.UserRecordSchema, data);
6625
6635
  }
6626
6636
  var UserManager = class {
6627
6637
  storageAccess;
6628
6638
  auth;
6629
- config;
6630
- constructor(storageAccess, auth, config) {
6639
+ constructor(storageAccess, auth) {
6631
6640
  this.storageAccess = storageAccess;
6632
6641
  this.auth = auth;
6633
- this.config = config;
6634
6642
  }
6635
6643
  get store() {
6636
6644
  return this.storageAccess.getStore();
@@ -6720,53 +6728,50 @@ var UserManager = class {
6720
6728
  });
6721
6729
  }
6722
6730
  /**
6723
- * Seed the configured admin (`auth.adminUsername` / `auth.adminPassword`,
6724
- * i.e. `CAMSTACK_ADMIN_USER` / `CAMSTACK_ADMIN_PASS`) — on FIRST boot only.
6731
+ * Seed {@link FIRST_BOOT_ADMIN_USERNAME} / {@link FIRST_BOOT_ADMIN_PASSWORD}
6732
+ * — on FIRST boot only, i.e. when no admin account exists.
6725
6733
  *
6726
- * The configured password is a seed, never an authority. Once any admin
6727
- * exists, this never writes a password: the operator's own change in the
6728
- * admin UI is the truth from then on. This method used to compare the stored
6729
- * hash with the configured password on every boot and rewrite it when they
6730
- * differed, so a password changed in the UI went back to the configured one
6731
- * (`changeme` on a Mac hub and on any container without `CAMSTACK_ADMIN_PASS`)
6732
- * at the next restart.
6734
+ * The seed is never an authority. Once any admin exists, this never writes a
6735
+ * password: the operator's own change in the admin UI is the truth from then
6736
+ * on (D728). This method once compared the stored hash with a configured
6737
+ * password on every boot and rewrote it when they differed, so a password
6738
+ * changed in the UI went back at the next restart.
6733
6739
  *
6734
6740
  * `resetPassword: true` is the one explicit exception — the operator's
6735
- * one-shot recovery request (see `admin-password-reset.ts`): the configured
6736
- * admin's password is set back to the configured one.
6741
+ * one-shot recovery request (`RESET_ADMIN_PASSWORD`, see
6742
+ * `admin-account-boot.ts`): the `admin` user's password goes back to
6743
+ * `changeme`, and the next sign-in forces a change again.
6737
6744
  */
6738
6745
  async ensureAdminExists(options = {}) {
6739
- const adminUsername = this.config.get("auth.adminUsername");
6740
- const adminPassword = this.config.get("auth.adminPassword");
6741
- if (!adminUsername || !adminPassword) return { kind: "not-configured" };
6742
- const existing = await this.findByUsername(adminUsername);
6746
+ const username = FIRST_BOOT_ADMIN_USERNAME;
6747
+ const existing = await this.findByUsername(username);
6743
6748
  if (existing) {
6744
6749
  if (options.resetPassword === true) {
6745
- await this.resetPassword(existing.id, adminPassword);
6750
+ await this.resetPassword(existing.id, FIRST_BOOT_ADMIN_PASSWORD);
6746
6751
  return {
6747
6752
  kind: "reset",
6748
- username: adminUsername
6753
+ username
6749
6754
  };
6750
6755
  }
6751
6756
  return {
6752
6757
  kind: "exists",
6753
- username: adminUsername
6758
+ username
6754
6759
  };
6755
6760
  }
6756
6761
  if ((await this.listAll()).some((u) => u.isAdmin)) return {
6757
6762
  kind: "other-admin-exists",
6758
- username: adminUsername
6763
+ username
6759
6764
  };
6760
6765
  await this.create({
6761
- username: adminUsername,
6762
- password: adminPassword,
6766
+ username,
6767
+ password: FIRST_BOOT_ADMIN_PASSWORD,
6763
6768
  isAdmin: true,
6764
6769
  allowedProviders: "*",
6765
6770
  allowedDevices: {}
6766
6771
  });
6767
6772
  return {
6768
6773
  kind: "seeded",
6769
- username: adminUsername
6774
+ username
6770
6775
  };
6771
6776
  }
6772
6777
  /**
@@ -6779,6 +6784,65 @@ var UserManager = class {
6779
6784
  return this.auth.comparePassword(password, user.passwordHash);
6780
6785
  }
6781
6786
  };
6787
+ var JwtSigningKeyKeeper = class {
6788
+ deps;
6789
+ retryTimer = null;
6790
+ unsubscribe = null;
6791
+ stopped = false;
6792
+ constructor(deps) {
6793
+ this.deps = deps;
6794
+ }
6795
+ async start() {
6796
+ const secrets = this.deps.secrets;
6797
+ if (secrets === null) {
6798
+ this.apply({
6799
+ state: "unavailable",
6800
+ reason: "no keychain door in this process"
6801
+ });
6802
+ return;
6803
+ }
6804
+ this.unsubscribe = secrets.onChange((ref) => {
6805
+ if (ref === require_jwt_signing_key.JWT_SIGNING_KEY_REF) this.load(secrets);
6806
+ });
6807
+ await this.load(secrets);
6808
+ }
6809
+ stop() {
6810
+ this.stopped = true;
6811
+ if (this.retryTimer !== null) clearTimeout(this.retryTimer);
6812
+ this.retryTimer = null;
6813
+ this.unsubscribe?.();
6814
+ this.unsubscribe = null;
6815
+ }
6816
+ async load(secrets) {
6817
+ if (this.stopped) return;
6818
+ const next = await require_jwt_signing_key.loadJwtSigningKey({ secrets });
6819
+ if (this.stopped) return;
6820
+ this.apply(next);
6821
+ if (next.state === "unavailable" && require_jwt_signing_key.getProcessJwtSigningKeyState().state !== "ready") this.scheduleRetry(secrets);
6822
+ }
6823
+ apply(next) {
6824
+ const previous = require_jwt_signing_key.getProcessJwtSigningKeyState();
6825
+ if (next.state === "ready") {
6826
+ if (previous.state !== "ready") this.deps.logger.info("Session signing key loaded from the keychain");
6827
+ require_jwt_signing_key.setProcessJwtSigningKey(next);
6828
+ return;
6829
+ }
6830
+ if (previous.state === "ready") return;
6831
+ require_jwt_signing_key.setProcessJwtSigningKey(next);
6832
+ this.deps.logger.error("Sessions are UNAVAILABLE: the session signing key cannot be read from the keychain. No one can sign in and no session token verifies until the keychain unlocks (System → Secrets; or fix keychain.keyFile in config.yaml and restart). No fallback key is used.", { meta: {
6833
+ reason: next.reason,
6834
+ ref: require_jwt_signing_key.JWT_SIGNING_KEY_REF
6835
+ } });
6836
+ }
6837
+ scheduleRetry(secrets) {
6838
+ if (this.stopped || this.retryTimer !== null) return;
6839
+ this.retryTimer = setTimeout(() => {
6840
+ this.retryTimer = null;
6841
+ this.load(secrets);
6842
+ }, this.deps.retryMs ?? 3e4);
6843
+ this.retryTimer.unref?.();
6844
+ }
6845
+ };
6782
6846
  //#endregion
6783
6847
  //#region src/builtins/local-auth/auth-schema.ts
6784
6848
  /**
@@ -9568,153 +9632,6 @@ var TotpManager = class {
9568
9632
  }
9569
9633
  };
9570
9634
  //#endregion
9571
- //#region src/kernel/config-schema.ts
9572
- /** Default data directory — used when CAMSTACK_DATA env var is not set */
9573
- var DEFAULT_DATA_PATH = "camstack-data";
9574
- /**
9575
- * A list of browser origins from config.yaml (a YAML list) or from the
9576
- * `CAMSTACK_CORS_ORIGINS` env var (one comma-separated string — env carries
9577
- * only strings). Validation of each entry is the CORS policy's job
9578
- * (`server/backend/src/api/cors/cors-policy.ts`), which logs what it rejects;
9579
- * the schema only fixes the SHAPE so a typo cannot stop the hub from booting.
9580
- */
9581
- var originListSchema = zod.z.preprocess((value) => typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) : value, zod.z.array(zod.z.string()));
9582
- var serverSchema = zod.z.object({
9583
- port: zod.z.number().default(4443),
9584
- host: zod.z.string().default("0.0.0.0"),
9585
- dataPath: zod.z.string().default(DEFAULT_DATA_PATH),
9586
- /**
9587
- * EXTRA browser origins allowed to call this hub cross-origin (D707), on
9588
- * top of the built-ins: the hub's own origin, the hosted viewer PWA and the
9589
- * native app's opaque `null` origin. Bootstrap-owned on purpose — widening
9590
- * who may read hub responses in a browser is a security decision, and the
9591
- * policy is live from the first request, before the settings store exists.
9592
- */
9593
- corsOrigins: originListSchema.default([])
9594
- });
9595
- var authSchema = zod.z.object({
9596
- jwtSecret: zod.z.string().nullable().default(null),
9597
- adminUsername: zod.z.string().default("admin"),
9598
- adminPassword: zod.z.string().default("changeme")
9599
- });
9600
- var hubSchema = zod.z.object({
9601
- url: zod.z.string().default("ws://localhost:4443/agent"),
9602
- token: zod.z.string().default("")
9603
- });
9604
- var agentSchema = zod.z.object({
9605
- name: zod.z.string().default(""),
9606
- /** Port for the agent status page (minimal HTML) */
9607
- statusPort: zod.z.number().default(4444)
9608
- });
9609
- var tlsSchema = zod.z.object({
9610
- /**
9611
- * Enable HTTPS (default: true). The certificate itself is never configured
9612
- * here: the admin UI (Network → Local access) generates or uploads it
9613
- * (D726), and boot serves whatever `tls/mode` says.
9614
- */
9615
- enabled: zod.z.boolean().default(true) });
9616
- /**
9617
- * Bootstrap-time addon configuration — read PURELY from YAML, before the
9618
- * kernel can talk to the settings-store. This breaks the chicken-and-egg
9619
- * where the settings store itself is implemented by an addon: the kernel
9620
- * needs to know WHICH addon to load first to provide the store, and that
9621
- * decision can't itself live in the store.
9622
- *
9623
- * Backward-compatible: every field is optional + defaults reproduce the
9624
- * current hard-coded behavior. Existing config.yaml files keep working.
9625
- */
9626
- var bootstrapAddonsSchema = zod.z.object({
9627
- /**
9628
- * Addon implementing the `settings-store` capability. Loaded FIRST,
9629
- * before any other addon is enabled — every other addon's enable
9630
- * state, settings, and ordering lives in the store it provides.
9631
- *
9632
- * Default: '@camstack/addon-sqlite-storage' (the workspace's
9633
- * default SQLite-backed store). Future alternatives could swap this
9634
- * for postgres/redis without touching kernel code.
9635
- */
9636
- settingsStoreAddon: zod.z.string().default("@camstack/addon-sqlite-storage"),
9637
- /**
9638
- * How the launcher provisions REQUIRED_PACKAGES into `data/addons/`
9639
- * on startup.
9640
- *
9641
- * - `'npm'` — pull from the npm registry (production default).
9642
- * - `'local'` — copy `packages/<X>/` from the workspace into
9643
- * `data/addons/<X>/`. Use for the Electron-
9644
- * packaged bundle and the e2e harness.
9645
- * - `'symlink'` — point `data/addons/<X>` at the workspace source
9646
- * dir via a directory symlink. Dev-only: a
9647
- * workspace rebuild (`npm run build -w <X>`) is
9648
- * immediately reflected at the next addon load,
9649
- * no copy step. Falls back to `'local'` on
9650
- * platforms that reject symlink creation (Windows
9651
- * without developer-mode).
9652
- *
9653
- * The `CAMSTACK_INSTALL_SOURCE` env var, when set, wins over this
9654
- * field. Leave undefined to keep the launcher's default behaviour
9655
- * (npm when no workspace, local when a bundled addons dir is
9656
- * present, …).
9657
- */
9658
- installSource: zod.z.enum([
9659
- "npm",
9660
- "local",
9661
- "symlink"
9662
- ]).optional(),
9663
- /**
9664
- * Override the kernel's hard-coded `AddonInstaller.REQUIRED_PACKAGES`.
9665
- * Passing an array here REPLACES the default list entirely — useful
9666
- * for stripped-down deployments (only the bundle of addons the
9667
- * operator actually wants) and for forcing a different storage
9668
- * backend at first boot.
9669
- *
9670
- * Leave undefined to use the kernel-shipped defaults.
9671
- */
9672
- requiredAddons: zod.z.array(zod.z.string()).optional(),
9673
- /**
9674
- * Disable list applied AFTER the settings-store reports the enabled
9675
- * set. Operator escape hatch — boot without a known-broken addon
9676
- * without having to mutate the store.
9677
- */
9678
- forceDisable: zod.z.array(zod.z.string()).default([])
9679
- });
9680
- /**
9681
- * Where the keychain's master key lives (D788). config.yaml ONLY: no env var
9682
- * maps here, and the settings store may not answer it
9683
- * (`BOOTSTRAP_OWNED_SECTIONS`). Absent → the desktop OS keychain when the hub
9684
- * runs inside the desktop app, else `<dataDir>/.keychain/master.key`.
9685
- */
9686
- var keychainSchema = zod.z.object({
9687
- /** A file holding 32 random bytes, base64 (or hex). Keep it OFF the data volume. */
9688
- keyFile: zod.z.string().min(1).optional(),
9689
- /**
9690
- * The key being rotated away from — consulted only to UNLOCK, so a move to a
9691
- * new key file is: new file, old one here, restart, rotate, remove this.
9692
- */
9693
- previousKeyFile: zod.z.string().min(1).optional()
9694
- });
9695
- /** Bootstrap config -- loaded from config.yaml ONLY at startup.
9696
- * All other settings live in SQL (system_settings table). */
9697
- var bootstrapSchema = zod.z.object({
9698
- /** Server mode: 'hub' (full server) or 'agent' (worker node) */
9699
- mode: zod.z.enum(["hub", "agent"]).default("hub"),
9700
- server: serverSchema.default(serverSchema.parse({})),
9701
- auth: authSchema.default(authSchema.parse({})),
9702
- /** Hub connection config — only used when mode='agent' */
9703
- hub: hubSchema.default(hubSchema.parse({})),
9704
- /** Agent-specific config — only used when mode='agent' */
9705
- agent: agentSchema.default(agentSchema.parse({})),
9706
- /** TLS configuration */
9707
- tls: tlsSchema.default(tlsSchema.parse({})),
9708
- /**
9709
- * Addon bootstrap directives — minimum addon set needed before the
9710
- * kernel can reach the settings-store. See bootstrapAddonsSchema.
9711
- * Spec: docs/superpowers/specs/2026-05-15-yaml-bootstrap-expansion.md
9712
- */
9713
- bootstrap: bootstrapAddonsSchema.default(bootstrapAddonsSchema.parse({})),
9714
- /** Keychain master-key location (config.yaml only). */
9715
- keychain: keychainSchema.default({})
9716
- });
9717
- //#endregion
9718
9635
  //#region src/builtins/local-auth/admin-account-boot.ts
9719
9636
  /**
9720
9637
  * The admin account at boot: seed it once, reset it only on the operator's
@@ -9722,16 +9639,16 @@ var bootstrapSchema = zod.z.object({
9722
9639
  *
9723
9640
  * ## Why a trigger FILE, not a variable
9724
9641
  *
9725
- * The configured password (`CAMSTACK_ADMIN_PASS` / `auth.adminPassword`) seeds
9726
- * the admin on first boot and is never applied again — a password the operator
9727
- * changes in the admin UI survives every restart. Recovering a forgotten
9642
+ * A fresh hub seeds `admin` / `changeme` on its first boot (D797) and never
9643
+ * applies it again — a password the operator changes in the admin UI survives
9644
+ * every restart. Recovering a forgotten
9728
9645
  * password therefore needs an explicit request, and that request must be
9729
9646
  * ONE-SHOT: an environment flag stays set in a container template until someone
9730
9647
  * remembers to remove it, and while it is set it reverts the password on every
9731
9648
  * boot — the very defect this replaced. A file the hub DELETES after acting on
9732
9649
  * it cannot be left armed: create `RESET_ADMIN_PASSWORD` in the node's data
9733
- * directory (`/data` in the container), restart, and the configured admin's
9734
- * password is set back to the configured one, once.
9650
+ * directory (`/data` in the container), restart, and the `admin` user's
9651
+ * password is set back to `changeme`, once — the next sign-in forces a change.
9735
9652
  */
9736
9653
  /** The trigger's name, at the root of the node's data directory. */
9737
9654
  var ADMIN_PASSWORD_RESET_TRIGGER = "RESET_ADMIN_PASSWORD";
@@ -9753,23 +9670,20 @@ async function reconcileAdminAccountAtBoot(input) {
9753
9670
  function logOutcome(outcome, logger) {
9754
9671
  switch (outcome.kind) {
9755
9672
  case "seeded":
9756
- logger.info("Admin account seeded from the configured credentials (first boot)", { meta: { username: outcome.username } });
9673
+ logger.info("Admin account seeded (first boot) — sign in as admin / changeme and set a password", { meta: { username: outcome.username } });
9757
9674
  return;
9758
9675
  case "reset":
9759
- logger.warn("Admin password reset to the configured password, as requested", { meta: { username: outcome.username } });
9676
+ logger.warn("Admin password reset to the first-boot password, as requested", { meta: { username: outcome.username } });
9760
9677
  return;
9761
9678
  case "other-admin-exists":
9762
- logger.info("Configured admin username not seeded: another admin account already exists", { meta: { username: outcome.username } });
9763
- return;
9764
- case "not-configured":
9765
- logger.warn("No admin username/password configured — no admin account seeded");
9679
+ logger.info("First-boot admin not seeded: another admin account already exists", { meta: { username: outcome.username } });
9766
9680
  return;
9767
9681
  case "exists": return;
9768
9682
  }
9769
9683
  }
9770
9684
  /** Delete the trigger after acting on it — a reset must never repeat by itself. */
9771
9685
  function consumeTrigger(triggerPath, outcome, input) {
9772
- if (outcome.kind !== "reset") input.logger.warn("Admin password reset requested, but no configured admin account to reset", { meta: {
9686
+ if (outcome.kind !== "reset") input.logger.warn("Admin password reset requested, but there is no `admin` user to reset", { meta: {
9773
9687
  trigger: triggerPath,
9774
9688
  outcome: outcome.kind
9775
9689
  } });
@@ -9788,28 +9702,22 @@ function consumeTrigger(triggerPath, outcome, input) {
9788
9702
  * Every config path this reader can answer. Anything else resolves to
9789
9703
  * `undefined` by construction — see the module docblock.
9790
9704
  */
9791
- function createAuthConfigReader(resolved, writeAuthSection) {
9705
+ function createAuthConfigReader(resolved) {
9792
9706
  return {
9793
9707
  get(path) {
9794
9708
  switch (path) {
9795
- case "auth.jwtSecret": return narrow(resolved.jwtSecret);
9796
- case "auth.adminUsername": return narrow(resolved.adminUsername);
9797
- case "auth.adminPassword": return narrow(resolved.adminPassword);
9798
9709
  case "auth.tokenExpiry": return narrow(resolved.tokenExpiry);
9799
9710
  default: return;
9800
9711
  }
9801
9712
  },
9802
- update(_section, data) {
9803
- if (typeof data["jwtSecret"] === "string") writeAuthSection({ jwtSecret: data["jwtSecret"] });
9804
- }
9713
+ update() {}
9805
9714
  };
9806
9715
  }
9807
9716
  /**
9808
9717
  * The one documented type boundary in this module. `AuthConfigReader.get` is
9809
9718
  * the legacy `get<T>(path)` shape shared with `ConfigManager.get`, which is
9810
9719
  * likewise unchecked at runtime — every value returned here is a `string |
9811
- * undefined` the caller immediately re-validates (`parseTokenExpiry`, the
9812
- * truthiness gate on `jwtSecret`).
9720
+ * undefined` the caller immediately re-validates (`parseTokenExpiry`).
9813
9721
  */
9814
9722
  function narrow(value) {
9815
9723
  return value;
@@ -10169,23 +10077,19 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
10169
10077
  reapTimer = null;
10170
10078
  totpManager = null;
10171
10079
  externalIdentities = null;
10080
+ signingKeyKeeper = null;
10172
10081
  constructor() {
10173
- super({
10174
- jwtSecret: "",
10175
- adminUsername: "",
10176
- adminPassword: ""
10177
- });
10082
+ super({});
10178
10083
  }
10179
10084
  async onInitialize() {
10180
- const authSection = await this.ctx.settings?.getSection("auth") ?? {};
10181
- const reader = createAuthConfigReader({
10182
- jwtSecret: typeof authSection["jwtSecret"] === "string" ? authSection["jwtSecret"] : this.config.jwtSecret ?? "",
10183
- adminUsername: typeof authSection["adminUsername"] === "string" && authSection["adminUsername"] ? authSection["adminUsername"] : this.config.adminUsername ?? "",
10184
- adminPassword: typeof authSection["adminPassword"] === "string" && authSection["adminPassword"] ? authSection["adminPassword"] : this.config.adminPassword ?? "",
10185
- tokenExpiry: typeof authSection["tokenExpiry"] === "string" && authSection["tokenExpiry"] ? authSection["tokenExpiry"] : void 0
10186
- }, (patch) => {
10187
- this.ctx.settings?.setSection("auth", patch);
10085
+ const forOwner = this.ctx.secrets?.forOwner ?? null;
10086
+ this.signingKeyKeeper = new JwtSigningKeyKeeper({
10087
+ secrets: forOwner === null ? null : forOwner("system"),
10088
+ logger: this.ctx.logger
10188
10089
  });
10090
+ await this.signingKeyKeeper.start();
10091
+ const authSection = await this.ctx.settings?.getSection("auth") ?? {};
10092
+ const reader = createAuthConfigReader({ tokenExpiry: typeof authSection["tokenExpiry"] === "string" && authSection["tokenExpiry"] ? authSection["tokenExpiry"] : void 0 });
10189
10093
  this.authManager = new AuthManager(reader, this.ctx.logger);
10190
10094
  const store = this.ctx.api?.settingsStore;
10191
10095
  if (store) {
@@ -10193,7 +10097,7 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
10193
10097
  const storageAccess = { getStore() {
10194
10098
  return store;
10195
10099
  } };
10196
- this.userManager = new UserManager(storageAccess, this.authManager, reader);
10100
+ this.userManager = new UserManager(storageAccess, this.authManager);
10197
10101
  this.apiKeyManager = new ApiKeyManager(storageAccess, this.authManager);
10198
10102
  this.scopedTokenManager = new ScopedTokenManager(store);
10199
10103
  this.oauthSessionManager = new OauthSessionManager(store);
@@ -10471,6 +10375,8 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
10471
10375
  }
10472
10376
  }
10473
10377
  async onShutdown() {
10378
+ this.signingKeyKeeper?.stop();
10379
+ this.signingKeyKeeper = null;
10474
10380
  if (this.reapTimer !== null) {
10475
10381
  clearInterval(this.reapTimer);
10476
10382
  this.reapTimer = null;
@@ -10486,13 +10392,13 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
10486
10392
  //#endregion
10487
10393
  exports.ApiKeyManager = ApiKeyManager;
10488
10394
  exports.AuthManager = AuthManager;
10489
- exports.DEFAULT_DATA_PATH = DEFAULT_DATA_PATH;
10395
+ exports.FIRST_BOOT_ADMIN_PASSWORD = FIRST_BOOT_ADMIN_PASSWORD;
10396
+ exports.FIRST_BOOT_ADMIN_USERNAME = FIRST_BOOT_ADMIN_USERNAME;
10490
10397
  exports.LocalAuthAddon = LocalAuthAddon;
10491
10398
  exports.default = LocalAuthAddon;
10492
10399
  exports.ScopedTokenManager = ScopedTokenManager;
10493
10400
  exports.SettingsReadUnavailableError = SettingsReadUnavailableError;
10494
10401
  exports.UserManager = UserManager;
10495
- exports.bootstrapSchema = bootstrapSchema;
10496
10402
  exports.isSettingsReadUnavailable = isSettingsReadUnavailable;
10497
10403
  Object.defineProperty(exports, "require_ms", {
10498
10404
  enumerable: true,