@ouro.bot/cli 0.1.0-alpha.748 → 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,13 @@
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
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.748",
6
13
  "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
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.748",
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.748</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.748",
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.748",
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.748",
3
+ "version": "0.1.0-alpha.749",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },