wren-linux / tools / test-install-iso.sh
#!/bin/bash
# Checks prep/make-install-iso.sh's pieces without a network, an ISO, xorriso or
# the Debian installer: picking the image from Debian's list, the installer's
# answers (nothing secret, the three questions left to you), the boot menu
# edits (on excerpts of Debian 13's own menu files), md5sum.txt, the three
# scripts that run inside the installer (skipping network setup without a
# cable, choosing the disk, copying Wren Linux), and the apt sources an install
# without a network is left with. Changes nothing outside a temp folder.
# Run by test.sh, or: bash tools/test-install-iso.sh
# shellcheck disable=SC2016 # the inner shells expand their own variables
HERE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
# shellcheck disable=SC1091
. "$HERE/lib.sh"
. "$HERE/lib/appimage.sh"
. "$HERE/lib/install-iso.sh"
set +e +o pipefail
T=$(mktemp -d) || { echo " FAIL couldn't make a temp folder (is /tmp full?)"; exit 1; }
trap 'chmod -R u+w "$T" 2>/dev/null; rm -rf "$T"' EXIT
fails=0
check() { # DESCRIPTION CONDITION-COMMAND...
local what=$1
shift
if "$@" >/dev/null 2>&1; then printf ' ok %s\n' "$what"; else printf ' FAIL %s\n' "$what"; fails=$((fails + 1)); fi
}
not() { ! "$@"; }
# ---- the image ----
h() { printf '%0128d' "$1"; }
printf '%s %s\n' "$(h 1)" debian-live-13.7.0-amd64-standard.iso.contents "$(h 2)" debian-live-13.7.0-amd64-standard.iso \
"$(h 3)" debian-live-13.7.0-amd64-gnome.iso "$(h 4)" debian-live-13.7.0-amd64-standard.log >"$T/SHA512SUMS"
check "the live standard image is picked from Debian's list" test "$(live_name "$T/SHA512SUMS")" = debian-live-13.7.0-amd64-standard.iso
printf '%s %s\n' "$(h 1)" 'debian-live-13.7.0-amd64-standard.iso;rm' "$(h 2)" '../debian-live-13.7.0-amd64-standard.iso' >"$T/BAD"
check "a list entry that isn't a live standard image name is ignored" test -z "$(live_name "$T/BAD")"
check "the live image is the only one (no DVD-1 left)" bash -c '! grep -q "dvd_name\|DEBIAN_CD_BASE\|DVD-1 installer" "$1/prep/make-install-iso.sh" "$1/lib/install-iso.sh"' _ "$HERE"
check "--live still works, doing nothing" grep -q '^ --live) shift ;;$' "$HERE/prep/make-install-iso.sh"
echo "wrenctl test image" >"$T/debian-live-13.7.0-amd64-standard.iso"
sum=$(sha512sum "$T/debian-live-13.7.0-amd64-standard.iso" | cut -d' ' -f1)
printf '%s %s\n' "$sum" debian-live-13.7.0-amd64-standard.iso >"$T/SUMS"
check "an image with the listed checksum passes" debian_cd_image_ok "$T/SUMS" "$T/debian-live-13.7.0-amd64-standard.iso" debian-live-13.7.0-amd64-standard.iso
# the download is checked while it is still NAME.part (a check by file name refused every download)
cp "$T/debian-live-13.7.0-amd64-standard.iso" "$T/debian-live-13.7.0-amd64-standard.iso.part"
check "...also while the download is still called NAME.part" debian_cd_image_ok "$T/SUMS" "$T/debian-live-13.7.0-amd64-standard.iso.part" debian-live-13.7.0-amd64-standard.iso
check "the script checks the download under the image's name" grep -q 'debian_cd_image_ok "$work/SHA512SUMS" "$iso.part" "$name"' "$HERE/prep/make-install-iso.sh"
echo changed >>"$T/debian-live-13.7.0-amd64-standard.iso"
check "a changed image is refused" not debian_cd_image_ok "$T/SUMS" "$T/debian-live-13.7.0-amd64-standard.iso" debian-live-13.7.0-amd64-standard.iso
check "an image the list doesn't name is refused" not debian_cd_image_ok "$T/SUMS" "$T/debian-live-13.7.0-amd64-standard.iso.part" debian-live-14.0.0-amd64-standard.iso
check "the Debian CD signing key is pinned" test "$DEBIAN_CD_FPR" = DF9B9C49EAA9298432589D76DA87E80D6294BE9B
check "what it needs (wget, gpg, git) is installed at the start if missing" \
bash -c 'grep -q "^need_tools .*gpg:gpg" "$1" && grep -q "^need_tools .*git:git" "$1"' _ "$HERE/prep/make-install-iso.sh"
check "age and diceware are part of the base install" \
bash -c '. "$1/packages.sh"; printf "%s\n" "${PKGS[@]}" | grep -qx age && printf "%s\n" "${PKGS[@]}" | grep -qx diceware' _ "$HERE"
check "xorriso isn't part of the base install" \
bash -c '. "$1/packages.sh"; ! printf "%s\n" "${PKGS[@]}" | grep -qx xorriso' _ "$HERE"
check "a missing xorriso is installed for this run, under a marker of its own" \
bash -c 'grep -q "^ WREN_OWNER=\$XORRISO_OWNER apt_install xorriso$" "$1" && ! grep -q "^need_tools .*xorriso" "$1"' _ "$HERE/prep/make-install-iso.sh"
# the clean-up, run on its own: it takes xorriso out only if this run put it in,
# and only xorriso's marker (not the one holding wget, gpg and git)
eval "$(sed -n '/^XORRISO_OWNER=/p; /^cleanup() {/,/^}/p' "$HERE/prep/make-install-iso.sh")"
apt_purge() { apt_marker >>"$T/purged"; }
work=$T/work; mkdir -p "$work"; installed_here=true
cleanup >/dev/null 2>&1
check "at the end, xorriso is removed again if this run installed it" grep -qx wren-make-install-iso-xorriso "$T/purged"
check "...and nothing else this script installed" test "$(wc -l <"$T/purged")" -eq 1
check "...and the temporary folder is gone" test ! -e "$T/work"
: >"$T/purged"; work=''; installed_here=false
cleanup >/dev/null 2>&1
check "an xorriso that was there before stays" test ! -s "$T/purged"
check "the clean-up runs whatever happens (on exit)" grep -qx 'trap cleanup EXIT' "$HERE/prep/make-install-iso.sh"
check "...and the marker is named for the script, so it comes out cleanly" \
test "$(cd "$HERE" && env -u WREN_OWNER bash -c '. ./lib.sh; apt_marker' "$HERE/prep/make-install-iso.sh")" = wren-make-install-iso
# ---- the offline Wi-Fi packages: refreshed before they get too old ----
O=$T/odir
inrelease() { # NAME DATE [VALID-UNTIL]: a saved list
mkdir -p "$O/offline/debs/verify/$1"
{ echo "Date: $2"; [ -z "${3:-}" ] || echo "Valid-Until: $3"; } >"$O/offline/debs/verify/$1/InRelease"
}
mkdir -p "$O/prep"
# The pretend fetch script writes down how it ran, and (unless FETCH_FAILS)
# leaves lists from "today" behind
cat >"$O/prep/fetch-offline-debs.sh" <<EOF
echo "fetch \$*" >>"$T/fetched"
[ -z "\${FETCH_FAILS:-}" ] || exit 1
mkdir -p "$O/offline/debs/verify/debian" && echo "Date: Mon, 02 Nov 2026 00:00:00 UTC" >"$O/offline/debs/verify/debian/InRelease"
EOF
refresh() { : >"$T/fetched"; (WREN_DIR=$O OFFLINE_DIR=$O/offline WREN_NOW=$(date -d '2026-11-02 12:00 UTC' +%s) offline_debs_refresh) >"$T/said" 2>&1; }
. "$HERE/lib/offline.sh"
inrelease debian 'Sat, 12 Sep 2026 07:55:41 UTC'
inrelease security 'Sat, 31 Oct 2026 22:02:52 UTC' 'Sat, 07 Nov 2026 22:02:52 UTC'
check "good until: the earliest of each list's Valid-Until and Date + 120 days" \
test "$(date -u -d "@$(offline_good_until "$O/offline/debs/verify")" +%F)" = 2026-11-07
inrelease security 'Sat, 31 Oct 2026 22:02:52 UTC' 'Sat, 21 Nov 2026 22:02:52 UTC'
: >"$O/offline/debs/Packages"
refresh
check "packages good for more than 5 days: left as they are" test ! -s "$T/fetched"
check "...and it says until when they work" grep -q 'work until 2026-11-21' "$T/said"
inrelease security 'Sat, 31 Oct 2026 22:02:52 UTC' 'Sat, 05 Nov 2026 22:02:52 UTC'
refresh
check "good for under 5 days: the signed lists are refreshed, the packages checked against them" test "$(cat "$T/fetched")" = "fetch --check"
rm -rf "$O/offline"; mkdir -p "$O/offline/debs"
refresh
check "none saved: fetched, with firmware for every card (the laptop may be another)" grep -qx 'fetch --all' "$T/fetched"
rm -rf "$O/offline"; mkdir -p "$O/offline/debs"; touch "$O/offline/debs/Packages" "$O/offline/debs/firmware-iwlwifi_1_all.deb" "$O/offline/debs/firmware-realtek_2_all.deb"
sed -i '1i [ "$1" != --check ] || { echo "fetch --check" >>"'"$T"'/fetched"; exit 1; }' "$O/prep/fetch-offline-debs.sh"
refresh
check "a saved package changed since (--check fails): the same firmware is fetched again" \
test "$(paste -sd, "$T/fetched")" = "fetch --check,fetch firmware-iwlwifi firmware-realtek"
rm -rf "$O/offline"; mkdir -p "$O/offline/debs"
FETCH_FAILS=1 refresh
check "no network to fetch them: the build goes on, saying the laptop needs a cable" grep -q 'needs a cable or a phone' "$T/said"
check "make-install-iso.sh does this before it puts Wren Linux on the image" \
bash -c 'grep -n "^ *offline_debs_refresh$\|^say \"What goes on it\"$" "$1" | cut -d: -f2 | sed "s/^ *//" | paste -sd, | grep -qx "offline_debs_refresh,say \"What goes on it\""' _ "$HERE/prep/make-install-iso.sh"
# ---- the installer's answers ----
P=$(fill_preseed "$HERE/data/install-iso-preseed.cfg" me de_DE.UTF-8 de true)
ans=$(grep -v '^[[:space:]]*#' <<<"$P")
check "every @value@ is filled in" not grep -q '@' <<<"$ans"
check "the user, locale and keyboard are the ones asked for" \
bash -c 'grep -qx "d-i passwd/username string me" <<<"$1" && grep -qx "d-i debian-installer/locale string de_DE.UTF-8" <<<"$1" && grep -qx "d-i keyboard-configuration/xkb-keymap select de" <<<"$1"' _ "$ans"
check "no disk passphrase is set: the installer asks you" not grep -q 'partman-crypto/passphrase' <<<"$ans"
check "no user password is set: the installer asks you" not grep -q 'passwd/user-password' <<<"$ans"
check "\"write the changes to disks?\" isn't answered: the installer asks you" not grep -qE 'partman/confirm |partman-crypto/confirm ' <<<"$ans"
check "no root password: root is locked, your user gets sudo" grep -qx 'd-i passwd/root-login boolean false' <<<"$ans"
check "the whole disk, encrypted LVM" bash -c 'grep -qx "d-i partman-auto/method string crypto" <<<"$1"' _ "$ans"
check "the disk is filled with random data first" grep -qx 'd-i partman-crypto/erase_disks boolean true' <<<"$ans"
check "...unless --no-erase" grep -qx 'd-i partman-crypto/erase_disks boolean false' <(fill_preseed "$HERE/data/install-iso-preseed.cfg" me en_US.UTF-8 us false)
check "--noencrypt: plain partitions, no encryption" grep -qx 'd-i partman-auto/method string regular' <(fill_preseed "$HERE/data/install-iso-preseed.cfg" me en_US.UTF-8 us true UTC regular)
check "...asked for, after a warning, and not the default" \
bash -c 'grep -q "^ --noencrypt) encrypt=false; shift ;;$" "$1" && grep -q "ask \"Build an image that installs without disk encryption?\" n" "$1"' _ "$HERE/prep/make-install-iso.sh"
check "--simplesway: the image carries the extras to tick" \
grep -q 'if \$simplesway; then echo one-workspace >"\$stage/wren-install/preselect"; fi' "$HERE/prep/make-install-iso.sh"
check "--autologin: the image carries the autologin rule for the user" \
grep -q 'if \$autologin; then autologin_conf "\$user" >"\$stage/wren-install/autologin.conf"; fi' "$HERE/prep/make-install-iso.sh"
check "the autologin rule logs that user in on tty1, with login -f" \
bash -c 'c=$(WREN_DIR=$1 bash -c ". \"\$WREN_DIR/lib/install-iso.sh\"; autologin_conf me"); grep -qx "ExecStart=" <<<"$c" && grep -qF -- "-o '"'"'-p -f -- \\\\u'"'"'" <<<"$c" && grep -qF -- "--autologin me - \${TERM}" <<<"$c"' _ "$HERE"
check "...and no user name that isn't one" bash -c '! WREN_DIR=$1 bash -c ". \"\$WREN_DIR/lib/install-iso.sh\"; autologin_conf \"me x\""' _ "$HERE"
check "the time zone is the one asked for" grep -qx 'd-i time/zone string Europe/Berlin' <(fill_preseed "$HERE/data/install-iso-preseed.cfg" me de_DE.UTF-8 de true Europe/Berlin)
check "...UTC when none is given" grep -qx 'd-i time/zone string UTC' <<<"$ans"
check "...and the clock is kept right over NTP" grep -qx 'd-i clock-setup/ntp boolean true' <<<"$ans"
for good in UTC Europe/Berlin America/New_York America/Argentina/Buenos_Aires Etc/GMT+5; do check "the time zone '$good' is taken" timezone_ok "$good"; done
for bad in 'Europe/Berlin;id' 'x|y' '../etc' 'Europe/ Berlin' '' utc; do check "the time zone '$bad' is refused" not timezone_ok "$bad"; done
mkdir -p "$T/tzetc"; ln -s /usr/share/zoneinfo/Europe/Paris "$T/tzetc/localtime"
check "the default time zone is this computer's" test "$(WREN_ETC=$T/tzetc this_timezone)" = Europe/Paris
rm "$T/tzetc/localtime"; ln -s /usr/share/zoneinfo/Etc/UTC "$T/tzetc/localtime"
check "...UTC for a computer on UTC" test "$(WREN_ETC=$T/tzetc this_timezone)" = UTC
check "only standard system utilities, no desktop" grep -qx 'tasksel tasksel/first multiselect standard' <<<"$ans"
check "no popularity contest" grep -qx 'popularity-contest popularity-contest/participate boolean false' <<<"$ans"
check "packages come over https" grep -qx 'd-i mirror/protocol string https' <<<"$ans"
for bad in root Me '1x' 'a b' 'me;reboot' ''; do check "the user name '$bad' is refused" not user_ok "$bad"; done
for bad in 'us|x' 'u s' 'us;id' ''; do check "the keyboard '$bad' is refused" not keymap_ok "$bad"; done
check "a layout with a variant is fine" keymap_ok 'us(intl)'
for bad in 'en_US' 'en_US.UTF-8;x' ''; do check "the locale '$bad' is refused" not locale_ok "$bad"; done
# ---- the boot menus (excerpts of Debian 13.7.0's live standard image) ----
cat >"$T/grub.cfg" <<'EOF'
source /boot/grub/config.cfg
# Live boot
menuentry "Live system (amd64)" --hotkey=l {
linux /live/vmlinuz-6.12.107+deb13-amd64 boot=live components quiet splash findiso=${iso_path}
initrd /live/initrd.img-6.12.107+deb13-amd64
}
menuentry "Live system (amd64 fail-safe mode)" {
linux /live/vmlinuz-6.12.107+deb13-amd64 boot=live components memtest noapic noapm nodma nomce nosmp nosplash vga=788
initrd /live/initrd.img-6.12.107+deb13-amd64
}
# Installer (if any)
if true; then
source /boot/grub/install_start.cfg
submenu 'Advanced install options ...' --hotkey=a {
source /boot/grub/theme.cfg
source /boot/grub/install.cfg
}
fi
EOF
cp "$T/grub.cfg" "$T/grub.orig"
grub_add_entry "$T/grub.cfg"
check "GRUB: the automatic install is the first entry (the default)" \
test "$(grep -m1 '^menuentry' "$T/grub.cfg")" = "menuentry --hotkey=d 'Automatic install with Wren Linux (asks for your passphrase and password)' {"
check "...starting the live image's installer with Wren Linux's answers" \
bash -c 'grep -qF "linux /install/gtk/vmlinuz vga=788 $2 --- quiet" "$1" && grep -qF "initrd /install/gtk/initrd.gz" "$1"' _ "$T/grub.cfg" "$AUTO_ARGS"
check "...and only that entry gets them" test "$(grep -c 'preseed/file=' "$T/grub.cfg")" = 1
check "...its hotkey isn't one the menu already uses" bash -c '! grep -q "hotkey=d" "$1"' _ "$T/grub.orig"
check "...nothing else in the menu changes" diff "$T/grub.orig" <(sed '4,8d' "$T/grub.cfg")
echo 'menuentry "Something else" {' >"$T/odd.cfg"
check "GRUB: a menu that isn't Debian's as expected is refused" not grub_add_entry "$T/odd.cfg"
printf '%s\n' 'menu hshift 0' 'menu width 82' '' 'menu title Boot menu' 'include stdmenu.cfg' 'include live.cfg' 'include install.cfg' >"$T/menu.cfg"
printf '%s\n' 'label live-amd64' ' menu label ^Live system (amd64)' ' menu default' ' linux /live/vmlinuz' \
' initrd /live/initrd.img' ' append boot=live components quiet splash' '' 'label live-amd64-failsafe' \
' menu label Live system (amd64 fail-safe mode)' ' linux /live/vmlinuz' >"$T/live.cfg"
isolinux_edit "$T/menu.cfg" "$T/live.cfg"
check "BIOS: the menu includes the automatic install before the live system" \
test "$(grep -n 'include wren.cfg' "$T/menu.cfg" | cut -d: -f1)" -lt "$(grep -n 'include live.cfg' "$T/menu.cfg" | cut -d: -f1)"
check "...which is the default" grep -qx ' menu default' <(isolinux_entry)
check "...starting the live image's installer" grep -qF "append vga=788 initrd=/install/gtk/initrd.gz $AUTO_ARGS --- quiet" <(isolinux_entry)
check "...and the live system no longer is (its entries stay)" \
bash -c '! grep -q "menu default" "$1" && grep -q "label live-amd64$" "$1"' _ "$T/live.cfg"
check "BIOS: a menu that isn't as expected is refused" not isolinux_edit "$T/menu.cfg" "$T/live.cfg"
mkdir -p "$T/root/boot/grub"
echo new >"$T/root/boot/grub/grub.cfg"
printf '%s\n' "0123456789abcdef0123456789abcdef ./boot/grub/grub.cfg" "fedcba9876543210fedcba9876543210 ./README.txt" >"$T/md5sum.txt"
md5_update "$T/md5sum.txt" "$T/root" ./boot/grub/grub.cfg
check "md5sum.txt: a changed file gets its new checksum" grep -qx "$(md5sum "$T/root/boot/grub/grub.cfg" | cut -d' ' -f1) ./boot/grub/grub.cfg" "$T/md5sum.txt"
check "...once, and the other lines stay" bash -c '[ "$(grep -c grub.cfg "$1")" = 1 ] && grep -q README "$1"' _ "$T/md5sum.txt"
# ---- inside the installer: which disk ----
D=$HERE/data/install-iso-disk.sh
disk() { # MOUNTED-ON-/cdrom DISK...: what disk.sh sets
printf '%s /cdrom iso9660 ro 0 0\n' "$1" >"$T/mounts"
shift
printf '#!/bin/sh\nprintf "%%s\\n" %s\n' "$*" >"$T/list"
printf '#!/bin/sh\necho "$@" >>"%s/set"\n' "$T" >"$T/setq"
chmod +x "$T/list" "$T/setq"
: >"$T/set"
WREN_MOUNTS=$T/mounts WREN_LIST=$T/list WREN_SET=$T/setq sh "$D"
cat "$T/set"
}
out=$(disk /dev/sdb1 /dev/nvme0n1 /dev/sdb)
check "the installer's own stick is left out, the one other disk chosen" grep -qx 'partman-auto/disk /dev/nvme0n1' <<<"$out"
check "...and the boot loader goes on that disk, not the stick" grep -qx 'grub-installer/bootdev /dev/nvme0n1' <<<"$out"
check "a stick on an NVMe-style name is left out too" grep -qx 'partman-auto/disk /dev/sda' <<<"$(disk /dev/nvme1n1p1 /dev/sda /dev/nvme1n1)"
check "...and an SD card (mmcblk0p1)" grep -qx 'partman-auto/disk /dev/sda' <<<"$(disk /dev/mmcblk0p1 /dev/sda /dev/mmcblk0)"
check "from a DVD every disk counts" grep -qx 'partman-auto/disk /dev/sda' <<<"$(disk /dev/sr0 /dev/sda)"
check "two disks besides the stick: nothing is chosen, the installer asks" test -z "$(disk /dev/sdc1 /dev/sda /dev/nvme0n1 /dev/sdc)"
check "no disk besides the stick: nothing is chosen" test -z "$(disk /dev/sdb1 /dev/sdb)"
# ---- inside the installer: no cable, no network setup ----
N=$HERE/data/install-iso-net.sh
net() { # PORT:CARRIER... (w = wifi): what net.sh emits for the installer
rm -rf "$T/net"; mkdir -p "$T/net/lo"
for p in "$@"; do
n=${p%%:*}; c=${p#*:}
mkdir -p "$T/net/$n"
case $n in w*) mkdir "$T/net/$n/wireless" ;; *) mkdir "$T/net/$n/device" ;; esac
[ "$c" = - ] || echo "$c" >"$T/net/$n/carrier"
done
: >"$T/inc"; : >"$T/out"
WREN_NET=$T/net WREN_INC=$T/inc WREN_WAIT=1 sh "$N" >"$T/out"
cat "$T/inc"; echo "stdout:[$(cat "$T/out")]"
}
check "a cable plugged in: the network is left alone" test "$(net eth0:1 wlan0:1)" = 'stdout:[]'
check "a cable plugged in: net.sh writes no part and prints nothing" test ! -s "$T/inc" -a ! -s "$T/out"
check "wifi only: network setup is switched off" grep -qx 'd-i netcfg/enable boolean false' <<<"$(net wlan0:1)"
check "...and no mirror is asked for" grep -qx 'd-i apt-setup/use_mirror boolean false' <<<"$(net wlan0:1)"
check "...and no upgrade, which the network would be needed for" grep -qx 'd-i pkgsel/upgrade select none' <<<"$(net wlan0:1)"
check "a wired port with no cable: switched off" grep -qx 'd-i netcfg/enable boolean false' <<<"$(net eth0:0 wlan0:1)"
check "a wired port that reports no carrier at all: switched off" grep -qx 'd-i netcfg/enable boolean false' <<<"$(net eth0:-)"
check "no ports at all: switched off" grep -qx 'd-i netcfg/enable boolean false' <<<"$(net)"
check "wifi only: net.sh's stdout is exactly the part's location" test "$(cat "$T/out")" = "file://$T/inc"
check "net.sh is in the preseed" grep -q 'preseed/include_command string sh /cdrom/wren-install/net.sh' "$HERE/data/install-iso-preseed.cfg"
check "the installer picks the interface again (auto is netcfg's default too)" \
grep -q '^d-i netcfg/choose_interface select auto' "$HERE/data/install-iso-preseed.cfg"
check "the boot line leaves the network to net.sh (nothing is forced off)" \
not grep -q 'netcfg/enable=false' <<<"$AUTO_ARGS"
# ---- back home: apt sources after an install without a network ----
A=$T/apt
sources() { mkdir -p "$A/sources.list.d"; printf '%s\n' "$@" >"$A/sources.list"; }
apt_ok() { ( VERSION_CODENAME=trixie; apt_sources_ok "$A" ); }
apt_fix() { # HTTPS(true|false): apt_sources_ensure's work in $A (for trixie)
( case $1 in
true) is_installed() { return 0; } ;; # ca-certificates is there
false) is_installed() { return 1; } ;; # and it is not
esac
VERSION_CODENAME=trixie WREN_APT_DIR=$A SUDO='' apt_sources_ensure )
}
sources '# deb cdrom:[Debian GNU/Linux 13.7.0 _Trixie_ - Official]/ trixie main non-free-firmware'
apt_fix true >/dev/null
check "a list with only the installer's commented cdrom line: Debian's sources are added" \
bash -c 'grep -qxF "deb https://deb.debian.org/debian trixie main non-free-firmware" "$1" &&
grep -qxF "deb https://deb.debian.org/debian trixie-updates main non-free-firmware" "$1" &&
grep -qxF "deb https://security.debian.org/debian-security trixie-security main non-free-firmware" "$1" &&
grep -qF "# deb cdrom:" "$1"' _ "$A/sources.list"
check "...and those lines count as the release having sources" apt_ok
before=$(md5sum <"$A/sources.list")
apt_fix true >/dev/null
check "sources that already name the release: nothing is touched" \
test "$before" = "$(md5sum <"$A/sources.list")"
printf '%s\n' 'Types: deb' 'URIs: http://deb.debian.org/debian' 'Suites: trixie' 'Components: main' >"$A/sources.list.d/other.sources"
rm -f "$A/sources.list"
apt_fix true >/dev/null
check "a deb822 stanza naming the release is enough: nothing is added" test ! -e "$A/sources.list"
check "...and it counts as the release having sources" apt_ok
rm -f "$A/sources.list.d/other.sources"
sources '# deb cdrom:[Debian GNU/Linux 13.7.0 _Trixie_ - Official]/ trixie main non-free-firmware'
apt_fix false >/dev/null
check "without ca-certificates the new lines come over http" \
bash -c '! grep -q "^deb https://" "$1" && grep -q "^deb http://deb.debian.org/debian" "$1"' _ "$A/sources.list"
n=$(grep -c '' "$A/sources.list")
( VERSION_CODENAME='' WREN_APT_DIR=$A SUDO='' apt_sources_ensure ) >/dev/null 2>&1
check "the release name is missing: nothing is bolted on (it would be broken)" test "$(grep -c '' "$A/sources.list")" = "$n"
# ---- inside the installer: Wren Linux's package into the new system ----
L=$T/late.sh
sed -e "s|/cdrom|$T/cdrom|g" -e "s|/target|$T/target|g" -e "s|^NET=/sys/class/net$|NET=$T/sys|" "$HERE/data/install-iso-late.sh" >"$L"
grep -q "^NET=$T/sys$" "$L" || { echo " FAIL late.sh's NET line wasn't found"; exit 1; }
# the installer's ports: a cable with a link, a wired port without one, a Wi-Fi card
mkdir -p "$T/sys/enp0s2/device" "$T/sys/eth1/device" "$T/sys/wlan0/device" "$T/sys/wlan0/wireless" "$T/target/etc/network"
echo 1 >"$T/sys/enp0s2/carrier"; echo 0 >"$T/sys/eth1/carrier"; echo 1 >"$T/sys/wlan0/carrier"
printf '%s\n' 'source /etc/network/interfaces.d/*' '' 'auto lo' 'iface lo inet loopback' >"$T/target/etc/network/interfaces"
mkdir -p "$T/cdrom/wren-install/scripts" "$T/cdrom/wren-install/offline/debs" "$T/target/home/me" "$T/target/tmp" "$T/fake"
echo "the package" >"$T/cdrom/wren-install/wren-linux_1.0_all.deb"
echo x >"$T/cdrom/wren-install/offline/debs/Packages"
echo x >"$T/cdrom/wren-install/scripts/mine.txt"
cp "$HERE/data/install-iso-first-login.sh" "$T/cdrom/wren-install/first-login.sh"
printf '%s\n' '# ~/.profile: Debian'"'"'s own' 'PATH="$HOME/.local/bin:$PATH"' >"$T/target/home/me/.profile"
chmod -R a-w "$T/cdrom"
# a pretend in-target: writes down what ran; dpkg -i fails while $T/dpkg-fails exists
printf '#!/bin/sh\necho "in-target $*" >>"%s/intarget"\ncase "$*" in "dpkg -i "*) rm -f "%s/installed.deb"; cp "%s/target$3" "%s/installed.deb"; [ ! -e "%s/dpkg-fails" ] ;; esac\n' "$T" "$T" "$T" "$T" "$T" >"$T/fake/in-target"
chmod +x "$T/fake/in-target"
PATH="$T/fake:$PATH" sh "$L" me
check "the package goes into the new system (dpkg -i, inside it)" \
bash -c 'grep -qx "in-target dpkg -i /tmp/wren-linux.deb" "$1" && cmp -s "$2/installed.deb" "$3"' _ "$T/intarget" "$T" "$T/cdrom/wren-install/wren-linux_1.0_all.deb"
check "...and nothing of Wren Linux goes into the home folder" test ! -e "$T/target/home/me/wren"
check "...no copy of the package is left behind" test ! -e "$T/target/tmp/wren-linux.deb"
W=$T/target/etc/network/interfaces.d/wren-wired
check "the wired port the install used comes up by itself in the new system (DHCP)" \
bash -c 'grep -qx "allow-hotplug enp0s2" "$1" && grep -qx "iface enp0s2 inet dhcp" "$1"' _ "$W"
check "...not a port without a cable, nor the Wi-Fi card" bash -c '! grep -qE "eth1|wlan0" "$1"' _ "$W"
check "...and /etc/network/interfaces still reads that folder, once" test "$(grep -c '^source /etc/network/interfaces.d' "$T/target/etc/network/interfaces")" = 1
check "the saved downloads go to ~/.local/share/wren/offline, the user's" \
bash -c '[ -f "$1/.local/share/wren/offline/debs/Packages" ] && grep -qx "in-target chown -R me:me /home/me/.local" "$2"' _ "$T/target/home/me" "$T/intarget"
check "your own scripts go to ~/scripts" test -f "$T/target/home/me/scripts/mine.txt"
check "a bad user name is refused" not env PATH="$T/fake:$PATH" sh "$L" '../x'
check "the new user's .profile gets the first-login question, after what it had" \
bash -c 'head -1 "$1" | grep -q "Debian" && grep -qx "# >>> Wren Linux first login >>>" "$1"' _ "$T/target/home/me/.profile"
PATH="$T/fake:$PATH" sh "$L" me
check "...once, even if the installer's step runs again" test "$(grep -c '^# >>> Wren Linux first login >>>$' "$T/target/home/me/.profile")" = 1
check "...and the wired port's entry isn't doubled" test "$(grep -c '^iface enp0s2' "$W")" = 1
# ---- after the install: wifi.sh with no Wi-Fi device keeps the cable for boot ----
N=$T/netdir
mkdir -p "$N" "$T/fake2"
printf '%s\n' 'auto lo' 'iface lo inet loopback' >"$N/interfaces"
printf '#!/bin/sh\nexit 0\n' >"$T/fake2/ifup"; cp "$T/fake2/ifup" "$T/fake2/dhcpcd"; chmod +x "$T/fake2/"*
kw() { PATH="$T/fake2:$PATH" WREN_NETWORK_DIR=$N bash -c 'note() { :; }; eval "$(sed -n "/^keep_wired() {/,/^}/p" "$1")"; keep_wired "$2"' _ "$HERE/wifi.sh" "$1"; }
kw enp0s2
check "wifi.sh, no Wi-Fi device: the cable it went online with is kept for boot" \
bash -c 'grep -qx "allow-hotplug enp0s2" "$1/interfaces.d/wren-wired-enp0s2" && grep -qx "iface enp0s2 inet dhcp" "$1/interfaces.d/wren-wired-enp0s2" && grep -q "^source /etc/network/interfaces.d" "$1/interfaces"' _ "$N"
printf '%s\n' 'allow-hotplug eno1' 'iface eno1 inet static' >"$N/interfaces.d/mine"
kw eno1
check "...but a port something else already sets up is left alone" test ! -e "$N/interfaces.d/wren-wired-eno1"
check "...and wifi.sh tries the cable before saying there is no Wi-Fi device" bash -c '
c=$(grep -n "^if \[ -z \"\$(wifi_ifaces)\" \] && online_by_cable; then" "$1" | cut -d: -f1); f=$(grep -n "^find_iface$" "$1" | cut -d: -f1)
[ -n "$c" ] && [ "$c" -lt "$f" ]' _ "$HERE/wifi.sh"
check "without --autologin, tty1 asks for the password as usual" test ! -e "$T/target/etc/systemd/system/getty@tty1.service.d"
chmod -R u+w "$T/cdrom"
echo '[Service]' >"$T/cdrom/wren-install/autologin.conf"
chmod -R a-w "$T/cdrom"
PATH="$T/fake:$PATH" sh "$L" me
check "with --autologin, the rule goes into the new system's tty1" \
cmp "$T/cdrom/wren-install/autologin.conf" "$T/target/etc/systemd/system/getty@tty1.service.d/50-wren-autologin.conf"
check "without --simplesway, nothing is ticked for the first install list" test ! -e "$T/target/home/me/.config/wren/preselect"
chmod -R u+w "$T/cdrom"
echo one-workspace >"$T/cdrom/wren-install/preselect"
chmod -R a-w "$T/cdrom"
PATH="$T/fake:$PATH" sh "$L" me
check "with --simplesway, the first install list gets one-workspace ticked, owned by the user" \
bash -c 'grep -qx one-workspace "$1/.config/wren/preselect" && grep -qx "in-target chown -R me:me /home/me/.config" "$2"' _ "$T/target/home/me" "$T/intarget"
# the package doesn't install: it stops and says so, and copies nothing into the home instead
: >"$T/dpkg-fails"
out=$(PATH="$T/fake:$PATH" sh "$L" me 2>&1); rc=$?
check "the package doesn't install: the step fails, saying so, with nothing in the home folder instead" \
bash -c '[ "$1" -ne 0 ] && grep -q "didn.t install" <<<"$2" && [ ! -e "$3/wren" ]' _ "$rc" "$out" "$T/target/home/me"
rm -f "$T/dpkg-fails"
chmod -R u+w "$T/cdrom"
rm -f "$T/cdrom/wren-install"/wren-linux_*.deb
out=$(PATH="$T/fake:$PATH" sh "$L" me 2>&1); rc=$?
check "no package on the image: the step fails, saying so" bash -c '[ "$1" -ne 0 ] && grep -q "no Wren Linux package" <<<"$2"' _ "$rc" "$out"
# ---- the first login: asks once, on the console ----
F=$T/first
login() { # TTY ANSWER: a login shell reading that ~/.profile; what it printed in $T/said
printf '%s\n' '# ~/.profile: Debian'"'"'s own' 'echo before' >"$F/.profile"
# (the package's folder, /usr/share/wren, is $F/wren here)
sed "s|/usr/share/wren/start.sh|$F/wren/start.sh|g" "$HERE/data/install-iso-first-login.sh" >>"$F/.profile"
echo 'echo after' >>"$F/.profile"
printf '%s\n' "$2" | HOME=$F WREN_TTY=$1 sh -c '. "$HOME/.profile"' >"$T/said" 2>&1
}
mkdir -p "$F/wren"
printf '#!/bin/sh\necho "start.sh ran as $(id -un)"\n' >"$F/wren/start.sh"
login /dev/tty1 ''
check "the first login runs the package's start.sh, in /usr/share/wren" \
bash -c 'grep -q "bash /usr/share/wren/start.sh" "$1" && ! grep -q "HOME/wren" "$1"' _ "$HERE/data/install-iso-first-login.sh"
check "first login on the console: asks \"Set it up now?\", and Enter runs start.sh" \
bash -c 'grep -q "Set it up now? \[Y/n\]" "$1" && grep -q "start.sh ran as" "$1"' _ "$T/said"
check "...then the question is gone from ~/.profile, the rest kept" \
bash -c '! grep -q "Wren Linux first login" "$1" && grep -qx "echo before" "$1" && grep -qx "echo after" "$1"' _ "$F/.profile"
check "...and the login goes on" grep -qx after "$T/said"
login /dev/tty1 n
check "no: start.sh doesn't run, it says how to later, and doesn't ask again" \
bash -c '! grep -q "start.sh ran" "$1" && grep -q "Later: bash .*/wren/start.sh" "$1" && ! grep -q "Wren Linux first login" "$2"' _ "$T/said" "$F/.profile"
login /dev/pts/0 ''
check "a login over ssh or in a terminal window doesn't ask, and keeps the question for the console" \
bash -c '! grep -q "Set it up" "$1" && grep -q "Wren Linux first login" "$2"' _ "$T/said" "$F/.profile"
mv "$F/wren" "$F/gone"
login /dev/tty1 ''
check "without the package it says nothing, and takes the question out" \
bash -c '! grep -q "Set it up" "$1" && ! grep -q "Wren Linux first login" "$2"' _ "$T/said" "$F/.profile"
# ---- trying the image in a VM (tools/try-install-iso.sh), its commands only ----
TRY=$HERE/tools/try-install-iso.sh
q=$(XDG_CACHE_HOME=$T/cache bash "$TRY" --print --iso /x/my.iso)
inst=$(sed -n '/^# the installer:/{n;p}' <<<"$q")
booted=$(sed -n '/^# the installed system:/{n;p}' <<<"$q")
check "VM: the image is plugged in as a USB stick, read-only, and started first" \
bash -c 'grep -qF -- "-drive if=none,id=stick,format=raw,readonly=on,file=/x/my.iso" <<<"$1" && grep -qF -- "-device usb-storage,drive=stick,bootindex=0" <<<"$1"' _ "$inst"
check "...with UEFI, as on a laptop (the firmware read-only, its settings a copy)" \
bash -c 'grep -qF "if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd" <<<"$1" && grep -qF "if=pflash,format=raw,file=$2/wren/iso-test/vars.fd" <<<"$1"' _ "$inst" "$T/cache"
check "...and one empty disk, in the user's cache folder" \
bash -c 'grep -qF "if=none,id=disk,file=$2/wren/iso-test/disk.qcow2" <<<"$1" && ! grep -q disk2 <<<"$1"' _ "$inst" "$T/cache"
check "VM: when the installer restarts at the end, QEMU stops (not the stick again)" grep -qF -- ' -no-reboot' <<<"$inst"
check "...but the installed system can restart normally" not grep -qF -- '-no-reboot' <<<"$booted"
check "VM: QEMU is asked for a window (not a hidden VNC screen), for both runs" \
bash -c 'grep -qF -- "-display gtk" <<<"$1" && grep -qF -- "-display gtk" <<<"$2"' _ "$inst" "$booted"
check "VM: QEMU's window package is installed if missing" grep -qF 'is_installed qemu-system-gui || need+=(qemu-system-gui)' "$TRY"
check "VM: an interrupted install doesn't go on to the installed system" \
grep -qF '"${install_cmd[@]}" || die ' "$TRY"
check "VM: the installed system starts without the stick" bash -c '[ -n "$1" ] && ! grep -q stick <<<"$1"' _ "$booted"
check "VM: --two-disks adds a second disk (the installer should then ask)" \
test "$(XDG_CACHE_HOME=$T/cache bash "$TRY" --print --two-disks --iso /x/my.iso | grep -c 'id=disk2,file=')" = 2
q=$(XDG_CACHE_HOME=$T/cache bash "$TRY" --print --no-net --iso /x/my.iso)
inst=$(sed -n '/^# the installer:/{n;p}' <<<"$q")
check "VM: --no-net takes the network card out (the way a wifi-only laptop would be)" \
bash -c 'grep -qF -- "-nic none" <<<"$1" && ! grep -q virtio-net <<<"$1"' _ "$inst"
check "...while the default keeps its virtio card" \
bash -c 'grep -qF -- "-nic user,model=virtio-net-pci" <<<"$1"' \
_ "$(XDG_CACHE_HOME=$T/cache bash "$TRY" --print --iso /x/my.iso | sed -n '/^# the installer:/{n;p}')"
check "VM: --no-net is in the usage, and the what-to-check mentions it" \
bash -c 'grep -qF "NO network questions at all" "$1" && bash "$1" --help | grep -q -- --no-net' _ "$TRY"
check "VM: a missing image is refused" not env XDG_CACHE_HOME="$T/cache" bash "$TRY" --iso "$T/nothing.iso"
check "VM: an unknown option is refused" not bash "$TRY" --frobnicate
mkdir -p "$T/cache/wren/iso-test" && echo x >"$T/cache/wren/iso-test/disk.qcow2"
XDG_CACHE_HOME=$T/cache bash "$TRY" --clean </dev/null >/dev/null 2>&1
check "VM: --clean deletes the VM's files" test ! -e "$T/cache/wren/iso-test"
clean_block=$(sed -n '/^if \[ "\$mode" = clean \]/,/^fi/p' "$TRY")
remove_block=$(sed -n '/^if \[ "\$mode" = remove \]/,/^fi/p' "$TRY")
check "...and removes no software (that is --remove)" bash -c '[ -n "$1" ] && ! grep -qE "apt_purge|ask " <<<"$1"' _ "$clean_block"
check "VM: --remove names what this script installed, asks (default no), then removes through its marker" \
bash -c 'grep -q "marker_depends" <<<"$1" && grep -q "ask \"Remove them?\" n && apt_purge" <<<"$1"' _ "$remove_block"
[ "$fails" -eq 0 ]