bootstrap-bookworm.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. #!/bin/bash -e
  2. #----------
  3. # Interactive installation steps for debian bookworm from GRML using debootstrap
  4. # Design decisions
  5. # - Fokus on a small and simple setup
  6. # - Use systemd whereever possible (network, ntp, cron, journald logging)
  7. # - Minimal number of packages
  8. # - One disk
  9. # - Schema VM: single ext4 partition, cloud kernel, grub-pc
  10. # - Schema Physical Server partitions for efi, os and luks data container
  11. # - Random root and admin user password generation
  12. # - Swap-file as safety net
  13. # - SSH on port 50101 limited to the admin user
  14. # Usage
  15. # Boot grml or run from an existing debian/ubuntu installation
  16. # grml: passwd root; grml-network; Start ssh
  17. # git clone https://git.in-ulm.de/ulpeters/bootstrap.git
  18. # cp config.sh.template config.sh # copy template
  19. # config-get-netconf-eth0.sh # get running grml network config
  20. # vi config.sh # update installation variables
  21. # bootstrap-bookworm.sh install # start installation
  22. # !! Note down the admin passwords and reboot
  23. # sudo /installer/bootstrap-bookworm.sh postinstall # run postinstall in the new system
  24. # Default variables
  25. mnt="/mnt/root" # mountpoint for the new root filesystem
  26. hostname="somehost.example.com"
  27. kernelPkg="linux-image-cloud-amd64" # alternative: linux-image-amd64
  28. partition="mbr-single" # mbr-single or efi-crypt
  29. disk="/dev/vda" # find with: lsblk --list
  30. disk0=$disk"p1" # efi partion, only relevant if partion="efi-crypt"
  31. disk1=$disk"1" # "p1" for nbd or p2 for efi-crypt
  32. netDev="eth0" # find with: ip link
  33. netAddress="203.0.113.66/24" # "" blank for dhcp on e*
  34. netGateway="203.0.113.1" # likely first IP within the network
  35. netBroadcast="203.0.113.255" # last IP within the network
  36. netDNS1="192.0.2.10"
  37. netDNS2="198.51.100.10"
  38. netNTP="pool.ntp.org"
  39. pwdAdmin="" # "" blank for auto-generation
  40. pwdRoot="" # "" blank for auto-generation
  41. debootstrap="native" # docker, to run in docker container
  42. extraPackages="qemu-guest-agent" # additional packages to install, e.g. cryptsetup
  43. # Overwrite default variables from config file
  44. [ -f ./config.sh ] && source config.sh
  45. # Setup network in grml based on config.sh
  46. grmlnetwork(){
  47. ip link show # list interfaces
  48. ip addr add $netAddress dev $netDev
  49. ip link set $netDev up
  50. ip route add default via $netGateway
  51. echo nameserver $netDNS1 >> /etc/resolv.conf
  52. echo nameserver $netDNS2 >> /etc/resolv.conf
  53. }
  54. install(){
  55. #----------
  56. # Prepare disk
  57. echo "🗑️ Wipe existing partition table to supress warnings from parted"
  58. dd if=/dev/zero of=$disk bs=512 count=34
  59. # Parition disks -- pkg: parted
  60. # Prepare partition tables and partitions
  61. # -parted --script does not accept blanks in partition names
  62. # Prepare disks with a single mbr partition
  63. if [ "$partition" = "mbr-single" ]
  64. then
  65. echo "📐 Prepare mbr partition table with a single partition"
  66. parted $disk --script \
  67. mklabel msdos \
  68. mkpart primary ext4 512M 100% toggle 1 boot \
  69. print
  70. fi
  71. # Prepare disks with gpt/efi
  72. if [ "$partition" = "efi-crypt" ]
  73. then
  74. echo "📐 Prepare gpt partition table with following layout:"
  75. echo "- 301 MB partition for EFI --> p1"
  76. echo "- 50 GB root partition for the OS (includes /boot) --> p2"
  77. echo "- Remaining disk left to create a luks container --> p3"
  78. parted $disk --script \
  79. mklabel gpt \
  80. mkpart EFI_system_partition fat32 1MiB 301MiB \
  81. set 1 esp on \
  82. set 1 boot on \
  83. align-check optimal 1 \
  84. mkpart Linux_system_parition ext4 301MiB 50GiB \
  85. align-check optimal 2 \
  86. mkpart Data_partion 50GiB 100% \
  87. align-check optimal 3 \
  88. unit MiB \
  89. print
  90. # Inform OS about partition table change
  91. partprobe $disk && sleep 1
  92. # Format EFI partition -- pkg: dosfstools e2fsprogs
  93. mkfs.fat -v -F 32 -n EFIBOOT $disk0 && fsck $disk0
  94. fi
  95. # Format OS disk -- pkg: e2fsprogs
  96. mkfs.ext4 -v -F $disk1 && fsck $disk1
  97. # Prepare mount points and mount
  98. mkdir -p $mnt
  99. mount $disk1 $mnt
  100. # Create swapfile
  101. swapfile=$mnt/swapfile
  102. dd if=/dev/zero of=$swapfile bs=1M count=1024 status=progress # create 1GB file
  103. chmod 600 $swapfile # restric permissions
  104. mkswap $swapfile # format file
  105. #----------
  106. # Bootstrap -- pkg: debootstrap
  107. # Remark: Debootstrap does not install recommands!!
  108. case "$debootstrap" in
  109. native) debootstrap --variant=minbase --arch=amd64 bookworm $mnt http://ftp2.de.debian.org/debian/
  110. ;;
  111. # Alternatively bootstrap from a docker container, e.g. if no recent debootstrap is available
  112. docker) docker run -it --rm --cap-add=SYS_CHROOT --name debootstrap \
  113. -v $mnt:$mnt -e mnt=$mnt debian /bin/sh -c \
  114. "apt-get update && apt-get install --yes debootstrap \
  115. && debootstrap --variant=minbase --arch=amd64 bookworm \
  116. $mnt http://ftp2.de.debian.org/debian/"
  117. ;;
  118. esac
  119. # Configure disk mounts
  120. # Or get UUID from blkid...
  121. [ "$partition" = "efi-crypt" ] && \
  122. echo "$disk0 /boot/efi/ vfat rw 0 0" >> $mnt/etc/fstab
  123. echo "$disk1 / ext4 rw 0 0" >> $mnt/etc/fstab
  124. echo "/swapfile none swap defaults 0 0" >> $mnt/etc/fstab
  125. # Configure sources.list
  126. cat >$mnt/etc/apt/sources.list <<EOL
  127. deb http://deb.debian.org/debian bookworm main non-free-firmware
  128. # deb-src http://deb.debian.org/debian bookworm main non-free-firmware
  129. deb http://deb.debian.org/debian-security/ bookworm-security main non-free-firmware
  130. # deb-src http://deb.debian.org/debian-security/ bookworm-security main non-free-firmware
  131. deb http://deb.debian.org/debian bookworm-updates main non-free-firmware
  132. # deb-src http://deb.debian.org/debian bookworm-updates main non-free-firmware
  133. EOL
  134. # Configure hostname
  135. echo "127.0.0.1 $hostname" >> $mnt/etc/hosts
  136. echo "$hostname" > $mnt/etc/hostname
  137. #----------
  138. # Prepare chroot
  139. mount -o bind /dev $mnt/dev
  140. mount -o bind /dev/pts $mnt/dev/pts
  141. mount -t sysfs /sys $mnt/sys
  142. mount -t proc /proc $mnt/proc
  143. cp /proc/mounts $mnt/etc/mtab
  144. cp /etc/resolv.conf $mnt/etc/resolv.conf
  145. mkdir -p $mnt/installer
  146. cp $(dirname `realpath $0`)/*.sh $mnt/installer
  147. # Run script in chroot
  148. chroot $mnt /bin/bash /installer/bootstrap-bookworm.sh install2
  149. # Install bootloader
  150. $0 bootloader
  151. }
  152. #----------
  153. # Function executed within chroot
  154. install2(){
  155. source /installer/config.sh
  156. # Select grub version based on partition table
  157. case "$partition" in
  158. mbr-single) grubPkg="grub-pc" ;;
  159. efi-crypt) grubPkg="grub-efi" ;;
  160. esac
  161. # Install basic system
  162. apt-get update
  163. export DEBIAN_FRONTEND=noninteractive
  164. apt-get install --yes \
  165. apt-utils dialog file msmtp-mta \
  166. systemd-sysv polkitd locales tzdata haveged \
  167. $kernelPkg $grubPkg \
  168. iproute2 netbase \
  169. ssh sudo molly-guard \
  170. less vim-tiny bash-completion pwgen lsof \
  171. dnsutils iputils-ping pciutils curl wget bzip2 \
  172. $extraPackages
  173. # Upgrade and clean up
  174. apt-get upgrade --yes
  175. apt-get autoremove --yes
  176. apt-get clean --yes
  177. # Setup users and passwords
  178. [ -z $pwdAdmin ] && pwdAdmin=`pwgen --capitalize --numerals --ambiguous 12 1`
  179. useradd admin --create-home --shell /bin/bash
  180. echo "admin:$pwdAdmin" | chpasswd
  181. echo -e "\e[1;33;4;44m🔐 Password for the user admin: $pwdAdmin\e[0m"
  182. pass=`pwgen --capitalize --numerals --ambiguous 12 1`
  183. [ -z $pwdRoot ] && pwdRoot=`pwgen --capitalize --numerals --ambiguous 12 1`
  184. echo "root:$pwdRoot" | chpasswd
  185. echo -e "\e[1;33;4;44m🔐 Password for the user root: $pwdRoot\e[0m"
  186. # Harden SSHD
  187. sed -i -e 's/#Port 22/Port 50101/g' /etc/ssh/sshd_config
  188. sed -i -e 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/g' /etc/ssh/sshd_config
  189. # https://infosec.mozilla.org/guidelines/openssh.html
  190. # Allow admin to sudo without password
  191. echo AllowUsers admin >> /etc/ssh/sshd_config
  192. echo "admin ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/admin
  193. ## Configure network using systemd
  194. if [ -z $netAddress ]
  195. then
  196. ## Network OPTION 1 - DHCP
  197. cat >/etc/systemd/network/20-wired.network <<EOL
  198. [Match]
  199. Name=e*
  200. [Network]
  201. DHCP=ipv4
  202. IPv6PrivacyExtensions=false
  203. IPv6AcceptRA=false
  204. NTP=$netNTP
  205. EOL
  206. else
  207. ## Network OPTION 2 - static
  208. cat >/etc/systemd/network/20-wired.network <<EOL
  209. [Match]
  210. Name=$netDev
  211. [Network]
  212. Address=$netAddress
  213. Gateway=$netGateway
  214. Broadcast=$netBroadcast
  215. DNS=$netDNS1
  216. DNS=$netDNS2
  217. NTP=$netNTP
  218. EOL
  219. fi
  220. ## Setup systemd resolver
  221. apt-get install --yes systemd-resolved
  222. echo -e "\n# Disable local name resolution" >> /etc/systemd/resolved.conf
  223. echo "LLMNR=no" >> /etc/systemd/resolved.conf
  224. echo "MulticastDNS=no" >> /etc/systemd/resolved.conf
  225. systemctl enable systemd-networkd
  226. systemctl enable systemd-resolved
  227. # Limit journald logging to 1 month, 1 GB in total and split files per week
  228. mkdir -p /etc/systemd/journald.conf.d/
  229. cat >/etc/systemd/journald.conf.d/retention.conf <<EOL
  230. [Journal]
  231. MaxRetentionSec=1month
  232. SystemMaxUse=1G
  233. MaxFileSec=1week
  234. EOL
  235. # Show errors in motd
  236. rm /etc/motd
  237. cat >/etc/update-motd.d/15-boot-errors<<EOL
  238. #!/bin/sh
  239. echo
  240. journalctl --boot --priority=3 --no-pager
  241. EOL
  242. chmod 755 /etc/update-motd.d/15-boot-errors
  243. # Setup keyboard layout
  244. cat >/etc/default/keyboard <<EOL
  245. XKBMODEL="pc105"
  246. XKBLAYOUT="de"
  247. XKBVARIANT="nodeadkeys"
  248. XKBOPTIONS=""
  249. BACKSPACE="guess"
  250. EOL
  251. # Leave chroot
  252. exit
  253. }
  254. bootloader(){
  255. # Install grub
  256. echo "$partition partition"
  257. case "$partition" in
  258. mbr-single)
  259. chroot $mnt /bin/bash -c "grub-install $disk && update-grub"
  260. ;;
  261. efi-crypt)
  262. chroot $mnt /bin/bash -c \
  263. "mkdir -p /boot/efi && mount /boot/efi \
  264. && grub-install --target=x86_64-efi --efi-directory=/boot/efi \
  265. --bootloader-id=grub && update-grub \
  266. && umount /boot/efi"
  267. ;;
  268. esac
  269. }
  270. unmount(){
  271. # Unmount if mounted
  272. # Remark: Function called unmount and not umount to avoid collision with the command
  273. ! mountpoint -q $mnt/proc || umount -v $mnt/proc
  274. ! mountpoint -q $mnt/sys || umount -v $mnt/sys
  275. ! mountpoint -q $mnt/dev/pts || umount -v $mnt/dev/pts
  276. ! mountpoint -q $mnt/dev || umount -v $mnt/dev
  277. ! mountpoint -q $mnt || umount -v $mnt
  278. # Delete mount-point if empty and not mounted
  279. [ -d $mnt ] && [ -z "$(ls -A $mnt)" ] && ! mountpoint -q $mnt \
  280. && rm -R $mnt && echo "🗑️ mountpoint $mnt deleted"
  281. }
  282. postinstall(){
  283. ####----REBOOT into the new system, so we'll have dbus running
  284. localectl set-locale LANG=de_DE.UTF-8 # Default for LC_* variables not set.
  285. localectl set-locale LC_MESSAGES=en_US.UTF-8 # System messages.
  286. #localectl set-locale LC_RESPONSE=en_US.UTF-8 # How responses (such as Yes and No) appear
  287. update-locale
  288. timedatectl set-timezone Europe/Berlin
  289. }
  290. # Switch to functions...
  291. case $1 in
  292. grmlnetwork)
  293. echo Setup network in grml
  294. grmlnetwork
  295. ;;
  296. install)
  297. echo "Stage 1: Start installation"
  298. install
  299. ;;
  300. install2)
  301. echo "Stage 2: Start installation in chroot"
  302. install2
  303. ;;
  304. bootloader)
  305. echo "Stage 3: Install bootloader and unmount chroot"
  306. bootloader
  307. unmount
  308. echo "👍 We're done and can reboot now"
  309. ;;
  310. postinstall)
  311. echo "Stage 4: Start post-installation in live system"
  312. postinstall
  313. ;;
  314. unmount)
  315. echo "Unmount chroot, e.g. in case installation fails"
  316. unmount
  317. ;;
  318. *)
  319. echo "Valid functions are: grmlnetwork, install, postinstall and unmount" >&2
  320. ;;
  321. esac