@ouro.bot/cli 0.1.0-alpha.747 → 0.1.0-alpha.749

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,20 @@
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.749",
6
+ "changes": [
7
+ "Fix Unraid Butler autostart updates through the root-local Docker manager backend with durable rollback and ghost-entry preservation.",
8
+ "Align the packaged Sanctuary template and bundle runtime metadata with alpha.749."
9
+ ]
10
+ },
11
+ {
12
+ "version": "0.1.0-alpha.748",
13
+ "changes": [
14
+ "Reject any unexpected running Ouro image or writable Sanctuary-root overlap, including legacy and package-managed layouts, across zero-poller and active-poller deployment handoffs.",
15
+ "Remove the exact legacy Unraid boot hook that rewrites the managed Usenet cron entry after reboot."
16
+ ]
17
+ },
4
18
  {
5
19
  "version": "0.1.0-alpha.747",
6
20
  "changes": [
@@ -52,12 +52,55 @@ Effective-spec audit helper:
52
52
  trap - EXIT || return $?
53
53
  )
54
54
  }
55
- Define these helpers in the same root shell. Each autostart change uses
56
- Unraid's authenticated loopback WebGUI endpoint with the live root-session
57
- CSRF token supplied only on request stdin. Requests have hard connection and
58
- total timeouts, discard response bodies, and read back the durable array
59
- autostart state without writing its backing file directly:
60
- set_butler_autostart() {
55
+ Define these helpers in the same root shell. Each profile change calls Unraid's
56
+ own root-local Docker manager backend, after proving exact durable identities.
57
+ A missing Butler entry is a no-op when disabling; this avoids the backend's
58
+ false-to-index-zero behavior. The helper preserves every non-Butler row byte
59
+ for byte and restores the complete captured file atomically on any failure:
60
+ butler_autostart_counts() {
61
+ (
62
+ AUTOSTART_FILE=/var/lib/docker/unraid-autostart
63
+ test -f "$AUTOSTART_FILE" || return $?
64
+ awk '
65
+ $1 == "ouro-butler" { production++ }
66
+ $1 == "ouro-butler-staging" { staging++ }
67
+ $1 == "ouro-butler-rollback" { rollback++ }
68
+ $1 == "ouro-butler-legacy-evidence" { legacy++ }
69
+ END { printf "%d %d %d %d", production + 0, staging + 0, rollback + 0, legacy + 0 }
70
+ ' "$AUTOSTART_FILE"
71
+ )
72
+ }
73
+ snapshot_nonbutler_autostart() {
74
+ awk '
75
+ $1 != "ouro-butler" && $1 != "ouro-butler-staging" && $1 != "ouro-butler-rollback" && $1 != "ouro-butler-legacy-evidence" { print }
76
+ ' /var/lib/docker/unraid-autostart
77
+ }
78
+ run_unraid_autostart_backend() {
79
+ (
80
+ AUTOSTART_ACTION=$1
81
+ AUTOSTART_CONTAINER=$2
82
+ AUTOSTART_VALUE=$3
83
+ case "$AUTOSTART_ACTION:$AUTOSTART_VALUE" in wait:0|autostart:true|autostart:false) ;; *) return 1 ;; esac
84
+ case "$AUTOSTART_CONTAINER" in
85
+ ouro-butler|ouro-butler-staging|ouro-butler-rollback|ouro-butler-legacy-evidence) ;;
86
+ *) return 1 ;;
87
+ esac
88
+ timeout -s KILL 20 /usr/bin/php -r '
89
+ [$action, $container, $value] = array_slice($argv, 1);
90
+ $allowed = ["ouro-butler", "ouro-butler-staging", "ouro-butler-rollback", "ouro-butler-legacy-evidence"];
91
+ if (!in_array($container, $allowed, true)) exit(2);
92
+ if ($action === "wait" && $value === "0") {
93
+ $_POST = ["action" => "wait", "container" => $container, "wait" => "0"];
94
+ } elseif ($action === "autostart" && ($value === "true" || $value === "false")) {
95
+ $_POST = ["action" => "autostart", "container" => $container, "auto" => $value, "wait" => "0"];
96
+ } else exit(2);
97
+ $_SERVER["DOCUMENT_ROOT"] = "/usr/local/emhttp";
98
+ $_SERVER["REQUEST_URI"] = "docker";
99
+ require "/usr/local/emhttp/plugins/dynamix.docker.manager/include/UpdateConfig.php";
100
+ ' "$AUTOSTART_ACTION" "$AUTOSTART_CONTAINER" "$AUTOSTART_VALUE"
101
+ )
102
+ }
103
+ mutate_butler_autostart() {
61
104
  (
62
105
  AUTOSTART_CONTAINER=$1
63
106
  AUTOSTART_ENABLED=$2
@@ -66,35 +109,102 @@ Effective-spec audit helper:
66
109
  *) return 1 ;;
67
110
  esac
68
111
  case "$AUTOSTART_ENABLED" in true|false) ;; *) return 1 ;; esac
69
- AUTOSTART_CSRF_FILE=/var/local/emhttp/var.ini
70
- test -f "$AUTOSTART_CSRF_FILE" || return $?
71
- AUTOSTART_CSRF_TOKEN=$(awk -F= '$1 == "csrf_token" {
72
- value = $2
73
- sub(/^"/, "", value)
74
- sub(/"$/, "", value)
75
- print value
76
- }' "$AUTOSTART_CSRF_FILE") || return $?
77
- test -n "$AUTOSTART_CSRF_TOKEN" || return $?
78
- case "$AUTOSTART_CSRF_TOKEN" in *[!A-Za-z0-9._-]*) return 1 ;; esac
79
- printf '%s' "action=autostart&container=$AUTOSTART_CONTAINER&auto=$AUTOSTART_ENABLED&wait=0&csrf_token=$AUTOSTART_CSRF_TOKEN" | \
80
- curl --silent --show-error --fail --request POST \
81
- --connect-timeout 5 --max-time 15 \
82
- --header 'Content-Type: application/x-www-form-urlencoded' \
83
- --data-binary @- --output /dev/null \
84
- http://127.0.0.1/plugins/dynamix.docker.manager/include/UpdateConfig.php || return $?
112
+ AUTOSTART_COUNT=$(awk -v name="$AUTOSTART_CONTAINER" '$1 == name { count++ } END { print count + 0 }' /var/lib/docker/unraid-autostart) || return $?
113
+ case "$AUTOSTART_COUNT" in 0|1) ;; *) return 1 ;; esac
114
+ if test "$AUTOSTART_ENABLED" = false && test "$AUTOSTART_COUNT" -eq 0; then
115
+ return 0
116
+ fi
117
+ if test "$AUTOSTART_COUNT" -eq 1; then
118
+ run_unraid_autostart_backend wait "$AUTOSTART_CONTAINER" 0 || return $?
119
+ fi
120
+ if test "$AUTOSTART_ENABLED" = false; then
121
+ run_unraid_autostart_backend autostart "$AUTOSTART_CONTAINER" false || return $?
122
+ AUTOSTART_EXPECTED_COUNT=0
123
+ else
124
+ if test "$AUTOSTART_COUNT" -eq 0; then
125
+ run_unraid_autostart_backend autostart "$AUTOSTART_CONTAINER" true || return $?
126
+ fi
127
+ AUTOSTART_EXPECTED_COUNT=1
128
+ fi
129
+ AUTOSTART_COUNT=$(awk -v name="$AUTOSTART_CONTAINER" '$1 == name { count++ } END { print count + 0 }' /var/lib/docker/unraid-autostart) || return $?
130
+ test "$AUTOSTART_COUNT" -eq "$AUTOSTART_EXPECTED_COUNT" || return $?
85
131
  )
86
132
  }
