diff --git a/.github/workflows/publish-ppa.yaml b/.github/workflows/publish-ppa.yaml index cffdd40f..5ffb5868 100644 --- a/.github/workflows/publish-ppa.yaml +++ b/.github/workflows/publish-ppa.yaml @@ -16,11 +16,9 @@ concurrency: cancel-in-progress: false env: - # Ubuntu series to target, as : pairs. Zeal needs Qt >= 6.4.2, - # so 24.04 (noble) is the floor — 22.04 ships Qt 6.2 and cannot build. The numeric - # version forms the ~ubuntu suffix (Launchpad's recommended scheme, which - # sorts correctly across series upgrades); the codename is the changelog - # distribution. Keep trimmed to supported series; Launchpad rejects EOL/unknown ones. + # Ubuntu series as : pairs. 24.04 (noble) is the floor; 22.04's + # Qt 6.2 is too old. forms the ~ubuntu suffix, is the + # changelog distribution. Launchpad rejects EOL or unknown series. SERIES: "noble:24.04 questing:25.10 resolute:26.04" PPA: "ppa:zealdocs/ppa" @@ -35,8 +33,8 @@ jobs: steps: - name: Determine version id: version - # Pass untrusted event/input values through env, never interpolated into - # the script body, then validate before use to block shell injection. + # Untrusted event/input values go through env and are validated before use + # to block shell injection. env: DISPATCH_VERSION: ${{ inputs.version }} RELEASE_TAG: ${{ github.event.release.tag_name }} @@ -47,7 +45,7 @@ jobs: else version="$RELEASE_TAG" fi - version="${version#v}" # tolerate an optional leading v from either source + version="${version#v}" # tolerate an optional leading v case "$version" in ''|*[!0-9A-Za-z.+~-]*) echo "::error::Unexpected version string: '${version}'" @@ -93,10 +91,9 @@ jobs: fi echo "GPG_KEYID=${keyid}" >> "$GITHUB_ENV" - # dpkg-buildpackage -p takes a single token (no spaces), so wrap gpg in - # a script that adds loopback signing and the passphrase when one is set. - # Written with printf so the shebang lands at column 0 (a leading-space - # shebang is not honoured). + # dpkg-buildpackage -p takes one token, so wrap gpg in a script that adds + # loopback signing (and the passphrase when set). printf keeps the shebang + # at column 0. if [ -n "$GPG_PASSPHRASE" ]; then printf '%s' "$GPG_PASSPHRASE" > ~/.ppa-passphrase chmod 600 ~/.ppa-passphrase @@ -140,8 +137,8 @@ jobs: first=true status=0 for entry in $SERIES; do - series="${entry%%:*}" # codename, e.g. noble — the changelog distribution - relver="${entry##*:}" # version, e.g. 24.04 — the ~ubuntu suffix + series="${entry%%:*}" # codename, e.g. noble + relver="${entry##*:}" # version, e.g. 24.04 ppa_ver="1:${VERSION}-0ubuntu1~ubuntu${relver}.1" file_ver="${VERSION}-0ubuntu1~ubuntu${relver}.1" # epoch is stripped from filenames echo "::group::${series} (${relver})" @@ -150,10 +147,8 @@ jobs: tar -C "$src_root" -xzf "$src_root/zeal_${VERSION}.orig.tar.gz" cp -r pkg/ppa/debian "$work/debian" - # Write the changelog for this series. The ~ubuntu.1 suffix - # (numeric series version) keeps the version unique and correctly ordered - # across series upgrades, and lets the official archive reclaim users once - # it ships the same upstream. + # Per-series changelog. The ~ubuntu.1 suffix orders correctly + # across series upgrades and lets the archive reclaim users later. { printf 'zeal (%s) %s; urgency=medium\n\n' "$ppa_ver" "$series" printf ' * New upstream release %s.\n\n' "$VERSION" @@ -168,12 +163,9 @@ jobs: tarball_opt=-sd fi - # One failing series (e.g. an EOL or mistyped entry Launchpad rejects) must - # not abort the rest, so guard the build+upload and record the failure. The - # if-condition context suspends set -e, so a failure falls through to else. - # debuild options precede dpkg-buildpackage options (-S/-sa/-k/-p). Flip - # `first` only on success, so the orig tarball still ships with the next - # series if the first one failed before uploading it. + # Guard so one rejected series doesn't abort the rest (the if-context + # suspends set -e). Flip `first` only on success, so the orig tarball + # still ships with the next series if this upload failed. if ( cd "$work" && debuild --no-lintian -S "$tarball_opt" -k"$GPG_KEYID" -p"$HOME/.gpg-sign" ) \ && dput "$PPA" "$src_root/zeal_${file_ver}_source.changes"; then first=false diff --git a/pkg/ppa/README.md b/pkg/ppa/README.md index 0ab352f2..88d11494 100644 --- a/pkg/ppa/README.md +++ b/pkg/ppa/README.md @@ -1,126 +1,93 @@ # Ubuntu PPA packaging -Debian/Ubuntu source packaging for Zeal, plus the automation that publishes it -to Launchpad. Two flows feed two PPAs: +Debian/Ubuntu source packaging for Zeal and the automation that publishes it to +Launchpad. Two flows feed two PPAs: -| Flow | PPA | Trigger | Signed by | -| ------- | ------------------------------ | ----------------------------------------- | ----------------- | -| Release | `ppa:zealdocs` | `publish-ppa.yaml` on each GitHub release | repo GPG secret | -| Nightly | `ppa:zealdocs/nightly` | Launchpad daily-build recipe (`main`) | Launchpad buildds | +| Flow | PPA | Trigger | Signed by | +| ------- | ---------------------- | ----------------------------------------- | ----------------- | +| Release | `ppa:zealdocs` | `publish-ppa.yaml` on each GitHub release | repo GPG secret | +| Nightly | `ppa:zealdocs/nightly` | Launchpad recipe (`main`) | Launchpad buildds | -The stable PPA is the team's default (named `ppa`), so `ppa:zealdocs` is shorthand -for `ppa:zealdocs/ppa`. +`ppa:zealdocs` is shorthand for the team's default PPA, `ppa:zealdocs/ppa`. Launchpad +builds the binaries from the uploaded source package, once per series and architecture. -Launchpad builds the binaries from the uploaded *source* package, once per -target series, for every architecture the PPA enables. +## Files -## Layout - -- `debian/` — the Debian source package (Qt 6). `rules` is a thin `dh` wrapper - because CMake already installs the binary, `.desktop`, AppStream metainfo, and - icons under `/usr` (see `assets/freedesktop/CMakeLists.txt`). -- `zeal.recipe` — the Launchpad git-build recipe used for nightly builds. +- `debian/` is the Debian source package (Qt 6). `rules` is a thin `dh` wrapper, since + CMake installs the binary, `.desktop`, metainfo, and icons under `/usr`. +- `zeal.recipe` is the Launchpad recipe for nightly builds. ## Target series -Zeal requires **Qt ≥ 6.4.2** (`src/app/CMakeLists.txt`), so **24.04 (noble)** is -the oldest buildable series; 22.04 (Qt 6.2) cannot build it. Targets are noble -and every newer supported series. - -For releases, the series list is the single `SERIES` variable at the top of -`.github/workflows/publish-ppa.yaml` — trim or extend it as releases come and go. -Launchpad rejects uploads for EOL or unknown series, so a stale entry only fails -its own upload. For nightlies, the series are chosen in the recipe's settings on -Launchpad. +The series live in the `SERIES` variable at the top of `publish-ppa.yaml`. The floor is +24.04 (noble); 22.04's Qt 6.2 is too old (Zeal needs Qt >= 6.4.2). Launchpad rejects EOL +or unknown series, so a stale entry only fails its own upload. Nightly series are set in +the recipe on Launchpad. ## Release uploads (`publish-ppa.yaml`) -On a published release (or via **Run workflow** with a version), the workflow -builds a GPG-signed source package per series and `dput`s it to -`ppa:zealdocs/ppa`, versioned `1:-0ubuntu1~ubuntu.1` -(e.g. `1:0.8.2-0ubuntu1~ubuntu24.04.1`). The `1:` epoch is mandatory, not -cosmetic: the official archive package carries it too (`1:0.7.x`), so without -it a PPA build would sort *below* the archive and users would never receive it. -The numeric `~ubuntu` suffix (Launchpad's recommended scheme) orders -correctly across series upgrades and lets the archive reclaim users once it -ships the same upstream. +On a release (or manual run with a version), the workflow builds a GPG-signed source +package per series and `dput`s it to `ppa:zealdocs/ppa`, versioned +`1:-0ubuntu1~ubuntu.1`. The `1:` epoch is required: the official archive +package carries it (`1:0.7.x`), so without it a PPA build would sort below the archive +and never reach users. The `~ubuntu` suffix orders correctly across series +upgrades. ### One-time setup (releases) -1. Create a dedicated, passphrase-less ed25519 signing key. It **must include an - encryption subkey** — Launchpad confirms key ownership with an *encrypted* - email, so a sign-only key cannot be registered: +1. Create a passphrase-less ed25519 signing key with an **encryption subkey**. Launchpad + confirms ownership via an encrypted email, so a sign-only key can't be registered: ```sh gpg --quick-generate-key "Zeal Release " ed25519 sign never - gpg --quick-add-key cv25519 encr never # encryption subkey + gpg --quick-add-key cv25519 encr never ``` - Leave the passphrase empty — in CI it would live in the same secret store as - the key, so it adds no security. This is the project's general release-signing - key and can also sign other artifacts (e.g. AppImages). - -2. Publish the **public** key and register it on Launchpad. It must be registered - to a Launchpad account that has signed the Ubuntu Code of Conduct and has - upload rights to `ppa:zealdocs` — and the key's UID email must be a verified - email on that account: +2. Publish the public key and register it on Launchpad. The account must have signed the + Ubuntu CoC and have upload rights to `ppa:zealdocs`, and the key's UID email must be + verified on that account: ```sh gpg --keyserver keyserver.ubuntu.com --send-keys ``` - Then add the fingerprint at and confirm - the encrypted email Launchpad sends to the UID address. (keyserver.ubuntu.com - is append-only, so published UIDs are permanent — only put role addresses on - the key, never a personal one.) -3. Add the repository secret: - - `RELEASE_GPG_PRIVATE_KEY` — ASCII-armored private key - (`gpg --armor --export-secret-keys `). - - `RELEASE_GPG_PASSPHRASE` — only if the key has a passphrase (not recommended; - see above). + Add the fingerprint at and confirm the email + Launchpad sends. keyserver.ubuntu.com is append-only, so put only role addresses on + the key, never a personal one. +3. Add the secret `RELEASE_GPG_PRIVATE_KEY` (armored key from + `gpg --armor --export-secret-keys `). Set `RELEASE_GPG_PASSPHRASE` only if the + key has one (not recommended). ## Nightly builds (Launchpad recipe) -Launchpad rebuilds nightlies itself from `main` — no GitHub cron and no signing -secret, since Launchpad's build farm signs internally. It only rebuilds when -`main` has new commits. +Launchpad rebuilds nightlies from `main` itself, when `main` changes. No GitHub cron or +signing secret, since the buildds sign internally. ### One-time setup (nightly) -1. Create the PPA `ppa:zealdocs/nightly`. -2. Import the GitHub repo into Launchpad as a Git repository (Code → Import), so - `lp:zeal` is available to recipes. -3. Create a new source package recipe - () and paste the contents of - `zeal.recipe`. The recipe nests `pkg/ppa/debian` into `debian/` via - `nest-part` and derives the version from `deb-version` - (`1:{debupstream}-0~{revtime}`). The `-0~{revtime}` keeps each nightly below - the next stable release and keys off the commit time; the `1:` epoch keeps it - above the archive. -4. Set the recipe to build into `nightly`, select the target series, and - tick **Build daily**. -5. Request an initial build to confirm it produces binaries. +1. Create `ppa:zealdocs/nightly`. +2. Import the GitHub repo into Launchpad (Code -> Import) so `lp:zeal` is available. +3. Create a recipe () from `zeal.recipe`. It + nests `pkg/ppa/debian` into `debian/` and versions via `1:{debupstream}-0~{revtime}`, + keeping each nightly below the next release and above the archive. +4. Set it to build into `nightly`, pick the series, and tick **Build daily**. +5. Request an initial build to confirm it works. ## Local testing -Build a source package the way the workflow does, then build the binary in a -clean chroot: +Build a source package as the workflow does, then build the binary in a clean chroot: ```sh -VERSION=0.8.2 +VERSION=$(git describe --tags --abbrev=0 | sed 's/^v//') ZEAL=~/zeal # your checkout -# Pristine upstream tarball, placed in /tmp — debuild looks for the .orig tarball -# in the build dir's parent. Use the release tag, or HEAD/a branch to test -# unreleased changes. -git -C "$ZEAL" archive --prefix="zeal-${VERSION}/" "v${VERSION}" \ - | gzip -9 > "/tmp/zeal_${VERSION}.orig.tar.gz" - +# debuild looks for the .orig tarball in the parent directory. +git -C "$ZEAL" archive --prefix="zeal-${VERSION}/" "v${VERSION}" | gzip -9 \ + > "/tmp/zeal_${VERSION}.orig.tar.gz" cd /tmp && tar xf "zeal_${VERSION}.orig.tar.gz" && cd "zeal-${VERSION}" cp -r "$ZEAL/pkg/ppa/debian" debian -# Overwrite the seed changelog with a target-series entry, exactly as the workflow -# does. (debian/changelog already exists in pkg/ppa/debian, so `dch --create` fails.) +# Overwrite the seed changelog with a target-series entry, as the workflow does. cat > debian/changelog < $(date -R) EOF -debuild -S -us -uc # build an unsigned source package -sudo pbuilder build ../zeal_*.dsc # build the binary against noble +debuild -S -us -uc # unsigned source package +sudo pbuilder build ../zeal_*.dsc # binary against noble ```