Merge pull request '3-xen-orchestra-install' (#4) from 3-xen-orchestra-install into main
Reviewed-on: #4
This commit is contained in:
commit
38396d7ccf
260
README.md
260
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::+512M' --new '2' --typecode '1:EF00' --typecode '2:BF00' /dev/sda
|
||||
mkfs.vfat /dev/sda1
|
||||
```
|
||||
> `--new '1::+512M'`: 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 `+512M` meaning plus 512 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`, a 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 685.5M 1 loop /run/archiso/airootfs
|
||||
/dev/sr0 11:0 1 808.3M 0 rom /run/archiso/bootmnt
|
||||
/dev/sda 202:0 0 10G 0 disk
|
||||
├─/dev/sda1 202:1 0 512M 0 part
|
||||
└─/dev/sda2 202:2 0 9.5G 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.
|
||||
@ -19,17 +48,37 @@ The script will use the `EF00` partition to install a ZFSBootMenu EFI executable
|
||||
- Boot an Arch Linux live CD ISO image
|
||||
- Run:
|
||||
```
|
||||
export SCRIPT_URL='https://quico.space/quico-os-setup/arch-zbm/raw/branch/main/setup.sh'
|
||||
curl -s "${SCRIPT_URL}" | bash
|
||||
export SCRIPT_URL='https://quico.space/quico-os-setup/arch-zbm/raw/branch/main/setup.sh' && curl -s "${SCRIPT_URL}" | bash
|
||||
```
|
||||
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 property `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.1.9 for details. See `zpool get feature@lz4_compress <pool>` to check this feature's status on your `<pool>`.
|
||||
|
||||
To get a 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 property `encryption=on`. In ZFS 2.1.9 this defaults to `encryption=aes-256-gcm`.
|
||||
|
||||
To get a zpool with unencrypted datasets 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.
|
||||
The script takes the following installation steps.
|
||||
|
||||
1. Install ZFS tools and kernel module with [github.com/eoli3n/archiso-zfs](https://github.com/eoli3n/archiso-zfs)
|
||||
1. Create one encrypted ZFS zpool on top of `BF00` partition, password `password`
|
||||
1. Create one ZFS zpool on top of `BF00` partition, encrypted and compressed datasets, password `password`
|
||||
1. _See paragraphs [Compression](#compression)/[Encryption](#encryption) to optionally disable properties_
|
||||
1. Create dataset for Arch Linux and `/home`
|
||||
1. Install Arch Linux into pool
|
||||
1. Add ZFSBootMenu to `EF00` partition if it doesn't exist already
|
||||
@ -59,7 +108,37 @@ After installation you're going to want to at least touch these points in your n
|
||||
- Passwords
|
||||
- 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`.
|
||||
- Arch User Repository (AUR) helper: We installed [paru](https://github.com/Morganamilo/paru) as our AUR helper, we installed from GitHub via `makepkg -si` then replaced itself with its [paru-bin](https://aur.archlinux.org/packages/paru-bin) version from AUR.
|
||||
- In `/etc/systemd/network/50-wired.network` instead of a DHCP-based network config you can get a static one. The DHCP-based one for reference looks like:
|
||||
```
|
||||
...
|
||||
|
||||
[Network]
|
||||
DHCP=ipv4
|
||||
IPForward=yes
|
||||
Domains=~.
|
||||
|
||||
[DHCP]
|
||||
UseDNS=yes
|
||||
RouteMetric=10
|
||||
```
|
||||
A static config 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
|
||||
Domains=~.
|
||||
```
|
||||
- In case you later want a graphical interface and specifically NetworkManager (via package `networkmanager`) consider telling it to keep its hands off of some of your network interfaces. The bullet point above adds a `systemd`-style config file that `systemd-networkd.service` will read and use. Should you ever install NetworkManager it will by default assume that it must manage all interfaces. It'll use its own DHCP client to try and get IP addresses for _managed interfaces_ in which case you'll end up with whatever addressing scheme you configured in a `.network` unit file plus NetworkManager's additional address. Create `/etc/NetworkManager/conf.d/99-unmanaged-devices.conf` for example to declare some interfaces as off-limits or _unmanaged_:
|
||||
```
|
||||
[keyfile]
|
||||
unmanaged-devices=mac:52:54:00:74:79:56;type:ethernet
|
||||
```
|
||||
Check out [ArchWiki article "NetworkManager" section "Ignore specific devices"](https://wiki.archlinux.org/title/NetworkManager#Ignore_specific_devices) for more info.
|
||||
|
||||
# Password change
|
||||
|
||||
@ -144,7 +223,7 @@ In order to generate a new master key after you've changed your user key as ment
|
||||
- With `-v` we get verbose progress output
|
||||
- Argument `-u` makes sure the dataset does not get mounted after transfer. ZFS would mount it into `/` which wouldn't be helpful since we're currently using that filesystem ourselves.
|
||||
- We set encryption properties `keyformat`, `keylocation` and most importantly `encryption`. The latter will turn our transferred dataset into its own `encryptionroot` which in turn generates a new master key. The auto-generated new master key gets wrapped with our updated passphrase in `keylocation`. This basically reencrypts all data in this dataset during transfer.
|
||||
- We set `mountpoint` and `canmount` as well as a `org.zfsbootmenu:commandline` as we would for any new system dataset.
|
||||
- We set `mountpoint` and `canmount` as well as an `org.zfsbootmenu:commandline` as we would for any new system dataset.
|
||||
1. Change zpool's `bootfs` property to new system dataset
|
||||
```
|
||||
zpool set bootfs=zpool/root/archlinux-frn zpool
|
||||
@ -204,6 +283,8 @@ zpool status zpool
|
||||
|
||||
# ZFS setup explained
|
||||
|
||||
## Overview
|
||||
|
||||
The ZFS pool and dataset setup that makes this tick, explained in plain English.
|
||||
|
||||
1. Create zpool with options:
|
||||
@ -215,8 +296,9 @@ The ZFS pool and dataset setup that makes this tick, explained in plain English.
|
||||
1. `-O keylocation=file://...`: This property is only set for encrypted datasets which are encryption roots. Controls where the user's encryption key will be loaded from by default for commands such as `zfs load-key`.
|
||||
1. `-O keyformat=passphrase`: Controls what format the user's encryption key will be provided as. Passphrases must be between 8 and 512 bytes long.
|
||||
1. At this time the newly created zpool is not mounted anywhere. Next we create the "root" dataset, that's an arbitary term for the parent dataset of all boot environments. Boot environments in your case may be for example different operating systems all of which live on separate datasets underneath the root.
|
||||
1. `-o canmount=off`: Same as above, the root dataset can - just like the pool - not be mounted.
|
||||
1. `-o mountpoint=none`: Same as above, the root dataset has - just like the pool - no mountpoint configured.
|
||||
1. `zfs set org.zfsbootmenu:commandline=...`: Set a common kernel command line for all boot environment such as `"ro quiet"`.
|
||||
1. `zfs set org.zfsbootmenu:commandline=...`: Set a common kernel command line for all boot environments such as `"ro quiet"`.
|
||||
1. Neither the root dataset nor the pool are mounted at this time. We now create one boot environment dataset where we want to install Arch Linux.
|
||||
1. `-o mountpoint=/`: Our Arch Linux dataset will be mounted at `/`.
|
||||
1. `-o canmount=noauto`: When set to `noauto`, a dataset can only be mounted and unmounted explicitly. The dataset is not mounted automatically when the dataset is created or imported, nor is it mounted by the `zfs mount -a` command or unmounted by the `zfs unmount -a` command.
|
||||
@ -229,15 +311,171 @@ The ZFS pool and dataset setup that makes this tick, explained in plain English.
|
||||
1. For a `zpool/data/home` child dataset:
|
||||
1. We do not specify any properties. Since `canmount` cannot be inherited the parent's `canmount=off` does not apply, it instead defaults to `canmount=on`. The parent's `mountpoint=/` property on the other hand is inherited so for a `home` child dataset it conveniently equals `mountpoint=/home`.
|
||||
1. In effect this `zpool/data/home` dataset is subject to `zfs mount -a` and will happily automount into `/home`.
|
||||
1. We export the zpool once, we then reimport it by scanning only inside `/dev/disk/by-id`, again setting `-R /mnt` as we did during pool creation a moment ago and we do not mount any file systems.
|
||||
1. We export the zpool once, we then reimport it by scanning only inside `/dev/disk/by-partuuid`, again setting `-R /mnt` as we did during pool creation a moment ago and we do not mount any file systems.
|
||||
1. We `zfs load-key <encryptionroot>` which will load the key from `keylocation` after which the `keystatus` property for `<encryptionroot>` and all child datasets will change from `unavailable` to `available`.
|
||||
1. We mount our Arch Linux boot environment dataset. It automatically get prepended with `-R /mnt` since that's how we imported the pool.
|
||||
1. We mount our Arch Linux boot environment dataset. It automatically gets prefixed with `-R /mnt` since that's how we imported the pool.
|
||||
1. We `zfs mount -a` which automounts `zpool/data/home` into `/home`, which again gets auto-prepended by `/mnt`.
|
||||
1. We lastly mount our EFI partition into `/mnt/efi`.
|
||||
1. We instruct ZFS to save its pool configuration via `zpool set cachefile=/etc/zfs/zpool.cache zpool`.
|
||||
|
||||
The complete ZFS structure now exists and is mounted at `/mnt` ready for any `pacstrap`, [debootstrap](https://wiki.debian.org/Debootstrap), `dnf --installroot` or other bootstrapping action.
|
||||
|
||||
## Adding another boot environment-independent dataset
|
||||
|
||||
Assume that in addition to your `/home` data which lives on `zpool/data/home` you want another dataset that is exempt from Arch Linux snapshots.
|
||||
|
||||
Consider an example `/opt/git` directory where a bunch of Git repos are checked out on which you work. You don't want them to be snapshotted - and rolled back - when something goes sideways: they are decoupled from everything else that goes on on your machine so you can easily and safely have a static `/opt/git` directory available in all boot environments.
|
||||
|
||||
Move your current `/opt/git` data out of the way for a moment:
|
||||
```
|
||||
mv '/opt/git'{,'.bak'}
|
||||
```
|
||||
Create datasets
|
||||
```
|
||||
zfs create -o canmount=off zpool/data/opt
|
||||
zfs create zpool/data/opt/git
|
||||
```
|
||||
Remember that the `zpool/data` dataset already exists and that it has both `mountpoint=/` and `canmount=off` set. It is not and cannot be mounted itself, it instead conveniently anchors datasets at `/`. Since the `canmount` dataset property cannot be inherited and defaults to `canmount=on` we have to manually specify `-o canmount=off`. Our new `zpool/data/opt` should not automatically mount into `/opt`.
|
||||
|
||||
We then create the child dataset `zpool/data/opt/git`, it defaults to `canmount=on` thus immediately shows up at `/opt/git`.
|
||||
|
||||
Move data back into place and clean up temp directory
|
||||
```
|
||||
rsync -av --remove-source-files '/opt/git'{'.bak',}'/'
|
||||
find '/opt/git.bak' -type d -empty -delete
|
||||
```
|
||||
|
||||
An example `zpool/data` dataset may now look like so:
|
||||
```
|
||||
# zfs list -r -oname,mountpoint,canmount,mounted zpool/data
|
||||
NAME MOUNTPOINT CANMOUNT MOUNTED
|
||||
zpool/data / off no
|
||||
zpool/data/home /home on yes
|
||||
zpool/data/opt /opt off no
|
||||
zpool/data/opt/git /opt/git on yes
|
||||
```
|
||||
|
||||
## Nested environment-independent datasets
|
||||
|
||||
### Caution
|
||||
|
||||
If you want a dedicated dataset for a directory that lives deeper in your file system tree than just `/opt/git`, for example like `/var/lib/docker` make sure to not recursively create this structure in a single `zfs create` command.
|
||||
|
||||
In [Adding another boot environment-independent dataset](#adding-another-boot-environment-independent-dataset) above you can safely do:
|
||||
```
|
||||
zfs create -o canmount=off zpool/data/opt
|
||||
```
|
||||
Here `zpool/data` already exists, you're only creating one child dataset `opt` and you're setting `-o canmount=off` so that it never mounts into your `/opt` directory.
|
||||
|
||||
Now consider the same setup for `/var/lib/docker`. If you follow the exact same approach:
|
||||
```
|
||||
zfs create -o canmount=off zpool/data/var/lib
|
||||
```
|
||||
Docker will correctly report:
|
||||
```
|
||||
cannot create 'zpool/data/var/lib': parent does not exist
|
||||
```
|
||||
You might want to just create the parent then with `-p` argument:
|
||||
```
|
||||
zfs create -p -o canmount=off zpool/data/var/lib
|
||||
~~
|
||||
```
|
||||
Note, however, that `-o canmount=off` only applies to `lib` dataset and that `zpool/data/var` has just been auto-mounted into `/var`:
|
||||
```
|
||||
# zfs list -r -oname,mountpoint,canmount,mounted zpool/data
|
||||
NAME MOUNTPOINT CANMOUNT MOUNTED
|
||||
zpool/data / off no
|
||||
zpool/data/home /home on yes
|
||||
zpool/data/opt /opt off no
|
||||
zpool/data/opt/git /opt/git on yes
|
||||
zpool/data/var /var on yes <---
|
||||
zpool/data/var/lib /var/lib off no
|
||||
```
|
||||
|
||||
### Advice
|
||||
|
||||
Instead create nested parents in multiple steps where you set each one to `-o canmount=off`:
|
||||
```
|
||||
zfs create -o canmount=off zpool/data/var
|
||||
zfs create -o canmount=off zpool/data/var/lib
|
||||
```
|
||||
Lastly create the dataset you want mounted:
|
||||
```
|
||||
zfs create zpool/data/var/lib/docker
|
||||
```
|
||||
|
||||
## Mounting zpool for maintenance
|
||||
|
||||
In case you want to mount your zpool on an external operating system such as an Arch Linux live CD ISO image do it like so:
|
||||
|
||||
```
|
||||
zpool import zpool -d /dev/disk/by-partuuid -R /mnt -f -N
|
||||
zfs load-key -L prompt zpool
|
||||
zfs mount zpool/root/archlinux
|
||||
zfs mount -a
|
||||
mount /dev/sda1 /mnt/efi
|
||||
arch-chroot /mnt /bin/bash
|
||||
```
|
||||
|
||||
When done exit `chroot` and cleanly remove your work:
|
||||
|
||||
```
|
||||
umount /mnt/efi
|
||||
zfs umount -a
|
||||
zpool export zpool
|
||||
```
|
||||
|
||||
Explanation:
|
||||
|
||||
- We always want to mount pools `by-partuuid` for consistency so we specifically only look for pools at `/dev/disk/by-partuuid`.
|
||||
- We mount our zpool with `-R /mnt` (aka `-o cachefile=none -o altroot=/mnt`). The pool is never cached, i.e. it's considered temporary. All pool and dataset mount paths have `/mnt` prepended. From `man zpoolprops`:
|
||||
> This can be used when examining an unknown pool where the mount points cannot be trusted, or in an alternate boot environment, where the typical paths are not valid. `altroot` is not a persistent property. It is valid only while the system is up.
|
||||
- With `-f` and `-N` we force-mount our pool (`-f`) even if it previously wasn't cleanly exported; and we do not auto-mount any of its datasets (`-N`), not even the ones that have `canmount=on` set.
|
||||
|
||||
```
|
||||
# zfs list -oname,mountpoint,canmount,mounted
|
||||
NAME MOUNTPOINT CANMOUNT MOUNTED
|
||||
zpool none off no
|
||||
zpool/data /mnt off no
|
||||
zpool/data/home /mnt/home on no <-- Not immediately mounted
|
||||
zpool/root none off no
|
||||
zpool/root/archlinux /mnt noauto no <-- Not immediately mounted
|
||||
```
|
||||
- We load the decryption key by temporarily overriding the `keylocation` property to `-L prompt`. The default value is `file:///etc/zfs/zpool.key` which in all likelihood doesn't exist in this environment.
|
||||
- We mount our desired boot environment with `zfs mount zpool/root/archlinux`
|
||||
|
||||
```
|
||||
# zfs list -oname,mountpoint,canmount,mounted
|
||||
NAME MOUNTPOINT CANMOUNT MOUNTED
|
||||
zpool none off no
|
||||
zpool/data /mnt off no
|
||||
zpool/data/home /mnt/home on no
|
||||
zpool/root none off no
|
||||
zpool/root/archlinux /mnt noauto yes <-- Only boot env now mounted
|
||||
```
|
||||
- We mount all child datasets with `zfs mount -a` making `/mnt/home` available as well as any others you may have created yourself.
|
||||
|
||||
```
|
||||
# zfs list -oname,mountpoint,canmount,mounted
|
||||
NAME MOUNTPOINT CANMOUNT MOUNTED
|
||||
zpool none off no
|
||||
zpool/data /mnt off no
|
||||
zpool/data/home /mnt/home on yes <-- Now mounted
|
||||
zpool/root none off no
|
||||
zpool/root/archlinux /mnt noauto yes <-- Now mounted
|
||||
```
|
||||
- We lastly mount our EFI system partition (ESP), in this example it's living at `/dev/sda1` so adjust this path accordingly.
|
||||
|
||||
```
|
||||
# df -hTP
|
||||
Filesystem Type Size Used Avail Use% Mounted on
|
||||
... ... ... ... ... ... ...
|
||||
zpool/root/archlinux zfs 8.6G 2.5G 6.2G 29% /mnt
|
||||
zpool/data/home zfs 6.3G 161M 6.2G 3% /mnt/home
|
||||
/dev/sda1 vfat 511M 31M 481M 6% /mnt/efi
|
||||
```
|
||||
- We're ready to `arch-chroot` into our boot environment.
|
||||
|
||||
# Development
|
||||
|
||||
## Conventional commits
|
||||
@ -257,10 +495,10 @@ Commit _types_ besides `fix` and `feat` are:
|
||||
|
||||
The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:
|
||||
|
||||
- `iso`: Changing Arch Linux ISO CD
|
||||
- `iso`: Changing Arch Linux live CD ISO image
|
||||
- `zbm`: Adjusting ZFSBootMenu's behavior
|
||||
- `zfs`: A change to how ZFS interacts with the system, either a pool or a dataset
|
||||
- `os`: Getting an perating system set up to correctly work in a ZFS boot environment
|
||||
- `os`: Getting an operating system set up to correctly work in a ZFS boot environment
|
||||
- `meta`: Affects the project's repo layout, readme content, file names etc.
|
||||
|
||||
# Credits
|
||||
|
527
setup.sh
527
setup.sh
@ -1,7 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Whatever comes in on file descriptor (FD) 3 gets redirected to where file
|
||||
# descriptor 1 is pointing. File descriptor 1 points to stdout so when we
|
||||
# output-redirect something into FD 3 it shows up on stdout. We can use this
|
||||
# to produce arbitrary logging output inside a subshell like so:
|
||||
#
|
||||
# function my_func () {
|
||||
# some_command "${1:?}"
|
||||
# >&3 echo 'A log message'
|
||||
# }
|
||||
#
|
||||
# var="$(my_func arg_1)"
|
||||
#
|
||||
# Here "${var}" will only capture the output of some_command "${1:?}". It
|
||||
# will not capture 'echo' which will instead show up on our stdout/FD 1.
|
||||
exec 3>&1
|
||||
|
||||
declare this_script_url
|
||||
this_script_url="${SCRIPT_URL:?}"
|
||||
postconf_hook="$(dirname "${this_script_url}")"'/zbm_set_new_uefi_boot_entries.sh'
|
||||
|
||||
declare zpool_name zfs_arch_dataset_name
|
||||
zpool_name='zpool'
|
||||
@ -13,10 +30,6 @@ trap '[ "$?" -ne 77 ] || exit 77' ERR
|
||||
|
||||
declare zpool_drive efi_drive
|
||||
|
||||
function set_ntp () {
|
||||
timedatectl set-ntp true
|
||||
}
|
||||
|
||||
function we_are_changerooted () {
|
||||
if [ "$(stat -c '%d:%i' '/')" != "$(stat -c '%d:%i' '/proc/1/root/.')" ]; then
|
||||
return 0
|
||||
@ -25,20 +38,38 @@ function we_are_changerooted () {
|
||||
fi
|
||||
}
|
||||
|
||||
function no_kernel_update_in_iso () {
|
||||
#1.1
|
||||
sed -ri -e 's'$'\x1''#(IgnorePkg)[^\r\n\f]+'$'\x1''\1 = linux linux-headers'$'\x1''g' /etc/pacman.conf
|
||||
}
|
||||
|
||||
function set_ntp () {
|
||||
#1.2
|
||||
timedatectl set-ntp true
|
||||
}
|
||||
|
||||
function resize_cow_space () {
|
||||
#1.3
|
||||
mount -o remount,size='50%' /run/archiso/cowspace
|
||||
}
|
||||
|
||||
function update_pacman_db () {
|
||||
#1.4
|
||||
printf -- '%s\n' 'Refreshing mirror list ...'
|
||||
systemctl start reflector
|
||||
# In an ISO and for the minimal number of packages we need we do not
|
||||
# care about partial upgrades
|
||||
pacman -Sy
|
||||
pacman -Syyuu --noconfirm
|
||||
}
|
||||
|
||||
function install_pkgs () {
|
||||
#1.5
|
||||
printf -- '%s\n' 'Installing packages ...'
|
||||
pacman -S --needed --noconfirm "${@}"
|
||||
}
|
||||
|
||||
function install_zfs () {
|
||||
#1.6
|
||||
declare reset_colors='\033[0m'
|
||||
curl -s 'https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init' | bash
|
||||
printf -- "${reset_colors}"
|
||||
@ -72,7 +103,7 @@ function get_parts () {
|
||||
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1
|
||||
;;
|
||||
*)
|
||||
>2 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...'
|
||||
>&3 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...'
|
||||
exit 77
|
||||
;;
|
||||
esac
|
||||
@ -88,7 +119,7 @@ function we_have_exactly_one_part () {
|
||||
if [[ "$(wc -c <<<"${parts_list}")" -gt '1' ]]; then
|
||||
case "${parts_count}" in
|
||||
0)
|
||||
>2 printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting ...'
|
||||
>&3 printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting ...'
|
||||
exit 77
|
||||
;;
|
||||
1)
|
||||
@ -98,7 +129,7 @@ function we_have_exactly_one_part () {
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
>2 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting ...'
|
||||
>&3 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting ...'
|
||||
exit 77
|
||||
fi
|
||||
}
|
||||
@ -111,7 +142,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 ...'
|
||||
>&3 printf -- '%s\n' 'No '"'${1:?}'"' partition entry in /dev/disk/by-partuuid, exiting ...'
|
||||
exit 77
|
||||
}
|
||||
|
||||
@ -127,10 +158,26 @@ function select_part () {
|
||||
parts="$(get_parts "${part_type}")"
|
||||
fi
|
||||
|
||||
if [[ ! "${parts}" ]]; then
|
||||
case "${part_type}" in
|
||||
efi)
|
||||
part_type_human_readable='EFI system partition (ESP) with partition type code EF00'
|
||||
;;
|
||||
zfs)
|
||||
part_type_human_readable='ZFS zpool partition with partition type code BF00'
|
||||
;;
|
||||
esac
|
||||
>&3 printf -- '%s\n' \
|
||||
'It looks as if there is no '"${part_type_human_readable}" \
|
||||
'on any of the disks. Did you correctly partition a disk before starting?' \
|
||||
'Check https://quico.space/quico-os-setup/arch-zbm#prep. Exiting ...'
|
||||
exit 77
|
||||
fi
|
||||
|
||||
if we_have_exactly_one_part "${part_type}" "${parts}"; then
|
||||
part="${parts}"
|
||||
else
|
||||
2> printf -- '%s\n' 'More than one '"${part_type^^}"' partition to pick for installation. Cowardly exiting ...'
|
||||
>&3 printf -- '%s\n' 'More than one '"${part_type^^}"' partition to pick for installation. Cowardly exiting ...'
|
||||
exit 77
|
||||
fi
|
||||
printf -- '%s' "${part}"
|
||||
@ -151,22 +198,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' \
|
||||
@ -176,7 +234,7 @@ function create_pool () {
|
||||
}
|
||||
|
||||
function create_root_dataset () {
|
||||
zfs create -o mountpoint=none "${zpool_name}"'/root'
|
||||
zfs create -o 'mountpoint=none' -o 'canmount=off' "${zpool_name}"'/root'
|
||||
# zfs set org.zfsbootmenu:commandline="ro quiet" "${zpool_name}"'/root'
|
||||
zfs set org.zfsbootmenu:commandline="ro" "${zpool_name}"'/root'
|
||||
}
|
||||
@ -198,11 +256,12 @@ function export_pool () {
|
||||
}
|
||||
|
||||
function setup_zpool () {
|
||||
#1.7
|
||||
local drive_by_id
|
||||
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
|
||||
@ -211,12 +270,13 @@ function setup_zpool () {
|
||||
export_pool
|
||||
import_pool
|
||||
else
|
||||
printf -- '%s\n' 'A zpool already exists, that is unexpected. Cowardly exiting 1 ...'
|
||||
>&3 printf -- '%s\n' 'A zpool already exists, that is unexpected. Cowardly exiting 1 ...'
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function mount_system () {
|
||||
#1.8
|
||||
zfs mount "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
|
||||
zfs mount -a
|
||||
|
||||
@ -229,16 +289,29 @@ function mount_system () {
|
||||
}
|
||||
|
||||
function copy_zpool_cache () {
|
||||
#1.9
|
||||
mkdir -p '/mnt/etc/zfs'
|
||||
zpool set 'cachefile=/etc/zfs/'"${zpool_name}"'.cache' "${zpool_name}"
|
||||
}
|
||||
|
||||
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.
|
||||
#
|
||||
# We're setting this in Arch Linux ISO CD while we install proper Arch.
|
||||
# No need to revert this later as it is ephemeral anyway.
|
||||
sed -ri -e 's'$'\x1''^.*?(CheckSpace)([^\r\n\f]*)'$'\x1''#\1\2'$'\x1''g' '/etc/pacman.conf'
|
||||
}
|
||||
|
||||
function install_archlinux () {
|
||||
#1.10
|
||||
pacman_dl_parallel
|
||||
pacman_dont_check_space
|
||||
pacstrap /mnt \
|
||||
base \
|
||||
base-devel \
|
||||
@ -263,6 +336,7 @@ function install_archlinux () {
|
||||
}
|
||||
|
||||
function gen_fstab () {
|
||||
#1.11
|
||||
genfstab -U /mnt | grep -v "${zpool_name}" | tr -s '\n' | sed -r -e 's/\/mnt//' -e '/./,$!d' > '/mnt/etc/fstab'
|
||||
}
|
||||
|
||||
@ -275,6 +349,7 @@ EOF
|
||||
}
|
||||
|
||||
function set_hostname () {
|
||||
#1.12
|
||||
declare new_hostname
|
||||
install_pkgs 'pwgen'
|
||||
new_hostname="$(pwgen --no-numerals --no-capitalize --ambiguous 8)"
|
||||
@ -283,6 +358,7 @@ function set_hostname () {
|
||||
}
|
||||
|
||||
function set_locale () {
|
||||
#1.13
|
||||
printf -- '%s\n' \
|
||||
'KEYMAP=de-latin1' \
|
||||
'FONT=Lat2-Terminus16' \
|
||||
@ -293,17 +369,22 @@ 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.
|
||||
#1.14
|
||||
# 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 () {
|
||||
#1.15
|
||||
# No need to build fallback initramfs, our new fallback is ZFS snapshots
|
||||
sed -ri \
|
||||
-e '/^#/d' \
|
||||
@ -317,65 +398,12 @@ 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
|
||||
#1.16
|
||||
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
|
||||
}
|
||||
|
||||
function enter_chroot () {
|
||||
arch-chroot /mnt /bin/bash -xe <<EOF
|
||||
curl --silent '${this_script_url}' | bash
|
||||
EOF
|
||||
}
|
||||
|
||||
function get_pkg_info () {
|
||||
declare from_where local_pkg_info local_pkg_version version_search
|
||||
from_where="${1}"
|
||||
version_search='/Version/{print $3}'
|
||||
pkg_info="$(paru -$([[ "${from_where}" == 'local' ]] && printf -- '%s' 'Q' || printf -- '%s' 'S')i "${2}" 2>&1)"
|
||||
if [[ "${from_where}" == 'local' ]] && grep -Piq -- '^error: package .*? was not found' <<<"${pkg_info}"; then
|
||||
return 1
|
||||
else
|
||||
local_pkg_version="$(awk "${version_search}" <<<"${pkg_info}")"
|
||||
fi
|
||||
printf -- '%s' "${local_pkg_version}"
|
||||
return 0
|
||||
}
|
||||
|
||||
function paru_with_zfs_first () {
|
||||
if [[ "${#}" -eq '0' ]]; then
|
||||
declare -A local_pkg_info
|
||||
/usr/bin/paru -Sy
|
||||
if local_pkg_info['zfs-dkms']="$(get_pkg_info 'local' 'zfs-dkms')" && local_pkg_info['zfs-utils']="$(get_pkg_info 'local' 'zfs-utils')"; then
|
||||
local_pkg_info['zfs-dkms']="$(get_pkg_info 'local' 'zfs-dkms')"
|
||||
local_pkg_info['zfs-utils']="$(get_pkg_info 'local' 'zfs-utils')"
|
||||
|
||||
declare -A remote_pkg_info
|
||||
remote_pkg_info['zfs-dkms']="$(get_pkg_info 'remote' 'zfs-dkms')"
|
||||
remote_pkg_info['zfs-utils']="$(get_pkg_info 'remote' 'zfs-utils')"
|
||||
|
||||
/usr/bin/paru -S --needed archlinux-keyring
|
||||
|
||||
if [[ "${local_pkg_info['zfs-dkms']}" == "${remote_pkg_info['zfs-dkms']}" ]] && \
|
||||
[[ "${local_pkg_info['zfs-utils']}" == "${remote_pkg_info['zfs-utils']}" ]]; then
|
||||
/usr/bin/paru -Su
|
||||
else
|
||||
/usr/bin/paru -Sy 'zfs-dkms' 'zfs-utils' \
|
||||
--assume-installed zfs-dkms="${local_pkg_info['zfs-dkms']}" \
|
||||
--assume-installed zfs-dkms="${remote_pkg_info['zfs-dkms']}" \
|
||||
--assume-installed zfs-utils="${local_pkg_info['zfs-utils']}" \
|
||||
--assume-installed zfs-utils="${remote_pkg_info['zfs-utils']}"
|
||||
/usr/bin/paru -Su
|
||||
fi
|
||||
else
|
||||
/usr/bin/paru -S --needed archlinux-keyring
|
||||
/usr/bin/paru -Su
|
||||
fi
|
||||
else
|
||||
/usr/bin/paru "${@}"
|
||||
fi
|
||||
}
|
||||
|
||||
function create_unpriv_user () {
|
||||
account_name="${1:?}"
|
||||
full_name="${2:-${account_name}}"
|
||||
@ -387,37 +415,15 @@ function create_unpriv_user () {
|
||||
chown -R "${account_name}"':' '/home/'"${account_name}"; chmod -R 'u=rwX,go=' "$(dirname "${authorized_keys_abs_path}")"
|
||||
}
|
||||
|
||||
function unleash_makepkg () {
|
||||
local path_prefix
|
||||
path_prefix='/mnt'
|
||||
if we_are_changerooted; then
|
||||
path_prefix=''
|
||||
fi
|
||||
sed -ri \
|
||||
-e 's'$'\x1''^(#?(MAKEFLAGS=))[^\r\n\f]*'$'\x1''\2"-j$(nproc --ignore 1)"'$'\x1''g' \
|
||||
"${path_prefix}"'/etc/makepkg.conf'
|
||||
}
|
||||
|
||||
function get_aur_helper () {
|
||||
create_unpriv_user 'build'
|
||||
usermod --append --groups 'wheel' 'build'
|
||||
printf -- '%s\n' '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' > '/etc/sudoers.d/10-wheel-group-no-passwd-prompt'
|
||||
pushd /tmp
|
||||
git clone 'https://aur.archlinux.org/paru.git'
|
||||
chown -R 'build:' 'paru'
|
||||
pushd 'paru'
|
||||
sudo --user 'build' makepkg -si --noconfirm
|
||||
popd
|
||||
rm -rf 'paru'
|
||||
popd
|
||||
alias paru='paru_with_zfs_first'
|
||||
}
|
||||
|
||||
function paru_install () {
|
||||
sudo --user build paru -S --noconfirm "${@}"
|
||||
function enter_chroot () {
|
||||
#2.1
|
||||
arch-chroot /mnt /bin/bash -xe <<EOF
|
||||
curl --silent '${this_script_url}' | bash
|
||||
EOF
|
||||
}
|
||||
|
||||
function keep_initiramfs_root_only_rw () {
|
||||
#2.3
|
||||
declare systemd_local_admin_override_path unit_name
|
||||
systemd_local_admin_override_path='/etc/systemd/system'
|
||||
unit_name='chmod-initramfs'
|
||||
@ -448,7 +454,27 @@ EOF
|
||||
systemctl enable "${path_unit}"
|
||||
}
|
||||
|
||||
function pacman_dl_parallel () {
|
||||
#2.4
|
||||
# We're setting this in Arch Linux ISO CD while we install proper Arch.
|
||||
# No need to revert this later as it is ephemeral anyway.
|
||||
sed -ri -e 's'$'\x1''^.*?(ParallelDownloads)[^\r\n\f]*'$'\x1''\1 = 20'$'\x1''g' '/etc/pacman.conf'
|
||||
}
|
||||
|
||||
function unleash_makepkg () {
|
||||
#2.5
|
||||
local path_prefix
|
||||
path_prefix='/mnt'
|
||||
if we_are_changerooted; then
|
||||
path_prefix=''
|
||||
fi
|
||||
sed -ri \
|
||||
-e 's'$'\x1''^(#?(MAKEFLAGS=))[^\r\n\f]*'$'\x1''\2"-j$(nproc --ignore 1)"'$'\x1''g' \
|
||||
"${path_prefix}"'/etc/makepkg.conf'
|
||||
}
|
||||
|
||||
function add_motd_getting_started_msg () {
|
||||
#2.6
|
||||
cat > '/etc/motd' <<"EOF"
|
||||
|
||||
####################
|
||||
@ -457,13 +483,121 @@ GUI basics:
|
||||
|
||||
paru -S xorg plasma-meta kde-applications-meta sddm
|
||||
localectl set-x11-keymap de
|
||||
useradd --create-home --shell /bin/bash --user-group --groups wheel <user>
|
||||
passwd <user>
|
||||
systemctl enable --now sddm.service
|
||||
|
||||
####################
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
function get_aur_helper () {
|
||||
#2.7
|
||||
create_unpriv_user 'build'
|
||||
usermod --append --groups 'wheel' 'build'
|
||||
printf -- '%s\n' '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' > '/etc/sudoers.d/10-wheel-group-no-passwd-prompt'
|
||||
pushd /tmp
|
||||
git clone 'https://aur.archlinux.org/paru.git'
|
||||
chown -R 'build:' 'paru'
|
||||
pushd 'paru'
|
||||
sudo --user 'build' makepkg -si --noconfirm
|
||||
popd
|
||||
rm -rf 'paru'
|
||||
popd
|
||||
}
|
||||
|
||||
function paru_install () {
|
||||
declare -a paru_install_packages
|
||||
[[ "${1}" ]] && while :; do
|
||||
case "${1}" in
|
||||
-[[:alnum:]]*)
|
||||
>&3 printf -- '%s\n' \
|
||||
'Short-form argument '"'${1}'"' not supported for function '"'${FUNCNAME[0]}()'"'. Only known accepted argument' \
|
||||
'is '"'"'--replace-conflicting'"'"' without a value given. Exiting ...'
|
||||
exit 77
|
||||
;;
|
||||
--replace-conflicting)
|
||||
pacman_force_yes='true'
|
||||
shift
|
||||
continue
|
||||
;;
|
||||
--*)
|
||||
>&3 printf -- '%s\n' \
|
||||
'Long-form argument '"'${1}'"' not supported for function '"'${FUNCNAME[0]}()'"'. Only known accepted argument' \
|
||||
'is '"'"'--replace-conflicting'"'"' without a value given. Exiting ...'
|
||||
exit 77
|
||||
;;
|
||||
'')
|
||||
# All arguments processed
|
||||
break
|
||||
;;
|
||||
*)
|
||||
paru_install_packages+=("${1}")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done || {
|
||||
>&3 printf -- '%s\n' \
|
||||
'No argument '"'${1}'"' given for function '"'${FUNCNAME[0]}'"'. Exiting ...'
|
||||
exit 77
|
||||
}
|
||||
if [[ "${pacman_force_yes}" ]]; then
|
||||
yes 'y' | sudo --user 'build' paru -S "${paru_install_packages[@]}"
|
||||
unset -v pacman_force_yes
|
||||
else
|
||||
sudo --user 'build' paru -S --noconfirm "${paru_install_packages[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
function configure_zfsbootmenu () {
|
||||
#2.9
|
||||
paru_install 'zfsbootmenu'
|
||||
mkdir -p '/etc/zfsbootmenu/posthooks.d'
|
||||
cat > '/etc/zfsbootmenu/config.yaml' <<EOF
|
||||
Global:
|
||||
ManageImages: true
|
||||
BootMountPoint: /efi
|
||||
InitCPIO: true
|
||||
PostHooksDir: /etc/zfsbootmenu/posthooks.d
|
||||
Components:
|
||||
Enabled: false
|
||||
EFI:
|
||||
ImageDir: /efi/EFI/ZBM
|
||||
Versions: false
|
||||
Enabled: true
|
||||
Stub: /etc/zfsbootmenu/stub-loader.d/linuxx64.efi.stub
|
||||
Kernel:
|
||||
CommandLine: ro loglevel=0 zbm.import_policy=hostid
|
||||
Prefix: vmlinuz
|
||||
EOF
|
||||
# Up here maybe 'ro quiet' instead of 'ro'
|
||||
zfs set org.zfsbootmenu:commandline='rw nowatchdog rd.vconsole.keymap=de-latin1' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
|
||||
}
|
||||
|
||||
function get_known_good_stub_loader () {
|
||||
local known_good_stub_loader local_stub_loader_abs
|
||||
known_good_stub_loader='https://github.com/zbm-dev/zfsbootmenu/raw/master/testing/stubs/linuxx64.efi.stub'
|
||||
local local_stub_loader_abs='/etc/zfsbootmenu/stub-loader.d/linuxx64.efi.stub'
|
||||
mkdir -p "$(dirname "${local_stub_loader_abs}")"
|
||||
curl --silent --location "${known_good_stub_loader}" --output "${local_stub_loader_abs}"
|
||||
}
|
||||
|
||||
function get_disks_with_one_efipart () {
|
||||
local disks_with_one_efipart
|
||||
# Find disks that have exactly one EFI partition and where that EFI
|
||||
# partition is partition number 1. We expect exactly one disk to meet
|
||||
# these criteria. Anything else and we bail.
|
||||
disks_with_one_efipart="$(lsblk --output PATH,PARTTYPE --json --tree | jq --raw-output '.[][] | select(.children | length > 0) | select( any (.children[]; (.path | test("^[^[:digit:]]+1")) and (.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) and ([select(.children[].parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b")] | length == 1) ) | .path')"
|
||||
if [[ "$(wc -l <<<"${disks_with_one_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_one_efipart}")" -gt '1' ]]; then
|
||||
printf -- '%s' "${disks_with_one_efipart}"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function install_os_in_chroot () {
|
||||
#2.2
|
||||
### Reinit keyring
|
||||
# As keyring is initialized at boot, and copied to the install dir with pacstrap, and ntp is running
|
||||
# Time changed after keyring initialization, it leads to malfunction
|
||||
@ -473,33 +607,32 @@ function install_os_in_chroot () {
|
||||
pacman-key --populate archlinux
|
||||
pacman -S archlinux-keyring --noconfirm
|
||||
|
||||
keep_initiramfs_root_only_rw
|
||||
pacman_dl_parallel
|
||||
unleash_makepkg
|
||||
add_motd_getting_started_msg
|
||||
get_aur_helper
|
||||
paru_install 'zfs-dkms' 'zfs-utils'
|
||||
hwclock --systohc
|
||||
locale-gen
|
||||
source /etc/locale.conf
|
||||
|
||||
keep_initiramfs_root_only_rw #2.3
|
||||
pacman_dl_parallel #2.4
|
||||
unleash_makepkg #2.5
|
||||
add_motd_getting_started_msg #2.6
|
||||
get_aur_helper #2.7
|
||||
paru_install --replace-conflicting 'paru-bin'
|
||||
paru_install 'zfs-dkms' 'zfs-utils' 'jq'
|
||||
hwclock --systohc
|
||||
mkinitcpio -P
|
||||
|
||||
# Install ZFSBootMenu and deps
|
||||
git clone --depth=1 https://github.com/zbm-dev/zfsbootmenu/ '/tmp/zfsbootmenu'
|
||||
paru_install 'cpanminus' 'kexec-tools' 'fzf' 'util-linux'
|
||||
pushd '/tmp/zfsbootmenu'
|
||||
make
|
||||
make install
|
||||
cpanm --notest --installdeps .
|
||||
popd
|
||||
rm -rf '/tmp/zfsbootmenu'
|
||||
# Install ZFSBootMenu image
|
||||
configure_zfsbootmenu #2.9
|
||||
get_known_good_stub_loader #2.10
|
||||
generate-zbm
|
||||
}
|
||||
|
||||
function set_root_pw () {
|
||||
printf -- '%s\n' 'root:password' | chpasswd --root '/mnt'
|
||||
#3.2
|
||||
printf -- '%s\n' 'root:password' | chpasswd --crypt-method 'SHA512' --root '/mnt'
|
||||
}
|
||||
|
||||
function configure_networking () {
|
||||
#3.3
|
||||
cat > '/mnt/etc/systemd/network/50-wired.network' <<"EOF"
|
||||
[Match]
|
||||
Name=en*
|
||||
@ -517,6 +650,7 @@ EOF
|
||||
}
|
||||
|
||||
function configure_dns () {
|
||||
#3.4
|
||||
rm '/mnt/etc/resolv.conf'
|
||||
ln -s '/run/systemd/resolve/stub-resolv.conf' '/mnt/etc/resolv.conf'
|
||||
|
||||
@ -527,119 +661,116 @@ function configure_dns () {
|
||||
}
|
||||
|
||||
function configure_reflector () {
|
||||
systemctl enable 'reflector' --root='/mnt'
|
||||
#3.5
|
||||
systemctl enable 'reflector.service' 'reflector.timer' --root='/mnt'
|
||||
}
|
||||
|
||||
function configure_zfs () {
|
||||
#3.6
|
||||
systemctl enable 'zfs-import-cache' 'zfs-mount' 'zfs-import.target' 'zfs.target' --root='/mnt'
|
||||
}
|
||||
|
||||
function configure_zfs_mount_gen () {
|
||||
#3.7
|
||||
mkdir -p '/mnt/etc/zfs/zfs-list.cache'
|
||||
touch '/mnt/etc/zfs/zfs-list.cache/'"${zpool_name}"
|
||||
zfs list -H -o name,mountpoint,canmount,atime,relatime,devices,exec,readonly,setuid,nbmand | sed 's/\/mnt//' > '/mnt/etc/zfs/zfs-list.cache/'"${zpool_name}"
|
||||
systemctl enable 'zfs-zed.service' --root='/mnt'
|
||||
}
|
||||
|
||||
function configure_zfsbootmenu () {
|
||||
curl -s 'https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/master/etc/zfsbootmenu/mkinitcpio.conf' | sed -r -e '/^#/d' -e '/^$/d' > '/mnt/etc/zfsbootmenu/mkinitcpio.conf'
|
||||
cat > '/mnt/etc/zfsbootmenu/config.yaml' <<EOF
|
||||
Global:
|
||||
ManageImages: true
|
||||
BootMountPoint: /efi
|
||||
InitCPIO: true
|
||||
function set_new_uefi_boot_entries () {
|
||||
#3.8
|
||||
declare -a uefi_images
|
||||
mapfile -t uefi_images < \
|
||||
<(find '/mnt/efi/EFI/ZBM' -type f -iname '*.efi' -print0 | \
|
||||
xargs -0 --no-run-if-empty --max-args '1' stat -c '%Y %n' | \
|
||||
sort -V | \
|
||||
awk '{print $2}')
|
||||
|
||||
Components:
|
||||
Enabled: false
|
||||
EFI:
|
||||
ImageDir: /efi/EFI/ZBM
|
||||
Versions: false
|
||||
Enabled: true
|
||||
Kernel:
|
||||
CommandLine: ro loglevel=0 zbm.import_policy=hostid
|
||||
Prefix: vmlinuz
|
||||
EOF
|
||||
# Up here maybe 'ro quiet' instead of 'ro'
|
||||
zfs set org.zfsbootmenu:commandline='rw nowatchdog rd.vconsole.keymap=de-latin1' "${zpool_name}"'/root/'"${zfs_arch_dataset_name}"
|
||||
}
|
||||
|
||||
function gen_zfsbootmenu () {
|
||||
arch-chroot /mnt /bin/bash -xe <<"EOF"
|
||||
source /etc/locale.conf
|
||||
mkdir -p '/efi/EFI/ZBM'
|
||||
find /efi/EFI/ZBM -type f -delete
|
||||
generate-zbm
|
||||
EOF
|
||||
}
|
||||
|
||||
function get_disks_with_one_efipart () {
|
||||
local disks_with_one_efipart
|
||||
# Find disks that have exactly one EFI partition and where that EFI
|
||||
# partition is partition number 1. We expect exactly one disk to meet
|
||||
# these criteria. Anything else and we bail.
|
||||
disks_with_one_efipart="$(lsblk --output PATH,PARTTYPE --json --tree | jq --raw-output '.[][] | select(.children | length > 0) | select( any (.children[]; (.path | test("^[^[:digit:]]+1")) and (.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) and ([select(.children[].parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b")] | length == 1) ) | .path')"
|
||||
if [[ "$(wc -l <<<"${disks_with_one_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_one_efipart}")" -gt '1' ]]; then
|
||||
printf -- '%s' "${disks_with_one_efipart}"
|
||||
return 0
|
||||
if efibootmgr | grep -Piq -- 'ZFSBootMenu'; then
|
||||
local -a old_uefi_entries
|
||||
mapfile -t old_uefi_entries < \
|
||||
<(efibootmgr | \
|
||||
grep -Pio -- '(?<=^Boot)[^\*[:space:]]+(?=\*? ZFSBootMenu)')
|
||||
for old_uefi_entry in "${old_uefi_entries[@]}"; do
|
||||
efibootmgr --bootnum "${old_uefi_entry}" --delete-bootnum &>/dev/null && {
|
||||
>&3 printf -- '%s\n' \
|
||||
'EFI boot entry '"${old_uefi_entry}"' deleted.'
|
||||
}
|
||||
done
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
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
|
||||
for uefi_image in "${uefi_images[@]}"; do
|
||||
uefi_image_version="$(basename "${uefi_image%%.EFI}")"
|
||||
uefi_image_inverted="${uefi_image#/mnt/efi}"
|
||||
uefi_image_inverted="${uefi_image_inverted//\//\\}"
|
||||
efibootmgr --disk "${efi_drive}" \
|
||||
--part 1 \
|
||||
--create \
|
||||
--label "ZFSBootMenu" \
|
||||
--loader "\EFI\ZBM\vmlinuz.efi" \
|
||||
--verbose
|
||||
--label 'ZFSBootMenu '"${uefi_image_version}" \
|
||||
--loader "${uefi_image_inverted}" &>/dev/null && {
|
||||
>&3 printf -- '%s\n' \
|
||||
'EFI boot entry ZFSBootMenu '"${uefi_image_version}"' added.'
|
||||
}
|
||||
done
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function insert_zbm_postconf_hook () {
|
||||
#3.9
|
||||
declare postconf_target_abs='/mnt/etc/zfsbootmenu/posthooks.d/'"$(basename "${postconf_hook}")"
|
||||
curl --silent --location "${postconf_hook}" --output "${postconf_target_abs}"
|
||||
chmod +x "${postconf_target_abs}"
|
||||
}
|
||||
|
||||
function umount_all () {
|
||||
#3.10
|
||||
umount '/mnt/efi'
|
||||
zfs umount -a
|
||||
zpool export "${zpool_name}"
|
||||
}
|
||||
|
||||
function finalize_os_setup () {
|
||||
set_root_pw
|
||||
configure_networking
|
||||
configure_dns
|
||||
configure_reflector
|
||||
configure_zfs
|
||||
configure_zfs_mount_gen
|
||||
configure_zfsbootmenu
|
||||
gen_zfsbootmenu
|
||||
add_zbm_to_efi
|
||||
umount_all
|
||||
#3.1
|
||||
set_root_pw #3.2
|
||||
configure_networking #3.3
|
||||
configure_dns #3.4
|
||||
configure_reflector #3.5
|
||||
configure_zfs #3.6
|
||||
configure_zfs_mount_gen #3.7
|
||||
set_new_uefi_boot_entries #3.8
|
||||
insert_zbm_postconf_hook #3.9
|
||||
umount_all #3.10
|
||||
}
|
||||
|
||||
function main () {
|
||||
if we_are_changerooted; then
|
||||
install_os_in_chroot
|
||||
install_os_in_chroot #2.2
|
||||
else
|
||||
set_ntp
|
||||
update_pacman_db
|
||||
install_pkgs 'jq'
|
||||
install_zfs
|
||||
setup_zpool
|
||||
mount_system
|
||||
copy_zpool_cache
|
||||
install_archlinux
|
||||
gen_fstab
|
||||
set_hostname
|
||||
set_locale
|
||||
add_zfs_hook_to_initramfs
|
||||
set_initramfs_build_list
|
||||
add_zfs_files_to_new_os
|
||||
enter_chroot
|
||||
no_kernel_update_in_iso #1.1
|
||||
set_ntp #1.2
|
||||
resize_cow_space #1.3
|
||||
update_pacman_db #1.4
|
||||
install_pkgs 'base-devel' 'git' 'jq' #1.5
|
||||
install_zfs #1.6
|
||||
setup_zpool #1.7
|
||||
mount_system #1.8
|
||||
copy_zpool_cache #1.9
|
||||
install_archlinux #1.10
|
||||
gen_fstab #1.11
|
||||
set_hostname #1.12
|
||||
set_locale #1.13
|
||||
add_zfs_hook_to_initramfs #1.14
|
||||
set_initramfs_build_list #1.15
|
||||
add_zfs_files_to_new_os #1.16
|
||||
enter_chroot #2.1
|
||||
# We're done in chroot
|
||||
finalize_os_setup
|
||||
finalize_os_setup #3.1
|
||||
fi
|
||||
}
|
||||
|
||||
|
177
zbm_set_new_uefi_boot_entries.sh
Normal file
177
zbm_set_new_uefi_boot_entries.sh
Normal file
@ -0,0 +1,177 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Whatever comes in on file descriptor (FD) 3 gets redirected to where file
|
||||
# descriptor 1 is pointing. File descriptor 1 points to stdout so when we
|
||||
# output-redirect something into FD 3 it shows up on stdout. We can use this
|
||||
# to produce arbitrary logging output inside a subshell like so:
|
||||
#
|
||||
# function my_func () {
|
||||
# some_command "${1:?}"
|
||||
# >&3 echo 'A log message'
|
||||
# }
|
||||
#
|
||||
# var="$(my_func arg_1)"
|
||||
#
|
||||
# Here "${var}" will only capture the output of some_command "${1:?}". It
|
||||
# will not capture 'echo' which will instead show up on our stdout/FD 1.
|
||||
exec 3>&1
|
||||
|
||||
# https://unix.stackexchange.com/a/48550
|
||||
set -E
|
||||
trap '[ "$?" -ne 77 ] || exit 77' ERR
|
||||
|
||||
function get_partitions () {
|
||||
declare partitions_json
|
||||
partitions_json="$(lsblk --output PATH,PARTTYPE --json --tree)" || return 1
|
||||
printf -- '%s' "${partitions_json}"
|
||||
return 0
|
||||
}
|
||||
|
||||
function get_parts () {
|
||||
local zfs_install_drive
|
||||
declare parttype parts
|
||||
parttype="${1:?}"
|
||||
zfs_install_drive="${2:-}"
|
||||
case "${parttype}" in
|
||||
zfs)
|
||||
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | .children[] | select(.parttype=="6a85cf4d-1dd2-11b2-99a6-080020736631") | .path')" || exit 1
|
||||
;;
|
||||
efi)
|
||||
parts="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.path=="'"${zfs_install_drive:?}"'") | .children[] | select(.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b") | .path')" || exit 1
|
||||
;;
|
||||
*)
|
||||
>&3 printf -- '%s\n' 'Unknown partition type '"'"'"${parttype}"'"'"', exiting ...'
|
||||
exit 77
|
||||
;;
|
||||
esac
|
||||
printf -- '%s' "${parts}"
|
||||
return 0
|
||||
}
|
||||
|
||||
function we_have_exactly_one_part () {
|
||||
local parttype parts_list parts_count
|
||||
parttype="${1:?}"
|
||||
parts_list="${2:?}"
|
||||
parts_count="$(wc -l <<<"${parts_list}")"
|
||||
if [[ "$(wc -c <<<"${parts_list}")" -gt '1' ]]; then
|
||||
case "${parts_count}" in
|
||||
0)
|
||||
>&3 printf -- '%s\n' 'No '"${parttype^^}"' partition found. Exiting ...'
|
||||
exit 77
|
||||
;;
|
||||
1)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
>&3 printf -- '%s\n' 'Partition list does not look valid. Cowardly exiting ...'
|
||||
exit 77
|
||||
fi
|
||||
}
|
||||
|
||||
function select_part () {
|
||||
local parts enriched_parts enriched_parts_count part_number part_type zfs_install_drive
|
||||
declare part
|
||||
part_type="${1:?}" # 'efi' or 'zfs'
|
||||
zfs_install_drive="${2:-}"
|
||||
if [[ "${zfs_install_drive}" ]]; then
|
||||
# This is intended to find correct EFI partition
|
||||
parts="$(get_parts "${part_type}" "${zfs_install_drive}")"
|
||||
else
|
||||
parts="$(get_parts "${part_type}")"
|
||||
fi
|
||||
|
||||
if [[ ! "${parts}" ]]; then
|
||||
case "${part_type}" in
|
||||
efi)
|
||||
part_type_human_readable='EFI system partition (ESP) with partition type code EF00'
|
||||
;;
|
||||
zfs)
|
||||
part_type_human_readable='ZFS zpool partition with partition type code BF00'
|
||||
;;
|
||||
esac
|
||||
>&3 printf -- '%s\n' \
|
||||
'It looks as if there is no '"${part_type_human_readable}" \
|
||||
'on any of the disks. Is this a chroot? Exiting ...'
|
||||
exit 77
|
||||
fi
|
||||
|
||||
if we_have_exactly_one_part "${part_type}" "${parts}"; then
|
||||
part="${parts}"
|
||||
else
|
||||
>&3 printf -- '%s\n' 'More than one '"${part_type^^}"' partition to pick for installation. Cowardly exiting ...'
|
||||
exit 77
|
||||
fi
|
||||
printf -- '%s' "${part}"
|
||||
return 0
|
||||
}
|
||||
|
||||
function get_part_parent () {
|
||||
local child_partition parent_partition
|
||||
child_partition="${1:?}"
|
||||
parent_partition="$(get_partitions | jq --raw-output '.[][] | select(.children | length > 0) | select(.children[].path=="'"${child_partition:?}"'") | .path')"
|
||||
printf -- '%s' "${parent_partition}"
|
||||
return 0
|
||||
}
|
||||
|
||||
function get_disks_with_one_efipart () {
|
||||
local disks_with_one_efipart
|
||||
# Find disks that have exactly one EFI partition and where that EFI
|
||||
# partition is partition number 1. We expect exactly one disk to meet
|
||||
# these criteria. Anything else and we bail.
|
||||
disks_with_one_efipart="$(lsblk --output PATH,PARTTYPE --json --tree | jq --raw-output '.[][] | select(.children | length > 0) | select( any (.children[]; (.path | test("^[^[:digit:]]+1")) and (.parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) and ([select(.children[].parttype=="c12a7328-f81f-11d2-ba4b-00a0c93ec93b")] | length == 1) ) | .path')"
|
||||
if [[ "$(wc -l <<<"${disks_with_one_efipart}")" -eq '1' ]] && [[ "$(wc -c <<<"${disks_with_one_efipart}")" -gt '1' ]]; then
|
||||
printf -- '%s' "${disks_with_one_efipart}"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function set_new_uefi_boot_entries () {
|
||||
declare -a uefi_images
|
||||
mapfile -t uefi_images < \
|
||||
<(find '/efi/EFI/ZBM' -type f -iname '*.efi' -print0 | \
|
||||
xargs -0 --no-run-if-empty --max-args '1' stat -c '%Y %n' | \
|
||||
sort -V | \
|
||||
awk '{print $2}')
|
||||
zpool_drive="$(select_part 'zfs')"
|
||||
zfs_parent="$(get_part_parent "${zpool_drive:?}")"
|
||||
efi_drive="${zfs_parent}"
|
||||
|
||||
if efibootmgr | grep -Piq -- 'ZFSBootMenu'; then
|
||||
local -a old_uefi_entries
|
||||
mapfile -t old_uefi_entries < \
|
||||
<(efibootmgr | \
|
||||
grep -Pio -- '(?<=^Boot)[^\*[:space:]]+(?=\*? ZFSBootMenu)')
|
||||
for old_uefi_entry in "${old_uefi_entries[@]}"; do
|
||||
efibootmgr --bootnum "${old_uefi_entry}" --delete-bootnum &>/dev/null && {
|
||||
>&3 printf -- '%s\n' \
|
||||
'EFI boot entry '"${old_uefi_entry}"' deleted.'
|
||||
}
|
||||
done
|
||||
fi
|
||||
|
||||
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
|
||||
for uefi_image in "${uefi_images[@]}"; do
|
||||
uefi_image_version="$(basename "${uefi_image%%.EFI}")"
|
||||
uefi_image_inverted="${uefi_image#/efi}"
|
||||
uefi_image_inverted="${uefi_image_inverted//\//\\}"
|
||||
efibootmgr --disk "${efi_drive}" \
|
||||
--part 1 \
|
||||
--create \
|
||||
--label 'ZFSBootMenu '"${uefi_image_version}" \
|
||||
--loader "${uefi_image_inverted}" &>/dev/null && {
|
||||
>&3 printf -- '%s\n' \
|
||||
'EFI boot entry ZFSBootMenu '"${uefi_image_version}"' added.'
|
||||
}
|
||||
done
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
set_new_uefi_boot_entries
|
Loading…
x
Reference in New Issue
Block a user