Compare commits
	
		
			8 Commits
		
	
	
		
			b9da09402a
			...
			16c746e9d4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 16c746e9d4 | |||
| 2f209db426 | |||
| ef029186fe | |||
| ba181c5fce | |||
| 7f2b16c6a4 | |||
| 7384dd769b | |||
| af9deec753 | |||
| 005fe43352 | 
							
								
								
									
										72
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								README.md
									
									
									
									
									
								
							@@ -8,8 +8,37 @@ We expect minimal prep on your end. Please make sure that before execution the f
 | 
			
		||||
 | 
			
		||||
- Arch Linux live CD ISO image sees exactly one partition with partition type code `BF00` ("Solaris root")
 | 
			
		||||
- Arch Linux live CD ISO image sees exactly one partition with partition type code `EF00` ("EFI system partition")
 | 
			
		||||
- The `EF00` EFI partition is mountable, in practical terms this usually only means it has a file system.
 | 
			
		||||
- No ZFS zpool exists
 | 
			
		||||
 | 
			
		||||
### How to prep
 | 
			
		||||
 | 
			
		||||
On a blank example disk `/dev/sda` you can fulfill the requirements (One `EF00` partition with a file system plus one `BF00` partition) for example like so:
 | 
			
		||||
```
 | 
			
		||||
sgdisk --new '1::+100M' --new '2' --typecode '1:EF00' --typecode '2:BF00' /dev/sda
 | 
			
		||||
mkfs.vfat /dev/sda1
 | 
			
		||||
```
 | 
			
		||||
> `--new '1::+100M'`: Create partition number `1`. The field separator `:` separates the partition number from start sector. In this case start sector is unspecified so start sector sits at whatever the system's default is for this operation. On a blank disk on an Arch Linux live CD ISO image this will default to sector `2048`. Partition ends at whatever the beginning is `+100M` meaning plus 100 Mebibytes.
 | 
			
		||||
>
 | 
			
		||||
> `--new '2'`: Create partition number `2`. Both field number 2, the start sector, and field number 3, the end sector, are unspecified, there's no field separator `:`. Field number 2 will be the first free sector - in this case right after partition 1 - and field number 3 will be end of disk. Thus partition `2` will fill the remaining free disk space.
 | 
			
		||||
>
 | 
			
		||||
> `--typecode '1:EF00'`: Partition 1 gets partition type code `EF00`, an EFI system partition.
 | 
			
		||||
>
 | 
			
		||||
> `--typecode '2:BF00'`: Partition 2 gets partition type code `BF00`, an Solaris root partition.
 | 
			
		||||
 | 
			
		||||
