Home Blog

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

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.