#!/bin/bash die() { printf "%s: Error: %s\n" "$0" "$*" >&2; exit 1; } whole_device() { case "$1" in *-part?) false ;; *-part??) false ;; *-part???) false ;; */usb\*) false ;; *) true ;; esac } confirm_usb() { local msg="This will completely overwrite device:\n\n\t%s\n\nType 'yes' to confirm.\nContinue? " printf "$msg" "$1" >&2 read line case "$line" in [yY][eE][sS]) return ;; *) die "Aborted by user." ;; esac } choose_usb() { local devs maj set -- /dev/disk/by-id/usb* for dev; do shift whole_device "$dev" || continue set -- "$@" "$dev" done if [ $# = 0 ]; then die "no usb device found" elif [ $# = 1 ]; then confirm_usb "$1" || die impossible outdev="$1" else die "multiple USB devices connected and choice between them is unimplemented. ($*)" fi } choose_cdrom() { die 'choose_cdrom: unimplemented; specify cdrom device with --out' } choose_outdev() { if [ "$CMDLINE_OUTDEV" ]; then outdev=$CMDLINE_OUTDEV~ NEED_STDIO=y elif [ "$USB" ]; then choose_usb NEED_STDIO=y else choose_cdrom NEED_STDIO= fi } generate_keys() { if [ "$ADAM" -o "$BOOTLOADER_ONLY" ]; then kiki init || die 'kiki init failed' GPG_INPUT_DIR=/root/.gnupg else keygen.sh "$child_dir" || die "keygen.sh failed" GPG_INPUT_DIR=$child_dir/root/.gnupg trap 'umount "$child_dir"; rmdir "$child_dir"' EXIT fi } try_mount() { # TODO: don't use /mnt # Ofc the real solution is to get help with xorriso. NEED_UNMOUNT="$1" mount -r "$1" /mnt && trap 'umount -l /mnt' EXIT } verbosely() { (set -x; "$@") } xorriso_cmd() { # input variables: ## that do not affect the output ISO image: # SHOW_XORRISO_CMD # SILENT # NEED_STDIO ## that do not vary by invocation: # volid # vmlinuz_dir # efi_dir # gpg_iso_path ## that specify sources of input: # INPUT_DEVICE # GPG_INPUT_DIR # $@ (btrfs filesystems) # EXTRA_INPUT_DIRS ## boolean flags # REMOVE_BTRFS # ADD_BTRFS ${SHOW_XORRISO_CMD:+ verbosely} ${NO_ACT:+ :} \ xorrisofs -iso-level 3 -- \ ${INPUT_DEVICE:+ -indev "$INPUT_DEVICE" } \ -outdev ${NEED_STDIO:+stdio:}"$outdev" \ -blank as_needed \ ${SILENT:+ -report_about mishap} \ -return_with sorry 0 \ -volid "$volid" \ -pathspecs on \ \ \ ${EXTRA_INPUT_DIRS} \ \ \ -rm_r linux -- -add linux="${vmlinuz_dir}" -- \ ${REMOVE_BTRFS:+ -rm_r rootfs -- } \ ${ADD_BTRFS:+ -follow link -add "$@" -- -follow default } \ \ \ -rm_r "${gpg_iso_path}" -- \ -add "${gpg_iso_path}=${GPG_INPUT_DIR}" -- \ \ \ -chown_r 0 / -- \ -chgrp_r 0 / -- \ -chmod_r go-rwx "${gpg_iso_path}" -- \ \ \ -as mkisofs -graft-points \ -b grub/i386-pc/eltorito.img \ -no-emul-boot -boot-info-table \ --embedded-boot "${efi_dir}"/embedded.img \ --protective-msdos-label \ grub="${efi_dir}"/grub } run_xorriso() { xorriso_cmd "$@" || die "xorriso exited $?" case "$outdev" in *~) [ -f "$outdev" ] && mv "$outdev" "${outdev%\~}" ;; esac if [ "$USB" -a "$DETACH" -a $? = 0 ]; then udisks --detach "$outdev" fi } find_child() { # TODO: Lookup by IP address, MAC address printf '%s' "$samizdat_child_dir"/child."$1" } . samizdat-paths.sh || die 'samizdat-path.sh not found' if [ -f xorriso-usb.config ]; then . xorriso-usb.config fi volid=SamizdatLive vmlinuz_dir=$samizdat_linux_dir efi_dir=$samizdat_grub_efi_dir gpg_iso_path=gnupghome outdev= GPG_INPUT_DIR= child_dir=$samizdat_child_dir/child.$$ OPT=$(getopt -o '' --long 'bootloader,reuse-child:,adam,usb,detach,in:,out:,test' -n "$0" -- "$@") || die 'getopt error' eval set -- "$OPT" unset OPT ADAM=; DETACH=; USB=y while [ $# -gt 0 ]; do case "$1" in --) shift; break;; --adam) shift; ADAM=y;; --bootloader) shift; export BOOTLOADER_ONLY=y;; --cdrom) shift; USB=;; --detach) shift; DETACH=y;; --in) INPUT_DEVICE="$2"; shift 2;; --out) CMDLINE_OUTDEV="$2"; USB=; shift 2;; --reuse-child) REUSE_CHILD=y; child_dir="$(find_child "$2")"; shift 2;; --test) shift; QUICK_TEST=y;; *) die 'getopt error';; esac done if [ "$INPUT_DEVICE" ]; then die "support for --in is disabled because xorriso needs different arguments to produce a bootable image" elif [ $# = 0 -a -z "$BOOTLOADER_ONLY" ]; then if mountpoint -q /cdrom; then INPUT_DEVICE=/cdrom elif [ -e /srv/nbd.btrfs ]; then set -- /srv/nbd.btrfs elif [ "$(blockdev --getsz /dev/nbd0)" -gt 0 ]; then (set -x; dd if=/dev/nbd0 of=/srv/nbd.btrfs~ && mv /srv/nbd.btrfs~ /srv/nbd.btrfs) || die "failed to copy network block device" set -- /srv/nbd.btrfs else die "no input device and no input btrfs layers: aborting." fi fi for fs; do [ -f "$fs" ] || die "not a file: $fs" case "$fs" in *.btrfs) ;; *) die "invalid name (does not match *.btrfs): $fs" ;; esac shift set -- "$@" "rootfs/${fs##*/}=$fs" done [ "$(id -u)" = 0 ] || die "you are not root." grub-efi.sh || die "grub-efi.sh failed" choose_outdev if [ "$REUSE_CHILD" ]; then GPG_INPUT_DIR=$child_dir/root/.gnupg [ -d "$GPG_INPUT_DIR" ] || die "invalid child" else generate_keys fi if [ "$INPUT_DEVICE" ]; then if [ "$INPUT_DEVICE" = /dev/md55 ]; then if mountpoint -q /cdrom; then EXTRA_INPUT_DIRS='-add /=/cdrom --' fi elif [ -d "$INPUT_DEVICE" ]; then EXTRA_INPUT_DIRS="-add /=$INPUT_DEVICE --" # TODO: escape elif try_mount "$INPUT_DEVICE"; then EXTRA_INPUT_DIRS='-add /=/mnt --' else REPLACE_INITRD= REMOVE_BTRFS= ADD_BTRFS= fi elif [ "$BOOTLOADER_ONLY" ]; then REPLACE_INITRD= REMOVE_BTRFS= ADD_BTRFS= else REPLACE_INITRD=y REMOVE_BTRFS=y ADD_BTRFS=y fi REPLACE_INITRD= # TODO: fix initrd replacing if [ "$QUICK_TEST" ]; then REMOVE_BTRFS=y ADD_BTRFS= fi if [ "$REPLACE_INITRD" ]; then # TODO: fix the paths so this backup isn't needed. What we need to do is # support tftp pointing to the cdrom. This probably means a tftp directory # that contains a symlink to /cdrom/linux which gets updated on success here mv "${vmlinuz_dir}" "${vmlinuz_dir}".bak mkdir "${vmlinuz_dir}" initrd.sh || { mv "${vmlinuz_dir}.bak" "${vmlinuz_dir}" die 'initrd.sh failed' } fi SILENT= SHOW_XORRISO_CMD=y run_xorriso "$@"