@ouro.bot/cli 0.1.0-alpha.836 → 0.1.0-alpha.837
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 +6 -0
- package/deploy/unraid/sanctuary-butler-upgrade.mjs +12 -9
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/daemon/sanctuary-authority-installation.js +12 -0
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +21 -3
- package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +2 -3
- package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
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.837",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Sanctuary: the root authority keeps a permanent empty child cgroup (ouro-keep), so Unraid's cgroup2-unraid can no longer remove the authority cgroup (D-026). The host keeper now lets the reaper run and pauses it only while the keep child is missing; every reader of the authority cgroup ignores the keep child."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.836",
|
|
6
12
|
"changes": [
|
|
@@ -428,12 +428,13 @@ const GATEWAY_SUPERVISOR = `${AUTHORITY_CUSTOM}/gateway-supervisor.sh`
|
|
|
428
428
|
const GATEWAY_WATCHDOG = `${AUTHORITY_CUSTOM}/gateway-keeper-watchdog.sh`
|
|
429
429
|
const UNRAID_STOP_HOOK = "/boot/config/stop"
|
|
430
430
|
const GATEWAY_SUPERVISOR_SH = String.raw`#!/bin/sh
|
|
431
|
-
# Ouro authority gateway keeper for Unraid
|
|
432
|
-
# The
|
|
433
|
-
#
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
# -
|
|
431
|
+
# Ouro authority gateway keeper for Unraid.
|
|
432
|
+
# The root-authority gateway needs /sys/fs/cgroup/ouro-authority to persist, and Unraid's
|
|
433
|
+
# cgroup2-unraid removes every top-level cgroup that reports populated 0. The kernel will not
|
|
434
|
+
# remove a cgroup that has a child cgroup, so the authority keeps an empty ouro-keep child
|
|
435
|
+
# (D-026). This keeper:
|
|
436
|
+
# - creates that keep child and lets the reaper run; it pauses the reaper only while the
|
|
437
|
+
# keep child is missing (a fresh boot, or an authority older than the keep child),
|
|
437
438
|
# - keeps the gateway process alive,
|
|
438
439
|
# - reconnects the resident (docker restart) if it is unhealthy while the gateway is ready
|
|
439
440
|
# (handles reboot ordering, where Docker autostarts the resident before the gateway).
|
|
@@ -443,15 +444,17 @@ R=/mnt/user/appdata/ouro-authority
|
|
|
443
444
|
GW=$R/package/dist/heart/daemon/sanctuary-telegram-authority-entry.js
|
|
444
445
|
CFG=$R/active.json
|
|
445
446
|
LOG=/var/log/ouro-gateway.log
|
|
446
|
-
|
|
447
|
+
reaper_policy() {
|
|
447
448
|
P=$(cat /run/cgroup2-unraid.pid 2>/dev/null) || return 0
|
|
448
449
|
[ -n "$P" ] || return 0
|
|
450
|
+
if [ -d "$CG/ouro-keep" ]; then kill -CONT "$P" 2>/dev/null; return 0; fi
|
|
449
451
|
S=$(ps -o stat= -p "$P" 2>/dev/null | tr -d ' ')
|
|
450
452
|
case "$S" in T*) : ;; *) kill -STOP "$P" 2>/dev/null ;; esac
|
|
451
453
|
}
|
|
452
454
|
ensure_cg() {
|
|
453
455
|
[ -d "$CG" ] || { mkdir -m 700 "$CG" 2>/dev/null; chown 0:0 "$CG" 2>/dev/null
|
|
454
456
|
for c in cpu memory pids; do echo "+$c" > "$CG/cgroup.subtree_control" 2>/dev/null; done; }
|
|
457
|
+
[ -d "$CG/ouro-keep" ] || mkdir -m 700 "$CG/ouro-keep" 2>/dev/null
|
|
455
458
|
}
|
|
456
459
|
ensure_sock() {
|
|
457
460
|
# Docker autostarts the resident before us and auto-creates its missing bind
|
|
@@ -475,12 +478,12 @@ start_gw() {
|
|
|
475
478
|
}
|
|
476
479
|
# An in-place upgrade pauses us with a flag; a stale flag (> 30 min) is ignored.
|
|
477
480
|
maintenance() { [ -n "$(find /run/ouro-authority-maintenance -mmin -30 2>/dev/null)" ]; }
|
|
478
|
-
|
|
481
|
+
ensure_cg; reaper_policy; ensure_sock
|
|
479
482
|
last_res=0
|
|
480
483
|
while true; do
|
|
481
484
|
if maintenance; then sleep 15; continue; fi
|
|
482
|
-
pause_reaper
|
|
483
485
|
ensure_cg
|
|
486
|
+
reaper_policy
|
|
484
487
|
ensure_sock
|
|
485
488
|
[ -n "$(gw_pid)" ] || { start_gw; sleep 8; }
|
|
486
489
|
rd=$(jq -rc .status "$R"/epochs/*/readiness.json 2>/dev/null)
|
|
@@ -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.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.837</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SANCTUARY_CGROUP_KEEP = void 0;
|
|
37
|
+
exports.sanctuaryCgroupChildren = sanctuaryCgroupChildren;
|
|
36
38
|
exports.verifySanctuaryAuthorityInstallation = verifySanctuaryAuthorityInstallation;
|
|
37
39
|
const node_crypto_1 = require("node:crypto");
|
|
38
40
|
const fs = __importStar(require("node:fs"));
|
|
@@ -70,6 +72,16 @@ function readOwned(filePath, uid, gid, mode) {
|
|
|
70
72
|
fs.closeSync(descriptor);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Unraid's cgroup2-unraid removes every top-level cgroup that reports `populated 0`,
|
|
77
|
+
* and the kernel refuses to remove a cgroup that has child cgroups. So the authority
|
|
78
|
+
* keeps one permanent empty child, and every reader of its cgroup ignores it (D-026).
|
|
79
|
+
*/
|
|
80
|
+
exports.SANCTUARY_CGROUP_KEEP = "ouro-keep";
|
|
81
|
+
/** The authority cgroup's execution children: directories other than the keep child. */
|
|
82
|
+
function sanctuaryCgroupChildren(cgroupRoot) {
|
|
83
|
+
return fs.readdirSync(cgroupRoot).filter((name) => name !== exports.SANCTUARY_CGROUP_KEEP && fs.lstatSync(path.join(cgroupRoot, name)).isDirectory());
|
|
84
|
+
}
|
|
73
85
|
function verifySanctuaryAuthorityInstallation(input) {
|
|
74
86
|
const { expectedUid: uid, expectedGid: gid } = input;
|
|
75
87
|
for (const root of [input.packageRoot, input.stateRoot, input.stagingRoot, input.cgroupRoot])
|
|
@@ -151,7 +151,7 @@ class SanctuaryAuthorityRootLifecycle {
|
|
|
151
151
|
throw new Error("Resolve pending execution state before re-pinning");
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
-
if (
|
|
154
|
+
if ((0, sanctuary_authority_installation_1.sanctuaryCgroupChildren)(this.#p(CGROUP)).length !== 0)
|
|
155
155
|
throw new Error("Resolve pending cgroups before re-pinning");
|
|
156
156
|
this.#verifyPrimitive("/usr/bin/prlimit", prlimitDigest);
|
|
157
157
|
this.#verifyPrimitive("/usr/bin/setsid", setsidDigest);
|
|
@@ -294,9 +294,26 @@ class SanctuaryAuthorityRootLifecycle {
|
|
|
294
294
|
throw new Error("Sanctuary installed boot lifecycle changed");
|
|
295
295
|
return true;
|
|
296
296
|
}
|
|
297
|
+
/** Create the authority cgroup with its keep child. The reaper can remove a freshly
|
|
298
|
+
* made (still empty) cgroup before the child exists, so retry that race a few times. */
|
|
299
|
+
#cgroup() {
|
|
300
|
+
const keep = this.#p(`${CGROUP}/${sanctuary_authority_installation_1.SANCTUARY_CGROUP_KEEP}`);
|
|
301
|
+
for (let attempt = 1; !fs.existsSync(keep); attempt += 1) {
|
|
302
|
+
try {
|
|
303
|
+
this.#directory(CGROUP);
|
|
304
|
+
fs.mkdirSync(keep, { mode: 0o700 });
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
if (error.code !== "ENOENT" || attempt >= 5)
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
this.#directory(CGROUP);
|
|
312
|
+
}
|
|
297
313
|
#runtimeDirectories() {
|
|
298
|
-
for (const directory of [this.#epochRoot(), STAGING
|
|
314
|
+
for (const directory of [this.#epochRoot(), STAGING])
|
|
299
315
|
this.#directory(directory);
|
|
316
|
+
this.#cgroup();
|
|
300
317
|
this.#directory(SOCKET, 0o750, this.#socketGid);
|
|
301
318
|
// Add only the required controllers; never replace another owner's controls.
|
|
302
319
|
for (const root of ["/sys/fs/cgroup", CGROUP]) {
|
|
@@ -546,7 +563,7 @@ class SanctuaryAuthorityRootLifecycle {
|
|
|
546
563
|
const epoch = this.#epoch();
|
|
547
564
|
if (proof.schemaVersion !== 1 || proof.keyId !== epoch.epochId || proof.publicKeyDigest !== epoch.publicKeyDigest || proof.quiescent !== true
|
|
548
565
|
|| !Number.isSafeInteger(proof.cursor) || proof.cursor < epoch.predecessorCursor
|
|
549
|
-
||
|
|
566
|
+
|| (0, sanctuary_authority_installation_1.sanctuaryCgroupChildren)(this.#p(CGROUP)).length !== 0)
|
|
550
567
|
throw new Error("Sanctuary gateway retirement cleanup is unproven");
|
|
551
568
|
return true;
|
|
552
569
|
}
|
|
@@ -854,6 +871,7 @@ class SanctuaryAuthorityRootLifecycle {
|
|
|
854
871
|
}
|
|
855
872
|
/** Start the gateway, then the resident, and prove both. */
|
|
856
873
|
async #startUpgraded() {
|
|
874
|
+
this.#runtimeDirectories();
|
|
857
875
|
await this.#startGateway();
|
|
858
876
|
this.#docker(["start", "ouro-butler"]);
|
|
859
877
|
await this.#wait(async () => this.#target().State.Running && this.#target().State.Health?.Status === "healthy" && await this.#ready(), 300_000);
|
|
@@ -39,6 +39,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
39
39
|
const fs = __importStar(require("node:fs"));
|
|
40
40
|
const path = __importStar(require("node:path"));
|
|
41
41
|
const runtime_1 = require("../../nerves/runtime");
|
|
42
|
+
const sanctuary_authority_installation_1 = require("./sanctuary-authority-installation");
|
|
42
43
|
const SCHEMA_VERSION = 1;
|
|
43
44
|
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
44
45
|
const PERMIT_ID = /^permit-[A-Za-z0-9_-]{43}$/u;
|
|
@@ -183,10 +184,8 @@ class DetachedSanctuaryHostSupervisor {
|
|
|
183
184
|
throw new Error("Sanctuary host supervisor state is unmatched");
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
|
-
for (const permitId of
|
|
187
|
+
for (const permitId of (0, sanctuary_authority_installation_1.sanctuaryCgroupChildren)(this.#options.cgroupRoot)) {
|
|
187
188
|
const cgroupPath = path.join(this.#options.cgroupRoot, permitId);
|
|
188
|
-
if (!fs.lstatSync(cgroupPath).isDirectory())
|
|
189
|
-
continue;
|
|
190
189
|
if (!PERMIT_ID.test(permitId))
|
|
191
190
|
throw new Error("Sanctuary host cgroup entry is invalid");
|
|
192
191
|
if (!known.has(permitId) && !await this.#reconcileCgroup(cgroupPath)) {
|
|
@@ -346,7 +346,7 @@ async function startSanctuaryTelegramAuthority(options) {
|
|
|
346
346
|
hostAuthority.retireRegistrations(ledger);
|
|
347
347
|
await reconcileExecutions();
|
|
348
348
|
if (hostAuthority.retireRegistrations(ledger).length !== 0
|
|
349
|
-
||
|
|
349
|
+
|| (0, sanctuary_authority_installation_1.sanctuaryCgroupChildren)(config.hostCgroupRoot).length !== 0)
|
|
350
350
|
throw new Error("Sanctuary authority retirement cleanup is unproven");
|
|
351
351
|
writePrivateJson(path.join(config.epochRoot, "retirement.json"), {
|
|
352
352
|
schemaVersion: 1, keyId: config.keyId, publicKeyDigest: config.publicKeyDigest,
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.837",
|
|
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.
|
|
9
|
+
"version": "0.1.0-alpha.837",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|