@ouro.bot/cli 0.1.0-alpha.840 → 0.1.0-alpha.841

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.
package/changelog.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.841",
6
+ "changes": [
7
+ "Sanctuary: the root lifecycle accepts the host keeper as the boot owner (D-045), so staging and retirement no longer refuse on a keeper-managed Unraid host; the upgrade orchestrator prunes old Butler images after a successful upgrade, keeping the live and rollback images (D-046: docker.img had reached 88%)."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.840",
6
12
  "changes": [
@@ -823,6 +823,21 @@ function pinTemplate(version) {
823
823
  ok(`DockerMan template pins ${image(version)}`)
824
824
  }
825
825
 
826
+ // Every release pulls a ~1 GB Butler image, and Unraid's docker.img filled to 88% with
827
+ // twenty of them (D-046). After a successful upgrade keep only the live image and the
828
+ // authority's recorded rollback image; Docker itself refuses to remove an image in use.
829
+ function pruneButlerImages() {
830
+ const activation = JSON.parse(readFileSync(`${ROOT}/activation.json`, "utf8"))
831
+ const keep = new Set([activation.targetImageId, activation.rollbackImageId])
832
+ let removed = 0
833
+ for (const line of docker(["images", "--no-trunc", "--format", "{{.ID}} {{.Tag}}", "ghcr.io/ourostack/ouroboros-butler"]).trim().split("\n").filter(Boolean)) {
834
+ const [id, tag] = line.split(" ")
835
+ if (keep.has(id) || !tag || tag === "<none>") continue
836
+ try { docker(["rmi", `ghcr.io/ourostack/ouroboros-butler:${tag}`], { stdio: ["ignore", "pipe", "pipe"] }); removed += 1 } catch { /* in use */ }
837
+ }
838
+ ok(`pruned ${removed} old Butler image(s); kept the live and rollback images`)
839
+ }
840
+
826
841
  function lifecycleFailure(e) {
827
842
  const detail = [e.stderr, e.stdout].map((x) => (x ? String(x).trim() : "")).filter(Boolean).join(" | ")
828
843
  let last = ""
@@ -871,7 +886,7 @@ function upgrade(version, rehearse) {
871
886
  resumeSupervision()
872
887
  fail(`ROLLBACK FAILED: ${lifecycleFailure(e)}\nThe journal is kept; the next authority boot (or \`upgrade-rollback\`) retries it.`)
873
888
  }
874
- } else pinTemplate(version)
889
+ } else { pinTemplate(version); pruneButlerImages() }
875
890
  resumeSupervision()
876
891
  say("preservation")
877
892
  docker(["inspect", "jellyfin", "--format", "{{.Id}}|{{.Image}}|{{.RestartCount}}|{{.State.StartedAt}}"]).trim() === jellyfinBefore ? ok("jellyfin unchanged") : fail("JELLYFIN CHANGED")
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.840",
2
+ "runtimeVersion": "0.1.0-alpha.841",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>ouro-butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.840</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.841</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -34,7 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  var _a;
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.SanctuaryAuthorityRootLifecycle = exports.SANCTUARY_UPGRADE_STEPS = void 0;
37
+ exports.SanctuaryAuthorityRootLifecycle = exports.SANCTUARY_UPGRADE_STEPS = exports.SANCTUARY_KEEPER_BOOT_LINES = void 0;
38
38
  exports.recordSanctuaryRootLifecycleFailure = recordSanctuaryRootLifecycleFailure;
39
39
  exports.runSanctuaryAuthorityRootCli = runSanctuaryAuthorityRootCli;
40
40
  const node_child_process_1 = require("node:child_process");
@@ -58,6 +58,16 @@ const STAGING = "/var/lib/ouro-authority/staging";
58
58
  const CGROUP = "/sys/fs/cgroup/ouro-authority";
59
59
  const BOOT = "/boot/config/custom/ouro-authority/start.sh";
60
60
  const BOOT_LINE = `/bin/sh ${BOOT} --boot & # ouro-authority`;
61
+ // On Unraid the host keeper owns boot instead (it runs BOOT --boot itself, D-032), and the
62
+ // upgrade orchestrator installs exactly these lines in place of BOOT_LINE (D-045).
63
+ const KEEPER_SUPERVISOR = "/boot/config/custom/ouro-authority/gateway-supervisor.sh";
64
+ const KEEPER_WATCHDOG = "/boot/config/custom/ouro-authority/gateway-keeper-watchdog.sh";
65
+ exports.SANCTUARY_KEEPER_BOOT_LINES = {
66
+ supervisor: `setsid /bin/sh ${KEEPER_SUPERVISOR} >/dev/null 2>&1 & # ouro-authority-gateway`,
67
+ watchdog: `(crontab -l 2>/dev/null | grep -v gateway-keeper-watchdog; echo "*/2 * * * * /bin/sh ${KEEPER_WATCHDOG} # ouro-authority-gateway-watchdog") | crontab - # ouro-authority-gateway-watchdog`,
68
+ };
69
+ const BOOT_OWNERS = [BOOT_LINE, exports.SANCTUARY_KEEPER_BOOT_LINES.supervisor];
70
+ const KNOWN_BOOT_LINES = [...BOOT_OWNERS, exports.SANCTUARY_KEEPER_BOOT_LINES.watchdog];
61
71
  const DIGEST = /^sha256:[a-f0-9]{64}$/u;
62
72
  // The gateway verifies every pinned package file before it reports ready. With a cold
63
73
  // page cache on Unraid's array that takes minutes, so readiness gets a generous budget
@@ -293,7 +303,7 @@ class SanctuaryAuthorityRootLifecycle {
293
303
  return false;
294
304
  this.#record(`${this.#epochRoot()}/stage.json`, { packageDigest: this.#request.packageDigest });
295
305
  this.#verifyPackage(undefined, hostExecution);
296
- if (this.#bootScript().text.split("\n").filter((line) => line === BOOT_LINE).length !== 1
306
+ if (this.#bootScript().text.split("\n").filter((line) => BOOT_OWNERS.includes(line)).length !== 1
297
307
  || digest(this.#private(BOOT)) !== digest(fs.readFileSync(this.#p(`${ROOT}/package/deploy/unraid/sanctuary-authority-service.sh`))))
298
308
  throw new Error("Sanctuary installed boot lifecycle changed");
299
309
  return true;
@@ -379,10 +389,10 @@ class SanctuaryAuthorityRootLifecycle {
379
389
  }
380
390
  this.#verifyPackage(undefined, hostExecution);
381
391
  const { text: boot, mode: bootMode } = this.#bootScript();
382
- if (boot.split("\n").some((line) => line.includes("ouro-authority") && line !== BOOT_LINE))
392
+ if (boot.split("\n").some((line) => line.includes("ouro-authority") && !KNOWN_BOOT_LINES.includes(line)))
383
393
  throw new Error("Sanctuary boot ownership is ambiguous");
384
394
  this.#write(BOOT, fs.readFileSync(this.#p(`${ROOT}/package/deploy/unraid/sanctuary-authority-service.sh`), "utf8"));
385
- if (!boot.split("\n").includes(BOOT_LINE))
395
+ if (!boot.split("\n").some((line) => BOOT_OWNERS.includes(line)))
386
396
  this.#write("/boot/config/go", `${boot}${boot.endsWith("\n") ? "" : "\n"}${BOOT_LINE}\n`, bootMode);
387
397
  this.#write(`${this.#epochRoot()}/stage.json`, JSON.stringify({ packageDigest: this.#request.packageDigest }));
388
398
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.840",
3
+ "version": "0.1.0-alpha.841",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@ouro.bot/cli",
9
- "version": "0.1.0-alpha.840",
9
+ "version": "0.1.0-alpha.841",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.78.0",
12
12
  "@azure/identity": "^4.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.840",
3
+ "version": "0.1.0-alpha.841",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },