@camstack/system 1.2.377 → 1.2.379

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 (93) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/auth/auth-manager.d.ts +10 -2
  6. package/dist/auth/jwt-signing-key.d.ts +42 -0
  7. package/dist/auth/user-manager.d.ts +19 -19
  8. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  11. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  12. package/dist/builtins/alerts/alerts.addon.js +1 -1
  13. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  14. package/dist/builtins/autotrack/index.js +1 -1
  15. package/dist/builtins/autotrack/index.mjs +1 -1
  16. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  17. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  18. package/dist/builtins/calendars/calendars.addon.js +1 -1
  19. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  20. package/dist/builtins/camera-grid/index.js +1 -1
  21. package/dist/builtins/camera-grid/index.mjs +1 -1
  22. package/dist/builtins/composer/composer.addon.js +2 -2
  23. package/dist/builtins/composer/composer.addon.mjs +2 -2
  24. package/dist/builtins/console-logging/index.js +1 -1
  25. package/dist/builtins/console-logging/index.mjs +1 -1
  26. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  27. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  28. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  29. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  30. package/dist/builtins/hub-forwarder/index.js +1 -1
  31. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  32. package/dist/builtins/keychain/index.js +67 -11
  33. package/dist/builtins/keychain/index.mjs +67 -11
  34. package/dist/builtins/keychain/keychain-service.d.ts +14 -2
  35. package/dist/builtins/keychain/system-held-secrets.d.ts +11 -0
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  37. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  38. package/dist/builtins/local-auth/admin-account-boot.d.ts +1 -1
  39. package/dist/builtins/local-auth/auth-config-reader.d.ts +9 -7
  40. package/dist/builtins/local-auth/jwt-signing-key-keeper.d.ts +30 -0
  41. package/dist/builtins/local-auth/local-auth.addon.d.ts +2 -6
  42. package/dist/builtins/local-auth/local-auth.addon.js +127 -221
  43. package/dist/builtins/local-auth/local-auth.addon.mjs +126 -220
  44. package/dist/builtins/local-network/local-network.addon.js +1 -1
  45. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  46. package/dist/builtins/loki-logging/index.js +1 -1
  47. package/dist/builtins/loki-logging/index.mjs +1 -1
  48. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  49. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  50. package/dist/builtins/platform-probe/index.js +1 -1
  51. package/dist/builtins/platform-probe/index.mjs +1 -1
  52. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  53. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  54. package/dist/builtins/snapshot/index.js +1 -1
  55. package/dist/builtins/snapshot/index.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  57. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  58. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  59. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  60. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  61. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  62. package/dist/builtins/system-config/cluster-secret-keeper.d.ts +36 -0
  63. package/dist/builtins/system-config/system-config.addon.d.ts +13 -0
  64. package/dist/builtins/system-config/system-config.addon.js +244 -53
  65. package/dist/builtins/system-config/system-config.addon.mjs +244 -54
  66. package/dist/builtins/winston-logging/index.js +1 -1
  67. package/dist/builtins/winston-logging/index.mjs +1 -1
  68. package/dist/{child-cap-dispatch-DlDSoqm1.js → child-cap-dispatch-CZeRUgYF.js} +1 -1
  69. package/dist/{child-cap-dispatch-CgmOo0yl.mjs → child-cap-dispatch-CpjohToR.mjs} +1 -1
  70. package/dist/cluster-secret-BDue-uJj.mjs +83 -0
  71. package/dist/cluster-secret-Dg0OOOSr.js +148 -0
  72. package/dist/{composition-sources-BNyXPSs_.js → composition-sources-CHvLMrFI.js} +1 -1
  73. package/dist/{composition-sources-D39bl0nP.mjs → composition-sources-DT_uJwI3.mjs} +1 -1
  74. package/dist/{dist-BWNoS-1S.mjs → dist-B9oFw2jh.mjs} +200 -3
  75. package/dist/{dist-D_a9mUiM.js → dist-Cc6KOdY-.js} +211 -2
  76. package/dist/{event-loop-stall-monitor-ClPhfx7Y.mjs → event-loop-stall-monitor-anUH2IE1.mjs} +21 -20
  77. package/dist/{event-loop-stall-monitor-DuOn8H1n.js → event-loop-stall-monitor-mk6WGxir.js} +21 -20
  78. package/dist/index.d.ts +4 -2
  79. package/dist/index.js +192 -62
  80. package/dist/index.mjs +171 -55
  81. package/dist/jwt-signing-key-C2rE3wH6.js +174 -0
  82. package/dist/jwt-signing-key-Qhde8r82.mjs +127 -0
  83. package/dist/kernel/config-manager.d.ts +1 -1
  84. package/dist/kernel/config-schema.d.ts +4 -11
  85. package/dist/kernel/index.d.ts +2 -1
  86. package/dist/kernel/moleculer/broker-factory.d.ts +0 -2
  87. package/dist/kernel/moleculer/cluster-secret.d.ts +31 -0
  88. package/dist/kernel/moleculer/hub-service.d.ts +7 -4
  89. package/dist/kernel/moleculer/node-registry.d.ts +1 -1
  90. package/dist/kernel/system-settings-policy.d.ts +7 -9
  91. package/dist/{retired-settings-keys-iGcmzwLV.mjs → retired-settings-keys-DlZ3VV6x.mjs} +1 -1
  92. package/dist/{retired-settings-keys-CDyOGAje.js → retired-settings-keys-YOOKLA3v.js} +1 -1
  93. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { Bn as userManagementCapability, J as authProviderCapability, R as ScopedTokenSchema, U as UserRecordSchema, lr as BaseAddon, r as ApiKeyRecordSchema } from "../../dist-BWNoS-1S.mjs";
