Running Arch Linux on the Framework Laptop 13
This article sums up why and how I run Arch Linux on my new Framework Laptop 13, which I received on the 3rd of this month.
I’ve been busy getting up+running and getting to know the device. TL;DR; I’m extremely impressed and very happy with this laptop - it is by far one of the best devices I’ve owned in a long time. It is near-silent, It uses ~3watts at idle, it’s fast, it’s sturdy and it looks good!
This is going to be quite a long article, so here’s a Table of Contents:
- Why Arch Linux
- BIOS settings
- Basic installation
- Packages I install
- Additionally install when you want dynamic application of powermanagement settings
- Additionally install when you want to use dracut instead of mkinitcpio
- Additionally install when you use X11 instead of Wayland and want gestures
- Additionally install on devices with fingerprint reader
- Additionally install on devices with Intel graphics
- Additionally install on devices with AMD graphics
- Additionally install on devices with NVidia graphics
- Installing lib32 package equivalents (optional)
- Service configuration
- How I use AUR
- Final thoughts
Why Arch Linux
Prior to my Framework Laptop adventures, I’ve been planning to move to Arch Linux for a while. I’m a long-time Linux desktop user. I started out with those Red Hat CD-ROMs you’d buy at your local bookshop (this was around ‘96). I fooled around with SCO UnixWare, had a whole period of SGI IRIX after that and then some distro-hopping to Debian, Fedora, Arch Linux (in 2007), Gentoo and Void Linux, more-or-less in that order.
The last two-ish years I’ve been running on Void Linux, which I still strongly recommend and love - it’s a really good distribution with a nice balance between stability and simplicity.
I used the OS package manager for the essentials, like Gnome, Firefox, the
terminal, Wayland and X11, and used /opt
like a sort of Program Files
or
Applications
directory where I had my own programs like IntelliJ, Postman,
PostgreSQL, etc.
This works relatively well: you don’t have a lot of demands on package availability in the distro itself and you keep things stable - only update your own stuff when you feel like it and/or when you need to.
At a certain point I had about 70 applications in there which became a burden to keep up to date, so I set out to find a distribution that packages as much as possible of the software I use, and where packaging it yourself is as simple as possible.
I definitely prefer a rolling-release distro. I checked out Nix, Gentoo and Arch Linux. Nix I found really appealing (hard declarative) but I hated the syntax (maybe Guix one day? I like Scheme better).
Gentoo did have a comparable set of packaged software available, and it is one of my favorite distributions, but I had too many issues on my test-build virtual machine which blocked me from experimenting prior to running it on my daily driver.
So why move to Arch? Well, in one word: mind-share. The wiki, the amount of packaged software, sane package standards:
- The Arch wiki has essentially become the de-facto standard Linux wiki
- Really huge package collection, fantastic community participation with AUR
- Upstream stable usually means an upstream update is in repos within minutes
- Does not have separate dev/devel packages for headers
The above three things means a lot of convenience to a Linux desktop user. On Arch Linux, 99.9% of the software I ever used on Linux is packaged, most of it in the official repositories and a few in AUR. Next to that, AUR and PKGBUILD are so easy to get into, so you can easily package things yourself and share it with the community.
On most other distros the gap between what’s packaged and what isn’t is quite a bit larger which means you need alternative ways to get that software and keep it up-to-date, which in practice becomes error-prone and time-consuming.
On Arch Linux I made the choice to run everything packaged with pacman
. If
I need something that isn’t packaged yet (currently the case for 5 packages of
which I packaged one already), I package it and publish it to AUR.
The rest of this article is essentially a how-to with notes about what I run and install. Feel free to peruse and/or replay!
BIOS settings
You can find the BIOS guide on the Framework community website. The only settings I changed were:
- CPU Configuration -> Boot Performance mode: MAX BATTERY
- CPU Configuration -> Intel Turbo Boost Max Technology 3.0: DISABLED
- Secure Boot -> Enforce Secure Boot: DISABLED
The above causes the laptop to effectively run in a lower TDP setting. I think
it goes from 28watts to 22watts. This has a dramatic effect on the battery life
and thermals of the device. I don’t want a wild beast that blasts the fans as
soon as I type ls
, so these settings are great for me. Did I mention the
laptop performs really great with these settings?
With the above and the settings I configure in TLP I get ~3watts idle with screen on normal brightness, Wi-Fi and Bluetooth enabled, and about ~45 celsius core temperatures.
Basic installation
Basically, download the ISO and follow the
installation guide. I
opted to use systemd-boot
as boot manager and NetworkManager
as my network
configuration tooling.
Additional kernel parameters I use
Whichever boot manager you use, you might want to set a few extra kernel parameters. I found the following additionals handle a bunch of stuff nicely on my Framework Laptop 13:
net.ifnames=0 libata.allow_tpm=1 module_blacklist=cros_ec_lpcs,hid_sensor_hub acpi_osi="!Windows 2020" tpm_tis.interrupts=0 nvme.noacpi=1 mem_sleep_default=s2idle
Packages I install
Next to the default core
and extra
repositories, I enable multilib
in
pacman.conf
before installing. Additionally, I blacklist pam_systemd_home.so
because I don’t use systemd-homed
and it spams the journal on any login or
auth action:
--- pacman.conf.orig 2023-08-06 12:09:44.849855338 +0300
+++ pacman.conf 2023-08-06 12:01:30.261984035 +0300
@@ -26,7 +26,7 @@
#IgnoreGroup =
#NoUpgrade =
-#NoExtract =
+NoExtract = usr/lib/security/pam_systemd_home.so
# Misc options
#UseSyslog
@@ -87,12 +87,11 @@
#[multilib-testing]
#Include = /etc/pacman.d/mirrorlist
-#[multilib]
-#Include = /etc/pacman.d/mirrorlist
+[multilib]
+Include = /etc/pacman.d/mirrorlist
You can install the following packages right after your system comes up first
boot, or you could do it during install with pacstrap
(it doesn’t really
matter, but in any case, make sure core
, contrib
and multilib
are enabled
in /etc/pacman.conf
first):
pacman -S --needed acpi acpi_call-dkms acpid alsa-utils ansible-language-server ant ardour audacious autoconf automake aws-cli bash bash-language-server bc bind bison blender bookworm btop bubblewrap cabal-install calibre cameractrls carla cdemu-daemon cdemu-client cdrdao cdrtools cifs-utils clang cmake corkscrew cpio cpupower cue cups curl dagger dash dcraw deno desmume devtools discord distrobox dive dmidecode dmraid dnsmasq docker docker-buildx docker-compose dos2unix dosfstools dotnet-sdk dstat dvd+rw-tools efibootmgr elixir emacs-wayland erlang ethtool evince extra-cmake-modules exfatprogs fakeroot fd file file-roller fio firefox flex foomatic-db-engine foomatic-db-nonfree-ppds foomatic-db-ppds fop fractal fs-uae fs-uae-launcher furnace fwupd fzf gamemode gcc gdb gdm ghc ghidra gimp git glab gnome-backgrounds gnome-browser-connector gnome-calculator gnome-characters gnome-control-center gnome-disk-utility gnome-session gnome-settings-daemon gnome-shell gnome-shell-extensions gnome-system-monitor gnome-terminal gnome-themes-extra gnome-tweaks gnupg go gopls gparted gradle groovy guile gvfs gvfs-gphoto2 gvfs-mtp gvfs-nfs gvfs-smb gvim handbrake harfbuzz-cairo haskell-language-server hdparm helm hplip htop i2c-tools ifuse inkscape iperf3 iptables-nft irssi jfsutils jq k9s kafka kubectl libcroco libindicator-gtk3 libreoffice-still libretro-beetle-pce libretro-beetle-psx-hw libretro-core-info libretro-desmume libretro-dolphin libretro-duckstation libretro-flycast libretro-mame libretro-mgba libretro-mupen64plus-next libretro-nestopia libretro-pcsx2 libretro-picodrive libretro-ppsspp libretro-sameboy libretro-scummvm libretro-snes9x libretro-yabause libva-mesa-driver libva-utils libvirt libvisual libxcrypt-compat linux-headers lldb lm_sensors lm_sensors loupe lshw lsof lsscsi ltrace lua-language-server make mame mame-tools man-db man-pages mattermost-desktop maven mbedtls2 mednafen mesa-utils mesa-vdpau meson mgba-qt minikube mitmproxy mono mono-msbuild moreutils mplayer mpv mtools multipath-tools mupen64plus mupdf-tools mutter nasm nautilus neofetch net-tools netbeans network-manager-applet networkmanager networkmanager-openvpn nfs-utils ninja nmap nodejs npm ntfs-3g nuget nvchecker nvme-cli nvtop openbsd-netcat opencl-headers opencl-clhpp openldap openssh openvpn p7zip pacutils pandoc-cli patch patchelf pavucontrol pciutils perl perl-net-dbus perl-x11-protocol pinentry piper pipewire pipewire-alsa pipewire-jack pipewire-pulse pipewire-v4l2 pkgconf postgresql powertop ppsspp pyright python python-kubernetes python-ldap python-opengl python-pip python-pycryptodomex python-pyopenssl python-setuptools python-websockets python-wheel qbittorrent qemu-full qjackctl qmc2 qpwgraph qt5-declarative qt5-tools qt5-wayland qt5-webchannel qt5-webengine qt5ct qt6-multimedia-ffmpeg qt6-tools qt6-wayland qt6ct quodlibet rabbitmq racket retroarch retroarch-assets-ozone retroarch-assets-glui ripgrep rsync ruby ruby-rake-compiler rust s-tui samba sane sbt scons screen scummvm sdl2_mixer seahorse signal-desktop smartmontools smbclient snapshot snes9x speedtest-cli squashfs-tools stack steam step-ca step-cli stern strace sudo syncthing sysprof tar texlive-bin texlive-core the_silver_searcher thunderbird tmux traceroute tracker3-miners tree ttf-joypixels typescript typescript-language-server udftools unixodbc unzip urlwatch usbutils util-linux v4l-utils valgrind vdpauinfo vhba-module-dkms virt-manager vlc vulkan-tools wake wayland-utils wgetpaste wine winetricks wireless_tools wireplumber wireshark-cli wireshark-qt wmctrl wol xchm xclip xdg-desktop-portal-gnome xdg-user-dirs-gtk xdg-utils xdotool xfsprogs xorg-font-util xorg-fonts-100dpi xorg-mkfontscale xorg-server xorg-server-devel xorg-xauth xorg-xdpyinfo xorg-xdriinfo xorg-xev xorg-xfontsel xorg-xhost xorg-xinit xorg-xinput xorg-xkill xorg-xprop xorg-xrandr xorg-xrdb xorg-xset xorg-xsetroot xorg-xvinfo xorg-xwayland xorg-xwininfo xsane xsane-gimp xterm yarn yasm yq yt-dlp yuzu zig zip zls zstd
Additionally install when you want dynamic application of powermanagement settings
You can install tlp
to enable dynamically applying powermanagement settings,
based on if your power connector is connected or if you’re on battery.
pacman -S --needed tlp
Additionally install when you want to use dracut instead of mkinitcpio
I use dracut
on my desktop computer, because I need working LVM RAID mirrors
there. I couldn’t get that to work with mkinitcpio
based initrd.
If you want to use dracut
instead of mkinitcpio
to generate initrd images,
you need to install it and remove the default initrd tooling:
pacman -S --needed dracut
pacman -R mkinitcpio mkinitcpio-busybox
Note that there are no hooks by default for rebuilding dracut-based initrd
images, so you’d need to do that manually after linux
kernel package updates.
For example:
# Kernel version in package contains dot, on-disk it contains dash, hence the sed.
export KERNEL_VERSION=$(pacman -Q linux | awk '{print $2}' | sed 's|\.arch|-arch|g')
cp /lib/modules/$KERNEL_VERSION/vmlinuz /boot/vmlinuz-linux
dracut --kver $KERNEL_VERSION --force /boot/initramfs-linux-fallback.img
dracut --hostonly --no-hostonly-cmdline --kver $KERNEL_VERSION --force /boot/initramfs-linux.img
Additionally install when you use X11 instead of Wayland and want gestures
Wayland provides working gestures on Gnome out of the box, and Wayland is the default on Arch Linux. If, however, you have some reason to use X11 instead of Wayland by default and you want gestures to work, you can install Touchégg:
pacman -S --needed touchegg
Make sure you enable the service:
systemctl enable --now touchegg
And also install the required Gnome extension.
Additionally install on devices with fingerprint reader
Devices like the Framework Laptop 13 have a fingerprint reader. If you want to use it, make sure to install the following packages:
pacman -S --needed libfprint fprintd
And make sure you enable the service:
systemctl enable --now fprintd
Note that the latest Goodix MOC fingerprint readers which the Framework Laptop
13 uses, use a firmware that is not compatible with Linux. For more information,
see here.
Apparently, when one installs the Windows drivers for these things, the Windows
driver actually downgrades the firmware to a version that works with Linux..
Installing Windows 10 in a virtual machine and passing through the Fingerprint
USB device and then installing the Windows drivers,
downgrades the firwmware, after which the device is usable in Linux.
If your fingerprint reader is working, you can continue to follow the steps here to set it up for usage.
Additionally install on devices with Intel graphics
My Framework Laptop 13 is intel-based, so I install these packages additionally:
pacman -S --needed intel-gpu-tools vulkan-intel intel-media-driver libvdpau-va-gl
Additionally install on devices with AMD graphics
If you have an AMD device instead, you might want these ones:
pacman -S --needed radeontop vulkan-radeon
Additionally install on devices with NVidia graphics
If you have an NVidia device instead, you might want these ones:
pacman -S --needed cuda cuda-tools ffnvcodec-headers libva-nvidia-driver nvidia-cg-toolkit nvidia-settings nvidia-utils nvidia-dkms nvtop opencl-nvidia
Installing lib32 package equivalents (optional)
I like to install the lib32-* package equivalents of packages I installed. There isn’t an easy way to do this and it is a bit messy, but here’s how I do it:
# Clean beginnings.
mkdir -p ~/Desktop
rm ~/Desktop/lib32-candidates ~/Desktop/lib32-notfound
# Append a list of possible lib32-* package names by prepending to package name.
for p in `pacman -Qq | grep -v lib32`; do echo lib32-$p >> ~/Desktop/lib32-candidates; done
# Append a list of possible lib32-* package names which consist of lib32-firstnamepart.
for p in `pacman -Qq | grep -v lib32 | cut -d- -f1 | sort -u`; do echo lib32-$p >> ~/Desktop/lib32-candidates; done
# Remove known-not-working elements and make sure the output file is sorted and unique.
cat ~/Desktop/lib32-candidates | grep -v rustup | grep -v openssl-1.1 | grep -v mesa-amber | grep -v mesa-demos | sort -u -o ~/Desktop/lib32-candidates
# Abuse pacman -S to obtain invalid package names.
pacman -S --needed `cat ~/Desktop/lib32-candidates | sort -u` 2>&1 | grep 'error: target not found' | awk '{print $5}' > ~/Desktop/lib32-notfound
# Make sure that's sorted and unique too.
sort -o ~/Desktop/lib32-notfound ~/Desktop/lib32-notfound
# Use comm to diff the lists and only feed valid package names to pacman -S.
pacman -S --needed `comm -23 ~/Desktop/lib32-candidates ~/Desktop/lib32-notfound`
Service configuration
I use and customized a bunch of services on my device. So I don’t forget what I customized and why, let me document it.
CpuPower
The cpupower
service reads from /etc/default/cpupower
and configures the
default scheduler. Edit that file to set the default scheduler to powersave
or
performance
and then enable it:
systemctl enable --now cpupower.service
Avahi
I don’t want any service auto-configuring stuff on my system, especially things like printers for example. Therefore I disable the Avahi zeroconf service:
systemctl mask avahi-daemon.service
systemctl mask avahi-daemon.socket
systemctl mask avahi-dnsconfd.service
Bluetooth
Enable bluetooth
with:
systemctl enable --now bluetooth.service
Bluetooth mostly just works out of the box, except for my XBox Series X|S
Wireless Game Controller. To get this to run, I use the xpadneo-dkms
AUR
package. Additionally, I need to configure a few settings in
/etc/bluetooth/main.conf
(add or set these settings yourself, or use patch
to apply the settings to your main.conf
):
--- main.conf.orig 2023-07-31 19:01:29.473651656 +0300
+++ main.conf 2023-08-01 23:39:23.294653784 +0300
@@ -49,7 +49,7 @@
# Restricts all controllers to the specified transport. Default value
# is "dual", i.e. both BR/EDR and LE enabled (when supported by the HW).
# Possible values: "dual", "bredr", "le"
-#ControllerMode = dual
+ControllerMode = dual
# Maximum number of controllers allowed to be exposed to the system.
# Default=0 (unlimited)
@@ -100,7 +100,7 @@
# Specify the policy to the JUST-WORKS repairing initiated by peer
# Possible values: "never", "confirm", "always"
# Defaults to "never"
-#JustWorksRepairing = never
+JustWorksRepairing = confirm
# How long to keep temporary devices around
# The value is in seconds. Default is 30.
@@ -212,9 +212,9 @@
# LE default connection parameters. These values are superceeded by any
# specific values provided via the Load Connection Parameters interface
-#MinConnectionInterval=
-#MaxConnectionInterval=
-#ConnectionLatency=
+MinConnectionInterval=7
+MaxConnectionInterval=9
+ConnectionLatency=0
#ConnectionSupervisionTimeout=
#Autoconnecttimeout=
@@ -318,7 +318,7 @@
# AutoEnable defines option to enable all controllers when they are found.
# This includes adapters present on start as well as adapters that are plugged
# in later on. Defaults to 'true'.
-#AutoEnable=true
+AutoEnable=false
# Audio devices that were disconnected due to suspend will be reconnected on
# resume. ResumeDelay determines the delay between when the controller
After changes, restart the service:
systemctl restart bluetooth.service
GDM
Enable gdm
with:
systemctl enable --now gdm.service
I prefer auto-login on some of my devices (no on laptop, yes on desktop). Add
the two lines or apply the below diff to /etc/gdm/custom.conf
using patch
if you would like your user to allow GDM to automatically login (don’t forget
to replace $USER
with your user name):
--- custom.conf.orig 2023-07-31 19:08:35.307832755 +0300
+++ custom.conf 2023-07-31 19:08:25.741219958 +0300
@@ -1,6 +1,8 @@
# GDM configuration storage
[daemon]
+AutomaticLogin=$USER
+AutomaticLoginEnable=True
# Uncomment the line below to force the login screen to use Xorg
#WaylandEnable=false
Reboot for the above to take effect.
Libvirtd
Enable libvirtd
with:
systemctl enable --now libvirtd.service
I run libvirtd
mostly stock. I do set unix_sock_group
to libvirt
and add
myself to the libvirt
group. I then set unix_sock_ro_perms
,
unix_sock_rw_perms
and unix_sock_admin_perms
to 0770
(Meaning, the owner
and group can read, write and execute, everybody else can do nothing).
unix_sock_group = "libvirt"
unix_sock_ro_perms = "0770"
unix_sock_rw_perms = "0770"
unix_sock_admin_perms = "0770"
You need to change this for a whole lot of files under /etc/libvirt
. Not doing
this causes problems when connecting with your user instead of root using the
virsh
or virt-manager
clients. don’t forget to restart the service after
changes:
systemctl restart libvirtd.service
Furthermore, I configure the virt0
interface of the default
NAT-enabled
network to have a specific IP address (10.10.11.1) and range (note: needs to
be run after starting/restarting libvirtd):
export UUID=$(uuidgen)
cat <<EOF > "/tmp/net-default.xml"
<network>
<name>default</name>
<uuid>$UUID</uuid>
<forward mode='nat'>
<nat>
<port start='1024' end='65535'/>
</nat>
</forward>
<bridge name='virt0' stp='off' delay='0'/>
<ip address='10.10.11.1' netmask='255.255.255.0'>
<dhcp>
<range start='10.10.11.2' end='10.10.11.254'/>
</dhcp>
</ip>
</network>
EOF
virsh net-destroy default
virsh net-undefine default
virsh net-define /tmp/net-default.xml
virsh net-start default
rm -qf /tmp/net-default.xml
NetworkManager
Enable NetworkManager
with:
systemctl enable --now NetworkManager.service
NetworkManager works mostly out of the box, normally no special settings needed.
However I did run into an issue connecting with older VPN environments related to OpenSSL 3.x disabling various legacy encapsulation and connection modes by default. The error you would see in such a case is:
Jul 31 20:26:38 FRAME nm-openvpn[58956]: OpenSSL: error:11800071:PKCS12 routines::mac verify failure
Jul 31 20:26:38 FRAME nm-openvpn[58956]: OpenSSL: error:0308010C:digital envelope routines::unsupported
Jul 31 20:26:38 FRAME nm-openvpn[58956]: Decoding PKCS12 failed. Probably wrong password or unsupported/legacy encryption
Furthermore, when you are behind corporate proxies, you might also have
difficulties passing through the corporate proxy without the settings
UnsafeLegacyRenegotiation
and UnsafeLegacyServerConnect
(which were allowed
by default on OpenSSL 1.x).
To work-around these issues, I place a custom /etc/ssl/openssl.cnf
:
cp -n "/etc/ssl/openssl.cnf" "/etc/ssl/openssl.cnf.orig"
cat <<EOF > "/etc/ssl/openssl.cnf"
HOME = .
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
ssl_conf = ssl_sect
[provider_sect]
default = default_sect
legacy = legacy_sect
[default_sect]
activate = 1
[legacy_sect]
activate = 1
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyRenegotiation,UnsafeLegacyServerConnect
EOF
Note that I would only do the above if you need to interact with some old legacy VPN stuff, or if you’re behind moron-grade SSL-terminating proxies.
NFSv4
Enable nfsv4
with:
systemctl enable --now nfsv4-server.service
Quick note: the above start may fail if you updated the linux
package but did
not reboot yet, you’ll see an error about a dependency failure.
I use NFS only on internal interfaces, specifically the virt0
interface of
the default
network (remember that ip address 10.10.11.1?). This allows me
to work with shared storage on other operating systems that I fool around with
on Qemu/KVM (Note that you have much better options for modern Linux systems -
there you can use enable shared memory
and a virtiofs
device to essentially
loop-mount a memory block device which is a directory on the host).
Since we’re only doing NFSv4, and we’re not interested in user/group ID mapping, let’s stop and mask a couple of RPC services first:
systemctl stop rpcbind.service
systemctl mask rpcbind.service
systemctl stop nfs-blkmap
systemctl mask nfs-blkmap
systemctl stop nfs-idmapd
systemctl mask nfs-idmapd
systemctl stop nfs-mountd
systemctl mask nfs-mountd
To make NFSv4 only listen on a specific interface, and to disable version 3 of
the protocol explicitly, we patch /etc/nfs.conf
(use patch
or add the
host=
, vers3=
and vers4=
elements by hand under [nfsd]
:
--- nfs.conf.orig 2023-07-31 21:16:20.438028044 +0300
+++ nfs.conf 2023-08-03 09:01:05.586457300 +0300
@@ -67,13 +67,14 @@
# debug=0
# threads=8
-# host=
+host=10.10.11.1
# port=0
# grace-time=90
# lease-time=90
# udp=n
# tcp=y
-# vers3=y
-# vers4=y
+vers3=n
+vers4=y
# vers4.0=y
# vers4.1=y
# vers4.2=y
Create an exports for /home:
cp -n "/etc/exports.d/home.exports" "/etc/exports.d/home.exports.orig"
cat <<EOF > "/etc/exports.d/home.exports"
/home 10.10.11.0/24(rw,sync,crossmnt,no_subtree_check)
EOF
After the above changes, restart the service:
systemctl restart nfsv4-server.service
Samba
Enable smbd
and nmbd
with:
systemctl enable --now nmb.service smb.service
I use Samba for the same reasons as I use NFS, that is to have shared storage
on various older virtual machines (like Windows NT 4.0). Let’s create an
smb.conf
file:
cat <<EOF > "/etc/samba/smb.conf"
[global]
workgroup = WORKGROUP
netbios name = $(hostname | tr 'a-z' 'A-Z' | cut -d. -f1)
server string =
server role = standalone server
server min protocol = NT1
ntlm auth = yes
lanman auth = yes
hosts allow = 10.10.11.
log file = /var/log/samba/log.smbd
max log size = 100
interfaces = 10.10.11.1/24
bind interfaces only = yes
dns proxy = yes
wins proxy = yes
wins support = yes
local master = yes
domain master = yes
preferred master = yes
os level = 33
[homes]
comment = Home Directories
acl allow execute always = True
browsable = yes
writable = yes
valid users = %U
create mask = 0644
directory mask = 0755
EOF
After creating or changing smb.conf
, restart the services:
systemctl restart nmb.service smb.service
TLP
Enable tlp
with:
systemctl enable --now tlp.service
TLP is used to manage power-saving modes of various hardware. It is usually configured to enable power-saving when not connected to AC, and to disable it when connected to AC. It does that for all kinds of things, like Wi-Fi, USB, PCIe, Bluetooth, the CPU scheduler, etc.
I’ve made a custom TLP configuration for my Framework Laptop 13 which you can install as follows:
cat <<EOF > "/etc/tlp.d/01-custom.conf"
CPU_SCALING_GOVERNOR_ON_AC=powersave
CPU_SCALING_GOVERNOR_ON_BAT=powersave
CPU_BOOST_ON_AC=0
CPU_BOOST_ON_BAT=0
PCIE_ASPM_ON_BAT=powersupersave
PLATFORM_PROFILE_ON_AC=balanced
PLATFORM_PROFILE_ON_BAT=low-power
USB_ALLOWLIST=32ac:0002
USB_EXCLUDE_BTUSB=1
USB_EXCLUDE_PRINTER=0
WIFI_PWR_ON_AC=off
WIFI_PWR_ON_BAT=off
WOL_DISABLE=N
EOF
After the above, you need to restart the service:
systemctl restart tlp.service
Cups
Enable cups
with:
systemctl enable --now cups.socket
I also don’t want cups via cups-browsed
to be able to auto-add printers, so I
patch /etc/cups/cups-browsed.conf
as follows:
--- cups-browsed.conf.default 2023-08-29 09:57:47.157250003 +0200
+++ cups-browsed.conf 2023-08-29 09:58:45.054108764 +0200
@@ -53,7 +53,7 @@
# BrowseLocalProtocols.
# Can use DNSSD and/or CUPS and/or LDAP, or 'none' for neither.
-# BrowseProtocols none
+BrowseProtocols none
# Only browse remote printers (via DNS-SD or CUPS browsing) from
Docker
Enable bluetooth with:
systemctl enable --now docker.socket
I use docker without any adjustments.
CDemu
I use cdemu
and related vhba
kernel module to emulate optical drives for
use with emulation stuff. If you’d like to run CDemu, make sure the required
modules are loaded at boot:
# Load modules at boot.
cat <<EOF > "/etc/modules-load.d/cdemu.conf"
sg
sr_mod
vhba
EOF
# Do it now too.
modprobe -a sg sr_mod vhba
Enabling user services
I use the following user-level services (do as logged in user):
for u in syncthing.service wireplumber.service pipewire.socket pipewire-pulse.socket; do systemctl enable --now --user $u; done
How I use AUR
All the hip young things are running yay
these days, but I like to do it the
bare-hands way. That way I have more feeling with what’s going on with AUR
packages.
Setting up your own custom local repository
First, let’s create a custom
repository source for pacman. Note that I sign
my packages using my GnuPG key, so the following assumes that. If you don’t
want to sign your own packages, you need to change the SigLevel
setting in
/etc/pacman.conf
for your custom
repository and you need to tell makepkg
not to sign your built package.
Also note that my package build root location is specific to my needs; feel free to change it to anything you like.
# Set package build root location.
export PKG_ROOT="$HOME/Syncthing/Packaging/Arch"
# Create a directory structure for Arch packaging.
mkdir -p "$PKG_ROOT"
cd "$PKG_ROOT"
install -d Build Packages 'Source Packages' Sources -o $USER
# Create a repository database.
cd "$PKG_ROOT/Packages"
repo-add custom.db.tar.gz
# Add entry to /etc/pacman.conf.
cat <<EOF >> "/etc/pacman.conf"
[custom]
SigLevel = Required DatabaseRequired TrustedOnly
Server = file://$PKG_ROOT/Packages
EOF
# Add your public key to pacman keychain and set trust (assuming key matches $USER).
gpg --export --armor $USER > your.key
pacman-key --add your.key
pacman-key --lsign-key $USER
rm -qf your.key
# Run update for db and files, you should see custom being referenced.
pacman -Syu
pacman -Fy
# Unset variables.
unset PKG_ROOT
Configuring for package building
Now we configure makepkg
defaults. Make sure you configure the PKG_ROOT,
GPG_PUBKEY and PACKAGER environment variables to your specific likings:
# Set package build root location, gpgpubkey-id and packager string.
export PKG_ROOT="$HOME/Syncthing/Packaging/Arch"
export GPG_PUBKEY='89E5EB2541BC6668A9C165D424BD51CD12534CE6'
export PACKAGER='Rubin Simons <me@rubin55.org>'
cat <<EOF > "$HOME/.makepkg.conf"
# ~/.makepkg.conf.
MAKEFLAGS="-j$(nproc)"
BUILDENV=(!distcc color !ccache check sign)
BUILDDIR="$PKG_ROOT/Build"
PKGDEST="$PKG_ROOT/Packages"
SRCDEST="$PKG_ROOT/Sources"
SRCPKGDEST="$PKG_ROOT/Source Packages"
GPGKEY="$GPG_PUBKEY"
PACKAGER="$PACKAGER"
EOF
# Unset variables.
unset PKG_ROOT GPG_PUBKEY PACKAGER
Example interactions using AUR
Here are a few example interactions with package fetching, building, repository updating and package removal to get you started:
# Set package build root location.
export PKG_ROOT="$HOME/Syncthing/Packaging/Arch"
# Get an AUR package.
cd "$PKG_ROOT/Build"
git clone https://aur.archlinux.org/aurutils.git
# Build an AUR package.
cd aurutils
makepkg -cCs
ls ../../Packages/aurutils*
# Show information about a built package.
cd "$PKG_ROOT/Packages"
pacman -Qpi aurutils-*-any.pkg.tar.zst
# Build a source package.
cd aurutils
makepkg -cCsS
ls ../../Source\ Packages
# Update local repository (adds new packages, removes older ones).
cd "$PKG_ROOT/Packages"
repo-add -n -R -s custom.db.tar.gz *.zst
# Remove a specific AUR package from local repository
cd "$PKG_ROOT/Packages"
repo-remove -s custom.db.tar.gz aurutils
AUR packages I build and install
So now to fill that $PKG_ROOT/Build
directory with packages from AUR so we
can build some stuff and put it in our own repository:
# Set package build root location.
export PKG_ROOT="$HOME/Syncthing/Packaging/Arch"
# Git clone them all.
cd "$PKG_ROOT/Build"
for p in adwaita-qt-git akku ares-emu attract-git aurutils authy azure-cli chez-scheme conan cubeb dolphin-emu-git dosbox-x duckstation-git earthly eclipse-java elixir-ls erlang_ls exercism flycast gnome-shell-performance godot-mono-bin google-cloud-cli groovy-language-server-git ibmcloud-cli icaclient imhex irccloud-bin jdk17-graalvm-bin jdk17-jetbrains-bin jdk17-openj9-bin jdk21-jetbrains-bin jdtls jetbrains-toolbox krew kubelogin lagrange lib32-gperftools lib32-intel-gmmlib lib32-intel-media-driver libretro-beetle-lynx-git libretro-beetle-pcfx-git libretro-bluemsx-git libretro-dosbox-pure-git libretro-fsuae-git libspng license-wtfpl m64py mathematica mednaffe mei-amt-check-git metals moonlight-qt ms-sys mutter-performance ncurses5-compat-libs nestopia openmsx openshift-client-bin openshift-codeready-bin openshift-developer-bin openshift-pipelines-bin parsec-bin passmark-performancetest-bin pcsx2-git pegasus-frontend-git postman-bin powershell-bin protonmail-bridge-bin protontricks ps3-disc-dumper-bin python-patch-ng python-pluginbase python-pysdl2 python-vdf rabtap rcu-bin rebar3 remark-language-server roomeqwizard rpcs3-git ruby-backport ruby-e2mmap ruby-jaro_winkler ruby-reverse_markdown ruby-solargraph ryujinx-git sameboy sbt scala-dotty scala-scala3-symlink scheme-chez-symlink sedutil skyscraper-git soapui sublime-text-4 sunshine tla-toolbox townsemu-git ums ungoogled-chromium-bin visual-studio-code-bin vi-vim-symlink vmware-horizon-client vmware-keymaps xpadneo-dkms zeal-git zlib-ng zoom; do git clone https://aur.archlinux.org/$p.git; done
# Build all (I wouldn't do this, I would initially enter one-by-one and do
# git log ; makepkg -cCs manually). Will result in packages under $PKG_ROOT/Packages.
cd "$PKG_ROOT/Build"
for p in *; do cd $p; makepkg -cCs; cd -; done
# Update custom repository.
cd "$PKG_ROOT/Packages"
repo-add -n -R custom.db.tar.gz *.zst
# Update pacman databases.
pacman -Syu
# List all packages pacman sees in custom repository.
pacman -Sl custom
# Install all not-installed packages from custom repository.
pacman -S --needed $(pacman -Sl custom | grep -v installed | awk '{print $2}')
Personal AUR packages
I maintain these AUR packages:
- openshift-codeready-bin
- openshift-developer-bin
- openshift-pipelines-bin
- rcu-bin
- scala-scala3-symlink
- scheme-chez-symlink
Here are a few more things I plan to create AUR packages for:
Final thoughts
After you’ve done (most of) the above, a reboot
is in order; the system should
come up cleanly, without errors or stalls.
I’ve be been using this setup for the last month and it has been pretty great. I get really good battery life, The fan almost never comes on, sleep and resume work reliably, bluetooth works with mouse, gamepad and headphones, Libvirt has been amazing with a bunch of interesting virtual machines (Guix, Windows NT, RHEL6 with Softimage).
I hope to be using this machine and operating system for a long time!