Up front: Calamares (Kubuntu installer) didn't cut it. So I went the well-trodden path of manual installation combined with later package selection that would mimick what Calamares would have picked.
These instructions make assumptions. The main assumption is, that you dear reader, are not a numpty. Therefore use your own judgment and adjust arguments (especially device paths) to your own needs. Don't blame me for doing stupid things on your end. Got enough stupid decisions of my own to deal with ...
- you may want to enter the system setup (F1) and enable secure boot
- you may want to live-boot once, run
fwupdmgr updateand then reboot to have the latest UEFI version
- Boot the Kubuntu 26.04 live media
- Open the terminal and
sudo su -; pretty much everything below requires superuser rights, so we won't bother withsudoin front of every invocation - Update the live system, prepare for convenient use:
apt update apt install -y mc unzip tmux git make gdisk debootstrap vim-nox openssh-server apt full-upgrade git clone https://github.com/assarbad/dotfiles .dotfiles make -C .dotfiles TGTDIR=/root install - time to
sshinto the system, useip aor whatever else rocks your boat on the live-booted system to determine the correct IP addressssh root@1.2.3.4- then I like to start Tmux with my own
muxalias, but YMMV
- Optionally: if you are working with a system using an NVMe, install:
apt install sedutil nvme-clinvme id-ctrl /dev/nvme0n1sedutil-cli --query /dev/nvme0n1
- Set up partitions using
gdisk- UEFI ESP: partition 1,
512M, typeEF00 /boot: partition 2,2G, type8309- remainder of space goes into a third unformatted partition: partition 3, accept start and end with Enter, type
8309 - for good measure you may want to run
partprobe /dev/nvme0n1
- UEFI ESP: partition 1,
- Format the unencrypted ESP partition:
mkfs.fat -n ESP -F 32 /dev/nvme0n1p1 - Set up LUKS2 volumes:
- Set up LUKS1 volume for
/boot:cryptsetup luksFormat --type luks1 --label=BOOT.LUKS /dev/nvme0n1p2(I ran into compatibility issues with GRUB, so this was the cure: LUKS1 instead of LUKS2) - Set up LUKS2 volume for the remainder:
cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --key-size 512 --hash sha256 --pbkdf argon2id --pbkdf-memory $((1024*1024)) --pbkdf-parallel 4 --iter-time 2500 --label=REMAINDER /dev/nvme0n1p3
(this assumes Bash with its arithmetic operations)- Another option is to use
--pbkdf-parallel $(nproc)above ...
- Another option is to use
- Set up LUKS1 volume for
- Open the LUKS2 volumes:
cryptsetup open /dev/nvme0n1p2 cryptbootcryptsetup open /dev/nvme0n1p3 cryptmain(if rerunning this step on a pre-partitioned device:partprobe /dev/mapper/cryptmain)
- Format the
/bootpartition:mkfs.ext4 -m 1 -L BOOT /dev/mapper/cryptboot - Partition the
/dev/mapper/cryptmaincontainer, for me that's a root and a data partition, again use your judgment for your use casegdisk /dev/mapper/cryptmainpartprobe /dev/mapper/cryptmain- Format the partitions:
mkfs.btrfs -fKL ROOT --checksum xxhash /dev/mapper/cryptmain1mkfs.btrfs -fKL DATA --checksum xxhash /dev/mapper/cryptmain2
The result in my case looks like this:
# sgdisk -p /dev/nvme0n1
# ...
Number Start (sector) End (sector) Size Code Name
1 2048 1050623 512.0 MiB EF00
2 1050624 5244927 2.0 GiB 8309 Linux LUKS
3 5244928 1953523711 929.0 GiB 8309 Linux LUKS
# sgdisk -p /dev/mapper/cryptmain
#...
Number Start (sector) End (sector) Size Code Name
1 2048 268437503 128.0 GiB 8300 Linux filesystem
2 268437504 1948243967 801.0 GiB 8300 Linux filesystem
If the LUKS2 volumes are opened, it looks like this when viewed from the live-booted system:
# lsblk --exclude 7 -o NAME,MAJ:MIN,SIZE,RO,TYPE,FSTYPE,LABEL,MOUNTPOINT
NAME MAJ:MIN SIZE RO TYPE FSTYPE LABEL MOUNTPOINT
sda 8:0 0B 0 disk
sr0 11:0 4.7G 0 rom iso9660 Kubuntu 26.04 amd64 /cdrom
nvme0n1 259:0 931.5G 0 disk
├─nvme0n1p1 259:1 512M 0 part vfat ESP
├─nvme0n1p2 259:2 2G 0 part crypto_LUKS BOOT.LUKS
│ └─cryptboot 252:0 2G 0 crypt ext4 BOOT
└─nvme0n1p3 259:3 929G 0 part crypto_LUKS REMAINDER
└─cryptmain 252:1 929G 0 crypt
... after partprobe /dev/mapper/cryptmain it looks like this then:
# lsblk --exclude 7 -o NAME,MAJ:MIN,SIZE,RO,TYPE,FSTYPE,LABEL,MOUNTPOINT
NAME MAJ:MIN SIZE RO TYPE FSTYPE LABEL MOUNTPOINT
sda 8:0 0B 0 disk
sr0 11:0 4.7G 0 rom iso9660 Kubuntu 26.04 amd64 /cdrom
nvme0n1 259:0 931.5G 0 disk
├─nvme0n1p1 259:1 512M 0 part vfat ESP
├─nvme0n1p2 259:2 2G 0 part crypto_LUKS BOOT.LUKS
│ └─cryptboot 252:0 2G 0 crypt ext4 BOOT
└─nvme0n1p3 259:3 929G 0 part crypto_LUKS REMAINDER
└─cryptmain 252:1 929G 0 crypt
├─cryptmain1 252:2 128G 0 part btrfs ROOT
└─cryptmain2 252:3 801G 0 part btrfs DATA
Up front we want to prepare the btrfs volumes on the root partition:
mount -m -o subvolid=5 /dev/mapper/cryptmain1 /target(mount root volume by ID)btrfs subvolume create /target/@our future/btrfs subvolume create /target/@homeour future/homeumount /target
Proceed as follows to mount the volumes as needed:
mount -m -o subvol=@ /dev/mapper/cryptmain1 /targetmount -m -o subvol=@home /dev/mapper/cryptmain1 /target/homemount -m /dev/mapper/cryptboot /target/bootmount -m /dev/nvme0n1p1 /target/boot/efi(ESP)
# lsblk --exclude 7 -o NAME,MAJ:MIN,SIZE,RO,TYPE,FSTYPE,LABEL,MOUNTPOINT
NAME MAJ:MIN SIZE RO TYPE FSTYPE LABEL MOUNTPOINT
sda 8:0 0B 0 disk
sr0 11:0 4.7G 0 rom iso9660 Kubuntu 26.04 amd64 /cdrom
nvme0n1 259:0 931.5G 0 disk
├─nvme0n1p1 259:1 512M 0 part vfat ESP /target/boot/efi
├─nvme0n1p2 259:2 2G 0 part crypto_LUKS BOOT.LUKS
│ └─cryptboot 252:0 2G 0 crypt ext4 BOOT /target/boot
└─nvme0n1p3 259:3 929G 0 part crypto_LUKS REMAINDER
└─cryptmain 252:1 929G 0 crypt
├─cryptmain1 252:2 128G 0 part btrfs ROOT /target/home
└─cryptmain2 252:3 801G 0 part btrfs DATA
Very simply (adjust the URL to a local mirror):
debootstrap --arch=amd64 resolute /target http://de.archive.ubuntu.com/ubuntu/
Personally I do this in Tmux, so I sometimes have several windows ("tabs") open to do things in parallel.
To be able to do most things, including installations beyond bootstrapping, you should run the following to bind-mount some paths from the host (live-booted system) into the chroot target environment:
for d in proc sys dev run; do mount -m --rbind --make-rslave /$d /target/$d; done
The above for loop is crucial to be able to do things like installing the boot-loader while inside the chroot.
NB: do this only once. No need to do this prior to every chroot to hop in.
If you're unsure, check with mount|grep -E '/target/(proc|sys|dev|run)'|wc -l and it should show a number above 0 ;)
After this point you can hop into the target environment (and out again) at will with:
chroot /target /bin/bash
The /etc/apt/sources.list file inside the chroot should be edited to read something like:
deb http://de.archive.ubuntu.com/ubuntu/ resolute main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu/ resolute-security main restricted universe multiverse
deb http://de.archive.ubuntu.com/ubuntu/ resolute-updates main restricted universe multiverse
Easiest method (assuming your live-booted system is the same Kubuntu version you want to install): cp -fa /etc/apt/sources.list /target/etc/apt/sources.list ¯\(ツ)/¯ ... otherwise use your favorite editor.
Note that 26.04 == resolute and again you may want to pick a local mirror.
If everything is in place, we can now switch into the target environment and from there:
apt update- We'll ignore the message:
Notice: Some sources can be modernized. Run 'apt modernize-sources' to do so.altogether
- We'll ignore the message:
- Optionally:
apt full-upgrade - Installing Kubuntu:
apt install --install-recommends kubuntu-desktop - We'll want to boot a Linux kernel, too:
apt install linux-image-generic dracut shim-signed cryptsetup btrfs-progs kpartx- enable cryptodisk:
echo "GRUB_ENABLE_CRYPTODISK=y"|tee -a /etc/default/grub- Actually I did:
printf "GRUB_ENABLE_CRYPTODISK=y\nGRUB_DISABLE_OS_PROBER=true\n"|tee -a /etc/default/grub
- Actually I did:
- install GRUB:
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Ubuntu
- enable cryptodisk:
- Optional: I always want phased updates directly
printf "Update-Manager::Always-Include-Phased-Updates;\nAPT::Get::Always-Include-Phased-Updates;\n"|tee /etc/apt/apt.conf.d/99-Phased-Updates - Set a host name:
echo "yourhostname"|tee /etc/hostname- Make it known in
hosts:export NEW_HOSTNAME=$(cat /etc/hostname) sed '/^127\.0\.0\.1/s/$/ '"$NEW_HOSTNAME"'/g; /^::1\s/s/$/ '"$NEW_HOSTNAME"'/g' /etc/hosts - Optionally:
hostnamectl hostname yourhostname
- Make it known in
- Set timezone:
ln -sf /usr/share/zoneinfo/Etc/UTC /etc/localtime(ordpkg-reconfigure tzdata) - Configure locale:
update-locale LANG=en_US.UTF-8followed bylocale-gen(ordpkg-reconfigure locales) - Create unprivileged user:
adduser oliverusermod -aG sudo,adm,cdrom,dip,plugdev,lpadmin,users oliver
- Populate
/etc/crypttab:for luks in cryptboot:/dev/nvme0n1p2 cryptmain:/dev/nvme0n1p3; do printf "%s% 48s - luks,password-echo=masked,fido2-device=auto,nofail,discard\n" "${luks%%:*}" "UUID=$(cryptsetup luksUUID "${luks##*:}")"; done|tee -a /etc/crypttab - Populate
/etc/fstabwith something along the lines of (useblkidto figure out UUID values):# <fs> <mount point> <type> <options> <dump> <pass> UUID=... / btrfs defaults,subvol=@,noatime,discard,compress-force=zstd:6 0 1 UUID=... /home btrfs defaults,subvol=@home,noatime,discard,compress-force=zstd:6 0 1 UUID=... /boot ext4 defaults,noatime,discard 0 2 PARTUUID=... /boot/efi vfat umask=0077,noatime,discard 0 1 - Needed so that
/etc/crypttabchanges take effect:update-grub - Optional: I like Samba/CIFS for file sharing:
apt install cifs-utils sambausermod -aG sambashare oliver(oradduser oliver sambashare)
- Optional: I like using
etckeeperwith Git:apt install etckeeper git- Optionally: disable signing for
sudoscenarios:printf '#!/bin/sh\nset -e\ngit config --local commit.gpgsign false\n'|tee /etc/etckeeper/commit.d/01disable-signing && chmod +x /etc/etckeeper/commit.d/01disable-signing
Dracut has replaced the older method of creating an initramfs. But Dracut tries to be extra-smart, thereby making it harder to work with LUKS volumes that don't contain the extra LVM layer below. You may have noticed that I created my devices straight in the block device of the unlocked LUKS volume (cryptmain). Without rereading partition information the kernel won't recognize the contained partitions.
Now Dracut doesn't even care if a given LUKS volume is needed for the boot, it will simply put every LUKS volume into the cmdline and then the initrd will attempt to unlock that. For me that meant it would attempt to also unlock /boot again, which was already unlocked by GRUB for the boot. Alas, Dracut also behaves weirdly when it comes to prompting several times for the LUKS unlocking. With Plymouth it's hard to tell what I am unlocking, which may work nice when using the same passphrases and what not, but is a bad deal when you don't.
NB: I went through several iterations and it would be an understatement to say that this was a painful process. In the end I settled for the solution below. The underlying issue was that Dracut didn't issue a reread of the partition table in the unlocked LUKS block device, thereby preventing the real / from being mounted successfully. So I got dropped into the emergency shell numerous times.
This is the stansdard module-setup.sh for any Dracut module. I use 90rereadpart as the module name.
This script is the work horse. It uses cryptmain and cryptboot from /etc/crypttab to filter out the cryptboot (GRUB helps me unlock that) and making sure that the partition info gets reread in the unlocked LUKS block device.
#!/bin/bash
# vim: set autoindent smartindent ts=4 sw=4 sts=4 et filetype=sh:
command -v label_uuid_to_dev > /dev/null || label_uuid_to_dev() { # from modules.d/80base/dracut-lib.sh
local _dev
_dev="${1#block:}"
case "$_dev" in
LABEL=*)
echo "/dev/disk/by-label/$(echo "${_dev#LABEL=}" | sed 's,/,\\x2f,g;s, ,\\x20,g')"
;;
PARTLABEL=*)
echo "/dev/disk/by-partlabel/$(echo "${_dev#PARTLABEL=}" | sed 's,/,\\x2f,g;s, ,\\x20,g')"
;;
UUID=*)
echo "/dev/disk/by-uuid/${_dev#UUID=}"
;;
PARTUUID=*)
echo "/dev/disk/by-partuuid/${_dev#PARTUUID=}"
;;
*)
echo "$_dev"
;;
esac
}
check() { return 0; }
depends() { echo crypt dm; }
_remove_by_mapper_name() {
# We need these dependencies to be gone from the initrd!
(
set -ex
find "${initdir}" ! -type d -name "*$1.*" -print -exec rm -f -- {} +
find "${initdir}" -type d -name "*$1.*" -print -exec rm -rf -- {} +
sed -i "/$1/d" "${initdir}/usr/lib/dracut/hostonly-files"
) >&2
}
# called by dracut
install() {
inst_multiple -o blockdev udevadm partx partprobe kpartx gdisk sgdisk
inst_simple "$moddir/partprobe-cryptmain.service" "$systemdsystemunitdir/partprobe-cryptmain.service"
systemctl --root="$initdir" add-wants initrd-root-device.target partprobe-cryptmain.service >&2
local _mapper _dev _luksfile _luksoptions _uuid_dev _uuid
local -a INITRD_LUKS_NEEDED=()
local -a INITRD_LUKS_SUPPRESSED=()
if ! cp -a "${initdir}"/etc/crypttab "${initdir}"/etc/crypttab.orig; then
echo "Failed to backup crypttab" >&2
else
while read -r _mapper _dev _luksfile _luksoptions || [[ -n $_mapper ]]; do
# ignore comments
[[ "$_mapper" =~ ^[[:space:]]*# ]] && continue
# ignore paths followed by a device specification
if [[ $_luksfile == *":"* ]]; then
continue
fi
_uuid_dev=$(label_uuid_to_dev "$_dev")
_uuid=${_uuid_dev##*/}
case "$_mapper" in
cryptboot)
_remove_by_mapper_name "$_mapper"
INITRD_LUKS_SUPPRESSED+=("$_uuid")
;;
cryptmain)
# Write to filtered crypttab
printf "%s %s %s %s\n" "$_mapper" "$_dev" "$_luksfile" "$_luksoptions"
INITRD_LUKS_NEEDED+=("$_uuid")
;;
esac
done < "${initdir}"/etc/crypttab.orig > "$initdir"/etc/crypttab
# Suppress cryptboot
for _uuid in "${INITRD_LUKS_SUPPRESSED[@]}"; do
sed -i "/$_uuid/d" "${initdir}/etc/block_uuid.map"
done
for _uuid in "${INITRD_LUKS_NEEDED[@]}"; do
printf " rd.luks.uuid=luks-%s\n" "$_uuid"
done > "${initdir}/etc/cmdline.d/20-crypt.conf"
fi
# blkid -c /dev/null -t TYPE=crypto_LUKS
}Service unit for rereading the partition information. This is the glue between unlocking the cryptmain LUKS volume and finalizing the initrd phase before pivoting to the real root (initrd-root-device.target).
# /etc/systemd/system/partprobe-cryptmain.service (installed into initrd)
[Unit]
Description=Rescan partitions on cryptmain after LUKS unlock
After=systemd-cryptsetup@cryptmain.service
Requires=systemd-cryptsetup@cryptmain.service
Before=initrd-root-device.target
DefaultDependencies=no
[Service]
Type=oneshot
ExecStart=/sbin/partprobe /dev/mapper/cryptmain
ExecStartPost=/bin/udevadm settle
RemainAfterExit=yes
[Install]
WantedBy=initrd-root-device.target
/mounts from insidecryptmainafter rereading the partition information- initrd pivots to the real root
/bootdoes not get unlocked and mounted into the real root- I addressed this by creating an extra slot in the LUKS1 volume holding the
/bootpartition and using a key file (contained in the real root) to unlock it once again, postponing the mounts via systemdx-systemd.after=options (see below).
- I addressed this by creating an extra slot in the LUKS1 volume holding the
UUID=... /boot ext4 defaults,noatime,discard,x-systemd.after=systemd-cryptsetup@cryptboot.service 0 2
PARTUUID=... /boot/efi vfat umask=0077,noatime,discard,x-systemd.after=boot.mount 0 1
apt install -y mc unzip tmux git make gdisk debootstrap vim-nox openssh-server ripgrep fd-find && git clone https://github.com/assarbad/dotfiles .dotfiles && make -C .dotfiles TGTDIR=/root install
# Separate commands for the "one-liner" above
apt install -y mc unzip tmux git make gdisk debootstrap vim-nox openssh-server ripgrep fd-find
git clone https://github.com/assarbad/dotfiles .dotfiles
make -C .dotfiles TGTDIR=/root install
apt full-upgrade -y
cryptsetup open /dev/nvme0n1p2 cryptboot; cryptsetup open /dev/nvme0n1p3 cryptmain; partprobe /dev/mapper/cryptmain
# Separate commands for the "one-liner" above
cryptsetup open /dev/nvme0n1p2 cryptboot
cryptsetup open /dev/nvme0n1p3 cryptmain
partprobe /dev/mapper/cryptmain
mount -m -o subvol=@ /dev/mapper/cryptmain1 /target && mount -m -o subvol=@home /dev/mapper/cryptmain1 /target/home && mount -m /dev/mapper/cryptboot /target/boot && mount -m /dev/nvme0n1p1 /target/boot/efi && for d in proc sys dev run; do mount -m --rbind --make-rslave /$d /target/$d; done && chroot /target /bin/bash
# Separate commands for the "one-liner" above
mount -m -o subvol=@ /dev/mapper/cryptmain1 /target
mount -m -o subvol=@home /dev/mapper/cryptmain1 /target/home
mount -m /dev/mapper/cryptboot /target/boot
mount -m /dev/nvme0n1p1 /target/boot/efi
for d in proc sys dev run; do mount -m --rbind --make-rslave /$d /target/$d; done
chroot /target /bin/bash
# inside
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Ubuntu
grub-mkconfig -o /boot/grub/grub.cfg
dracut -fv
update-grub
test -d /initrd || mkdir /initrd; rm -rf -- /initrd/*; cd /initrd && lsinitrd --unpack /boot/initrd.img
CONUSER=kubuntu; printf "[AC][Display]\nDimDisplayIdleTimeoutSec=900\nTurnOffDisplayIdleTimeoutSec=3600\n\n[AC][SuspendAndShutdown]\nAutoSuspendAction=0\nLidAction=0\n" > /home/$CONUSER/.config/powerdevilrc; chown $CONUSER: /home/$CONUSER/.config/powerdevilrc; sudo -u $CONUSER bash -c 'env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus systemctl --user restart plasma-powerdevil.service'
# Separate commands for the "one-liner" above
CONUSER=kubuntu
printf "[AC][Display]\nDimDisplayIdleTimeoutSec=900\nTurnOffDisplayIdleTimeoutSec=3600\n\n[AC][SuspendAndShutdown]\nAutoSuspendAction=0\nLidAction=0\n" > /home/$CONUSER/.config/powerdevilrc
chown $CONUSER: /home/$CONUSER/.config/powerdevilrc
sudo -u $CONUSER bash -c 'env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus systemctl --user restart plasma-powerdevil.service'
The crux here is to run as the user interactively logged on, which is kubuntu from Kubuntu installation media.
udevadm info /dev/mapper/<name>udevadm test /run/udev/rules/...journalctl -b | grep -i udevudevadm control --log-priority=debugudevadm trigger --action=add /dev/mapper/<name>or--type=devicesappropriatelyjournalctl -b | tail -100udevadm trigger --action=change /dev/mapper/<name>udevadm settlecryptsetup luksDump /dev/<partition> | grep -A5 Tokens: