Distro is trival, just learn the basics and build your own.
Series Index
- Linux Bootstrap Installation
- Linux A/B System Updates via BTRFS Snapshot
- Linux Post Installation: Desktop Preparation
- Linux Desktop: Sway, Labwc, GUI Apps
Preface
Want to stop distro hopping? Sure, just go read through the ArchWiki. Don’t get me wrong, I’m not selling Arch Linux to you, just the wiki. The reason you always be distracted by shining fancy components from some new releases or emerging distros, is that you don’t have a big picture about linux and its ecosystem, and you haven’t figure out what do you really need. This is the most thing I learned after reading through the wiki. When I finished reading, I learned what choices are there, and found my needs, then built my own configurations.
In fact, you can install nearly all the linux distros manually in a similar way, aka “the arch way”, since the installers they offered are doing the same job under the hood, but with less flexibility and more bloat. So if you want to settle down, go read the wiki, learn the basics, then you can pick up whatever distros you like and tweaking them to the shape you want, the only differences are just package management system, release model and community support.
This guide is based on Arch Linux, but also works for Debian/Ubutnu and Fedora, the differences are minor, demonstrated in Debian Fedora section. I’m trying my best to make it distro irrelevant.
Live ISO
You need a live iso image to boot into live system for doing installation.
To create bootable USB stick, use Ventoy or Rufus.
Partition Disk
In my experience, the best partition practice is to create a separate EFI system partition and a root partition with BTRFS filesystem, we will discuss BTRFS later.
We will follow the Discoverable Partitions Specification to let systemd automatically discover, mount and enable the root partition based on GPT (GUID Partition Tables), by specifying dedicated UUIDs to partitions.
Using Parted to do the job:
(root)# parted /dev/nvme0n1
(parted) mklabel gpt
(parted) mkpart EFIPART fat32 1MiB 1025MiB
(parted) set 1 esp on
(parted) mkpart ROOTPART btrfs 1025MiB 100%
(parted) type 2 4f68bce3-e8cd-4db1-96e7-fbcaf984b709
(parted) quit
Partitions need to be aligned to specific size for LUKS working correctly, a typical practice is 1MiB. In our example, we assigned 1GB to the EFI partition, the rest to the root partition, and aligned them to 1MiB.
Note that we specified the discoverable UUID for the root partition, but not for
the EFI partition, because it is done by the esp on commands.
LUKS
Next we set Device Encryption. Even you don’t want to bother typing another passphrase beside system login password every time when booting up, I still strongly recommend to configure it with a preset key file (we will discuss this in later section), which do not need you to enter passphrase, it will do decrypting automatically using the key file. Although it is missing the point of disk encryption in this way, it will still be beneficial, if your laptop is lost, this encryption setting can prevent your data being read from random people, since there’s low possibility the person who picked your device is computer expert with knowledges about Linux and LUKS.
You still need to set a passphrase when configuring LUKS, save it carefully, may be use a password manager to store it, KeePass is a good one.
After partitioning, you can locate partitions via their labels:
(root)# cryptsetup luksFormat /dev/disk/by-partlabel/ROOTPART
(root)# cryptsetup open /dev/disk/by-partlabel/ROOTPART root
(root)# ls /dev/mapper/root
Now the ROOTPART is encrypted, and must be decrypted via cryptsetup open
command to let it work, /dev/mapper/root is the decrypted root partition,
we will create filesystem on top of it.
BTRFS
Ext4 may be the most solid filesystem, but BTRFS is a better choice for personal use because of its modern features.
Recall the early days when I was learning and tinkering with linux, It was always a hard decision on how much storage to allocate for the separate /home partition, today with BTRFS, it’s not a problem anymore. You could just create BTRFS subvolumes for whatever directories you want to separate, they will share the whole storage of the root partition, just like normal folders do.
(root)# mkfs.btrfs /dev/mapper/root
(root)# mount /dev/mapper/root /mnt
(root)# btrfs subvolume create /mnt/@
(root)# btrfs subvolume create /mnt/@home
(root)# btrfs subvolume create /mnt/@data
(root)# umount /mnt
(root)# mount -o subvol=@ /dev/mapper/root /mnt
(root)# mount -o subvol=@home --mkdir /dev/mapper/root /mnt/home
(root)# mount -o subvol=@data --mkdir /dev/mapper/root /mnt/data
(root)# mount --mkdir /dev/disk/by-partlabel/EFIPART /mnt/efi
Another great BTRFS feature is it’s easy to create snapshots by its Copy on Write (CoW) nature, useful for creating backup against system crash.
WiFi
Use iwd to connect to WiFi.
Repo Mirror
Check the mirrorlist from official website,
then edit /etc/pacman.d/mirrorlist.
For Debian/Ubuntu and Fedora, refer to Debian Fedora section.
Base System
Now we are ready to install the base system. We will use a dedicated tool to install base system packages into /mnt.
For Arch it’s Pacstrap:
(root)# pacstrap -K /mnt \
base linux linux-firmware btrfs-progs dracut zram-generator neovim iwd
Also install amd-ucode or intel-ucode for CPU microcode updates.
For Debian/Ubuntu and Fedora, refer to Debian Fedora section.
Fstab
Let’s generate the fstab
before entering chroot system, since we need to get partition UUIDs from
live system. First we write partition UUIDs
to temporary text files using blkid command for later use, because typing UUID
manually is annoying and error prone, note the UUID for the root partition must
be the decrypted one, which is /dev/mapper/root, not the ROOTPART.
(root)# blkid -s UUID -o value /dev/mapper/root > /tmp/rootuuid.txt
(root)# blkid -s UUID -o value /dev/disk/by-partlabel/EFIPART > /tmp/efiuuid.txt
Then we edit /mnt/etc/fstab. If you use nano text editor, you can press
Ctrl + r to read UUID from temporary file, or if you use vim, you can run
:r /tpm/rootuuid.txt command to read UUID.
UUID=xxxxxxxx-...-xxxxxxxxxxxx / btrfs compress=zstd,subvol=/@ 0 0
UUID=xxxxxxxx-...-xxxxxxxxxxxx /home btrfs compress=zstd,subvol=/@home 0 0
UUID=xxxxxxxx-...-xxxxxxxxxxxx /data btrfs compress=zstd,subvol=/@data 0 0
UUID=XXXX-XXXX /efi vfat defaults 0 0
Chroot
Mount virtual filesystems to /mnt then chroot into it :
(root)# for dir in dev proc run sys; do mount --rbind --make-rslave /$dir /mnt/$dir; done
(root)# chroot /mnt /bin/bash
Timezone
(root)# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
Localization
(root)# echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
(root)# echo "zh_CN.UTF-8 UTF-8" >> /etc/locale.gen
(root)# locale-gen
(root)# echo "LANG=en_US.UTF-8" >> /etc/locale.conf
Hostname
(root)# echo "archlinux" > /etc/hostname
Root Password
(root)# passwd
Systemd-Networkd
In my experience, systemd-networkd is better than NetworkManager especially for maintaining bridged network interfaces for virtual machines.
Run ip link show command to get your network interface names,
for example: enp0s1, wlan0.
For wired network interface, create /etc/systemd/network/23-lan.network.
[Match]
Name=enp0s1
[Link]
RequiredForOnline=routable
[Network]
DHCP=yes
[DHCPv4]
RouteMetric=100
[IPV6AcceptRA]
RouteMetric=100
For wireless network interface, create /etc/systemd/network/25-wlan.network.
[Match]
Name=wlan0
[Link]
RequiredForOnline=routable
[Network]
DHCP=yes
IgnoreCarrierLoss=3s
[DHCPv4]
RouteMetric=600
[IPV6AcceptRA]
RouteMetric=600
Setting unique RouteMetric for different network interfaces is necessary,
or they will enter into “race condition”, which will cause extreamly slow network
connections.
RequiredForOnline=routable is necessary to prevent
systemd-networkd-wait-online.service hanging the systemd boot process.
You may need to disable ManageForeignRoutingPolicyRules option in
/etc/systemd/networkd.conf, since it will flush all your custom
rules that are not configured in .network units, such as the rules added
by ip rule command.
[Network]
ManageForeignRoutingPolicyRules=no
Enable the services.
(root)# systemctl enable systemd-networkd.service
(root)# systemctl enable systemd-resolved.service
Zram
We have lage memory storage nowadays, so just put the swap into RAM using zram.
Create /etc/systemd/zram-generator.conf, the size is in MiB.
[zram0]
zram-size = min(ram, 8192)
compression-algorithm = zstd
Dracut
Remeber I mentioned setting LUKS with a preset key file? This is the right time. We use dracut to generate initramfs image, and pack the key file into it.
Apply key file to encrypted partition.
cryptsetup luksAddKey /dev/disk/by-partlabel/ROOTPART /etc/cryptsetup-keys.d/root.key
Create /etc/dracut.conf.d/dracut.conf.
hostonly="yes"
enhanced_cpio="yes"
compress="cat"
do_strip="no"
install_optional_items+=" /etc/cryptsetup-keys.d/root.key "
Systemd-Boot
bootctl install
systemctl enable systemd-boot-update.service
Note: Debian/Ubuntu and Fedora need some extra work to continue, refer to Debian Fedora section then jump back.
Since the kernel and initramfs image will be installed to /boot/ by default,
we need to copy them to our
EFI system partition
manually and create
systemd hooks
to update them automatically.
(root)# mkdir -p /efi/boota
(root)# cp -a /boot/vmlinuz-linux /efi/boota/
(root)# cp -a /boot/initramfs-linux.img /efi/boota/
Create /etc/systemd/system/efistub-update.path.
[Unit]
Description=Copy EFISTUB Kernel to EFI system partition
[Path]
PathChanged=/boot/initramfs-linux.img
[Install]
WantedBy=multi-user.target
WantedBy=system-update.target
Create /etc/systemd/system/efistub-update.service.
[Unit]
Description=Copy EFISTUB Kernel to EFI system partition
[Service]
Type=oneshot
ExecStart=/usr/bin/cp -af /boot/vmlinuz-linux /efi/boota/
ExecStart=/usr/bin/cp -af /boot/initramfs-linux.img /efi/boota/
Enable the units.
(root)# systemctl enable efistub-update.{path,service}
Create bootloader entry /efi/loader/entries/boota.conf.
title Arch Linux
linux /boota/vmlinuz-linux
initrd /boota/initramfs-linux.img
options rootflags=subvol=@ quiet splash
To use
BTRFS subvolume as root
mountpoint, use kernel parameter rootflags=subvol=@,
or you would get an error “Failed to start Switch Root” when booting up.
Edit /efi/loader/loader.conf.
default boota.conf
timeout 0
editor no
timeout 0 means the boot menu will not be displayed by default,
and the system will immediately boot into the default entry.
To reveal the boot menu in this scenario, a key needs to be pressed and
held down during the boot process, before systemd-boot initializes.
The recommended key for this action is the space bar.
Other keys may also work, but space bar is widely suggested.
Note: If disk partitions were not following the
Discoverable Partitions Specification
, which means root partition would not be discovered and auto mounted,
booting system would stuck at
a start job is running for /dev/gpt-auto-root and timeout.
To fix this,
name root partition in kernel parameters
using rd.luks.name.
options rd.luks.name=<UUID>=root root=/dev/mapper/root rootflags=subvol=@
Debian Fedora
<-> Live ISO
<-> Repo Mirror
Debian: Check Debian Mirrors (worldwide),
then edit /etc/apt/sources.list.
Ubuntu: Check Mirrors : Ubuntu
then edit /etc/apt/sources.list.
Fedora: Check MirrorManager,
then edit /etc/yum.repos.d/fedora.repo.
<-> Base System
Debian/Ubuntu: Debootstrap:
(root)# debootstrap --include=\
linux-image-amd64,non-free-firmware,btrfs-progs,dracut,\
systemd-zram-generator,systemd-boot,neovim,iwd \
stable /mnt http://deb.debian.org/debian/
The Repo URL for Ubuntu is http://archive.ubuntu.com/ubuntu/
Fedora: DNF:
(root)# dnf --use-host-config --releasever=43 --installroot=/mnt group install core
(root)# dnf --use-host-config --releasever=43 --installroot=/mnt install \
kernel linux-firmware btrfs-progs dracut zram-generator systemd-boot neovim iwd
Microcode packages:
Fedora: AMD amd-ucode-firmware, Intel microcode_ctl
Debian: AMD amd64-microcode, Intel intel-microcode
<-> Systemd-Boot
Unlike Arch Linux, Debian and Fedora will trigger systemd’s kernel-install(8) to copy initramfs and kernel images to ESP partition and generate boot entry automatically when using dracut and systemd-boot. Since we want to maintain this process in our own way for the flexibility, we need to disable their kernel-install plugins and write our own.
(root)# ln -s /dev/null /etc/kernel/install.d/50-dracut.install
(root)# ln -s /dev/null /etc/kernel/install.d/90-loaderentry.install
Create /etc/kernel/install.d/60-bootstub.install, make it executable.
#!/bin/bash
set -e
[[ ${#} == 4 ]] || exit 0
_command="${1}"
_kernel_verion="${2}"
_dest_dir="/boot"
_kernel_image="${4}"
[[ "${_command}" == "add" ]] || exit 0
[[ -f "${_kernel_image}" ]] || exit 1
cp -f "${_kernel_image}" "${_dest_dir}/vmlinuz-linux"
dracut -f \
--kver "${_kernel_verion}" \
--kernel-image "${_kernel_image}" \
"${_dest_dir}/initramfs-linux.img"
chmod 600 "${_dest_dir}/initramfs-linux.img"
<-> Systemd-Networkd
For Debian you need to move out /etc/network/interfaces according to
SystemdNetworkd - Debian Wiki
(root)# mv /etc/network/interfaces /etc/network/interfaces.old