The result will be something like this at which point you can start the `setup.sh` script, see [How to run this?](#how-to-run-this) below for more details.
 | 
			
		||||
```
 | 
			
		||||
# lsblk --paths 
 | 
			
		||||
NAME         MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
 | 
			
		||||
/dev/loop0     7:0    0 688.5M  1 loop /run/archiso/airootfs
 | 
			
		||||
/dev/sr0      11:0    1 810.3M  0 rom  /run/archiso/bootmnt
 | 
			
		||||
/dev/sda     202:0    0    10G  0 disk 
 | 
			
		||||
├─/dev/sda1  202:1    0   100M  0 part
 | 
			
		||||
└─/dev/sda2  202:2    0   9.9G  0 part 
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## ZFS dataset layout
 | 
			
		||||
 | 
			
		||||
The script will create a single ZFS zpool `zpool` on the `BF00` partition with dataset child `zpool/root` which itself has one child `zpool/root/archlinux`, that's where Arch Linux gets installed. Parallel to `zpool/root` it'll create `zpool/data` with a `zpool/data/home` child dataset that gets mounted at `/home`.
 | 
			
		||||
 | 
			
		||||
The script will use the `EF00` partition to install a ZFSBootMenu EFI executable if `efibootmgr` says that no such `ZFSBootMenu` entry exists. If ZFSBootMenu gets added to the EFI partition it'll become primary boot option.
 | 
			
		||||
@@ -24,6 +53,26 @@ The script will use the `EF00` partition to install a ZFSBootMenu EFI executable
 | 
			
		||||
    ```
 | 
			
		||||
    During execution the script will call itself when it changes into its `chroot`, that's why we `export SCRIPT_URL`. Feel free to update `"${SCRIPT_URL}"` with whatever branch or revision you want to use from [quico.space/quico-os-setup/arch-zbm](https://quico.space/quico-os-setup/arch-zbm). Typically `.../branch/main/setup.sh` as shown above is what you want.
 | 
			
		||||
 | 
			
		||||
### Options
 | 
			
		||||
 | 
			
		||||
#### Compression
 | 
			
		||||
 | 
			
		||||
By default we create a zpool with ZFS properties `compression=on`. If the `lz4_compress` pool feature is active this will by default enable `compression=lz4`. See `man 7 zfsprops` for example in ZFS 2.19 for details. See `zpool get feature@lz4_compress <pool>` to check this feature's status on your `<pool>`.
 | 
			
		||||
 | 
			
		||||
To get zpool with uncompressed datasets export the shell variable `ARCHZBM_ZFSPROPS_NO_COMPRESSION` with any value prior to running this script. Literally any value works as long as you're not setting this to an empty string:
 | 
			
		||||
```
 | 
			
		||||
export ARCHZBM_ZFSPROPS_NO_COMPRESSION=yesplease
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Encryption
 | 
			
		||||
 | 
			
		||||
By default we encrypt the zpool with ZFS properties `encryption=on`. In ZFS 2.1.9 this defaults to `encryption=aes-256-gcm`.
 | 
			
		||||
 | 
			
		||||
To get an unencrypted zpool export the shell variable `ARCHZBM_ZFSPROPS_NO_ENCRYPTION` with any value prior to running this script:
 | 
			
		||||
```
 | 
			
		||||
export ARCHZBM_ZFSPROPS_NO_ENCRYPTION=yup
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Steps
 | 
			
		||||
 | 
			
		||||
The scripts takes the following installation steps.
 | 
			
		||||
@@ -60,6 +109,29 @@ After installation you're going to want to at least touch these points in your n
 | 
			
		||||
    - ZFS: The password for all datasets underneath `zpool` is `password`.
 | 
			
		||||
    - Local `root` account: The local `root` account's password is `password`.
 | 
			
		||||
- Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si`.
 | 
			
		||||
- In `/etc/systemd/network/50-wired.network` instead of a DHCP-based network config you can get a static one. The DHCP-based looks for reference looks like:
 | 
			
		||||
    ```
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
    [Network]
 | 
			
		||||
    DHCP=ipv4
 | 
			
		||||
    IPForward=yes
 | 
			
		||||
    Domains=~.
 | 
			
		||||
 | 
			
		||||
    [DHCP]
 | 
			
		||||
    UseDNS=yes
 | 
			
		||||
    RouteMetric=10
 | 
			
		||||
    ```
 | 
			
		||||
    Dynamic one does away with the `[DHCP]` section:
 | 
			
		||||
    ```
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
    [Network]
 | 
			
		||||
    Address=10.10.10.2/24
 | 
			
		||||
    Gateway=10.10.10.1
 | 
			
		||||
    DNS=10.10.10.1
 | 
			
		||||
    IPForward=yes
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
# Password change
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								setup.sh
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								setup.sh
									
									
									
									
									
								
							@@ -111,7 +111,7 @@ function get_drive_id () {
 | 
			
		||||
        printf -- '%s' "${drive_id_single}"
 | 
			
		||||
        return 0
 | 
			
		||||
    fi
 | 
			
		||||
    >2 printf -- '%s\n' 'Not exactly one '"'${1:?}'"' partition entry in /dev/disk/by-id, exiting ...'
 | 
			
		||||
    >2 printf -- '%s\n' 'No '"'${1:?}'"' partition entry in /dev/disk/by-partuuid, exiting ...'
 | 
			
		||||
    exit 77
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -151,22 +151,33 @@ function set_zpool_password () {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function import_pool () {
 | 
			
		||||
    zpool import -d '/dev/disk/by-id' -R '/mnt' "${zpool_name}" -N -f
 | 
			
		||||
    zfs load-key "${zpool_name}"
 | 
			
		||||
    zpool import -d '/dev/disk/by-partuuid' -R '/mnt' "${zpool_name}" -N -f
 | 
			
		||||
    [[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && zfs load-key "${zpool_name}"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function create_pool () {
 | 
			
		||||
    # Create a temporary pool that is not cached
 | 
			
		||||
    # 
 | 
			
		||||
    # Add zfsprops 'compression' unless environment variable
 | 
			
		||||
    # ARCHZBM_ZFSPROPS_NO_COMPRESSION is set to any value.
 | 
			
		||||
    # 
 | 
			
		||||
    # Add zfsprops 'encryption' along with 'keyformat' and a 'keylocation'
 | 
			
		||||
    # unless environment variable ARCHZBM_ZFSPROPS_NO_ENCRYPTION is set to
 | 
			
		||||
    # any value.
 | 
			
		||||
    zpool create -f                                                     \
 | 
			
		||||
                 -o 'ashift=12'                                         \
 | 
			
		||||
                 -o 'autotrim=on'                                       \
 | 
			
		||||
                 -O 'acltype=posix'                                     \
 | 
			
		||||
                 -O 'compression=on'                                    \
 | 
			
		||||
                 $([[ ! "${ARCHZBM_ZFSPROPS_NO_COMPRESSION}" ]] &&      \
 | 
			
		||||
                    printf -- '%s '                                     \
 | 
			
		||||
                '-O compression=on')                                    \
 | 
			
		||||
                 -O 'relatime=on'                                       \
 | 
			
		||||
                 -O 'xattr=sa'                                          \
 | 
			
		||||
                 -O 'encryption=on'                                     \
 | 
			
		||||
                 -O 'keyformat=passphrase'                              \
 | 
			
		||||
                 -O 'keylocation=file:///etc/zfs/'"${zpool_name}"'.key' \
 | 
			
		||||
                 $([[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] &&       \
 | 
			
		||||
                    printf -- '%s '                                     \
 | 
			
		||||
                '-O encryption=on'                                      \
 | 
			
		||||
                '-O keyformat=passphrase'                               \
 | 
			
		||||
                '-O keylocation=file:///etc/zfs/'"${zpool_name}"'.key') \
 | 
			
		||||
                 -O 'normalization=formD'                               \
 | 
			
		||||
                 -O 'mountpoint=none'                                   \
 | 
			
		||||
                 -O 'canmount=off'                                      \
 | 
			
		||||
@@ -202,7 +213,7 @@ function setup_zpool () {
 | 
			
		||||
    zpool_drive="$(select_part 'zfs')"
 | 
			
		||||
    drive_by_id="$(get_drive_id "${zpool_drive}")"
 | 
			
		||||
 | 
			
		||||
    set_zpool_password
 | 
			
		||||
    [[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && set_zpool_password
 | 
			
		||||
    if no_zpool_exists; then
 | 
			
		||||
        create_pool "${drive_by_id}"
 | 
			
		||||
        create_root_dataset
 | 
			
		||||
@@ -237,8 +248,20 @@ function pacman_dl_parallel () {
 | 
			
		||||
    sed -ri -e 's'$'\x1''^.*?(ParallelDownloads)[^\r\n\f]*'$'\x1''\1 = 5'$'\x1''g' '/etc/pacman.conf'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function pacman_dont_check_space () {
 | 
			
		||||
    # See pacman bug comment
 | 
			
		||||
    # https://bugs.archlinux.org/task/45070#comment142712
 | 
			
		||||
    # 
 | 
			
		||||
    # When we pacstrap onto ZFS pacman incorrectly calculates and
 | 
			
		||||
    # overestimates required disk space. We instead assume an installation
 | 
			
		||||
    # gets done with at least a 10 GiB drive which is plenty. Skip pacman's
 | 
			
		||||
    # size check.
 | 
			
		||||
    sed -ri -e 's'$'\x1''^.*?(CheckSpace)([^\r\n\f]*)'$'\x1''#\1\2'$'\x1''g' '/etc/pacman.conf'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function install_archlinux () {
 | 
			
		||||
    pacman_dl_parallel
 | 
			
		||||
    pacman_dont_check_space
 | 
			
		||||
    pacstrap /mnt              \
 | 
			
		||||
        base                   \
 | 
			
		||||
        base-devel             \
 | 
			
		||||
@@ -293,14 +316,17 @@ function set_locale () {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function add_zfs_hook_to_initramfs () {
 | 
			
		||||
    # Add zfs hook, remove fsck hook from initramfs. Also add plain text key
 | 
			
		||||
    # file into initramfs since it's living inside an encrypted pool anyway.
 | 
			
		||||
    # Add zfs hook, remove fsck hook from initramfs.
 | 
			
		||||
    sed -ri \
 | 
			
		||||
        -e 's'$'\x1''^(FILES=)[^\r\n\f]*'$'\x1''\1(/etc/zfs/'"${zpool_name}"'.key)'$'\x1''g' \
 | 
			
		||||
        -e 's'$'\x1''(HOOKS=)(.*?[\(| ])(filesystems)([\)| ][^\r\n\f]*)'$'\x1''\1\2zfs \3\4'$'\x1''g' \
 | 
			
		||||
        -e 's'$'\x1''((\()(fsck)(\)))'$'\x1''\2\4'$'\x1''g' \
 | 
			
		||||
        -e 's'$'\x1''(([[:space:]]+)(fsck)|(fsck)([[:space:]]+))'$'\x1'''$'\x1''g' \
 | 
			
		||||
        '/mnt/etc/mkinitcpio.conf'
 | 
			
		||||
    # Also unless encryption's unwanted add plain text key file into
 | 
			
		||||
    # initramfs since it's living inside an encrypted pool anyway.
 | 
			
		||||
    [[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && sed -ri \
 | 
			
		||||
        -e 's'$'\x1''^(FILES=)[^\r\n\f]*'$'\x1''\1(/etc/zfs/'"${zpool_name}"'.key)'$'\x1''g' \
 | 
			
		||||
        '/mnt/etc/mkinitcpio.conf'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function set_initramfs_build_list () {
 | 
			
		||||
@@ -317,7 +343,7 @@ function set_initramfs_build_list () {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function add_zfs_files_to_new_os () {
 | 
			
		||||
    for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' '/etc/zfs/'"${zpool_name}"'.key'; do
 | 
			
		||||
    for zfs_file in '/etc/hostid' '/etc/zfs/zpool.cache' $([[ ! "${ARCHZBM_ZFSPROPS_NO_ENCRYPTION}" ]] && printf -- '%s' '/etc/zfs/'"${zpool_name}"'.key'); do
 | 
			
		||||
        rsync -av --itemize-changes {'','/mnt'}"${zfs_file}"
 | 
			
		||||
    done
 | 
			
		||||
}
 | 
			
		||||
@@ -586,7 +612,7 @@ function get_disks_with_one_efipart () {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function add_zbm_to_efi () {
 | 
			
		||||
    if ! efibootmgr | grep -Pi -- 'ZFSBootMenu'; then
 | 
			
		||||
    if ! efibootmgr | grep -Piq -- 'ZFSBootMenu'; then
 | 
			
		||||
        local efi_disks_list
 | 
			
		||||
        efi_disks_list="$(get_disks_with_one_efipart)"
 | 
			
		||||
        if grep -Piq -- '^'"${efi_drive}"'$' <<<"${efi_disks_list}"; then
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user