I have spent a lot of time spent over the years to tweak my system to my taste. Things like Openbox, Vim, Zsh, Tmux have a myriad of options and plugins that I put effort into configuring, and also I have my collection of installed programs whose configuration files I tuned.

When it comes to replicating my setup in a new system, such as my work computer, tablet PC, or a VM for instance it is time consuming and off-putting to go through all that process again and again. On top of that, personalization is an endless process so we have the problem of synchronizing future changes to all our devices.

Just as many other Arch Linux users, I ended up writing my installation script. I self host my configuration scripts and files in my home git instance, and I used to keep an automatically up to date list of installed packages as explained in this other post.

While this is a step forward, it is not ideal when you have a bunch of devices to synchronize. I don’t want all my packages in all my systems and some parts of the configuration may vary.

To address the second issue, I normally copy blindly the main configuration file, which then sources a local configuration file that is synchronized separately.

For instance, a .vimrc file can end with a source .vimrc.local file, or sometimes I set up a conf.d kind of directory where I can add or remove files from.

I was inspired by this post to try the meta package approach to solve both problems: replicating my packages and configuration files, and synchronizing my changes. All my credit goes to the author, only that in my case I prefer to self host my packages.

Arch metapackages

A metapackage is just a package that doesn’t actually package files. Instead we use the metadata to do things like grouping dependencies together, specifying package conflicts and so on.



Whenever we install the metapackage, all the dependencies will be pulled in as well. Pretty easy. This is not much better than the previous approach: we merely changed from a script installing a package list to embedding the package list in a metapackage that the package manager is able to understand.

The interesting part is that we can also add our configuration files to the package. When we update the package those files will be updated as well without any additional effort. Also, pacman is aware of what files belong to what package, so it will warn us if we have modified them and it will save a copy named pacnew file so we can compare them. We can configure certain configuration files to be backed up automatically as well, instead of simply overridden.

# pacman -Rdd nacho-vim Packages (1) nacho-vim-0.0.1-2 Total Removed Size: 1.36 MiB :: Do you want to remove these packages? [Y/n] :: Processing package changes... (1/1) removing nacho-vim [###########################################] 100% warning: /home/nacho/.vimrc saved as /home/nacho/.vimrc.pacsave # mv .vimrc.pacsave .vimrc # pacman -S nacho-vim resolving dependencies... looking for conflicting packages... Packages (1) nacho-vim-0.0.1-2 Total Installed Size: 1.36 MiB :: Proceed with installation? [Y/n] (1/1) checking keys in keyring [###########################################] 100% (1/1) checking package integrity [###########################################] 100% (1/1) loading package files [###########################################] 100% (1/1) checking for file conflicts [###########################################] 100% (1/1) checking available disk space [###########################################] 100% :: Processing package changes... (1/1) installing nacho-vim [###########################################] 100% warning: /home/nacho/.vimrc installed as /home/nacho/.vimrc.pacnew

I also package my many custom scripts, and wrappers that live in my ~/.bin folder. We can package any files we want.

It is also useful to create relationships between metapackages when this makes sense. For instance, I always want to install vim alongside with ctags and cscope , or zsh with zsh-suggestions , powerline and all the cool stuff, together with their respective dotfiles. So I have them grouped in the nacho-vim and nacho-zsh metapackages, and instead of adding vim and zsh as dependencies of nacho-base I do add nacho-vim and nacho-zsh in their place.

Finally we can add custom post ugrade and post install hooks to run operations such as enabling a service we just installed or adjusting permissions of the files that pacman copied over.

The great thing about metapackages is that we get the functionality of a dotfile manager without requiring additional tools. Just running pacman -Syu as we normally do will also update our configuration files and an new packages we might have added to our favorite setup.

Some of this can be done with package groups, but the important feature I am after is that all my systems upgrade automatically. From the Arch Wiki

The advantage of a meta package, compared to a group, is that any new member packages will be installed when the meta package itself is updated with a new set of dependencies. This is in contrast to a group where new group members will not be automatically installed.

Creating our metapackages

No need to explain again what is already well explained. Packages are built from a PKGBUILD file with the makepkg command. You can check out the wiki about how to create packages.

There are many ways to skin a cat. I will only mention a couple things that I like to do.

I like to use this structure

$ tree zsh ├── PKGBUILD ├── _zprofile ├── _zsh_aliases ├── _zshrc ├── zsh.install └── zsh.txt

The files to be included need to be in the same directory as the PKGBUILD file. zsh.install is where I define my post-upgrade and post-update hooks

