#!/bin/bash -e #---------- # Interactive installation steps for debian bookworm from GRML using debootstrap # Design decisions # - Fokus on a small and simple setup # - Use systemd whereever possible (network, ntp, cron, journald logging) # - Minimal number of packages # - One disk # - Schema VM: single ext4 partition, cloud kernel, grub-pc # - Schema Physical Server partitions for efi, os and luks data container # - Random root and admin user password generation # - Swap-file as safety net # - SSH on port 50101 limited to the admin user # Usage # Boot grml or run from an existing debian/ubuntu installation # grml: passwd root; grml-network; Start ssh # git clone https://git.in-ulm.de/ulpeters/bootstrap.git # cp config.sh.template config.sh # copy template # config-get-netconf-eth0.sh # get running grml network config # vi config.sh # update installation variables # bootstrap-bookworm.sh install # start installation # !! Note down the admin passwords and reboot # sudo /installer/bootstrap-bookworm.sh postinstall # run postinstall in the new system # Default variables mnt="/mnt/root" # mountpoint for the new root filesystem hostname="somehost.example.com" kernelPkg="linux-image-cloud-amd64" # alternative: linux-image-amd64 partition="mbr-single" # mbr-single or efi-crypt disk="/dev/vda" # find with: lsblk --list disk0=$disk"p1" # efi partion, only relevant if partion="efi-crypt" disk1=$disk"1" # "p1" for nbd or p2 for efi-crypt netDev="eth0" # find with: ip link netAddress="203.0.113.66/24" # "" blank for dhcp on e* netGateway="203.0.113.1" # likely first IP within the network netBroadcast="203.0.113.255" # last IP within the network netDNS1="192.0.2.10" netDNS2="198.51.100.10" netNTP="pool.ntp.org" pwdAdmin="" # "" blank for auto-generation pwdRoot="" # "" blank for auto-generation debootstrap="native" # docker, to run in docker container extraPackages="qemu-guest-agent" # additional packages to install, e.g. cryptsetup # Overwrite default variables from config file [ -f ./config.sh ] && source config.sh # Setup network in grml based on config.sh grmlnetwork(){ ip link show # list interfaces ip addr add $netAddress dev $netDev ip link set $netDev up ip route add default via $netGateway echo nameserver $netDNS1 >> /etc/resolv.conf echo nameserver $netDNS2 >> /etc/resolv.conf } install(){ #---------- # Prepare disk echo "🗑️ Wipe existing partition table to supress warnings from parted" dd if=/dev/zero of=$disk bs=512 count=34 # Parition disks -- pkg: parted # Prepare partition tables and partitions # -parted --script does not accept blanks in partition names # Prepare disks with a single mbr partition if [ "$partition" = "mbr-single" ] then echo "📐 Prepare mbr partition table with a single partition" parted $disk --script \ mklabel msdos \ mkpart primary ext4 512M 100% toggle 1 boot \ print fi # Prepare disks with gpt/efi if [ "$partition" = "efi-crypt" ] then echo "📐 Prepare gpt partition table with following layout:" echo "- 301 MB partition for EFI --> p1" echo "- 50 GB root partition for the OS (includes /boot) --> p2" echo "- Remaining disk left to create a luks container --> p3" parted $disk --script \ mklabel gpt \ mkpart EFI_system_partition fat32 1MiB 301MiB \ set 1 esp on \ set 1 boot on \ align-check optimal 1 \ mkpart Linux_system_parition ext4 301MiB 50GiB \ align-check optimal 2 \ mkpart Data_partion 50GiB 100% \ align-check optimal 3 \ unit MiB \ print # Inform OS about partition table change partprobe $disk && sleep 1 # Format EFI partition -- pkg: dosfstools e2fsprogs mkfs.fat -v -F 32 -n EFIBOOT $disk0 && fsck $disk0 fi # Format OS disk -- pkg: e2fsprogs mkfs.ext4 -v -F $disk1 && fsck $disk1 # Prepare mount points and mount mkdir -p $mnt mount $disk1 $mnt # Create swapfile swapfile=$mnt/swapfile dd if=/dev/zero of=$swapfile bs=1M count=1024 status=progress # create 1GB file chmod 600 $swapfile # restric permissions mkswap $swapfile # format file #---------- # Bootstrap -- pkg: debootstrap # Remark: Debootstrap does not install recommands!! case "$debootstrap" in native) debootstrap --variant=minbase --arch=amd64 bookworm $mnt http://ftp2.de.debian.org/debian/ ;; # Alternatively bootstrap from a docker container, e.g. if no recent debootstrap is available docker) docker run -it --rm --cap-add=SYS_CHROOT --name debootstrap \ -v $mnt:$mnt -e mnt=$mnt debian /bin/sh -c \ "apt-get update && apt-get install --yes debootstrap \ && debootstrap --variant=minbase --arch=amd64 bookworm \ $mnt http://ftp2.de.debian.org/debian/" ;; esac # Configure disk mounts # Or get UUID from blkid... [ "$partition" = "efi-crypt" ] && \ echo "$disk0 /boot/efi/ vfat rw 0 0" >> $mnt/etc/fstab echo "$disk1 / ext4 rw 0 0" >> $mnt/etc/fstab echo "/swapfile none swap defaults 0 0" >> $mnt/etc/fstab # Configure sources.list cat >$mnt/etc/apt/sources.list <> $mnt/etc/hosts echo "$hostname" > $mnt/etc/hostname #---------- # Prepare chroot mount -o bind /dev $mnt/dev mount -o bind /dev/pts $mnt/dev/pts mount -t sysfs /sys $mnt/sys mount -t proc /proc $mnt/proc cp /proc/mounts $mnt/etc/mtab cp /etc/resolv.conf $mnt/etc/resolv.conf mkdir -p $mnt/installer cp $(dirname `realpath $0`)/*.sh $mnt/installer # Run script in chroot chroot $mnt /bin/bash /installer/bootstrap-bookworm.sh install2 # Install bootloader $0 bootloader } #---------- # Function executed within chroot install2(){ source /installer/config.sh # Select grub version based on partition table case "$partition" in mbr-single) grubPkg="grub-pc" ;; efi-crypt) grubPkg="grub-efi" ;; esac # Install basic system apt-get update export DEBIAN_FRONTEND=noninteractive apt-get install --yes \ apt-utils dialog file msmtp-mta \ systemd-sysv polkitd locales tzdata haveged \ $kernelPkg $grubPkg \ iproute2 netbase \ ssh sudo molly-guard \ less vim-tiny bash-completion pwgen lsof \ dnsutils iputils-ping pciutils curl wget bzip2 \ $extraPackages # Upgrade and clean up apt-get upgrade --yes apt-get autoremove --yes apt-get clean --yes # Setup users and passwords [ -z $pwdAdmin ] && pwdAdmin=`pwgen --capitalize --numerals --ambiguous 12 1` useradd admin --create-home --shell /bin/bash echo "admin:$pwdAdmin" | chpasswd usermod -a -G sudo admin echo -e "\e[1;33;4;44m🔐 Password for the user admin: $pwdAdmin\e[0m" pass=`pwgen --capitalize --numerals --ambiguous 12 1` [ -z $pwdRoot ] && pwdRoot=`pwgen --capitalize --numerals --ambiguous 12 1` echo "root:$pwdRoot" | chpasswd echo -e "\e[1;33;4;44m🔐 Password for the user root: $pwdRoot\e[0m" # Harden SSHD sed -i -e 's/#Port 22/Port 50101/g' /etc/ssh/sshd_config sed -i -e 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/g' /etc/ssh/sshd_config # https://infosec.mozilla.org/guidelines/openssh.html # Allow admin to sudo without password echo AllowUsers admin >> /etc/ssh/sshd_config echo "admin ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/admin ## Configure network using systemd if [ -z $netAddress ] then ## Network OPTION 1 - DHCP cat >/etc/systemd/network/20-wired.network </etc/systemd/network/20-wired.network <> /etc/systemd/resolved.conf echo "LLMNR=no" >> /etc/systemd/resolved.conf echo "MulticastDNS=no" >> /etc/systemd/resolved.conf systemctl enable systemd-networkd systemctl enable systemd-resolved # Limit journald logging to 1 month, 1 GB in total and split files per week mkdir -p /etc/systemd/journald.conf.d/ cat >/etc/systemd/journald.conf.d/retention.conf </etc/update-motd.d/15-boot-errors</etc/default/keyboard <&2 ;; esac