openmates 0.12.0-alpha.28 → 0.12.0-alpha.29

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.
@@ -8964,7 +8964,7 @@ function formatTs(ts) {
8964
8964
  }
8965
8965
 
8966
8966
  // src/server.ts
8967
- import { execSync, spawn as nodeSpawn } from "child_process";
8967
+ import { execFileSync as execFileSync2, execSync, spawn as nodeSpawn } from "child_process";
8968
8968
  import { createHash as createHash6, randomBytes as randomBytes2 } from "crypto";
8969
8969
  import { chmodSync as chmodSync2, copyFileSync, cpSync, existsSync as existsSync5, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync5, readdirSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
8970
8970
  import { createInterface as createInterface2 } from "readline";
@@ -9104,6 +9104,49 @@ function planRestore(input) {
9104
9104
  steps: input.yes === true ? ["stop", "restore", "start", "health-check"] : ["confirm", "stop", "restore", "start", "health-check"]
9105
9105
  };
9106
9106
  }
9107
+ function parseSecretEnvKey(envKey) {
9108
+ if (!envKey.startsWith("SECRET__")) return null;
9109
+ const parts = envKey.slice("SECRET__".length).split("__", 2);
9110
+ if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
9111
+ return {
9112
+ envKey,
9113
+ vaultPath: `kv/data/providers/${parts[0].toLowerCase()}`,
9114
+ vaultKey: parts[1].toLowerCase()
9115
+ };
9116
+ }
9117
+ function summarizeSecretPreflight(input) {
9118
+ const inlineSecretEnvKeys = [];
9119
+ const importedSecretEnvKeys = [];
9120
+ const emptySecretEnvKeys = [];
9121
+ const importedVaultPresent = [];
9122
+ const importedVaultMissing = [];
9123
+ const importedVaultUnavailable = [];
9124
+ for (const [envKey, rawValue] of Object.entries(input.env).sort(([a], [b]) => a.localeCompare(b))) {
9125
+ if (!parseSecretEnvKey(envKey)) continue;
9126
+ const value = rawValue.trim();
9127
+ if (!value) {
9128
+ emptySecretEnvKeys.push(envKey);
9129
+ continue;
9130
+ }
9131
+ if (value !== "IMPORTED_TO_VAULT") {
9132
+ inlineSecretEnvKeys.push(envKey);
9133
+ continue;
9134
+ }
9135
+ importedSecretEnvKeys.push(envKey);
9136
+ const presence = input.vaultPresence?.[envKey];
9137
+ if (presence === "present") importedVaultPresent.push(envKey);
9138
+ else if (presence === "missing") importedVaultMissing.push(envKey);
9139
+ else importedVaultUnavailable.push(envKey);
9140
+ }
9141
+ return {
9142
+ inlineSecretEnvKeys,
9143
+ importedSecretEnvKeys,
9144
+ emptySecretEnvKeys,
9145
+ importedVaultPresent,
9146
+ importedVaultMissing,
9147
+ importedVaultUnavailable
9148
+ };
9149
+ }
9107
9150
  function planCaddyCommand(input) {
9108
9151
  const role = parseServerRole(input.role);
9109
9152
  const templatePath = `templates/caddy/${role}/Caddyfile`;
@@ -9796,6 +9839,89 @@ function missingRequiredEnvKeys(installPath, role) {
9796
9839
  const env = readEnvMap(installPath);
9797
9840
  return requiredRuntimeEnvKeys(role).filter((key) => !env[key]);
9798
9841
  }
9842
+ function vaultCheckContainerForRole(role) {
9843
+ if (role === "core") return "api";
9844
+ if (role === "upload") return "app-uploads";
9845
+ return null;
9846
+ }
9847
+ function readImportedVaultSecretPresence(role, importedSecrets) {
9848
+ const checks = importedSecrets.filter((item) => item !== null);
9849
+ if (checks.length === 0) return { presence: {}, checked: true, unavailableReason: null };
9850
+ const container = vaultCheckContainerForRole(role);
9851
+ if (!container) {
9852
+ return { presence: {}, checked: false, unavailableReason: `${role} role does not expose a Vault-backed app container for secret verification.` };
9853
+ }
9854
+ const script = `
9855
+ import json
9856
+ import os
9857
+ import urllib.error
9858
+ import urllib.request
9859
+
9860
+ checks = json.loads(os.environ["OPENMATES_SECRET_CHECKS"])
9861
+ token_path = "/vault-data/api.token"
9862
+ try:
9863
+ with open(token_path, "r", encoding="utf-8") as token_file:
9864
+ token = token_file.read().strip()
9865
+ except Exception as exc:
9866
+ print(json.dumps({"ok": False, "reason": f"could not read {token_path}: {exc}", "presence": {}}))
9867
+ raise SystemExit(0)
9868
+
9869
+ presence = {}
9870
+ for item in checks:
9871
+ request = urllib.request.Request(
9872
+ f"http://vault:8200/v1/{item['vaultPath']}",
9873
+ headers={"X-Vault-Token": token},
9874
+ )
9875
+ try:
9876
+ with urllib.request.urlopen(request, timeout=3) as response:
9877
+ payload = json.loads(response.read().decode("utf-8"))
9878
+ value = payload.get("data", {}).get("data", {}).get(item["vaultKey"])
9879
+ presence[item["envKey"]] = "present" if value else "missing"
9880
+ except urllib.error.HTTPError as exc:
9881
+ presence[item["envKey"]] = "missing" if exc.code == 404 else "unavailable"
9882
+ except Exception:
9883
+ presence[item["envKey"]] = "unavailable"
9884
+
9885
+ print(json.dumps({"ok": True, "presence": presence}))
9886
+ `;
9887
+ try {
9888
+ const output = execFileSync2(
9889
+ "docker",
9890
+ ["exec", "-e", `OPENMATES_SECRET_CHECKS=${JSON.stringify(checks)}`, container, "python", "-c", script],
9891
+ { encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
9892
+ ).trim();
9893
+ const parsed = JSON.parse(output);
9894
+ if (parsed.ok === false) return { presence: {}, checked: false, unavailableReason: parsed.reason ?? "Vault check failed." };
9895
+ return { presence: parsed.presence ?? {}, checked: true, unavailableReason: null };
9896
+ } catch (error) {
9897
+ return {
9898
+ presence: {},
9899
+ checked: false,
9900
+ unavailableReason: `Vault check unavailable. Is the ${container} container running? ${error instanceof Error ? error.message : String(error)}`
9901
+ };
9902
+ }
9903
+ }
9904
+ function runtimeSecretPreflight(installPath, role) {
9905
+ const env = readEnvMap(installPath);
9906
+ const importedSecrets = Object.entries(env).filter(([, value]) => value.trim() === "IMPORTED_TO_VAULT").map(([envKey]) => parseSecretEnvKey(envKey)).filter((item) => item !== null);
9907
+ const vault = readImportedVaultSecretPresence(role, importedSecrets);
9908
+ return {
9909
+ ...summarizeSecretPreflight({ env, vaultPresence: vault.presence }),
9910
+ vaultChecked: vault.checked,
9911
+ vaultUnavailableReason: vault.unavailableReason
9912
+ };
9913
+ }
9914
+ function formatSecretPreflight(preflight) {
9915
+ const parts = [
9916
+ `${preflight.importedVaultPresent.length}/${preflight.importedSecretEnvKeys.length} imported verified`
9917
+ ];
9918
+ if (preflight.importedVaultMissing.length) parts.push(`missing in Vault: ${preflight.importedVaultMissing.join(", ")}`);
9919
+ if (preflight.importedVaultUnavailable.length) parts.push(`unverified: ${preflight.importedVaultUnavailable.join(", ")}`);
9920
+ if (preflight.inlineSecretEnvKeys.length) parts.push(`inline SECRET entries: ${preflight.inlineSecretEnvKeys.length}`);
9921
+ if (preflight.emptySecretEnvKeys.length) parts.push(`empty SECRET entries: ${preflight.emptySecretEnvKeys.length}`);
9922
+ if (preflight.vaultUnavailableReason) parts.push(preflight.vaultUnavailableReason);
9923
+ return parts.join("; ");
9924
+ }
9799
9925
  function writeChecksums(rootDir) {
9800
9926
  const lines = [];
9801
9927
  const walk = (dir) => {
@@ -10339,6 +10465,12 @@ async function serverUpdate(rest, flags) {
10339
10465
  const filterRequested = hasServiceFilter(flags);
10340
10466
  const selectedServices = filterRequested ? selectedComposeServices(role, flags) : [];
10341
10467
  const missingEnvKeys = missingRequiredEnvKeys(installPath, role);
10468
+ const secretPreflight = runtimeSecretPreflight(installPath, role);
10469
+ const missingOrUnverifiedSecrets = [
10470
+ ...missingEnvKeys,
10471
+ ...secretPreflight.importedVaultMissing,
10472
+ ...secretPreflight.importedVaultUnavailable
10473
+ ];
10342
10474
  if (installMode === "source" && (flags["image-tag"] !== void 0 || flags.channel !== void 0)) {
10343
10475
  throw new Error("--image-tag and --channel only apply to image-mode installs. Source-mode installs update from Git.");
10344
10476
  }
@@ -10348,7 +10480,7 @@ async function serverUpdate(rest, flags) {
10348
10480
  const currentTag = getImageTagFromEnv(installPath, config);
10349
10481
  const target = resolveTargetImageTag(flags, currentTag, getPackageVersion());
10350
10482
  const templateRef = templateRefForImageTag(target.tag, getPackageVersion());
10351
- const safetyPlan = planUpdate({ role, selectedServices, dryRun, skipBackup: flags["skip-backup"] === true, continuous: false, missingRequiredSecrets: missingEnvKeys });
10483
+ const safetyPlan = planUpdate({ role, selectedServices, dryRun, skipBackup: flags["skip-backup"] === true, continuous: false, missingRequiredSecrets: missingOrUnverifiedSecrets });
10352
10484
  const plan = {
10353
10485
  command: "update",
10354
10486
  role,
@@ -10362,6 +10494,7 @@ async function serverUpdate(rest, flags) {
10362
10494
  steps: safetyPlan.steps,
10363
10495
  backupName: safetyPlan.backupName,
10364
10496
  missingRequiredEnvKeys: missingEnvKeys,
10497
+ secretPreflight,
10365
10498
  blocked: safetyPlan.blocked,
10366
10499
  blockReason: safetyPlan.blockReason,
10367
10500
  dryRun
@@ -10380,13 +10513,16 @@ async function serverUpdate(rest, flags) {
10380
10513
  console.log(` Backup: ${safetyPlan.backupName ?? "none"}`);
10381
10514
  console.log(` Steps: ${safetyPlan.steps.join(" -> ")}`);
10382
10515
  console.log(` Env preflight: ${missingEnvKeys.length ? `missing ${missingEnvKeys.join(", ")}` : "ok"}`);
10516
+ console.log(` Vault secrets: ${formatSecretPreflight(secretPreflight)}`);
10383
10517
  console.log(" Commands: refresh compose, docker compose pull, docker compose up -d, health checks");
10384
10518
  }
10385
10519
  return;
10386
10520
  }
10387
10521
  if (safetyPlan.blocked) throw new Error(safetyPlan.blockReason ?? "Update blocked by preflight.");
10388
- if (missingEnvKeys.length && flags.yes !== true) {
10389
- throw new Error(`Required environment keys are missing: ${missingEnvKeys.join(", ")}. Add them to .env or rerun with --yes after reviewing.`);
10522
+ if (missingOrUnverifiedSecrets.length && flags.yes !== true) {
10523
+ throw new Error(
10524
+ `Required runtime secret checks failed: ${missingOrUnverifiedSecrets.join(", ")}. Add missing non-SECRET values to .env, ensure IMPORTED_TO_VAULT markers exist in Vault, or rerun with --yes after reviewing.`
10525
+ );
10390
10526
  }
10391
10527
  console.error(`Mode: image`);
10392
10528
  console.error(`Current image tag: ${currentTag || "unknown"}`);
@@ -10939,8 +11075,14 @@ async function serverPreflight(flags) {
10939
11075
  const config = loadConfigForInstallPath(installPath);
10940
11076
  const role = getServerRole(flags, config);
10941
11077
  const services = hasServiceFilter(flags) ? selectedComposeServices(role, flags) : [];
10942
- const updatePlan = planUpdate({ role, selectedServices: services, dryRun: true });
10943
11078
  const missingEnvKeys = missingRequiredEnvKeys(installPath, role);
11079
+ const secretPreflight = runtimeSecretPreflight(installPath, role);
11080
+ const missingOrUnverifiedSecrets = [
11081
+ ...missingEnvKeys,
11082
+ ...secretPreflight.importedVaultMissing,
11083
+ ...secretPreflight.importedVaultUnavailable
11084
+ ];
11085
+ const updatePlan = planUpdate({ role, selectedServices: services, dryRun: true, continuous: true, missingRequiredSecrets: missingOrUnverifiedSecrets });
10944
11086
  const runtimePlan = planServerRuntime({
10945
11087
  role,
10946
11088
  profile: getCoreProfile(flags, config),
@@ -10958,6 +11100,10 @@ async function serverPreflight(flags) {
10958
11100
  updateSteps: updatePlan.steps,
10959
11101
  backupName: updatePlan.backupName,
10960
11102
  missingRequiredEnvKeys: missingEnvKeys,
11103
+ secretPreflight,
11104
+ missingOrUnverifiedSecrets,
11105
+ blocked: updatePlan.blocked,
11106
+ blockReason: updatePlan.blockReason,
10961
11107
  caddy: caddyPlan
10962
11108
  };
10963
11109
  if (flags.json === true) {
@@ -10970,6 +11116,8 @@ async function serverPreflight(flags) {
10970
11116
  console.log(` Services: ${hasServiceFilter(flags) ? services.join(", ") : "all"}`);
10971
11117
  console.log(` Backup: ${updatePlan.backupName ?? "none"}`);
10972
11118
  console.log(` Env preflight: ${missingEnvKeys.length ? `missing ${missingEnvKeys.join(", ")}` : "ok"}`);
11119
+ console.log(` Vault secrets: ${formatSecretPreflight(secretPreflight)}`);
11120
+ if (updatePlan.blocked) console.log(` Blocked: ${updatePlan.blockReason}`);
10973
11121
  console.log(` Health checks: ${runtimePlan.healthChecks.join(", ")}`);
10974
11122
  console.log(` Caddy steps: ${caddyPlan.steps.join(" -> ")}`);
10975
11123
  }
@@ -11022,14 +11170,16 @@ WARNING: This will replace ${appliedPath} with the packaged ${role} Caddyfile.`)
11022
11170
  }
11023
11171
  }
11024
11172
  const backupPath = `${appliedPath}.openmates-backup-${nowStamp()}`;
11173
+ const hadExistingCaddyfile = existsSync5(appliedPath);
11025
11174
  try {
11026
- if (existsSync5(appliedPath)) copyFileSync(appliedPath, backupPath);
11175
+ mkdirSync3(dirname(appliedPath), { recursive: true });
11176
+ if (hadExistingCaddyfile) copyFileSync(appliedPath, backupPath);
11027
11177
  copyFileSync(templatePath, appliedPath);
11028
11178
  execSync("systemctl reload caddy", { stdio: "inherit" });
11029
11179
  } catch (error) {
11030
11180
  throw new Error(`Could not apply Caddyfile. Run with sudo or use --config <writable path>. ${error instanceof Error ? error.message : String(error)}`);
11031
11181
  }
11032
- console.log(`Applied Caddyfile for ${role}. Backup: ${backupPath}`);
11182
+ console.log(`Applied Caddyfile for ${role}. ${hadExistingCaddyfile ? `Backup: ${backupPath}` : "No previous Caddyfile existed."}`);
11033
11183
  return;
11034
11184
  }
11035
11185
  console.log(`Caddy ${action} plan:`);
@@ -25307,7 +25457,8 @@ version_number: 1`,
25307
25457
  ],
25308
25458
  metadata: {
25309
25459
  featured: true,
25310
- order: 105
25460
+ order: 105,
25461
+ content_embed_examples: ["mindmaps.mindmap"]
25311
25462
  }
25312
25463
  };
25313
25464
 
@@ -28033,6 +28184,12 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
28033
28184
  continue: {
28034
28185
  text: "Continue"
28035
28186
  },
28187
+ skip: {
28188
+ text: "Skip"
28189
+ },
28190
+ select_interests: {
28191
+ text: "Select interests"
28192
+ },
28036
28193
  software_development: {
28037
28194
  text: "software development"
28038
28195
  },
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getExtForLang,
4
4
  serializeToYaml
5
- } from "./chunk-TTQIZ476.js";
5
+ } from "./chunk-FCSXUBOU.js";
6
6
  import "./chunk-AXNRPVLE.js";
7
7
  export {
8
8
  getExtForLang,
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  getExtForLang,
12
12
  normalizeInterestTagIds,
13
13
  serializeToYaml
14
- } from "./chunk-TTQIZ476.js";
14
+ } from "./chunk-FCSXUBOU.js";
15
15
  import "./chunk-AXNRPVLE.js";
16
16
  export {
17
17
  ASSISTANT_FEEDBACK_REPORT_TITLE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmates",
3
- "version": "0.12.0-alpha.28",
3
+ "version": "0.12.0-alpha.29",
4
4
  "description": "OpenMates CLI and SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",