- Python 95.9%
- Shell 2.3%
- Dockerfile 1.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| bin | ||
| debbuild | ||
| images | ||
| tests | ||
| workflows | ||
| .gitignore | ||
| config.yml | ||
| README.md | ||
infra/debbuild
Builds and imports Debian/Ubuntu packages for an aptrepo
repository from Forgejo Actions. A push to a component repo (debbuild/<component>)
builds or imports every package version the repo doesn't have yet, signs the
.changes with the build key and uploads it to incoming/<dist>/. Publishing
stays manual: aptrepo ingest with your forwarded gpg-agent.
Every run is idempotent. A target is skipped when its version is already in
the repo's Packages index or its .changes is waiting in incoming/<dist>/,
so re-running a workflow retries exactly what failed.
Layout
debbuild/ the tool (Python 3.11+, distro packages only)
bin/debbuild entry point
config.yml dists, architectures, mirrors, repo endpoints
images/Dockerfile builder image, one per dist and platform
images/build.sh builds/pushes images from images/targets.tsv
(tags <image>:<dist>-<arch>; build jobs use these)
images/sources/ apt sources per dist+arch (generated, see below)
images/targets.tsv what images exist (generated)
images/repo-key.asc public part of the repo signing key (you add this)
workflows/component.yml the workflow every component repo uses
.forgejo/workflows/images.yml tests + image builds for this repo
tests/ unit tests
Build jobs run in <image>:<dist>-<arch>; <image>:<dist> is a multi-platform
tag of the amd64/arm64 images for the plan and import jobs. Runners always
pull the host's platform, so the i386/armhf images are declared
linux/amd64/linux/arm64 with an i386/armhf system inside (see
images/Dockerfile).
After changing config.yml, run bin/debbuild gen-images -c ./config.yml and
commit the result; CI fails while the generated files are stale.
Package folders
A component repo has a debbuild.yml at the root and one folder per package:
| Folder contains | Type | What happens |
|---|---|---|
debian/ (+ optional package.yml) |
built | dpkg-buildpackage per dist and arch; version gets the dist suffix (3.15.0-1+deb13) |
package.yml with import.url |
URL import | upstream .deb downloaded and checked against a signature or sha256sums |
package.yml with import.apt |
APT import | .deb fetched from the upstream APT repo with python-apt, verified through its signed index |
# debbuild.yml
component: observability
dists: ["debian>=12", "ubuntu>=24.04"] # optional default for all packages
# <pkg>/package.yml for a built package (optional)
dists: ["debian>=13", "!noble"]
architectures: [amd64, arm64] # narrows what debian/control allows
# <pkg>/package.yml for an APT import
import:
apt:
url: "https://packages.fluentbit.io/{vendor}/{dist}"
suite: "{dist}"
components: [main]
keyring: fluentbit.asc # in the package folder
dist-map: {resolute: noble} # optional
packages: [fluent-bit]
versions: ["4.1.1"]
architectures: [amd64, arm64]
# <pkg>/package.yml for a URL import
import:
url:
template: "https://github.com/TecharoHQ/anubis/releases/download/v{version}/anubis_{version}_{arch}.deb"
arch-map: {} # e.g. {i386: "386"}
verify: {signature: "{url}.asc", keyring: keyring.gpg} # or {sha256sums: sha256sums.txt}
package: anubis
versions: ["1.24.0"]
architectures: [amd64, arm64]
Dist selectors: a dist name, a vendor (debian), a vendor with a comparison
against a version or a dist name of that vendor (debian>=13, debian>=trixie,
ubuntu<26.04), ! to exclude. Codenames are resolved through config.yml, so
they must be configured dists: after removing a dist from the config, selectors
naming it fail until they are changed (numeric ones keep working). Unknown keys and selectors
that match nothing are errors.
Imports of architecture-independent packages use architectures: [all]; they
are fetched once per dist by the amd64 job and appear in every architecture's
index.
Rules for built packages: only the top changelog entry is built; changing an existing version's packaging needs a revision bump. arch:all binaries are built only by the amd64 job.
Binaries from container images
For software that is only published as container images (and can't
reasonably be built from source), a built package can take files out of the
upstream image with debbuild-oci-extract, which is in the builder images:
# debian/rules
override_dh_auto_build:
debbuild-oci-extract --image ghcr.io/anatol/pacoloco --digests oci-digests.txt \
--version $(DEB_VERSION_UPSTREAM) --arch $(DEB_HOST_ARCH) \
--path /pacoloco/pacoloco --dest build
oci-digests.txt pins every version to the image's digest, like
sha256sums.txt pins tarballs. Get the line for a new version with
debbuild oci-digest ghcr.io/anatol/pacoloco:1.9 # prints: 1.9 sha256:…
(also works from the builder image: docker run --rm <image>:trixie-amd64 debbuild oci-digest …).
Everything downloaded is verified against that pin (index, platform manifest,
every layer); a version without a pin fails the build. --arch picks the
platform from the image (amd64, arm64, armhf = arm/v7, i386 = 386). --path
can be given several times and may be a file or a directory; symlinks inside
the image are followed. The extracted binaries are checked: an ELF file for
the wrong architecture fails, and one linked against musl prints a warning,
since the package then needs Depends: musl. Public images only (anonymous
pull tokens, as ghcr.io, Docker Hub and quay.io hand out).
Commands
debbuild plan [--github-output FILE] what is missing; emits the job matrix
debbuild build --dist D --arch A [PKG...] build what is missing for D/A
debbuild import [--dist D] [PKG...] run missing imports
debbuild gen-images [--check] regenerate images/ files from config.yml
debbuild selftest [--dist D --arch A] check the environment
debbuild oci-digest IMAGE:TAG [--version V] print the oci-digests.txt line for an image
debbuild oci-extract … (= debbuild-oci-extract) extract files from a pinned image
Common options: -c CONFIG, -C REPO_DIR, --repo-url URL (e.g. file:///srv/apt/repo).
build/import upload only with --upload or DEBBUILD_UPLOAD=true, and
then need DEBBUILD_SIGNING_KEY, DEBBUILD_SSH_KEY and DEBBUILD_SSH_KNOWN_HOSTS.
Local build without CI (no upload):
docker run --rm -v $PWD:/src -w /src forgejo.example.org/infra/debbuild:trixie \
debbuild build --dist trixie --arch amd64 prom-server
Setup checklist
- aptrepo: apply the patches (pool collisions,
.buildinfoarchiving, empty indices); addi386/armhfto a dist'sarchitectureswhere wanted. - Repo server: user
debbuildwith write access toincoming/<dist>/(SSH on another port than 22: setrepo.upload_portinconfig.yml),authorized_keys:command="rrsync -no-lock -no-del /srv/apt/incoming",restrict ssh-ed25519 AAAA…(-no-lock: parallel build jobs connect at the same time) - Build key:
gpg --quick-gen-key "debbuild <…>" ed25519 sign never(no passphrase); public part to aptrepo'ssigner_keyring, fingerprint toallowed_signers. - Forgejo org
debbuild: secretsDEBBUILD_SIGNING_KEY,DEBBUILD_SSH_KEY; variablesDEBBUILD_IMAGE,DEBBUILD_SSH_KNOWN_HOSTS. SameDEBBUILD_IMAGEandREGISTRY_USERvariables plusREGISTRY_TOKENsecrets forinfra/debbuild. - Runners: labels
amd64andarm64for job containers on the two VMs; an extra host-mode runner per VM registered toinfra/debbuildonly (amd64-host,arm64-host) for the image builds. The registry package must be pullable by the runners' docker (public, or docker login on the VMs). - This repo: fill in
config.yml, addimages/repo-key.asc, runbin/debbuild gen-images, push. The images workflow builds<image>:<dist>. - Component repos:
debbuild.yml,.forgejo/workflows/build.yml(copy ofworkflows/component.yml), package folders.
Tests
python3 -m unittest discover -t . -s tests -v