87
- butler_autostart_counts() {
133
+ set_butler_autostart() {
88
134
  (
135
+ AUTOSTART_PROFILE=$1
136
+ case "$AUTOSTART_PROFILE" in disabled|production) ;; *) return 1 ;; esac
137
+ test "$(id -u)" -eq 0 || return $?
89
138
  AUTOSTART_FILE=/var/lib/docker/unraid-autostart
90
- test -f "$AUTOSTART_FILE" || return $?
91
- awk '
92
- $1 == "ouro-butler" { production++ }
93
- $1 == "ouro-butler-staging" { staging++ }
94
- $1 == "ouro-butler-rollback" { rollback++ }
95
- $1 == "ouro-butler-legacy-evidence" { legacy++ }
96
- END { printf "%d %d %d %d", production + 0, staging + 0, rollback + 0, legacy + 0 }
97
- ' "$AUTOSTART_FILE"
139
+ AUTOSTART_INCLUDE=/usr/local/emhttp/plugins/dynamix.docker.manager/include/UpdateConfig.php
140
+ test -f "$AUTOSTART_FILE" && test ! -L "$AUTOSTART_FILE" || return 1
141
+ test "$(stat -c '%u:%g:%a' "$AUTOSTART_FILE")" = 0:0:644 || return $?
142
+ test -f "$AUTOSTART_INCLUDE" && test ! -L "$AUTOSTART_INCLUDE" || return 1
143
+ test "$(stat -c '%u:%g:%a' "$AUTOSTART_INCLUDE")" = 0:0:644 || return $?
144
+ AUTOSTART_COUNTS=$(butler_autostart_counts) || return $?
145
+ set -- $AUTOSTART_COUNTS
146
+ test "$#" -eq 4 || return $?
147
+ for AUTOSTART_COUNT in "$@"; do case "$AUTOSTART_COUNT" in 0|1) ;; *) return 1 ;; esac; done
148
+ if test "$AUTOSTART_PROFILE" = production; then
149
+ docker container inspect ouro-butler >/dev/null 2>&1 || return $?
150
+ fi
151
+ AUTOSTART_BACKUP=$(mktemp /var/lib/docker/.unraid-autostart.ouro.XXXXXX) || return $?
152
+ AUTOSTART_NONBUTLER_BEFORE=$(mktemp /run/unraid-autostart.nonbutler-before.XXXXXX) || { rm -f -- "$AUTOSTART_BACKUP"; return 1; }
153
+ AUTOSTART_NONBUTLER_AFTER=$(mktemp /run/unraid-autostart.nonbutler-after.XXXXXX) || { rm -f -- "$AUTOSTART_BACKUP" "$AUTOSTART_NONBUTLER_BEFORE"; return 1; }
154
+ AUTOSTART_COMMITTED=no
155
+ AUTOSTART_BACKUP_READY=no
156
+ AUTOSTART_RESTORE_TMP=
157
+ autostart_cleanup() {
158
+ AUTOSTART_STATUS=$?
159
+ trap - EXIT HUP INT TERM
160
+ if test "$AUTOSTART_COMMITTED" = yes; then
161
+ rm -f -- "$AUTOSTART_BACKUP" || AUTOSTART_STATUS=1
162
+ elif test "$AUTOSTART_BACKUP_READY" = yes; then
163
+ AUTOSTART_RESTORE_TMP=$(mktemp /var/lib/docker/.unraid-autostart.restore.XXXXXX) || AUTOSTART_RESTORE_TMP=
164
+ if test -n "$AUTOSTART_RESTORE_TMP" \
165
+ && cp -p -- "$AUTOSTART_BACKUP" "$AUTOSTART_RESTORE_TMP" \
166
+ && sync -f "$AUTOSTART_RESTORE_TMP" \
167
+ && mv -f -- "$AUTOSTART_RESTORE_TMP" "$AUTOSTART_FILE" \
168
+ && sync -f "$AUTOSTART_FILE" \
169
+ && sync -f /var/lib/docker; then
170
+ rm -f -- "$AUTOSTART_BACKUP" || AUTOSTART_STATUS=1
171
+ else
172
+ test -z "$AUTOSTART_RESTORE_TMP" || rm -f -- "$AUTOSTART_RESTORE_TMP"
173
+ printf 'CRITICAL: Butler autostart rollback failed; recovery copy preserved at %s\n' "$AUTOSTART_BACKUP" >&2
174
+ AUTOSTART_STATUS=1
175
+ fi
176
+ else
177
+ rm -f -- "$AUTOSTART_BACKUP" || AUTOSTART_STATUS=1
178
+ fi
179
+ rm -f -- "$AUTOSTART_NONBUTLER_BEFORE" "$AUTOSTART_NONBUTLER_AFTER" || AUTOSTART_STATUS=1
180
+ exit "$AUTOSTART_STATUS"
181
+ }
182
+ trap autostart_cleanup EXIT
183
+ trap 'exit 129' HUP
184
+ trap 'exit 130' INT
185
+ trap 'exit 143' TERM
186
+ cp -p -- "$AUTOSTART_FILE" "$AUTOSTART_BACKUP" || return $?
187
+ cmp -s -- "$AUTOSTART_FILE" "$AUTOSTART_BACKUP" || return 1
188
+ sync -f "$AUTOSTART_BACKUP" || return $?
189
+ sync -f /var/lib/docker || return $?
190
+ AUTOSTART_BACKUP_READY=yes
191
+ snapshot_nonbutler_autostart >"$AUTOSTART_NONBUTLER_BEFORE" || return $?
192
+ mutate_butler_autostart ouro-butler-staging false || return $?
193
+ mutate_butler_autostart ouro-butler-rollback false || return $?
194
+ mutate_butler_autostart ouro-butler-legacy-evidence false || return $?
195
+ if test "$AUTOSTART_PROFILE" = production; then
196
+ mutate_butler_autostart ouro-butler true || return $?
197
+ AUTOSTART_EXPECTED_COUNTS="1 0 0 0"
198
+ else
199
+ mutate_butler_autostart ouro-butler false || return $?
200
+ AUTOSTART_EXPECTED_COUNTS="0 0 0 0"
201
+ fi
202
+ verify_butler_autostart "$AUTOSTART_EXPECTED_COUNTS" || return $?
203
+ snapshot_nonbutler_autostart >"$AUTOSTART_NONBUTLER_AFTER" || return $?
204
+ cmp -s -- "$AUTOSTART_NONBUTLER_BEFORE" "$AUTOSTART_NONBUTLER_AFTER" || return 1
205
+ sync -f "$AUTOSTART_FILE" || return $?
206
+ sync -f /var/lib/docker || return $?
207
+ AUTOSTART_COMMITTED=yes
98
208
  )