3
- import { z } from "zod";
2
+ import { Bn as userManagementCapability, J as authProviderCapability, R as ScopedTokenSchema, U as UserRecordSchema, lr as BaseAddon, r as ApiKeyRecordSchema } from "../../dist-B9oFw2jh.mjs";
3
+ import { a as loadJwtSigningKey, i as getProcessJwtSigningKeyState, o as processJwtSigningKey, s as setProcessJwtSigningKey, t as JWT_SIGNING_KEY_REF } from "../../jwt-signing-key-Qhde8r82.mjs";
4
4
  import * as crypto$2 from "node:crypto";
5
5
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
6
6
  import nodeCrypto from "crypto";
@@ -6195,7 +6195,7 @@ var noopLogger = {
6195
6195
  };
6196
6196
  var AuthManager = class {
6197
6197
  config;
6198
- jwtSecret;
6198
+ signingKey;
6199
6199
  logger;
6200
6200
  /**
6201
6201
  * Offending `auth.tokenExpiry` values already reported by this instance.
@@ -6207,17 +6207,19 @@ var AuthManager = class {
6207
6207
  * rest resolve silently to the same default until the value changes.
6208
6208
  */
6209
6209
  reportedInvalidExpiries = /* @__PURE__ */ new Set();
6210
- constructor(config, logger = noopLogger) {
6210
+ /**
6211
+ * @param signingKey where the session signing key comes from — the process
6212
+ * slot `local-auth` fills from the keychain (D797). Never config, never
6213
+ * env; read on every mint and verify, and it THROWS while unavailable.
6214
+ */
6215
+ constructor(config, logger = noopLogger, signingKey = processJwtSigningKey) {
6211
6216
  this.config = config;
6217
+ this.signingKey = signingKey;
6212
6218
  this.logger = logger;
6213
- const configured = this.config.get("auth.jwtSecret");
6214
- if (configured) this.jwtSecret = configured;
6215
- else {
6216
- const secret = crypto$2.randomBytes(32).toString("hex");
6217
- this.config.update("auth", { jwtSecret: secret });
6218
- this.logger.info("Generated JWT secret and saved to config.yaml (auth.jwtSecret)");
6219
- this.jwtSecret = secret;
6220
- }
6219
+ }
6220
+ /** The current signing key; throws `JwtSigningKeyUnavailableError` while there is none. */
6221
+ get jwtSecret() {
6222
+ return this.signingKey.current();
6221
6223
  }
6222
6224
  signToken(payload) {
6223
6225
  const configured = this.readConfiguredExpiry();
@@ -6610,17 +6612,23 @@ var ScopedTokenManager = class {
6610
6612
  //#endregion
6611
6613
  //#region src/auth/user-manager.ts
6612
6614
  var USERS_COLLECTION$1 = "users";
6615
+ /**
6616
+ * The account a fresh hub seeds on its first boot (D797). Not configurable —
6617
+ * no env variable, no config.yaml key: the first sign-in with this password
6618
+ * forces a change (admin-ui `mustChangePassword`), and an existing user is
6619
+ * never overwritten (D728).
6620
+ */
6621
+ var FIRST_BOOT_ADMIN_USERNAME = "admin";
6622
+ var FIRST_BOOT_ADMIN_PASSWORD = "changeme";
6613
6623
  function parseUser(data) {
6614
6624
  return parseRecord("user", UserRecordSchema, data);
6615
6625
  }
6616
6626
  var UserManager = class {
6617
6627
  storageAccess;
6618
6628
  auth;
6619
- config;
6620
- constructor(storageAccess, auth, config) {
6629
+ constructor(storageAccess, auth) {
6621
6630
  this.storageAccess = storageAccess;
6622
6631
  this.auth = auth;
6623
- this.config = config;
6624
6632
  }
6625
6633
  get store() {
6626
6634
  return this.storageAccess.getStore();
@@ -6710,53 +6718,50 @@ var UserManager = class {
6710
6718
  });
6711
6719
  }
6712
6720
  /**
6713
- * Seed the configured admin (`auth.adminUsername` / `auth.adminPassword`,
6714
- * i.e. `CAMSTACK_ADMIN_USER` / `CAMSTACK_ADMIN_PASS`) — on FIRST boot only.
6721
+ * Seed {@link FIRST_BOOT_ADMIN_USERNAME} / {@link FIRST_BOOT_ADMIN_PASSWORD}
6722
+ * — on FIRST boot only, i.e. when no admin account exists.
6715
6723
  *
6716
- * The configured password is a seed, never an authority. Once any admin
6717
- * exists, this never writes a password: the operator's own change in the
6718
- * admin UI is the truth from then on. This method used to compare the stored
6719
- * hash with the configured password on every boot and rewrite it when they
6720
- * differed, so a password changed in the UI went back to the configured one
6721
- * (`changeme` on a Mac hub and on any container without `CAMSTACK_ADMIN_PASS`)
6722
- * at the next restart.
6724
+ * The seed is never an authority. Once any admin exists, this never writes a
6725
+ * password: the operator's own change in the admin UI is the truth from then
6726
+ * on (D728). This method once compared the stored hash with a configured
6727
+ * password on every boot and rewrote it when they differed, so a password
6728
+ * changed in the UI went back at the next restart.
6723
6729
  *
6724
6730
  * `resetPassword: true` is the one explicit exception — the operator's
6725
- * one-shot recovery request (see `admin-password-reset.ts`): the configured
6726
- * admin's password is set back to the configured one.
6731
+ * one-shot recovery request (`RESET_ADMIN_PASSWORD`, see
6732
+ * `admin-account-boot.ts`): the `admin` user's password goes back to
6733
+ * `changeme`, and the next sign-in forces a change again.
6727
6734
  */
6728
6735
  async ensureAdminExists(options = {}) {
6729
- const adminUsername = this.config.get("auth.adminUsername");
6730
- const adminPassword = this.config.get("auth.adminPassword");
6731
- if (!adminUsername || !adminPassword) return { kind: "not-configured" };
6732
- const existing = await this.findByUsername(adminUsername);
6736
+ const username = FIRST_BOOT_ADMIN_USERNAME;
6737
+ const existing = await this.findByUsername(username);
6733
6738
  if (existing) {
6734
6739
  if (options.resetPassword === true) {
6735
- await this.resetPassword(existing.id, adminPassword);
6740
+ await this.resetPassword(existing.id, FIRST_BOOT_ADMIN_PASSWORD);
6736
6741
  return {
6737
6742
  kind: "reset",
6738
- username: adminUsername
6743
+ username
6739
6744
  };
6740
6745
  }
6741
6746
  return {
6742
6747
  kind: "exists",
6743
- username: adminUsername
6748
+ username
6744
6749
  };
6745
6750
  }
6746
6751
  if ((await this.listAll()).some((u) => u.isAdmin)) return {
6747
6752
  kind: "other-admin-exists",
6748
- username: adminUsername
6753
+ username
6749
6754
  };
6750
6755
  await this.create({
6751
- username: adminUsername,
6752
- password: adminPassword,
6756
+ username,
6757
+ password: FIRST_BOOT_ADMIN_PASSWORD,
6753
6758
  isAdmin: true,
6754
6759
  allowedProviders: "*",
6755
6760
  allowedDevices: {}
6756
6761
  });
6757
6762
  return {
6758
6763
  kind: "seeded",
6759
- username: adminUsername
6764
+ username
6760
6765
  };
6761
6766
  }
6762
6767
  /**
@@ -6769,6 +6774,65 @@ var UserManager = class {
6769
6774
  return this.auth.comparePassword(password, user.passwordHash);
6770
6775
  }
6771
6776
  };
6777
+ var JwtSigningKeyKeeper = class {
6778
+ deps;
6779
+ retryTimer = null;
6780
+ unsubscribe = null;
6781
+ stopped = false;
6782
+ constructor(deps) {
6783
+ this.deps = deps;
6784
+ }
6785
+ async start() {
6786
+ const secrets = this.deps.secrets;
6787
+ if (secrets === null) {
6788
+ this.apply({
6789
+ state: "unavailable",
6790
+ reason: "no keychain door in this process"
6791
+ });
6792
+ return;
6793
+ }
6794
+ this.unsubscribe = secrets.onChange((ref) => {
6795
+ if (ref === JWT_SIGNING_KEY_REF) this.load(secrets);
6796
+ });
6797
+ await this.load(secrets);
6798
+ }
6799
+ stop() {
6800
+ this.stopped = true;
6801
+ if (this.retryTimer !== null) clearTimeout(this.retryTimer);
6802
+ this.retryTimer = null;
6803
+ this.unsubscribe?.();
6804
+ this.unsubscribe = null;
6805
+ }
6806
+ async load(secrets) {
6807
+ if (this.stopped) return;
6808
+ const next = await loadJwtSigningKey({ secrets });
6809
+ if (this.stopped) return;
6810
+ this.apply(next);
6811
+ if (next.state === "unavailable" && getProcessJwtSigningKeyState().state !== "ready") this.scheduleRetry(secrets);
6812
+ }
6813
+ apply(next) {
6814
+ const previous = getProcessJwtSigningKeyState();
6815
+ if (next.state === "ready") {
6816
+ if (previous.state !== "ready") this.deps.logger.info("Session signing key loaded from the keychain");
6817
+ setProcessJwtSigningKey(next);
6818
+ return;
6819
+ }
6820
+ if (previous.state === "ready") return;
6821
+ setProcessJwtSigningKey(next);
6822
+ 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: {
6823
+ reason: next.reason,
6824
+ ref: JWT_SIGNING_KEY_REF
6825
+ } });
6826
+ }
6827
+ scheduleRetry(secrets) {
6828
+ if (this.stopped || this.retryTimer !== null) return;
6829
+ this.retryTimer = setTimeout(() => {
6830
+ this.retryTimer = null;
6831
+ this.load(secrets);
6832
+ }, this.deps.retryMs ?? 3e4);
6833
+ this.retryTimer.unref?.();
6834
+ }
6835
+ };
6772
6836
  //#endregion
6773
6837
  //#region src/builtins/local-auth/auth-schema.ts
6774
6838
  /**
@@ -9558,153 +9622,6 @@ var TotpManager = class {
9558
9622
  }
9559
9623
  };
9560
9624
  //#endregion
9561
- //#region src/kernel/config-schema.ts
9562
- /** Default data directory — used when CAMSTACK_DATA env var is not set */
9563
- var DEFAULT_DATA_PATH = "camstack-data";
9564
- /**
9565
- * A list of browser origins from config.yaml (a YAML list) or from the
9566
- * `CAMSTACK_CORS_ORIGINS` env var (one comma-separated string — env carries
9567
- * only strings). Validation of each entry is the CORS policy's job
9568
- * (`server/backend/src/api/cors/cors-policy.ts`), which logs what it rejects;
9569
- * the schema only fixes the SHAPE so a typo cannot stop the hub from booting.
9570
- */
9571
- var originListSchema = z.preprocess((value) => typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) : value, z.array(z.string()));
9572
- var serverSchema = z.object({
9573
- port: z.number().default(4443),
9574
- host: z.string().default("0.0.0.0"),
9575
- dataPath: z.string().default(DEFAULT_DATA_PATH),
9576
- /**
9577
- * EXTRA browser origins allowed to call this hub cross-origin (D707), on
9578
- * top of the built-ins: the hub's own origin, the hosted viewer PWA and the
9579
- * native app's opaque `null` origin. Bootstrap-owned on purpose — widening
9580
- * who may read hub responses in a browser is a security decision, and the
9581
- * policy is live from the first request, before the settings store exists.
9582
- */
9583
- corsOrigins: originListSchema.default([])
9584
- });
9585
- var authSchema = z.object({
9586
- jwtSecret: z.string().nullable().default(null),
9587
- adminUsername: z.string().default("admin"),
9588
- adminPassword: z.string().default("changeme")
9589
- });
9590
- var hubSchema = z.object({
9591
- url: z.string().default("ws://localhost:4443/agent"),
9592
- token: z.string().default("")
9593
- });
9594
- var agentSchema = z.object({
9595
- name: z.string().default(""),
9596
- /** Port for the agent status page (minimal HTML) */
9597
- statusPort: z.number().default(4444)
9598
- });
9599
- var tlsSchema = z.object({
9600
- /**
9601
- * Enable HTTPS (default: true). The certificate itself is never configured
9602
- * here: the admin UI (Network → Local access) generates or uploads it
9603
- * (D726), and boot serves whatever `tls/mode` says.
9604
- */
9605
- enabled: z.boolean().default(true) });
9606
- /**
9607
- * Bootstrap-time addon configuration — read PURELY from YAML, before the
9608
- * kernel can talk to the settings-store. This breaks the chicken-and-egg
9609
- * where the settings store itself is implemented by an addon: the kernel
9610
- * needs to know WHICH addon to load first to provide the store, and that
9611
- * decision can't itself live in the store.
9612
- *
9613
- * Backward-compatible: every field is optional + defaults reproduce the
9614
- * current hard-coded behavior. Existing config.yaml files keep working.
9615
- */
9616
- var bootstrapAddonsSchema = z.object({
9617
- /**
9618
- * Addon implementing the `settings-store` capability. Loaded FIRST,
9619
- * before any other addon is enabled — every other addon's enable
9620
- * state, settings, and ordering lives in the store it provides.
9621
- *
9622
- * Default: '@camstack/addon-sqlite-storage' (the workspace's
9623
- * default SQLite-backed store). Future alternatives could swap this
9624
- * for postgres/redis without touching kernel code.
9625
- */
9626
- settingsStoreAddon: z.string().default("@camstack/addon-sqlite-storage"),
9627
- /**
9628
- * How the launcher provisions REQUIRED_PACKAGES into `data/addons/`
9629
- * on startup.
9630
- *
9631
- * - `'npm'` — pull from the npm registry (production default).
9632
- * - `'local'` — copy `packages/<X>/` from the workspace into
9633
- * `data/addons/<X>/`. Use for the Electron-
9634
- * packaged bundle and the e2e harness.
9635
- * - `'symlink'` — point `data/addons/<X>` at the workspace source
9636
- * dir via a directory symlink. Dev-only: a
9637
- * workspace rebuild (`npm run build -w <X>`) is
9638
- * immediately reflected at the next addon load,
9639
- * no copy step. Falls back to `'local'` on
9640
- * platforms that reject symlink creation (Windows
9641
- * without developer-mode).
9642
- *
9643
- * The `CAMSTACK_INSTALL_SOURCE` env var, when set, wins over this
9644
- * field. Leave undefined to keep the launcher's default behaviour
9645
- * (npm when no workspace, local when a bundled addons dir is
9646
- * present, …).
9647
- */
9648
- installSource: z.enum([
9649
- "npm",
9650
- "local",
9651
- "symlink"
9652
- ]).optional(),
9653
- /**
9654
- * Override the kernel's hard-coded `AddonInstaller.REQUIRED_PACKAGES`.
9655
- * Passing an array here REPLACES the default list entirely — useful
9656
- * for stripped-down deployments (only the bundle of addons the
9657
- * operator actually wants) and for forcing a different storage
9658
- * backend at first boot.
9659
- *
9660
- * Leave undefined to use the kernel-shipped defaults.
9661
- */
9662
- requiredAddons: z.array(z.string()).optional(),
9663
- /**
9664
- * Disable list applied AFTER the settings-store reports the enabled
9665
- * set. Operator escape hatch — boot without a known-broken addon
9666
- * without having to mutate the store.
9667
- */
9668
- forceDisable: z.array(z.string()).default([])
9669
- });
9670
- /**
9671
- * Where the keychain's master key lives (D788). config.yaml ONLY: no env var
9672
- * maps here, and the settings store may not answer it
9673
- * (`BOOTSTRAP_OWNED_SECTIONS`). Absent → the desktop OS keychain when the hub
9674
- * runs inside the desktop app, else `<dataDir>/.keychain/master.key`.
9675
- */
9676
- var keychainSchema = z.object({
9677
- /** A file holding 32 random bytes, base64 (or hex). Keep it OFF the data volume. */
9678
- keyFile: z.string().min(1).optional(),
9679
- /**
9680
- * The key being rotated away from — consulted only to UNLOCK, so a move to a
9681
- * new key file is: new file, old one here, restart, rotate, remove this.
9682
- */
9683
- previousKeyFile: z.string().min(1).optional()
9684
- });
9685
- /** Bootstrap config -- loaded from config.yaml ONLY at startup.
9686
- * All other settings live in SQL (system_settings table). */
9687
- var bootstrapSchema = z.object({
9688
- /** Server mode: 'hub' (full server) or 'agent' (worker node) */
9689
- mode: z.enum(["hub", "agent"]).default("hub"),
9690
- server: serverSchema.default(serverSchema.parse({})),
9691
- auth: authSchema.default(authSchema.parse({})),
9692
- /** Hub connection config — only used when mode='agent' */
9693
- hub: hubSchema.default(hubSchema.parse({})),
9694
- /** Agent-specific config — only used when mode='agent' */
9695
- agent: agentSchema.default(agentSchema.parse({})),
9696
- /** TLS configuration */
9697
- tls: tlsSchema.default(tlsSchema.parse({})),
9698
- /**
9699
- * Addon bootstrap directives — minimum addon set needed before the
9700
- * kernel can reach the settings-store. See bootstrapAddonsSchema.
9701
- * Spec: docs/superpowers/specs/2026-05-15-yaml-bootstrap-expansion.md
9702
- */
9703
- bootstrap: bootstrapAddonsSchema.default(bootstrapAddonsSchema.parse({})),
9704
- /** Keychain master-key location (config.yaml only). */
9705
- keychain: keychainSchema.default({})
9706
- });
9707
- //#endregion
9708
9625
  //#region src/builtins/local-auth/admin-account-boot.ts
9709
9626
  /**
9710
9627
  * The admin account at boot: seed it once, reset it only on the operator's
@@ -9712,16 +9629,16 @@ var bootstrapSchema = z.object({
9712
9629
  *
9713
9630
  * ## Why a trigger FILE, not a variable
9714
9631
  *
9715
- * The configured password (`CAMSTACK_ADMIN_PASS` / `auth.adminPassword`) seeds
9716
- * the admin on first boot and is never applied again — a password the operator
9717
- * changes in the admin UI survives every restart. Recovering a forgotten
9632
+ * A fresh hub seeds `admin` / `changeme` on its first boot (D797) and never
9633
+ * applies it again — a password the operator changes in the admin UI survives
9634
+ * every restart. Recovering a forgotten
9718
9635
  * password therefore needs an explicit request, and that request must be
9719
9636
  * ONE-SHOT: an environment flag stays set in a container template until someone
9720
9637
  * remembers to remove it, and while it is set it reverts the password on every
9721
9638
  * boot — the very defect this replaced. A file the hub DELETES after acting on
9722
9639
  * it cannot be left armed: create `RESET_ADMIN_PASSWORD` in the node's data
9723
- * directory (`/data` in the container), restart, and the configured admin's
9724
- * password is set back to the configured one, once.
9640
+ * directory (`/data` in the container), restart, and the `admin` user's
9641
+ * password is set back to `changeme`, once — the next sign-in forces a change.
9725
9642
  */
9726
9643
  /** The trigger's name, at the root of the node's data directory. */
9727
9644
  var ADMIN_PASSWORD_RESET_TRIGGER = "RESET_ADMIN_PASSWORD";
@@ -9743,23 +9660,20 @@ async function reconcileAdminAccountAtBoot(input) {
9743
9660
  function logOutcome(outcome, logger) {
9744
9661
  switch (outcome.kind) {
9745
9662
  case "seeded":
9746
- logger.info("Admin account seeded from the configured credentials (first boot)", { meta: { username: outcome.username } });
9663
+ logger.info("Admin account seeded (first boot) — sign in as admin / changeme and set a password", { meta: { username: outcome.username } });
9747
9664
  return;
9748
9665
  case "reset":
9749
- logger.warn("Admin password reset to the configured password, as requested", { meta: { username: outcome.username } });
9666
+ logger.warn("Admin password reset to the first-boot password, as requested", { meta: { username: outcome.username } });
9750
9667
  return;
9751
9668
  case "other-admin-exists":
9752
- logger.info("Configured admin username not seeded: another admin account already exists", { meta: { username: outcome.username } });
9753
- return;
9754
- case "not-configured":
9755
- logger.warn("No admin username/password configured — no admin account seeded");
9669
+ logger.info("First-boot admin not seeded: another admin account already exists", { meta: { username: outcome.username } });
9756
9670
  return;
9757
9671
  case "exists": return;
9758
9672
  }
9759
9673
  }
9760
9674
  /** Delete the trigger after acting on it — a reset must never repeat by itself. */
9761
9675
  function consumeTrigger(triggerPath, outcome, input) {
9762
- if (outcome.kind !== "reset") input.logger.warn("Admin password reset requested, but no configured admin account to reset", { meta: {
9676
+ if (outcome.kind !== "reset") input.logger.warn("Admin password reset requested, but there is no `admin` user to reset", { meta: {
9763
9677
  trigger: triggerPath,
9764
9678
  outcome: outcome.kind
9765
9679
  } });
@@ -9778,28 +9692,22 @@ function consumeTrigger(triggerPath, outcome, input) {
9778
9692
  * Every config path this reader can answer. Anything else resolves to
9779
9693
  * `undefined` by construction — see the module docblock.
9780
9694
  */
9781
- function createAuthConfigReader(resolved, writeAuthSection) {
9695
+ function createAuthConfigReader(resolved) {
9782
9696
  return {
9783
9697
  get(path) {
9784
9698
  switch (path) {
9785
- case "auth.jwtSecret": return narrow(resolved.jwtSecret);
9786
- case "auth.adminUsername": return narrow(resolved.adminUsername);
9787
- case "auth.adminPassword": return narrow(resolved.adminPassword);
9788
9699
  case "auth.tokenExpiry": return narrow(resolved.tokenExpiry);
9789
9700
  default: return;
9790
9701
  }
9791
9702
  },
9792
- update(_section, data) {
9793
- if (typeof data["jwtSecret"] === "string") writeAuthSection({ jwtSecret: data["jwtSecret"] });
9794
- }
9703
+ update() {}
9795
9704
  };
9796
9705
  }
9797
9706
  /**
9798
9707
  * The one documented type boundary in this module. `AuthConfigReader.get` is
9799
9708
  * the legacy `get<T>(path)` shape shared with `ConfigManager.get`, which is
9800
9709
  * likewise unchecked at runtime — every value returned here is a `string |
9801
- * undefined` the caller immediately re-validates (`parseTokenExpiry`, the
9802
- * truthiness gate on `jwtSecret`).
9710
+ * undefined` the caller immediately re-validates (`parseTokenExpiry`).
9803
9711
  */
9804
9712
  function narrow(value) {
9805
9713
  return value;
@@ -10159,23 +10067,19 @@ var LocalAuthAddon = class extends BaseAddon {
10159
10067
  reapTimer = null;
10160
10068
  totpManager = null;
10161
10069
  externalIdentities = null;
10070
+ signingKeyKeeper = null;
10162
10071
  constructor() {
10163
- super({
10164
- jwtSecret: "",
10165
- adminUsername: "",
10166
- adminPassword: ""
10167
- });
10072
+ super({});
10168
10073
  }
10169
10074
  async onInitialize() {
10170
- const authSection = await this.ctx.settings?.getSection("auth") ?? {};
10171
- const reader = createAuthConfigReader({
10172
- jwtSecret: typeof authSection["jwtSecret"] === "string" ? authSection["jwtSecret"] : this.config.jwtSecret ?? "",
10173
- adminUsername: typeof authSection["adminUsername"] === "string" && authSection["adminUsername"] ? authSection["adminUsername"] : this.config.adminUsername ?? "",
10174
- adminPassword: typeof authSection["adminPassword"] === "string" && authSection["adminPassword"] ? authSection["adminPassword"] : this.config.adminPassword ?? "",
10175
- tokenExpiry: typeof authSection["tokenExpiry"] === "string" && authSection["tokenExpiry"] ? authSection["tokenExpiry"] : void 0
10176
- }, (patch) => {
10177
- this.ctx.settings?.setSection("auth", patch);
10075
+ const forOwner = this.ctx.secrets?.forOwner ?? null;
10076
+ this.signingKeyKeeper = new JwtSigningKeyKeeper({
10077
+ secrets: forOwner === null ? null : forOwner("system"),
10078
+ logger: this.ctx.logger
10178
10079
  });
10080
+ await this.signingKeyKeeper.start();
10081
+ const authSection = await this.ctx.settings?.getSection("auth") ?? {};
10082
+ const reader = createAuthConfigReader({ tokenExpiry: typeof authSection["tokenExpiry"] === "string" && authSection["tokenExpiry"] ? authSection["tokenExpiry"] : void 0 });
10179
10083
  this.authManager = new AuthManager(reader, this.ctx.logger);
10180
10084
  const store = this.ctx.api?.settingsStore;
10181
10085
  if (store) {
@@ -10183,7 +10087,7 @@ var LocalAuthAddon = class extends BaseAddon {
10183
10087
  const storageAccess = { getStore() {
10184
10088
  return store;
10185
10089
  } };
10186
- this.userManager = new UserManager(storageAccess, this.authManager, reader);
10090
+ this.userManager = new UserManager(storageAccess, this.authManager);
10187
10091
  this.apiKeyManager = new ApiKeyManager(storageAccess, this.authManager);
10188
10092
  this.scopedTokenManager = new ScopedTokenManager(store);
10189
10093
  this.oauthSessionManager = new OauthSessionManager(store);
@@ -10461,6 +10365,8 @@ var LocalAuthAddon = class extends BaseAddon {
10461
10365
  }
10462
10366
  }
10463
10367
  async onShutdown() {
10368
+ this.signingKeyKeeper?.stop();
10369
+ this.signingKeyKeeper = null;
10464
10370
  if (this.reapTimer !== null) {
10465
10371
  clearInterval(this.reapTimer);
10466
10372
  this.reapTimer = null;
@@ -10474,4 +10380,4 @@ var LocalAuthAddon = class extends BaseAddon {
10474
10380
  }
10475
10381
  };
10476
10382
  //#endregion
10477
- export { LocalAuthAddon, LocalAuthAddon as default, AuthManager as a, require_ms as c, ScopedTokenManager as i, require_safe_buffer as l, bootstrapSchema as n, SettingsReadUnavailableError as o, UserManager as r, isSettingsReadUnavailable as s, DEFAULT_DATA_PATH as t, ApiKeyManager as u };
10383
+ export { LocalAuthAddon, LocalAuthAddon as default, AuthManager as a, require_ms as c, ScopedTokenManager as i, require_safe_buffer as l, FIRST_BOOT_ADMIN_USERNAME as n, SettingsReadUnavailableError as o, UserManager as r, isSettingsReadUnavailable as s, FIRST_BOOT_ADMIN_PASSWORD as t, ApiKeyManager as u };
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-D_a9mUiM.js");
3
+ const require_dist = require("../../dist-Cc6KOdY-.js");
4
4
  const require_tls = require("../../tls-CCMX7MjX.js");
5
5
  let zod = require("zod");
6
6
  let node_os = require("node:os");
@@ -1,4 +1,4 @@
1
- import { Cn as resolveHubAddressRoles, En as resolveNotificationTapAddress, Gr as EventCategory, Hn as validateCustomAddress, It as isAppLinkBase, Yt as localNetworkCapability, fn as pickAppLinkHub, lr as BaseAddon, nt as checkRoleAddress, s as CAMSTACK_APP_LINK_ORIGIN, yn as resolveAppConnections, zt as isDefaultAppConnectionEndpoint } from "../../dist-BWNoS-1S.mjs";
1
+ import { Cn as resolveHubAddressRoles, En as resolveNotificationTapAddress, Hn as validateCustomAddress, It as isAppLinkBase, Yt as localNetworkCapability, fn as pickAppLinkHub, lr as BaseAddon, nt as checkRoleAddress, qr as EventCategory, s as CAMSTACK_APP_LINK_ORIGIN, yn as resolveAppConnections, zt as isDefaultAppConnectionEndpoint } from "../../dist-B9oFw2jh.mjs";
2
2
  import { P as readLanHttpState, _ as writeExtraSans, h as readTlsMode, i as storeUploadedCertificate, j as applyLanHttp, k as DEFAULT_LAN_HTTP_PORT, m as readTlsAccessStatus, n as regenerateCertificate, p as readExtraSans, r as revertToGeneratedCertificate, t as applyCertificateToListener, u as servedHttpsLeafFingerprint, y as ensureTlsCert } from "../../tls-CJHogwTl.mjs";
3
3
  import { z } from "zod";
4
4
  import * as os from "node:os";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-D_a9mUiM.js");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { Zt as logDestinationCapability, lr as BaseAddon } from "../../dist-BWNoS-1S.mjs";
1
+ import { Zt as logDestinationCapability, lr as BaseAddon } from "../../dist-B9oFw2jh.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ 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-D_a9mUiM.js");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
7
  const require_proc_stat = require("../../proc-stat-DL-WUOhl.js");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_os = require("node:os");
@@ -1,4 +1,4 @@
1
- import { Gr as EventCategory, Mt as foldSnapshotByFunction, Sr as createEvent, _n as reducePoints, bn as resolveBucketMs, cr as errMsg, in as metricsProviderCapability, lr as BaseAddon } from "../../dist-BWNoS-1S.mjs";
1
+ import { Cr as createEvent, Mt as foldSnapshotByFunction, _n as reducePoints, bn as resolveBucketMs, cr as errMsg, in as metricsProviderCapability, lr as BaseAddon, qr as EventCategory } from "../../dist-B9oFw2jh.mjs";
2
2
  import { a as parseProcStat, i as nodeProcReader, n as elapsedSecSince, o as parseProcStatusRss, r as lifetimeCpuPercent, t as commandFromCmdline } from "../../proc-stat-CbC55Kqi.mjs";
3
3
  import { access, readFile, readdir } from "node:fs/promises";
4
4
  import * as os from "node:os";
@@ -3,7 +3,7 @@ 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-D_a9mUiM.js");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
7
  let node_os = require("node:os");
8
8
  node_os = require_chunk.__toESM(node_os);
9
9
  let node_fs = require("node:fs");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { An as scoreRuntimes, Ct as enumerateInferenceDevices, Gr as EventCategory, cr as errMsg, lr as BaseAddon, mn as platformProbeCapability, wr as emitReadiness } from "../../dist-BWNoS-1S.mjs";
2
+ import { An as scoreRuntimes, Ct as enumerateInferenceDevices, Tr as emitReadiness, cr as errMsg, lr as BaseAddon, mn as platformProbeCapability, qr as EventCategory } from "../../dist-B9oFw2jh.mjs";
3
3
  import * as os from "node:os";
4
4
  import * as fs from "node:fs";
5
5
  import { execFile } from "node:child_process";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-D_a9mUiM.js");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { Gr as EventCategory, lr as BaseAddon } from "../../dist-BWNoS-1S.mjs";
1
+ import { lr as BaseAddon, qr as EventCategory } from "../../dist-B9oFw2jh.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ 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-D_a9mUiM.js");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
7
  const require_snapshot_courtesy = require("../../snapshot-courtesy-D_C5-nC0.js");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { Gr as EventCategory, Nn as snapshotCapability, X as bareAddonId, Z as batteryCapability, _t as deriveBatteryPresence, a as BatteryStatusSchema, ar as DeviceFeature, cr as errMsg, jr as nodePin, lr as BaseAddon, rn as memoizePinnedFfmpeg, sr as DeviceType, zn as streamQualityLabel } from "../../dist-BWNoS-1S.mjs";
1
+ import { Mr as nodePin, Nn as snapshotCapability, X as bareAddonId, Z as batteryCapability, _t as deriveBatteryPresence, a as BatteryStatusSchema, ar as DeviceFeature, cr as errMsg, lr as BaseAddon, qr as EventCategory, rn as memoizePinnedFfmpeg, sr as DeviceType, zn as streamQualityLabel } from "../../dist-B9oFw2jh.mjs";
2
2
  import { i as renderCourtesyOverlayJpeg, n as courtesyCacheKey, r as renderCourtesyJpeg } from "../../snapshot-courtesy-CyFs1M3I.mjs";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -3,7 +3,7 @@ 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-D_a9mUiM.js");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Ln as storageProviderCapability, jt as filesystemBrowseCapability, lr as BaseAddon } from "../../dist-BWNoS-1S.mjs";
1
+ import { Ln as storageProviderCapability, jt as filesystemBrowseCapability, lr as BaseAddon } from "../../dist-B9oFw2jh.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ 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-D_a9mUiM.js");
6
+ const require_dist = require("../../dist-Cc6KOdY-.js");
7
7
  const require_hub_cap_forward = require("../../hub-cap-forward-COCmYaup.js");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");