diff options
Diffstat (limited to 'infra/guix/test-recovery.sh')
| -rw-r--r-- | infra/guix/test-recovery.sh | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/infra/guix/test-recovery.sh b/infra/guix/test-recovery.sh new file mode 100644 index 0000000..c03ed2f --- /dev/null +++ b/infra/guix/test-recovery.sh @@ -0,0 +1,133 @@ +#!/bin/sh +# Copyright (C) 2026 Doan Luu Duc Tai <doan@luuduct.ai> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -eu +umask 077 +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +command -v bwrap >/dev/null || { echo 'Recovery tests require bubblewrap.' >&2; exit 1; } +STAGE=$(mktemp -d) +trap 'chmod -R u+w "$STAGE"; rm -rf -- "$STAGE"' EXIT HUP INT TERM +mkdir -p "$STAGE/capsule" "$STAGE/runtime/bin" "$STAGE/test-bin" +cp "$ROOT/infra/guix/restore.sh" "$STAGE/capsule/restore" +cp "$ROOT/infra/guix/run.sh" "$STAGE/capsule/run" +cp "$ROOT/infra/guix/containers.conf" "$STAGE/capsule/containers.conf" +printf '%s\n' 'opaque encrypted backup fixture' > "$STAGE/capsule/backup.tar.gz.gpg" +printf '%s\n' 'opaque release fixture' > "$STAGE/capsule/release.tar.gz" +printf '%s\n' 80 > "$STAGE/lowport" +printf '%s\n' '#!/bin/sh' 'exit 0' > "$STAGE/runtime/bin/gpg" +cat > "$STAGE/runtime/bin/podman" <<'PODMAN' +#!/bin/sh +set -eu +[ "$CONTAINERS_CONF" = /capsule/containers.conf ] +printf '%s\n' "$*" >> /tmp/podman.calls +PODMAN +cat > "$STAGE/test-bin/id" <<'ID' +#!/bin/sh +case "$1" in -u) echo 1000 ;; -un) echo service ;; *) exit 1 ;; esac +ID +for HELPER in newuidmap newgidmap; do + printf '%s\n' '#!/bin/sh' 'exit 0' > "$STAGE/test-bin/$HELPER" +done +cat > "$STAGE/capsule/stack" <<'STACK' +#!/bin/sh +set -eu +[ "$CONTAINERS_CONF" = /capsule/containers.conf ] +case "$(command -v podman)" in /tmp/*/runtime/bin/podman) ;; *) exit 97 ;; esac +case "$1" in + restore) + [ "$#" = 3 ] && [ "$2" = /capsule/backup.tar.gz.gpg ] + mkdir -p "$3/var/data" "$3/bin" + printf '%s\n' private-environment > "$3/.env" + printf '%s\n' encrypted-history > "$3/var/data/history" + printf '%s\n' images > "$3/var/images.tar" + printf '%s\n' image-lock > "$3/var/image-lock.json" + printf '%s\n' '#!/bin/sh' 'exit 99' > "$3/bin/stack" + chmod 700 "$3/bin/stack" + printf '%s\n' old-source > "$3/source" + echo restored >> /tmp/restorations + ;; + --root) + [ "$#" = 4 ] && [ "$3" = activate ] && [ "$4" = /capsule/release.tar.gz ] + [ ! -e /proc/self/fd/9 ] + [ "$(cat "$4")" = 'opaque release fixture' ] + echo activated >> /tmp/activations + [ ! -f /tmp/fail-activation ] || exit 42 + mkdir -p "$2/infra/guix" + printf '%s\n' '#!/bin/sh' 'echo new-management' > "$2/bin/stack" + chmod 700 "$2/bin/stack" + printf '%s\n' new-source > "$2/source" + cp /capsule/run "$2/infra/guix/run.sh" + cp /capsule/run "$2/bin/run" + cp /capsule/containers.conf "$2/infra/guix/containers.conf" + ;; + *) exit 98 ;; +esac +STACK +chmod 700 "$STAGE/capsule/stack" "$STAGE/runtime/bin/"* "$STAGE/test-bin/"* +tar -czf "$STAGE/capsule/runtime.tar.gz" -C "$STAGE/runtime" . +(cd "$STAGE/capsule" && sha256sum backup.tar.gz.gpg release.tar.gz runtime.tar.gz restore run stack containers.conf > SHA256SUMS) +bwrap --unshare-user --uid 0 --gid 0 --unshare-pid --cap-add CAP_DAC_OVERRIDE \ + --tmpfs / --ro-bind /usr /usr \ + --symlink usr/bin /bin --symlink usr/lib /lib --symlink usr/lib /lib64 \ + --ro-bind "$STAGE/capsule" /fixture --ro-bind "$STAGE/test-bin" /test-bin \ + --proc /proc --dev /dev --tmpfs /tmp \ + --ro-bind "$STAGE/lowport" /proc/sys/net/ipv4/ip_unprivileged_port_start \ + --setenv PATH /test-bin:/usr/bin --setenv XDG_RUNTIME_DIR /tmp/xdg \ + --chdir / /bin/sh -eu <<'TEST' +fail() { printf '%s\n' "$1" >&2; exit 1; } +reject() { if sh /capsule/restore "$1"; then fail "$2"; fi; } +cp -a /fixture /capsule +mkdir -p /etc /gnu/.stack-runtimes /tmp/xdg +printf '%s\n' service:100000:65536 > /etc/subuid +cp /etc/subuid /etc/subgid +DIGEST=$(sha256sum /capsule/runtime.tar.gz) +DIGEST=${DIGEST%% *} +printf '%s\n' "$DIGEST" > "/gnu/.stack-runtimes/$DIGEST" +touch /tmp/fail-activation +STATUS=0 +sh /capsule/restore /recovered || STATUS=$? +[ "$STATUS" = 42 ] || fail 'Did not reach the paired release activation.' +[ -f /recovered/var/guix-recovery ] +[ "$(cat /recovered/.env)" = private-environment ] +[ "$(cat /recovered/var/data/history)" = encrypted-history ] +[ "$(cat /recovered/source)" = old-source ] +cp /capsule/backup.tar.gz.gpg /tmp/original-backup +echo changed >> /capsule/backup.tar.gz.gpg +(cd /capsule && sha256sum backup.tar.gz.gpg release.tar.gz runtime.tar.gz restore run stack containers.conf > SHA256SUMS) +reject /recovered 'Accepted a different backup for pending recovery.' +[ "$(wc -l < /tmp/restorations)" = 1 ] && [ "$(wc -l < /tmp/activations)" = 1 ] +cp /tmp/original-backup /capsule/backup.tar.gz.gpg +(cd /capsule && sha256sum backup.tar.gz.gpg release.tar.gz runtime.tar.gz restore run stack containers.conf > SHA256SUMS) +exec 9>/recovered/var/guix-recovery.lock +flock 9 +reject /recovered 'Accepted concurrent recovery.' 9>&- +exec 9>&- +[ "$(wc -l < /tmp/activations)" = 1 ] +rm /tmp/fail-activation +sh /capsule/restore /recovered +[ "$(wc -l < /tmp/restorations)" = 1 ] && [ "$(wc -l < /tmp/activations)" = 2 ] +[ ! -e /recovered/var/guix-recovery ] +[ "$(/recovered/bin/stack)" = new-management ] && [ "$(cat /recovered/source)" = new-source ] +cmp /capsule/run /recovered/infra/guix/run.sh +cmp /capsule/containers.conf /recovered/infra/guix/containers.conf +[ "$(cat /recovered/.env)" = private-environment ] +[ "$(cat /recovered/var/data/history)" = encrypted-history ] +mkdir /unrelated +reject /unrelated 'Accepted unrelated existing destination.' +reject /recovered 'Repeated a completed recovery.' +[ "$(wc -l < /tmp/restorations)" = 1 ] && [ "$(wc -l < /tmp/activations)" = 2 ] +printf '%s\n' 'Synthetic recovery orchestration, resume, intent, locks and preservation passed; no real encryption or container lifecycle proved.' +TEST |