99
209
  }
100
210
  verify_butler_autostart() {
@@ -105,17 +215,11 @@ Effective-spec audit helper:
105
215
  )
106
216
  }
107
217
  disable_butler_autostart() {
108
- set_butler_autostart ouro-butler-staging false || return $?
109
- set_butler_autostart ouro-butler-rollback false || return $?
110
- set_butler_autostart ouro-butler-legacy-evidence false || return $?
111
- set_butler_autostart ouro-butler false || return $?
218
+ set_butler_autostart disabled || return $?
112
219
  verify_butler_autostart "0 0 0 0" || return $?
113
220
  }
114
221
  enable_butler_autostart() {
115
- set_butler_autostart ouro-butler-staging false || return $?
116
- set_butler_autostart ouro-butler-rollback false || return $?
117
- set_butler_autostart ouro-butler-legacy-evidence false || return $?
118
- set_butler_autostart ouro-butler true || return $?
222
+ set_butler_autostart production || return $?
119
223
  verify_butler_autostart "1 0 0 0" || return $?
120
224
  }
121
225
  This bounded wait accepts only Docker's healthy state. The image healthcheck
@@ -141,7 +245,8 @@ Effective-spec audit helper:
141
245
  return 1
142
246
  }
143
247
  Define these fail-closed image and topology helpers in the same shell. They use
144
- exact Docker image IDs and exact container names; no mutation is performed:
248
+ exact Docker image IDs and container identity, image provenance, and writable
249
+ root overlap; no mutation is performed:
145
250
  validate_exact_image_id() {
146
251
  VALIDATE_IMAGE_ID=$1
147
252
  case "$VALIDATE_IMAGE_ID" in
@@ -155,18 +260,43 @@ Effective-spec audit helper:
155
260
  docker image inspect "$VALIDATE_IMAGE_ID" >/dev/null || return $?
156
261
  }
157
262
  assert_only_running_butler() {
263
+ (
158
264
  EXPECTED_RUNNING_BUTLER=$1
159
- RUNNING_BUTLER_NAMES=$(docker container ls --format '{{.Names}}') || return $?
160
- RUNNING_BUTLER_COUNTS=$(printf '%s\n' "$RUNNING_BUTLER_NAMES" | awk -v expected="$EXPECTED_RUNNING_BUTLER" '
161
- {
162
- if ($0 == "ouro-butler" || $0 == "ouro-butler-staging" || $0 == "ouro-butler-rollback" || $0 == "ouro-butler-legacy-evidence") {
163
- butlers++
164
- if ($0 == expected) matching++
265
+ case "$EXPECTED_RUNNING_BUTLER" in
266
+ -) EXPECTED_RUNNING_BUTLER_COUNT=0 ;;
267
+ ouro-butler|ouro-butler-staging) EXPECTED_RUNNING_BUTLER_COUNT=1 ;;
268
+ *) return 1 ;;
269
+ esac
270
+ RUNNING_CONTAINER_IDS=$(docker container ls -q) || return $?
271
+ MATCHING_RUNNING_BUTLERS=0
272
+ for RUNNING_CONTAINER_ID in $RUNNING_CONTAINER_IDS; do
273
+ RUNNING_CONTAINER_NAME=$(docker container inspect --format '{{.Name}}' "$RUNNING_CONTAINER_ID") || return $?
274
+ RUNNING_CONTAINER_NAME=${RUNNING_CONTAINER_NAME#/}
275
+ if test "$RUNNING_CONTAINER_NAME" = "$EXPECTED_RUNNING_BUTLER"; then
276
+ MATCHING_RUNNING_BUTLERS=$(( MATCHING_RUNNING_BUTLERS + 1 ))
277
+ continue
278
+ fi
279
+ RUNNING_CONTAINER_IMAGE_ID=$(docker container inspect --format '{{.Image}}' "$RUNNING_CONTAINER_ID") || return $?
280
+ RUNNING_CONTAINER_IMAGE_REF=$(docker container inspect --format '{{.Config.Image}}' "$RUNNING_CONTAINER_ID") || return $?
281
+ RUNNING_CONTAINER_IMAGE_SOURCE=$(docker image inspect --format '{{with .Config.Labels}}{{index . "org.opencontainers.image.source"}}{{end}}' "$RUNNING_CONTAINER_IMAGE_ID") || return $?
282
+ test "$RUNNING_CONTAINER_IMAGE_SOURCE" != https://github.com/ourostack/ouroboros || return 1
283
+ test "$RUNNING_CONTAINER_IMAGE_ID" != sha256:681449ad47a2621705cd339b481e6339236b31dc65e195b1cf5025d0f2191d7d || return 1
284
+ case "$RUNNING_CONTAINER_IMAGE_REF" in
285
+ ouro-butler|ouro-butler:*|ouro-butler@sha256:*|ouroboros-butler|ouroboros-butler:*|ouroboros-butler@sha256:*|ghcr.io/ourostack/ouroboros-butler|ghcr.io/ourostack/ouroboros-butler:*|ghcr.io/ourostack/ouroboros-butler@sha256:*) return 1 ;;
286
+ esac
287
+ RUNNING_WRITABLE_MOUNT_SOURCES=$(docker container inspect --format '{{range .Mounts}}{{if .RW}}{{println .Source}}{{end}}{{end}}' "$RUNNING_CONTAINER_ID") || return $?
288
+ if printf '%s\n' "$RUNNING_WRITABLE_MOUNT_SOURCES" | awk '
289
+ function overlaps(path, root) {
290
+ return length(path) > 0 && (path == root || index(path, root "/") == 1 || index(root, path "/") == 1)
165
291
  }
166
- }
167
- END { printf "%d %d", butlers + 0, matching + 0 }
168
- ') || return $?
169
- test "$RUNNING_BUTLER_COUNTS" = "1 1" || return $?
292
+ overlaps($0, "/mnt/user/appdata/ouro-butler/runtime") || overlaps($0, "/mnt/user/appdata/ouro-butler/runtime/.ouro-cli") || overlaps($0, "/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro") || overlaps($0, "/mnt/user/appdata/ouro-butler/AgentBundles") { found = 1 }
293
+ END { exit found ? 0 : 1 }
294
+ '; then
295
+ return 1
296
+ fi
297
+ done
298
+ test "$MATCHING_RUNNING_BUTLERS" -eq "$EXPECTED_RUNNING_BUTLER_COUNT" || return $?
299
+ )
170
300
  }
