Goal: host a private pacman repo on a web server reachable over SSH, and
have local scripts that (1) scaffold the remote repo layout once, and
(2) build, sign and publish packages from this arch/ tree into it.
Tooling constraint: oksh instead of bash, no Python. Scripts follow
the house style already used in arch/scripts/ (POSIX-ish shell, tabs,
set -u, plain test/[ ] guards, header comments explaining the
why, config sourced from a key=value file via .).
Standard multi-arch pacman repo tree, one directory per architecture
under an os/ subdir (this is what dbscripts/repod and every distro
mirror use, and it’s what a Server = .../os/$arch line in pacman.conf
expects):
${REPOSITORY_REMOTE_PATH}/os/x86_64/
${REPOSITORY_REMOTE_PATH}/os/i686/
${REPOSITORY_REMOTE_PATH}/os/pentium4/
${REPOSITORY_REMOTE_PATH}/os/i486/
...
(REPOSITORY_REMOTE_PATH already includes the repo name —
/data/arch32/mirror/archlinuxaba — so no extra ${REPOSITORY_NAME}
path segment is inserted; REPOSITORY_NAME is only used for the db
filename itself.)
Each dir eventually holds *.pkg.tar.zst (+.sig, always) and the repo
database (${REPOSITORY_NAME}.db.tar.gz / .files.tar.gz, unsigned by
default — SIGN_DATABASE=no — since signing the database on top of
already-signed packages isn’t done for repos like this one) — those are
created/updated by repo-add, not by our scripts directly.
repo_create_remote.sh seeds an empty database in every arch dir right
away (see §3), so pointing a fresh machine’s pacman.conf at this repo
before any package has ever been published doesn’t 404 on pacman -Sy.
Client-side pacman.conf entry (documented only, not written by any
script):
[archlinuxaba]
SigLevel = Required DatabaseOptional
Server = https://archlinux32.andreasbaumann.cc/archlinuxaba/os/$arch
repo_detect_archs.sh scans every arch=(...) line under
arch/*/*/PKGBUILD, unions the values, drops any, and prints the
result. Decision: auto-detect the full set (not narrowed to
archlinux32’s 32-bit archs), even though the host/URL are
archlinux32-branded — confirmed explicitly. Real set currently found:
x86_64 i686 pentium4 i486 aarch64 armv6h armv7h arm riscv64
…but only x86_64, i686, pentium4, i486 have a local
extra-<arch>-build script (i.e. can actually be built on this
machine). repo_create_remote.sh creates dirs for the full detected set
(cheap, idempotent mkdir -p); repo_build.sh only ever attempts archs
it can build locally and skips+reports the rest (see §3).
REPO_ARCHS in the config can override the detected list if that’s
ever wanted, but is left blank by default.
New file, arch/config/repo.conf, same key=value format as the existing
arch/config/global.conf, sourced with . by every script:
REPOSITORY_HOST=euroweb.lan
REPOSITORY_SSH_USER=root
REPOSITORY_SSH_PORT=22
REPOSITORY_SSH_KEY=
REPOSITORY_REMOTE_PATH=/data/arch32/mirror/archlinuxaba
REPOSITORY_NAME=archlinuxaba
REPOSITORY_URL=https://archlinux32.andreasbaumann.cc/archlinuxaba/
GPG_SIGNING_KEY=16194A82231E9EF823562181C8E8F5A0AF9BA7E7
SIGN_DATABASE=no
REPO_ARCHS=
REPOSITORY_HOST is the bare hostname; REPOSITORY_SSH_USER (blank =
ssh/rsync’s own default) is combined into REPO_SSH_TARGET
(user@host, computed in repo_lib.sh) used everywhere a connection
target is needed. Login as build’s own SSH identity worked, but
root@euroweb.lan is the account with actual write access to
REPOSITORY_REMOTE_PATH, hence the explicit user.REPOSITORY_SSH_KEY blank means use the default SSH identity/agent —
no -i flag passed.GPG_SIGNING_KEY is the existing personal key already in the
keyring (Andreas Baumann (sign) <mail@andreasbaumann.cc>).REPO_ARCHS, if set to more than one arch, must be quoted (e.g.
REPO_ARCHS="x86_64 i686") since this file is sourced as real shell.arch/scripts/)| script | purpose |
|---|---|
repo_lib.sh |
not executed directly; sourced by the others. Loads config/repo.conf, validates required keys are non-empty, computes REPO_SSH_TARGET (user@host), and exposes repo_ssh/repo_rsync wrapper functions (inject port+identity file) plus repo_add_remote <dir> [pkgfile...] (runs repo-add for REPOSITORY_NAME’s db in a remote dir, adding -s -k only if SIGN_DATABASE=yes; with zero pkgfile args it seeds a fresh empty db, a safe no-op if one already exists). |
repo_detect_archs.sh |
prints the union of arch=() values found under arch/*/*/PKGBUILD (minus any), or REPO_ARCHS if set. Used by both scaffolding and reporting. |
repo_create_remote.sh |
idempotent. For each arch from repo_detect_archs.sh: ssh mkdir -p ${REPOSITORY_REMOTE_PATH}/os/<arch>, then repo_add_remote with no files to seed an empty database there (so a fresh pacman.conf pointed at the repo doesn’t 404 before anything’s published). Safe to re-run whenever a new arch shows up in the tree. |
repo_build.sh <pkgdir> |
like the existing build_all_archs.sh, but a real build (no --nobuild). For arch=any (or an empty arch=()) packages: build once via the x86_64 build command only (an “any” package doesn’t need multiple chroots). For a normal multi-arch package: for each arch in its own arch=(), run that arch’s build command if a local one exists; otherwise print SKIPPED (no local archbuild) and continue — never aborts the whole run just because e.g. aarch64 isn’t buildable here. REPO_BUILD_ONLY_ARCH, if set, restricts this to just that one arch. The build command itself is resolved per-arch by a _build_cmd helper: x86_64 uses archlinuxaba-x86_64-build (see below), every other arch still uses plain extra-<arch>-build. Leaves the built *.pkg.tar.zst files in pkgdir. Exit status is non-zero only if zero archs built successfully. On success only (so a failed run’s logs survive for debugging), cleans up every untracked (git status --porcelain) byproduct from pkgdir – never anything git already tracks, or the *.pkg.tar.*/.sig outputs themselves. |
archlinuxaba-archbuild (symlinked as archlinuxaba-x86_64-build) |
a copy of the system /usr/bin/archbuild (devtools32), changed in one place: pacman_config is resolved from arch/config/pacman.conf.d/ first, falling back to the system /usr/share/devtools/pacman.conf.d/. arch/config/pacman.conf.d/archlinuxaba.conf is a copy of stock extra.conf with our own [archlinuxaba] repo (SigLevel = Optional TrustAll, Server = ${REPOSITORY_URL}os/$arch) added ahead of [core]/[extra] — mirroring devtools32’s own *-with-build-support config layering pattern. Uses its own chroot (/var/lib/archbuild/archlinuxaba-x86_64), never touches the stock extra-x86_64 chroot or anything under /usr. Fixes the intra-tree dependency-ordering gap (a package here depending on another custom package from this same tree, already built+published earlier in the same repo_release_all.sh run, used to fail with error: target not found) — see memory/TOOLING_NOTES.md and memory/kdstatemachineeditor.md. Only wired up for x86_64 so far. |
repo_sign.sh <pkgfile...> |
gpg --detach-sign --local-user "${GPG_SIGNING_KEY}" for each package file, producing .sig next to it. Refuses to overwrite an existing .sig unless -f is given. |
repo_publish.sh <pkgfile...> |
for each package file: derive its pkgname/arch from the filename. Before publishing a new version: repo-remove the pkgname from the affected db(s) (harmless no-op on a first-ever publish), and move any older real package file for that exact pkgname (matched by exact string equality on the parsed-out name, not a glob — see memory/repo_publish_archive_bug.md for why a glob is unsafe here) into ${REPOSITORY_REMOTE_PATH}/archive/ (a flat dir, sibling of os/, append-only — never delete/move anything back out of it, see memory/feedback_archive_append_only.md). Then: if it’s an any package, rsync it to one real arch dir (x86_64), then for every other arch dir, ssh ln -sf ../<chosen-arch>/<file> <path> (relative symlink, so no bytes are duplicated) for both the package and its .sig, clearing old symlinks the same exact-match way first. If it’s a real per-arch package: rsync pkg+sig straight to the matching os/<arch>/ dir only. Then repo_add_remote on every arch db that was touched. |
repo_release.sh <pkgdir> |
orchestrator, mirrors update_cycle.sh: build → sign → publish in one call for a single package directory. Once publish succeeds, removes the local *.pkg.tar.zst/.sig (safely on the repo host now) so the package directory doesn’t stay permanently dirty. |
repo_release_all.sh |
walks every PKGBUILD under arch/ (excluding attic/), running repo_release.sh for each that can build for REPO_BUILD_ONLY_ARCH (default x86_64); prints one PASS/FAIL/SKIP summary line per package to stdout and each package’s full build/sign/publish output to ../repo_release_logs/<relpath-with-underscores>.log. Reads its package list via fd 3, not stdin — repo_ssh’s ssh -n plus this keeps any subprocess from ever consuming the loop’s remaining input and silently truncating the run after one package (a real bug hit and fixed 2026-07-07). Safe to re-run. |
None of these touch git or the AUR — they only read PKGBUILD/build
output and talk to the private repo host.
# one-off / whenever a new arch appears
arch/scripts/repo_create_remote.sh
# per package release
cd arch/maintained/<pkg>
../../scripts/repo_release.sh .
# or, stepwise:
../../scripts/repo_build.sh .
../../scripts/repo_sign.sh *.pkg.tar.zst
../../scripts/repo_publish.sh *.pkg.tar.zst
set -u, explicit test "x$FOO" = "x" guards, errors to stderr with
ERROR: prefix and non-zero exit — same as add_maintained.sh /
del_maintained.sh.repo_create_remote.sh only ever mkdir -ps; it never deletes
anything remote.repo_sign.sh never clobbers an existing .sig silently.repo_publish.sh uses rsync (not blind scp overwrite) so partial
transfers are resumable/verifiable; repo-add itself is the only step
that mutates the remote database, and it’s idempotent (re-adding the
same version is a no-op).git add/git commit, per
CLAUDE.md.arch=any handling → symlink into other arch dirs (§3).arch/config/repo.conf.16194A82...BA7E7.repo_build.sh → skip and report, never
hard-fail; any/empty arch=() builds via x86_64 only.REPOSITORY_SSH_KEY blank.REPOSITORY_HOST=euroweb.lan,
REPOSITORY_REMOTE_PATH=/data/arch32/mirror/archlinuxaba,
REPOSITORY_URL=https://archlinux32.andreasbaumann.cc/archlinuxaba/.REPOSITORY_HOST (bare host) +
REPOSITORY_SSH_USER=root (write access to the remote path), combined
into REPO_SSH_TARGET in repo_lib.sh.SIGN_DATABASE=no), packages are
still always signed by repo_sign.sh. Traditional for repos like
this one.repo_create_remote.sh now seeds an empty
repo-add database per arch dir as part of scaffolding, not just the
directory.All scripts written under arch/scripts/, syntax-checked with
oksh -n, and functionally smoke-tested locally (fake extra-*-build
stubs for repo_build.sh’s skip/build logic, real gpg signing for
repo_sign.sh, a fake local ssh stub for repo_create_remote.sh’s
mkdir+empty-db-seed flow including idempotency). Not yet run against the
real euroweb.lan host by Claude — that’s for the user to do (or ask for
explicitly), since it’s a live remote system.