Installing Arch Linux on Thinkpad X1 Carbon 12th Gen


Create USB disk to boot the installer from

First, I downloaded the ISO with a torrent obtained from https://archlinux.org/download/. I used BitTorrent client Deluge.

I then used dd to write the ISO to the flash drive. I figured out the /dev/<device> by running dmesg as I plugged in the USB disk:

# dmesg
..
[40163.683166] usb 3-7: new high-speed USB device number 18 using xhci_hcd
..
[40166.445458] sd 0:0:0:0: [sda] Attached SCSI removable disk

The sda bit was all I needed, I could then run dd to write the file to the disk:

# dd bs=4M \
     if=tmp/archlinux-2024.05.01-x86_64.iso \
     of=/dev/sda conv=fsync \
     oflag=direct \
     status=progress

Getting X1 to boot from the USB

Enter the BIOS/UEFI by hitting F2 (or Esc, I hammered on both of them) to enter the BIOS/UEFI configuration and disable Secure Boot (which requires the OS to be signed my Microsoft's key.

Then reboot again and hit F12 to get the boot menu. From there, select the USB disk.

Let the fun begin

On my old computer next to the new one, I had the Arch Linux installation guide which steps you through the different commands you must type to get the bare boned OS installed.

Network in the installer

I set up the wireless card with:

# iwctl
station wlan0 connect my-ssid
<pasword>
exit
# dhclient -v wlan0

Partioning the harddrive

# cfisk /dev/nme0n1

Full disk encryption

https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system#LUKS_on_a_partition

I opted for the bootloader I've used the last 15 years, GRUB. I did forget to Generatethe main GRUB configuration file, so be sure to do that.

Boot loader

# pacman -S grub efibootmgr

First, get the UUIDs of the encrypted partition, /dev/nvme0n1p2, and the decrypted, mounted partition, /dev/mapper/root:

# blkid
/dev/nvme0n1p1: UUID="D0BB-3264" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="26cb7637-9a3e-4be1-b477-07af31946dee"
/dev/nvme0n1p2: UUID="0ac79fb6-12f5-4d7b-9501-f7c185f94c00" TYPE="crypto_LUKS" PARTUUID="3300b64d-1e98-443f-a59e-8c9fae104d21"
/dev/mapper/root: UUID="4941d62c-51af-4dbb-8390-51f83411edec" BLOCK_SIZE="4096" TYPE="ext4"

In /etc/default/grub, edit these two variables:

GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3
cryptdevice=UUID=0ac79fb6-12f5-4d7b-9501-f7c185f94c00:root
root=/dev/mapper/root"

GRUB_ENABLE_CRYPTODISK=y

Note, the cryptdevice is UUID of the encrypted partition itself, whereas what's in /etc/fstab is the UUID of the decrypted and mounted partition:

$ grep ext4 /etc/fstab
UUID=4941d62c-51af-4dbb-8390-51f83411edec / ext4 rw,relatime 0 1

Every time after editing /etc/default/grub, re-generate the runtime conf:

# grub-mkconfig -o /boot/grub/grub.cfg

In the generated /boot/grub/grub.cfg, you can see the two UUIDs of the encrypted disk, 0ac79fb6-12f5-4d7b-9501-f7c185f94c00, and the decrypted block of the root partiation, 4941d62c-51af-4dbb-8390-51f83411edec:

    linux   /vmlinuz-linux root=UUID=4941d62c-51af-4dbb-8390-51f83411edec rw  loglevel=3 cryptdevice=UUID=0ac79fb6-12f5-4d7b-9501-f7c185f94c00:root root=/dev/mapper/root

The final step, is to install the bootloader binary itself. Normally, you only need this comamnd once, this is to install the grub boot loader binary into the right place on the disk. Whatever you put in --bootloader-id will show up in the UEFI/BIOS in the computer as well as in the machine boot menu that you typically access with F12

# grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=Arch

No sounds

Got a vital clue from:

$ journalctl -b

which said (search for both snd and sof):

May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3: hda codecs found, mask 5
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3: using HDA machine driver skl_hda_dsp_generic now
..
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3: SOF firmware and/or topology file not found.
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3: Supported default profiles
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3: - ipc type 1 (Requested):
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3:  Firmware file: intel/sof-ipc4/mtl/sof-mtl.ri
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3:  Topology file: intel/sof-ace-tplg/sof-hda-generic-2ch.tplg
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3: Check if you have 'sof-firmware' package installed.
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3: Optionally it can be manually downloaded from:
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3:    https://github.com/thesofproject/sof-bin/
May 31 09:31:27 mithrandir kernel: sof-audio-pci-intel-mtl 0000:00:1f.3: error: sof_probe_work failed err: -2

I was sure I had already installed sof-firmware, but you know what? I hadn't! This was shortly remedied with:

# pacman -S sof-firmware
# reboot

Fonts in Emacs

To get the fonts for the nice icons on the mode line that doom-modeline-mode puts on, I had to install the package:

# pacman -S ttf-nerd-fonts-symbols-mono

LSP in Emacs

M-x lsp-mode RET gave the error:

make-process--with-editor-process-filter: Doing vfork: No such file or directory

I found no good way of getting Emacs or lsp to tell me exactly what wasn't found, so I had to bisect my .emacs manually. It turned out that

(setq lsp-java-java-path "/usr/lib/jvm/java-21-openjdk/bin/java")

Pointed to a non-existing JDK. After installing the needed version, LSP worked as it should. I've suggested an improvement in lsp-java to help future Emacs users.

Firefox cannot display Chinese characters

Both Simplified Chinese and Traditional Chinese characters showed up as boxes:

Firefox showing text where Chinese characters are showed as boxes

Installing this font:

# pacman -S wqy-microhei

and restarting Firefox resolved the issue. I can now read Simplified Chinese and Traditional Chinese to my heart full content.

libvirtd / virt-manager VMs don't get IP

Turns out dnsmasq wasn't running:

# systemctl status dnsmasq
× dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
     Loaded: loaded (/usr/lib/systemd/system/dnsmasq.service; disabled; preset: disabled)
     Active: failed (Result: exit-code) since Mon 2024-06-10 09:33:37 CEST; 1min 2s ago
..

Starting it again gave a clue:

# systemctl start dnsmasq
# journalctl -u dnsmasq -f
..
Jun 10 09:38:24 mithrandir dnsmasq[29904]: failed to create listening socket for port 53: Address already in use

Indeed:

# netstat --tcp -nlp | grep 53
tcp        0      0 192.168.122.1:53        0.0.0.0:*               LISTEN      1967/dnsmasq

So I'd better close that one down. but first, I wanted to find out what started it:

$ ps -u -p 1967
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
nobody      1967  0.0  0.0  13916  1968 ?        S    Jun07   0:00 /usr/bin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/defaul

Now, that looks very daemony, running as user nobody and all, but that's something of a relic from LSB and with systemd, running with DynamicUser is a better option (or indeed any dedicated user, to ensure that the same user, no matter how unprivileged, is shared across processes).

Killing that method with:

# kill -9 1967

And starting dnsmasq again, looked so much better:

# journalctl -u dnsmasq -f
..
Jun 10 09:47:01 mithrandir systemd[1]: Starting dnsmasq - A lightweight DHCP and caching DNS server...
Jun 10 09:47:01 mithrandir dnsmasq[31222]: dnsmasq: syntax check OK.
Jun 10 09:47:01 mithrandir systemd[1]: Started dnsmasq - A lightweight DHCP and caching DNS server.
Jun 10 09:47:01 mithrandir dnsmasq[31223]: started, version 2.90 cachesize 150
Jun 10 09:47:01 mithrandir dnsmasq[31223]: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset nftset auth cryptohash DNSSEC loop-detect inotify dumpfile
Jun 10 09:47:01 mithrandir dnsmasq[31223]: DBus support enabled: connected to system bus
Jun 10 09:47:01 mithrandir dnsmasq[31223]: reading /etc/resolv.conf
Jun 10 09:47:01 mithrandir dnsmasq[31223]: using nameserver 10.0.128.100#53
Jun 10 09:47:01 mithrandir dnsmasq[31223]: using nameserver 10.0.128.110#53
Jun 10 09:47:01 mithrandir dnsmasq[31223]: using nameserver 8.8.8.8#53
Jun 10 09:47:01 mithrandir dnsmasq[31223]: read /etc/hosts - 0 names

The VM didn't get an IP via DHCP directly, so I tried retarting it. Still no cigar. Then, I tried to restart libvirtd itself:

# systemctl restart libvirtd

I thought I was homefree, but alas:

# journalctl -u libvirtd -f
..
Jun 10 09:50:28 mithrandir systemd[1]: Started libvirt legacy monolithic daemon.
Jun 10 09:50:29 mithrandir dnsmasq[31614]: failed to create listening socket for 192.168.122.1: Address already in use
Jun 10 09:50:29 mithrandir dnsmasq[31614]: FAILED to start up
Jun 10 09:50:29 mithrandir libvirtd[31561]: libvirt version: 10.4.0
Jun 10 09:50:29 mithrandir libvirtd[31561]: hostname: mithrandir
Jun 10 09:50:29 mithrandir libvirtd[31561]: internal error: Child process (VIR_BRIDGE_NAME=virbr0 /usr/bin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/lib/libvirt/libvirt_leaseshelper) unexpected exit status 2:
                                            dnsmasq: failed to create listening socket for 192.168.122.1: Address already in use
Jun 10 09:51:57 mithrandir libvirtd[31561]: Domain id=2 name='debian12' uuid=1181da1e-8e70-4a45-831b-ff6a61de639a is tainted: host-cpu

I then got an idea: Perhaps the dnsmasq service should be disabled? Since it looks like libvirtd is a service that starts multiple services, including dnsmasq:

# systemctl stop dnsmasq
# systemctl disable dnsmasq
# systemctl restart libvirtd

Now, that looked better:

# journalctl -u libvirtd -f
..
Jun 10 09:55:01 mithrandir systemd[1]: Started libvirt legacy monolithic daemon.
Jun 10 09:55:02 mithrandir dnsmasq[32243]: started, version 2.90 cachesize 150
..
Jun 10 09:55:02 mithrandir dnsmasq-dhcp[32243]: DHCP, IP range 192.168.122.2 -- 192.168.122.254, lease time 1h
Jun 10 09:55:02 mithrandir dnsmasq-dhcp[32243]: DHCP, sockets bound exclusively to interface virbr0

But my VMs were still not getting an IP.

It turned out my firewall was in the way. On my VM, I checked if I could access dnsmasq on the host machine:

$ nc -zv 192.168.122.1 53
^C

Thus, I needed to punch a hole in my firewall to allow VMs on my libvirtd created network to request IPs from dnsmasq:

# ufw allow from 192.168.122.0/24 to 192.168.122.1 port 53 proto tcp

Now, my VMs could finally request access the DHCP server:

# nc -zv 192.168.122.1 53
Connection to 192.168.122.1 53 port [tcp/domain] succeeded!

Licensed under CC BY Creative Commons License ~ ✉ torstein.k.johansen @ gmail ~ 🐘 @skybert@hachyderm.io ~ 🐦 @torsteinkrause