#!/bin/sh # TODO: Make sure fdisk from busybox is NOT used, it's counting sectors incorrectly (?!) # TODO: Check connectivity with API server BUTTERKNIFE_POOL_MOUNTPOINT=/var/lib/butterknife/pool TARGET_MOUNTPOINT=/mnt/target AGENT="Butterknife-Provisioning-Image/0.2" URL_LOCAL=http://butterknife #for i in $(cat /proc/cmdline); do # case i in # bk_debug) set -x # Echo # ;; # esac #done set -e # Bail on error ####################################### ### Transfer method selection phase ### ####################################### if [ -z $BUTTERKNIFE_TRANSFER_METHOD ]; then BUTTERKNIFE_TRANSFER_METHOD=$(dialog --menu "Select transfer method" 0 0 0 \ http "HTTP-only" \ multicast "Multicast receive" \ tee "Multicast via HTTP and write" \ proxy "Only proxy HTTP to multicast" 2>&1 >$(tty)) clear fi ################################## ### Harddisk preparation phase ### ################################## case $BUTTERKNIFE_TRANSFER_METHOD in http|multicast|tee) ############################# ### Target disk selection ### ############################# if [ -z $bk_disk_slug ]; then bk_disk_slug=$(butterknife-select-disk 2>&1 >$(tty)) fi BUTTERKNIFE_DISK=/dev/$bk_disk_slug if [ -z $BUTTERKNIFE_PARTITIONING_METHOD ]; then BUTTERKNIFE_PARTITIONING_METHOD=$(dialog --menu "Partitioning $BUTTERKNIFE_DISK" 0 0 0 \ receive "Receive into existing btrfs filesystem" \ unpartitioned "Use unpartitioned area" \ resize "Resize last partition" \ reformat "Reformat partition" \ mbr "Wipe <2TB disk" \ gpt "Wipe 2TB+ disk" \ efi "Wipe and do EFI install" \ 2>&1 >$(tty)) clear fi # TODO: deploy "Deploy received template" \ # TODO: postinstall "Run postinstall scripts (reinstall GRUB)" \ case $BUTTERKNIFE_PARTITIONING_METHOD in unpartitioned) echo "Attempting to create new partition in unpartitioned space" echo -e "n\np\n\n\n\nw" | fdisk $BUTTERKNIFE_DISK ;; resize) NTFS_PARTITION=$(ls $BUTTERKNIFE_DISK?* | tail -n 1) # TODO: Assert last one is NTFS # TODO: Suggested size heuristics MINSIZE=$(ntfsresize $NTFS_PARTITION -m | grep Minsize | cut -d ':' -f 2) SUGGESTED=${MINSIZE}M SIZE=$(dialog --inputbox "Enter new filesystem size of at least ${MINSIZE}M" 0 0 $SUGGESTED 2>&1 >$(tty)) clear ntfsresize -s $SIZE $NTFS_PARTITION echo -e "d\n\nw" | fdisk $BUTTERKNIFE_DISK # Remove last partition echo -e "n\np\n\n\n+$SIZE\nt\n\n7\nw" | fdisk $BUTTERKNIFE_DISK # Re-create NTFS echo -e "n\np\n\n\n\nw" | fdisk $BUTTERKNIFE_DISK # Create partition for btrfs ;; mbr) echo "Purging whole disk" echo -e "o\nn\np\n\n\n\na\n1\nw" | fdisk $BUTTERKNIFE_DISK ;; gpt) sgdisk $BUTTERKNIFE_DISK -o -g \ -n 1::+2MB -t 1:ef02 -c 1:"BIOS Boot Partition" \ -n 2 -t 2:8300 -c 2:"Butterknife pool" -p ;; efi) sgdisk $BUTTERKNIFE_DISK -o -g \ -n 1::+100MB -t 1:ef00 -c 1:"EFI System Partition" \ -n 2 -t 2:8300 -c 2:"Butterknife pool" -p ;; reformat|receive) # NOOP ;; *) echo "Invalid partitioning method $BUTTERKNIFE_PARTITIONING_METHOD" exit 255 ;; esac ############################################ ### Target partition determination phase ### ############################################ case $BUTTERKNIFE_PARTITIONING_METHOD in reformat|receive) # Dialog to select partition for reformat or receive for partition in $BUTTERKNIFE_DISK?*; do partition_slug=$(echo $BUTTERKNIFE_PARTITION | cut -d "/" -f 3) sector_count=$(cat /sys/block/$bk_disk_slug/$BUTTERKNIFE_PARTITION_slug/size) sector_size=$(cat /sys/block/$bk_disk_slug/queue/hw_sector_size) size=$(expr $sector_count / 1000000 \* $sector_size / 1000 || true)G if [ $size == "0G" ]; then size=$(expr $sector_count / 1000 \* $sector_size / 1000 || true)M fi echo "$BUTTERKNIFE_PARTITION \"$size\""; done > /tmp/partitions BUTTERKNIFE_PARTITION=$(dialog \ --menu "Target partition" 0 0 0 \ --file /tmp/partitions \ 2>&1 >$(tty)) clear ;; unpartitioned|resize|mbr|gpt|efi) # Assume last partition BUTTERKNIFE_PARTITION=$(ls $BUTTERKNIFE_DISK?* | tail -n 1) ;; *) echo "Invalid partitioning method $BUTTERKNIFE_PARTITIONING_METHOD" exit 255 ;; esac PARTITION_SLUG=$(echo $BUTTERKNIFE_PARTITION | cut -d "/" -f 3) ######################################## ### Target filesystem creation phase ### ######################################## case $BUTTERKNIFE_PARTITIONING_METHOD in efi) BUTTERKNIFE_EFI_SYSTEM_PARTITION=$(ls ${BUTTERKNIFE_DISK}?* | sort | head -n 1) mkfs.vfat ${BUTTERKNIFE_EFI_SYSTEM_PARTITION} ;; esac case $BUTTERKNIFE_PARTITIONING_METHOD in mbr|gpt|efi|reformat|unpartitioned|resize) echo "Creating clean btrfs filesystem on $PARTITON" mkfs.btrfs -f $BUTTERKNIFE_PARTITION ;; esac # Attempt to mount target directory mkdir -p $BUTTERKNIFE_POOL_MOUNTPOINT mount $BUTTERKNIFE_PARTITION $BUTTERKNIFE_POOL_MOUNTPOINT -o subvol=/ -t btrfs if [ $? -ne 0 ]; then dialog --msgbox "Mounting $BUTTERKNIFE_PARTITION at $BUTTERKNIFE_POOL_MOUNTPOINT failed, are you sure kernel has btrfs support built-in?" 0 0 exit 255 fi ################ ### Clean up ### ################ for subvol in $(ls $BUTTERKNIFE_POOL_MOUNTPOINT | (grep "^@template:" || true)); do set +e touch $BUTTERKNIFE_POOL_MOUNTPOINT/$subvol/.test if [ $? -eq 0 ]; then set -e btrfs subvol delete $BUTTERKNIFE_POOL_MOUNTPOINT/$subvol fi set -e done ;; *) BUTTERKNIFE_PARTITIONING_METHOD="pass" ;; esac ############################## ### Determine architecture ### ############################## bk_arch=$(uname -m | sed 's/^i.86$/x86/') case $BUTTERKNIFE_TRANSFER_METHOD in http|tee|proxy) ############################## ### Server selection phase ### ############################## if [ -z $bk_url ]; then bk_url=$(dialog --menu "Select server" 0 0 0 \ mdns:// "Autodiscover" \ $URL_LOCAL "Manually enter" \ https://butterknife.k-space.ee "K-SPACE MTÜ" 2>&1 >$(tty)) if [ "$bk_url" == "mdns://" ]; then butterknife-discover > /tmp/discovered_servers bk_url=$(dialog --menu "Select one of discovered servers" \ 0 0 0 --file /tmp/discovered_servers 2>&1 >$(tty)) elif [ "$bk_url" == $URL_LOCAL ]; then bk_url=$(dialog --inputbox "Manually enter the URL of Butterknife server" \ 0 0 $URL_LOCAL 2>&1 >$(tty)) fi clear fi ################################ ### Template selection phase ### ################################ if [ -z $bk_template ]; then # Fetch template list curl -A $AGENT -s $bk_url/api/template \ | jq '.templates[] | .namespace + "." + .identifier + " \"" + .description + "\""' -r \ > /tmp/available_templates bk_template=$(dialog \ --menu "Select template to deploy" 0 0 0 \ --file /tmp/available_templates \ 2>&1 >$(tty)) clear fi ############################### ### Version selection phase ### ############################### if [ -z $bk_version ]; then # Fetch version list curl -A $AGENT -s $bk_url/api/template/$bk_template/arch/$bk_arch/version \ > /tmp/available_versions.json cat /tmp/available_versions.json \ | jq '.versions[] | .identifier + " \"" + .comment + "\""' -r \ | head -n 100 \ > /tmp/available_versions bk_version=$(dialog \ --menu "Select version to deploy" 0 0 0 \ --file /tmp/available_versions \ 2>&1 >$(tty)) clear fi BUTTERKNIFE_TEMPLATE_SUBVOL="@template:$bk_template:$bk_arch:$bk_version" ##################################### ### Stream URL construction phase ### ##################################### # Build btrfs-stream URL STREAM="$bk_url/$BUTTERKNIFE_TEMPLATE_SUBVOL" ;; esac ############################################## ### Allow differential versions using HTTP ### ############################################## case $BUTTERKNIFE_TRANSFER_METHOD in http) # Determine differential version parent cat /tmp/available_versions.json | jq -r '.versions[] .identifier' > /tmp/available_version_names ls $BUTTERKNIFE_POOL_MOUNTPOINT | (grep "^@template:$bk_template:$bk_arch:" || true) | cut -d ":" -f 4 > /tmp/local_version_names PARENT=$(cat /tmp/local_version_names /tmp/available_version_names | sort | grep -v $bk_version | uniq -d | sort -t p -k 2n | tail -n 1) if [ -z $PARENT ]; then echo "Could not determine parent, falling back to full snapshot" else STREAM="$STREAM?parent=@template:$bk_template:$bk_arch:$PARENT" fi echo "Final URL is $STREAM" ;; esac #################################################### ### Enable compression if we're going over HTTPS ### #################################################### case $STREAM in https://*) STREAM="--compressed $STREAM" ;; esac ###################### ### Transfer phase ### ###################### case $BUTTERKNIFE_TRANSFER_METHOD in multicast) ls $BUTTERKNIFE_POOL_MOUNTPOINT | (grep "^@template:" || true) > /tmp/local_templates udp-receiver --nokbd | btrfs receive $BUTTERKNIFE_POOL_MOUNTPOINT # Heuristics to determine name of received snapshot ls $BUTTERKNIFE_POOL_MOUNTPOINT | (grep "^@template:" || true) > /tmp/new_templates BUTTERKNIFE_TEMPLATE_SUBVOL=$(cat /tmp/local_templates /tmp/new_templates | sort | uniq -u) # TODO: Break here if we got garbage bk_template=$(echo $BUTTERKNIFE_TEMPLATE_SUBVOL | cut -d ":" -f 2) bk_arch=$(echo $BUTTERKNIFE_TEMPLATE_SUBVOL | cut -d ":" -f 3) bk_version=$(echo $BUTTERKNIFE_TEMPLATE_SUBVOL | cut -d ":" -f 4) ;; http) curl -A $AGENT $STREAM | btrfs receive $BUTTERKNIFE_POOL_MOUNTPOINT ;; tee) dialog --msgbox "Press enter once all the other machines are ready to receive" 0 0 mkfifo /tmp/multicast_stream /tmp/local_stream cat /tmp/local_stream | btrfs receive $BUTTERKNIFE_POOL_MOUNTPOINT & sleep 1 udp-sender --nokbd --no-progress --min-receivers 1 --min-wait 5 /tmp/multicast_stream & sleep 1 curl -A $AGENT -s $STREAM | tee /tmp/multicast_stream > /tmp/local_stream sleep 2 # TODO: Ensure btrfs receive has finished ;; proxy) dialog --msgbox "Press enter once all the other machines are ready to receive" 0 0 curl -A $AGENT -s $STREAM \ | udp-sender --nokbd --min-receivers 1 --min-wait 5 ;; esac sync case $BUTTERKNIFE_PARTITIONING_METHOD in pass) echo "Skipping template deployment" ;; *) ################################# ### Template deployment phase ### ################################# BUTTERKNIFE_DEPLOY_SUBVOL="@root:$bk_template:$bk_arch:$bk_version" btrfs subvolume snapshot \ $BUTTERKNIFE_POOL_MOUNTPOINT/$BUTTERKNIFE_TEMPLATE_SUBVOL \ $BUTTERKNIFE_POOL_MOUNTPOINT/$BUTTERKNIFE_DEPLOY_SUBVOL # Symlink @root:active to current deployment subvol rm -f $BUTTERKNIFE_POOL_MOUNTPOINT/@root:active ln -s \ $BUTTERKNIFE_DEPLOY_SUBVOL \ $BUTTERKNIFE_POOL_MOUNTPOINT/@root:active ############################### ### Run post-deploy scripts ### ############################### # Mount deployment subvolume at target directory mkdir -p $TARGET_MOUNTPOINT mount -o subvol=$BUTTERKNIFE_DEPLOY_SUBVOL \ $BUTTERKNIFE_PARTITION \ $TARGET_MOUNTPOINT # Mount pool also for chroot mkdir -p $TARGET_MOUNTPOINT$BUTTERKNIFE_POOL_MOUNTPOINT mount -o subvol=/ \ $BUTTERKNIFE_PARTITION \ $TARGET_MOUNTPOINT$BUTTERKNIFE_POOL_MOUNTPOINT # Mount stuff for chroot mount --bind /dev/ $TARGET_MOUNTPOINT/dev/ mount --bind /sys/ $TARGET_MOUNTPOINT/sys/ mount --bind /proc/ $TARGET_MOUNTPOINT/proc/ mount --bind /run/ $TARGET_MOUNTPOINT/run/ mount none $TARGET_MOUNTPOINT/tmp/ -t tmpfs case $BUTTERKNIFE_PARTITIONING_METHOD in efi) mount ${BUTTERKNIFE_EFI_SYSTEM_PARTITION} $TARGET_MOUNTPOINT/boot/efi ;; esac # Export variables for postinstall scripts export BUTTERKNIFE_DOMAIN export BUTTERKNIFE_TEMPLATE_SUBVOL export BUTTERKNIFE_DEPLOY_SUBVOL export BUTTERKNIFE_PARTITION export BUTTERKNIFE_DISK export BUTTERKNIFE_POOL_MOUNTPOINT export BUTTERKNIFE_PARTITIONING_METHOD export BUTTERKNIFE_TRANSFER_METHOD export BUTTERKNIFE_POOL_UUID=$(blkid -s UUID -o value $BUTTERKNIFE_PARTITION) # Copy DNS config mkdir -p /run/resolvconf mkdir -p /run/systemd/resolve cat /etc/resolv.conf > $TARGET_MOUNTPOINT/etc/resolv.conf # Export sensible PATH export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" # Run postinstall scripts, presumably in sorted order chroot $TARGET_MOUNTPOINT butterknife-deploy chroot $TARGET_MOUNTPOINT butterknife-maintenance # Be forgiving from now on set +e # Unmount directories echo "Unmounting filesystems" umount -a echo "Flushing buffers" sync sleep 1 echo "Rebooting machine" reboot -f ;; esac