wren-linux / extras / get-iso.sh
#!/bin/bash
# get-iso: downloads another system's install image and checks it, then offers
# to write it to a USB stick. It sets up the command get-iso, which:
# - offers Debian, Tails, Kicksecure and Qubes OS;
# - finds the newest release in the project's own release list or folder;
# - downloads it through Tor when Tor runs here (apt over Tor, remote access,
# the onion site), so your network can't see what you fetch; otherwise it
# asks before downloading directly;
# - keeps it only if it is that newest release and its own signature, or a
# signed list of checksums naming it, is by the project's key pinned in
# pins.sh (a download that fails is deleted);
# - keeps the checked image in ~/ISOs/NAME (the newest only), checked again
# before every use, for a VM in virt-manager or the next stick;
# - if you say so, lists only USB sticks, never the disk this system runs
# from, asks you to type the stick's name, writes it, and reads it back.
# Nothing it fetches is run here. Writing a stick erases everything on it.
# Usage: bash get-iso.sh set up get-iso
# bash get-iso.sh --get [NAME] what get-iso runs (NAME: see --list)
# bash get-iso.sh --list
# bash get-iso.sh --remove
# bash get-iso.sh --manual the steps to do it by hand
# summary: Other systems' images, checked, onto a USB stick
# security: most
# category: systems
# status: bin:get-iso
. "$(dirname -- "${BASH_SOURCE[0]}")/../lib.sh"
. "$WREN_DIR/lib/appimage.sh"
. "$WREN_DIR/lib/tails.sh"
. "$WREN_DIR/lib/usb-stick.sh"
. "$WREN_DIR/lib/get-iso.sh"
if [ "${1:-}" = --manual ]; then print_manual; exit; fi
need_user
ISOS=${WREN_ISO_DIR:-$HOME/ISOs}
if [ "${1:-}" = --remove ]; then
remove_bin get-iso
apt_purge
if [ -d "$ISOS" ] && ask "Also delete the images kept in $ISOS?" n; then
rm -rf "$ISOS"
note "Deleted."
fi
note "Sticks you made keep working."
exit
fi
if [ "${1:-}" = --list ]; then
while IFS=$'\t' read -r name what; do printf ' %-16s %s\n' "$name" "$what"; done <<<"$ISO_LIST"
exit 0
fi
# pick: a NAME from the list, asked for by number
pick() {
local names=() choice name what
# (on stderr: what this prints on stdout is the answer)
say "Which system?" >&2
while IFS=$'\t' read -r name what; do
names+=("$name")
printf ' %d) %-16s %s\n' "${#names[@]}" "$name" "$what" >&2
done <<<"$ISO_LIST"
tty_read choice "Which (1-${#names[@]})? "
[[ $choice =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#names[@]} ] || die "None chosen; nothing was downloaded."
echo "${names[choice - 1]}"
}
get() {
local name=$1 tmp v url algo sum file dir img i min
tmp=$(mktemp -d)
# (its name now: tmp is this function's, gone by the time the script exits)
# shellcheck disable=SC2064
trap "rm -rf '$tmp'" EXIT
say "How to download"
if [ -n "${WREN_ISO_MIRROR:-}" ]; then
:
elif systemctl is-active --quiet tor@default.service && command -v torsocks >/dev/null; then
ISO_VIA_TOR=true
note "Through Tor (the system tor service runs here): your network won't see what you fetch."
else
note "Tor doesn't run here, so your network would see which system you download"
note "(not what you do with it later). Set up apt over Tor first (wrenctl install apt-tor)"
note "to download through Tor instead."
ask "Download directly?" y || die "Nothing was downloaded."
fi
say "The newest $name"
iso_release "$name" "$tmp" || die "Nothing was downloaded."
read -r v url algo sum <"$tmp/release"
file=${url##*/}
[[ $file =~ ^[A-Za-z0-9._+-]+$ ]] || die "The image's name ($file) isn't one get-iso expects."
dir=$ISOS/$name
img=$dir/$file
note "$name $v: $file"
if [ -f "$img" ] && iso_ok "$img" "$tmp" 2>/dev/null; then
note "Using the copy kept here, checked again: $img"
else
mkdir -p "$dir"
say "Downloading $file"
if ! iso_fetch "$url" "$img.part"; then
[ "$(df -Pm "$dir" | awk 'NR == 2 { print $4 }')" -ge 64 ] ||
die "The disk is full ($dir): make room, then run get-iso $name again to go on where it stopped."
die "The download didn't finish; run get-iso $name again to go on where it stopped."
fi
say "Checking it"
if ! iso_ok "$img.part" "$tmp"; then
rm -f "$img.part"
die "The download didn't pass the checks, so it was deleted."
fi
mv "$img.part" "$img"
# Keep only the newest
for i in "$dir"/*; do
[ "$i" = "$img" ] || rm -f "$i"
done
note "Checked against $name's pinned key, and the newest release: $img"
fi
ask "Write it to a USB stick now (everything on the stick is erased)?" n || {
note "Kept in $img. For a stick later: get-iso $name"
return 0
}
# The image, and a little room: sticks sold as 8 GB hold a little less
min=$(stat -c %s "$img")
[ "$name" != tails ] || min=$((7500 * 1000 * 1000))
stick_write "$img" "$min" "$name $v"
note "Written and read back: the stick holds exactly the checked image."
note "To start from it: plug it in, start the computer, and pick the stick in the boot"
note "menu (often F12, F9, F10, F11 or Esc; on a Mac hold Option)."
}
case ${1:-} in
--get)
for c in wget gpg python3 lsblk; do command -v "$c" >/dev/null || die "$c is missing. Run: bash $WREN_DIR/extras/get-iso.sh"; done
name=${2:-}
[ -n "$name" ] || name=$(pick)
grep -qx -- "$name" <<<"$(iso_names)" || die "get-iso doesn't know \"$name\". It knows: $(iso_names | paste -sd' ')"
get "$name" ;;
"")
say "Installing what get-iso needs"
# torsocks: the download through Tor; python3: reading Tails' release list
apt_install wget gpg python3 torsocks util-linux
install_bin get-iso
note "get-iso is set up. Run: get-iso (or get-iso NAME; get-iso --list names them)"
pause_if_asked ;;
*) die "Usage: bash extras/get-iso.sh [--get [NAME] | --list | --remove | --manual]" ;;
esac