post_install() { # set zsh as my default shell chsh -s /usr/bin/zsh nacho } post_upgrade() { # pacman copies the files as root:root chown -R nacho: /home/nacho/{.zsh,.oh-my-zsh,.config,.zshrc,.zsh_aliases,.zprofile} }

zsh.txt is only the list of dependencies

zsh zsh-syntax-highlighting zsh-theme-powerlevel9k # and so on

I follow the same pattern for all my metapackages. First, I source this simple file to avoid repetition

# package version gittag=$(git describe --tags --always) pkgver=${gittag%%-*} pkgrel=${gittag##*-} # home directory home="\${pkgdir}"/home/nacho # add all files in this directory to the package source=($(find . -maxdepth 1 -type f)) for i in $(seq 1 ${#source[@]}); do sha256sums+=(SKIP); done # use .txt and .install if they exist for deps an hooks config_from_files() { [[ -f "$1.txt" ]] && depends=($(grep -v "^#" "$1.txt")) [[ -f "$1.install" ]] && install="$1.install" return 0 } # for convenience INSTALL_="install -D -o nacho -g nacho --verbose --backup --compare -m644" INSTALLX="install -D -o nacho -g nacho --verbose --backup --compare -m750" INSTALLD="install -d -o nacho -g nacho --verbose --backup --compare "

This way I only need to set the git tag in the repository and the package versions will be derived from it. Also I can add the .txt and the .install files if I want to.

Finally, a simplified PKGBUILD can look something like this

# Maintainer: nachoparker <nacho@ownyourbits.com> pkgname='nacho-zsh' pkgdesc="zsh personalization" arch=('any') license=('GPL2') url="https://server/bla" source ../common.sh package_nacho-zsh() { config_from_files zsh local home=$(eval echo "${home}") # Install oh-my-zsh, autosuggestions and all the rest here # Package home files as backup (replace initial _ for .) for file in "${source[@]}"; do local f=$(basename "${file}" | sed 's=^_=.=') [[ "${f}" =~ ^\..* ]] || continue $INSTALL_ "${file}" "${home}"/"${f}" backup+=("home/nacho/${f}") done }

Once we have this, 95% of the maintenance is simply updating the package list with the techniques from the last post on this topic.

Self hosting our Arch repository

So we already have our own packages and metapackages in place, now we need to synchronize them between our machines.

A repository is no more than a collection of packages and a database file that contains the package list and metadata.

Once we have our packages we generate the database file with the repo-add helper

repo-add nacho.db.tar.xz $generated_packages

Then, we can place packages and database in different places that pacman understands. This can be in our own filesystem (useful to install packages from USB or CD), HTTP or FTP for example.



If we want to use something like a Raspberry Pi all we need is to statically serve a directory with these files. This would suffice

sudo -u http darkhttpd /path/to/repo --no-server-id

However, since I am already using Apache for NextCloudPi, I added a new Virtual Host on port 8888

$ cat /etc/apache2/sites-available/arch-repo.conf Listen 8888 <VirtualHost *:8888> DocumentRoot /path/to/arch-repo/ </VirtualHost> <Directory /path/to/arch-repo/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> # a2ensite arch-repo

Now we have all the pieces so let’s just put them together. Whenever I change my package list in one of the .txt files, I run the following script

#!/bin/bash set -e for d in $(ls -d */); do (cd $d && makepkg -f -c); done pkgs=$(find -name 'nacho-*.pkg.tar.xz') repo-add nacho.db.tar.xz $pkgs scp nacho.db $pkgs home-server:/path/to/arch-repo git clean -fd .

Generate packages, then create the database file and send it all to our statically hosted directory.

Finally we add to our installation script a line where we point pacman to our personal repository. We need the following in all our machines

$ tail -3 /etc/pacman.conf [nacho] SigLevel = Optional TrustAll Server = http://mycloud.com:8888/

Next time we upgrade our system our personal repository will also be synchronized

# pacman -Syu :: Synchronizing package databases... core is up to date extra is up to date community is up to date nacho 3.3 KiB 329K/s 00:00 [###########################################] 100% :: Starting full system upgrade... resolving dependencies... looking for conflicting packages... Packages (4) nacho-base-0.0.1-3 nacho-openbox-0.0.1-3 nacho-vim-0.0.1-3 nacho-zsh-0.0.1-3 Total Download Size: 4.65 MiB Total Installed Size: 7.91 MiB Net Upgrade Size: 0.02 MiB :: Proceed with installation? [Y/n]

There we go, that was pretty easy. Whenever we do our routine pacman -Syu the database will be synchronized and if there’s a new version of the package it will be downloaded and installed, synchronizing all our configuration and new dependencies in the process.

No doubt this will keep evolving over time. Hopefully it gave you some ideas.