171
301
  assert_update_topology() {
172
302
  EXPECTED_ROLLBACK_IMAGE_ID=$1
@@ -786,6 +916,7 @@ Effective-spec audit helper:
786
916
  && CURRENT_LEGACY_EVIDENCE_IMAGE_ID=$(docker inspect --format '{{.Image}}' ouro-butler-legacy-evidence) \
787
917
  && test "$CURRENT_LEGACY_EVIDENCE_IMAGE_ID" = "$LEGACY_STAGING_IMAGE_ID" \
788
918
  && test "$(docker inspect --format '{{.State.Running}}' ouro-butler-legacy-evidence)" = false \
919
+ && assert_only_running_butler - \
789
920
  && docker create --name ouro-butler-staging --network host --restart unless-stopped --user 10001:10001 \
790
921
  --mount "type=bind,src=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli,dst=/home/ouro/.ouro-cli" \
791
922
  --mount "type=bind,src=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
@@ -799,6 +930,7 @@ Effective-spec audit helper:
799
930
  && docker stop ouro-butler-staging \
800
931
  && test "$(docker inspect --format '{{.Image}}' ouro-butler-staging)" = "$IMAGE_ID" \
801
932
  && docker rm ouro-butler-staging \
933
+ && assert_only_running_butler - \
802
934
  && docker create --name ouro-butler --network host --restart unless-stopped --user 10001:10001 \
803
935
  --mount "type=bind,src=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli,dst=/home/ouro/.ouro-cli" \
804
936
  --mount "type=bind,src=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
@@ -1362,6 +1494,7 @@ Update:
1362
1494
  cmp -s "$EVENT_SCRIPT_STAGE/install-usenet-guard.sh" /boot/config/custom/ouro-events/install-usenet-guard.sh
1363
1495
  test "$(grep -Fxc '/bin/bash /boot/config/custom/ouro-events/install-usenet-guard.sh --boot --install-root /boot/config/custom' /boot/config/go)" = 1
1364
1496
  test "$(grep -Fxc '/boot/config/custom/ouro-events/bootstrap-spool.sh --mount' /boot/config/go || true)" = 0
1497
+ test "$(grep -Fxc '(crontab -l 2>/dev/null | grep -v usenet_health; echo "*/15 * * * * /bin/bash /boot/config/custom/usenet_health.sh") | crontab -' /boot/config/go || true)" = 0
1365
1498
  INSTALLED_GUARD_CRON=$(crontab -l)
1366
1499
  test "$(printf '%s\n' "$INSTALLED_GUARD_CRON" | grep -Fxc '*/15 * * * * /bin/bash /boot/config/custom/usenet_health.sh # ouro:usenet-health')" = 1
1367
1500
  INSTALLED_LEGACY_GUARD_COUNT=$(printf '%s\n' "$INSTALLED_GUARD_CRON" | grep -Fxc '*/15 * * * * /bin/bash /boot/config/custom/usenet_health.sh' || true)
@@ -1511,6 +1644,7 @@ ouro-butler-rollback
1511
1644
  && remove_stopped_rollback_if_present "$ROLLBACK_IMAGE_ID" \
1512
1645
  && docker rename ouro-butler ouro-butler-rollback \
1513
1646
  && test "$(docker inspect --format '{{.State.Running}}' ouro-butler-rollback)" = false \
1647
+ && assert_only_running_butler - \
1514
1648
  && migrate_sanctuary_package_managed_bundle "$IMAGE_ID" migrate "$ROLLBACK_IMAGE_ID"; then
1515
1649
  :
1516
1650
  else
@@ -1566,7 +1700,8 @@ ouro-butler-rollback
1566
1700
  && assert_only_running_butler ouro-butler-staging \
1567
1701
  && wait_butler_ready ouro-butler-staging \
1568
1702
  && docker stop ouro-butler-staging \
1569
- && docker rm ouro-butler-staging; then
1703
+ && docker rm ouro-butler-staging \
1704
+ && assert_only_running_butler -; then
1570
1705
  :
1571
1706
  else
1572
1707
  STAGING_ACTIVATION_STATUS=$?
@@ -49,6 +49,7 @@ LEGACY_BOOT_HOOK="/boot/config/custom/ouro-events/bootstrap-spool.sh --mount"
49
49
  LEGACY_BASH_BOOT_HOOK="/bin/bash /boot/config/custom/ouro-events/bootstrap-spool.sh --mount"
50
50
  CANONICAL_PREVIOUS_BOOT_HOOK="/bin/bash /boot/config/custom/ouro-events/install-usenet-guard.sh --boot"
51
51
  CANONICAL_BOOT_HOOK="$CANONICAL_PREVIOUS_BOOT_HOOK --install-root /boot/config/custom"
52
+ LEGACY_CRON_BOOT_HOOK='(crontab -l 2>/dev/null | grep -v usenet_health; echo "*/15 * * * * /bin/bash /boot/config/custom/usenet_health.sh") | crontab -'
52
53
  printf -v HEALTH_SCRIPT_Q '%q' "$INSTALL_ROOT/usenet_health.sh"
53
54
  CRON_LINE="*/15 * * * * /bin/bash $HEALTH_SCRIPT_Q # ouro:usenet-health"
54
55
  LEGACY_CRON_LINE="*/15 * * * * /bin/bash $HEALTH_SCRIPT_Q"
@@ -125,7 +126,7 @@ render_inactive_cron() {
125
126
 
126
127
  is_canonical_butler_go_hook() {
127
128
  case "$1" in
128
- "$LEGACY_BOOT_HOOK"|"$LEGACY_BASH_BOOT_HOOK"|"$CANONICAL_PREVIOUS_BOOT_HOOK"|"$CANONICAL_BOOT_HOOK") return 0 ;;
129
+ "$LEGACY_BOOT_HOOK"|"$LEGACY_BASH_BOOT_HOOK"|"$CANONICAL_PREVIOUS_BOOT_HOOK"|"$CANONICAL_BOOT_HOOK"|"$PREVIOUS_INSTALLER_BOOT_HOOK"|"$BOOT_HOOK"|"$LEGACY_CRON_BOOT_HOOK") return 0 ;;
129
130
  *) return 1 ;;
130
131
  esac
131
132
  }
@@ -232,7 +233,7 @@ install_transaction() {
232
233
  printf '#!/bin/bash\n' > "$stage/go.base"
233
234
  fi
234
235
  go_candidate="$stage/go.candidate"
235
- grep -Fvx -e "$LEGACY_BOOT_HOOK" -e "$LEGACY_BASH_BOOT_HOOK" -e "$PREVIOUS_INSTALLER_BOOT_HOOK" -e "$BOOT_HOOK" "$stage/go.base" > "$stage/go.filtered" || true
236
+ render_go_without_owned "$stage/go.base" "$stage/go.filtered"
236
237
  if [ -s "$stage/go.filtered" ]; then
237
238
  awk -v hook="$BOOT_HOOK" 'NR == 1 && /^#!/ { print; print hook; next } NR == 1 { print hook } { print }' "$stage/go.filtered" > "$go_candidate"
238
239
  else
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.747",
2
+ "runtimeVersion": "0.1.0-alpha.749",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-08-30T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>Mendelow Cloud Butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.747</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.749</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.747",
3
+ "version": "0.1.0-alpha.749",
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.747",
9
+ "version": "0.1.0-alpha.749",
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.747",
3
+ "version": "0.1.0-alpha.749",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },