Everything needed to rebuild my Arch Linux desktop running Hyprland: user dotfiles, system files under /etc, package lists for eight package managers, and a Makefile that provisions, audits and cleans the machine.
Warning
This is a personal setup for one machine: AMD CPU, Realtek RTL8922AE Wi-Fi, LG ultrawide, French keyboard.
Several files hardcode /home/theo and expect the repo at ~/dotfiles.
Borrow freely, but read before running make setup elsewhere: it overwrites files in /etc.
| Path | Contents |
|---|---|
config/ |
One GNU Stow package per program, symlinked into ~ |
system/ |
Files installed under / as root: boot, initramfs, locales, pacman, networking, Bluetooth, SDDM, keyd |
packages/ |
Package lists for pacman, AUR, npm, pnpm, pip, cargo, go and composer |
services.txt |
System units enabled beyond their vendor preset |
system-ignore.txt |
/etc paths deliberately left out of the repo, each with a reason |
Makefile |
Provision, capture, audit and clean the machine; make help lists every target |
tools/ |
Helpers for the Makefile, such as the make help renderer |
The desktop itself:
- Hyprland with hypridle, hyprlock, hyprsunset and a wallpaper that rotates every 30 minutes
- Waybar, the Walker launcher with Elephant providers, and dunst notifications
- kitty, fish and helix, plus lazygit, btop and fzf
- Mac-style shortcuts through keyd, with Ctrl and Command swapped
- Calendars synced by vdirsyncer, shown in khal, with desktop notifications
- Hardware extras: an OpenRGB profile at boot, the Thermalright LCD, AirPods kept as the default audio sink
- Boot: Plymouth
motiontheme and the SDDMsilenttheme with an on-screen keyboard
-
A base Arch install with network access, a user in the
wheelgroup, andgitandbase-develinstalled -
yay, which
makeuses for AUR packages:git clone https://aur.archlinux.org/yay.git /tmp/yay cd /tmp/yay && makepkg -si
-
The secret files that some configs read, which never go in the repo:
File Used by ~/.secrets/airpodsmake dotfiles, to generate the WirePlumber AirPods rule~/.secrets/icloud.envvdirsyncer, iCloud calendar ~/.secrets/google_hartprint.envvdirsyncer, Google calendar ~/.secrets/resticBackups: the password of both repositories, see Backups ~/.secrets/airpodsholds a single line:AIRPODS_MAC="XX:XX:XX:XX:XX:XX".
git clone https://github.com/Androlax2/dotfiles.git ~/dotfiles
cd ~/dotfiles
make setupmake setup runs these targets in order, and each one can also be run on its own:
packagesinstalls the pacman and AUR packagesdotfileslinks everyconfig/package into~global-packagesinstalls the npm, pnpm, pip, cargo, go and composer packagessystemcopiessystem/into/as rootlocalegenerates the locales enabled in/etc/locale.genservicesenables the units inservices.txtinitramfsrebuilds the initramfs for the Plymouth hooks
Then finish the manual steps and reboot.
make checkEach section should be empty. Anything listed is drift between the machine and the repo.
make update # upgrade pacman and AUR packages
make check # what changed on this machine that the repo doesn't know about?
make dump # write those changes into the repo, then review them with git diff
make disk # where is the disk space going?
make clean # remove orphans, old package caches, dev tool caches, journal older than 4 weeksmake clean asks before removing packages. Two cleanups stay separate because they destroy data:
make clean-docker # stopped containers, unused images, and every unused volume with its data
make clean-trash # empty the desktop trash for goodAfter installing something or editing a config, run make check, then make dump, and commit what git diff shows.
make dump rewrites the package lists, services.txt and the files already in system/.
It never picks up a new /etc file by itself. make check lists those instead, and each one needs a decision:
-
Track it by copying it into
system/at the same path:install -D -m 644 /etc/foo/bar.conf system/etc/foo/bar.conf
-
Ignore it by adding a regex and the reason to
system-ignore.txt.
.pacnew files and editor leftovers are never ignored: merge or delete them.
Move its config into a new stow package, then relink:
mkdir -p config/foo/.config
mv ~/.config/foo config/foo/.config/
make dotfilesDon't stow files that programs rewrite by replacing them, such as mimeapps.list: the program replaces the symlink with a plain file.
PC ── restic over SFTP, daily ──> NAS share "restic" (btrfs snapshots, immutable for 7 days)
PC ── restic over SFTP, daily ──> Hetzner Storage Box (Storage Box snapshots)
NAS ── its own data, nightly, from the homelab repo ──> Hetzner Storage Box
- Two independent copies of the PC. The NAS and Hetzner each hold a separate restic repository, so losing either one, or the NAS with the house, still leaves a backup.
- The NAS backs up its own data. Photos, apps and databases leave the NAS through the homelab repository, not from this machine.
- Encrypted on the PC. restic encrypts and deduplicates before upload: the NAS and Hetzner only store ciphertext.
- Everything in
~is backed up except the caches, game installs, programs and build output listed inexcludes.txt.make backup-listshows the size of every included folder, andmake backup-list DEPTH=2shows fewer levels. - One script does the work.
backup.shruns behind themake backup*targets and both timers.
| Job | When | Keeps |
|---|---|---|
Backup to both, restic-backup.timer |
15 minutes after boot, then daily | Every run until maintenance |
Maintenance on both, restic-maintenance.timer |
Sundays at 14:00, or at the next boot if missed | 7 daily, 4 weekly and 12 monthly snapshots |
NAS snapshots of the restic share |
Daily | 14 days, immutable for 7 |
| Storage Box snapshots | Automatic, set in Hetzner Console | Up to 10 |
A target that can't be reached, away from home or offline, is skipped quietly while the other still runs. After 3 days without a backup to a target you get a notification, and any failure sends a critical one.
Caution
The backups can't be decrypted without ~/.secrets/restic. Keep a copy outside the NAS, for example on paper stored away from home: Bitwarden here is the Vaultwarden instance running on the NAS, so it disappears with it.
On the NAS, in DSM:
- In Control Panel, open Shared Folder and create
resticon Volume 1, with data checksum on and read/write access for your account. - In Control Panel, open File Services, then FTP, and enable SFTP.
- In Snapshot Replication, schedule daily snapshots of
restic, kept 14 days and immutable for 7.
On Hetzner:
- In Hetzner Console, open the Storage Box and turn on External Reachability and SSH Support. Without External Reachability, only machines inside Hetzner's network can connect.
- In the Storage Box's Snapshots tab, turn on automatic snapshots. This machine holds an SSH key to the box, so snapshots are what protect the backups if it is compromised.
On the PC:
-
Install restic and record it in the package list:
sudo pacman -S restic make dump-packages
-
Generate the repository password, then store a copy outside the NAS:
(umask 077 && head -c 32 /dev/urandom | base64 > ~/.secrets/restic)
-
Name the Storage Box
storageboxin~/.ssh/config, with the username from Hetzner Console. Use port 23: there the box accepts the usual one-line OpenSSH key, while port 22 only accepts keys in RFC4716 format:Host storagebox HostName u000000.your-storagebox.de Port 23 User u000000Add this machine's public key, as its one-line
.pubfile, to the box's.ssh/authorized_keys. Then connect once to accept its host key, since restic can't answer that prompt:sftp storagebox
-
Create both repositories, check what would be uploaded, then run the first backup. Plug in Ethernet for this one, since it uploads everything twice:
make backup-init ~/.config/restic/backup.sh run --dry-run -v make backup -
Turn on the schedule:
systemctl --user enable --now restic-backup.timer restic-maintenance.timer
Pick the target with nas or hetzner. They hold the same data, so use the NAS at home, since it's faster:
make backup-status
~/.config/restic/backup.sh restic nas restore latest --target /tmp/restore --include "$HOME/IdeaProjects/opusline"To browse every snapshot as folders, mount a repository:
mkdir -p /tmp/restic-mount
~/.config/restic/backup.sh restic nas mount /tmp/restic-mountIf the NAS is lost, restore from Hetzner the same way, with hetzner as the target.
These can't be automated safely, so they stay manual.
Plymouth needs quiet splash on the kernel command line.
Loader entries contain the disk's PARTUUID, so they are not versioned:
sudoedit /boot/loader/entries/<date>_linux.confoptions root=PARTUUID=... quiet splash
system/ only tracks regular files, so the fontconfig links are created by hand:
sudo ln -s /usr/share/fontconfig/conf.avail/10-sub-pixel-rgb.conf /etc/fonts/conf.d/
sudo ln -s /usr/share/fontconfig/conf.avail/10-hinting-slight.conf /etc/fonts/conf.d/
sudo ln -s /usr/share/fontconfig/conf.avail/10-autohint.conf /etc/fonts/conf.d/
sudo ln -s /usr/share/fontconfig/conf.avail/11-lcdfilter-default.conf /etc/fonts/conf.d/
sudo ln -s /usr/share/fontconfig/conf.avail/75-apple-color-emoji.conf /etc/fonts/conf.d/
sudo fc-cache -fvDon't enable 70-no-bitmaps.conf or any other config that disables embedded bitmaps. Color emoji fonts such as Apple Color Emoji rely on them.
The rtw89_8922ae driver stalls when the access point changes channel or bandwidth.
Traffic stops, but the driver never reports a disconnect, so NetworkManager keeps showing the link as connected.
- Stop the Livebox from hopping channels. On
http://192.168.1.1, open Wi-Fi, then the 5 GHz advanced settings:- Fix the channel to 36, 40, 44 or 48, which avoid radar (DFS) channels.
- Set the width to 80 MHz, because 160 MHz spans DFS channels and forces moves.
- Turn off any smart channel or channel optimisation option.
- Let the PC recover on its own.
make systeminstalls a NetworkManager dispatcher script that power-cycles the radio whenever the connectivity check fails whilewlan0still claims to be connected. It also installs a module config that disables PCIe and driver power saving.
Check that the recovery works, and that the regulatory database loaded after a reboot:
journalctl -t wifi-reconnect
iw reg get # should show "country FR", not "country 00"These came from vendor installers, so make can't reinstall them:
| Software | Location | Notes |
|---|---|---|
| Claude Code | ~/.local/bin/claude |
Updates itself |
| CodeRabbit CLI | ~/.local/bin/coderabbit |
cr is a symlink to it |
| greywall, greyproxy | ~/.local/bin |
The opencode wrapper in config/bin runs through greywall; greyproxy is a user service |
| opencode | ~/.opencode/bin |
Started through ~/.local/bin/opencode |
| Herd Lite | ~/.config/herd-lite |
PHP, Composer and the Laravel installer, added to PATH by ~/.profile |
| uv | /usr/local/bin |
make clean-caches uses it |
| Private Internet Access | /opt/piavpn |
Its installer also writes piavpn.service and wgpia.conf |
| Thermalright LCD control | /usr/local/bin |
Its unit file is versioned in system/ |
- DNS goes through systemd-resolved with Quad9 and Cloudflare, set in
system/etc/systemd/resolved.conf. NetworkManager hands DNS over to it throughdns.conf. - User services and timers are versioned with their enable links in
config/systemd, somake dotfilesenables them too. - The wallpaper is picked by
wallpaper-rotator.sh, which writes~/.config/hypr/wallpaper.conf. That file is git-ignored because it changes every 30 minutes.

