Build and publishing tooling for the private [sionik] pacman repository
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-21 15:35:05 +02:00
skills/new-package Add the new-package skill, and point the layout at the sionik-pkgbuilds org 2026-08-21 15:35:05 +02:00
build-and-push.sh Rename the pacman repository from sinic to sionik 2026-08-18 08:51:06 +02:00
README.md Add the new-package skill, and point the layout at the sionik-pkgbuilds org 2026-08-21 15:35:05 +02:00

pkgrepo-tools

Publishing tooling for the private [sionik] pacman repository on arche.

The point is independence from AUR maintainers: llama.cpp needs upstream commits that have no release tag yet whenever a new model architecture arrives, and the build must carry both GPU backends. An own repository also means pacman -Syu handles the upgrade, instead of a rebuild by hand on every host.

Layout

~/devel/src/arch-packages/ is a plain directory, not a git repository. Every package below it is its own git repository in AUR layout, with a remote on forgejo, in the sionik-pkgbuilds organisation (ssh://git@git.sinic.eu/sionik-pkgbuilds/<pkgbase>.git):

arch-packages/                          <- plain directory, not versioned
├── pkgrepo-tools/                      <- git: this repository
│   ├── build-and-push.sh
│   ├── skills/new-package/             <- Claude Code skill, symlinked into .claude/
│   └── README.md
├── llama.cpp/                          <- git: llama-cpp-sionik
│   ├── PKGBUILD
│   ├── .SRCINFO
│   ├── llama.cpp.service
│   └── llama.cpp.conf
├── llama-swap/                         <- git: llama-swap-sionik
└── libreoffice-extension-nelson-mcp/   <- git: same shape

One repository per package, for two reasons: each package gets its own version history, and publishing to the AUR later is nothing but adding a remote and pushing. That is also why this script lives outside the package repositories — a package repository holds only what the AUR expects.

The local directory name does not have to match pkgbase (llama.cpp holds llama-cpp-sionik). Only the repository name must match pkgbase, because that is what the AUR checks on push.

How the pieces fit

workstation                                 arche                    clients
-----------                                 -----                    -------
makepkg  ->  gpg --detach-sign  ==(ssh)==>  /srv/pkgrepo/...   <--  pacman -Syu
                                            repo-add -R             (http, :9130)

The private signing key stays on the workstation. arche only stores files and runs repo-add; it never needs the key, because only the packages are signed, not the database (SigLevel = Required DatabaseOptional, the same arrangement as on the official mirrors).

The repository is served over plain HTTP on the LAN. The signatures are what makes that safe, so --no-sign is for local test builds only.

Build and publish

From the arch-packages directory:

pkgrepo-tools/build-and-push.sh llama.cpp

Options: --chroot (clean chroot instead of the host, needs devtools), --cache, --no-push, --no-sign, --help. Host and paths come from REPO_* environment variables, see the top of the script.

--cache routes the compiler through sccache. It pays off precisely because every build is a --cleanbuild, so ninja can never work incrementally: after a version bump the unchanged translation units come out of the cache. It works by exporting CMAKE_{C,CXX,HIP}_COMPILER_LAUNCHER, which CMake initialises from the environment — so no PKGBUILD needs to know about it. BUILDENV=(ccache) in makepkg.conf would be the makepkg-native route but is the wrong tool here: it masquerades gcc/g++ on PATH and therefore misses a compiler named by absolute path, which is how a ROCm build invokes the HIP part. The LTO link and the Vulkan shaders are not cached either way. Mind max_size in ~/.config/sccache/config; the default 10 GiB is easily filled by other work. Not combinable with --chroot, where the launcher binary does not exist.

The script regenerates .SRCINFO before every build, so a pkgver bump cannot reach the repository without it, and warns when the package directory has uncommitted changes — a published package should correspond to a commit.

Version bumps are the package's own business, because where the upstream version comes from differs per package. llama.cpp/update.sh reads the latest release tag from the GitHub API, writes pkgver and refreshes the checksums. With --commit it also commits the two files, by path and only when neither was already modified, so unrelated work cannot be swept into a version bump. Such a script belongs in the package repository, not here.

The build always runs with makepkg --cleanbuild. This is not about tidiness: GitHub tarballs carry commit mtimes, so after a version change the object files of the previous build are newer than the new sources, and make then keeps stale objects for changed files. The result is a binary that contains new code but still behaves like the old one.

namcap runs on every built package. Because the build does not happen in a chroot by default, that check is the only thing which catches a library the package links against but does not declare — a mistake that stays invisible on the workstation and breaks on every other host.

A new package

The steps below by hand, or /new-package in Claude Code: the skill in skills/new-package/ walks the same route with the naming and collision checks in front of it, and with a review gate before anything is published. It is reachable because arch-packages/.claude/skills/new-package is a symlink to it, so the skill is versioned here rather than in an unversioned directory.

mkdir ~/devel/src/arch-packages/<name> && cd $_
git init -b main
# write PKGBUILD and .gitignore (src/ pkg/ *.pkg.tar.zst* *.tar.gz)
makepkg --printsrcinfo > .SRCINFO
git add -A && git commit
fj org repo create sionik-pkgbuilds <pkgbase> -d "<one line>"
git remote add origin ssh://git@git.sinic.eu/sionik-pkgbuilds/<pkgbase>.git
git push -u origin main

To publish it to the AUR later, from the same repository:

git remote add aur ssh://aur@aur.archlinux.org/<pkgbase>.git
git push aur main:master

The AUR requires .SRCINFO to be committed and its pkgbase to match the repository name in the URL.

Signing key — one-time setup

The key is created by hand, so no secret ever passes through a script or a log:

gpg --quick-generate-key "Simon Richter (arch-packages) <simon@sinic.eu>" ed25519 sign never
gpg --list-secret-keys --keyid-format=long          # note the key id

Then name it in /etc/makepkg.conf (or export GPGKEY before a build):

PACKAGER="Simon Richter (arch-packages) <simon@sinic.eu>"
GPGKEY="<key id>"

Export the public key for the clients and keep it in the infra repository — public keys are not secrets:

gpg --export --armor "<key id>" > ~/devel/src/infra/homelab/hosts/files/arche/sionik-repo.asc

Client setup

Import and locally sign the key, once per host:

pacman-key --add sionik-repo.asc
pacman-key --lsign-key "<key id>"

Add to /etc/pacman.conf. The position matters: for a package name that also exists in an official repository, pacman takes the first repository in file order, so [sionik] must sit above [core] to win.

[sionik]
SigLevel = Required DatabaseOptional
Server = http://arche:9130/$repo/os/$arch

llama-cpp-sionik uses provides/conflicts on llama-cpp, so it displaces extra/llama-cpp regardless of the ordering. The ordering matters for any future package that keeps an official name.

Server side (arche)

See infra/homelab/hosts/arche.md. In short: /srv/pkgrepo/sionik/os/x86_64/ served by darkhttpd on port 9130 (pkgrepo-serve.service). Port 80 on arche belongs to the CoMaps mirror, whose server handles one client at a time — the package repository must not share it.