Archlinux installation process on Dell XPS 15 7590 2023-11-27

I am once more installing Archlinux after my SSD fried. I had documented the process last time I did this in a list of bullet points, this article meant to formalize it a bit more.

This is mostly targeted at my future self, so all the software, config, and procedure will be tailored to my needs. Other people might find some value in it though, so I'm putting it out there.

A lot of the information in here is also available in the Archlinux installation guide.

Create a live USB

We first need to prepare a bootable USB with the latest iso image, which you can find here.

Do make sure to verify the checksum of the image after downloading it. While it's rare, I have been burned beforeby a corrupted image, and it's not fun to debug issues for hours before realizing that the error was in the very first step, and was easily avoidable. Check the output of this command against the checksum on the download page (search for SHA256):

sha256sum archlinux-2023.11.01-x86_64.iso

Then plug in your USB stick, make sure it doesn't contain important data, and figure out its device name somehow. I used sudo dmesg, which should show the device name around the time you plugged it in:

dmesg showing the device name

In my case, my device is named /dev/sda.

Note the distinction between the device name and the partition names. In my case, the device name is /dev/sda, and the partition names are /dev/sda1 and /dev/sda2.

Then, use dd to write the iso image to the USB stick:

# make sure the USB stick is not mounted
sudo umount /dev/sda*
# note that the dd command targets the device, not one of its partitions
sudo dd if=archlinux-2023.11.01-x86_64.iso of=/dev/sda status=progress
# make sure all the data is properly written
sync

Boot from the USB stick

You can then extract the USB stick and plug it in the computer where you want to install Archlinux. Restart the computer, and adjust your BIOS settings if necessary:

You can then boot the USB stick. Set your keyboard layout and time zone:

loadkeys fr-latin1
timedatectl set-timezone Europe/Oslo

Connect to the internet:

# check whether your network interface is up
rfkill
ip link
# turn it on if it's not
ip link set dev wlan0 up
# connect to your wifi
iwctl

# iwctl commands:
# device list
# station wlan0 scan
# station wlan0 get-networks
# station wlan0 connect <SSID>
# station wlan0 show

# check that you're connected
ping archlinux.org

Pro tip: tab completion works on the SSID, you don't need to type it all out.

Get a GUI

I don't like using fdisk or parted to manage my partitions, I'd much rather use gparted, which is a graphical interface for parted. I also don't want to type out all of the install commands, I prefer to copy-paste them. For these reasons, we'll get a GUI up and running as soon as possible.

# resize the root partition on the fly to make room for packages
mount -o remount,size=4G /run/archiso/cowspace
# refresh the package list
pacman -Syy
# install a display server, a light weight desktop environment and a browser
pacman -S xorg-server xorg-xinit xfce4 firefox
# configure Xorg to start xfce
echo "exec startxfce4" >> ~/.xinitrc
# start the graphical session
startx

From there, we can use the browser to copy commands into the terminal.

If, for some reason, you need to get back to the tty, you can use Ctrl+Alt+F2.

# set the keyboard layout in the X session
setxkbmap fr

Partition the disk

Next, we'll create all of our partitions. I use Windows on a dual boot (already installed), I'll try and not break it.

Here are the partitions I want:

I don't like using fdisk or parted as I find it to easy to break things and hard to keep track of what I'm doing, so I'll use gparted, which is the main reason why I wanted a graphical session:

# install gparted
pacman -S gparted
# run it
gparted

Existing partitions in gparted

As you can see, Windows already created a few partitions of its own. We will be adding an entry in the EFI partition to boot Archlinux, and reducing the size of the main windows partition. I don't know what the other two partitions are for, but they're tiny so I'll leave them be.

The first step is to resize the Windows partition.

You should do this from Windows instead, if you don't want to run into the same issues I did.

