#!/bin/sh . loop-layer.sh . mdadm-dup.sh losetup() { /sbin/losetup "$@"; } luks_secret() { local parms=$-; # this junk keeps set -x from being too annoying set +x [ -n "$luks_secret" ] || luks_secret="$(head -c256 /dev/urandom)" printf %s "$luks_secret" case $parms in *x*) set -x; set -x ;; esac } floor4() { # Negatives round up, but aren't used. echo $(($1 / 4 * 4)) } ceil4() { local x="$1" [ $((x % 4)) -eq 0 ] || x=$((x + 4 - x % 4)) printf '%d\n' "$x" } kernel_commandline_has() { local v="$1" c read c < /proc/cmdline for c in $c do case "$c" in "$v"|"$v"=*) true; return;; esac done false } netbooted() { kernel_commandline_has BOOTIF } cdrom_has_rootfs() { if netbooted then false else bootwait samizdat-cdrom [ -d /cdrom/rootfs ] fi } losetup_layers() { if cdrom_has_rootfs then # TODO: This is some kind of shortcut or short circuit to find these # files, that ought to be found through the grok-block system (i.e., # event-driven rather than polling). local fs fs_rw for fs in /cdrom/rootfs/*.btrfs; do fs_rw=/"${fs##*/}".rw dd if=/dev/zero of="$fs_rw" bs=1M count=10 losetup_snapshot "$fs" "$fs_rw" || return done elif [ -e /dev/disk/by-partlabel/samizdat-rootfs ] then # TODO: prevent raciness umount /dev/disk/by-partlabel/samizdat-rootfs if [ -e /dev/disk/by-partlabel/samizdat-patchfs ] then umount /dev/disk/by-partlabel/samizdat-patchfs mountdev=/dev/disk/by-partlabel/samizdat-patchfs else mountdev=/dev/disk/by-partlabel/samizdat-rootfs fi else bootwait samizdat-nbd-dev local dev for dev in nbd0 nbd1 do dd if=/dev/zero of=/$dev.rw bs=1M count=10 dm_snapshot /dev/$dev /$dev.rw done fi } init_samizdat() { local blockdev="$1" imgfile="$2" uuid losetup_layers || return modprobe btrfs || return btrfs device scan || return if [ "$mountdev" ] then mount -t btrfs "$mountdev" /root || return else uuid=$(choose_uuid) || return [ "$uuid" ] || return mount -t btrfs UUID="$uuid" /root || return fi btrfs device add "$blockdev" /root || return mount -o rw,remount /root || return samizdat_movemounts "$imgfile" || return initialize_root_filesystem || return } movemounts() { # Move mounted filesystems to the future root filesystem local dev mp rest target while read dev mp rest; do case "$mp" in /root/*|/root|/|/proc|/dev|/dev/pts|/sys|/run) continue ;; *) target=/root/$mp ;; esac mkdir -p "$target" mount -n -o move "$mp" "$target" done /dev/null 2>&1 } make_subvolume_idem() { is_subvolume "$1" && return if [ -d "$1" ]; then rmdir "$1" || mv "$1" "$1"~ btrfs subvolume create "$1" mv "$1"~/* "$1"~/.* "$1"/ 2>&1 rmdir "$1"~ else if [ -e "$1" ]; then rm -f "$1" fi btrfs subvolume create "$1" fi } initialize_root_filesystem() { local uhome=/home/u local uhome_subs="${uhome} ${uhome}/.cache ${uhome}/.stack ${uhome}/.rustup ${uhome}/src/fsmgr/_build ${uhome}/Downloads ${uhome}/.mozilla" for d in /root /srv /var/cache/apt/archives /home $uhome_subs do make_subvolume_idem /root/${d} done chroot /root chown -R u:u ${uhome} mv /root/root/.gnupg /root/root/.gnupg~ mv /gpg/gnupghome /root/root/.gnupg || return copy_execs sbin mdadm dmsetup cryptsetup fsck.hfsplus copy_execs bin btrfs rsync gpg gpg2 gpg-agent # Copy these over unconditionally, because they ought to remain in sync with # the initrd. cp /bin/mdadm-dup.sh /root/sbin/ cp /bin/samizdat-eject.sh /root/sbin/ sed -i -e 's/^root:x:/root::/' /root/etc/passwd cp /patchroot/* /root/root/ true } # Get the uuid of the filesystem with the most devices, # excluding filesystems that don't incorporate loop devices. # This is used to choose the latest seed -- which should have # the most layers. choose_uuid() { local seen_loop= seen_uuid= seen_devs= btrfs filesystem show | while read line; do case "$line" in Label*) seen_uuid=${line##*uuid: } seen_devs= seen_loop= ;; *Total\ devices*) seen_devs=${line#*Total devices } seen_devs=${seen_devs%% *} ;; *path\ /dev/mapper/*) seen_loop=t;; esac [ "$seen_loop" ] && echo "$seen_devs $seen_uuid" done | uniq | sort -nr | head -n1 | (read _ x; echo $x) } filesystem_incomplete() { local n n=$(btrfs filesystem show "$1" | sed -ne 's/.*Total devices \([^ ]*\) .*/\1/p') [ "$n" != 1 ] } partition_new_hard_drive_DESTROYING_EVERYTHING() { local target="$1" sz=2910 # [ "$(parted -sm "$target" print | grep -c :)" = 1 ] || return parted "$target" -sm \ unit MiB \ mklabel gpt \ mkpart samizdat-grub-incomplete 1 8 \ set 1 bios_grub on \ mkpart samizdat-plaintext-incomplete btrfs 64 $((sz + 64)) \ mkpart samizdat-luks-encrypted-incomplete $((sz + 64)) 100% \ && udevadm settle } mark_partitions_as_complete() { local dev="$1" # TODO: Verify existing names parted "$dev" -sm \ name 1 samizdat-grub \ name 2 samizdat-plaintext \ name 3 samizdat-luks-encrypted } open_samizdat() { local blockdev=/dev/mapper/samizdatcrypt fs if filesystem_incomplete "$blockdev"; then losetup_layers fi modprobe btrfs || return btrfs device scan || return mount -t btrfs "$blockdev" /root || return samizdat_movemounts "$imgfile" LoSetup -D } init_samizdat_lodev() { local imgfile="$1" megs=$(ceil4 "$2") dev truncate -s ${megs}M "$imgfile" || return dev=$(losetup -f) && losetup "$dev" "$imgfile" || return echo "$dev" } open_samizdat_blockdev_from_loop() { local imgfile="$1" keyfile="$2" dev dev=$(losetup -f) && losetup "$dev" "$imgfile" || return open_samizdat_blockdev "$dev" "$keyfile" } open_samizdat_blockdev() { local dev="$1" keyfile="$2" local cryptname=samizdatcrypt decrypted_keyfile=/luks.secret gpg2 --verify "$keyfile" || return # TODO: we should be ensuring we can decrypt this secret key before even # offering the option to boot the encrypted filesystem # The first --decrypt merely strips the signature. The option is # poorly named for that case. gpg2 --decrypt "$keyfile" | gpg2 --decrypt > "$decrypted_keyfile" || return cryptsetup --key-file "$decrypted_keyfile" luksOpen "$dev" "$cryptname" || return [ -b /dev/mapper/"$cryptname" ] || return } init_samizdat_blockdev() { local dev="$1" keyfile="$2" local cryptname=samizdatcrypt [ ! -b /dev/mapper/"$cryptname" ] || return luks_secret >/dev/null luks_secret | gpg2 --default-recipient-self --encrypt --armor | gpg2 --clearsign --output "$keyfile" || return luks_secret | cryptsetup -v luksFormat "$dev" - || return cryptsetup luksDump "$dev" >&2 luks_secret | cryptsetup --key-file - luksOpen "$dev" "$cryptname" || return [ -b /dev/mapper/"$cryptname" ] || return } majmin() { local dev="$1" major minor eval $(stat -c 'major=%t minor=%T' "$dev") || return [ "$major" -a "$minor" ] || return printf '%d:%d\n' 0x$major 0x$minor } cryptdev_to_dev() { local dev="$1" majmin majmin=$(majmin "$dev") || return set -- /sys/dev/block/$majmin/slaves/* [ $# = 1 ] || return cryptsetup status "$dev" |while read k v; do if [ "$k" = device: ]; then echo $v; break; fi; done } cryptdev_to_backing_file() { local dev="$1" majmin result majmin="$(majmin "$dev")" || return set -- /sys/dev/block/$majmin/slaves/* [ $# = 1 ] || return read result < "$1"/loop/backing_file || return printf '%s\n' "$result" } lodev_to_file() { local result majmin dev="$1" majmin="$(majmin "$dev")" || return read result < /sys/dev/block/$majmin/loop/backing_file || return printf '%s' "$result" } mountpoint_to_dev() { local wantmp="$1" dev mp rest mountpoint -q "$wantmp" || return while read dev mp rest; do if [ "$mp" = "$wantmp" ]; then echo "$dev"; return; fi; done < /proc/mounts return 1 } get_cdrom_sizelimit() { # returns bytes local dev="$1" sectors sectors=$(blockdev --getsz "$dev") || return if dd count=2 if="$dev" bs=2048 skip=$((sectors/4 - 2)) of=/dev/null 2>/dev/null; then return else echo $(((sectors-8)*512)) fi } init_gpg() { export GNUPGHOME=/gpg/gnupghome mkdir -p "$GNUPGHOME" if [ -e /gnupghome.tar ]; then tar -C "$GNUPGHOME" -zxf /gnupghome.tar && bootdone samizdat-gpg return else bootwait samizdat-cdrom (umask 077; rsync --exclude '/luks-key*' --ignore-existing -rpP /cdrom/gnupghome/ "$GNUPGHOME") bootdone samizdat-gpg fi local LOG_DIR=/run/initramfs/samizdat/log if samizdat-password-agent > "$LOG_DIR"/samizdat-password-agent.log 2>&1; then clear true else echo 'samizdat-password-agent failed; continuing in hope of hope...' true # false fi } start_meter() { local startmsg="$*" (exec >&4 clear echo -n $startmsg set +x while sleep 2; do echo -n . done) & meterpid=$! } stop_meter() { local endmsg="$*" kill $meterpid echo " $endmsg" >&4 }