Files
omarchy/test/shell.d/factory-reset-accounts-test.sh
T
omarchybotandClaude Opus 5 a00be8fa16 Remove the subuid and subgid backups during the factory scrub
userdel rewrites /etc/subuid and /etc/subgid, and like every shadow-utils database write it leaves the previous contents behind in a dash-suffixed backup. The scrub removed four of the six backups those tools produce, so the retained @factory baseline still named the previous owner in /etc/subuid- and /etc/subgid- along with their subordinate ID range.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 05:37:10 -07:00

149 lines
5.3 KiB
Bash

#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
if (( EUID != 0 )); then
if unshare --user --map-root-user true 2>/dev/null; then
exec unshare --user --map-root-user bash "$0"
fi
pass "no unprivileged user namespace; skipping factory account cleanup"
exit 0
fi
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
# Load the production functions without self-elevation or the reset entrypoint.
awk '
/^[a-z_]+\(\) \{/ { copying = 1 }
copying { print }
/^}/ { copying = 0 }
' "$ROOT/bin/omarchy-system-factory-reset" >"$test_tmp/functions"
cat >"$test_tmp/reset" <<'SH'
#!/bin/bash
set -euo pipefail
source "$1/functions"
TOP_MNT="$2"
NEXT_NAME=@omarchy-reset-next
PROVISIONING_DIR=/var/lib/omarchy/provisioning
LOG_FILE="$TOP_MNT/reset.log"
log() { printf '%s\n' "$1" >>"$LOG_FILE"; }
fail() { log "$1"; exit 1; }
# Account tools are real. Only snapshots, boot rebuilding, and system services
# are replaced: all writes stay inside this test's disposable directory.
btrfs() {
if [[ $1 == "subvolume" && $2 == "snapshot" ]]; then
mkdir -p "$4"
cp -a "$3/." "$4/"
elif [[ $1 == "property" ]]; then
printf '%s\n' "$6" >"$4/read-only"
else
return 1
fi
}
systemd-id128() { printf '%032d\n' 1; }
install_provisioning_units() { :; }
encrypted_install() { return 1; }
rebuild_next_boot() { touch "$TOP_MNT/rebuilt"; }
sync() { :; }
userdel() {
[[ ${FAIL_COMMAND:-} == "userdel" && $2 == "$FAIL_ROOT" ]] && return 42
command userdel "$@"
}
usermod() {
[[ ${FAIL_COMMAND:-} == "usermod" && $2 == "$FAIL_ROOT" ]] && return 42
command usermod "$@"
}
rm() {
[[ ${FAIL_COMMAND:-} == "rm" && $* == *"$FAIL_ROOT/etc/shadow-"* ]] && return 42
command rm "$@"
}
stage_full_reset
SH
make_fixture() {
local top="$1" root_hash="${2:-original-root-hash}"
local factory="$top/@factory"
mkdir -p "$top/@" "$factory/etc" "$factory/home/seller" \
"$factory/usr/bin" "$factory/usr/share/omarchy/install/provisioning" \
"$factory/var/lib/omarchy/provisioning/packages"
touch "$top/@/old-system" "$factory/home/seller/private-file" \
"$factory/usr/share/omarchy/install/provisioning/omarchy-provision-owner.service" \
"$factory/var/lib/omarchy/provisioning/packages/node-v0.tar.gz"
printf '#!/bin/bash\n' >"$factory/usr/bin/omarchy-provision-owner"
chmod +x "$factory/usr/bin/omarchy-provision-owner"
printf 'true\n' >"$factory/read-only"
cat >"$factory/etc/passwd" <<'EOF'
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/:/usr/bin/nologin
seller:x:1000:1000:Seller:/home/seller:/bin/bash
EOF
printf 'root:%s:20000:0:99999:7:::\ndaemon:*:20000:0:99999:7:::\nseller:original-user-hash:20000:0:99999:7:::\n' \
"$root_hash" >"$factory/etc/shadow"
printf 'root:x:0:\ndaemon:x:1:\nseller:x:1000:\nwheel:x:998:seller\n' >"$factory/etc/group"
printf 'root:!::\ndaemon:!::\nseller:!::\nwheel:!::seller\n' >"$factory/etc/gshadow"
printf 'USERGROUPS_ENAB yes\n' >"$factory/etc/login.defs"
printf 'seller:100000:65536\n' >"$factory/etc/subuid"
printf 'seller:100000:65536\n' >"$factory/etc/subgid"
chmod 600 "$factory/etc/"{shadow,gshadow}
for file in passwd shadow group gshadow subuid subgid; do
cp "$factory/etc/$file" "$factory/etc/$file-"
done
}
assert_scrubbed() {
local root="$1" file
[[ $(awk -F: '$1 == "root" { print $2 }' "$root/etc/shadow") == "!" ]] ||
fail "reset erases the root hash while keeping the account locked"
! grep -q 'original-.*-hash\|seller' "$root/etc/"{passwd,shadow,group,gshadow} ||
fail "reset removes seller account credentials and group membership"
[[ ! -e $root/home/seller ]] || fail "reset removes the seller's baseline home"
grep -q '^daemon:\*:' "$root/etc/shadow" || fail "reset preserves service accounts"
[[ $(stat -c '%a' "$root/etc/shadow") == "600" ]] || fail "shadow stays private"
for file in passwd shadow group gshadow subuid subgid; do
[[ ! -e $root/etc/$file- ]] || fail "reset removes the $file backup"
done
}
for scenario in normal locked; do
top="$test_tmp/$scenario"
if [[ $scenario == "locked" ]]; then
make_fixture "$top" '!'
else
make_fixture "$top"
fi
bash "$test_tmp/reset" "$test_tmp" "$top" || fail "$scenario reset stages successfully"
assert_scrubbed "$top/@factory"
assert_scrubbed "$top/@"
[[ $(cat "$top/@factory/read-only") == "true" ]] || fail "baseline returns to read-only"
[[ -f $top/@/var/lib/omarchy/provisioning/pending && -f $top/rebuilt ]] ||
fail "reset reaches provisioning after cleanup"
bash "$test_tmp/reset" "$test_tmp" "$top" || fail "$scenario reset can be repeated"
assert_scrubbed "$top/@factory"
assert_scrubbed "$top/@"
pass "$scenario reset scrubs both roots, preserves service accounts, and can be repeated"
done
for target in @omarchy-reset-next @factory; do
for command in userdel usermod rm; do
top="$test_tmp/fail-$target-$command"
make_fixture "$top"
if FAIL_COMMAND="$command" FAIL_ROOT="$top/$target" bash "$test_tmp/reset" "$test_tmp" "$top"; then
fail "reset accepted failed $command in $target"
fi
[[ -f $top/@/old-system && ! -e $top/rebuilt ]] ||
fail "failed cleanup must not activate or rebuild the reset system"
[[ $(cat "$top/@factory/read-only") == "true" ]] ||
fail "failed cleanup must leave the baseline read-only"
pass "failed $command in $target aborts reset before activation"
done
done