Scripts for building and releasing Debian/Ubuntu (.deb) packages for
Session-related software (oxen-core, session-storage-server, session-router, and
libraries such as libsession-util, oxen-encoding, oxen-mq, oxen-logging, libquic,
…).
This directory is the canonical working tree for packaging. Each package's
repository is cloned in as a git-ignored checkout (a subdirectory, e.g.
liboxenmq/, session-router/, oxen-core/). They are plain clones, not
submodules; clone whichever ones you need to work on:
git clone [email protected]:session-foundation/oxen-mq liboxenmqAll tools are run from this top-level directory with the checkout directory
as the first argument, e.g. ./deb-version-bump liboxenmq.
The actual debian packaging lives in debian/<codename> and ubuntu/<codename>
branches inside each package repo, managed with
git-buildpackage
(gbp). debian/sid is the primary branch; the others carry the same packaging
with a per-distro version suffix.
- Package versions get a debian revision:
1.3.0-1,-2, … (sid, no suffix). - Other distros append a suffix from
build-distros.bash:1.3.0-1~deb13(trixie),1.3.0-1~ubuntu2404(noble), etc. - The changelog distribution field is the codename (
trixie,noble, …), exceptdebian/sidwhich usesunstable. - Some libraries embed the soname version in the binary package name (e.g.
liboxenmq1.3.0) via adebian/control.intemplate plus adebian/update-lib-version.sh(orupdate-liboxen-ver.sh) script that regeneratesdebian/controlafter the changelog is bumped. The tools run that script automatically when present.
Builds happen in CI: the debian/* / ubuntu/* branches carry their own CI
config that builds the package and uploads the artifacts (via
debian/ci-upload.sh) to the builds file server. Pushing a branch triggers its
build — so none of these tools push automatically; you inspect locally and
push with deb-push when ready.
The packaging CI config is one of:
.woodpecker/override-deb.star(Woodpecker Starlark). The CI server's config extension (session-woodpecker-config) uses any.woodpecker/override*files in place of the rest of.woodpecker/, so upstream's own.woodpecker/configs merge into the packaging branches unchanged and conflict-free, and are ignored there.- A completely replaced
.drone.jsonnet, on branches not yet migrated. This only works while upstream has no.woodpecker/(or.woodpecker.*) config: once upstream moves to one, it takes priority and CI runs upstream's builds instead.deb-version-bumpwarns when a merge brings that in, anddeb-pushrefuses to push such a branch until it has anoverride-deb.star.
Both define the same settings as plain assignments the tools read: distro,
repo_suffix, the builder image (builder_image, distro_docker in jsonnet) and
the architectures built (an arches list, or the deb_pipeline(...) calls in
jsonnet).
The built packages are then copied into the reprepro repositories at
https://deb.session.foundation — this step is manual and stays manual
(it uses a signing key that is never stored unencrypted). There are three repos:
the root (public releases), /beta (semi-public testing), and /staging
(build-only, used to chain dependency builds); publish-debs.sh (see
Publishing) does the copying. Which repo a branch's CI build
pulls its dependencies from is set by repo_suffix in that branch's CI config.
All are run from this directory; the first argument is always the checkout dir.
None of them push (except deb-push); each prints the push command to run next.
Anywhere a branch or --only/glob is expected, a bare codename works as
shorthand for its full branch: sid → debian/sid, noble → ubuntu/noble,
etc. (the known codenames are the version_suffix keys in build-distros.bash;
they're unique across debian/ubuntu). So --only sid,trixie,forky and
./deb-push oxen-mq sid,noble both work.
New upstream release across all active distro branches. Reads the new version
from <source-ref>'s top-level CMakeLists.txt (project(... VERSION x.y.z)),
or takes it from --version; the package version becomes <version>-1<suffix>.
<source-ref> defaults to origin/stable. For each branch: merge upstream →
rebase & re-export the patch queue → changelog → regenerate control → commit.
Aborts (touching nothing) unless the new upstream version is greater than every
branch's current version.
To re-release an upstream version the branches already carry (e.g. upstream
re-tagged 1.3.2 and the packages are at 1.3.2-3), either pass --force, which
continues at the next -N (1.3.2-4), or give the revision explicitly with
--version 1.3.2-4. Either way the new version must still outrank every branch's
current one, because dch refuses anything else.
If an active distro branch doesn't exist yet, it's created as part of this
release rather than being a hard error. The existing branches are bumped first;
then the new branch is forked off its now-updated family base (debian/* from
debian/sid, ubuntu/* from the newest ubuntu/*) and given a debut changelog
entry at the new version — exactly what deb-add-distro does after a release, so
it debuts directly at the new version with no invented prior history. You're
prompted first (after a builder-image check); --create-missing skips the prompt
for non-interactive runs. A branch that can't be forked (no family base) is still
a hard error pointing at deb-add-distro.
Cherry-pick one or more source commits into the gbp patch queue across the
active branches, with a -N revision bump. --only <glob>[,…] restricts to
matching branches for a fix only some distros need; a partial run appends/bumps a
+M (leaving -N alone, so distro-upgrade ordering is preserved) instead of
bumping -N. --no-bump applies the patch with no version bump or changelog
entry at all (takes no -m) — use it when the current version was never
built/published, so the fix needs no new version to distinguish it (e.g. fixing a
build error on the distros whose build failed); combine with --only to target them.
No-change rebuild bump (Debian binNMU-style) — no source or packaging change,
just a version bump + changelog entry so CI rebuilds. Use it to relink against a
new system-library soname (e.g. libsodium moved on forky). -m defaults to
"Rebuild"; --only works as above (+M for a partial rebuild).
Propagate a packaging change (to debian/…) with a -N revision bump. First
commit your change on debian/sid yourself, then run this: it bumps sid's
changelog and cherry-picks the packaging commit(s) onto every other branch. With
no commit refs it auto-detects the packaging commits on sid since the last
changelog entry (and asks for confirmation if there's more than one).
HEAD-relative refs (HEAD, HEAD~2, @^, …) always resolve against
debian/sid, whatever branch the checkout has checked out — so
./deb-pkg-update <repo> HEAD --no-bump propagates sid's tip commit.
--no-bump cherry-picks the given commit(s) onto every active branch that lacks
them with no version bump and no changelog entry — for when a version bump is
coming separately and you don't want a throwaway -N. Explicit commit ref(s) are
required (they can live on any branch, e.g. a fix committed on one distro);
branches that already have the commit, or don't exist yet, are skipped; -m isn't
accepted.
Push branches to origin (triggering CI). Globs match the active distro list, e.g.
debian/sid, 'debian/*', 'ubuntu/*' (quote them), or bare codenames. Give
several space- or comma-separated (debian/sid forky or sid,forky). No
argument = all. If any glob matches nothing, nothing is pushed. Before pushing it
checks that CI would actually run each branch's packaging build, not
upstream's own CI (see the CI config notes above); if not, nothing is pushed. It
then runs a dependency pre-check: for each branch it verifies every versioned
build-dependency that comes from our repo — Session-family packages, and anything
else the repo carries for that distro, such as the ngtcp2 backports — is available
at the required version in that branch's target reprepro repo, for every
architecture that branch builds (per
its CI config, not just amd64); if any is missing on any built arch, nothing is
pushed (an unsatisfied dep is a guaranteed CI failure — publish the dependency
first, or drop that arch from the branch's CI config).
After pushing it watches the triggered CI builds to completion — the same
live, refreshing per-branch status display deb-cascade uses, with links to each
build — and exits non-zero if any build fails. It watches whether or not anything
was pushed, so if you interrupt the watch, re-running the same deb-push resumes
it. Pass --no-wait to skip the watch (it's also skipped automatically if
woodpecker-cli isn't installed or can't authenticate).
Restart CI builds without pushing anything, e.g. after fixing a CI-side problem
such as a broken upload step. Branch selection works like deb-push (default:
all). For each branch it restarts the latest pipeline for the commit on origin,
whatever its state, stopping it first if it's still running or queued, then
watches the new builds like deb-push does. To resume an interrupted watch,
re-run deb-push: re-running deb-ci-restart would restart everything again.
Create packaging for a new distro release. New debian/* branches fork from
debian/sid; new ubuntu/* branches fork from the newest existing ubuntu/*
branch. The ~suffix must already be defined in build-distros.bash. Makes the
three standard edits (the CI config's distro, debian/gbp.conf
debian-branch+dist, and a new changelog entry) and commits. It also checks
that the builder docker image for the new codename exists first. Remember to add
the new branch to the distros list in build-distros.bash when it should join
the regular build set.
To build a whole set of packages — e.g. add a new distro like ubuntu/resolute
everywhere — order matters: a package can't build until the Session-family
packages it build-depends on have been built by CI and uploaded to
/staging. Two tools handle this, driven by the build-order file (one line per
build step; repos on a line have no interdependency and build in parallel; each
line must be published to /staging before the next):
Create the branch in every repo in build-order (order-independent; skips repos
that already have it; does not push). Prep step before a cascade.
Push the branch across all repos in dependency order, one build-order line
at a time. For each line it: skips repos already in /staging at that version
(so it's safe to re-run after a failure), pushes the rest, watches each CI
build to completion (alerting on success, stopping on failure), then pauses for
you to upload the built packages to /staging (the manual signing step),
auto-rechecking until they appear before moving to the next line. Requires
woodpecker-cli, set up for the CI server (woodpecker-cli setup, or
WOODPECKER_SERVER/WOODPECKER_TOKEN).
So, to add resolute everywhere:
./deb-add-distro-all ubuntu/resolute # create + review branches locally
./deb-cascade ubuntu/resolute # push in order, monitor CI, pause for /stagingThese two run on the servers rather than in a packaging checkout's workflow.
Run by hand on the reprepro host: includes the latest CI build of every project
(or just the named ones, as builds-tree paths like session-foundation/liboxenmq)
into the repo, for every distro in build-distros.bash or just DISTRO.
DEBS_TO_REPO_SUFFIX picks /beta or /staging instead of the main repo. It
lists everything and waits for confirmation, signs each distribution once, and
stops at the first failure (a cancelled signing prompt included); re-running is
safe. Ubuntu .ddeb debug-symbol packages are published along with the .debs.
Host-specific paths come from ~/.publish-debs.conf (sourced as bash), which must
set BUILDS_DIR (the local copy of the builds tree), REPREPRO_DIR (the main
repo's reprepro base; the other repos are below it) and SYNC_DEST (an rsync
destination the whole tree is mirrored to afterwards, or empty to skip that when
publishing on the serving host itself).
Run every minute from cron on the builds file server: maintains the latest and
per-version symlinks in each distro directory of the builds tree (which
publish-debs.sh reads), plus some *-LATEST links for binary builds.
deb-version-bump, deb-add-patch, and deb-pkg-update operate over ~10
branches and can hit merge / rebase / cherry-pick conflicts. .drone.jsonnet
conflicts (on branches still using it) are auto-resolved (keep the packaging
branch's copy); anything else stops with instructions. Progress is recorded in
<repo>/.git/session-pkg-state. To resume: resolve the conflict, complete the
git operation (git merge/rebase/cherry-pick --continue), and re-run the exact
same command — it skips completed branches and continues. To abandon, delete
that state file.
Sourced by all tools. Defines:
version_suffix— every known distro's version suffix (includes old/future entries as a handy reference).distros— the branches the tools currently act on.skip_distros— per-repo opt-outs: an associative array (keyed by checkout dir, values space-separated full branch names) of distro branches a given repo does not build. The tools drop these fromdistrosfor that repo, sodeb-version-bumpwon't build or recreate them anddeb-pushwon't push them. Existing branches just go stale (delete them by hand if you want them gone).
The cross-repo dependency ordering used by deb-cascade (see above). Hand-
maintained; the header comment records the build-dep graph it was derived from.
Keep every repo on a line strictly later than all of its dependencies.
A one-shot, uncommitted helper that rewrites the dead builds.lokinet.dev
(and file-server oxen.rocks) hostnames to builds.session.codes across a
repo's branches. Not part of the maintained toolset — it stays untracked (not
git-ignored, so git status keeps it in view).