#!/usr/bin/env sh # No Docker daemon: verify actual restore transport, guards and exit propagation. set -eu wrapper="$(pwd)/deploy/docker/restore.sh" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT HUP INT TERM mkdir "$tmp/bin" backup="$tmp/input backup.db" printf 'synthetic backup' >"$backup" cat >"$tmp/bin/docker" <<'DOCKER' #!/usr/bin/env sh set -eu printf '%s\n' "$*" >>"$FAKE_LOG" case "$1" in volume) [ "$2" = inspect ]; exit 0 ;; ps) count=0; [ ! -f "$FAKE_COUNT" ] || count="$(cat "$FAKE_COUNT")" count=$((count + 1)); printf '%s' "$count" >"$FAKE_COUNT" if [ "$FAKE_CASE" = ps-initial-failure ] || { [ "$FAKE_CASE" = ps-later-failure ] && [ "$count" = 2 ]; }; then exit 29; fi if [ "$FAKE_CASE" = live-initial ] || { [ "$FAKE_CASE" = live-later ] && [ "$count" = 2 ]; }; then printf 'other-writer\n'; fi ;; create) printf 'helper-id\n' ;; cp) [ "$2" = "$FAKE_BACKUP" ]; [ "$FAKE_CASE" != cp-failure ] || exit 17 ;; start) [ "$FAKE_CASE" != start-failure ] || exit 19 ;; wait) if [ "$FAKE_CASE" = restore-failure ]; then printf '23\n'; else printf '0\n'; fi ;; rm) exit 0 ;; *) echo 'Unexpected Docker operation in restore' >&2; exit 98 ;; esac DOCKER chmod +x "$tmp/bin/docker" for scenario in success cp-failure start-failure restore-failure live-initial live-later ps-initial-failure ps-later-failure; do log="$tmp/$scenario.log" if PATH="$tmp/bin:$PATH" FAKE_LOG="$log" FAKE_COUNT="$tmp/$scenario.count" FAKE_CASE="$scenario" FAKE_BACKUP="$backup" sh "$wrapper" image:immutable target-named-volume "$backup"; then actual=0; else actual=$?; fi case "$scenario" in success) expected=0;; cp-failure) expected=17;; start-failure) expected=19;; restore-failure) expected=23;; ps-*-failure) expected=29;; *) expected=1;; esac [ "$actual" = "$expected" ] || { echo "Unexpected status for $scenario: $actual" >&2; exit 1; } grep -F 'volume inspect target-named-volume' "$log" >/dev/null if grep -E -- '(type=bind|--privileged|--network host|docker.sock|^volume rm)' "$log" >/dev/null; then echo 'Restore must not use a host bind or remove the target volume' >&2; exit 1; fi if [ "$scenario" = live-initial ] || [ "$scenario" = ps-initial-failure ]; then if grep -E '^(create|cp|start) ' "$log" >/dev/null; then echo 'Active target must fail before helper creation' >&2; exit 1; fi continue fi grep -E '^create --name evofw-restore-[^ ]+ --network none --cap-drop ALL --security-opt no-new-privileges:true --mount type=volume,src=target-named-volume,dst=/data --entrypoint node image:immutable tools/sqlite-backup.mjs restore /tmp/evofw-restore-input.db /data/app.db --offline$' "$log" >/dev/null grep -E '^cp .*input backup.db evofw-restore-[^ ]+:/tmp/evofw-restore-input.db$' "$log" >/dev/null grep -E '^rm -fv evofw-restore-' "$log" >/dev/null if [ "$scenario" = cp-failure ] || [ "$scenario" = live-later ] || [ "$scenario" = ps-later-failure ]; then if grep -E '^start ' "$log" >/dev/null; then echo 'Incomplete input or newly active target must never start restore' >&2; exit 1; fi elif [ "$scenario" != start-failure ]; then grep -E '^wait evofw-restore-' "$log" >/dev/null [ "$(cat "$tmp/$scenario.count")" = 2 ] fi done echo 'Restore wrapper regression passed (remote copy, offline guards, failures, cleanup)'