- Shell 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| skills/new-package | ||
| build-and-push.sh | ||
| README.md | ||
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.