No description
  • Python 95.9%
  • Shell 2.3%
  • Dockerfile 1.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Christian Pointner 9cd8f9ceaf
Some checks failed
images / test (push) Successful in 39s
images / images (arm64-host) (push) Failing after 18m35s
images / images (amd64-host) (push) Failing after 20m12s
images / manifest (push) Has been cancelled
add missing files for oci-extract
2026-10-24 16:48:51 +02:00
.forgejo/workflows move REGISTRY_USER to variables 2026-10-17 22:57:29 +02:00
bin add missing files for oci-extract 2026-10-24 16:48:51 +02:00
debbuild add missing files for oci-extract 2026-10-24 16:48:51 +02:00
images add oci-extract 2026-10-24 16:47:04 +02:00
tests add missing files for oci-extract 2026-10-24 16:48:51 +02:00
workflows fix concurrent uploads and i386, armhf builds 2026-10-18 04:10:49 +02:00
.gitignore initial commit 2026-10-17 22:15:47 +02:00
config.yml fix concurrent uploads and i386, armhf builds 2026-10-18 04:10:49 +02:00
README.md add oci-extract 2026-10-24 16:47:04 +02:00

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

  1. aptrepo: apply the patches (pool collisions, .buildinfo archiving, empty indices); add i386/armhf to a dist's architectures where wanted.
  2. Repo server: user debbuild with write access to incoming/<dist>/ (SSH on another port than 22: set repo.upload_port in config.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)
  3. Build key: gpg --quick-gen-key "debbuild <…>" ed25519 sign never (no passphrase); public part to aptrepo's signer_keyring, fingerprint to allowed_signers.
  4. Forgejo org debbuild: secrets DEBBUILD_SIGNING_KEY, DEBBUILD_SSH_KEY; variables DEBBUILD_IMAGE, DEBBUILD_SSH_KNOWN_HOSTS. Same DEBBUILD_IMAGE and REGISTRY_USER variables plus REGISTRY_TOKEN secrets for infra/debbuild.
  5. Runners: labels amd64 and arm64 for job containers on the two VMs; an extra host-mode runner per VM registered to infra/debbuild only (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).
  6. This repo: fill in config.yml, add images/repo-key.asc, run bin/debbuild gen-images, push. The images workflow builds <image>:<dist>.
  7. Component repos: debbuild.yml, .forgejo/workflows/build.yml (copy of workflows/component.yml), package folders.

Tests

python3 -m unittest discover -t . -s tests -v