I reduced it to 150GB, which is more than it currently needs but leaves some room for it to grow. I like to reboot here into Windows to make sure it is still working fine, even if it means redoing all of the previous preparation steps. It is usually easier to fix issues as early as possible, rather than do a bunch of changes at once and not know which one caused the issue. That was a good shout, because Windows got stuck in a loop of trying to repair disk issues and rebooting. I solved this by running bootrec /FixMbr in the command prompt available in Windows' recovery tool, but it is probably better to resize the partition from the official Windows utility instead (not sure how it would handle resizing the partition it is currently running on though). If that doesn't work for you, you could try one of the other solutions suggested here.

Here is the list of partitions I created, with names and labels always with the same value:

Once the changes are applied, we can reboot to make sure Windows can see and use the new data partition.

Install the system

We'll start with mounting all the partitions.

# set variables so we don't have to remember which partition is which all the time
export PARTITION_EFI="/dev/nvme0n1p1"
export PARTITION_BOOT="/dev/nvme0n1p5"
export PARTITION_ROOT="/dev/nvme0n1p6"
export PARTITION_HOME="/dev/nvme0n1p7"
export PARTITION_SWAP="/dev/nvme0n1p8"
export PARTITION_DATA="/dev/nvme0n1p9"

# mount the partitions
mount $PARTITION_ROOT /mnt
mount --mkdir $PARTITION_EFI /mnt/efi
mount --mkdir $PARTITION_BOOT /mnt/boot
mount --mkdir $PARTITION_HOME /mnt/home

The Archlinux wiki explains in great detail the various ways to handle the EFI partition. I'll go for the second typical mount point described there: mounting the EFI partition to /efi.

Install the base system, after making sure you will be using a mirror close to you:

reflector --country 'Norway,Sweden' > /etc/pacman.d/mirrorlist
pacstrap -K /mnt base linux linux-firmware

We'll then use genfstab to make sure all the partitions we just mounted are always mounted automatically in the new system.

genfstab -U /mnt >> /mnt/etc/fstab

We can now chroot into the system and configure it more.

# chroot into the new system
arch-chroot /mnt
# enable colors in pacman
sed -i 's/#Color/Color/g' /etc/pacman.conf
# upgrade everything
pacman -Syyu
# update your processor microcode if necessary
pacman -S intel-ucode
# configure the timezone
ln -sf /usr/share/zoneinfo/Europe/Oslo /etc/localtime
# set the hardware clock to UTC
hwclock --systohc
# uncomment the locale you want to use in this file, and install it
# en_DK.UTF-8 uses ISO date format (yyyy-mm-dd)
sed -i 's/#en_DK.UTF-8 UTF-8/en_DK.UTF-8 UTF-8/' /etc/locale.gen
sed -i 's/#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen
echo "LANG=en_US.UTF-8" > /etc/locale.conf
# make your keyboard layout in the console persistent
echo "KEYMAP=fr-latin1" > /etc/vconsole.conf
# set your hostname
echo "maahl-laptop" > /etc/hostname
# set your root password
passwd

You might need to set Windows to use UTC as well.

We'll configure the bootloader with systemd-boot. If you would prefer to use something else, the wiki might have some documentation.

# create your initial ramdisk
mkinitcpio -P
# install systemd-boot on your EFI partition
bootctl install
# configure the archlinux boot entry
# don't forget the microcode line if you installed it
echo "title Arch Linux
linux /vmlinuz-linux
initrd /intel-ucode.img
initrd /initramfs-linux.img
options root=LABEL=arch_root" > /boot/loader/entries/arch.conf
# configure the loader
echo "timeout 2" > /efi/loader/loader.conf

Note that there are two config folders for systemd-boot: /boot/loader and /efi/loader. I found this very confusing, but what clarified it for me is this excerpt from the wiki (with paths adjusted to the current situation):

