arch-sync: Keeping Arch Linux Machines In Sync

Table of Contents
Post Updates #
Update 8/6/2026: Expanded the post to cover hostname-based filtering, home directory and file linking, deduplication and sorting, and the refactor of the script into individual steps. The functionality has grown since the original post, and this update reflects the current status of the project.
Introduction #
I recently released the arch-sync repository. From the README:
This is a template repository for syncing packages and configurations across multiple Arch Linux installations. It includes a script to automate the installation and removal of packages and the synchronization of configuration files.
Motivation #
The motivation is simple – to maintain a consistent setup across systems with minimal effort and simplify the overall management of the installations with respect to package management and configuration. I want to be able to add and remove packages from one machine, modify configuration files, and propogate those changes across several other installations with a single command.
Requirements #
The requirements are short:
- Arch Linux installed on your machines. If you need a starting point, see my Arch Linux install guide.
gitinstalled, to fork/clone the repository and pull/push changes. See my git quick start guide if you need a refresher.yayinstalled, for managing AUR packages.
I use yay for most of my AUR package management, but the script can be modified to use a different AUR helper.
Getting started #
This is a template repository, so the intended workflow is to fork it and then edit the config files to match your own setup.
- Fork it on GitHub, then clone your fork:
$ git clone https://github.com/username/arch-sync.git
- Either copy the example config files or rename and modify them to include what you want installed, removed, and linked.
- Finally, run the script:
$ cd arch-sync
$ chmod +x arch-sync.sh
$ ./arch-sync.sh
Note that the script checks the current state of the system against the config files and only acts on the difference, so running it twice in a row does nothing the second time, and there is no need to go back and clean up the package related config files after a sync.
What the script does #
arch-sync.sh is a wrapper for the individual scripts, which are located under scripts/, and each of which is standalone and independently runnable. They share configuration and helper functions through scripts/common.sh, which is sourced rather than executed.
The steps run in this order:
| Step script | What it does |
|---|---|
update-databases.sh | Refresh package databases (yay -Sy) |
sync-mirrors.sh | Copy mirrorlist → /etc/pacman.d/mirrorlist |
sync-pacman-conf.sh | Copy pacman.conf → /etc/pacman.conf |
dedupe-package-lists.sh | Deduplicate the package lists |
remove-packages.sh | Remove listed packages |
remove-directories.sh | Remove listed directories (interactive) |
link-home-directories.sh | Symlink home directories into the sync folder |
link-home-files.sh | Symlink home files into the sync folder |
install-packages.sh | Install official packages |
install-aur-packages.sh | Install AUR packages |
sort-package-lists.sh | Sort the package lists alphabetically |
The ordering matters in a couple of places. Mirrors and pacman.conf are synced before anything touches packages, so installs use the intended mirrors. Deduplication runs before removal and installation, so the lists are correct before they’re acted on.
Splitting the steps out means any one of them can be run on its own without a full sync:
$ ./scripts/sort-package-lists.sh # just re-sort the package lists
$ ./scripts/install-packages.sh # just install official packages
$ ./scripts/link-home-files.sh # just re-link home files
Note that the script does not run as root, and uses sudo only for the steps that need it.
Configuration files #
The config files are found in config/:
packages-install.txt: packages to install withpacman.packages-aur-install.txt: AUR packages to install withyay.packages-remove.txt: packages to remove.directories-remove.txt: directories and files to remove.home-directories.txt: home directory paths to symlink into the sync folder.home-files.txt: home file paths to symlink into the sync folder.mirrorlist: copied verbatim to/etc/pacman.d/mirrorlist.pacman.conf: copied verbatim to/etc/pacman.conf.
The package and path lists are one entry per line, with # comments supported both as full lines and inline.
Hostname-based filtering #
This is the feature that makes a single set of config files usable across multiple machines. My laptops and desktop share the vast majority of their packages, but differ when it comes to database servers, some specific python packages, image editing programs (which can have a significant number of dependencies), and a few other things.
Every config file supports optional @hostname tags, with examples as follows:
# Installed on all machines
neovim
# Installed only on the host named 'armini'
postgresql @armini
# Installed on 'arbook' and 'arpad', but not on 'armini'
steam @arbook @arpad
# Inline comments work alongside hostname tags
virtualbox @arbook # only on the main workstation
The rules are:
- Entries with no tags apply to all hosts.
- Entries with one or more tags apply only to hosts matching one of the tags, and will not apply to any other host.
- Tags can go anywhere on the line after the entry name, before any
#comment.
The script reads $HOSTNAME at startup and skips any entry whose tags don’t match.
Home directories and files #
home-directories.txt and home-files.txt list paths relative to $HOME that get symlinked into a sync folder – in my case ~/Cloud_Storage/Dropbox – set as SYNC_DIR in scripts/common.sh. Point it at whatever sync service or folder you use.
The result is that dotfiles and important directories live in one synced location and every machine symlinks to them, so a change made on one machine shows up everywhere. Examples as follows:
# Link ~/.claude on all machines
.claude
# Link ~/Documents only on arbook
Documents @arbook
# Link ~/.config/some-app on arbook and arpad
.config/some-app @arbook @arpad
The linking logic avoids inadvertently removing files with the following rules:
- If the target is already a symlink, it’s left alone.
- If the target exists as a real file or directory, it’s renamed to
<entry>_oldbefore the symlink is created. For files, the original content is copied into the sync folder first if it isn’t already there. - If
<entry>_oldalready exists, the entry is skipped with a warning rather than overwritten. - Parent directories inside the sync folder are created as needed.
It is definitely worth manually reviewing the existing files in a directory before running the script, to avoid losing anything important.
Deduplication and sorting #
Hand-editing package lists across several machines produces duplicates, and eventually produces a package that’s in both an install list and the remove list. The dedupe step handles both before anything is acted on:
- Within-file: if the same untagged package appears more than once in a file, occurrences after the first are dropped, and the first occurrence’s inline comment is kept.
- Cross-file: if an untagged package appears in an install list and in
packages-remove.txt, it’s dropped from the install list and kept only in the remove list.
Both rules deliberately skip anything carrying an @hostname tag. That’s what preserves an intentional split like postgresql @armini in the install list alongside postgresql @arbook @arpad in the remove list – install on one machine, remove on the others. A naive dedupe would treat that as a contradiction and break it.
Dropped entries are reported with [WARN] so nothing disappears silently.
Sorting runs at the end of every sync and alphabetizes the three package lists. The leading comment header block is preserved, inline comments and @hostname tags stay attached to their entries, and blank lines within the body are dropped. The sorting makes it simple to see what packages are installed, removed, and installed from the AUR, and easier to add new entries in the right place.
Workflow #
With this script, and the following workflow, I can maintain Arch Linux systems with a few simple commands:
$ yay # this runs yay -Syu by default
$ ./arch-sync.sh # the arch-sync script referenced above
$ yay -Sc # clears the pacman cache and locally cached files from the AUR
$ yay -Yc # removes unneeded dependencies
Then commit and push whatever changed:
$ git add .
$ git commit -m "Update package lists and configurations"
$ git push
Regardless of how recently a system has been updated, the above commands ensure that all machines are in sync.