Linux tips and tricks
Accumulated notes that are individually too small to be posts and collectively annoying to rediscover. Arch on a desktop and a ThinkPad, Hyprland, fish.
Dotfiles across two machines
The interesting problem is not "put configs in git," it is keeping two machines mostly identical without either one's quirks leaking into the other. Three long-lived branches:
| Branch | Role |
|---|---|
base |
shared, machine-neutral config, byte-identical across machines |
master |
desktop, base plus desktop bits |
thinkpad |
laptop, base plus laptop bits |
A shared change is committed on base and propagated:
git switch master && git merge base
git switch thinkpad && git merge base
A machine-specific change is committed on that machine's branch only. Catching
a shared change on a machine branch means cherry-picking it back to base.
Deployment is GNU Stow:
stow -t ~/.config -S <module>
Per-machine files are not tracked at all. They are gitignored and seeded
from a tracked *.example template, which is what keeps base genuinely
identical everywhere rather than merely similar:
cp hypr/hypr/monitors.conf.example hypr/hypr/monitors.conf
cp kitty/kitty/kitty-local.conf.example kitty/kitty/kitty-local.conf
The generated-file problem
Some tracked files get rewritten at runtime. Fish toggles a helix theme file
between light and dark, for instance. You want the committed copy to exist so a
fresh clone works, but you do not want the local churn showing up in every
git status.
.gitignore cannot do this. It only affects untracked files, and these are
tracked on purpose. The tool is skip-worktree:
git update-index --skip-worktree <file>
To update the committed baseline later, reverse it, commit, and re-apply:
git update-index --no-skip-worktree <file>
Worth keeping this in a script, because the list grows and reapplying it is a per-clone step that is easy to forget.
Networking
dnsmasq under NetworkManager
Local caching resolver, with NetworkManager managing it:
# /etc/NetworkManager/conf.d/dns.conf
[main]
dns=dnsmasq
sudo nmcli general reload
NetworkManager starts dnsmasq itself and points /etc/resolv.conf at
127.0.0.1. The upstream servers it actually forwards to end up in
/run/NetworkManager/no-stub-resolv.conf.
To confirm caching is real rather than assumed, run the same lookup twice and compare query times:
drill example.com
See the Arch wiki.
mDNS and .local
sudo pacman -S avahi nss-mdns
sudo systemctl enable --now avahi-daemon.service
Then /etc/nsswitch.conf:
hosts: mymachines mdns_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files myhostname dns
Order matters here. mdns_minimal has to come before resolve and dns, or
.local names go to the upstream resolver and fail.
DNS inside Docker containers
If name resolution fails inside a container, check whether you are running a
DNS resolver container. If so, point the host's /etc/resolv.conf at something
external such as 1.1.1.1. The container inherits the host's resolver, and a
resolver that is itself containerised produces a loop.
Keyboard
Caps lock, via keyd
Install and enable keyd, then /etc/keyd/default.conf:
[ids]
*
[main]
# Escape when tapped, control when held.
capslock = overload(control, esc)
leftshift+leftmeta = layer(meta)
Works at the evdev layer, so it applies under both X and Wayland, unlike the various per-compositor remapping options.
Vial and QMK keyboards
A udev rule is needed before Vial can talk to the board. Get the vendor and
product IDs out of lsusb first:
echo 'KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="3434", ATTRS{idProduct}=="0350", MODE="0660", GROUP="users", TAG+="uaccess", TAG+="udev-acl"' \
| sudo tee /etc/udev/rules.d/99-vial.rules
Replace both IDs with your own.
Power and boot
AMD P-state
Add amd_pstate=active to the kernel command line. In /etc/default/grub:
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet nvidia-drm.modeset=1 amd_pstate=active"
systemd-boot entry
title Arch Linux (linux)
linux /vmlinuz-linux
initrd /initramfs-linux.img
options root=PARTUUID=<partuuid> rootflags=subvol=@ rw rootfstype=btrfs nvidia-drm.modeset=1 quiet splash
Get the PARTUUID from blkid. To dual boot Windows, copy EFI\Microsoft to
/boot/EFI.
Recovering a lost boot entry
From a live image:
mount /dev/nvme0n1p1 /boot
mount /dev/nvme0n1p2 /mnt
arch-chroot /mnt
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=arch
grub-mkconfig -o /boot/grub/grub.cfg
Storage
blkid gives the UUIDs for /etc/fstab:
# <file system> <dir> <type> <options> <dump> <pass>
UUID=<uuid> / ext4 rw,relatime 0 1
UUID=<uuid> /home/<user>/Storage ext4 rw,relatime 0 1
UUID=<uuid> /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 2
btrfs subvolume mount options worth reusing:
mount -o noatime,compress=zstd,space_cache=v2,subvol=@ /dev/sdX /mnt
rclone over SFTP
~/.config/rclone/rclone.conf:
[remote]
type = sftp
host = xfer.hpc.example.edu
user = <username>
key_file = ~/.ssh/id_ed25519
key_file_pass = <obscured>
md5sum_command = md5sum
sha1sum_command = sha1sum
shell_type = unix
key_file_pass is not plaintext. Generate it with
echo "passphrase" | rclone obscure -, and note that obscuring is obfuscation,
not encryption; the config file still deserves 600.
rclone mount remote:/some/path ~/mnt/path
rclone rcd --rc-web-gui --rc-no-auth # local web UI
Desktop odds and ends
Default file manager for directories:
xdg-mime default org.gnome.Nautilus.desktop inode/directory
A custom .desktop entry for a terminal program:
[Desktop Entry]
Type=Application
Name=Lynx
Exec=lynx %u
Terminal=true
Packages that come up on every fresh install:
yay -S nerd-fonts-meta noto-fonts-emoji bibata-cursor-theme-bin ncpamixer
- SMB in Nautilus needs both
sambaandgvfs-smb, then a restart. - Bluetooth headphones need
pulseaudio-bluetooth. gnome-keyringis what lets VS Code use keyring storage.- Chinese input: Rime.
Bluetooth controllers
bluetoothctl
scan on
trust AA:BB:CC:DD:EE:FF
connect AA:BB:CC:DD:EE:FF
If an Xbox controller refuses to pair, its firmware probably needs updating, which unfortunately requires Windows.
A Windows VM without libvirt
qemu-system-x86_64 -enable-kvm -cpu host -smp $(nproc) -m 16G \
-vga virtio -display gtk,gl=on -usb -device usb-tablet \
-device ich9-intel-hda -device hda-output \
-boot d -cdrom ~/Downloads/windows.iso \
-drive format=qcow2,file=windows
gnome-boxes is the easier route and prefers qcow2 over raw.