Added ZFS install for alpine-server.

This commit is contained in:
Luc Bijl 2024-08-10 21:54:34 +02:00
parent e6bae601d3
commit 822f0bd55e
14 changed files with 205 additions and 120 deletions

View file

@ -24,7 +24,7 @@ For `nix` to be able to install packages it is necessary to add a few channels/r
```
https://nixos.org/channels/nixos-<version> nixpkgs
https://nixos.org/channels/nixos-unstable nixpkgs-unstable
https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable
https://github.com/nix-community/nixGL/archive/main.tar.gz nixgl
```

View file

@ -2,7 +2,7 @@
It might be nice to add a user to your system.
## doas
## Wheel
Before creating the user install `doas`, to use when root is required:

View file

@ -1,3 +1,5 @@
# An Alpine Linux server installation
This guide will demonstrate how to install [Alpine Linux](https://www.alpinelinux.org/) for server application. With tpm encryption, secureboot, a copy on write filesystem and Podman to handle containers. Alpine Linux makes a good base for a server because of its simplicity, lightweightness and security. Check out the [Alpine Linux wiki](https://wiki.alpinelinux.org/wiki/Main_Page) for additional resources and information.
This guide will demonstrate how to install [Alpine Linux](https://www.alpinelinux.org/) for server application. Alpine Linux will run on a raid configured ZFS filesystem with an encrypted home dataset, user services with runsvdir and user containers with podman.
Alpine Linux makes a good base for a server because of its simplicity, lightweightness and security. Check out the [Alpine Linux wiki](https://wiki.alpinelinux.org/wiki/Main_Page) for additional resources and information.

View file

@ -1,20 +1,36 @@
# Installation
To install the Alpine Linux distribution on the system, the root subvolume and the efi partition have to be mounted to the main system.
To install the Alpine Linux distribution on the system, the datasets of the system pool and the EFI partitions have to be mounted to the main system.
First import the system pool
```
# mount -o subvol=@root /dev/mapper/luks /mnt -t btrfs
# mkdir /mnt/efi -p
# mount /dev/<disk>1 /mnt/efi -t vfat
# zpool import -N -R /mnt tank
```
Then set up the base system using `setup disk`:
Mount the datasets in the system pool and decrypt the home dataset
```
# zfs mount tank/root/alpine
# zfs load-key -L prompt tank/home
# zfs mount tank/home
# zfs mount tank/var
```
Mount the ESP
```
# mkdir /mnt/esp
# mount /dev/md/esp /mnt/esp -t vfat
```
Then install Alpine Linux
```
# setup-disk -m sys /mnt
```
This will also add grub as bootloader which will be replaced but for now it will reside on the efi partition.
This will also add `grub` as bootloader which will be replaced but for now it will reside on the ESP.
To make it possible to chroot into the system, mount the other directories:
@ -34,7 +50,8 @@ The other setup scripts can be used to configure key aspects of the system. Besi
# setup-ntp openntpd
# rc-update add acpid default
# rc-update add seedrng boot
# rm -rf /var/tmp ; ln -s /tmp /var/tmp
# rm -rf /var/tmp
# ln -s /tmp /var/tmp
# passwd root
```
@ -48,22 +65,29 @@ clock_hctosys="NO"
clock_systohc="NO"
```
Configure the ESP raid array to mount
```
# modprobe raid1
# echo raid1 >> /etc/modules-load.d/raid1.conf
# mdadm --detail --scan >> /etc/mdadm.conf
# rc-update add mdadm boot
# rc-update add mdadm-raid boot
```
Configure ZFS to mount
```
rc-update add zfs-import sysinit
rc-update add zfs-mount sysinit
```
Edit `/etc/fstab` for correct mounts:
```
/dev/disk/by-label/efi /efi vfat defaults,nodev,nosuid,noexec 0 2
/dev/disk/by-uuid/<volume-uuid> / btrfs defaults,noatime,subvol=/@root 0 1
/dev/disk/by-uuid/<volume-uuid> /home btrfs defaults,noatime,nodev,nosuid,subvol=/@home 0 2
/dev/disk/by-uuid/<volume-uuid> /var btrfs defaults,nodev,nosuid,noexec,subvol=/@var 0 2
/dev/disk/by-uuid/<volume-uuid> /nix btrfs defaults,noatime,nodev,nosuid,subvol=/@nix 0 2
tmpfs /tmp tmpfs rw,size=4G,nr_inodes=5k,noexec,nodev,nosuid,mode=1777 0 0
proc /proc proc nosuid,nodev,noexec,hidepid=2 0 0
```
Here `<volume-uuid>` has to be replaced with the uuid of the root volume:
```
# blkid /dev/mapper/luks >> /etc/fstab
/dev/md/esp /esp vfat defaults,nodev,nosuid,noexec 0 2
tmpfs /tmp tmpfs rw,size=4G,nr_inodes=5k,nodev,nosuid,noexec,mode=1777 0 0
proc /proc proc nodev,nosuid,noexec,hidepid=2 0 0
```
By default, Alpine Linux uses `mkinitfs` to create an initial ram filesystem, although it is minimal that also means that it lacks some functionality which is needed for a proper setup. Because of this `mkinitfs` and `grub-efi `will be replaced with `booster` and `secureboot-hook`.
@ -76,6 +100,7 @@ By default, Alpine Linux uses `mkinitfs` to create an initial ram filesystem, al
To configure booster edit `/etc/booster.yaml`:
```
enable_zfs: true
busybox: false
modules: vfat,nls_cp437,nls_iso8859_1
```
@ -95,34 +120,31 @@ to:
and configure `/etc/kernel-hooks.d/secureboot.conf` for cmdline and secureboot.
```
cmdline="rw rd.luks.name="<partition-uuid>"=luks root=/dev/disk/by-uuid/<volume-uuid> rootflags=subvol=/@root quiet splash"
cmdline="rw zfs=tank/root/alpine quiet splash"
signing_cert="/usr/share/secureboot/keys/db/db.pem"
signing_key="/usr/share/secureboot/keys/db/db.key"
output_dir="/efi/EFI/Linux"
output_dir="/esp/efi/linux"
output_name="alpine-linux-{flavor}.efi"
```
Here `<partition-uuid>` and `<volume-uuid>` have to be replaced with the uuid of the root partition and volume respectively.
```
# blkid /dev/<disk>2 >> /etc/kernel-hooks.d/secureboot.conf
# blkid /dev/mapper/luks >> /etc/kernel-hooks.d/secureboot.conf
```
Use `sbctl` to create secureboot keys and sign them.
```
# sbctl create-keys
# sbctl enroll-keys
...
```
> Whilst enrolling the keys it might be necessary to add the `--microsoft` flag if you are unable to use custom keys.
Now to see if everything went succesfully run:
Set the cache-file of the ZFS pool
```
# zpool set cachefile=/etc/zfs/zpool.cache tank
```
Now to see if everything went successfully, run:
```
# apk fix kernel-hooks
@ -134,21 +156,26 @@ As discussed earlier `grub` will be replaced, install `gummiboot` as a bootloade
```
# apk add gummiboot
# gummiboot install --path=/efi
# sbctl sign -s /efi/EFI/gummiboot/gummibootx64.efi
# sbctl sign -s /efi/EFI/Boot/BOOTX64.EFI
# mkdir /esp/loader
# mkdir /esp/efi/boot
# cp /usr/lib/gummiboot/gummibootx64.efi /esp/efi/boot/bootx64.efi
```
Sign the bootloader with `sbctl`
```
# sbctl sign -s /esp/efi/boot/bootx64.efi
```
And also remove some remnants of `grub`.
```
# rm -rf /efi/EFI/alpine
# rm -rf /efi/grub
# rm -rf /boot/grub
# rm -rf /etc/default
# cd /boot && unlink boot
# cd /boot && unlink boot && cd ..
```
`gummiboot` can be configured with the file `/efi/loader/loader.conf` with which the timeout and the default OS can be specified.
`gummiboot` can be configured with the file `/esp/loader/loader.conf` with which the timeout and the default OS can be specified.
```
default alpine-linux-lts.efi
@ -161,9 +188,6 @@ Now exit the chroot and you should be able to reboot into a working Alpine syste
```
# exit
# umount -lf /mnt
# zpool export tank
# reboot
```
When booting up your screen might appear blank, this is the encryption prompt. Enter the encryption key and press enter to boot.
> Do note that "Linux Boot Manager" will have to be set to load first in your bios.

View file

@ -1,73 +0,0 @@
# Provisioning
After flasing the Alpine Linux extended ISO, partition a disk. For this action internet is required since `gptfdisk` is not included on the extended ISO, therefore it needs to be obtained from the repository.
To set it up `setup-interfaces` and `setup-apkrepos` will be used.
```
# setup-interfaces -ar
# setup-apkrepos -c1
```
A few packages will have to be installed first:
```
# apk add cryptsetup lsblk btrfs-progs gptfdisk dosfstools acpid
```
The drive should be partitioned using `gdisk` (or `cfdisk`). It should have at least two partitions with one `EFI System` partition and one `Linux filesystem` partition and look something like this:
| Number of partition | Size | Type |
|:-----:|:-----:|:-----:|
| 1 | 512 MB or more | EFI System |
| 2 | Rest of the drive | Linux filesystem |
Then to create the filesystem on the efi partition.
```
# mkfs.fat -F 32 -n efi /dev/<disk>1
```
The root partition of the system is going to be encrypted using `cryptsetup`. First generate a key that will be used to encrypt the device and save it temporarily to the file `/tmp/crypt-key.txt` with:
```
# cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1 > /tmp/crypt-key.txt && cat /tmp/crypt-key.txt
```
Later on in the guide `clevis` will be used for automatic decryption, so this key only has to be entered a few times. However, if any changes are made to the bios or secureboot then this key will be needed again so make sure to write it down.
Then format the partition using `cryptsetup`:
```
# cryptsetup luksFormat /dev/<disk>2 --type luks2 --cipher aes-xts-plain64 --hash sha512 --iter-time 4000 --key-size 512 --pbkdf argon2id --verify-passphrase
[Enter the generated key]
# cryptsetup open --type luks /dev/<disk>2 luks
```
This creates a formatted partition on `\dev\mapper\luks` which is denoted as the root volume. A btrfs filesystem will be created on the root volume by:
```
# mkfs.btrfs -L alpinelinux -n 32k /dev/mapper/luks
```
with `-n` the `nodesize`, larger nodesize gives better packing and less fragmentation at the cost of more expensive memory operations while updating metadata blocks. The default is 16k.
To access the root volume it needs to be mounted.
```
# mount /dev/mapper/luks /mnt -t btrfs
```
Then to create the necessary subvolumes on the root volume, we use:
```
for i in root home var nix; do
> btrfs subvolume create /mnt/@$i
> done
```
Now unmount the root volume and provisioning is finished.
```
# umount -lf /mnt
```

View file

@ -0,0 +1 @@
alpine-desktop-setup/post-install/drivers.md

View file

@ -0,0 +1 @@
alpine-desktop-setup/post-install/security.md

View file

@ -0,0 +1 @@
alpine-desktop-setup/post-install/swap.md

View file

@ -0,0 +1,125 @@
# Provisioning
After flashing the Alpine Linux extended ISO, partition the disks. For this action internet is required since `zfs` and `sgdisk` are not included on the extended ISO, therefore it needs to be obtained from the repository.
To set it up `setup-interfaces` and `setup-apkrepos` will be used.
```
# setup-interfaces -ar
# setup-apkrepos -c1
```
A few packages will have to be installed first:
```
# apk add zfs lsblk sgdisk wipefs dosfstools acpid mdadm
```
and load the ZFS kernel module
```
# modprobe zfs
```
Define the disks you want to use for this install
```
# export disks="/dev/disk/by-id/<id-disk-1> ... /dev/disk/by-id/<id-disk-n>"
```
with `<id-disk-n>` for $n \in \mathbb{N}$ the `id` of the disk.
> According to [openzfs-FAQ](https://openzfs.github.io/openzfs-docs/Project%20and%20Community/FAQ.html) using `/dev/disk/by-id/` is the best practice for small pools. For larger pools, using serial Attached SCSI (SAS) and the like, see [vdev_id](https://openzfs.github.io/openzfs-docs/man/master/5/vdev_id.conf.5.html) for proper configuration.
Wipe the existing disk partitions
```
# for disk in $disks; do
> zpool labelclear -f $disk
> wipefs -a $disk
> sgdisk --zap-all $disk
> done
```
Create on each disk an `EFI system` partition (ESP) and a `Linux filesystem` partition
```
# for disk in $disks; do
> sgdisk -n 1:1m:+512m -t 1:ef00 $disk
> sgdisk -n 2:0:-10m -t 2:8300 $disk
> done
```
Create device nodes
```
# mdev -s
```
Define the EFI partitions
```
# export efiparts=""
# for disk in $disks; do
> efipart=${disk}-part-1
> efiparts="$efiparts $efipart"
> done
```
Create a `mdraid` array on the EFI partitions
```
# modprobe raid1
# mdadm --create --level 1 --metadata 1.0 --raid-devices <n> /dev/md/esp $efiparts
# mdadm --assemble --scan
```
Format the array with a FAT32 filesystem
```
# mkfs.fat -F 32 /dev/md/esp
```
## ZFS pool creation
Define the pool partitions
```
# export poolparts=""
# for disk in $disks; do
> poolpart=${disk}-part-2
> poolparts="$poolparts $poolpart"
> done
```
Create the system pool
```
# zpool create -f \
-o ashift=12 \
-O compression=lz4 \
-O acltype=posix \
-O xattr=sa \
-O dnodesize=auto \
-m none \
tank raidz1 $poolparts
```
> Additionally, the `spare` option can be used to indicate spare disks. If more redundancy is preferred than `raidz2` and `raidz3` are possible [alternatives](https://openzfs.github.io/openzfs-docs/man/master/7/zpoolconcepts.7.html) for `raidz1`. If a single disk is used the `raidz` option can be left aside. For further information see [zpool-create](https://openzfs.github.io/openzfs-docs/man/master/8/zpool-create.8.html).
Then create the system datasets
```
# zfs create -o mountpoint=none tank/root
# zfs create -o canmount=noauto -o mountpoint=/ -o atime=off -o quota=24g tank/root/alpine
# zfs create -o mountpoint=/home -o atime=off -o setuid=off -o devices=off -o quota=<home-quota> -o encryption=on -o keyformat=passphrase tank/home
# zfs create -o mountpoint=/var -o exec=off -o setuid=off -o devices=off -o quota=16g tank/var
```
> Setting the `<home-quota>` depends on the total size of the pool, generally try to reserve some empty space in the pool.
Finally, export the zpool
```
# zpool export tank
```

View file

@ -74,8 +74,12 @@ nav:
- 'Installation': alpine-server-setup/installation/installation.md
- 'Post installation':
- 'Repositories': alpine-server-setup/post-install/repositories.md
- 'Firmware and drivers': alpine-server-setup/post-install/drivers.md
- 'Security': alpine-server-setup/post-install/security.md
- 'Logging': alpine-server-setup/post-install/logging.md
- 'Swap': alpine-server-setup/post-install/swap.md
- 'Users': alpine-server-setup/post-install/users.md
- 'Podman': alpine-server-setup/post-install/podman.md
- 'Void-desktop setup':
- void-desktop-setup/index.md