#!/bin/sh # Copyright (C) 2026 Doan Luu Duc Tai # # 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 . set -eu umask 077 ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) command -v bwrap >/dev/null || { echo 'Bootstrap 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/pack/gnu/store/00000000000000000000000000000000-tool/bin" \ "$STAGE/pack/gnu/store/11111111111111111111111111111111-profile/bin" "$STAGE/pack/etc" cp "$ROOT/infra/guix/bootstrap.sh" "$STAGE/capsule/bootstrap" printf '%s\n' '#!/bin/sh' 'printf "%s\n" canonical-tool' > "$STAGE/pack/gnu/store/00000000000000000000000000000000-tool/bin/tool" chmod 755 "$STAGE/pack/gnu/store/00000000000000000000000000000000-tool/bin/tool" ln -s /gnu/store/00000000000000000000000000000000-tool/bin/tool "$STAGE/pack/gnu/store/11111111111111111111111111111111-profile/bin/tool" ln -s /gnu/store/11111111111111111111111111111111-profile/bin "$STAGE/pack/bin" printf '%s\n' 'Must stay inside pack.' > "$STAGE/pack/etc/pack-sentinel" tar -czf "$STAGE/capsule/runtime.tar.gz" -C "$STAGE/pack" . (cd "$STAGE/capsule" && sha256sum bootstrap runtime.tar.gz > SHA256SUMS) cp -a "$STAGE/capsule" "$STAGE/tampered" printf '%s\n' 'tamper' >> "$STAGE/tampered/runtime.tar.gz" cp -a "$STAGE/capsule" "$STAGE/next" mkdir "$STAGE/pack/gnu/store/22222222222222222222222222222222-next" printf '%s\n' 'Second closure.' > "$STAGE/pack/gnu/store/22222222222222222222222222222222-next/value" tar -czf "$STAGE/next/runtime.tar.gz" -C "$STAGE/pack" . (cd "$STAGE/next" && sha256sum bootstrap runtime.tar.gz > 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" /capsule --ro-bind "$STAGE/next" /next \ --ro-bind "$STAGE/tampered" /tampered --proc /proc --dev /dev --tmpfs /tmp \ --chdir / /bin/sh -eu <<'TEST' fail() { printf '%s\n' "$1" >&2; exit 1; } reject() { if sh "$1/bootstrap"; then fail "$2"; fi; } TOOL=/gnu/store/00000000000000000000000000000000-tool/bin/tool PROFILE=/gnu/store/11111111111111111111111111111111-profile mkdir /etc printf '%s\n' 'Host configuration.' > /etc/host-sentinel [ ! -e /gnu ] && [ ! -e /var/guix ] if sh /capsule/bootstrap extra; then fail 'Accepted bootstrap arguments.'; fi reject /tampered 'Accepted altered runtime.' [ ! -e /gnu ] sh /capsule/bootstrap > /tmp/bootstrap-first.log & FIRST=$! sh /capsule/bootstrap wait "$FIRST" DIGEST=$(sha256sum /capsule/runtime.tar.gz) DIGEST=${DIGEST%% *} [ "$(cat "/gnu/.stack-runtimes/$DIGEST")" = "$DIGEST" ] [ -z "$(find /gnu/store -mindepth 1 ! -user 0 -print -quit)" ] [ -z "$(find /gnu/store -mindepth 1 ! -type l -perm /222 -print -quit)" ] [ "$("$PROFILE/bin/tool")" = canonical-tool ] [ "$(readlink "$PROFILE/bin/tool")" = "$TOOL" ] [ "$(readlink /bin)" = usr/bin ] [ "$(cat /etc/host-sentinel)" = 'Host configuration.' ] [ ! -e /etc/pack-sentinel ] INODE=$(stat -c %i "/gnu/.stack-runtimes/$DIGEST") sh /capsule/bootstrap [ "$(stat -c %i "/gnu/.stack-runtimes/$DIGEST")" = "$INODE" ] rm "/gnu/.stack-runtimes/$DIGEST" chmod -R u+w "$PROFILE" rm -rf "$PROFILE" sh /capsule/bootstrap [ "$("$PROFILE/bin/tool")" = canonical-tool ] [ -f "/gnu/.stack-runtimes/$DIGEST" ] [ -z "$(find /gnu -maxdepth 1 -name '.stack-stage.*' -print -quit)" ] sh /next/bootstrap [ "$("$PROFILE/bin/tool")" = canonical-tool ] [ "$(cat /gnu/store/22222222222222222222222222222222-next/value)" = 'Second closure.' ] [ -f "/gnu/.stack-runtimes/$DIGEST" ] rm "/gnu/.stack-runtimes/$DIGEST" chmod u+w "$TOOL" printf '%s\n' 'conflicting content' >> "$TOOL" chmod a-w "$TOOL" reject /capsule 'Overwrote conflicting store entry.' [ ! -e "/gnu/.stack-runtimes/$DIGEST" ] [ -z "$(find /gnu -maxdepth 1 -name '.stack-stage.*' -print -quit)" ] tail -n 1 "$TOOL" | grep -Fx 'conflicting content' chmod -R u+w /gnu rm -rf /gnu mkdir -p /var/guix reject /capsule 'Accepted managed Guix installation.' [ ! -e /gnu ] rm -rf /var/guix mkdir -p /gnu/store printf '%s\n' 'Foreign store.' > /gnu/store/sentinel reject /capsule 'Accepted foreign store.' [ "$(cat /gnu/store/sentinel)" = 'Foreign store.' ] rm -rf /gnu mkdir /outside ln -s /outside /gnu reject /capsule 'Accepted symlink /gnu.' [ -z "$(find /outside -mindepth 1 -print -quit)" ] rm /gnu mkdir -p /gnu/.stack-runtimes chmod 755 /gnu /gnu/.stack-runtimes ln -s /outside /gnu/store reject /capsule 'Accepted symlink store.' [ -z "$(find /outside -mindepth 1 -print -quit)" ] rm /gnu/store chmod 777 /gnu/.stack-runtimes reject /capsule 'Accepted writable ownership metadata.' [ ! -e /gnu/store ] printf '%s\n' 'Canonical bootstrap, retry, idempotency, preservation and refusal checks passed.' TEST