commit d6c6bc72dc8150b2eb8f838afb4e15b4dce19eaa Author: Carl Kittelberger Date: Thu May 6 14:36:52 2021 +0200 Initial commit. diff --git a/install-from-rescue.sh b/install-from-rescue.sh new file mode 100755 index 0000000..b9c2013 --- /dev/null +++ b/install-from-rescue.sh @@ -0,0 +1,773 @@ +#!/bin/bash -ex + +# Automatic setup of Debian or Ubuntu on an Online.net or Hetzner rescue system. +# +# ref https://github.com/openzfs/zfs/wiki/Debian-Buster-Root-on-ZFS +# +# Author: Carl Kittelberger +# Version: 3.0.0-beta.2 +# Date: 2021-05-01 + +raidtype=auto +disks=() +hostname=${hostname:-$(hostname)} +rpool="" +bpool="" +system="${system:-$(lsb_release -is | tr '[[:upper:]]' '[[:lower:]]')}" +mirror="http://deb.debian.org/debian" + +# Ensure only valid defaults are set for system +case "$system" in +debian|ubuntu) + ;; +*) + system="" + ;; +esac + +usage() { + echo "Usage: $0 [options] [...]" >&2 + echo "" >&2 + echo "Disks need to be supplied as /dev/disk/by-id/... path references.">&2 + echo "" >&2 + echo "Options:">&2 + echo "" >&2 + echo " -h, --help Show this help message" >&2 + echo " --hostname HOSTNAME Set hostname to be written to the installed operating system. Default: $hostname" >&2 + echo " --rpool RPOOL Set name of ZFS data pool. Default: -rpool ($hostname-rpool)" >&2 + echo " --bpool BPOOL Set name of ZFS data pool. Default: -bpool ($hostname-bpool)" >&2 + if [ -n "$system" ] + then + echo " -s, --system SYSTEM The system to install. Valid values are debian or ubuntu. Defaults to currently running operating system ($system)." >&2 + else + echo " -s, --system SYSTEM The system to install. Valid values are debian or ubuntu. Required because the current operating system is not actually supported as target operating system." >&2 + fi + echo " --hetzner Use Hetzner network mirror." >&2 + echo " --online Use Online.net network mirror." >&2 + echo " --mirror MIRROR Use custom mirror . Default: $mirror" >&2 +} + +noproc=0 +while [ "$noproc" -eq 0 ] && [ "$#" -gt 0 ] +do + opt="$1" + case "$opt" in + -h|--help) + usage + exit + ;; + --hostname) + if [ -z "$2" ] + then + echo "ERROR: Expected a value following $opt." >&2 + fi + hostname="$2" + shift 2 + ;; + --rpool) + if [ -z "$2" ] + then + echo "ERROR: Expected a value following $opt." >&2 + fi + rpool="$2" + shift 2 + ;; + --bpool) + if [ -z "$2" ] + then + echo "ERROR: Expected a value following $opt." >&2 + fi + bpool="$2" + shift 2 + ;; + -s|--system) + if [ -z "$2" ] + then + echo "ERROR: Expected a value following $opt." >&2 + fi + system="$2" + shift 2 + ;; + -t|--type) + if [ -z "$2" ] + then + echo "ERROR: Expected a value following $opt." >&2 + fi + raidtype="$2" + shift 2 + ;; + --hetzner) + mirror="http://mirror.hetzner.de/${system}/packages" + shift 1 + ;; + --online) + mirror="https://mirrors.online.net/${system}" + shift 1 + ;; + --mirror) + if [ -z "$2" ] + then + echo "ERROR: Expected a value following $opt." >&2 + fi + mirror="$2" + shift 2 + ;; + --) + noproc=1 + shift 1 + ;; + *) + noproc=1 + ;; + esac +done + +rpool="${rpool:-${hostname}-rpool}" +bpool="${bpool:-${hostname}-bpool}" + +echo "Data pool will be named $rpool." >&2 +echo "Boot pool will be named $bpool." >&2 + +# Check disks use /dev/disk/by-id/... style reference +for disk in "$@" +do + case "$disk" in + /dev/disk/by-id/*) + echo "Considering disk $disk." >&2 + disks+=("$disk") + ;; + *) + echo "ERROR: Invalid disk reference $disk, needs to be formatted like this: /dev/disk/by-id/..." >&2 + exit 1 + ;; + esac +done + +# Did we receive any disk information at all? +if [ "${#disks[@]}" -lt 1 ] +then + echo "ERROR: Need at least one disk." >&2 + exit 1 +fi + +# Check if we got a valid target operating system to install +case "$system" in + debian|ubuntu) + ;; + "") + echo "ERROR: You need to set a system to be installed." >&2 + exit 1 + ;; + *) + echo "ERROR: You need to set a valid system to be installed. $system is not supported, only debian and ubuntu are." >&2 + exit 1 + ;; +esac + +# If raidtype is auto, automatically select one: +# - 1 disk: striped +# - 2 disks: mirror +# - 3+ disks: raidz +if [ "$raidtype" = auto ] +then + raidtype=striped + if [ "${#disks[@]}" -gt 2 ] + then + raidtype=raidz + elif [ "${#disks[@]}" -gt 1 ] + then + raidtype=mirror + fi + echo "Autoselected RAID type for ${#disks[@]} disks: $raidtype" >&2 +fi + +alldisks() { + for disk in "${disks[@]}" + do + "$@" "$disk" || return "$?" + done +} + +allpartref() { + for disk in "${disks[@]}" + do + echo "${disk}-part$1" + done +} + +# Only apply a patch if it wasn't already applied. +# Turns the error case of an already applied patch into a success case. +softpatch() { + patch "$@" || if [ "$?" -ne 1 ] + then + return $? + else + true + fi +} + +if [ "$(id -u)" != 0 ]; then + sudo -v +else + sudo() { + "$@" + } + export sudo +fi + +export DEBIAN_FRONTEND="${DEBIAN_FRONTEND:-noninteractive}" + +sudo apt install -y "linux-image-$(uname -r)" "linux-headers-$(uname -r)" eatmydata +sudo eatmydata apt-get autoremove -y $(eatmydata apt list | grep -P '^linux-(image|headers)' | grep -vP "^linux-(image|headers)-$(uname -r)" | cut -d/ -f 1) +for d in /lib/modules/*/ +do + kernel_version=$(basename "$d") + if [ "${kernel_version}" != "$(uname -r)" ] + then + rm -rv "$d" + fi +done + +sudo umount -l /mnt/dev || true +sudo umount -l /mnt/proc || true +sudo umount -l /mnt/sys || true + +#sudo touch /boot/vmlinuz-4.4.0-97 /boot/vmlinuz-4.4.0-97-generic /boot/initrd.img-4.4.0-97 /boot/initrd.img-4.4.0-97-generic + +#sudo sed -i 's,\bmain\b,main contrib,g' /etc/apt/sources.list +sudo eatmydata apt install -y mdadm gdisk "linux-headers-$(uname -r)" +if ! modprobe zfs +then + # check for backports + codename="$(lsb_release -cs)" + has_backports="$(curl -Ifso /dev/null "https://deb.debian.org/debian/dists/${codename}-backports/Release" && echo 1 || echo 0)" + if [ "${has_backports}" -ne 0 ] + then + echo "deb https://deb.debian.org/debian ${codename}-backports main contrib" > /etc/apt/sources.list.d/backports.list + eatmydata apt update + fi + + if [ "${has_backports}" -ne 0 ] + then + sudo eatmydata apt install -y -t "${codename}-backports" zfs-dkms + + # sudo eatmydata apt install -y devscripts + + # # Build spl + # sudo eatmydata apt build-dep -y spl-linux + # rm -rf spl-linux* + # eatmydata apt source spl-linux + # wget -O spl-pr-710.patch https://github.com/zfsonlinux/spl/pull/710.patch + # if (cd spl-linux*/ && patch --dry-run --silent -N -p1 -i ../spl-pr-710.patch) + # then + # cd spl-linux*/ + # patch -N -p1 -i ../spl-pr-710.patch + # echo '4.19' > debian/linux_compat + # dch -v 0.7.12 "Apply spl 0.7.12 patchset." + # cd ../spl-linux-*/ + # dpkg-buildpackage -b + # cd .. + # sudo eatmydata apt install -y ./*.deb + # rm *.deb + # elif [ "$?" -eq 1 ] + # then + # echo "Patch was already applied, skipping spl-linux build." >&2 + # else + # echo "ERROR: Failed to apply patch." >&2 + # exit 1 + # fi + # rm spl-pr-710.patch + + # # Build zfs + # sudo eatmydata apt build-dep -y zfs-linux + # rm -rf zfs-linux* + # eatmydata apt source zfs-linux + # wget -O zfs-pr-8078.patch https://github.com/zfsonlinux/zfs/pull/8078.patch + # if (cd zfs-linux*/ && patch --dry-run --silent -N -p1 -i ../zfs-pr-8078.patch) + # then + # cd zfs-linux*/ + # patch -N -p1 -i ../zfs-pr-8078.patch + # echo '4.19' > debian/linux_compat + # dch -v 0.7.12 "Apply zfsonlinux 0.7.12 patchset." + # cd ../zfs-linux-*/ + # dpkg-buildpackage -b + # cd .. + # rm *dracut*.deb + # sudo eatmydata apt install -y ./*.deb + # rm *.deb + # elif [ "$?" -eq 1 ] + # then + # echo "Patch was already applied, skipping zfs build and installing zfs-dkms binary distribution." >&2 + # sudo eatmydata apt install -y zfs-dkms + # else + # echo "ERROR: Failed to apply patch." >&2 + # exit 1 + # fi + # rm zfs-pr-8078.patch + else + sudo eatmydata apt install -y zfs-dkms + fi + sudo modprobe zfs +fi + +sudo eatmydata apt install -y jq +debootstrap_version=$(wget -qO- https://sources.debian.org/api/src/debootstrap/ | jq '.versions[0].version' -r) +case "$system" in + debian) + sudo eatmydata apt install -y debian-archive-keyring + wget -Odebootstrap.deb https://deb.debian.org/debian/pool/main/d/debootstrap/debootstrap_${debootstrap_version}_all.deb + ;; + ubuntu) + sudo eatmydata apt install -y ubuntu-keyring + wget -Odebootstrap.deb http://de.archive.ubuntu.com/ubuntu/pool/main/d/debootstrap/debootstrap_${debootstrap_version}ubuntu1_all.deb + ;; + *) + echo "Unsupported system: $system" + exit 1 + ;; +esac + +sudo eatmydata apt install -y --allow-downgrades ./debootstrap.deb + +sudo zpool export "${hostname}" || true +sudo zpool export "${bpool}" || true +sudo zpool export "${rpool}" || true +sudo zpool destroy "${hostname}" || true +sudo zpool destroy "${bpool}" || true +sudo zpool destroy "${rpool}" || true +alldisks sudo mdadm --zero-superblock --force || true +alldisks sudo sgdisk --zap-all +alldisks sudo blkdiscard || for d in "${disks[@]}"; do + sudo dd if=/dev/zero of="${d}" bs=32M count=4 oflag=direct status=progress +done +partBIOS=1 +partUEFI=2 +partBootPool=3 +partRootPool=4 +alldisks sudo sgdisk -a1 -n1:24K:+1000K -t1:EF02 # bios boot +alldisks sudo sgdisk -n2:1M:+512M -t2:EF00 # uefi boot +alldisks sudo sgdisk -n3:0:+1G -t3:BF01 # boot pool +alldisks sudo sgdisk -n4:0:0 -t4:BF01 # zfs pool +sudo partprobe +# sudo hdparm -z "${disks[@]}" +sleep 1 +for d in $( + allpartref "${partBootPool}" + allpartref "${partRootPool}" +) +do + sudo blkdiscard "$d" || sudo dd if=/dev/zero of="$d" bs=1M count=1 +done + +# create boot pool +sudo zpool create -o ashift=12 -d \ + -o feature@async_destroy=enabled \ + -o feature@bookmarks=enabled \ + -o feature@embedded_data=enabled \ + -o feature@empty_bpobj=enabled \ + -o feature@enabled_txg=enabled \ + -o feature@extensible_dataset=enabled \ + -o feature@filesystem_limits=enabled \ + -o feature@hole_birth=enabled \ + -o feature@large_blocks=enabled \ + -o feature@lz4_compress=enabled \ + -o feature@spacemap_histogram=enabled \ + -o feature@userobj_accounting=enabled \ + -o feature@zpool_checkpoint=enabled \ + -o feature@spacemap_v2=enabled \ + -o feature@project_quota=enabled \ + -o feature@resilver_defer=enabled \ + -o feature@allocation_classes=enabled \ + -O acltype=posixacl -O canmount=off -O compression=lz4 -O devices=off \ + -O normalization=formD -O relatime=on -O xattr=sa \ + -O mountpoint=/ -R /mnt -f \ + "$bpool" "$raidtype" \ + $(allpartref "${partBootPool}") + +# create root pool +sudo zpool create -o ashift=12 \ + -O acltype=posixacl -O canmount=off -O compression=lz4 \ + -O dnodesize=auto -O normalization=formD -O relatime=on -O xattr=sa \ + -O mountpoint=/ -R /mnt -f \ + "$rpool" "$raidtype" \ + $(allpartref "${partRootPool}") + +# create filesystem dataset +sudo zfs create -o canmount=off -o mountpoint=none "$rpool/ROOT" +sudo zfs create -o canmount=off -o mountpoint=none "$bpool/BOOT" + +# create filesystem dataset for root fs +sudo zfs create -o canmount=noauto -o mountpoint=/ "$rpool/ROOT/operating_system" +sudo zfs mount $rpool/ROOT/operating_system +sudo zfs create -o canmount=noauto -o mountpoint=/boot "$bpool/BOOT/operating_system" +sudo zfs mount $bpool/BOOT/operating_system +#sudo zpool set bootfs=$rpool/ROOT/operating_system $rpool + +# create datasets for folders +sudo zfs create -o setuid=off "$rpool/home" +sudo zfs create -o mountpoint=/root "$rpool/home/root" +sudo zfs create -o canmount=off -o setuid=off -o exec=off "$rpool/var" +sudo zfs create -o canmount=off "$rpool/var/lib" +sudo zfs create -o acltype=posixacl -o xattr=sa "$rpool/var/log" +sudo zfs create "$rpool/var/mail" +sudo zfs create "$rpool/var/spool" + +# Exclude cache and tmp files from snapshots +sudo zfs create -o com.sun:auto-snapshot=false "$rpool/var/cache" +sudo zfs create -o com.sun:auto-snapshot=false -o exec=on "$rpool/var/tmp" + +# We may be using /usr/local +sudo zfs create -o canmount=off "$rpool/usr" +sudo zfs create "$rpool/usr/local" + +# We may be using /opt +sudo zfs create "$rpool/opt" + +# Do not snapshot ZFS +sudo zfs create "$rpool/srv" +# sudo zfs create -o com.sun:auto-snapshot=false -o mountpoint=/srv/nfs4 "$rpool/srv/nfs" + +# install system +sudo chmod 1777 /mnt/var/tmp + +if [ -z "$codename" ] +then + case "$system" in + debian) + codename="$(curl -L https://deb.debian.org/debian/dists/stable/Release | grep -Po '^Codename: \K(.+)$')" + ;; + ubuntu) + codename="bionic" # TODO + ;; + *) + echo "ERROR: Unsupported system: $system" >&2 + exit 1 + ;; + esac +fi +sudo eatmydata debootstrap --include=console-setup,eatmydata,ca-certificates,coreutils,gdisk,ssh,zsh,nano,lsb-release "${codename}" /mnt "$mirror" + +sudo zfs set devices=off "$rpool" + +# hostname/hosts configuration +echo "${hostname}" | sudo tee /mnt/etc/hostname +sudo tee /mnt/etc/hosts </dev/null +sudo mkdir -p /mnt/etc/initramfs-tools/conf.d +echo RESUME=none | sudo tee /mnt/etc/initramfs-tools/conf.d/resume >/dev/null + +# prepare for chroot +sudo mount --bind /dev /mnt/dev +sudo mount --bind /proc /mnt/proc +sudo mount --bind /sys /mnt/sys + +# configure +codename="$(sudo chroot /mnt lsb_release -cs)" +case "$system" in + debian) + sudo chroot /mnt grep '\bcontrib\b' /etc/apt/sources.list ||\ + sudo chroot /mnt sed -i 's,\bmain\b,main contrib,g' /etc/apt/sources.list + url="https://deb.debian.org/debian" + if curl -o /dev/null --fail "${url}/dists/${codename}-backports/Release" + then + echo "deb ${url} ${codename}-backports main contrib" |\ + sudo chroot /mnt tee /etc/apt/sources.list.d/backports.list + fi + success=0 + for url in \ + https://deb.debian.org/debian-security \ + http://security.debian.org + do + for name in \ + "${codename}/updates" \ + "${codename}" + do + if curl -o /dev/null --fail "${url}/dists/${codename}/updates/Release" + then + echo "deb ${url} ${name} main contrib" |\ + sudo chroot /mnt tee /etc/apt/sources.list.d/security.list + success=1 + break + fi + done + if [ "$success" -ne 0 ] + then + break + fi + done + ;; + ubuntu) + sudo chroot /mnt eatmydata apt update + sudo chroot /mnt eatmydata apt install -y software-properties-common + sudo chroot /mnt eatmydata apt-add-repository "${mirror}" multiverse universe + sudo chroot /mnt ln -s /proc/self/mounts /etc/mtab + ;; +esac +sudo chroot /mnt eatmydata apt update +sudo chroot /mnt eatmydata apt install -y locales +sudo chroot /mnt dpkg-reconfigure locales +sudo chroot /mnt dpkg-reconfigure tzdata +case "$system" in + debian) + sudo chroot /mnt eatmydata apt install -y linux-headers-amd64 linux-image-amd64 + ;; + ubuntu) + sudo chroot /mnt eatmydata apt install -y --no-install-recommends linux-image-generic linux-headers-generic + ;; +esac +sudo chroot /mnt eatmydata apt install -y -t "${codename}-backports" zfsutils-linux zfs-dkms zfs-initramfs +sudo chroot /mnt sh -c 'ZPOOL_VDEV_NAME_PATH=YES DEBIAN_NONINTERACTIVE=true eatmydata apt install -y grub-pc' +sudo tee /etc/ssh/sshd_config <&2 + exit 1 +fi +sudo chroot /mnt update-initramfs -u -k all +sudo chroot /mnt sed -e 's#GRUB_CMDLINE_LINUX="\(.*\)"#GRUB_CMDLINE_LINUX="root=ZFS='"$rpool"'/ROOT/operating_system \1"#' -i /etc/default/grub +sudo chroot /mnt update-grub +for d in "${disks[@]}" +do + sudo chroot /mnt sh -c "ZPOOL_VDEV_NAME_PATH=YES grub-install '${d}'" +done +if [ "$(sudo chroot /mnt find /boot/grub -name zfs.mod | wc -l)" -lt 1 ] +then + echo "ERROR: Installed GRUB does not seem to have ZFS module. Aborting." >&2 + exit 1 +fi + +# fix filesystem mount ordering +sudo zfs set mountpoint=legacy "$bpool/BOOT/operating_system" +sudo tee -a /mnt/etc/fstab < /dev/null +#sudo chroot /mnt ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf +#sudo chroot /mnt systemctl enable systemd-resolved.service + +sudo chroot /mnt zfs snapshot "$bpool/BOOT/operating_system@install" +sudo chroot /mnt zfs snapshot "$rpool/ROOT/operating_system@install" + +sudo chroot /mnt hostnamectl set-hostname "${hostname}" ||\ +(printf "%s" "${hostname}" | sudo chroot /mnt tee /etc/hostname >/dev/null) + +sudo chroot /mnt bash -i + +# unmount +sudo umount -l /mnt/dev +sudo umount -l /mnt/proc +sudo umount -l /mnt/sys +#mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | xargs -i{} sudo umount -lf {} +sudo zpool export "$bpool" +sudo zpool export "$rpool" + +echo "Installation done."