systemd-boot will search for boot menu items in /efi/loader/entries/*.conf and additionally in /boot/loader/entries/*.conf if using XBOOTLDR. Note that entries in /efi can only use files (e.g. kernels, initramfs, images, etc.) in /efi. Similarly, entries in /boot can only use files in /boot.

We are using XBOOTLDR (this is what setting the bts_boot flag on the /boot partition did at the gparted step), and the kernels will be installed to /boot, so we need to put our bootloader entry in /boot as well.

We can now make sure bootctl doesn't report any error:

bootctl status

We'll now configure our main user.

# install sudo for privilege escalation, the shell that will be used by the user, and
# vim for visudo
pacman -S sudo zsh vim
# create a group for sudoers
groupadd sudo
# create the user and add it to the sudoers group
useradd -m -G sudo,wheel -s /usr/bin/zsh maahl
# set the user's password
passwd maahl
# make members of the sudo group part of the sudoers
# uncomment this line:
# %sudo ALL=(ALL:ALL) ALL
visudo

Now on to installing some packages.

For my window manager, I use i3, mainly for the capability to assign workspaces to monitors, and being able to switch workspace on one monitor without anything changing on the other monitors.

I would have liked to have something with a few more bells and whistles, but I didn't manage to adjust the alternatives I investigated to my needs (Gnome Shell with the gnome-shell-extension-dash-to-panel extension, and Hyprland).

# install a backup text editor
pacman -S nano
# install i3 and related tools
pacman -S \
  i3 i3lock polybar rofi picom dunst \
  arandr autorandr rofi-autorandr \
  lightdm lightdm-slick-greeter lightdm-settings
sudo sed -i 's/#greeter-session=example-gtk-gnome/greeter-session=lightdm-slick-greeter/' /etc/lightdm/lightdm.conf
systemctl enable lightdm
# install some graphical utilities
pacman -S firefox terminator nemo gparted
# install network utilities
pacman -S networkmanager networkmanager-openvpn network-manager-applet
systemctl enable NetworkManager
# install some command line tools
pacman -S base-devel git inetutils wget efibootmgr

When given a choice about audio stuff, I went with those:

I also like to disable the locking system that prevents auth after 3 failed attempts, especially since I tend to juggle between keyboard layouts.

sudo sed -i 's/# deny = 3/deny = 0/g' /etc/security/faillock.conf

We can now reboot into Windows first to make sure we didn't break anything, then to our new system to finish the configuration.

We haven't configured a display manager yet, so after rebooting into Linux, you'll be greeted with a tty. After login, you can just run Hyprland to start it.

Install dotfiles

I'll first start by fetching my dotfiles, to prevent software I install from creating them before I created the right symlinks.

# set your keyboard layout
sudo localectl set-keymap fr
sudo localectl set-x11-keymap fr
# install oh-my-zsh and a plugin for it
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" && rm ~/.zshrc
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
# generate an ssh key
ssh-keygen -t ed25519 -C "[your@email]"
# add the public key to your git platform
cat ~/.ssh/id_ed25519.pub
# clone your config files repo
git clone [ssh://your-repo]
# create symlinks to the config files
cd config-files && ./init-config.sh
# install a vim plugin manager, and install the plugins
git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim
vim +PluginInstall +qall

Install paru

In order to install packages from the AUR, I use paru.

sudo pacman -S rustup
rustup default stable
mkdir ~/tmp && cd ~/tmp
git clone https://aur.archlinux.org/paru.git && cd paru && makepkg -si
# get the most relevant results at the bottom
sudo sed -i 's/#BottomUp/BottomUp/g' /etc/paru.conf

Install other packages

# address some of the "missing firmware" warnings during mkinitcpio
paru -S mkinitcpio-firmware
# power management tools
paru -S powertop tlp tlp-rdw
sudo systemctl enable tlp
sudo systemctl enable NetworkManager-dispatcher
sudo systemctl mask systemd-rfkill.service
sudo systemctl mask systemd-rfkill.socket
# some useful command line tools
paru -S autojump-rs bat diff-so-fancy exa htop man-db man-pages pspg ripgrep starship sysfsutils tldr
# vscode, the MS release as I need some extensions that are only on their marketplace
paru -S visual-studio-code-bin
# some development tools
paru -S aws-session-manager-plugin cmake docker docker-compose jq python-pynvim tfenv tk valgrind
sudo usermod -aG docker $USER
sudo usermod -aG tfenv $USER
# python environments manager, so as not to mess with system files
curl https://pyenv.run | bash
pyenv install 3.12
pyenv global 3.12
# some other tools
paru -S android-tools flameshot peek zola

Set up a backup kernel

If some kernel update were to break something, it's nice to have a backup option rather than having to fix the issue from the rescue mode. To that end, we'll install the long-term support (LTS) version of the kernel, and add a boot entry for it.

# install the lts kernel
paru -S linux-lts
# create the boot entry for it
echo "title Arch Linux with LTS kernel
linux /vmlinuz-linux-lts
initrd /intel-ucode.img
initrd /initramfs-linux-lts.img
options root=LABEL=arch_root" | sudo tee /boot/loader/entries/arch-lts.conf

We can then reboot with the new boot entry to confirm that the kernel can be used.

Configure audio

I'll use Pipewire, which should have been installed earlier as a dependency of another package.

# enable sound, approve removing pulseaudio packages when asked
paru -S pipewire-pulse
# enable bluetooth
paru -S bluez bluez-utils blueman
sudo systemctl enable bluetooth
sudo systemctl start bluetooth
sudo systemctl enable blueman-mechanism
sudo systemctl start blueman-mechanism
# to control audio input and output
paru -S pavucontrol pulseaudio-control

Configure hibernation

We'll use suspend-then-hibernate, where the system saves its state both to RAM and disk. This allows us to have the system immediately available if we wake it up before the hibernate delay, but to preserve more battery if the laptop remains asleep for longer.

First, let's set up hibernation. We'll use a swap file, so that if we do end up using the swap partition, we can still hibernate.

# create a swap file the size of RAM
sudo dd if=/dev/zero of=/swapfile bs=1M count=16k
sudo chmod 0600 /swapfile
# format it
sudo mkswap /swapfile
# enable swap on it
sudo swapon /swapfile
# add it to fstab
echo "# swap file for hibernation
/swapfile none swap defaults 0 0" | sudo tee -a /etc/fstab
# this should now list your swap partition and the newly created file
sudo swapon -s

The kernel needs to know where to check for resume info when waking up from hibernation. We do so by adding the resume hook to the initramfs and pointing the kernel to the file.

# add `resume` to the HOOKS array, somewhere after udev, eg:
# HOOKS=(base udev autodetect modconf keyboard keymap consolefont block filesystems resume fsck)
sudo vim /etc/mkinitcpio.conf
# regenerate the initramfs
sudo mkinitcpio -P
# remember which partition is your root partition
export PARTITION_ROOT="/dev/nvme0n1p6"
# get the UUID of your root partition
export PARTITION_ROOT_UUID=$(sudo blkid $PARTITION_ROOT -s UUID -o value)
# get the offset of your swap file
export SWAP_FILE_OFFSET=$(sudo filefrag -v /swapfile | awk '$1=="0:" {print substr($4, 1, length($4)-2)}')
# output the kernel options you will need
echo "resume=UUID=${PARTITION_ROOT_UUID} resume_offset=${SWAP_FILE_OFFSET}"
# copy the output of the previous command at the end of the options line in this file
# this will tell the kernel where to resume from
sudo vim /boot/loader/entries/arch.conf

After a reboot, you should be able to hibernate and resume.

# set up a short hibernate delay for testing
sudo sed -i 's/#HibernateDelaySec=/HibernateDelaySec=60/g' /etc/systemd/sleep.conf
# put the computer to sleep, wake it up immediately and note how long it gets to wake up
systemctl suspend
# set it to suspend then hibernate, wait a bit longer than the delay you set earlier,
# and wake the laptop again
systemctl suspend-then-hibernate

Don't forget to set the HibernateDelaySec parameter to a more sensible value once you're done with tests:

sudo sed -i 's/HibernateDelaySec=60/HibernateDelaySec=3600/g' /etc/systemd/sleep.conf

Now I want my computer to automatically suspend-then-hibernate when I close it:

# change all three "LidSwitch" actions
sudo sed -i 's/#HandleLidSwitch=suspend/HandleLidSwitch=suspend-then-hibernate/g' /etc/systemd/logind.conf
sudo sed -i 's/#HandleLidSwitchExternalPower=suspend/HandleLidSwitchExternalPower=suspend-then-hibernate/g' /etc/systemd/logind.conf
sudo sed -i 's/#HandleLidSwitchDocked=ignore/HandleLidSwitchDocked=suspend-then-hibernate/g' /etc/systemd/logind.conf
# make sure clicking on the power button by mistake does turn everything off
sudo sed -i 's/#HandlePowerKey=poweroff/HandlePowerKey=suspend-then-hibernate/g' /etc/systemd/logind.conf

I also want my system to be locked whenever I suspend my laptop. I achieved this with a systemd unit file:

echo "Description=User suspend actions
Before=sleep.target

[Service]
User=%I
Type=forking
Environment=DISPLAY=:0
ExecStart=/usr/bin/i3lock -f -c 444444
ExecStartPost=/usr/bin/sleep 1

[Install]
WantedBy=sleep.target" | sudo tee /etc/systemd/system/suspend@.service
sudo systemctl enable suspend@$USER.service

Configure the GPU

The GPU on this laptop is an Nvidia GeForce GTX 1650 Mobile.

To save battery, I want the GPU to be enabled only when I explicitly require it, and to use the integrated graphics the rest of the time. This can be done with Prime, and we'll be using the nvidia proprietary driver, to avoid some potential issues with nouveau.

Configure the driver

# install some utils
paru -S mesa-utils
# check what gpu is currently in use, intel graphics in my case
glxinfo | grep "OpenGL renderer"
# install the drivers for the usual kernel and the lts one
paru -S nvidia nvidia-lts
# remove "kms" from the HOOKS array in that file, so as not to load nouveau
sudo vim /etc/mkinitcpio.conf
# regenerate the initramfs
sudo mkinitcpio -P
# configure the driver
echo "options nvidia_drm modeset=1 fbdev=1" | sudo tee /etc/modprobe.d/nvidia-drm.conf
echo "options nvidia NVreg_DynamicPowerManagement=0x02
blacklist nouveau" | sudo tee /etc/modprobe.d/nvidia.conf
# create a udev rule to turn off the GPU when not in use
echo '# Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"

# Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
' | sudo tee /etc/udev/rules.d/80-nvidia-pm.rules

Reboot and make sure both the usual kernel and the lts one allow you to boot your system properly.

Use the GPU when desired

We can then setup GPU switching with prime:

paru -S nvidia-prime
# make sure you can now use the discrete graphics card on demand
# this should say intel
glxinfo | grep "OpenGL renderer"
# this should say nvidia
prime-run glxinfo | grep "OpenGL renderer"

You can now check that the GPU gets properly suspended when nothing uses it:

# check that this says suspended
cat /sys/bus/pci/devices/0000:01:00.0/power/runtime_status
# use the GPU, this should say "nvidia"
prime-run glxinfo | grep "OpenGL renderer"
# this should say "active" for about 10 seconds after the end of the previous command,
# then "suspended" when the GPU is turned back off
cat /sys/bus/pci/devices/0000:01:00.0/power/runtime_status

Keep in mind that utilities such as nvidia-smi will power the card on even if it is off, for about 10 seconds.

If you use an app launcher that reads .desktop entries, you can run a program on the GPU by default (if run with xdg-open or anything that reads the .desktop entry) by editing the corresponding .desktop entry to use prime-run.

For instance, here is how I did it for Factorio:

sed -i 's#Exec="/home/maahl/games/factorio/start.sh" ""#Exec="prime-run" "/home/maahl/games/factorio/start.sh"#' ~/.local/share/applications/gog_com-Factorio_1.desktop

The change will be taken into account next time the desktop entry is reloaded, for instance after logging out and back in.

At any given time, you can run nvidia-smi to see what processes are using the GPU.

There is technically a PrefersNonDefaultGPU field in the .desktop specification, but the only program I've seen that respects it is Gnome Shell with switcherooctl (not sure which program is responsible for what there). It's also limited in that if your default GPU is the discrete GPU, this won't behave the same as setups that use the integrated GPU by default.

Preserving video memory allocations

With the configuration described above, there is an issue where if a program is using the GPU when the laptop goes to sleep, it will have glitches when it resumes. This is because the nvidia driver only saves essential video memory allocations, and has to ditch the rest.

Visual glitches in Factorio

This page describes two methods that are supposed to fix the issue: NVreg_PreserveVideoMemoryAllocations and NVreg_EnableS0ixPowerManagement. I'll describe both here in case you want to try them out, but neither fixed the issue for me, so I resigned myself to have to restart any program running on the GPU if I need to hibernate.

The first method using the NVreg_PreserveVideoMemoryAllocations parameter:

# allow turning off the GPU entirely when it's not in use
echo "options nvidia NVreg_DynamicPowerManagement=0x02 NVreg_PreserveVideoMemoryAllocations=1 NVreg_TemporaryFilePath=/var/tmp
blacklist nouveau" | sudo tee /etc/modprobe.d/nvidia.conf
# enable nvidia services
sudo systemctl enable nvidia-suspend
sudo systemctl enable nvidia-hibernate
sudo systemctl enable nvidia-resume

This works for suspend and hibernate, however it doesn't for suspend-then-hibernate, and fails as if the services weren't enabled:

# after a reboot so the kernel module param is taken into account
systemctl suspend-then-hibernate
NVRM: GPU 0000:01:00.0: PreserveVideoMemoryAllocations module parameter is set. System Power Management attempted without driver procfs suspend interface. Please refer to the 'Configuring Power Management Support' section in the driver README.

There is a dirty workaround described here, that consists in replicating the nvidia services' functionality manually.

The solution described in that post requires overriding a script that is managed by the nvidia package, so I'll instead make a separate script that only handles the pre case.

# disable the nvidia services
sudo systemctl disable nvidia-suspend
sudo systemctl disable nvidia-hibernate
sudo systemctl disable nvidia-resume
# replicate their functionality
echo '#!/bin/sh

case "$1" in
    pre)
        sleep 4
        case "$SYSTEMD_SLEEP_ACTION" in
            suspend|hibernate)
                /usr/bin/nvidia-sleep.sh "$SYSTEMD_SLEEP_ACTION"
                ;;
            suspend-after-failed-hibernate)
                /usr/bin/nvidia-sleep.sh "suspend"
                ;;
        esac
        ;;
esac
' | sudo tee /usr/lib/systemd/system-sleep/nvidia-suspend-then-hibernate-fix
sudo chmod +x /usr/lib/systemd/system-sleep/nvidia-suspend-then-hibernate-fix

I still had artifacts upon resuming in programs running on the GPU, so I tried the S0ix method instead. Note that this method would only work if your GPU supports it and your system's suspend state is s2idle.

# remove the script we just created
sudo rm /usr/lib/systemd/system-sleep/nvidia-suspend-then-hibernate-fix
# use another method of preserving video memory
echo "options nvidia NVreg_DynamicPowerManagement=0x02 NVreg_EnableS0ixPowerManagement=1
blacklist nouveau" | sudo tee /etc/modprobe.d/nvidia.conf

While this fixed the issue for suspend, this didn't solve the glitches after hibernation either, so I just scrapped the idea and will just restart any program that needs the GPU after I hibernate.

I'm not sure if that makes any sense, but just in case I tried combining both methods:

# configure both video memory preservation methods:
echo "options nvidia NVreg_DynamicPowerManagement=0x02 NVreg_EnableS0ixPowerManagement=1 NVreg_PreserveVideoMemoryAllocations=1 NVreg_TemporaryFilePath=/var/tmp
blacklist nouveau" | sudo tee /etc/modprobe.d/nvidia.conf
# enable nvidia services
sudo systemctl enable nvidia-suspend
sudo systemctl enable nvidia-hibernate
sudo systemctl enable nvidia-resume

Sadly I still got glitches, so I abandoned altogether the idea of recovering GPU programs from hibernation.

I ran this to return to the state before this section:

# same as the first time
echo "options nvidia NVreg_DynamicPowerManagement=0x02
blacklist nouveau" | sudo tee /etc/modprobe.d/nvidia.conf
# disable the nvidia services
sudo systemctl disable nvidia-suspend
sudo systemctl disable nvidia-hibernate
sudo systemctl disable nvidia-resume
Early loading nvidia drivers

I had initially set the nvidia driver and modules to early load, by adding them to mkinitcpio.conf. However, this caused a number of issues with hibernation, that are described here.

I'll copy here the steps I initially took to make that work, but keep in mind this won't play nicely with hibernation.

# add "nvidia nvidia_modeset nvidia_uvm nvidia_drm" to the MODULES array, and add
# the udev rule to the initramfs, by adding /etc/udev/rules.d/80-nvidia-pm.rules
# to the FILES array
sudo vim /etc/mkinitcpio.conf
# regenerate the initramfs
sudo mkinitcpio -P
# make sure the initramfs is always regenerated after a driver update
echo "[Trigger]
Operation=Install
Operation=Upgrade
Operation=Remove
Type=Package
Target=nvidia

[Action]
Description=Update NVIDIA module in initcpio
Depends=mkinitcpio
When=PostTransaction
NeedsTargets
Exec=/usr/bin/mkinitcpio -P" | sudo tee /etc/pacman.d/hooks/nvidia.hook

Update firmware

We'll install firmware updates if any are available. Note that you must be connected to AC power for many of them.

# install a firmware updater and a GUI for it
paru -S fwupd gnome-firmware
# download metadata
fwupdmgr refresh
# check for available updates
fwupdmgr get-updates
# install updates, if any
fwupdmgr update

Other misc configuration

Anything else that doesn't fit in the other sections.

# sync your system time with the internet
sudo timedatectl set-ntp true
# authorize a thunderbolt device
paru -S bolt
boltctl list
boltctl enroll [uuid-of-your-device]
# install more fonts
paru -S \
    adobe-source-code-pro-fonts \
    adobe-source-han-sans-otc-fonts \
    adobe-source-sans-fonts \
    adobe-source-serif-fonts \
    awesome-terminal-fonts \
    noto-fonts-emoji \
    ttf-material-design-icons-git \
    nerd-fonts
# customize the login screen, note that wallpapers should not be in /home
sudo mkdir -p /usr/share/backgrounds
sudo chmod go+w /usr/share/backgrounds
cp my-wallpaper.png /usr/share/backgrounds
sudo lightdm-settings
slick-greeter --test-mode
# install more gtk themes
paru -S gnome-themes-extra
# install nicer-looking cursors
paru -S vimix-cursors
# install nicer-looking icons
paru -S obsidian-icon-theme
# install a utility to switch gtk themes
paru -S lxappearance-gtk3
lxappearance
# make sure autorandr is run when lightdm starts
sudo ln -s ~/.config/autorandr /etc/xdg/autorandr
sudo sed -i 's/#display-setup-script=/display-setup-script=autorandr --change/' /etc/lightdm/lightdm.conf
# wallpaper utility
paru -S feh

I want to have a randomized wallpaper that changes every once in a while. feh will handle the displaying of the wallpaper, and systemd will handle the "once in a while", by adding this to my i3 config file:

# random wallpaper every once in a while
set $set_wallpaper "/usr/bin/feh --bg-fill --randomize --recursive ~/wallpapers"
exec --no-startup-id systemd-run --on-calendar="*-*-* *:00:00" --user -- $set_wallpaper
exec --no-startup-id $set_wallpaper

See here for the systemd timer format.