collapse into one blog page

This commit is contained in:
Luc Bijl 2025-08-28 21:29:58 +02:00
parent 4cc79798f0
commit b5e73a228d
13 changed files with 4 additions and 44 deletions

View file

@ -0,0 +1,353 @@
---
title: A simple Void Linux base installation
slug: simple-void-base-install
date: 2022-08-10
draft: false
authors:
- luc
- nils
tags:
- Void Linux
categories:
- Base installation
---
This blog entry will demonstrate how to install a `luks` encrypted `x86_64` [Void Linux](https://voidlinux.org/) `musl`/`UEFI signed UKI` operating system on a `ext4` filesystem. This entry is based on the [Void Handbook](https://docs.voidlinux.org/about/index.html) and the [Void man pages](https://man.voidlinux.org/man-pages.7).
<!-- more -->
## Provisioning
Flash the Void Linux `musl` ISO. After booting the ISO, partition the disk with either `fdisk` or `cfdisk`. Create an `EFI System` partition (ESP) and a `Linux Filesystem` partition (LFP).
It should look something like this:
| Partition | Size | Type |
| :-------: | :--: | :--: |
| 1 | 512MB | EFI System |
| 2 | Rest | Linux filesystem |
Format the ESP with a `FAT 32` filesystem:
``` shell-session
sh# mkfs.fat -F 32 -n esp /dev/<disk>1
```
Encrypt the LFP with `luks`:
``` shell-session
sh# cryptsetup luksFormat /dev/<disk>2 --type luks2
```
Open the encrypted partition and format it with a `ext4` filesystem:
``` shell-session
sh# cryptsetup open --type luks /dev/<disk2> root
sh# mkfs.ext4 -L root /dev/mapper/root
```
## Installation
To install Void Linux on the system, the ESP and LFP have to be mounted to the live (ISO) environment:
``` shell-session
sh# mount -t ext4 /dev/mapper/root /mnt
sh# mkdir /mnt/efi
sh# mount -t vfat /dev/disk/by-label/esp /mnt/efi
```
Now we may install Void Linux `musl` with `xbps-install`:
``` shell-session
sh# xbps-install -Sy -R https://repo-default.voidlinux.org/current/musl -r /mnt base-system cryptsetup openntpd
```
To have a functional chroot into the system, copy `resolv.conf` and bind the system process directories:
``` shell-session
sh# cp /etc/resolv.conf /mnt/etc/
sh# for dir in dev proc sys run; do
> mount --rbind --make-rslave /$dir /mnt/$dir
> done
sh# chroot /mnt
```
Configure some key aspects of the system:
``` shell-session
sh# echo <hostname> > /etc/hostname
sh# ln -sf /usr/share/zoneinfo/<area>/<subarea> /etc/localtime
sh# ln -s /etc/sv/dhcpcd /var/service/
sh# ln -s /etc/sv/opennptd /var/service/
sh# ln -s /etc/sv/acpid /var/service/
sh# passwd root #(1)!
```
1. The root password does not really matter because it is going to be locked after a user has been created.
Add the encrypted partition to the `crypttab`:
``` shell title="/etc/crypttab"
root /dev/disk/by-uuid/<uuid> none #(1)!
```
1. The simplest way to add the `uuid` into `/etc/crypttab` is by performing:
``` shell-session
sh# blkid -o value -s UUID /dev/<disk>2 >> /etc/crypttab
```
and enable the `crypttab` module for `dracut`:
``` shell title="/etc/dracut.conf.d/crypt.conf"
install_items+=" /etc/crypttab "
```
Edit the `fstab` to set the correct mounts:
``` shell title="/etc/fstab"
/dev/disk/by-label/root / ext4 defaults,noatime 0 1
/dev/disk/by-label/esp /efi vfat defaults,nodev,nosuid,noexec,umask=0077 0 2
tmpfs /tmp tmpfs rw,nodev,nosuid,noexec,mode=1777 0 0
proc /proc proc nodev,nosuid,noexec,hidepid=2 0 0
```
Configure the kernel command-line:
``` shell title="/etc/dracut.conf.d/cmdline.conf"
hostonly="yes"
kernel_cmdline="rw rd.luks.name=<uuid>=root root=/dev/mapper/root quiet splash" #(1)!
```
1. The simplest way to add the `uuid` into `/etc/dracut.conf.d/cmdline.conf` is by performing:
``` shell-session
sh# blkid -o value -s UUID /dev/<disk>2 >> /etc/dracut.conf.d/cmdline.conf
```
Install the bootloader `systemd-boot` and some hooks for `dracut` (1) necessary for building and signing the Unified Kernel Image (UKI):
{ .annotate }
1. The initramfs builder.
``` shell-session
sh# xbps-install systemd-boot dracut-uefi sbctl sbsigntool
```
> Verify that secureboot mode is in `setup mode` with `sbctl status`.
Replace the default `dracut` kernel hooks with those provided by `dracut-uefi`:
``` shell-session
sh# xbps-alternatives -s dracut-uefi
```
and set the directory where the UKI will be deposited:
``` shell title="/etc/default/dracut-uefi-hook"
UEFI_BUNDLE_DIR="/efi/EFI/Linux"
```
Create and enroll the secureboot keys into the system:
``` shell-session
sh# sbctl create-keys
sh# sbctl enroll-keys #(1)!
```
1. Whilst enrolling the keys it might be necessary to add the `--microsoft` flag if you are unable to use custom keys.
Set the key and certificate required for signing the UKI:
``` shell title="/etc/dracut.conf.d/uki.conf"
uefi_secureboot_cert="/var/lib/sbctl/keys/db/db.pem"
uefi_secureboot_key="/var/lib/sbctl/keys/db/db.key"
```
Install the bootloader:
``` shell-session
sh# bootctl install
```
Configure the bootloader:
``` shell title="/efi/loader/loader.conf"
timeout 3
editor no
```
Sign the bootloader with `sbctl`:
``` shell-session
sh# sbctl sign -s /efi/EFI/Boot/BOOTX64.efi
```
Finally, reconfigure the kernel to execute the `dracut-uefi` hook:
``` shell-session
sh# xbps-reconfigure -f linux<version>
```
> One may verify the signed files by running `sbctl verify`.
Now exit the chroot, unmount the filesystem and reboot:
``` shell-session
sh# exit
sh# umount -lf /mnt
sh# reboot
```
## Post installation
### Firmware and drivers
Install the device firmware for either AMD or Intel:
=== "AMD"
``` shell-session
sh# xbps-install linux-firmware-amd
```
=== "Intel"
``` shell-session
sh# xbps-install void-repo-nonfree
sh# xbps-install -S intel-ucode
```
### Swap
Add swap by creating a swapfile:
``` shell-session
sh# dd if=/dev/zero of=/swapfile bs=8m count=512 status=progress #(1)!
```
1. To create a swapfile of different size (now 4 GB), change the `count` to the desirable size.
Assign the correct permissions to the swapfile and make swap from the swapfile:
``` shell-session
sh# chmod 600 /swapfile
sh# mkswap /swapfile
```
Enable the swap:
``` shell-session
sh# swapon /swapfile
```
and make it persistent by adding it to the `fstab`:
``` shell title="/etc/fstab"
/swapfile none swap defaults 0 0
```
Reconfigure the kernel:
``` shell-session
sh# xbps-reconfigure -f linux<version>
```
### Users
To run processes securely, in an environment with fewer privileges, a user is necessary.
Before creating the user, install `doas`, to be able to "do as" root when it is required:
``` shell-session
sh# xbps-install opendoas
```
and configure `doas` by editing:
``` shell title="/etc/doas.conf"
permit persist :wheel as root
```
The alternative package `sudo` that is present in the `base-system` will be removed, since it is bloatware. To persist this, that is sudo will not be installed ever again on the system, insert:
``` shell title="/etc/xbps.d/nosudo.conf"
ignorepkg=sudo
```
and remove `sudo`:
``` shell-session
sh# xbps-remove -R sudo
```
Create a symbolic link from `doas` to `sudo` to impose backward compatiblility:
``` shell-session
sh# ln -s /bin/doas /bin/sudo
```
We can add a user, set its password and add it to the `wheel` group with:
``` shell-session
sh# useradd <user>
sh# passwd <user>
sh# usermod --append --groups wheel <user>
```
You may have to change the shell of the user in `/etc/passwd` from `/sbin/nologin` to a shell from `/etc/shells`. Void Linux comes with `/bin/bash` by default:
``` shell title="/etc/passwd"
<username>:x:1234:1234:<Full Name>:/home/<username>:/bin/<shell>
```
If you have checked that doas works with the user then you can lock the root account because it imposes security risks if it is kept open. This can be done with:
``` shell-session
sh# passwd -l root
```
and by changing its login shell to:
``` shell title="/etc/passwd"
root:x:0:0:root:/root:/sbin/nologin
```
### Networking
For desktop use `NetworkManager` is preferred over `dhcpcd` as network daemon, due to its versatility, i.e. Wi-Fi and VPN compatibility, MAC randomisation, et cetera. Install `NetworkManager` with:
``` shell-session
sh# xbps-install NetworkManager
```
Configure `NetworkManager` with MAC randomisation:
``` shell title="/etc/NetworkManager/NetworkManager.conf"
[main]
hostname-mode=none
plugins=ifupdown,keyfile
[ifupdown]
managed=true
[device]
wifi.scan-rand-mac-address=yes
[connection-mac-randomization]
ethernet.cloned-mac-address=random
wifi.cloned-mac-address=random
```
Disable `dhcpcd` and enable the `NetworkManager` daemon and its dependency, the `dbus` daemon:
``` shell-session
sh# rm -rf /var/service/dhcpcd
sh# ln -s /etc/sv/dbus /var/service/
sh# ln -s /etc/sv/NetworkManager /var/service/
```
For users to be able to modify connections on the system they will have to be added to the `network` group.
## Concluding remarks
This is the bare minimum for a Void Linux desktop system. Some additional features such as bluetooth, laptop battery management, printer compatiblity, et cetera, have been documented well in the [Void Handbook](https://docs.voidlinux.org/about/index.html), and can thus be found there. The next steps are the improvement of the security of the system and the configuration of the graphical user interface.

View file

@ -0,0 +1,459 @@
---
title: An Alpine Linux base installation
slug: alpine-linux-base-install
date: 2024-08-12
draft: false
authors:
- luc
- nils
tags:
- Alpine Linux
categories:
- Base installation
---
This blog entry will demonstrate how to install `x86_64` [Alpine Linux](https://www.alpinelinux.org/) for a server application. Alpine Linux will run on a raid configured encrypted ZFS filesystem with automatic decryption using TPM. 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.
<!-- more -->
## Provisioning
Flash the Alpine Linux extended ISO and make sure the secureboot keys are reset and TPM is enabled in the BIOS of the host.
After booting the Alpine Linux extended ISO, partition the disks. For this action internet is required since `zfs`, `sgdisk` and various other necessary packages are not included on the extended ISO, therefore they need to be obtained from the alpine package repository.
To set it up the `setup-interfaces` and `setup-apkrepos` scripts present on the Alpine Linux ISO will be used.
``` shell-session
sh# setup-interfaces -ar #(1)!
sh# setup-apkrepos -c1
```
1. To use Wi-Fi simply run `setup-interfaces -r` and select `wlan0` or similar.
A few packages will have to be installed first.
``` shell-session
sh# apk add zfs lsblk sgdisk wipefs dosfstools mdadm zlevis
```
and load the ZFS kernel module:
``` shell-session
sh# modprobe zfs
```
Define the disks you want to use for this install:
``` shell-session
sh# export disks="/dev/disk/by-id/<id-disk-1> ... /dev/disk/by-id/<id-disk-n>"
```
with `<id-disk-n>` for `n` an integer, 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, the best practice changes to using serial Attached SCSI (SAS), 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:
``` shell-session
sh# 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:
``` shell-session
sh# for disk in $disks; do
> sgdisk -n 1:1m:+512m -t 1:ef00 $disk
> sgdisk -n 2:0:-10m -t 2:8300 $disk
> done
```
Reload the device nodes:
``` shell-session
sh# mdev -s
```
Define the EFI partitions:
``` shell-session
sh# export efiparts=""
sh# for disk in $disks; do
> efipart=${disk}-part-1
> efiparts="$efiparts $efipart"
> done
```
Create a `mdraid` array on the EFI partitions:
``` shell-session
sh# modprobe raid1
sh# mdadm --create --level 1 --metadata 1.0 --raid-devices <n> /dev/md/esp $efiparts
sh# mdadm --assemble --scan
```
Format the array with a FAT32 filesystem:
``` shell-session
sh# mkfs.fat -F 32 /dev/md/esp
```
## ZFS pool creation
Define the pool partitions:
``` shell-session
sh# export poolparts=""
sh# for disk in $disks; do
> poolpart=${disk}-part-2
> poolparts="$poolparts $poolpart"
> done
```
The ZFS system pool is going to be encrypted. First generate an encryption key and save it temporarily to the file `/tmp/rpool.key` with:
``` shell-session
sh# cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1 > /tmp/rpool.key && cat /tmp/rpool.key
```
> While `zlevis` is used for automatic decryption, this key is required when changes are made to the BIOS or secureboot, so make sure to save it.
Create the system pool:
``` shell-session
sh# zpool create -f \
-o ashift=12 \
-O compression=lz4 \
-O acltype=posix \
-O xattr=sa \
-O dnodesize=auto \
-O encryption=on \
-O keyformat=passphrase \
-O keylocation=prompt \
-m none \
rpool 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:
``` shell-session
sh# zfs create -o mountpoint=none rpool/root
sh# zfs create -o mountpoint=legacy -o quota=24g rpool/root/alpine
sh# zfs create -o mountpoint=legacy -o quota=16g rpool/root/alpine/var
sh# zfs create -o mountpoint=/home -o atime=off -o setuid=off -o devices=off -o quota=<home-quota> rpool/home
```
> Setting the `<home-quota>` depends on the total size of the pool, generally try to reserve some empty space in the pool.
Write the encryption key to TPM with `zlevis`:
``` shell-session
sh# zlevis encrypt rpool '{"pcr_ids":"0,5,7"}' < /tmp/rpool.key #(1)!
```
1. See [zlevis functionality](https://docs.ampel.dev/zlevis/functionality/) to see the functionality of each `pcr_id`, and the other options that can be set.
> To check if it worked, perform `zlevis decrypt rpool`.
Finally, export the zpool:
``` shell-session
sh# zpool export rpool
```
## Installation
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 live (ISO) environment.
First import and decrypt the system pool:
``` shell-session
sh# zpool import -N -R /mnt rpool
sh# zfs load-key -L file:///tmp/rpool.key rpool
```
Then mount the datasets and the ESP on `/mnt`:
``` shell-session
sh# mount -t zfs rpool/root/alpine /mnt
sh# mkdir /mnt/var
sh# mount -t zfs rpool/root/alpine/var /mnt/var
sh# mkdir /mnt/efi
sh# mount -t vfat /dev/md/esp /mnt/efi
```
Now we may install Alpine Linux with the `setup-disk` script:
``` shell-session
sh# export BOOTLOADER=none
sh# setup-disk -m sys /mnt
```
To have a functional chroot into the system, bind the system process directories:
``` shell-session
sh# for dir in dev proc sys run; do
> mount --rbind --make-rslave /$dir /mnt/$dir
> done
sh# chroot /mnt
```
The other setup scripts can be used to configure key aspects of the system. Besides that a few necessary services have to be activated.
``` shell-session
sh# setup-hostname <hostname>
sh# setup-keymap us
sh# setup-timezone -i <area>/<subarea>
sh# setup-ntp openntpd
sh# setup-sshd -c dropbear
sh# rc-update add acpid default
sh# rc-update add seedrng boot
sh# passwd root #(1)!
```
1. The root password does not really matter because it is going to be locked after a user has been created.
Set the `hwclock` to use `UTC` and disable writing the time to hardware. Running a NTP negates its usability.
``` shell title="/etc/conf.d/hwclock"
clock="UTC"
clock_hctosys="NO"
clock_systohc="NO"
```
Configure the ESP raid array to mount:
``` shell-session
sh# modprobe raid1
sh# echo raid1 >> /etc/modules-load.d/raid1.conf
sh# mdadm --detail --scan >> /etc/mdadm.conf
sh# rc-update add mdadm boot
sh# rc-update add mdadm-raid boot
```
Configure ZFS to mount:
``` shell-session
sh# rc-update add zfs-mount sysinit
sh# rc-update add zfs-import sysinit
sh# rc-update add zfs-load-key sysinit
```
> If a faster boot time is preferred, `zfs-import` and `zfs-load-key` can be omitted in certain cases.
Edit the fstab to set the correct mounts:
``` shell title="/etc/fstab"
rpool/root/alpine / zfs rw,noatime,xattr,posixacl,casesensitive 0 1
rpool/root/alpine/var /var zfs rw,noatime,nodev,nosuid,xattr,posixacl,casesensitive 0 2
/dev/md/esp /efi vfat defaults,nodev,nosuid,noexec,umask=0077 0 2
tmpfs /tmp tmpfs rw,nodev,nosuid,noexec,mode=1777 0 0
proc /proc proc nodev,nosuid,noexec,hidepid=2 0 0
```
Install the following packages to make `mkinitfs` compatible with secureboot and TPM decryption:
``` shell-session
sh# apk add secureboot-hook sbctl zlevis zlevis-mkinitfs #(1)!
```
1. The `mkinitfs-zlevis` package is as of this moment not yet in the alpine package repository, for the relevant steps see the [zlevis mkinitfs-implementation](https://docs.ampel.dev/zlevis/implementation/#mkinitfs).
Configure `mkinitfs` to disable the trigger and to add the `zlevis` module:
``` shell title="/etc/mkinitfs/mkinitfs.conf"
features="... zlevis"
disable_trigger="yes"
```
The most important step is the creation of a UKI using the `secureboot-hook` of `mkinitfs`, which also automatically signs them. Configure the `kernel-hooks` to set the kernel cmdline options and secureboot:
``` shell title="/etc/kernel-hooks.d/secureboot.conf"
cmdline="rw root=ZFS=rpool/root/alpine rootflags=noatime quiet splash"
signing_cert="/var/lib/sbctl/keys/db/db.pem"
signing_key="/var/lib/sbctl/keys/db/db.key"
output_dir="/efi/EFI/Linux"
output_name="alpine-linux-{flavor}.efi"
```
Use `sbctl` to create secureboot keys and sign them:
``` shell-session
sh# sbctl create-keys
sh# sbctl enroll-keys #(1)!
```
1. Whilst enrolling the keys it might be necessary to add the `--microsoft` flag if you are unable to use custom keys.
Set the cache-file of the ZFS pool:
``` shell-session
sh# zpool set cachefile=/etc/zfs/zpool.cache rpool
```
Now to see if everything went successfully, run:
``` shell-session
sh# apk fix kernel-hooks
```
and it should give no warnings if done properly.
To install `systemd-boot` as friendly bootloader:
``` shell-session
sh# apk add systemd-boot
sh# bootctl install
```
> One may verify the signed files by running `sbctl verify`.
Configure `systemd-boot` to specify the timeout and the default OS :
``` shell title="/efi/loader/loader.conf"
default alpine-linux-lts.efi
timeout 2
editor no
```
Now exit the chroot and you should be able to reboot into a working Alpine system.
``` shell-session
sh# exit
sh# umount -lf /mnt
sh# zpool export rpool
sh# reboot
```
## Post installation
### Repositories
To set the correct repositories configure:
``` shell title="/etc/apk/repositories"
https://dl-cdn.alpinelinux.org/alpine/latest-stable/main
https://dl-cdn.alpinelinux.org/alpine/latest-stable/community
```
This will use the latest stable repository of Alpine (for example `v3.19`). To use a different version of Alpine simply change `latest-stable` to whatever version you want. Do note that you cannot (easily) downgrade your system's version.
There is also the `edge` repository which contains the latest packages, but is not recommended, due to the instability it imposes on the system.
> If a package is not yet in a stable release one may additionally configure:
>
> ``` shell title="/etc/apk/repositories"
> @<repository> https://dl-cdn.alpinelinux.org/alpine/edge/<repository>
> ```
>
> for the relevant `<repository>` and perform:
>
> ``` shell-session
> sh# apk add <package>@<repository>
> ```
>
> for the relevant `<package>`.
### Firmware and drivers
Install the device firmware for either AMD or Intel:
=== "AMD"
``` shell-session
sh# apk add amd-ucode
```
=== "Intel"
``` shell-session
sh# apk add intel-ucode
```
To make sure it is included during boot, regenerate the UKI with:
``` shell-session
sh# apk fix kernel-hooks
```
### Swap
To configure Swap install `zram-init`:
``` shell-session
sh# apk add zram-init
```
Configure `zram-init` to create a swap device of size one fourth of the ram size:
``` shell title="/etc/conf.d/zram-init"
load_on_start="yes"
unload_on_stop="yes"
num_devices="1"
type0="swap"
size0=`LC_ALL=C free -m | awk '/^mem:/{print int($2/4)}'`
maxs0=1
algo0=zstd
labl0=zram_swap
```
and add `zram-init` to the default runlevel:
``` shell-session
sh# rc-update add zram-init default
```
### Users
To run applications securely, in an environment with fewer privileges, a user is necessary.
Before creating the user, install `doas`. To be able to "do as" root when it is required:
``` shell-session
sh# apk add doas
```
and configure `doas` by editing:
``` shell title="/etc/doas.d/wheel.conf"
permit persist :wheel as root
```
A user can be added in Alpine Linux with the `setup-user` script. Here we can specify the name, groups and more:
```
# setup-user -g wheel <username>
# passwd <username>
```
You may have to change the shell of the user in `/etc/passwd` from `/sbin/nologin` to a shell from `/etc/shells`. Alpine Linux comes with `/bin/ash` by default:
``` shell title="/etc/passwd"
<username>:x:1234:1234:<Full Name>:/home/<username>:/bin/<shell>
```
If you have checked that `doas` works with the user then you can lock the root account because it imposes security risks if it is kept open. This can be done with:
```
# passwd -l root
```
and by changing its login shell to:
``` shell title="/etc/passwd"
root:x:0:0:root:/root:/sbin/nologin
```
## Concluding remarks
This is essentially it, you now have a fully operational alpine base system running, configured for server use. The next steps are the improvement of the security of the system and the configuration of the container management software.

View file

@ -0,0 +1,315 @@
---
title: Some Linux security improvements
slug: linux-security-improvents
date: 2024-08-14
draft: false
authors:
- luc
- nils
tags:
- Alpine Linux
- Gentoo Linux
categories:
- Security
---
The security of a Linux system can be further improved as will be outlined in the chapters of this blog entry. These chapters will discuss how to harden the different layers of the operating system and are based on the [Madaidans-insecurities page](https://madaidans-insecurities.github.io/guides/linux-hardening.html#kernel), various Linux man pages and the security considerations of [PlagueOS](https://0xacab.org/optout/plagueos/-/wikis/Security-Considerations) and [secureblue](https://secureblue.dev/features). Hardening the system is done to prevent as many exploits as possible. Such that in the end, you, and only you are in control of your system.
<!-- more -->
## Linux Security modules
Linux Security Modules (LSM) is a framework that allows the implementation of various security models in the Linux kernel.
These security modules may be enabled by adding them to the kernel `cmdline`:
``` shell title="/etc/kernel/cmdline"
... lsm=landlock,lockdown,yama,integrity ...
```
### Landlock
Landlock (`landlock`) is an access-control system that enables any processes to securely restrict themselves and their future children, i.e. sandboxing.
### Lockdown
Lockdown (`lockdown`) prevents both direct and indirect access to a running kernel image, attempting to protect against unauthorized modification of the kernel image and to prevent access to security and cryptographic data located in kernel memory, whilst still permitting driver modules to be loaded.
### Yama
Yama (`yama`) restricts the usage of `ptrace` (process-trace). Where `ptrace` is a system call that enables the tracing of a process or signalling to a process from within another process. Although by default (without yama) only limited communication is possible due to the small fixed-size block of memory that can be passed between the two processes. Yama attaches a `ptrace` permission level (0-3) to each process, with these levels defined as
| Level | Restriction |
| :---: | ----------- |
| 0 | No |
| 1 | Descendants-only attach |
| 2 | Admin-only attach |
| 3 | No attach |
and therefore restricts which processes can trace or signal other processes, helping to mitigate certain types of attacks, such as privilege escalation.
### Integrity Policy Enforcement
Integrity Policy Enforcement (IPE) (`integrity`) takes a complementary approach to access control. Focusing on the immutable security properties inherent to system components. These properties are fundamental attributes or features of a system component that cannot be altered, ensuring a consistent and reliable basis for security decisions.
### SELinux
Security-Enhanced Linux implements mandatory access control (MAC) policies that restrict how processes interact with each other and with files.
### AppArmor
AppArmor is a security module that provides a simpler alternative to SELinux. It can dissallow access to files which the process would not require, as defined by the apparmor profile. Install the necessary packages: (1)
{ .annotate }
1. For Gentoo Linux make sure to set the `apparmor` USE flag.
=== "Alpine Linux"
``` shell-session
sh# apk add apparmor apparmor-utils apparmor-profiles
```
=== "Gentoo Linux"
``` shell-session
sh# emerge -a apparmor apparmor-utils apparmor-profiles
```
and add it to the boot runlevel:
``` shell-session
sh# rc-update add apparmor boot
```
Add `apparmor` to the kernel `cmdline` to make it operational:
``` shell title="/etc/kernel/cmdline"
... lsm=...,apparmor apparmor=1 ...
```
Then reconfigure the `kernel`:
=== "Alpine Linux"
``` shell-session
sh# apk fix kernel-hooks
```
=== "Gentoo Linux"
``` shell-session
sh# emerge --config gentoo-kernel
```
You can check the status of `apparmor` with `apparmor-utils`:
``` shell-session
sh# aa-status
```
## Kernel boot parameters
Boot parameters configure the bootloader to parse the relevant settings to the kernel at boot. Hardening the boot process will improve the overall security of the system. The listed boot parameters in this chapter can be parsed into the kernel `cmdline`:
``` shell title="/etc/kernel/cmdline"
... slab_nomerge init_on_alloc=1 init_on_free=1 page_alloc.shuffle=1 pti=on ...
```
### Mitigations of system vulnerabilities
* The setting `slab_nomerge` disables [slab merging](https://en.wikipedia.org/wiki/Slab_allocation) which helps to protect against heap exploitation.
* The settings `init_on_alloc=1 init_on_free=1` enable zeroing of memory during allocation and free time, which helps to mitigate use-after-free vulnerabilities.
* The setting `page_alloc.shuffle=1` randomises page allocator freelists, making page allocations less predictable. (1)
{ .annotate }
1. Setting this parameter actually improves performance.
* The setting `pti=on` enables [kernel page-table isolation]() that mitigates the [meltdown vulnerability](https://en.wikipedia.org/wiki/Meltdown_(security_vulnerability)) and helps to protect against attempts to bypass [kernel address space layout randomisation](https://en.wikipedia.org/wiki/Address_space_layout_randomization).
* The setting `randomize_kstack_offset=on` randomises the kernel stack offset on each syscall, which helps to protect against attacks that rely on deterministic kernel stack layouts.
* The setting `vsyscall=none` disables the deprecated `vsyscalls`.
* The setting `debugfs=off` disables the debugfs, removing a source of sensitive information about the kernel.
* The setting `module.sig_enforce=1` enforces that only signed kernel modules can be loaded.
* The setting `lockdown=confidentiality` sets the strictest option of the `lockdown` security module.
* The setting `mce=0` causes the kernel to panic on uncorrectable errors in ECC memory. This setting is unnecessary for non-ECC memory. (1)
{ .annotate }
1. ECC memory from a security and a redundancy perspective is always recommended. The ZFS filesystem also functions better with ECC memory.
### Hardware specific mitigations of vulnerabilities
* The setting `spectre_v2=on` enables the mitigation of [spectre](https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)), a speculative execution CPU vulnerability that is present in all pre-2019 CPUs.
* The setting `spec_store_bypass_disable=on` disables [Speculative Store Bypass](https://en.wikipedia.org/wiki/Speculative_Store_Bypass) (SSB), in all pre-2019 CPUs there is a vulnerability in the SSB.
* The setting `tsx=off` disables [Transactional Synchronisation Extensions](https://en.wikipedia.org/wiki/Transactional_Synchronization_Extensions) (TSX), which is a feature of pre-2019 Intel CPUs. TSXs are vulnerable to cache [side-channel attacks](https://en.wikipedia.org/wiki/Side-channel_attack).
* The setting `tsx_async_abort=full` mitigates the TSX vulnerability if you are stupid enough to keep TSX enabled.
* The setting `mds=full` enables the mitigation of the [Micro-architectural Data Sampling](https://en.wikipedia.org/wiki/Microarchitectural_Data_Sampling) (MDS), a set of weaknesses in pre-2020 Intel x86_64 CPUs.
* The setting `l1tf=flush` mitigates the [L1 Terminal Fault vulnerability](https://docs.kernel.org/admin-guide/hw-vuln/l1tf.html) present in pre-2019, by conditional flushing of the Level 1 Data Cache.
* The setting `kvm.nx_huge_pages=force` mitigates the [iTLB multihit vulnerability](https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/multihit.html) present in pre-2019 Intel CPUs.
> So if you have a pre-2019 Intel CPU, throw it out of the window right now! The performance hit is quite significant with all these mitigations.
### Hardening the boot process
* The settings `quiet loglevel=0` prevent information leaks during boot and must be used in combination with the `kernel.printk` sysctl setting.
* The settings `rd.shell=0 rd.emergency.reboot=reboot` impose that at critical failure in the boot process the system should be rebooted and that the shell cannot be accessed at all times during this process. Hardening the boot process.
## Kernel sysctl settings
Kernel self-protection can be configured by creating:
``` shell title="/etc/sysctl.d/kernel.conf" linenums="1"
kernel.kptr_restrict=2 #(1)!
kernel.dmesg_restrict=1 #(2)!
kernel.printk=3 3 3 3 #(3)!
kernel.unprivileged_bpf_disabled=1 #(4)!
net.core.bpf_jit_harden=2 #(5)!
dev.tty.ldisc_autoload=0 #(6)!
kernel.kexec_load_disabled=1 #(7)!
kernel.sysrq=0 #(8)!
kernel.perf_event_paranoid=3 #(9)!
```
1. Mitigate kernel pointer leaks.
2. Restrict kernel log to `CAP_SYSLOG` capability.
3. Restrict kernel log in console during boot.
4. Restrict eBPF to `CAP_SYSLOG` capability.
5. Restrict eBPF to `CAP_SYSLOG` capability.
6. Restrict TTY line disciplines to `CAP_SYS_MODULE` capability.
7. Disable kexec (system call to boot another kernel during runtime).
8. Disable SysRq key (debugging functionality).
9. Restrict performance events to `CAP_PERFORM` capability.
Network protection can be configured by creating:
``` shell title="/etc/sysctl.d/network.conf" linenums="1"
net.ipv4.icmp_echo_ignore_all=1 #(1)!
net.ipv4.tcp_syncookies=1 #(2)!
net.ipv4.tcp_rfc1337=1 #(3)!
net.ipv4.tcp_sack=0 #(4)!
net.ipv4.tcp_dsack=0 #(5)!
net.ipv4.tcp_fack=0 #(6)!
net.ipv4.conf.all.rp_filter=1 #(7)!
net.ipv4.conf.default.rp_filter=1 #(8)!
net.ipv4.conf.all.accept_redirects=0 #(9)!
net.ipv4.conf.default.accept_redirects=0 #(10)!
net.ipv4.conf.all.secure_redirects=0 #(11)!
net.ipv4.conf.default.secure_redirects=0 #(12)!
net.ipv6.conf.all.accept_redirects=0 #(13)!
net.ipv6.conf.default.accept_redirects=0 #(14)!
net.ipv4.conf.all.send_redirects=0 #(15)!
net.ipv4.conf.default.send_redirects=0 #(16)!
net.ipv4.conf.all.accept_source_route=0 #(17)!
net.ipv4.conf.default.accept_source_route=0 #(18)!
net.ipv6.conf.all.accept_source_route=0 #(19)!
net.ipv6.conf.default.accept_source_route=0 #(20)!
```
1. Ignore all ICMP requests, to avoid [Smurf attacks](https://en.wikipedia.org/wiki/Smurf_attack).
2. Restricts resources handling SYN requests, helps protect against [SYN flood attacks](https://en.wikipedia.org/wiki/SYN_flood).
3. Drops RST packets for sockets in the time-wait state, to avoid [time-wait assassination attacks](https://datatracker.ietf.org/doc/html/rfc1337).
4. Disables TCP SACK, for servers it could be relevant to keep this enabled.
5. Disables TCP SACK, for servers it could be relevant to keep this enabled.
6. Disables TCP SACK, for servers it could be relevant to keep this enabled.
7. Enables source validation of packets received from all interfaces, to avoid [IP spoofing](https://en.wikipedia.org/wiki/IP_address_spoofing).
8. Enables source validation of packets received from all interfaces, to avoid [IP spoofing](https://en.wikipedia.org/wiki/IP_address_spoofing).
9. Disables ICMP redirect acceptance and sending to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
10. Disables ICMP redirect acceptance and sending to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
11. Disables ICMP redirect acceptance and sending to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
12. Disables ICMP redirect acceptance and sending to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
13. Disables ICMP redirect acceptance and sending to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
14. Disables ICMP redirect acceptance and sending to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
15. Disables ICMP redirect acceptance and sending to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
16. Disables ICMP redirect acceptance and sending to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
17. Disables source routing to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
18. Disables source routing to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
19. Disables source routing to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
20. Disables source routing to prevent [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).
Protection of the user space can be configured by creating:
``` shell title="/etc/sysctl.d/user.conf" linenums="1"
kernel.yama.ptrace_scope=2 #(1)!
vm.mmap_rnd_bits=32 #(2)!
vm.mmap_rnd_compat_bits=16 #(3)!
fs.protected_symlinks=1 #(4)!
fs.protected_hardlinks=1 #(5)!
fs.protected_fifos=2 #(6)!
fs.protected_regular=2 #(7)!
```
1. Restrict `ptrace` usage to level 2 (only processes with `CAP_SYS_PTRACE` capability).
2. Increase the entropy for mmap ASLR, compatible with `x86_64`.
3. Increase the entropy for mmap ASLR, compatible with `x86_64`.
4. Restricts symlink following to only well-defined owner paths, preventing [TOC/TOU races](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use).
5. Restricts symlink following to only well-defined owner paths, preventing [TOC/TOU races](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use).
6. Prevent file creation in high-risk environments, helps to protect against [spoofing attakcs](https://en.wikipedia.org/wiki/Spoofing_attack).
7. Prevent file creation in high-risk environments, helps to protect against [spoofing attakcs](https://en.wikipedia.org/wiki/Spoofing_attack).
## Hardened memory allocator
The default memory allocator of `musl` is already reasonably secure but not as secure as [hardened-malloc](https://github.com/GrapheneOS/hardened_malloc/), install it with:
=== "Alpine Linux"
``` shell-session
sh# apk add hardened-malloc
```
=== "Gentoo Linux"
``` shell-session
sh# emerge -a hardened-malloc
```
and set it to system-wide edit:
``` shell title="/etc/ld-musl-x86_64.path"
/usr/lib/libhardened_malloc.so #(1)!
/lib
/usr/lib
/usr/local/lib
```
1. If problems with graphical applications occur the light variant of hardened-malloc `/usr/lib/libhardened_malloc-light.so` may also be used instead of `/usr/lib/libhardened_malloc.so`.
To accomodate the large number of guard pages created by `hardened-malloc` impose that we should set the following:
``` shell title="/etc/sysctl.d/malloc.conf"
vm.max_map_count=1048576
```
## Entropy
Improve the security of the system by increasing the entropy with the `jitterentropy` kernel module, install it with:
=== "Alpine Linux"
``` shell-session
sh# apk add jitterentropy-library
```
=== "Gentoo Linux"
``` shell-session
sh# emerge -a jitterentropy
```
and make sure that the module gets loaded:
``` shell title="/etc/modules-load.d/entropy.conf"
jitterentropy_rng
```

View file

@ -0,0 +1,363 @@
---
title: Rootless container management with Podman and runit
slug: rootless-container-management-with-podman-and-runit
date: 2024-08-30
draft: false
authors:
- luc
tags:
- Alpine Linux
- Gentoo Linux
categories:
- Container management
---
Containers and pods (a collection of containers in the same namespace) enables easy and secure management of hosted applications. Rootless containers and pods can be deployed on a server with [Podman](https://podman.io/) as the rootless container engine and [runit](https://smarden.org/runit/) as the user service manager. The service manager will be set-up to automatically start and update the containers and pods at boot and to periodically back-up the volumes and databases of the pods.
<!-- more -->
## User services with runsvdir
Using `runsvdir` requires `runit` to be installed on the system:
=== "Alpine Linux"
``` shell-session
sh# apk add runit
```
=== "Gentoo Linux"
``` shell-session
sh# emerge -a runit
```
Now create an `openrc` entry that will manage `runsvdir`:
``` shell title="/etc/init.d/runsvdir-user"
#!/sbin/openrc-run
user="${RC_SVCNAME##*.}"
svdir="/home/${user}/.local/service"
pidfile="/run/runsvdir-user.${user}.pid"
command="/usr/bin/runsvdir"
command_args="$svdir"
command_user="$user"
command_background=true
depend()
{
after network-online
}
```
Make the entry executable, link user `<username>` and add the service to the default runlevel:
``` shell-session
sh# chmod +x /etc/init.d/runsvdir-user
sh# ln -s /etc/init.d/runsvdir-user /etc/init.d/runsvdir-user.<username>
sh# rc-update add runsvdir-user.<username> default
```
> This process can of course be repeated for any number of users.
## Container management with Podman
Install `podman` with:
=== "Alpine Linux"
``` shell-session
sh# apk add podman
```
=== "Gentoo Linux"
``` shell-session
sh# emerge -a podman
```
Rootless `podman` requires `cgroups` to run, therefore add it to the default runlevel:
``` shell-session
sh# rc-update add cgroups default
```
Set up the network namespace configuration for the user:
``` shell-session
sh# modprobe tun
sh# echo tun >> /etc/modules-load.d/tun.conf
sh# for i in subuid subgid; do
> echo <username>:100000:65536 >> /etc/$i
> done
```
Run the following container to verify if everything works:
``` shell-session
sh$ podman run --rm hello-world
```
### Management of containers
To run a single container create:
``` shell title="~/.config/sv/{container-name}/run"
#!/bin/sh
command="/usr/bin/podman"
command_args="run --replace --rm --name=<container-name> --network=pasta"
env="<container-envs>"
ports="<container-ports>"
mounts="<container-mounts>"
image="<container-image>"
exec 2>&1
exec $command $command_args $env $ports $mounts $image
```
Make it executable and link it to the service directory:
``` shell-session
sh$ chmod +x ~/.config/sv/<container-name>/run
sh$ ln -s <home>/.config/sv/<container-name> <home>/.local/service
```
### Management of pods
To check if a pod is running, create:
``` shell title="~/.local/bin/checkpod"
#!/bin/sh
. ./conf
exec 2>&1
state=0
while [ $state == 0 ]
do
sleep 10
$command pod inspect ${name}-pod | grep -q '"State": "Running"' || state=1
done
```
and make it executable with:
``` shell-session
sh$ chmod +x ~/.local/bin/checkpod
```
To run a pod configured with `~/.config/pods/<pod-name>/<pod-name>.yml`, see [alpine-server](https://git.lucbijl.nl/luc/alpine-server) for examples, we setup the `runit` entry with a `conf`, `run` and `finish` structure. Therefore create:
``` shell title="~/.config/sv/{pod-name}/conf"
name="<pod-name>"
home="<home>"
pod_location="${home}/.config/pods/<pod-name>"
bin_location="${home}/.local/bin"
command="/usr/bin/podman"
command_args="--replace --network=pasta"
```
will contain all the relevant configuration specific to the pod. Now create:
``` shell title="~/.config/sv/{pod-name}/run"
#!/bin/sh
. ./conf
exec 2>&1
$command kube play $command_args ${pod_location}/${name}-pod.yml
exec ${bin_location}/checkpod
```
and create:
``` shell title="~/.config/sv/{pod-name}/finish"
#!/bin/sh
. ./conf
exec 2>&1
exec $command kube down ${pod_location}/${name}-pod.yml
```
will stay the same for any pod.
Make both `run` and `finish` executable:
``` shell-session
sh$ chmod +x ~/.config/sv/<pod-name>/run
sh$ chmod +x ~/.config/sv/<pod-name>/finish
```
Finally, link the pod to the service directory:
``` shell-session
sh$ ln -s <home>/.config/sv/<pod-name> <home>/.local/service
```
### Backup of volumes and databases
To back up volumes of containers and postgresql databases create:
``` shell title="~/.local/bin/dump"
#!/bin/sh
command="/usr/bin/podman"
# Dumps databases
postgres_databases="<list-of-postgres-databases>"
for database in $postgres_databases
do
exec $command exec -it ${database}-pod-postgres sh -c "pg_dumpall -U postgres | gzip > /dump/${database}.sql.gz"
done
# Exports volumes
volumes="<list-of-volumes>"
for volume in $volumes
do
exec $command volume export $volume --output <home>/.volumes/${volume}.tar
done
```
Make it executable:
``` shell-session
sh$ chmod +x ~/.local/bin/dump
```
Automate it with `snooze`:
=== "Alpine Linux"
``` shell-session
sh# apk add snooze
```
=== "Gentoo Linux"
``` shell-session
sh# emerge -a snooze
```
and create the corresponding `runit` entry:
``` shell title="~/.config/sv/dump/run"
#!/bin/sh
exec 2>&1
exec snooze -H* /home/neutrino/.local/bin/dump
```
which executes dump every hour.
Make it executable and link it to the service directory:
``` shell-session
sh$ chmod +x ~/.config/sv/dump/run
sh$ ln -s <home>/.config/dump <home>/.local/service
```
Then `restic` can be used to back up the `.dump` and `.volumes` folders to another server if necessary.
By creating:
``` shell title="~/.local/bin/load"
#!/bin/sh
command="/usr/bin/podman"
# Loads dumped databases
postgres_databases="<list-of-postgres-databases>"
for database in $postgres_databases
do
exec $command exec -it ${database}-pod-postgres sh -c "gunzip -c /dump/${database}.sql.gz | psql -U postgres"
done
# Imports volumes
volumes="<list-of-volumes>"
for volume in $volumes
do
exec $command volume import $volume <home>/.volumes/${volume}.tar
done
```
the volumes and postgresql databases can be reloaded.
Do not forget to make it executable:
``` shell-session
sh$ chmod +x ~/.local/bin/load
```
## Proxying with Caddy
While it would be more optimal to run a reverse proxy in a container and link the network namespaces to this container, this is unfortunately not possible with `pasta` user network namespaces. Therefore, the reverse proxy should be run in front of the containers and thus on the system.
Caddy is a simple and modern web-server that supports automatic HTTPS and can act as a reverse-proxy. Install `caddy` and `libcap` (necessary dependency) with:
=== "Alpine Linux"
``` shell-session
sh# apk add caddy libcap
```
=== "Gentoo Linux"
``` shell-session
sh# emerge -a caddy libcap
```
Give `caddy` privileges to accesss all ports: (1)
{ .annotate }
1. Such that we are able to run `caddy` rootless.
``` shell-session
sh# setcap cap_net_bind_service=+ep /usr/sbin/caddy
```
Create the `caddyfile` (1) according to your needs (2). Then convert it with the following to make it persistent:
{ .annotate }
1. The configuration file of `caddy`.
2. See [alpine-server](https://git.lucbijl.nl/luc/alpine-server) for examples.
``` shell-session
sh$ caddy adapt -c ~/.config/caddy/caddyfile -p > ~/.config/caddy/caddy.json
```
Create the corresponding `runit` entry for `caddy`:
``` shell title="~/.config/sv/caddy/run"
#!/bin/sh
command="/usr/sbin/caddy"
command_args="run"
exec ps | grep '[${command}] ${command_args}' > /dev/null
if [$? != 0]; then
exec 2>&1
exec $command $command_args
fi
```
Make it executable and link it to the service directory:
``` shell-session
sh$ chmod +x ~/.config/sv/caddy/run
sh$ ln -s <home>/.config/sv/caddy <home>/.local/service
```

View file

@ -0,0 +1,602 @@
---
title: A hardened Gentoo-Linux/openrc base installation
slug: gentoo-openrc-base-install
date: 2025-07-19
draft: false
authors:
- nils
- luc
tags:
- Gentoo Linux
categories:
- Base installation
---
This blog entry will demonstrate how to install a hardened `x86_64` [Gentoo Linux](https://www.gentoo.org/) `musl`/`openrc`/`UEFI signed UKI` operating system on an encrypted `ZFS` pool with automatic decryption using TPM. This entry is based on the [Gentoo `x86_64` handbook](https://wiki.gentoo.org/wiki/Handbook:AMD64) and the [Gentoo wiki](https://wiki.gentoo.org/wiki/Main_Page). Gentoo supplies the right tools to build a Linux operating system from scratch, suited to the hardware and needs of the user. This form of customizability and optimizability together with the strong community behind Gentoo makes it a good choice for a desktop operating system.
<!-- more -->
## Provisioning
To install Gentoo this guide will be using the [Alpine Extended ISO](https://alpinelinux.org/downloads/). It provides all of the necessary utilities for bootstrapping Gentoo. Make sure to boot with secureboot in setup mode or to already have keys ready to deploy.
After booting the Alpine Linux extended ISO, partition the disks. For this action internet is required since `zfs`, `sgdisk` and various other necessary packages are not included on the extended ISO, therefore they need to be obtained from the Alpine package repository.
Set it up with `setup-interfaces` and `setup-apkrepos`:
``` shell-session
sh# setup-interfaces -ar #(1)!
sh# setup-apkrepos -c1
```
1. To use Wi-Fi simply run `setup-interfaces -r` and select `wlan0` or similar.
Install the necessary packages:
``` shell-session
sh# apk add zfs lsblk sgdisk wipefs dosfstools zlevis #(1)!
```
1. The `zlevis` package is as of this moment not yet in the alpine package repository. Try to get it into the `bin` via a different method and add its dependencies `tpm2-tools` and `jose`.
and load the `ZFS` kernel module:
``` shell-session
sh# modprobe zfs
```
Wipe the existing disk partitions:
``` shell-session
sh# zpool labelclear -f /dev/<disk>
sh# wipefs -a /dev/<disk>
sh# sgdisk --zap-all /dev/<disk>
```
Create on the disk an `EFI system` partition (ESP) and a `Linux filesystem` partition (LFP):
``` shell-session
sh# sgdisk -n 1:1m:+512m -t 1:ef00 /dev/<disk>
sh# sgdisk -n 2:0:-10m -t 2:8300 /dev/<disk>
```
Reload the device nodes:
``` shell-session
sh# mdev -s
```
Format the ESP with a FAT32 filesystem:
``` shell-session
sh# mkfs.fat -F 32 -n esp /dev/<disk>1
```
## ZFS pool creation
The `ZFS` system pool is going to be encrypted. First generate an encryption key and save it temporarily to the file `/tmp/rpool.key` with:
``` shell-session
sh# cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1 > /tmp/rpool.key && cat /tmp/rpool.key
```
> All the while we use `zlevis` for automatic decryption, this key is required when making changes are made to the BIOS or secureboot, so make sure to save it.
Create the system pool:
``` shell-session
sh# zpool create -f \
-o ashift=12 \
-O compression=lz4 \
-O acltype=posix \
-O xattr=sa \
-O dnodesize=auto \
-O encryption=on \
-O keyformat=passphrase \
-O keylocation=prompt \
-m none \
rpool /dev/<disk>2
```
Then create the system datasets:
``` shell-session
sh# zfs create -o mountpoint=none rpool/root
sh# zfs create -o mountpoint=legacy -o quota=48g rpool/root/gentoo
sh# zfs create -o mountpoint=legacy -o quota=32g rpool/root/gentoo/var
sh# zfs create -o mountpoint=/home -o atime=off -o setuid=off -o devices=off -o quota=<home-quota> rpool/home
```
> Setting the `<home-quota>` depends on the total size of the pool, generally try to reserve some empty space in the pool.
Write the encryption key to TPM with `zlevis`:
``` shell-session
sh# zlevis encrypt rpool '{"pcr_ids":"0,5,7"}' < /tmp/rpool.key
```
> To check if it worked, perform `zlevis decrypt rpool`.
Finally, export the zpool:
``` shell-session
sh# zpool export rpool
```
## Installation
To install Gentoo Linux on the system, the ESP and the datasets of the system pool have to be mounted to the live (ISO) environment.
First import and decrypt the system pool:
``` shell-session
sh# zpool import -N -R /mnt rpool
sh# zfs load-key -L file:///tmp/rpool.key rpool
```
Then mount the datasets and the ESP on `/mnt`:
``` shell-session
sh# mount -t zfs rpool/root/gentoo /mnt
sh# mkdir /mnt/var
sh# mount -t zfs rpool/root/gentoo/var /mnt/var
sh# mkdir /mnt/efi
sh# mount -t vfat /dev/disk/by-label/esp /mnt/efi
```
Now we are going to fetch a `stage 3` tarball, the archive containing a minimal Gentoo environment. Which will act as the seed of the Gentoo install. Replace the `<release_date>` with the latest stage file release:
``` shell-session
sh# wget https://distfiles.gentoo.org/releases/amd64/autobuilds/current-stage3-amd64-musl-hardened/stage3-amd64-musl-hardened-<release_date>.tar.xz #(1) #(2)!
```
1. It is also possible to use `links` instead of `wget` which provides a small user interface for navigation:
``` shell-session
sh# links https://distfiles.gentoo.org/releases/amd64/autobuilds/current-stage3-amd64-musl-hardened
```
2. There are also other mirrors like `https://ftp.snt.utwente.nl/pub/os/linux/gentoo/releases/amd64/autobuilds/current-stage3-amd64-musl-hardened/` which might provide a faster download depending on your location. Check out <https://www.gentoo.org/downloads/mirrors/> for other mirrors.
Unpack the stage file in the root of the system:
``` shell-session
sh# tar -xpf stage3-*.tar.xz --numeric-owner -C /mnt
```
To have a functional chroot into the system, copy `resolv.conf` and bind the system process directories:
``` shell-session
sh# cp --dereference /etc/resolv.conf /mnt/etc/
sh# for dir in dev proc sys run; do
> mount --rbind --make-rslave /$dir /mnt/$dir
> done
sh# chroot /mnt
```
Configure `portage` (1) before doing anything else, an example file of its `make.conf` is given below:
{ .annotate }
1. The package manager of Gentoo Linux, which provisions build files from the [Gentoo ebuild repository](https://wiki.gentoo.org/wiki/Ebuild_repository#The_Gentoo_ebuild_repository) or any [additional ebuild repositories](https://wiki.gentoo.org/wiki/Portage#Ebuild_repositories).
``` shell title="/etc/portage/make.conf" linenums="1"
# A Gentoo installation is highly personal so diverting from this example is encouraged.
# Please consult /usr/share/portage/config/make.conf.example for a more detailed example.
# Compiler flags
COMMON_FLAGS="-march=native -O3 -pipe"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
FCFLAGS="${COMMON_FLAGS}"
FFLAGS="${COMMON_FLAGS}"
RUSTFLAGS="${RUSTFLAGS} -C target-cpu=native"
# Compile options
MAKEOPTS="-j8 -l9" #(1)!
# WARNING: Changing your CHOST is not something that should be done lightly.
# Please consult https://wiki.gentoo.org/wiki/Changing_the_CHOST_variable before changing.
CHOST="x86_64-pc-linux-musl"
# NOTE: This stage was built with the bindist USE flag enabled
# This sets the language of build output to English.
# Please keep this setting intact when reporting bugs.
LC_MESSAGES=C.utf8
# Logging
PORTAGE_ELOG_CLASSES="log warn error"
PORTAGE_LOGDIR="/var/log/portage"
PORTAGE_LOGDIR_CLEAN="find \"\${PORTAGE_LOGDIR}\" -type f ! -name \"summary.log*\" -mtime +7 -delete"
# Only accept free licenses
ACCEPT_LICENSE="-* @FREE"
# USE flags
USE="${USE} -debug -telemetry -modemmanager -ext* -ppp -systemd -elogind -X -kde -gnome -gtk-doc -webengine hardened dist-kernel udev initramfs hostonly secureboot modules-sign apparmor acpi networkmanager dbus hwaccel bash-completion man pam pipewire vulkan wayland eme-free jpeg png svg" #(2)!
# Emerge flags
EMERGE_DEFAULT_OPTS="${EMERGE_DEFAULT_OPTS} --with-bdeps y --quiet-build y"
# Secureboot settings
SECUREBOOT_SIGN_KEY="/var/lib/sbctl/keys/db/db.key"
SECUREBOOT_SIGN_CERT="/var/lib/sbctl/keys/db/db.pem"
MODULES_SIGN_KEY="${SECUREBOOT_SIGN_KEY}"
MODULES_SIGN_CERT="${SECUREBOOT_SIGN_CERT}"
MODULES_SIGN_HASH="sha512"
```
1. The `MAKEOPTS` defines (`-j` the number of jobs/processes) and limits (`-l` the load average) how many parallel make/compilation processes can be launched from `portage`. The number of parallel processes are limited by the number of available logical CPUs and by RAM, each process can take up to `2 GB` of RAM. The load average `-l` is generally set slighly above the number of jobs `-j`.
2. This selection of global (removed `-`) USE flags is rather personal and should be set according to the preferences of the user.
Synchronise the ebuild repositories and emerge some relevant packages:
``` shell-session
sh# emaint sync
sh# emerge -a app-portage/gentoolkit app-editors/vim sys-libs/timezone-data net-misc/openntpd sys-apps/musl-locales
```
Configure some key aspects of the system:
``` shell-session
sh# echo <hostname> > /etc/hostname
sh# echo TZ="/usr/share/zoneinfo/<Region>/<City>" > /etc/env.d/00tz
sh# echo MUSL_LOCPATH="/usr/share/i18n/locales/musl" > /etc/env.d/01locales #(1)!
sh# env-update && source /etc/profile
sh# eselect locale set <locale> #(2)!
sh# rc-update add ntpd default
sh# passwd root #(3)!
```
1. Musl does not support locales out of the box. They are not necessary but some programs rely on them to set the language of their application.
2. The correct `<locale>` can be found in the locale list:
``` shell-session
sh# eselect locale list
```
3. The root password does not really matter because it is going to be locked after a user has been created.
Edit the `fstab` to set the correct mounts:
``` shell title="/etc/fstab"
rpool/root/gentoo / zfs rw,noatime,xattr,posixacl,casesensitive 0 1
rpool/root/gentoo/var /var zfs rw,noatime,nodev,nosuid,xattr,posixacl,casesensitive 0 2
/dev/disk/by-label/esp /efi vfat defaults,nodev,nosuid,noexec,umask=0077 0 2
tmpfs /tmp tmpfs rw,nodev,nosuid,noexec,mode=1777 0 0
proc /proc proc nodev,nosuid,noexec,hidepid=2 0 0
```
Configure the kernel command-line to be able to boot correctly:
``` shell title="/etc/kernel/cmdline"
rw root=ZFS=rpool/root/gentoo rootflags=noatime quiet splash
```
Emerge `sbctl` and `sbsigntools` which will be used alongside `dracut` (1) to sign the build Unified Kernel Image (UKI):
{ .annotate }
1. The initramfs builder.
``` shell-session
sh# emerge -a app-crypt/sbctl app-crypt/sbsigntools
```
> Verify that secureboot is in `setup mode` with `sbctl status`.
Create and enroll the secureboot keys into the system:
``` shell-session
sh# sbctl create-keys
sh# sbctl enroll-keys #(1)!
```
1. Whilst enrolling the keys it might be necessary to add the `--microsoft` flag if you are unable to use custom keys.
Enable `dracut` to create a UKI with `ukify`:
``` shell title="/usr/lib/kernel/install.conf"
layout=uki
initrd_generator=dracut
uki_generator=ukify
```
and enable automatic signing with `sbsign`:
``` shell title="/etc/kernel/uki.conf"
[UKI]
SecureBootSigningTool=sbsign
```
Tell `portage` to generate a UKI when installing a kernel:
``` shell title="/etc/portage/package.use/installkernel"
sys-kernel/installkernel dracut ukify uki
```
Set the required USE flags for `systemd-utils` such that on emerging the bootloader `systemd-boot` will be installed:
``` shell title="/etc/portage/package.use/systemd-utils"
sys-apps/systemd-utils kernel-install boot ukify
```
The installation of the hardware firmware on the system, such as the CPU microcode, is hardware specific:
=== "AMD CPU"
The microcode updates for systems with an AMD CPU are all contained in `sys-kernel/linux-firmware`, accept its license:
``` shell title="/etc/portage/package.license/kernel"
sys-kernel/linux-firmware linux-fw-redistributable @BINARY-REDISTRIBUTABLE
```
and emerge them together with the Gentoo kernel, its necessary kernel modules for this system and the bootloader:
``` shell-session
sh# emerge -a sys-kernel/linux-firmware sys-fs/zfs-kmod sys-kernel/gentoo-kernel sys-apps/systemd-utils
```
=== "Intel CPU"
The microcode updates for systems with an Intel CPU require alongside `sys-kernel/linux-firmware` also `sys-kernel/intel-microcode`, accept its licenses:
``` shell title="/etc/portage/package.license/kernel"
sys-kernel/linux-firmware linux-fw-redistributable @BINARY-REDISTRIBUTABLE
sys-firmware/intel-microcode intel-ucode
```
and emerge them together with the Gentoo kernel, its necessary kernel modules for this system and the bootloader:
``` shell-session
sh# emerge -a sys-kernel/linux-firmware sys-firmware/intel-microcode sys-fs/zfs-kmod sys-kernel/gentoo-kernel sys-apps/systemd-utils
```
Install the bootloader on the ESP:
``` shell-session
sh# bootctl install
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed" to "/efi/EFI/systemd/systemd-bootx64.efi".
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed" to "/efi/EFI/BOOT/BOOTX64.EFI".
Random seed file /efi/loader/random-seed successfully refreshed (32 bytes).
Created EFI boot entry "Linux Boot Manager".
```
and configure the bootloader:
``` shell title="/efi/loader/loader.conf"
timeout 3
editor no
```
> One may verify the signed files by running `sbctl verify`.
Generate the `hostid` and reconfigure the Gentoo Kernel:
``` shell-session
sh# zgenhostid
sh# emerge --config gentoo-kernel
```
Finally, add some services for `ZFS`:
``` shell-session
sh# rc-update add zfs-mount sysinit
sh# rc-update add zfs-import sysinit #(1)!
sh# rc-update add zfs-load-key sysinit #(2)!
```
1. Omit if a faster boot time is preferred.
2. Omit if a faster boot time is preferred.
Now exit the chroot, unmount the datasets and reboot:
``` shell-session
sh# exit
sh# umount -lf /mnt
sh# reboot
```
## Optional installation
### Repositories
[GURU](https://wiki.gentoo.org/wiki/Project:GURU) is an extra repository containing ebuilds that are not available in the Gentoo repository. Although the packages it contains might not be as well tested as in the main repository they are still necessary for some setups, such as some dependencies required for automatic decryption or graphical user interfaces. Add GURU with:
``` shell-session
sh# emerge -a app-eselect/eselect-repository
sh# eselect repository enable guru
sh# emaint sync --repo guru
```
[Portage-ample](https://git.ampel.dev/ampel/portage-ample) is an extra extra repository containing ebuilds maintained by us, the [Ampel organisation](https://ampel.dev). Add portage-ample with:
``` shell-session
sh# eselect repository add portage-ample git https://git.ampel.dev/ampel/portage-ample
sh# emaint sync --repo portage-ample
```
### Automatic decryption
Automatic decryption will be managed with `zlevis`, which is able to unlock an encrypted `ZFS` root pool with keys saved in a TPM. Currently it is only available in the [portage-ample](https://git.ampel.dev/ampel/portage-ample) repository and also has some dependencies in the `guru` repository.
Set the `dracut` flag for `zlevis`:
``` shell title="/etc/portage/package.use/zlevis"
app-crypt/zlevis dracut
```
emerge `zlevis`:
``` shell-session
sh# emerge -a app-crypt/zlevis
```
and enable the `zlevis` module for `dracut`:
``` shell title="/etc/dracut.conf.d/zlevis.conf"
nofsck="yes"
add_dracutmodules+=" zlevis "
```
### Swap
To add swap to the system emerge `zram-init`:
``` shell-session
sh# emerge -a sys-block/zram-init
```
Configure `zram-init` to create a swap device of size one fourth of the ram size:
``` shell title="/etc/conf.d/zram-init"
load_on_start="yes"
unload_on_stop="yes"
num_devices="1"
type0="swap"
size0=`LC_ALL=C free -m | awk '/^Mem:/{print int($2/4)}'`
maxs0=1
algo0=zstd
labl0=zram_swap
```
and add `zram-init` to the default runlevel:
``` shell-session
sh# rc-update add zram-init default
```
### Compiler cache
Compiler cache can speed up recompile's, by avoiding recompilation of the same object files by fetching the result from a cache directory. The package `ccache` enables a compiler cache for `C/C++` object files, present in the Gentoo Kernel for example. Emerge it with:
``` shell-session
sh# emerge -a dev-util/ccache
```
and edit its configuration file:
``` shell title="/etc/ccache.conf"
cache_dir = /var/cache/ccache
max_size = 20G
umask = 002
hash_dir = false
compiler_check = %compiler% -dumpversion
compression = true
compression_level = 1
```
Configure `portage` to allow `ccache` for specified packages:
``` shell title="/etc/portage/env/enable-ccache.conf"
FEATURES="ccache"
CCACHE_DIR="/var/cache/ccache"
```
such that for every `C/C++` package with which you want to use `ccache`:
``` shell title="/etc/portage/package.env/ccache"
sys-kernel/gentoo-kernel enable-ccache.conf
...
```
### Users
To run processes securely, in an environment with fewer privileges, a user is necessary.
Before creating the user, emerge `doas`, to be able to "do as" root when it is required:
which requires us to set:
``` shell title="/etc/portage/package.use/doas"
app-admin/doas persist
```
then
``` shell-session
sh# emerge -a app-admin/doas
```
and configure `doas` by editing:
``` shell title="/etc/doas.conf"
permit persist :wheel as root
```
Now users who are in the `wheel` group are allowed to use the doas command to gain root privileges.
We can add a user, set its password and add it to the `wheel` group (if admin) with:
``` shell-session
sh# useradd -m -G wheel -s /bin/bash <username>
sh# passwd <username>
```
The `wheel` group should ideally only be assigned to one singular admin account. The users in the group are allowed to use the doas command to gain root privileges. This is necessary for installing packages and changing system files but not for a normal user.
If you have checked that doas works with the user then you can lock the root account because it imposes security risks if it is kept open. This can be done with:
``` shell-session
sh# passwd -l root
```
and by changing its login shell to:
``` shell title="/etc/passwd"
root:x:0:0:root:/root:/sbin/nologin
```
### Networking
For desktop use `NetworkManager` is preferred over `dhcpcd` as network daemon, due to its versatility, i.e. Wi-Fi and VPN compatibility, MAC randomisation, et cetera.
First set the relevant USE flags:
``` shell title="/etc/portage/package.use/networkmanager"
net-misc/networkmanager dhcpcd -wext
```
> Also make sure the `networkmanager` USE flag is enabled in your `make.conf`.
Now emerge `networkmanager`:
``` shell-session
sh# emerge -a net-misc/networkmanager
```
and configure `networkmanager` to have MAC randomisation by editing:
``` shell title="/etc/NetworkManager/NetworkManager.conf"
[main]
hostname-mode=none
plugins=ifupdown,keyfile
[ifupdown]
managed=true
[device]
wifi.scan-rand-mac-address=yes
[connection-mac-randomization]
ethernet.cloned-mac-address=random
wifi.cloned-mac-address=random
```
Then stop any other network service, such as `dhcpcd` running in the dynamic runlevel, and enable `NetworkManager`:
``` shell-session
sh# rc-update add NetworkManager default
```
For users to be able to modify connections on the system they will have to be added to the `plugdev` group.
## Concluding remarks
This is the bare minimum for a Gentoo Linux desktop system. Some additional features such as bluetooth, laptop battery management, printer compatiblity, et cetera, have been documented well in the [Gentoo Wiki](https://wiki.gentoo.org/wiki/Main_Page), and can thus be found there. The next steps are the improvement of the security of the system and the configuration of the graphical session.

View file

@ -0,0 +1,91 @@
---
title: Maintaining a system build on ZFS
slug: maintaining-a-system-build-on-zfs
date: 2025-08-02
draft: false
authors:
- luc
tags:
- Alpine Linux
- Gentoo Linux
categories:
- Maintenance
---
ZFS opens up novel methods to safely maintain a system. In this blog entry we will outline these methods in the form of an update protocol. To keep your system healthy this protocol should be executed on a weekly/monthly basis.
<!-- more -->
## Pre-update
To be able to rollback the system after a system update, one may create a `snapshot` of the root filesystem:
=== "Alpine Linux"
``` shell-session
sh# zfs snapshot rpool/root/alpine@previous
```
=== "Gentoo Linux"
``` shell-session
sh# zfs snapshot rpool/root/gentoo@previous
```
Furthermore, `zfs list -t snapshot` can be used to list snapshots and `zfs destroy` can be used to remove snapshots.
## Update
We may perform a system update:
=== "Alpine Linux"
``` shell-session
sh# apk upgrade
sh# reboot
```
=== "Gentoo Linux"
``` shell-session
sh# emerge -auDU @world
sh# reboot
```
If the system does not behave accordingly after reboot, one may `rollback` to the previous snapshot:
=== "Alpine Linux"
``` shell-session
sh# zfs rollback -r rpool/root/alpine@previous
```
=== "Gentoo Linux"
``` shell-session
sh# zfs rollback -r rpool/root/gentoo@previous
```
## Post-update
To maintain the performance of the SSDs in the system, perform a `trim` on the ZFS-pool:
``` shell-session
sh# zpool trim --secure --wait rpool #(1)!
```
1. Some devices may not support the option `--secure`.
A `scrub` on the ZFS-pool checks and repairs the data in the pool and is usually performed after a `trim`:
``` shell-session
sh# zpool scrub rpool
```
A `scrub` may take a while, its progress can be checked with:
``` shell-session
sh# zpool status rpool
```
> A ZFS scrub only repairs if `mirror` or a `zraid` mode is set in the pool.

View file

@ -0,0 +1,520 @@
---
title: A minimal graphical session with River, PipeWire and OpenRC
slug: minimal-graphical-session-with-river-pipewire-and-openrc
date: 2025-08-08
draft: false
authors:
- luc
tags:
- Gentoo Linux
categories:
- Graphical session
---
Graphical sessions are often full of unnecessary features, increasing (unnecessarily) the overall complexity of a desktop system. In this blog entry we intend to go back to the bare minimum, which defines itself as a reasonable step forward in practicality compared the default `tty`. Like being able to run graphical applications in windows and being able to manage these windows efficiently. We will meet but not exceed these requirements with [River](https://isaacfreund.com/software/river/) as our tiling window manager, [PipeWire](https://pipewire.org/) as our multimedia framework, and [OpenRC](https://wiki.gentoo.org/wiki/Project:OpenRC) as our user-service manager.
<!-- more -->
## User services with OpenRC
As of `openrc-0.60` user services are supported by `openrc` (1). To enable a `openrc` user session we require a runtime dir (`XDG_RUNTIME_DIR`), which can be supplied by [turnstile](https://github.com/chimera-linux/turnstile) (2). Therefore emerge `turnstile`:
{ .annotate }
1. Presently the versions `>=0.60` of `openrc` are unstable. Enable unstable releases of `openrc` with:
``` shell title="/etc/portage/package.accept_keyword/openrc"
sys-apps/openrc ~amd64
```
2. The `turnstile` ebuild can be found in the [portage-ample](https://git.ampel.dev/ampel/portage-ample) repository.
``` shell-session
sh# emerge -a sys-auth/turnstile
```
and enable runtime dir management by `turnstile` by setting `manage_rundir` to yes in `turnstiled.conf`:
``` shell title="/etc/turnstile/turnstiled.conf"
manage_rundir = yes
```
Add the `turnstile` daemon to the default runlevel:
``` shell-session
sh# rc-update add turnstiled default
```
`pam` enables us to generate this `rundir` and start the `openrc` user session when the user is logged in by enabling the `turnstile` and `openrc` `pam` modules in `pam.d/system-login`:
``` shell title="/etc/pam.d/system-login"
session optional pam_turnstile.so
session optional pam_openrc.so
```
This user session contains the usual runlevels (`boot`, `default`, `shutdown`) that are run in their respective regimes (1). One approach is to add the window manager in the `default` runlevel, such that it will be started at login. But there are particular use cases where the graphical session is not required or wanted (only taking up precious resources), it is therefore wise to configure the graphical session in its own runlevel, to have the freedom to enable it any time you want with `openrc -U <runlevel-graphical-session>` (2).
{ .annotate }
1. Though `boot` and `shutdown` should rather be interpreted as `login` and `logout` of the user session in this particular case.
2. Or to link it to the `default` runlevel.
Besides the `<runlevel-graphical-session>`, denoted with `g`, we will also define a `<runlevel-post-graphical-session>`, denoted with `h`, for the services depending on the graphical-session. The creation of these runlevels can be simply performed with `mkdir`:
``` shell-session
sh$ mkdir ~/.config/rc/runlevels/g
sh$ mkdir ~/.config/rc/runlevels/h
```
and stack them accordingly:
``` shell-session
sh$ ln -s /home/<user>/.config/rc/runlevels/default /home/<user>/.config/rc/runlevels/g
sh$ ln -s /home/<user>/.config/rc/runlevels/g /home/<user>/.config/rc/runlevels/h
```
A set of minimal applications consisting of the terminal emulator (`foot`), application launcher (`fuzzel`(1)), notification daemon (`mako`) and display configurator (`kanshi`(2)) that will be of use in the graphical session may be emerged via:
{ .annotate }
1. Presently `fuzzel` is unstable in `GURU`. Enable unstable releases of `fuzzel` with:
``` shell title="/etc/portage/package.accept_keywords/fuzzel"
gui-apps/fuzzel ~amd64
```
2. Presently `kanshi` and its dependency `libsfcg` is unstable in `GURU`. Enable unstable releases of `kanshi` and `libscfg` with:
``` shell title="/etc/portage/package.accept_keywords/kanshi"
gui-apps/kanshi ~amd64
dev-libs/libscfg ~amd64
```
``` shell-session
sh# emerge -a gui-apps/foot gui-apps/fuzzel gui-apps/mako gui-apps/kanshi
```
These applications require `WAYLAND_DISPLAY` to be set by the window manager. Therefore allow this variable to propagate in the user session:
``` shell title="~/.config/rc/rc.conf"
rc_env_allow="WAYLAND_DISPLAY"
```
The terminal emulator (`foot`) requires the backend `footserver` for its clients. Therefore create a user service that will supervise `footserver`:
``` shell title="~/.config/rc/init.d/footserver"
#!/usr/bin/openrc-run
depend() {
need graphical-session
}
supervisor=supervise-daemon
command="/usr/bin/foot"
command_args="--server"
```
Also the notification daemon (`mako`) and the display configurator (`kanshi`) require to be supervised, thus create a user service for them as well:
``` shell title="~/.config/rc/init.d/mako"
#!/usr/bin/openrc-run
depend() {
need graphical-session
}
supervisor=supervise-daemon
command="/usr/bin/mako"
```
``` shell title="~/.config/rc/init.d/kanshi"
#!/usr/bin/openrc-run
depend() {
need graphical-session
}
supervisor=supervise-daemon
command="/usr/bin/kanshi"
```
Adding these user services to `<runlevel-post-graphical-session>` can simply be performed with `rc-update -U`:
``` shell-session
sh$ rc-update -U add footserver h
sh$ rc-update -U add mako h
sh$ rc-update -U add kanshi h
```
## Window management with River
We require a seat manager as the mediating layer between the window manager and the hardware, to be able to run the window manager in the user session. We will use `seatd` as the seat management daemon, which requires us to set the following `USE` flags:
``` shell title="/etc/portage/package.use/seatd"
sys-auth/seatd server -builtin
```
Now emerge `seatd`:
``` shell-session
sh# emerge -a seatd
```
and add the daemon to the default runlevel:
``` shell-session
sh# rc-update add seatd default
```
> We need to be part of the `seat` group to effectively communicate with the seat manager:
>
> ``` shell-session
> sh# usermod --append --groups seat <user>
> ```
Video drivers are necessary for a graphical session to function. In Gentoo it is as simple as specifying what video card the system uses using the `USE` flags:
``` shell title="/etc/portage/package.use/video"
*/* VIDEO_CARDS: -* <amdgpu radeonsi || intel || nvidia>
```
By setting the `VIDEO_CARDS` `USE` flag the dependencies of `river` are compiled for the video card of the system, thus emerge `river` (1):
{ .annotate }
1. Presently `river` is unstable in `GURU`. Enable unstable releases of `river` with:
``` shell title="/etc/portage/package.accept_keywords/river"
gui-wm/river ~amd64
```
``` shell-session
sh# emerge -a gui-wm/river
```
> Buckle up, this is quite a hefty emerge.
`river` starts by executing an init (POSIX shell) script present in `~/.conig/river/init`, this scipt should contain or link to all the configuration of `river`. A minimal configuration of the `river/init` script is given below:
``` shell title="~/.config/river/init"
#!/bin/sh
# Set background and colours
swaybg -m fill -c 000000 -i $HOME/.local/share/backgrounds/<background> & #(1)!
riverctl border-color-focused 0xFFFFFFCC
riverctl border-color-unfocsed 0xFFFFFF66
riverctl border-color-urgent 0xFFFFFF
# Set border-width
riverctl border-width 2
# Set layout
riverctl default-layout rivertile
rivertile --view-padding 4 --outer-padding 4 &
# Source the keybinds
source ~/.config/river/binds
# Switch to post-graphical runlevel
openrc -U h
## Device input settings ##
# Keyboard layout
riverctl keyboard-layout qwerty
# Set keyboard repeat rate
riverctl set-repeat 50 300
# Set focus-follow-cursor
riverctl focus-follow-cursor normal
```
1. This line is optional, but if you want to spruce up your graphical session, then emerge:
``` shell-session
sh# emerge -a gui-apps/swaybg
```
with the `river/binds` given by:
``` shell title="~/.config/river/binds"
#!/bin/sh
# Super+Shift+Q to exit river
riverctl map normal Super+Shift Q spawn "openrc -U g && openrc -U default"
## Aplication mapping ##
# Super+Backspace to spawn waylock
riverctl map normal Super BackSpace spawn "waylock -ignore-empty-password" #(1)!
# Super+Return to spawn foot
riverctl map normal Super Return spawn footclient
# Super+Space to spawn fuzzel
riverctl map normal Super Space spawn fuzzel
# Super+D to dismiss mako notification
riverctl map normal Super D spawn "makoctl dismiss"
# Super+T to spawn datetime notification
riverctl map normal Super T spawn "$HOME/.local/bin/datetime" #(2)!
# Super+B to spawn battery state notification
riverctl map normal Super B spawn "$HOME/.local/bin/battery" #(3)!
# MonBrightnessUp or MonBrightnessDown to spawn brightness notification (4)
rivcertl map normal None XF86_MonBrightnessUp spawn "$HOME/.local/bin/brightness"
riverctl map normal None XF86_MonBrightnessDown spawn "$HOME/.local/bin/brightness"
# Print to spawn interactive screenshot
riverctl map normal None Print spawn "grim -g "(slurp -d)" - | wl-copy -t image/jpeg" #(5)!
# Shift+Print to spawn screenshot
riverctl map normal Shift Print spawn "grim - | wl-copy -t image/jpeg" #(6)!
## Focused view mapping ##
# Super+Q to close the focused view
riverctl map normal Super Q close
# Super+F to fullscreen toggle the focused view
riverctl map normal Super F toggle-fullscreen
# Super+BTN_MIDDLE to toggle float of the focused view
riverctl map-pointer normal Super BTN_MIDDLE toggle-float
# Super+BTN_LEFT to move the floated focused view
riverctl map-pointer normal Super BTN_LEFT move-view
# Super+BTN_RIGHT to resize the floated focused view
riverctl map-pointer normal Super BTN_RIGHT resize-view
# Super+{K;J} to focus next/previous view
riverctl map normal Super K focus-view next
riverctl map normal Super J focus-view previous
# Super+Shift+{K;J} to swap the focused view with the next/previous view
riverctl map normal Super+Shift K swap next
riverctl map normal Super+Shift J swap previous
# Super+{.;,} to focus the next/previous output
riverctl map normal Super Period focus-output next
riverctl map normal Super Comma focus-output previous
# Super+Shift+{.;,} to send the focused view to the next/previous output
riverctl map normal Super+Shift Period send-to-output next
riverctl map normal Super+Shift Comma send-to-output previous
## Rivertile mapping ##
# Super+{Up;Right;Down;Left} to change layout orientation
riverctl map normal Super Up send-layout-cmd rivertile "main-location top"
riverctl map normal Super Right send-layout-cmd rivertile "main-location right"
riverctl map normal Super Down send-layout-cmd rivertile "main-location down"
riverctl map normal Super Left send-layout-cmd rivertile "main-location left"
# Super+Shift+{Up;Down} to increment/decrement the main count of rivertile
riverctl map normal Super+Shift Up send-layout-cmd rivertile "main-count +1"
riverctl map normal Super+Shift Down send-layout-cmd rivertile "main-count -1"
# Super+Shift+{Right;Left} to increase/decrease the main ratio of rivertile
riverctl map normal Super+Shift Right send-layout-cmd rivertile "main-ratio +0.05"
riverctl map normal Super+Shift Left send-layout-cmd rivertile "main-ratio -0.05"
## Tag mapping ##
for i in $(seq 1 9)
do
j=$((1 << ($i - 1)))
# Super+[1-9] to focus tag [0-8]
riverctl map normal Super $i set-focused-tags $j
# Super+Shift+[1-9] to tag focused view with tag [0-8]
riverctl map normal Super+Shift $i set-view-tags $j
# Super+Control+[1-9] to toggle focus of tag [0-8]
riverctl map normal Super+Control $i toggle-focused-tags $j
# Super+Shift+Control+[1-9] to toggle tag [0-8] of focused view
riverctl map normal Super+Shift+Control $i toggle-view-tags $j
done
all_tags=$(((1 << 32) - 1))
# Super+O to focus all tags
riverctl map normal Super O set-focused-tags $all_tags
# Super+Shift+O to tag focused view with all tags
riverctl map normal Super+Shift O set-view-tags $all_tags
## Media mapping ## (7)
# AudioRaiseVolume to increase volume audio sink by 5%
riverctl map normal None XF86AudioRaiseVolume spawn "$HOME/.local/bin/audio sink volup"
# AudioLowerVolume to decrease volume audio sink by 5%
riverctl map normal None XF86AudioLowerVolume spawn "$HOME/.local/bin/audio sink voldown"
# AudioMute to toggle mute audio sink
riverctl map normal None XF86AudioMute spawn "$HOME/.local/bin/audio sink toggle"
# AudioMicRaiseVolume to increase volume audio source by 5%
riverctl map normal None XF86AudioMicRaiseVolume spawn "$HOME/.local/bin/audio source volup"
# AudioMicLowerVolume to decrease volume audio source by 5%
riverctl map normal None XF86AudioMicLowerVolume spawn "$HOME/.local/bin/audio source voldown"
# AudioMicMute to toggle mute audio source
riverctl map normal None XF86AudioMute spawn "$HOME/.local/bin/audio source toggle"
```
1. Optionally, it could be useful to lock your session, therefore emerge `waylock`(1):
{ .annotate }
1. Presently `waylock` is unstable in `GURU`. Enable unstable releases of `waylock` with:
``` shell title="/etc/portage/package.accept_keywords/fuzzel"
gui-apps/waylock ~amd64
```
``` shell-session
sh# emerge -a gui-apps/waylock
```
2. Optionally, it could be useful to see the datetime as a notification when you long for it, which is part of [ampel/notifiers](https://git.ampel.dev/ampel/notifiers).
3. Optionally, it could be useful to see the battery state as a notification, especially when you have a laptop. This functionality is part of [ampel/notifiers](https://git.ampel.dev/ampel/notifiers).
4. Optionally, it could be useful to see the brightness state of your display as a notification, which is part of [ampel/notifiers](https://git.ampel.dev/ampel/notifiers). As an additional remark, the default of configuration of `acpid` in Gentoo sucks, setting brightness capability with the same keys requires some changes in the configuration that can be found [here](https://git.lucbijl.nl/luc/gentoo-desktop/src/branch/main/etc/acpi).
5. Optional screenshot capability.
6. Optional screenshot capability.
7. Optionally, it could be useful to set and get the state of the audio sink and source, which is part of [ampel/notifiers](https://git.ampel.dev/ampel/notifiers).
To be able to run our window manager (`river`) supervised, we require the `river` user service:
``` shell title="~/.config/rc/init.d/river"
#!/usr/bin/openrc-run
depend() {
need dbus
provide graphical-session
}
supervisor=supervise-daemon
command="/usr/bin/river"
```
and add it to `<runlevel-graphical-session>`:
```
sh$ rc-update -U add river g
```
## Interfacing audio and video with PipeWire
We will interface audio and video with `pipewire` with `wireplumber` as fronted and `pulseaudio` support, which requires us to set the following `USE` flags:
``` shell title="/etc/portage/package.use/pipewire"
media-video/pipewire sound-server loudness echo-cancel bluetooth
media-sound/pulseaudio -daemon
```
Emerge `libpulse` for `pulseaudio` support, `pipewire` and `wireplumber`:
``` shell-session
sh# emerge -a media-libs/libpulse media-video/pipewire media-video/wireplumber
```
and copy the default configuration of `pipewire` and `wireplumber` to their respective `~/.config` folders:
``` shell-session
sh$ cp /usr/share/pipewire/pipewire.conf ~/.config/pipewire/
sh$ cp /usr/share/wirelumber/wireplumber.conf ~/.config/wireplumber/
```
Now create the corresponding user services for the `pipewire-pulse` support layer, `pipewire` itself, and `wireplumber`:
``` shell title="~/.config/rc/init.d/pipewire-pulse"
#!/usr/bin/openrc-run
depend() {
need audio-session
}
supervisor=supervise-daemon
command="/usr/bin/pipewire-pulse"
```
``` shell title="~/.config/rc/init.d/pipewire"
#!/usr/bin/openrc-run
depend() {
need graphical-session
provide audio-session
}
supervisor=supervise-daemon
command="/usr/bin/pipewire"
```
``` shell title="~/.config/rc/init.d/wireplumber"
#!/usr/bin/openrc-run
depend() {
need audio-session
}
supervisor=supervise-daemon
command="/usr/bin/wireplumber"
```
and add them to `<runlevel-post-graphical-session>`:
``` shell-session
sh$ rc-update -U add pipewire h
sh$ rc-update -U add pipewire-pulse h
sh$ rc-update -U add wireplumber h
```
> We need to be part of the `pipewire` group for `pipewire` and dependencies to work accordingly:
>
> ``` shell-session
> sh# usermod --append --groups pipewire <user>
> ```
For screencast and screenshot support in the graphical session we require the `xdg-desktop-portal` layer for rivers' backend `wlroots`, together with some tools (`grim`, `slurp`, `wl-clipboard`) to perform and save screenshots to the clipboard:
``` shell-session
sh# emerge -a gui-libs/xdg-desktop-portal-wlr gui-apps/grim gui-app/slurp gui-apps/wl-clipboard
```
The `xdg-desktop-portal` should be run supervised, therefore create the `xdg-desktop-portal-wlr` user service:
``` shell title="~/.config/rc/init.d/xdg-desktop-portal-wlr"
depend() {
need graphical-session
need audio-session
}
supervisor=supervise-daemon
command="/usr/libexec/xdg-desktop-portal-wlr"
```
and add it to `<runlevel-post-graphical-session>`:
```
sh$ rc-update -U add xdg-desktop-portal-wlr h
```
## Concluding remarks
Well, I think we are there, a minimal graphical session suitable for everyday use. Regarding the customisation of the installed gui-apps, you may have a look at [my gentoo-desktop repository](https://git.lucbijl.nl/luc/gentoo-desktop), or if you think you have a better taste for aesthetic, then do it yourself you cynical connoisseur!