├── README.md └── ubuntu_server_encrypted_root_zfs.sh /README.md: -------------------------------------------------------------------------------- 1 | # Ubuntu zfsbootmenu install script 2 | 3 | This script creates an Ubuntu installation using the ZFS filesystem. The installation has integrated snapshot management using pyznap. Snapshots can be rolled back remotely at boot over ssh using zfsbootmenu. This is useful where there is no physical access to the machine. 4 | 5 | Snapshots allow you to rollback your system to a previous state if there is a problem. The system automatically creates snapshots on a timer and also when the system is updated with apt. Snapshots are pruned over time to keep fewer older snapshots. 6 | 7 | Supports: 8 | - Ubuntu 22.04, 24.04. 9 | - Root filesystem on ZFS. 10 | - Choose from: Ubuntu Server, Ubuntu Desktop, Kubuntu, Xubuntu, Budgie, and Ubuntu MATE. 11 | - Single, mirror, raid0, raidz1, raidz2, and raidz3 topologies. 12 | - LUKS and native ZFS encryption. 13 | - Remote unlocking of encrypted pools at boot over SSH. 14 | - Automated system snapshots taken on a timer and also on system updates. 15 | - Remote rollback of snapshots at boot for system recovery over SSH. 16 | - Creation of a separate encrypted data pool (single/mirror/raidz). 17 | 18 | ## Usage 19 | Boot the system with an Ubuntu live desktop iso. Start the terminal (Ctrl+Alt+T) and enter the following. 20 | 21 | git clone https://github.com/Sithuk/ubuntu-server-zfsbootmenu.git ~/ubuntu-server-zfsbootmenu 22 | cd ~/ubuntu-server-zfsbootmenu 23 | chmod +x ubuntu_server_encrypted_root_zfs.sh 24 | 25 | Edit the variables in the ubuntu_server_encrypted_root_zfs.sh file to your preferences. 26 | 27 | nano ubuntu_server_encrypted_root_zfs.sh 28 | 29 | Run the "initial" option of the script. 30 | 31 | ./ubuntu_server_encrypted_root_zfs.sh initial 32 | 33 | Reboot after the initial installation completes and login to the new install. Username and password is as set in the script variables. Then run the second part of the script. 34 | 35 | ./ubuntu_server_encrypted_root_zfs.sh postreboot 36 | 37 | ## Optional: Remote access during boot 38 | The script includes an optional feature to provide remote access during boot. Remote access over ssh allows the system state to be rolled back to a previous snapshot without physical access to the system. This is helpful to return a system to a bootable state following a failed upgrade. 39 | 40 | Run the following optional part of the script to enable remote access to zfsbootmenu during boot. Guidance on the use of zfsbootmenu can be found at its project website linked in the credits below. 41 | 42 | ./ubuntu_server_encrypted_root_zfs.sh remoteaccess 43 | 44 | ## Optional: Create a zfs data pool 45 | The script includes an optional feature to create an encrypted zfs data pool on a non-root drive. The data pool will be unlocked automatically after the root drive password is entered at boot. 46 | 47 | ./ubuntu_server_encrypted_root_zfs.sh datapool 48 | 49 | ## FAQ 50 | Additional guidance and notes can be found in the script. 51 | 1. How do I rollback the system using a snapshot in zfsbootmenu? 52 | 53 | You can rollback to a snapshot by doing the following, for example if an upgrade does not work and you wish to revert to a previous state. I recommend testing any changes out in a virtual machine first before rolling them out in a production environment. 54 | - Reboot and enter zfsbootmenu 55 | - Select the boot environment and press Ctrl+S to show the snapshots. 56 | - Select the pre-upgrade snapshot and choose one of the following options. Either option will provide the ability to boot into the system as it was pre-upgrade. 57 | 58 | - Press Enter to create a "duplicate" boot environment. Zfsbootmenu will create a new boot environment that is entirely independent of the upgraded boot environment and its snapshots. The down sides of the duplicate option are that: 59 | - it requires sufficient disk space to create the duplicate; and 60 | - snapshots linked to the previous boot environment will not be duplicated. 61 | 62 | - Press Ctrl+X to "clone and promote". Zfsbootmenu will create a new boot environment that will have all the snapshot history up to the point the snapshot was created. The new boot environment will consume little additional space. The zfsbootmenu authors recommend the "clone and promote" option to rollback. 63 | 64 | 2. How do I delete a boot environment I no longer need? 65 | 66 | You can delete a boot environment you no longer need using "zfs destroy". You can do this by booting into a running system or from zfsbootmenu. Zfsbootmenu will list the root datasets that contain a linux kernel on its main menu. You can make a note of the dataset you want to delete from there or you can use "zfs list" from a command line. 67 | 68 | - Delete a boot environment from a running system 69 | - Use "zfs destroy" to delete the dataset that corresponds to the boot environment. For example, if you want to delete a root dataset called "ubuntu.2022.10.01" then you can enter the command "zfs destroy -r rpool/ROOT/ubuntu.2022.10.01". 70 | 71 | - Delete a boot environment from zfsbootmenu 72 | - From the main menu, select the boot environment you want to destroy. Press CTRL+W to re-import the pool as read/write, then CTRL+R to enter the recovery shell. You can then use "zfs destroy" as in the point above. Press CTRL+D to exit the shell and return to the menu when done. 73 | 74 | 3. Can I upgrade the system normally using do-release-upgrade? 75 | - Zfsbootmenu 76 | 77 | It is possible that upgrading ubuntu will cause a newer zfs version to be installed that is unsupported by zfsbootmenu. The system may not be able to boot if the zfs root pool is upgraded beyond what is supported by zfsbootmenu. Create a test system in a virtual machine first to duplicate your setup and test the upgrade process. 78 | - Pyznap 79 | 80 | Pyznap is not included as a package in the ubuntu repos at present. It may need to be re-compiled and re-installed. You can reference the install script for the relevant code to re-compile and re-install. 81 | 82 | 4. How do I change the password on a natively encrypted zfs root pool? 83 | 84 | You can change the password of your encrypted root as follows. Change "rpool" to the name of your root pool. 85 | - Update root pool password file. 86 | 87 | `nano /etc/zfs/rpool.key` 88 | - Update root pool key. 89 | 90 | `zfs change-key -o keylocation=file:///etc/zfs/rpool.key -o keyformat=passphrase rpool` 91 | - Optional: If you have an encrypted data pool that unlocks at boot using the root pool password, then update its key too. Change "datapool" to the name of your data pool. 92 | 93 | `zfs change-key -o keylocation=file:///etc/zfs/rpool.key -o keyformat=passphrase datapool` 94 | - Update initramfs. 95 | 96 | `update-initramfs -u -k all` 97 | 98 | ## Discussion threads 99 | Please use the discussions section. \ 100 | https://github.com/Sithuk/ubuntu-server-zfsbootmenu/discussions 101 | 102 | For historical reference, the initial discussion thread can be found on reddit. 103 | https://www.reddit.com/r/zfs/comments/mj4nfa/ubuntu_server_2104_native_encrypted_root_on_zfs/ 104 | 105 | ## Credits 106 | ahesford E39M5S62/zdykstra (https://github.com/zbm-dev/zfsbootmenu) 107 | 108 | cythoning (https://github.com/yboetz/pyznap) 109 | 110 | rlaager (https://openzfs.github.io/openzfs-docs/Getting%20Started/Ubuntu/Ubuntu%2022.04%20Root%20on%20ZFS.html) 111 | -------------------------------------------------------------------------------- /ubuntu_server_encrypted_root_zfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ##Script installs ubuntu on the zfs file system with snapshot rollback at boot. Options include encryption and headless remote unlocking. 3 | ##Script: https://github.com/Sithuk/ubuntu-server-zfsbootmenu 4 | ##Script date: 2025-01-13 5 | 6 | # shellcheck disable=SC2317 # Don't warn about unreachable commands in this file 7 | 8 | set -euo pipefail 9 | #set -x 10 | 11 | ##Usage: initial | postreboot | remoteaccess | datapool 12 | 13 | ##Script to be run in two parts. 14 | ##Part 1: Run with "initial" option from Ubuntu live iso (desktop version) terminal. 15 | ##Part 2: Reboot into new install. 16 | ##Part 2: Run with "postreboot" option after first boot into new install (login as user/password defined in variable section below). 17 | 18 | ##Remote access can be installed by either: 19 | ## setting the remoteaccess variable to "yes" in the variables section below, or 20 | ## running the script with the "remoteaccess" option after part 1 and part 2 are run. 21 | ##Connect as "root" on port 222 to the server's ip address. 22 | ##It's better to leave the remoteaccess variable below as "no" and run the script with the "remoteaccess" option 23 | ## as that will use the user's authorized_keys file. Setting the remoteaccess variable to "yes" will use root's authorized_keys. 24 | ##Login as "root" during remote access, even if using a user's authorized_keys file. No other users are available during remote access. 25 | 26 | ##A non-root drive can be setup as an encrypted data pool using the "datapool" option. 27 | ##The drive will be unlocked automatically after the root drive password is entered at boot. 28 | 29 | ##If running in a Virtualbox virtualmachine, setup tips below: 30 | ##1. Enable EFI. 31 | ##2. Set networking to bridged mode so VM gets its own IP. Fewer problems with ubuntu keyserver. 32 | ##3. Minimum drive size of 5GB. 33 | 34 | ##Rescuing using a Live CD 35 | ##zpool export -a #Export all pools. 36 | ##zpool import -N -R /mnt rpool #"rpool" should be the root pool name. 37 | ##zfs load-key -r -L prompt -a #-r Recursively loads the keys. -a Loads the keys for all encryption roots in all imported pools. -L is for a keylocation or to "prompt" user for an input. 38 | ##zfs mount -a #Mount all datasets. 39 | 40 | ##Variables: 41 | ubuntuver="noble" #Ubuntu release to install. "jammy" (22.04). "noble" (24.04). 42 | distro_variant="server" #Ubuntu variant to install. "server" (Ubuntu server; cli only.) "desktop" (Default Ubuntu desktop install). "kubuntu" (KDE plasma desktop variant). "xubuntu" (Xfce desktop variant). "budgie" (Budgie desktop variant). "MATE" (MATE desktop variant). 43 | user="testuser" #Username for new install. 44 | PASSWORD="testuser" #Password for user in new install. 45 | hostname="ubuntu" #Name to identify the main system on the network. An underscore is DNS non-compliant. 46 | zfs_root_password="testtest" #Password for encrypted root pool. Minimum 8 characters. "" for no password encrypted protection. Unlocking root pool also unlocks data pool, unless the root pool has no password protection, then a separate data pool password can be set below. 47 | zfs_root_encrypt="native" #Encryption type. "native" for native zfs encryption. "luks" for luks. Required if there is a root pool password, otherwise ignored. 48 | locale="en_GB.UTF-8" #New install language setting. 49 | timezone="Europe/London" #New install timezone setting. 50 | zfs_rpool_ashift="12" #Drive setting for zfs pool. ashift=9 means 512B sectors (used by all ancient drives), ashift=12 means 4KiB sectors (used by most modern hard drives), and ashift=13 means 8KiB sectors (used by some modern SSDs). 51 | mirror_archive="" #"" to use the default ubuntu repository. Set to an ISO 3166-1 alpha-2 country code to use a country mirror archive, e.g. "GB". A speed test is run and the fastest archive is selected. Country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 52 | 53 | RPOOL="rpool" #Root pool name. 54 | topology_root="single" #"single", "mirror", "raid0", "raidz1", "raidz2", or "raidz3" topology on root pool. 55 | disks_root="1" #Number of disks in array for root pool. Not used with single topology. 56 | EFI_boot_size="512" #EFI boot loader partition size in mebibytes (MiB). 57 | swap_size="500" #Swap partition size in mebibytes (MiB). Size of swap will be larger than defined here with Raidz topologies. 58 | datapool="datapool" #Non-root drive data pool name. 59 | topology_data="single" #"single", "mirror", "raid0", "raidz1", "raidz2", or "raidz3" topology on data pool. 60 | disks_data="1" #Number of disks in array for data pool. Not used with single topology. 61 | zfs_data_password="testtest" #If no root pool password is set, a data pool password can be set here. Minimum 8 characters. "" for no password protection. 62 | zfs_data_encrypt="native" #Encryption type. "native" for native zfs encryption. "luks" for luks. Required if there is a data pool password, otherwise ignored. 63 | datapoolmount="/mnt/$datapool" #Non-root drive data pool mount point in new install. 64 | zfs_dpool_ashift="12" #See notes for rpool ashift. If ashift is set too low, a significant read/write penalty is incurred. Virtually no penalty if set higher. 65 | zfs_compression="zstd" #"lz4" is the zfs default; "zstd" may offer better compression at a cost of higher cpu usage. 66 | mountpoint="/mnt/ub_server" #Mountpoint in live iso. 67 | remoteaccess_first_boot="no" #"yes" to enable remoteaccess during first boot. Recommend leaving as "no" and run script with "remoteaccess". See notes in section above. 68 | timeout_rEFInd="3" #Timeout in seconds for rEFInd boot screen until default choice selected. 69 | timeout_zbm_no_remote_access="3" #Timeout in seconds for zfsbootmenu when no remote access enabled. 70 | timeout_zbm_remote_access="45" #Timeout in seconds for zfsbootmenu when remote access enabled. The password prompt for an encrypted root pool with allow an indefinite time to connect. An unencrypted root pool will boot the system when the timer runs out, preventing remote access. 71 | quiet_boot="yes" #Set to "no" to show boot sequence. 72 | ethprefix="e" #First letter of ethernet interface. Used to identify ethernet interface to setup networking in new install. 73 | install_log="ubuntu_setup_zfs_root.log" #Installation log filename. 74 | log_loc="/var/log" #Installation log location. 75 | ipv6_apt_fix_live_iso="no" #Try setting to "yes" gif apt-get is slow in the ubuntu live iso. Doesn't affect ipv6 functionality in the new install. 76 | remoteaccess_hostname="zbm" #Name to identify the zfsbootmenu system on the network. 77 | remoteaccess_ip_config="dhcp" #"dhcp", "dhcp,dhcp6", "dhcp6", or "static". Automatic (dhcp) or static IP assignment for zfsbootmenu remote access. 78 | remoteaccess_ip="192.168.0.222" #Remote access static IP address to connect to ZFSBootMenu. Not used for automatic IP configuration. 79 | remoteaccess_netmask="255.255.255.0" #Remote access subnet mask. Not used for "dhcp" automatic IP configuration. 80 | ubuntu_original="http://archive.ubuntu.com/ubuntu" #Default ubuntu repository. 81 | install_warning_level="PRIORITY=critical" #"PRIORITY=critical", or "FRONTEND=noninteractive". Pause install to show critical messages only or do not pause (noninteractive). Script still pauses for keyboard selection. 82 | extra_programs="no" #"yes", or "no". Install additional programs if not included in the ubuntu distro package. Programs: cifs-utils, locate, man-db, openssh-server, tldr. 83 | 84 | ##Check for root priviliges 85 | if [ "$(id -u)" -ne 0 ]; then 86 | echo "Please run as root." 87 | exit 1 88 | fi 89 | 90 | ##Check for EFI boot environment 91 | if [ -d /sys/firmware/efi ]; then 92 | echo "Boot environment check passed. Found EFI boot environment." 93 | else 94 | echo "Boot environment check failed. EFI boot environment not found. Script requires EFI." 95 | exit 1 96 | fi 97 | 98 | ##Check encryption defined if password defined 99 | if [ -n "$zfs_root_password" ]; 100 | then 101 | if [ -z $zfs_root_encrypt ]; 102 | then 103 | echo "Password entered but no encryption method defined. Please define the zfs_root_encrypt variable." 104 | else true 105 | fi 106 | else true 107 | fi 108 | 109 | ##Functions 110 | live_desktop_check(){ 111 | ##Check for live desktop environment 112 | if [ "$(dpkg -l ubuntu-desktop)" ]; 113 | then 114 | echo "Desktop environment test passed." 115 | if grep casper /proc/cmdline >/dev/null 2>&1; 116 | then 117 | echo "Live environment present." 118 | else 119 | echo "Live environment test failed. Run script from a live desktop environment." 120 | exit 1 121 | fi 122 | else 123 | echo "Desktop environment test failed. Run script from a live desktop environment." 124 | exit 1 125 | fi 126 | 127 | ##Check live desktop version 128 | live_desktop_version="$( . /etc/os-release && echo ${VERSION_CODENAME} )" 129 | if [ $(echo "${live_desktop_version}" | tr '[:upper:]' '[:lower:]') = $(echo "${ubuntuver}" | tr '[:upper:]' '[:lower:]') ]; 130 | then 131 | echo "Live environment version test passed." 132 | else 133 | ##The zfs pool will be created with the zfs version of the live environment. 134 | ##If the zfs version is older in the distro to be installed than in the live environment then zfsbootmenu may be unable to mount the root pool at boot. 135 | ##The system will then fail to load. The reason is that Zfsbootmenu is installed with the zfs version in the distro to be installed, not the version in the live environment. 136 | echo "Live environment version test failed." 137 | echo "The live environment version does not match the Ubuntu version to be installed. Re-run script from an environment which matches the version to be installed. This is to avoid zfs version conflicts." 138 | exit 1 139 | fi 140 | 141 | } 142 | 143 | keyboard_console_settings(){ 144 | kb_console_settings=/tmp/kb_console_selections.conf 145 | 146 | apt install -y debconf-utils 147 | 148 | export DEBIAN_PRIORITY=high 149 | export DEBIAN_FRONTEND=dialog 150 | dpkg-reconfigure keyboard-configuration 151 | dpkg-reconfigure console-setup 152 | export DEBIAN_"${install_warning_level}" 153 | 154 | debconf-get-selections | grep keyboard-configuration | tee "${kb_console_settings}" 155 | debconf-get-selections | grep console-setup | tee -a "${kb_console_settings}" 156 | } 157 | 158 | topology_min_disk_check(){ 159 | ##Check that number of disks meets minimum number for selected topology. Disks_{root,data} variable ignored for single topology. 160 | pool="$1" 161 | echo "Checking script variables for $pool pool..." 162 | 163 | topology_pool_pointer="topology_$pool" 164 | eval echo "User defined topology for ${pool} pool: \$${topology_pool_pointer}" 165 | eval topology_pool_pointer="\$${topology_pool_pointer}" 166 | topology_pool_pointer_tmp="/tmp/topology_pool_pointer.txt" 167 | printf "%s" "${topology_pool_pointer}" > "${topology_pool_pointer_tmp}" 168 | 169 | disks_pointer="disks_${pool}" 170 | eval echo "User defined number of disks in pool: \$${disks_pointer}" 171 | eval disks_pointer=\$"${disks_pointer}" 172 | disks_pointer_tmp="/tmp/disks_pointer.txt" 173 | printf "%s" "${disks_pointer}" > "${disks_pointer_tmp}" 174 | 175 | num_disks_check(){ 176 | min_num_disks="$1" 177 | 178 | if [ "$disks_pointer" -lt "$min_num_disks" ] 179 | then 180 | echo "A ${topology_pool_pointer} topology requires at least ${min_num_disks} disks. Check variable for number of disks or change the selected topology." 181 | exit 1 182 | else true 183 | fi 184 | } 185 | 186 | case "$topology_pool_pointer" in 187 | single) true ;; 188 | 189 | mirror|raid0|raidz1) 190 | num_disks_check "2" 191 | ;; 192 | 193 | raidz2) 194 | num_disks_check "3" 195 | ;; 196 | 197 | raidz3) 198 | num_disks_check "4" 199 | ;; 200 | 201 | *) 202 | echo "Pool topology not recognised. Check pool topology variable." 203 | exit 1 204 | ;; 205 | esac 206 | printf "%s\n\n" "Minimum disk topology check passed for $pool pool." 207 | } 208 | 209 | logFunc(){ 210 | # Log everything we do 211 | exec > >(tee -a "$log_loc"/"$install_log") 2>&1 212 | } 213 | 214 | disclaimer(){ 215 | echo "***WARNING*** This script could wipe out all your data, or worse! I am not responsible for your decisions. Press Enter to Continue or CTRL+C to abort." 216 | read -r _ 217 | } 218 | 219 | connectivity_check(){ 220 | ##https://unix.stackexchange.com/a/190610 221 | test_site=google.com 222 | if nc -zw1 "${test_site}" 443 223 | then 224 | echo "Internet connectivity test passed." 225 | else 226 | echo "No internet connectivity available. Please check connectivity." 227 | exit 1 228 | fi 229 | } 230 | 231 | getdiskID(){ 232 | pool="$1" 233 | diskidnum="$2" 234 | total_discs="$3" 235 | 236 | ##Get disk ID(s) 237 | 238 | manual_read(){ 239 | ls -la /dev/disk/by-id 240 | echo "Enter Disk ID for disk $diskidnum of $total_discs on $pool pool (must match exactly):" 241 | read -r DISKID 242 | } 243 | #manual_read 244 | 245 | menu_read(){ 246 | diskidmenu_loc="/tmp/diskidmenu.txt" 247 | ls -la /dev/disk/by-id | awk '{ print $9, $11 }' | sed -e '1,3d' | grep -v "part\|CD-ROM" > "$diskidmenu_loc" 248 | 249 | echo "Please enter Disk ID option for disk $diskidnum of $total_discs on $pool pool." 250 | nl "$diskidmenu_loc" 251 | count="$(wc -l "$diskidmenu_loc" | cut -f 1 -d' ')" 252 | n="" 253 | while true; 254 | do 255 | read -r -p 'Select option: ' n 256 | if [ "$n" -eq "$n" ] && [ "$n" -gt 0 ] && [ "$n" -le "$count" ]; then 257 | break 258 | fi 259 | done 260 | DISKID="$(sed -n "${n}p" "$diskidmenu_loc" | awk '{ print $1 }' )" 261 | printf "%s\n\n" "Option number $n selected: '$DISKID'" 262 | } 263 | menu_read 264 | 265 | #DISKID=ata-VBOX_HARDDISK_VBXXXXXXXX-XXXXXXXX ##manual override 266 | ##error check 267 | errchk="$(find /dev/disk/by-id -maxdepth 1 -mindepth 1 -name "$DISKID")" 268 | if [ -z "$errchk" ]; 269 | then 270 | echo "Disk ID not found. Exiting." 271 | exit 1 272 | fi 273 | 274 | errchk="$(grep "$DISKID" /tmp/diskid_check_"${pool}".txt || true)" 275 | if [ -n "$errchk" ]; 276 | then 277 | echo "Disk ID has already been entered. Exiting." 278 | exit 1 279 | fi 280 | 281 | printf "%s\n" "$DISKID" >> /tmp/diskid_check_"${pool}".txt 282 | } 283 | 284 | getdiskID_pool(){ 285 | pool="$1" 286 | 287 | ##Check that number of disks meets minimum number for selected topology. 288 | topology_min_disk_check "$pool" 289 | 290 | echo "Carefully enter the ID of the disk(s) YOU WANT TO DESTROY in the next step to ensure no data is accidentally lost." 291 | 292 | ##Create temp file to check for duplicated disk ID entry. 293 | true > /tmp/diskid_check_"${pool}".txt 294 | 295 | case "$topology_pool_pointer" in 296 | single) 297 | echo "The $pool pool disk topology is a single disk." 298 | getdiskID "$pool" "1" "1" 299 | ;; 300 | 301 | mirror|raid0|raidz*) 302 | echo "The $pool pool disk topology is $topology_pool_pointer with $disks_pointer disks." 303 | diskidnum="1" 304 | while [ "$diskidnum" -le "$disks_pointer" ]; 305 | do 306 | getdiskID "$pool" "$diskidnum" "$disks_pointer" 307 | diskidnum=$(( diskidnum + 1 )) 308 | done 309 | ;; 310 | 311 | *) 312 | echo "Pool topology not recognised. Check pool topology variable." 313 | exit 1 314 | ;; 315 | 316 | esac 317 | 318 | } 319 | 320 | clear_partition_table(){ 321 | pool="$1" #root or data 322 | while IFS= read -r diskidnum; 323 | do 324 | echo "Clearing partition table on disk ${diskidnum}." 325 | sgdisk --zap-all /dev/disk/by-id/"$diskidnum" 326 | done < /tmp/diskid_check_"${pool}".txt 327 | } 328 | 329 | identify_ubuntu_dataset_uuid(){ 330 | rootzfs_full_name=0 331 | rootzfs_full_name="$(zfs list -o name | awk '/ROOT\/ubuntu/{print $1;exit}'|sed -e 's,^.*/,,')" 332 | } 333 | 334 | ipv6_apt_live_iso_fix(){ 335 | ##Try diabling ipv6 in the live iso if setting the preference to ipv4 doesn't work \ 336 | ## to resolve slow apt-get and slow debootstrap in the live Ubuntu iso. 337 | ##https://askubuntu.com/questions/620317/apt-get-update-stuck-connecting-to-security-ubuntu-com 338 | 339 | prefer_ipv4(){ 340 | sed -i 's,#precedence ::ffff:0:0/96 100,precedence ::ffff:0:0/96 100,' /etc/gai.conf 341 | } 342 | 343 | dis_ipv6(){ 344 | cat >> /etc/sysctl.conf <<-EOF 345 | net.ipv6.conf.all.disable_ipv6 = 1 346 | #net.ipv6.conf.default.disable_ipv6 = 1 347 | #net.ipv6.conf.lo.disable_ipv6 = 1 348 | EOF 349 | tail -n 3 /etc/sysctl.conf 350 | sudo sysctl -p /etc/sysctl.conf 351 | sudo netplan apply 352 | } 353 | 354 | if [ "$ipv6_apt_fix_live_iso" = "yes" ]; then 355 | prefer_ipv4 356 | #dis_ipv6 357 | else 358 | true 359 | fi 360 | 361 | } 362 | 363 | identify_apt_data_sources(){ 364 | 365 | if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; 366 | then 367 | apt_data_sources_loc="/etc/apt/sources.list.d/ubuntu.sources" 368 | else 369 | apt_data_sources_loc="/etc/apt/sources.list" 370 | fi 371 | 372 | } 373 | 374 | apt_sources(){ 375 | ##Initial system apt sources config 376 | script_env="$1" ##chroot, base 377 | source_archive="$2" 378 | 379 | cat > /tmp/apt_sources.sh <<-EOF 380 | #!/bin/sh 381 | if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; 382 | then 383 | apt_data_sources_loc="/etc/apt/sources.list.d/ubuntu.sources" 384 | cp "\${apt_data_sources_loc}" "\${apt_data_sources_loc}".orig 385 | if [ "${ubuntu_original}" != "${source_archive}" ]; 386 | then 387 | sed -i 's,${ubuntu_original},${source_archive},g' "\${apt_data_sources_loc}" 388 | else true 389 | fi 390 | else 391 | apt_data_sources_loc="/etc/apt/sources.list" 392 | cp "\${apt_data_sources_loc}" "\${apt_data_sources_loc}".orig 393 | 394 | cat > "\${apt_data_sources_loc}" <<-EOLIST 395 | deb ${ubuntu_original} $ubuntuver main universe restricted multiverse 396 | #deb-src ${ubuntu_original} $ubuntuver main universe restricted multiverse 397 | 398 | deb ${ubuntu_original} $ubuntuver-updates main universe restricted multiverse 399 | #deb-src ${ubuntu_original} $ubuntuver-updates main universe restricted multiverse 400 | 401 | deb ${ubuntu_original} $ubuntuver-backports main universe restricted multiverse 402 | #deb-src ${ubuntu_original} $ubuntuver-backports main universe restricted multiverse 403 | 404 | deb http://security.ubuntu.com/ubuntu $ubuntuver-security main universe restricted multiverse 405 | #deb-src http://security.ubuntu.com/ubuntu $ubuntuver-security main universe restricted multiverse 406 | EOLIST 407 | 408 | if [ "${ubuntu_original}" != "${source_archive}" ]; 409 | then 410 | cp "\${apt_data_sources_loc}" "\${apt_data_sources_loc}".non-mirror 411 | sed -i 's,${ubuntu_original},${source_archive},g' "\${apt_data_sources_loc}" 412 | else true 413 | fi 414 | 415 | fi 416 | EOF 417 | 418 | case "${script_env}" in 419 | chroot) 420 | cp /tmp/apt_sources.sh "$mountpoint"/tmp 421 | chroot "$mountpoint" /bin/bash -x /tmp/apt_sources.sh 422 | ;; 423 | base) 424 | /bin/bash /tmp/apt_sources.sh 425 | ;; 426 | *) 427 | exit 1 428 | ;; 429 | esac 430 | 431 | } 432 | 433 | apt_mirror_source(){ 434 | 435 | identify_apt_data_sources 436 | 437 | identify_apt_mirror(){ 438 | ##Identify fastest mirror. 439 | echo "Choosing fastest up-to-date ubuntu mirror based on download speed." 440 | apt update 441 | apt install -y curl 442 | ubuntu_mirror=$({ 443 | ##Choose mirrors that are up-to-date by checking the Last-Modified header. 444 | ##https://github.com/actions/runner-images/issues/675#issuecomment-1381837292 445 | { 446 | curl -s http://mirrors.ubuntu.com/"${mirror_archive}".txt | shuf -n 20 447 | } | xargs -I {} sh -c 'echo "$(curl -m 5 -sI {}dists/$(lsb_release -c | cut -f2)-security/Contents-$(dpkg --print-architecture).gz | sed s/\\r\$//|grep Last-Modified|awk -F": " "{ print \$2 }" | LANG=C date -f- -u +%s)" "{}"' | sort -rg | awk '{ if (NR==1) TS=$1; if ($1 == TS) print $2 }' 448 | } | xargs -I {} sh -c 'echo "$(curl -r 0-102400 -m 5 -s -w %{speed_download} -o /dev/null {}ls-lR.gz)" {}' \ 449 | | sort -g -r | head -1 | awk '{ print $2 }') 450 | } 451 | identify_apt_mirror 452 | 453 | if [ -z "${ubuntu_mirror}" ]; 454 | then 455 | echo "No mirror identified. No changes made." 456 | else 457 | if [ "${ubuntu_original}" != "${ubuntu_mirror}" ]; 458 | then 459 | cp "${apt_data_sources_loc}" "${apt_data_sources_loc}".non-mirror 460 | sed -i "s,${ubuntu_original},${ubuntu_mirror},g" "${apt_data_sources_loc}" 461 | echo "Selected '${ubuntu_mirror}'." 462 | else 463 | echo "Identified mirror is already selected. No changes made." 464 | fi 465 | fi 466 | 467 | } 468 | 469 | reinstate_apt(){ 470 | script_env="$1" ##chroot, base 471 | 472 | cat > /tmp/reinstate_apt.sh <<-EOF 473 | 474 | #!/bin/sh 475 | if [ -n "${mirror_archive}" ]; 476 | then 477 | 478 | if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; 479 | then 480 | apt_data_sources_loc="/etc/apt/sources.list.d/ubuntu.sources" 481 | else 482 | apt_data_sources_loc="/etc/apt/sources.list" 483 | fi 484 | 485 | cp "\${apt_data_sources_loc}" /tmp 486 | 487 | if [ -f "\${apt_data_sources_loc}".non-mirror ]; 488 | then 489 | cp "\${apt_data_sources_loc}".non-mirror /tmp 490 | mv "\${apt_data_sources_loc}".non-mirror "\${apt_data_sources_loc}" 491 | else true 492 | fi 493 | 494 | else true 495 | fi 496 | 497 | if [ -f /etc/apt/apt.conf.d/30apt_error_on_transient ]; 498 | then 499 | mv /etc/apt/apt.conf.d/30apt_error_on_transient /tmp ##Remove apt update error on transient in new install. 500 | else true 501 | fi 502 | 503 | EOF 504 | 505 | case "${script_env}" in 506 | chroot) 507 | cp /tmp/reinstate_apt.sh "$mountpoint"/tmp 508 | chroot "$mountpoint" /bin/bash -x /tmp/reinstate_apt.sh 509 | ;; 510 | base) 511 | /bin/bash /tmp/reinstate_apt.sh 512 | ;; 513 | *) 514 | exit 1 515 | ;; 516 | esac 517 | 518 | } 519 | 520 | logcopy(){ 521 | ##Copy install log to new installation. 522 | if [ -d "$mountpoint" ]; then 523 | cp "$log_loc"/"$install_log" "$mountpoint""$log_loc" 524 | echo "Log file copied into new installation at ${log_loc}." 525 | else 526 | echo "No mountpoint dir present. Install log not copied." 527 | fi 528 | } 529 | 530 | script_copy(){ 531 | ##Copy script to new installation 532 | cp "$(readlink -f "$0")" "$mountpoint"/home/"${user}"/ 533 | script_new_install_loc=/home/"${user}"/"$(basename "$0")" 534 | 535 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 536 | chown "${user}":"${user}" "$script_new_install_loc" 537 | chmod +x "$script_new_install_loc" 538 | EOCHROOT 539 | 540 | if [ -f "$mountpoint""$script_new_install_loc" ]; 541 | then 542 | echo "Install script copied to ${user} home directory in new installation." 543 | else 544 | echo "Error copying install script to new installation." 545 | fi 546 | } 547 | 548 | create_zpool_Func(){ 549 | 550 | ##Create zfs pool 551 | pool=$1 ##root, data 552 | 553 | ##Set pool variables 554 | case "$pool" in 555 | root) 556 | ashift="$zfs_rpool_ashift" 557 | keylocation="prompt" 558 | zpool_password="$zfs_root_password" 559 | zpool_encrypt="$zfs_root_encrypt" 560 | zpool_partition="-part3" 561 | zpool_name="$RPOOL" 562 | topology_pool="${topology_root}" 563 | ;; 564 | 565 | data) 566 | ashift="$zfs_dpool_ashift" 567 | 568 | if [ -n "$zfs_root_password" ]; 569 | then 570 | ##Set data pool key to use rpool key for single unlock at boot. So data pool uses the same password as the root pool. 571 | case "$zfs_root_encrypt" in 572 | native) 573 | datapool_keyloc="/etc/zfs/$RPOOL.key" 574 | ;; 575 | luks) 576 | datapool_keyloc="/etc/cryptsetup-keys.d/$RPOOL.key" 577 | ;; 578 | esac 579 | keylocation="file://$datapool_keyloc" 580 | else 581 | if [ -n "$zfs_data_password" ]; 582 | then 583 | keylocation="prompt" 584 | else 585 | true 586 | fi 587 | fi 588 | 589 | zpool_password="$zfs_data_password" 590 | zpool_encrypt="$zfs_data_encrypt" 591 | zpool_partition="" 592 | zpool_name="$datapool" 593 | topology_pool="${topology_data}" 594 | ;; 595 | esac 596 | 597 | zpool_create_temp="/tmp/${pool}_creation.sh" 598 | cat > "$zpool_create_temp" <<-EOF 599 | zpool create -f \\ 600 | -o ashift="$ashift" \\ 601 | -o autotrim=on \\ 602 | -O acltype=posixacl \\ 603 | -O compression=$zfs_compression \\ 604 | -O normalization=formD \\ 605 | -O relatime=on \\ 606 | -O dnodesize=auto \\ 607 | -O xattr=sa \\ 608 | EOF 609 | 610 | case "$pool" in 611 | root) 612 | echo -O canmount=off \\ >> "$zpool_create_temp" 613 | ;; 614 | esac 615 | 616 | if [ -n "$zpool_password" ]; 617 | then 618 | case "$zpool_encrypt" in 619 | native) 620 | echo "-O encryption=aes-256-gcm -O keylocation=$keylocation -O keyformat=passphrase \\" >> "$zpool_create_temp" 621 | ;; 622 | esac 623 | else 624 | true 625 | fi 626 | 627 | case "$pool" in 628 | root) 629 | echo "-O mountpoint=/ -R $mountpoint \\" >> "$zpool_create_temp" 630 | ;; 631 | data) 632 | echo "-O mountpoint=$datapoolmount \\" >> "$zpool_create_temp" 633 | ;; 634 | esac 635 | 636 | 637 | add_zpool_disks(){ 638 | 639 | loop_counter="$(mktemp)" 640 | echo 1 > "$loop_counter" ##Assign starting counter value. 641 | 642 | while IFS= read -r diskidnum; 643 | do 644 | if [ -n "$zpool_password" ]; 645 | then 646 | 647 | case "$zpool_encrypt" in 648 | 649 | native) 650 | echo "/dev/disk/by-id/${diskidnum}${zpool_partition} \\" >> "$zpool_create_temp" 651 | ;; 652 | 653 | luks) 654 | echo -e "$zpool_password" | cryptsetup -q luksFormat -c aes-xts-plain64 -s 512 -h sha256 /dev/disk/by-id/${diskidnum}${zpool_partition} 655 | 656 | i="$(cat "$loop_counter")" 657 | echo "$i" 658 | luks_dmname_base=luks 659 | luks_dmname=${luks_dmname_base}$i 660 | 661 | ##Check for luks device name conflict 662 | while [ $(find /dev/mapper -name ${luks_dmname} | wc -l) = 1 ]; 663 | do 664 | i=$((i + 1)) ##Increment counter. 665 | luks_dmname=${luks_dmname_base}$i 666 | done 667 | 668 | echo -e "$zpool_password" | cryptsetup luksOpen /dev/disk/by-id/${diskidnum}${zpool_partition} "${luks_dmname}" 669 | printf "%s\n" "${luks_dmname}" >> /tmp/luks_dmname_"${pool}".txt 670 | 671 | echo "/dev/mapper/${luks_dmname} \\" >> "$zpool_create_temp" 672 | 673 | i=$((i + 1)) ##Increment counter. 674 | echo "$i" > "$loop_counter" 675 | ;; 676 | 677 | *) 678 | echo "zpool_encrypt variable not recognised." 679 | exit 1 680 | ;; 681 | 682 | esac 683 | 684 | else 685 | echo "/dev/disk/by-id/${diskidnum}${zpool_partition} \\" >> "$zpool_create_temp" 686 | fi 687 | 688 | done < /tmp/diskid_check_"$pool".txt 689 | 690 | sed -i '$s,\\,,' "$zpool_create_temp" ##Remove escape character at end of file. 691 | } 692 | 693 | 694 | case "${topology_pool}" in 695 | single|raid0) 696 | echo "${zpool_name} \\" >> "$zpool_create_temp" 697 | add_zpool_disks 698 | ;; 699 | 700 | mirror) 701 | echo "${zpool_name} mirror \\" >> "$zpool_create_temp" 702 | add_zpool_disks 703 | ;; 704 | 705 | raidz1) 706 | echo "${zpool_name} raidz1 \\" >> "$zpool_create_temp" 707 | add_zpool_disks 708 | ;; 709 | 710 | raidz2) 711 | echo "${zpool_name} raidz2 \\" >> "$zpool_create_temp" 712 | add_zpool_disks 713 | ;; 714 | 715 | raidz3) 716 | echo "${zpool_name} raidz3 \\" >> "$zpool_create_temp" 717 | add_zpool_disks 718 | ;; 719 | 720 | *) 721 | echo "Pool topology not recognised. Check pool topology variable." 722 | exit 1 723 | ;; 724 | 725 | esac 726 | 727 | echo "$zpool_password" | sh "$zpool_create_temp" 728 | } 729 | 730 | update_crypttab_Func(){ 731 | ##Auto unlock using crypttab and keyfile 732 | 733 | script_env=$1 ##chroot, base 734 | pool=$2 ##root, data 735 | 736 | cat <<-EOH >/tmp/update_crypttab_$pool.sh 737 | 738 | ##Set pool variables 739 | case "$pool" in 740 | root) 741 | zpool_password="$zfs_root_password" 742 | zpool_partition="-part3" 743 | crypttab_parameters="luks,discard,initramfs" 744 | ;; 745 | 746 | data) 747 | zpool_password="$zfs_data_password" 748 | zpool_partition="" 749 | crypttab_parameters="luks,discard" 750 | ;; 751 | esac 752 | 753 | apt install -y cryptsetup 754 | 755 | loop_counter="\$(mktemp)" 756 | echo 1 > "\${loop_counter}" ##Assign starting counter value. 757 | 758 | while IFS= read -r diskidnum; 759 | do 760 | i="\$(cat "\$loop_counter")" 761 | echo "\$i" 762 | luks_dmname="\$(sed "\${i}q;d" /tmp/luks_dmname_"${pool}".txt)" 763 | 764 | blkid_luks="\$(blkid -s UUID -o value /dev/disk/by-id/\${diskidnum}\${zpool_partition})" 765 | 766 | echo "\${zpool_password}" | cryptsetup -v luksAddKey /dev/disk/by-uuid/\${blkid_luks} /etc/cryptsetup-keys.d/$RPOOL.key 767 | cryptsetup luksDump /dev/disk/by-uuid/\${blkid_luks} 768 | 769 | ##https://cryptsetup-team.pages.debian.net/cryptsetup/README.initramfs.html 770 | echo \${luks_dmname} UUID=\${blkid_luks} /etc/cryptsetup-keys.d/$RPOOL.key \${crypttab_parameters} >> /etc/crypttab 771 | 772 | i=\$((i + 1)) ##Increment counter. 773 | echo "\$i" > "\$loop_counter" 774 | 775 | done < /tmp/diskid_check_"${pool}".txt 776 | 777 | ##https://cryptsetup-team.pages.debian.net/cryptsetup/README.initramfs.html 778 | sed -i 's,#KEYFILE_PATTERN=,KEYFILE_PATTERN="/etc/cryptsetup-keys.d/*.key",' /etc/cryptsetup-initramfs/conf-hook 779 | 780 | EOH 781 | 782 | case "${script_env}" in 783 | chroot) 784 | cp /tmp/diskid_check_"${pool}".txt "$mountpoint"/tmp 785 | cp /tmp/update_crypttab_${pool}.sh "$mountpoint"/tmp 786 | chroot "$mountpoint" /bin/bash -x /tmp/update_crypttab_$pool.sh 787 | ;; 788 | base) 789 | ##Test for live environment. 790 | if grep casper /proc/cmdline >/dev/null 2>&1; 791 | then 792 | echo "Live environment present. Reboot into new installation." 793 | exit 1 794 | else 795 | /bin/bash /tmp/update_crypttab_$pool.sh 796 | fi 797 | ;; 798 | *) 799 | exit 1 800 | ;; 801 | esac 802 | 803 | } 804 | 805 | debootstrap_part1_Func(){ 806 | export DEBIAN_"${install_warning_level}" 807 | 808 | ##Error out script on apt update error such as network failure during package download. 809 | ##https://bugs.launchpad.net/ubuntu/+source/apt/+bug/1693900 810 | cat > /etc/apt/apt.conf.d/30apt_error_on_transient <<-EOF 811 | APT::Update::Error-Mode "any"; 812 | EOF 813 | 814 | ##Identify apt sources 815 | identify_apt_data_sources 816 | 817 | ##Identify live iso default archive 818 | #ubuntu_original="$(grep -v '^ *#\|security\|cdrom\|.*gpg' "${apt_data_sources_loc}" | sed '/^[[:space:]]*$/d' | awk '{ print $2 }' | sort -u | grep ubuntu)" 819 | 820 | apt_sources "base" "${ubuntu_original}" 821 | 822 | if [ -n "${mirror_archive}" ]; 823 | then 824 | apt_mirror_source 825 | else 826 | true 827 | fi 828 | 829 | cat "${apt_data_sources_loc}" 830 | #sed -i 's,deb http://security,#deb http://security,' "${apt_data_sources_loc}" ##Uncomment to resolve security pocket time out. Security packages are copied to the other pockets frequently, so should still be available for update. See https://wiki.ubuntu.com/SecurityTeam/FAQ 831 | 832 | trap 'printf "%s\n%s" "The script has experienced an error during the first apt update. That may have been caused by a queried server not responding in time. Try running the script again." "If the issue is the security server not responding, then comment out the security server in the "${apt_data_sources_loc}". Alternatively, you can uncomment the command that does this in the install script. This affects the temporary live iso only. Not the permanent installation."' ERR 833 | apt update 834 | trap - ERR ##Resets the trap to doing nothing when the script experiences an error. The script will still exit on error if "set -e" is set. 835 | 836 | keyboard_console_settings #Request keyboard and console settings. 837 | 838 | ssh_Func(){ 839 | ##Setup SSH to allow remote access in live environment 840 | apt install --yes openssh-server 841 | service sshd start 842 | ip addr show scope global | grep inet 843 | } 844 | #ssh_Func 845 | 846 | apt-get -yq install debootstrap software-properties-common gdisk zfs-initramfs 847 | if service --status-all | grep -Fq 'zfs-zed'; then 848 | systemctl stop zfs-zed 849 | fi 850 | 851 | ##Clear partition table 852 | clear_partition_table "root" 853 | partprobe 854 | sleep 2 855 | 856 | ##Partition disk 857 | partitionsFunc(){ 858 | ##gdisk hex codes: 859 | ##EF02 BIOS boot partitions 860 | ##EF00 EFI system 861 | ##BE00 Solaris boot 862 | ##BF00 Solaris root 863 | ##BF01 Solaris /usr & Mac Z 864 | ##8200 Linux swap 865 | ##8300 Linux file system 866 | ##8309 Linux LUKS 867 | ##FD00 Linux RAID 868 | 869 | case "$topology_root" in 870 | single|mirror) 871 | swap_hex_code="8200" 872 | ;; 873 | 874 | raid0|raidz*) 875 | swap_hex_code="FD00" 876 | ;; 877 | 878 | *) 879 | echo "topology_root variable not recognised." 880 | exit 1 881 | ;; 882 | esac 883 | 884 | if [ -n "$zfs_root_password" ]; 885 | then 886 | case "$zfs_root_encrypt" in 887 | native) 888 | root_hex_code="BF00" ##ZFS native encryption 889 | ;; 890 | 891 | luks) 892 | root_hex_code="FD00" ##luks 893 | ;; 894 | 895 | *) 896 | echo "zfs_root_encrypt variable not recognised." 897 | exit 1 898 | ;; 899 | esac 900 | else 901 | root_hex_code="BF00" ##unencrypted ZFS 902 | fi 903 | 904 | while IFS= read -r diskidnum; 905 | do 906 | echo "Creating partitions on disk ${diskidnum}." 907 | ##2.3 create bootloader partition 908 | sgdisk -n1:1M:+"${EFI_boot_size}"M -t1:EF00 /dev/disk/by-id/"${diskidnum}" 909 | 910 | ##2.4 create swap partition 911 | ##bug with swap on zfs zvol so use swap on partition: 912 | ##https://github.com/zfsonlinux/zfs/issues/7734 913 | ##hibernate needs swap at least same size as RAM 914 | ##hibernate only works with unencrypted installs 915 | sgdisk -n2:0:+"${swap_size}"M -t2:"${swap_hex_code}" /dev/disk/by-id/"${diskidnum}" 916 | 917 | ##2.6 Create root pool partition 918 | sgdisk -n3:0:0 -t3:"${root_hex_code}" /dev/disk/by-id/"${diskidnum}" 919 | 920 | done < /tmp/diskid_check_"${pool}".txt 921 | partprobe 922 | sleep 2 923 | } 924 | partitionsFunc 925 | } 926 | 927 | debootstrap_createzfspools_Func(){ 928 | 929 | ##Create root pool 930 | create_zpool_Func root 931 | 932 | ##System installation 933 | mountpointsFunc(){ 934 | 935 | ##zfsbootmenu setup for no separate boot pool 936 | ##https://github.com/zbm-dev/zfsbootmenu/wiki/Debian-Buster-installation-with-ESP-on-the-zpool-disk 937 | 938 | partprobe 939 | sleep 2 940 | 941 | ##Create filesystem datasets to act as containers 942 | zfs create -o canmount=off -o mountpoint=none "$RPOOL"/ROOT 943 | 944 | ##Create root filesystem dataset 945 | rootzfs_full_name="ubuntu.$(date +%Y.%m.%d)" 946 | zfs create -o canmount=noauto -o mountpoint=/ "$RPOOL"/ROOT/"$rootzfs_full_name" ##zfsbootmenu debian guide 947 | ##assigns canmount=noauto on any file systems with mountpoint=/ (that is, on any additional boot environments you create). 948 | ##With ZFS, it is not normally necessary to use a mount command (either mount or zfs mount). 949 | ##This situation is an exception because of canmount=noauto. 950 | zfs mount "$RPOOL"/ROOT/"$rootzfs_full_name" 951 | zpool set bootfs="$RPOOL"/ROOT/"$rootzfs_full_name" "$RPOOL" 952 | 953 | 954 | ##Create datasets 955 | ##Aim is to separate OS from user data. 956 | ##Allows root filesystem to be rolled back without rolling back user data such as logs. 957 | ##https://didrocks.fr/2020/06/16/zfs-focus-on-ubuntu-20.04-lts-zsys-dataset-layout/ 958 | ##https://openzfs.github.io/openzfs-docs/Getting%20Started/Debian/Debian%20Buster%20Root%20on%20ZFS.html#step-3-system-installation 959 | ##"-o canmount=off" is for a system directory that should rollback with the rest of the system. 960 | 961 | zfs create "$RPOOL"/srv ##server webserver content 962 | zfs create -o canmount=off "$RPOOL"/usr 963 | zfs create "$RPOOL"/usr/local ##locally compiled software 964 | zfs create -o canmount=off "$RPOOL"/var 965 | zfs create -o canmount=off "$RPOOL"/var/lib 966 | zfs create "$RPOOL"/var/games ##game files 967 | zfs create "$RPOOL"/var/log ##log files 968 | zfs create "$RPOOL"/var/mail ##local mails 969 | zfs create "$RPOOL"/var/snap ##snaps handle revisions themselves 970 | zfs create "$RPOOL"/var/spool ##printing tasks 971 | zfs create "$RPOOL"/var/www ##server webserver content 972 | 973 | 974 | ##USERDATA datasets 975 | zfs create "$RPOOL"/home 976 | zfs create -o mountpoint=/root "$RPOOL"/home/root 977 | chmod 700 "$mountpoint"/root 978 | 979 | 980 | ##optional 981 | ##exclude from snapshots 982 | zfs create -o com.sun:auto-snapshot=false "$RPOOL"/var/cache 983 | zfs create -o com.sun:auto-snapshot=false "$RPOOL"/var/tmp 984 | chmod 1777 "$mountpoint"/var/tmp 985 | zfs create -o com.sun:auto-snapshot=false "$RPOOL"/var/lib/docker ##Docker manages its own datasets & snapshots 986 | 987 | 988 | ##Mount a tempfs at /run 989 | mkdir "$mountpoint"/run 990 | mount -t tmpfs tmpfs "$mountpoint"/run 991 | 992 | } 993 | mountpointsFunc 994 | } 995 | 996 | debootstrap_installminsys_Func(){ 997 | ##Install minimum system 998 | ##drivesizecheck 999 | FREE="$(df -k --output=avail "$mountpoint" | tail -n1)" 1000 | if [ "$FREE" -lt 5242880 ]; then # 15G = 15728640 = 15*1024*1024k 1001 | echo "Less than 5 GBs free!" 1002 | exit 1 1003 | fi 1004 | 1005 | debootstrap "$ubuntuver" "$mountpoint" 1006 | } 1007 | 1008 | zfsbootmenu_install_config_Func(){ 1009 | zfsbootmenu_install_config_loc="/tmp/zfsbootmenu_install_config.sh" 1010 | cat <<-EOH >"${zfsbootmenu_install_config_loc}" 1011 | #!/bin/bash 1012 | set -euo pipefail 1013 | set -x 1014 | apt update 1015 | 1016 | compile_zbm_git(){ 1017 | ##https://github.com/zbm-dev/zfsbootmenu/blob/master/testing/helpers/chroot-ubuntu.sh 1018 | ##Prevent interactive prompts 1019 | #export DEBIAN_"${install_warning_level}" 1020 | #export DEBCONF_NONINTERACTIVE_SEEN=true 1021 | 1022 | ##bsdextrautils contains column utility used in zfsbootmenu UI. 1023 | apt-get install --yes bsdextrautils 1024 | 1025 | ##Install optional mbuffer package. 1026 | ##https://github.com/zbm-dev/zfsbootmenu/blob/master/zfsbootmenu/install-helpers.sh 1027 | apt-get install --yes mbuffer 1028 | 1029 | ##Install packages needed for zfsbootmenu 1030 | apt-get install --yes --no-install-recommends \\ 1031 | libsort-versions-perl \\ 1032 | libboolean-perl \\ 1033 | libyaml-pp-perl \\ 1034 | git \\ 1035 | fzf \\ 1036 | make \\ 1037 | kexec-tools \\ 1038 | dracut-core \\ 1039 | fzf 1040 | 1041 | apt-get install --yes curl 1042 | 1043 | mkdir -p /usr/local/src/zfsbootmenu 1044 | cd /usr/local/src/zfsbootmenu 1045 | 1046 | ##Download zfsbootmenu 1047 | zbm_release="git" ##"git" for git master. "release" for latest release. 1048 | 1049 | case "\${zbm_release}" in 1050 | git) 1051 | 1052 | ##Download the latest zfsbootmenu git master 1053 | git clone https://github.com/zbm-dev/zfsbootmenu . 1054 | 1055 | ;; 1056 | 1057 | release) 1058 | 1059 | ##Download the latest zbm release 1060 | #latest_zbm_source="https://get.zfsbootmenu.org/source" #Source code from zfsbootmenu website. 1061 | 1062 | use_yq="no" 1063 | case "\${use_yq}" in 1064 | yes) 1065 | ##https://github.com/mikefarah/yq 1066 | wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq 1067 | 1068 | latest_zbm_source="\$(curl -s https://api.github.com/repos/zbm-dev/zfsbootmenu/releases/latest | yq '.tarball_url')" 1069 | ;; 1070 | no) 1071 | latest_zbm_source="\$(curl -s https://api.github.com/repos/zbm-dev/zfsbootmenu/releases/latest | grep tarball | cut -d : -f 2,3 | tr -d \"|sed 's/^[ \t]*//'|sed 's/,//')" 1072 | ;; 1073 | esac 1074 | 1075 | curl -L "\${latest_zbm_source-default}" | tar -zxv --strip-components=1 -f - 1076 | 1077 | ;; 1078 | 1079 | *) 1080 | echo "Zfsbootmenu release version not recognised." 1081 | exit 1 1082 | ;; 1083 | 1084 | esac 1085 | 1086 | make core dracut ##"make install" installs mkinitcpio, not needed. 1087 | 1088 | } 1089 | compile_zbm_git 1090 | 1091 | ##configure zfsbootmenu 1092 | config_zbm(){ 1093 | 1094 | kb_layoutcode="\$(debconf-get-selections | grep keyboard-configuration/layoutcode | awk '{print \$4}')" 1095 | 1096 | ##https://github.com/zbm-dev/zfsbootmenu/blob/master/testing/helpers/configure-ubuntu.sh 1097 | ##Update configuration file 1098 | sed \\ 1099 | -e 's,ManageImages:.*,ManageImages: true,' \\ 1100 | -e 's@ImageDir:.*@ImageDir: /boot/efi/EFI/ubuntu@' \\ 1101 | -e 's,Versions:.*,Versions: false,' \\ 1102 | -e "/CommandLine/s,ro,rd.vconsole.keymap=\${kb_layoutcode} ro," \\ 1103 | -i /etc/zfsbootmenu/config.yaml 1104 | 1105 | if [ "$quiet_boot" = "no" ]; then 1106 | sed -i 's,ro quiet,ro,' /etc/zfsbootmenu/config.yaml 1107 | fi 1108 | 1109 | if [ -n "$zfs_root_password" ]; 1110 | then 1111 | case "$zfs_root_encrypt" in 1112 | luks) 1113 | ##https://github.com/agorgl/zbm-luks-unlock 1114 | zfsbootmenu_hook_root=/etc/zfsbootmenu/hooks ##https://docs.zfsbootmenu.org/en/v2.3.x/man/zfsbootmenu.7.html 1115 | 1116 | mkdir -p \${zfsbootmenu_hook_root}/early-setup.d 1117 | cd \${zfsbootmenu_hook_root}/early-setup.d 1118 | curl -L -O https://raw.githubusercontent.com/agorgl/zbm-luks-unlock/master/hooks/early-setup.d/luks-unlock.sh 1119 | chmod +x \${zfsbootmenu_hook_root}/early-setup.d/luks-unlock.sh 1120 | 1121 | #mkdir -p \${zfsbootmenu_hook_root}/boot-sel.d 1122 | #cd \${zfsbootmenu_hook_root}/boot-sel.d 1123 | #curl -L -O https://raw.githubusercontent.com/agorgl/zbm-luks-unlock/master/hooks/boot-sel.d/initramfs-inject.sh 1124 | #chmod +x \${zfsbootmenu_hook_root}/early-setup.d/initramfs-inject.sh 1125 | 1126 | cd /etc/zfsbootmenu/dracut.conf.d/ 1127 | curl -L -O https://raw.githubusercontent.com/agorgl/zbm-luks-unlock/master/dracut.conf.d/99-crypt.conf 1128 | ;; 1129 | esac 1130 | else 1131 | true 1132 | fi 1133 | 1134 | 1135 | } 1136 | config_zbm 1137 | 1138 | update-initramfs -c -k all 1139 | generate-zbm --debug 1140 | 1141 | EOH 1142 | 1143 | case "$1" in 1144 | chroot) 1145 | cp "${zfsbootmenu_install_config_loc}" "$mountpoint"/tmp 1146 | chroot "$mountpoint" /bin/bash -x "${zfsbootmenu_install_config_loc}" 1147 | ;; 1148 | base) 1149 | /bin/bash "${zfsbootmenu_install_config_loc}" 1150 | ;; 1151 | *) 1152 | exit 1 1153 | ;; 1154 | esac 1155 | 1156 | } 1157 | 1158 | remote_zbm_access_Func(){ 1159 | modulesetup="/usr/lib/dracut/modules.d/60crypt-ssh/module-setup.sh" 1160 | cat <<-EOH >/tmp/remote_zbm_access.sh 1161 | #!/bin/sh 1162 | ##Configure SSH server in Dracut 1163 | ##https://github.com/zbm-dev/zfsbootmenu/wiki/Remote-Access-to-ZBM 1164 | apt update 1165 | apt install -y dracut-network dropbear 1166 | apt install -y isc-dhcp-client 1167 | 1168 | config_dracut_crypt_ssh_module(){ 1169 | git -C /tmp clone 'https://github.com/dracut-crypt-ssh/dracut-crypt-ssh.git' 1170 | mkdir /usr/lib/dracut/modules.d/60crypt-ssh 1171 | cp /tmp/dracut-crypt-ssh/modules/60crypt-ssh/* /usr/lib/dracut/modules.d/60crypt-ssh/ 1172 | rm /usr/lib/dracut/modules.d/60crypt-ssh/Makefile 1173 | 1174 | ##Comment out references to /helper/ folder in module-setup.sh. Components not required for ZFSBootMenu. 1175 | sed -i \\ 1176 | -e 's, inst "\$moddir"/helper/console_auth /bin/console_auth, #inst "\$moddir"/helper/console_auth /bin/console_auth,' \\ 1177 | -e 's, inst "\$moddir"/helper/console_peek.sh /bin/console_peek, #inst "\$moddir"/helper/console_peek.sh /bin/console_peek,' \\ 1178 | -e 's, inst "\$moddir"/helper/unlock /bin/unlock, #inst "\$moddir"/helper/unlock /bin/unlock,' \\ 1179 | -e 's, inst "\$moddir"/helper/unlock-reap-success.sh /sbin/unlock-reap-success, #inst "\$moddir"/helper/unlock-reap-success.sh /sbin/unlock-reap-success,' \\ 1180 | "$modulesetup" 1181 | } 1182 | config_dracut_crypt_ssh_module 1183 | 1184 | setup_dracut_network(){ 1185 | ##setup network 1186 | mkdir -p /etc/cmdline.d 1187 | 1188 | remoteaccess_dhcp_ver(){ 1189 | dhcpver="\$1" 1190 | echo "ip=\${dhcpver:-default} rd.neednet=1" > /etc/cmdline.d/dracut-network.conf 1191 | } 1192 | 1193 | ##Dracut network options: https://github.com/dracutdevs/dracut/blob/master/modules.d/35network-legacy/ifup.sh 1194 | case "$remoteaccess_ip_config" in 1195 | dhcp | dhcp,dhcp6 | dhcp6) 1196 | remoteaccess_dhcp_ver "$remoteaccess_ip_config" 1197 | ;; 1198 | static) 1199 | echo "ip=$remoteaccess_ip:::$remoteaccess_netmask:::none rd.neednet=1 rd.break" > /etc/cmdline.d/dracut-network.conf 1200 | ;; 1201 | *) 1202 | echo "Remote access IP option not recognised." 1203 | exit 1 1204 | ;; 1205 | esac 1206 | 1207 | echo "send fqdn.fqdn \"$remoteaccess_hostname\";" >> /usr/lib/dracut/modules.d/35network-legacy/dhclient.conf 1208 | } 1209 | setup_dracut_network 1210 | 1211 | add_welcome_message(){ 1212 | ##add remote session welcome message 1213 | cat <<-EOF >/etc/zfsbootmenu/dracut.conf.d/banner.txt 1214 | Welcome to the ZFSBootMenu initramfs shell. Enter "zfsbootmenu" or "zbm" to start ZFSBootMenu. 1215 | EOF 1216 | chmod 755 /etc/zfsbootmenu/dracut.conf.d/banner.txt 1217 | 1218 | sed -i 's, /sbin/dropbear -s -j -k -p \${dropbear_port} -P /tmp/dropbear.pid, /sbin/dropbear -s -j -k -p \${dropbear_port} -P /tmp/dropbear.pid -b /etc/banner.txt,' /usr/lib/dracut/modules.d/60crypt-ssh/dropbear-start.sh 1219 | 1220 | ##Copy files into initramfs 1221 | sed -i '$ s,^},,' "$modulesetup" 1222 | echo " ##Copy dropbear welcome message" | tee -a "$modulesetup" 1223 | echo " inst /etc/zfsbootmenu/dracut.conf.d/banner.txt /etc/banner.txt" | tee -a "$modulesetup" 1224 | echo "}" | tee -a "$modulesetup" 1225 | } 1226 | add_welcome_message 1227 | 1228 | create_host_keys(){ 1229 | ##create host keys 1230 | mkdir -p /etc/dropbear 1231 | for keytype in rsa ecdsa ed25519; do 1232 | #dropbearkey -t "\${keytype}" -f "/etc/dropbear/ssh_host_\${keytype}_key" 1233 | ssh-keygen -t "\${keytype}" -m PEM -f "/etc/dropbear/ssh_host_\${keytype}_key" -N "" 1234 | ##-t key type 1235 | ##-m key format 1236 | ##-f filename 1237 | ##-N passphrase 1238 | done 1239 | } 1240 | create_host_keys 1241 | 1242 | ##Set ownership of initramfs authorized_keys 1243 | sed -i '/inst "\${dropbear_acl}"/a \\ chown root:root "\${initdir}/root/.ssh/authorized_keys"' "$modulesetup" 1244 | 1245 | config_dropbear(){ 1246 | cat <<-EOF >/etc/zfsbootmenu/dracut.conf.d/dropbear.conf 1247 | ## Enable dropbear ssh server and pull in network configuration args 1248 | ##The default configuration will start dropbear on TCP port 222. 1249 | ##This can be overridden with the dropbear_port configuration option. 1250 | ##You do not want the server listening on the default port 22. 1251 | ##Clients that expect to find your normal host keys when connecting to an SSH server on port 22 will 1252 | ## refuse to connect when they find different keys provided by dropbear. 1253 | 1254 | add_dracutmodules+=" crypt-ssh network-legacy " 1255 | install_optional_items+=" /etc/cmdline.d/dracut-network.conf " 1256 | 1257 | ## Copy system keys for consistent access 1258 | dropbear_rsa_key="/etc/dropbear/ssh_host_rsa_key" 1259 | dropbear_ecdsa_key="/etc/dropbear/ssh_host_ecdsa_key" 1260 | dropbear_ed25519_key="/etc/dropbear/ssh_host_ed25519_key" 1261 | 1262 | ##Access is by authorized keys only. No password. 1263 | ##By default, the list of authorized keys is taken from /root/.ssh/authorized_keys on the host. 1264 | ##A custom authorized_keys location can also be specified with the dropbear_acl variable. 1265 | ##You can add your remote user key to a user authorized_keys file from a remote machine's terminal using: 1266 | ##"ssh-copy-id -i ~/.ssh/id_rsa.pub $user@{IP_ADDRESS or FQDN of the server}" 1267 | ##Then amend/uncomment the dropbear_acl variable to match: 1268 | #dropbear_acl="/home/${user}/.ssh/authorized_keys" 1269 | ##Remember to "sudo generate-zbm" on the host after adding the remote user key to the authorized_keys file. 1270 | 1271 | ##Note that login to dropbear is "root" regardless of which authorized_keys is used. 1272 | EOF 1273 | 1274 | systemctl stop dropbear 1275 | systemctl disable dropbear 1276 | } 1277 | config_dropbear 1278 | 1279 | ##Increase ZFSBootMenu timer to allow for remote connection 1280 | sed -i 's,zbm.timeout=$timeout_zbm_no_remote_access,zbm.timeout=$timeout_zbm_remote_access,' /boot/efi/EFI/ubuntu/refind_linux.conf 1281 | 1282 | generate-zbm --debug 1283 | 1284 | EOH 1285 | 1286 | case "$1" in 1287 | chroot) 1288 | cp /tmp/remote_zbm_access.sh "$mountpoint"/tmp 1289 | chroot "$mountpoint" /bin/bash -x /tmp/remote_zbm_access.sh 1290 | ;; 1291 | base) 1292 | ##Test for live environment. 1293 | if grep casper /proc/cmdline >/dev/null 2>&1; 1294 | then 1295 | echo "Live environment present. Reboot into new installation to install remoteaccess." 1296 | exit 1 1297 | else 1298 | /bin/bash /tmp/remote_zbm_access.sh 1299 | 1300 | sed -i 's,#dropbear_acl,dropbear_acl,' /etc/zfsbootmenu/dracut.conf.d/dropbear.conf 1301 | mkdir -p /home/"$user"/.ssh 1302 | chown "$user":"$user" /home/"$user"/.ssh 1303 | touch /home/"$user"/.ssh/authorized_keys 1304 | chmod 644 /home/"$user"/.ssh/authorized_keys 1305 | chown "$user":"$user" /home/"$user"/.ssh/authorized_keys 1306 | #hostname -I 1307 | echo "Zfsbootmenu remote access installed. Connect as root on port 222 during boot: \"ssh root@{IP_ADDRESS or FQDN of zfsbootmenu} -p 222\"" 1308 | echo "Your SSH public key must be placed in \"/home/$user/.ssh/authorized_keys\" prior to reboot or remote access will not work." 1309 | echo "You can add your remote user key using the following command from the remote user's terminal if openssh-server is active on the host." 1310 | echo "\"ssh-copy-id -i ~/.ssh/id_rsa.pub $user@{IP_ADDRESS or FQDN of the server}\"" 1311 | echo "Run \"sudo generate-zbm\" after copying across the remote user's public ssh key into the authorized_keys file." 1312 | fi 1313 | ;; 1314 | *) 1315 | exit 1 1316 | ;; 1317 | esac 1318 | 1319 | } 1320 | 1321 | systemsetupFunc_part1(){ 1322 | 1323 | ##System configuration 1324 | 1325 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1326 | export DEBIAN_"${install_warning_level}" 1327 | EOCHROOT 1328 | 1329 | ##Error out script on apt update error such as network failure during package download. 1330 | cp /etc/apt/apt.conf.d/30apt_error_on_transient "$mountpoint"/etc/apt/apt.conf.d/ 1331 | 1332 | ##Configure hostname 1333 | echo "$hostname" > "$mountpoint"/etc/hostname 1334 | echo "127.0.1.1 $hostname" >> "$mountpoint"/etc/hosts 1335 | 1336 | ##Configure network interface 1337 | 1338 | ##Get ethernet interface 1339 | ethernetinterface="$(basename "$(find /sys/class/net -maxdepth 1 -mindepth 1 -name "${ethprefix}*")")" 1340 | echo "$ethernetinterface" 1341 | 1342 | ##troubleshoot: sudo netplan --debug generate 1343 | cat > "$mountpoint"/etc/netplan/01-"$ethernetinterface".yaml <<-EOF 1344 | network: 1345 | version: 2 1346 | ethernets: 1347 | $ethernetinterface: 1348 | dhcp4: yes 1349 | EOF 1350 | ##https://netplan.readthedocs.io/en/stable/reference/ 1351 | chmod 600 "$mountpoint"/etc/netplan/01-"$ethernetinterface".yaml 1352 | 1353 | ##Bind virtual filesystems from LiveCD to new system 1354 | mount --rbind /dev "$mountpoint"/dev 1355 | mount --rbind /proc "$mountpoint"/proc 1356 | mount --rbind /sys "$mountpoint"/sys 1357 | 1358 | ##Configure package sources 1359 | if [ -n "${mirror_archive}" ]; 1360 | then 1361 | apt_sources "chroot" "${ubuntu_mirror}" 1362 | else 1363 | apt_sources "chroot" "${ubuntu_original}" 1364 | fi 1365 | 1366 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1367 | ##4.5 configure basic system 1368 | apt update 1369 | 1370 | #dpkg-reconfigure locales 1371 | locale-gen en_US.UTF-8 $locale 1372 | echo 'LANG="$locale"' > /etc/default/locale 1373 | 1374 | ##set timezone 1375 | ln -fs /usr/share/zoneinfo/"$timezone" /etc/localtime 1376 | dpkg-reconfigure tzdata 1377 | 1378 | EOCHROOT 1379 | } 1380 | 1381 | systemsetupFunc_part2(){ 1382 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1383 | ##install zfs 1384 | apt update 1385 | 1386 | apt install --no-install-recommends -y linux-headers-generic linux-image-generic ##need to use no-install-recommends otherwise installs grub 1387 | 1388 | apt install --yes --no-install-recommends dkms wget nano 1389 | 1390 | apt install -yq software-properties-common 1391 | 1392 | ##Ubuntu kernels come with zfs module installed. No need to install zfs-dkms for zfs version in the default repositories. 1393 | #apt-get -yq install zfs-dkms 1394 | 1395 | apt install --yes zfsutils-linux zfs-zed 1396 | 1397 | apt install --yes zfs-initramfs 1398 | 1399 | 1400 | EOCHROOT 1401 | } 1402 | 1403 | systemsetupFunc_part3(){ 1404 | identify_ubuntu_dataset_uuid 1405 | 1406 | ##Create the EFI filesystem 1407 | apt install --yes dosfstools 1408 | 1409 | loop_counter="$(mktemp)" 1410 | echo 0 > "$loop_counter" ##Assign starting counter value. 1411 | while IFS= read -r diskidnum; 1412 | do 1413 | i="$(cat "$loop_counter")" 1414 | echo "$i" 1415 | if [ "$i" -eq 0 ]; 1416 | then 1417 | esp_mount="/boot/efi" 1418 | else 1419 | esp_mount="/boot/efi$i" 1420 | echo "$esp_mount" >> "$mountpoint"/tmp/backup_esp_mounts.txt 1421 | fi 1422 | 1423 | echo "Creating FAT32 filesystem in EFI partition of disk ${diskidnum}. ESP mountpoint is ${esp_mount}" 1424 | umount -q /dev/disk/by-id/"${diskidnum}"-part1 || true 1425 | mkdosfs -F 32 -s 1 -n EFI /dev/disk/by-id/"${diskidnum}"-part1 1426 | partprobe 1427 | sleep 2 1428 | blkid_part1="" 1429 | blkid_part1="$(blkid -s UUID -o value /dev/disk/by-id/"${diskidnum}"-part1)" 1430 | echo "$blkid_part1" >> /tmp/esp_partition_list_uuid.txt 1431 | 1432 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1433 | mkdir -p "${esp_mount}" 1434 | 1435 | ##fstab entry 1436 | echo /dev/disk/by-uuid/"$blkid_part1" \ 1437 | "${esp_mount}" vfat \ 1438 | defaults \ 1439 | 0 0 >> /etc/fstab 1440 | 1441 | ##mount from fstab entry 1442 | mount "${esp_mount}" 1443 | ##If mount fails error code is 0. Script won't fail. Need the following check. 1444 | ##Could use "mountpoint" command but not all distros have it. 1445 | if grep "${esp_mount}" /proc/mounts; then 1446 | echo "${esp_mount} mounted." 1447 | else 1448 | echo "${esp_mount} not mounted." 1449 | exit 1 1450 | fi 1451 | EOCHROOT 1452 | 1453 | i=$((i + 1)) ##Increment counter. 1454 | echo "$i" > "$loop_counter" 1455 | 1456 | done < /tmp/diskid_check_"${pool}".txt 1457 | 1458 | initial_boot_order="$(efibootmgr | grep "BootOrder" | cut -d " " -f 2)" ##Initial boot order before refind installed. 1459 | 1460 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1461 | apt-get -yq install refind kexec-tools 1462 | apt install --yes dpkg-dev git systemd-sysv 1463 | 1464 | ##Adjust timer on initial rEFInd screen 1465 | sed -i 's,^timeout .*,timeout $timeout_rEFInd,' /boot/efi/EFI/refind/refind.conf 1466 | 1467 | echo REMAKE_INITRD=yes > /etc/dkms/zfs.conf 1468 | sed -i 's,LOAD_KEXEC=false,LOAD_KEXEC=true,' /etc/default/kexec 1469 | EOCHROOT 1470 | 1471 | } 1472 | 1473 | systemsetupFunc_part4(){ 1474 | 1475 | cp /tmp/diskid_check_"${pool}".txt "$mountpoint"/tmp/ 1476 | 1477 | if [ -f /tmp/luks_dmname_"${pool}".txt ]; 1478 | then 1479 | cp /tmp/luks_dmname_"${pool}".txt "$mountpoint"/tmp/ 1480 | else true 1481 | fi 1482 | 1483 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1484 | encrypt_config(){ 1485 | if [ -n "$zfs_root_password" ]; 1486 | then 1487 | case "$zfs_root_encrypt" in 1488 | native) 1489 | ##Convert rpool to use keyfile. 1490 | echo $zfs_root_password > /etc/zfs/$RPOOL.key ##This file will live inside your initramfs stored on the ZFS boot environment. 1491 | chmod 600 /etc/zfs/$RPOOL.key ##Set access rights to keyfile. 1492 | 1493 | zfs change-key -o keylocation=file:///etc/zfs/$RPOOL.key -o keyformat=passphrase $RPOOL 1494 | 1495 | ##Setup key caching in zfsbootmenu 1496 | zfs set org.zfsbootmenu:keysource="$RPOOL/ROOT" $RPOOL 1497 | ;; 1498 | luks) 1499 | ##https://askubuntu.com/questions/996155/how-do-i-automatically-decrypt-an-encrypted-filesystem-on-the-next-reboot 1500 | 1501 | mkdir -p /etc/cryptsetup-keys.d/ 1502 | dd if=/dev/urandom of=/etc/cryptsetup-keys.d/$RPOOL.key bs=1024 count=4 1503 | chmod 600 /etc/cryptsetup-keys.d/$RPOOL.key ##Set access rights to keyfile. 1504 | 1505 | ;; 1506 | esac 1507 | 1508 | else 1509 | true 1510 | fi 1511 | 1512 | echo "UMASK=0077" > /etc/initramfs-tools/conf.d/umask.conf ##Set access rights for initramfs images generated by mkinitramfs. 1513 | 1514 | } 1515 | encrypt_config 1516 | EOCHROOT 1517 | 1518 | ##Update crypttab if luks used 1519 | if [ "$zfs_root_encrypt" = "luks" ]; 1520 | then 1521 | update_crypttab_Func "chroot" "root" 1522 | else true 1523 | fi 1524 | 1525 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1526 | if [ "$quiet_boot" = "yes" ]; then 1527 | zfs set org.zfsbootmenu:commandline="spl_hostid=\$( hostid ) ro quiet" "$RPOOL"/ROOT 1528 | else 1529 | zfs set org.zfsbootmenu:commandline="spl_hostid=\$( hostid ) ro" "$RPOOL"/ROOT 1530 | fi 1531 | EOCHROOT 1532 | 1533 | zfsbootmenu_install_config_Func "chroot" 1534 | 1535 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1536 | ##Update refind_linux.conf 1537 | config_refind(){ 1538 | ##zfsbootmenu command-line parameters: 1539 | ##https://github.com/zbm-dev/zfsbootmenu/blob/master/pod/zfsbootmenu.7.pod 1540 | cat <<-EOF > /boot/efi/EFI/ubuntu/refind_linux.conf 1541 | "Boot default" "zbm.timeout=$timeout_zbm_no_remote_access ro quiet loglevel=0" 1542 | "Boot to menu" "zbm.show ro quiet loglevel=0" 1543 | EOF 1544 | 1545 | if [ "$quiet_boot" = "no" ]; then 1546 | sed -i 's,ro quiet,ro,' /boot/efi/EFI/ubuntu/refind_linux.conf 1547 | fi 1548 | } 1549 | config_refind 1550 | EOCHROOT 1551 | 1552 | zbm_multiple_ESP(){ 1553 | esp_sync_path="/etc/zfsbootmenu/generate-zbm.post.d/esp-sync.sh" 1554 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1555 | mkdir -p "/etc/zfsbootmenu/generate-zbm.post.d/" 1556 | 1557 | #find /boot -maxdepth 1 -mindepth 1 -type d -not -path "/boot/efi" -path "/boot/efi*" > /tmp/backup_esp_mounts.txt 1558 | 1559 | cat > "$esp_sync_path" <<-"EOT" 1560 | #!/bin/sh 1561 | 1562 | sync_func(){ 1563 | 1564 | rsync --delete-after -axHAWXS --info=progress2 /boot/efi/ "\$1" 1565 | 1566 | } 1567 | 1568 | EOT 1569 | 1570 | while IFS= read -r esp_mount; 1571 | do 1572 | echo "sync_func \"\$esp_mount"\" >> "$esp_sync_path" 1573 | done < /tmp/backup_esp_mounts.txt 1574 | 1575 | chmod +x "$esp_sync_path" 1576 | apt install rsync 1577 | sh "$esp_sync_path" ##Sync main ESP to backup ESPs. 1578 | 1579 | EOCHROOT 1580 | 1581 | update_boot_manager(){ 1582 | ##Add backup ESPs to the EFI boot manager 1583 | loop_counter="$(mktemp)" 1584 | echo 0 > "$loop_counter" ##Assign starting counter value. 1585 | while IFS= read -r diskidnum; 1586 | do 1587 | i="$(cat "$loop_counter")" 1588 | echo "$i" 1589 | if [ "$i" -eq 0 ]; 1590 | then 1591 | true 1592 | else 1593 | device_name="$(readlink -f /dev/disk/by-id/"${diskidnum}")" 1594 | efibootmgr --create --disk "${device_name}" --label "rEFInd Boot Manager Backup $i" --loader \\EFI\\refind\\refind_x64.efi 1595 | fi 1596 | i=$((i + 1)) ##Increment counter. 1597 | echo "$i" > "$loop_counter" 1598 | done < /tmp/diskid_check_"${pool}".txt 1599 | 1600 | ##Adjust ESP boot order 1601 | ##Each boot entry in efibootmgr is identified by a boot number in hexadecimal. 1602 | primary_esp_hex="$(efibootmgr | grep -v "Backup" | grep -w "rEFInd Boot Manager" | cut -d " " -f 1 | sed 's,Boot,,' | sed 's,*,,')" 1603 | primary_esp_dec="$(printf "%d" 0x"${primary_esp_hex}")" 1604 | num_disks="$(wc -l /tmp/diskid_check_"${pool}".txt | awk '{ print $1 }')" 1605 | esp_loop_exit_dec="$(( "${primary_esp_dec}" + "${num_disks}" ))" 1606 | 1607 | i="${primary_esp_dec}" 1608 | while [ "$i" -ne "${esp_loop_exit_dec}" ] 1609 | do 1610 | if [ "$i" -eq "$primary_esp_dec" ]; 1611 | then 1612 | echo "${primary_esp_hex}," > /tmp/revised_boot_order.txt 1613 | else 1614 | loop_counter_hex="$(printf "%04X" "$i")" 1615 | sed -i "s/$/${loop_counter_hex},/g" /tmp/revised_boot_order.txt 1616 | fi 1617 | i=$((i + 1)) 1618 | done 1619 | sed -i "s/$/${initial_boot_order}/g" /tmp/revised_boot_order.txt 1620 | revised_boot_order="$(cat /tmp/revised_boot_order.txt)" 1621 | efibootmgr -o "${revised_boot_order}" 1622 | } 1623 | update_boot_manager 1624 | } 1625 | 1626 | topology_pool_pointer="$(cat "/tmp/topology_pool_pointer.txt")" 1627 | case "$topology_pool_pointer" in 1628 | single) 1629 | true 1630 | ;; 1631 | 1632 | mirror|raid0|raidz*) 1633 | echo "Configuring zfsbootmenu to update all ESPs." 1634 | zbm_multiple_ESP 1635 | ;; 1636 | 1637 | *) 1638 | echo "Pool topology not recognised. Check pool topology variable." 1639 | exit 1 1640 | ;; 1641 | esac 1642 | 1643 | if [ "${remoteaccess_first_boot}" = "yes" ]; 1644 | then 1645 | remote_zbm_access_Func "chroot" 1646 | else true 1647 | fi 1648 | 1649 | } 1650 | 1651 | systemsetupFunc_part5(){ 1652 | 1653 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1654 | ##Set root password 1655 | echo -e "root:$PASSWORD" | chpasswd -c SHA256 1656 | EOCHROOT 1657 | 1658 | ##Configure swap 1659 | 1660 | ##"plain" required in crypttab to avoid message at boot: "From cryptsetup: couldn't determine device type, assuming default (plain)." 1661 | crypttab_parameters="/dev/urandom plain,swap,cipher=aes-xts-plain64:sha256,size=512" 1662 | 1663 | mdadm_swap_Func(){ 1664 | mdadm_swap_loc="/tmp/multi_disc_swap.sh" 1665 | mdadm_level="$1" ##ZFS raidz = MDADM raid5, raidz2 = raid6. MDADM does not have raid7, so no triple parity equivalent to raidz3. 1666 | mdadm_devices="$2" ##Number of disks. 1667 | 1668 | cat > "$mdadm_swap_loc" <<-EOF 1669 | ##Swap setup for mirror or raidz topology. 1670 | apt install --yes mdadm 1671 | 1672 | ##Set MDADM level and number of disks. 1673 | mdadm --create /dev/md0 --metadata=1.2 \\ 1674 | --level="$mdadm_level" \\ 1675 | --raid-devices="$mdadm_devices" \\ 1676 | EOF 1677 | 1678 | ##Add swap disks. 1679 | while IFS= read -r diskidnum; 1680 | do 1681 | echo "/dev/disk/by-id/${diskidnum}-part2 \\" >> "$mdadm_swap_loc" 1682 | done < /tmp/diskid_check_root.txt 1683 | 1684 | sed -i '$s,\\,,' "$mdadm_swap_loc" ##Remove escape characters needed for last line of EOF code block. 1685 | 1686 | ##Update fstab and cryptsetup. 1687 | if [ -n "$zfs_root_password" ]; 1688 | then 1689 | cat >> "$mdadm_swap_loc" <<-EOF 1690 | apt install --yes cryptsetup 1691 | echo swap /dev/md0 ${crypttab_parameters} >> /etc/crypttab 1692 | echo /dev/mapper/swap none swap defaults 0 0 >> /etc/fstab 1693 | EOF 1694 | else 1695 | cat >> "$mdadm_swap_loc" <<-EOF 1696 | mkswap -f /dev/md0 1697 | blkid_md0="" 1698 | blkid_md0="\$(blkid -s UUID -o value /dev/md0)" 1699 | echo /dev/disk/by-uuid/\${blkid_md0} none swap defaults 0 0 >> /etc/fstab 1700 | EOF 1701 | fi 1702 | 1703 | ##Update mdadm configuration file 1704 | cat >> "$mdadm_swap_loc" <<-EOF 1705 | mdadm --detail --scan --verbose | tee -a /etc/mdadm/mdadm.conf 1706 | EOF 1707 | 1708 | ##Check MDADM status. 1709 | cat >> "$mdadm_swap_loc" <<-EOF 1710 | cat /proc/mdstat 1711 | mdadm --detail /dev/md0 1712 | EOF 1713 | 1714 | ##Copy MDADM setup file into chroot and run. 1715 | cp "$mdadm_swap_loc" "$mountpoint"/tmp/ 1716 | chroot "$mountpoint" /bin/bash -x "$mdadm_swap_loc" 1717 | } 1718 | 1719 | case "$topology_root" in 1720 | single) 1721 | if [ -n "$zfs_root_password" ]; 1722 | then 1723 | DISKID="$(cat /tmp/diskid_check_root.txt)" 1724 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1725 | apt install --yes cryptsetup 1726 | echo swap /dev/disk/by-id/"$DISKID"-part2 ${crypttab_parameters} >> /etc/crypttab 1727 | echo /dev/mapper/swap none swap defaults 0 0 >> /etc/fstab 1728 | EOCHROOT 1729 | else 1730 | blkid_part2="" 1731 | DISKID="$(cat /tmp/diskid_check_root.txt)" 1732 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1733 | mkswap -f /dev/disk/by-id/"$DISKID"-part2 1734 | blkid_part2="\$(blkid -s UUID -o value /dev/disk/by-id/$DISKID-part2)" 1735 | echo /dev/disk/by-uuid/\${blkid_part2} none swap defaults 0 0 >> /etc/fstab 1736 | sleep 2 1737 | swapon -a 1738 | EOCHROOT 1739 | fi 1740 | ;; 1741 | 1742 | mirror) 1743 | ##mdadm --level=mirror is the same as --level=1. 1744 | mdadm_swap_Func "mirror" "$disks_root" 1745 | ;; 1746 | 1747 | raid0) 1748 | ##No need for RAID0 for swap. The kernel supports stripe swapping on multiple devices if given the same priority in fstab. 1749 | ##https://raid.wiki.kernel.org/index.php/Why_RAID%3F#Swapping_on_RAID 1750 | 1751 | loop_counter="$(mktemp)" 1752 | echo 0 > "$loop_counter" ##Assign starting counter value. 1753 | while IFS= read -r diskidnum; 1754 | do 1755 | i="$(cat "$loop_counter")" 1756 | echo "$i" 1757 | swap_part_num="swap$i" 1758 | 1759 | if [ -n "$zfs_root_password" ]; 1760 | then 1761 | echo "${swap_part_num}" /dev/disk/by-id/"${diskidnum}"-part2 ${crypttab_parameters} >> ${mountpoint}/etc/crypttab 1762 | echo /dev/mapper/"${swap_part_num}" none swap defaults,pri=1 0 0 >> ${mountpoint}/etc/fstab 1763 | 1764 | else 1765 | mkswap -f /dev/disk/by-id/${diskidnum}-part2 1766 | blkid_part2="" 1767 | blkid_part2="$(blkid -s UUID -o value /dev/disk/by-id/${diskidnum}-part2)" 1768 | echo /dev/disk/by-uuid/${blkid_part2} none swap defaults,pri=1 0 0 >> ${mountpoint}/etc/fstab 1769 | fi 1770 | 1771 | i=$((i + 1)) ##Increment counter. 1772 | echo "$i" > "$loop_counter" 1773 | 1774 | done < /tmp/diskid_check_root.txt 1775 | ;; 1776 | 1777 | raidz1) 1778 | mdadm_swap_Func "5" "$disks_root" 1779 | ;; 1780 | 1781 | raidz2) 1782 | mdadm_swap_Func "6" "$disks_root" 1783 | ;; 1784 | 1785 | raidz3) 1786 | ##mdadm has no equivalent raid7 to raidz3. Use raid6. 1787 | mdadm_swap_Func "6" "$disks_root" 1788 | ;; 1789 | 1790 | *) 1791 | echo "Pool topology not recognised. Check pool topology variable." 1792 | exit 1 1793 | ;; 1794 | 1795 | esac 1796 | 1797 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1798 | ##Mount a tmpfs to /tmp 1799 | cp /usr/share/systemd/tmp.mount /etc/systemd/system/ 1800 | systemctl enable tmp.mount 1801 | 1802 | ##Setup system groups 1803 | addgroup --system lpadmin 1804 | addgroup --system lxd 1805 | addgroup --system sambashare 1806 | 1807 | EOCHROOT 1808 | 1809 | chroot "$mountpoint" /bin/bash -x <<-"EOCHROOT" 1810 | 1811 | ##Refresh initrd files 1812 | 1813 | ls /usr/lib/modules 1814 | 1815 | update-initramfs -c -k all 1816 | 1817 | EOCHROOT 1818 | 1819 | } 1820 | 1821 | usersetup(){ 1822 | ##Create user account and setup groups 1823 | zfs create -o mountpoint=/home/"$user" "$RPOOL"/home/${user} 1824 | 1825 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 1826 | 1827 | ##gecos parameter disabled asking for finger info 1828 | adduser --disabled-password --gecos "" "$user" 1829 | cp -a /etc/skel/. /home/"$user" 1830 | chown -R "$user":"$user" /home/"$user" 1831 | usermod -a -G adm,cdrom,dip,lpadmin,lxd,plugdev,sambashare,sudo "$user" 1832 | echo -e "$user:$PASSWORD" | chpasswd 1833 | 1834 | EOCHROOT 1835 | } 1836 | 1837 | distroinstall(){ 1838 | ##Upgrade the minimal system 1839 | 1840 | ##Configure package sources 1841 | if [ -n "${mirror_archive}" ]; 1842 | then 1843 | apt_mirror_source 1844 | else 1845 | true 1846 | fi 1847 | 1848 | export DEBIAN_"${install_warning_level}" 1849 | 1850 | #if [ ! -e /var/lib/dpkg/status ] 1851 | #then touch /var/lib/dpkg/status 1852 | #fi 1853 | 1854 | apt update 1855 | 1856 | apt dist-upgrade --yes 1857 | ##Install command-line environment only 1858 | 1859 | #rm -f /etc/resolv.conf ##Gives an error during ubuntu-server install. "Same file as /run/systemd/resolve/stub-resolv.conf". https://bugs.launchpad.net/ubuntu/+source/systemd/+bug/1774632 1860 | #ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf 1861 | 1862 | if [ "$distro_variant" != "server" ]; 1863 | then 1864 | zfs create "$RPOOL"/var/lib/AccountsService 1865 | fi 1866 | 1867 | case "$distro_variant" in 1868 | server) 1869 | ##Server installation has a command line interface only. 1870 | ##Minimal install: ubuntu-server-minimal 1871 | apt install --yes ubuntu-server 1872 | ;; 1873 | desktop) 1874 | ##Ubuntu default desktop install has a full GUI environment. 1875 | ##Minimal install: ubuntu-desktop-minimal 1876 | apt install --yes ubuntu-desktop 1877 | ;; 1878 | kubuntu) 1879 | ##Ubuntu KDE plasma desktop install has a full GUI environment. 1880 | ##Select sddm as display manager. 1881 | echo sddm shared/default-x-display-manager select sddm | debconf-set-selections 1882 | apt install --yes kubuntu-desktop 1883 | ;; 1884 | xubuntu) 1885 | ##Ubuntu xfce desktop install has a full GUI environment. 1886 | ##Select lightdm as display manager. 1887 | echo lightdm shared/default-x-display-manager select lightdm | debconf-set-selections 1888 | apt install --yes xubuntu-desktop 1889 | ;; 1890 | budgie) 1891 | ##Ubuntu budgie desktop install has a full GUI environment. 1892 | ##Select lightdm as display manager. 1893 | echo lightdm shared/default-x-display-manager select lightdm | debconf-set-selections 1894 | apt install --yes ubuntu-budgie-desktop 1895 | ;; 1896 | MATE) 1897 | ##Ubuntu MATE desktop install has a full GUI environment. 1898 | ##Select lightdm as display manager. 1899 | echo lightdm shared/default-x-display-manager select lightdm | debconf-set-selections 1900 | apt install --yes ubuntu-mate-desktop 1901 | ;; 1902 | #cinnamon) 1903 | ##ubuntucinnamon-desktop package unavailable in 22.04. 1904 | # ##Ubuntu cinnamon desktop install has a full GUI environment. 1905 | # apt install --yes ubuntucinnamon-desktop 1906 | #;; 1907 | *) 1908 | echo "Ubuntu variant variable not recognised. Check ubuntu variant variable." 1909 | exit 1 1910 | ;; 1911 | esac 1912 | 1913 | } 1914 | 1915 | NetworkManager_config(){ 1916 | 1917 | ##Update netplan config to use NetworkManager if installed. Otherwise will default to networkd. 1918 | if [ "$(dpkg-query --show --showformat='${db:Status-Status}\n' "network-manager")" = "installed" ]; 1919 | then 1920 | ##Update netplan configuration for NetworkManager. 1921 | ethernetinterface="$(basename "$(find /sys/class/net -maxdepth 1 -mindepth 1 -name "${ethprefix}*")")" 1922 | rm /etc/netplan/01-"$ethernetinterface".yaml 1923 | cat > /etc/netplan/01-network-manager-all.yaml <<-EOF 1924 | #Let NetworkManager manage all devices on this system. 1925 | network: 1926 | version: 2 1927 | renderer: NetworkManager 1928 | EOF 1929 | 1930 | ##Disable systemd-networkd to prevent conflicts with NetworkManager. 1931 | systemctl stop systemd-networkd 1932 | systemctl disable systemd-networkd 1933 | #systemctl mask systemd-networkd 1934 | 1935 | netplan apply 1936 | else true 1937 | fi 1938 | 1939 | } 1940 | 1941 | pyznapinstall(){ 1942 | ##snapshot management 1943 | 1944 | ##https://github.com/yboetz/pyznap 1945 | apt install -y python3-pip 1946 | pip3 --version 1947 | ##https://docs.python-guide.org/dev/virtualenvs/ 1948 | apt install -y python3-virtualenv 1949 | virtualenv --version 1950 | apt install -y python3-virtualenvwrapper 1951 | mkdir /opt/pyznap 1952 | cd /opt/pyznap 1953 | virtualenv venv 1954 | source venv/bin/activate ##enter virtual env 1955 | pip install setuptools ##Setuptools not present in virtual environments created with venv. Need to install it. 1956 | pip install pyznap 1957 | deactivate ##exit virtual env 1958 | ln -s /opt/pyznap/venv/bin/pyznap /usr/local/bin/pyznap 1959 | /opt/pyznap/venv/bin/pyznap setup ##config file created /etc/pyznap/pyznap.conf 1960 | chown root:root -R /etc/pyznap/ 1961 | ##update config 1962 | cat >> /etc/pyznap/pyznap.conf <<-EOF 1963 | [$RPOOL/ROOT] 1964 | frequent = 4 1965 | hourly = 24 1966 | daily = 7 1967 | weekly = 4 1968 | monthly = 6 1969 | yearly = 1 1970 | snap = yes 1971 | clean = yes 1972 | EOF 1973 | 1974 | cat > /etc/cron.d/pyznap <<-EOF 1975 | SHELL=/bin/sh 1976 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 1977 | */15 * * * * root /opt/pyznap/venv/bin/pyznap snap >> /var/log/pyznap.log 2>&1 1978 | EOF 1979 | 1980 | ##integrate with apt 1981 | cat > /etc/apt/apt.conf.d/80-zfs-snapshot <<-EOF 1982 | DPkg::Pre-Invoke {"if [ -x /usr/local/bin/pyznap ]; then /usr/local/bin/pyznap snap; fi"}; 1983 | EOF 1984 | 1985 | pyznap snap ##Take ZFS snapshots and perform cleanup as per config file. 1986 | 1987 | } 1988 | 1989 | extra_programs(){ 1990 | 1991 | case "$extra_programs" in 1992 | yes) 1993 | ##additional programs 1994 | 1995 | ##install samba mount access 1996 | apt install -yq cifs-utils 1997 | 1998 | ##install openssh-server 1999 | apt install -y openssh-server 2000 | 2001 | apt install --yes man-db tldr locate 2002 | 2003 | ;; 2004 | no) 2005 | true 2006 | ;; 2007 | *) 2008 | echo "Extra_programs variable not recognised. Check extra_programs variable." 2009 | exit 1 2010 | ;; 2011 | esac 2012 | 2013 | } 2014 | 2015 | logcompress(){ 2016 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 2017 | 2018 | ##Disable log compression 2019 | for file in /etc/logrotate.d/* ; do 2020 | if grep -Eq "(^|[^#y])compress" "\$file" ; then 2021 | sed -i -r "s/(^|[^#y])(compress)/\1#\2/" "\$file" 2022 | fi 2023 | done 2024 | 2025 | EOCHROOT 2026 | } 2027 | 2028 | keyboard_console_setup(){ 2029 | 2030 | cp "${kb_console_settings}" "$mountpoint"/tmp 2031 | 2032 | chroot "$mountpoint" <<-EOCHROOT 2033 | 2034 | apt install -y debconf-utils 2035 | cat "${kb_console_settings}" 2036 | debconf-set-selections < "${kb_console_settings}" 2037 | 2038 | ##https://serverfault.com/questions/539911/setting-debconf-selections-for-keyboard-configuration-fails-layout-ends-up-as 2039 | ##Delete the keyboard config file before running dpkg-reconfigure. Otherwise config file will not be updated and will stay as "us" default. 2040 | rm /etc/default/keyboard 2041 | 2042 | dpkg-reconfigure -f noninteractive keyboard-configuration 2043 | dpkg-reconfigure -f noninteractive console-setup 2044 | 2045 | EOCHROOT 2046 | } 2047 | 2048 | fixfsmountorder(){ 2049 | 2050 | identify_ubuntu_dataset_uuid 2051 | 2052 | chroot "$mountpoint" /bin/bash -x <<-EOCHROOT 2053 | ##Fix filesystem mount ordering 2054 | 2055 | mkdir -p /etc/zfs/zfs-list.cache 2056 | 2057 | touch /etc/zfs/zfs-list.cache/$RPOOL 2058 | #ln -s /usr/lib/zfs-linux/zed.d/history_event-zfs-list-cacher.sh /etc/zfs/zed.d 2059 | zed -F & 2060 | sleep 2 2061 | 2062 | ##Verify that zed updated the cache by making sure this is not empty: 2063 | ##If it is empty, force a cache update and check again: 2064 | ##Note can take a while. c.30 seconds for loop to succeed. 2065 | cat /etc/zfs/zfs-list.cache/$RPOOL 2066 | while [ ! -s /etc/zfs/zfs-list.cache/$RPOOL ] 2067 | do 2068 | zfs set canmount=noauto $RPOOL/ROOT/${rootzfs_full_name} 2069 | sleep 1 2070 | done 2071 | cat /etc/zfs/zfs-list.cache/$RPOOL 2072 | 2073 | ##Stop zed: 2074 | pkill -9 "zed*" 2075 | sleep 2 2076 | 2077 | ##Fix the paths to eliminate $mountpoint: 2078 | sed -Ei "s|$mountpoint/?|/|" /etc/zfs/zfs-list.cache/$RPOOL 2079 | cat /etc/zfs/zfs-list.cache/$RPOOL 2080 | #update-initramfs -u -k all ##Update zfs cache in initramfs 2081 | 2082 | EOCHROOT 2083 | 2084 | } 2085 | 2086 | unmount_datasets(){ 2087 | ##https://unix.stackexchange.com/questions/120827/recursive-umount-after-rbind-mount/371208#371208 2088 | ##https://unix.stackexchange.com/a/120901 2089 | mount --make-rslave "$mountpoint"/dev 2090 | mount --make-rslave "$mountpoint"/proc 2091 | mount --make-rslave "$mountpoint"/sys 2092 | 2093 | grep "$mountpoint" /proc/mounts | cut -f2 -d" " | sort -r | xargs umount -n 2094 | } 2095 | 2096 | setupremoteaccess(){ 2097 | if [ -f /etc/zfsbootmenu/dracut.conf.d/dropbear.conf ]; 2098 | then echo "Remote access already appears to be installed owing to the presence of /etc/zfsbootmenu/dracut.conf.d/dropbear.conf. Install cancelled." 2099 | else 2100 | disclaimer 2101 | remote_zbm_access_Func "base" 2102 | fi 2103 | } 2104 | 2105 | createdatapool(){ 2106 | disclaimer 2107 | 2108 | ##Check on whether data pool already exists 2109 | if [ "$(zpool status "$datapool")" ]; 2110 | then 2111 | echo "Warning: $datapool already exists. Are you use you want to wipe the drive and destroy $datapool? Press Enter to Continue or CTRL+C to abort." 2112 | read -r _ 2113 | else 2114 | echo "$datapool pre-existance check passed." 2115 | fi 2116 | 2117 | ##Warning on auto unlock 2118 | if [ -n "$zfs_data_password" ]; 2119 | then 2120 | echo "Warning: Encryption selected. If the root pool is also encrypted then the root pool keyfile will be used to auto unlock the data pool at boot. Press Enter to Continue or CTRL+C to abort." 2121 | read -r _ 2122 | else true 2123 | fi 2124 | 2125 | ##Get datapool disk ID(s) 2126 | getdiskID_pool "data" 2127 | 2128 | ##Clear partition table 2129 | clear_partition_table "data" 2130 | partprobe 2131 | sleep 2 2132 | 2133 | ##Create pool mount point 2134 | if [ -d "$datapoolmount" ]; then 2135 | echo "Data pool mount point exists." 2136 | else 2137 | mkdir -p "$datapoolmount" 2138 | chown "$user":"$user" "$datapoolmount" 2139 | echo "Data pool mount point created." 2140 | fi 2141 | echo "$datapoolmount" 2142 | 2143 | ##Automount with zfs-mount-generator 2144 | touch /etc/zfs/zfs-list.cache/"$datapool" 2145 | 2146 | ##Create data pool 2147 | create_zpool_Func "data" 2148 | 2149 | ##Update crypttab for autounlock if luks used on root pool 2150 | if [ "$zfs_data_encrypt" = "luks" ]; 2151 | then 2152 | if [ -f "/etc/cryptsetup-keys.d/$RPOOL.key" ]; 2153 | then 2154 | update_crypttab_Func "base" "data" 2155 | else 2156 | echo "$RPOOL.key not found in /etc/cryptsetup-keys.d/." 2157 | exit 1 2158 | fi 2159 | else true 2160 | fi 2161 | 2162 | ##Verify that zed updated the cache by making sure the cache file is not empty. 2163 | cat /etc/zfs/zfs-list.cache/"$datapool" 2164 | ##If it is empty, force a cache update and check again. 2165 | ##Note can take a while. c.30 seconds for loop to succeed. 2166 | while [ ! -s /etc/zfs/zfs-list.cache/"$datapool" ] 2167 | do 2168 | ##reset any pool property to update cache files 2169 | zfs set canmount=on "$datapool" 2170 | sleep 1 2171 | done 2172 | cat /etc/zfs/zfs-list.cache/"$datapool" 2173 | 2174 | ##Create link to datapool mount point in user home directory. 2175 | ln -s "$datapoolmount" "/home/$user/" 2176 | chown -R "$user":"$user" {"$datapoolmount","/home/$user/$datapool"} 2177 | 2178 | zpool status 2179 | zfs list 2180 | 2181 | } 2182 | 2183 | reinstall-zbm(){ 2184 | 2185 | isolate_generate_zbm_version(){ 2186 | ##generate-zbm quits after printing version number. Isolate in a function to allow script to continue. 2187 | set +e 2188 | generate-zbm --showver > /tmp/zfs_installed_version.txt || echo "No version number reported by generate-zbm --showver." > /tmp/zfs_installed_version.txt 2189 | set -e 2190 | } 2191 | 2192 | disclaimer 2193 | connectivity_check #Check for internet connectivity. 2194 | 2195 | ##Live environment check. 2196 | if grep casper /proc/cmdline >/dev/null 2>&1; 2197 | then 2198 | echo "Live environment present. Reboot into new installation to re-install zfsbootmenu." 2199 | exit 1 2200 | else 2201 | true 2202 | fi 2203 | 2204 | command -v generate-zbm >/dev/null 2>&1 || { echo >&2 "Please install zfsbootmenu before attempting to re-install. Exiting."; exit 1; } #Check for Zfsbootmenu. 2205 | 2206 | ##Version check 2207 | zbm_github_latest_version="$(curl -s https://api.github.com/repos/zbm-dev/zfsbootmenu/releases/latest | grep tag_name | cut -d : -f 2,3 | tr -d \"|sed 's/^[ \t]*//'|sed 's/,//'|sed 's,^v,,')" 2208 | printf '%s%s\n' "Latest zfsbootmenu github release version: " "${zbm_github_latest_version}" 2209 | 2210 | isolate_generate_zbm_version 2211 | 2212 | zbm_installed_version="$(cat /tmp/zfs_installed_version.txt)" 2213 | printf '%s%s\n' "Installed zfsbootmenu version: " "${zbm_installed_version}" 2214 | if [ "${zbm_github_latest_version}" = "${zbm_installed_version}" ]; 2215 | then 2216 | printf '%s\n' "Installed version of zfsbootmenu is the latest release. Enter Y to re-install or N to exit." 2217 | read -r reinstall_selection 2218 | case "${reinstall_selection-default}" in 2219 | Y|y) 2220 | echo "Re-installing zfsbootmenu." 2221 | zfsbootmenu_install_config_Func "base" 2222 | ;; 2223 | *) 2224 | printf "%s\n" "Exiting." 2225 | exit 0 2226 | ;; 2227 | esac 2228 | else 2229 | zfsbootmenu_install_config_Func "base" 2230 | fi 2231 | } 2232 | 2233 | reinstall-pyznap(){ 2234 | 2235 | if [ -f /etc/apt/apt.conf.d/80-zfs-snapshot ]; 2236 | then 2237 | rm /etc/apt/apt.conf.d/80-zfs-snapshot 2238 | else true 2239 | fi 2240 | 2241 | if [ -f /usr/local/bin/pyznap ]; 2242 | then 2243 | rm /usr/local/bin/pyznap 2244 | else true 2245 | fi 2246 | 2247 | if [ -d /opt/pyznap ]; 2248 | then 2249 | rm -rf /opt/pyznap 2250 | else true 2251 | fi 2252 | 2253 | if [ -f /etc/pyznap/pyznap.conf ]; 2254 | then 2255 | rm /etc/pyznap/pyznap.conf 2256 | else true 2257 | fi 2258 | 2259 | pyznapinstall 2260 | 2261 | } 2262 | 2263 | ##-------- 2264 | logFunc 2265 | date 2266 | update_date_time(){ 2267 | ##Update time to correct out of date virtualbox clock when using snapshots for testing. 2268 | timedatectl 2269 | 2270 | manual_set(){ 2271 | timedatectl set-ntp off 2272 | sleep 1 2273 | timedatectl set-time "2021-01-01 00:00:00" 2274 | } 2275 | #manual_set 2276 | 2277 | sync_ntp(){ 2278 | 2279 | systemctl restart systemd-timesyncd.service 2280 | systemctl status systemd-timesyncd.service 2281 | 2282 | } 2283 | sync_ntp 2284 | 2285 | timedatectl 2286 | } 2287 | update_date_time 2288 | 2289 | initialinstall(){ 2290 | 2291 | disclaimer 2292 | live_desktop_check 2293 | connectivity_check #Check for internet connectivity. 2294 | getdiskID_pool "root" 2295 | ipv6_apt_live_iso_fix #Only active if ipv6_apt_fix_live_iso variable is set to "yes". 2296 | 2297 | debootstrap_part1_Func 2298 | debootstrap_createzfspools_Func 2299 | debootstrap_installminsys_Func 2300 | systemsetupFunc_part1 #Basic system configuration. 2301 | systemsetupFunc_part2 #Install zfs. 2302 | systemsetupFunc_part3 #Format EFI partition. 2303 | 2304 | keyboard_console_setup #Configure keyboard and console. 2305 | systemsetupFunc_part4 #Install zfsbootmenu. 2306 | systemsetupFunc_part5 #Config swap, tmpfs, rootpass. 2307 | 2308 | usersetup #Create user account and setup groups. 2309 | logcompress #Disable log compression. 2310 | reinstate_apt "chroot" #Reinstate non-mirror package sources in new install. 2311 | script_copy #Copy script to new installation. 2312 | fixfsmountorder #ZFS file system mount ordering. 2313 | logcopy #Copy install log to new installation. 2314 | 2315 | #unmount_datasets #Unmount datasets. 2316 | 2317 | echo "Initial minimal system setup complete." 2318 | echo "Reboot required to complete installation." 2319 | echo "First login is ${user}:${PASSWORD-}" 2320 | echo "Following reboot, run script with postreboot option to complete installation." 2321 | echo "Reboot." 2322 | } 2323 | 2324 | postreboot(){ 2325 | disclaimer 2326 | connectivity_check #Check for internet connectivity. 2327 | 2328 | distroinstall #Upgrade the minimal system to the selected distro. 2329 | NetworkManager_config #Adjust networking config for NetworkManager, if installed by distro. 2330 | pyznapinstall #Snapshot management. 2331 | extra_programs #Install extra programs. 2332 | reinstate_apt "base" #Reinstate non-mirror package sources in new install. 2333 | 2334 | echo "Installation complete: ${distro_variant}." 2335 | echo "Reboot." 2336 | } 2337 | 2338 | case "${1-default}" in 2339 | initial) 2340 | echo "Running initial system installation. Press Enter to Continue or CTRL+C to abort." 2341 | read -r _ 2342 | initialinstall 2343 | ;; 2344 | postreboot) 2345 | echo "Running postreboot setup. Press Enter to Continue or CTRL+C to abort." 2346 | read -r _ 2347 | postreboot 2348 | ;; 2349 | remoteaccess) 2350 | echo "Running remote access to ZFSBootMenu install. Press Enter to Continue or CTRL+C to abort." 2351 | read -r _ 2352 | setupremoteaccess 2353 | ;; 2354 | datapool) 2355 | echo "Running create data pool on non-root drive. Press Enter to Continue or CTRL+C to abort." 2356 | read -r _ 2357 | createdatapool 2358 | ;; 2359 | reinstall-zbm) 2360 | echo "Re-installing zfsbootmenu. Press Enter to Continue or CTRL+C to abort." 2361 | read -r _ 2362 | reinstall-zbm 2363 | ;; 2364 | reinstall-pyznap) 2365 | echo "Re-installing pyznap. Press Enter to Continue ot CTRL+C to abort." 2366 | read -r 2367 | reinstall-pyznap 2368 | ;; 2369 | *) 2370 | printf "%s\n%s\n%s\n" "-----" "Usage: $0 initial | postreboot | remoteaccess | datapool | reinstall-zbm | reinstall-pyznap" "-----" 2371 | ;; 2372 | esac 2373 | 2374 | date 2375 | exit 0 2376 | --------------------------------------------------------------------------------