mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-18 00:57:55 +00:00
* ci(packaging): publish signed apt repository to etherpad.org/apt (closes #7610) Adds an `apt-publish` workflow job that turns the existing `.deb` build artefacts into a signed apt repository hosted at: https://etherpad.org/apt/ End-user install on any Debian/Ubuntu/Mint: curl -fsSL https://etherpad.org/key.asc \ | sudo gpg --dearmor -o /usr/share/keyrings/etherpad.gpg echo "deb [signed-by=/usr/share/keyrings/etherpad.gpg] \ https://etherpad.org/apt stable main" \ | sudo tee /etc/apt/sources.list.d/etherpad.list sudo apt update && sudo apt install etherpad `apt upgrade` works going forward — every tagged release republishes the repo metadata. Change type: patch (CI/distribution; no production behaviour change). ## Why etherpad.org/apt and not ether.github.io/etherpad/apt ether/etherpad's GitHub Pages is already configured as build-from-workflow on `develop` with CNAME `docs.etherpad.org`, and a repo can only have one Pages source. Pushing the apt repo to a gh-pages branch would either be ignored (Pages is reading from the docs workflow) or, if Pages were switched to it, would kill the docs site. ether/ether.github.com is a separate Next.js site that already deploys etherpad.org and serves `public/` verbatim, so cross-pushing the apt repo into `public/apt/` lands it at the canonical Etherpad URL with no infrastructure conflicts. ## What this PR ships 1. `apt-publish` job in `.github/workflows/deb-package.yml`. Runs after `release` on `v*` tag pushes: - Clones ether/ether.github.com over SSH using a deploy key. - Wipes site/public/apt/ and rebuilds it from the per-arch .deb artefacts using apt-ftparchive. - Signs Release + emits InRelease/Release.gpg using the keypair in APT_SIGNING_KEY. - Drops key.asc into site/public/key.asc. - Asserts both per-arch .debs are present before the wipe takes effect — refuses to publish a partial / empty repo if an artefact is missing or renamed. - Commits and pushes to master; the site repo's existing build pipeline picks it up. 2. `packaging/apt/key.asc` — Etherpad APT Repository public key, fingerprint 6953FA0C6431F30347D65B03AF0CD687D51A6E63. Served at https://etherpad.org/key.asc after the next release. 3. `packaging/apt/generate-signing-key.sh` — one-shot helper that generated the keypair, kept for documented future rotation. 4. `packaging/README.md` — apt-repo install recipe is now the recommended path. ## Required secrets before the next tagged release Two secrets on ether/etherpad before the next `v*` tag push: - APT_SIGNING_KEY — ASCII-armoured private key for the Etherpad APT Repository keypair (long key id AF0CD687D51A6E63), generated with packaging/apt/generate-signing-key.sh. - SITE_DEPLOY_KEY — SSH private key. The public half registered as a deploy key with WRITE access on ether/ether.github.com. If either is missing the job fails fast with a clear error. ## What this PR does not change - The release job still attaches both versioned (etherpad_<v>_<arch>.deb) and stable-aliased (etherpad-latest_<arch>.deb) artefacts to the GitHub Release. Anyone pulling from releases/latest/download/etherpad-latest_amd64.deb keeps working. - The build-job smoke test (start under systemd, /health, purge) is unchanged. - docs.etherpad.org is untouched; this PR never pushes to gh-pages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(packaging): emit unindented Release headers + tighten artefact glob Two corrections from a fresh Qodo review of the rebased apt-publish job: 1. The dists/${SUITE}/Release heredoc was indented with the workflow's YAML scope, which means the resulting file had 10-space-prefixed field lines (` Origin: Etherpad`). apt parsers reject any leading whitespace on header fields per RFC 822 / Debian control format, so the entire suite would have failed to parse on `apt update` even before checksums were appended. Replace the heredoc with `printf '%s\n' ...` so the indentation is entirely under workflow control and impossible to break with a future YAML re-indent. 2. Tighten the artefact glob from `etherpad_*_amd64.deb` to `etherpad_[0-9]*_amd64.deb`. The hyphen-separator distinction (etherpad_<v>_… vs etherpad-latest_…) already kept the alias out of the array — Qodo's analysis of a duplicate-Packages bug was incorrect. But pinning to a leading-digit version segment makes the contract explicit and defends against any future alias that accidentally lands on `dist/etherpad_<word>_<arch>.deb`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c55007361c
commit
0a2facb3fc
4 changed files with 291 additions and 1 deletions
168
.github/workflows/deb-package.yml
vendored
168
.github/workflows/deb-package.yml
vendored
|
|
@ -240,3 +240,171 @@ jobs:
|
|||
with:
|
||||
files: dist/*.deb
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
apt-publish:
|
||||
# Generates a signed apt repository (Packages.gz + Release/InRelease)
|
||||
# from the .deb artefacts and cross-pushes it into ether/ether.github.com
|
||||
# under public/apt/. The Next.js site that powers etherpad.org serves
|
||||
# public/ verbatim, so the repo lands at:
|
||||
#
|
||||
# https://etherpad.org/apt/ (apt repo root)
|
||||
# https://etherpad.org/key.asc (public key for `apt-key`/keyring)
|
||||
#
|
||||
# Tag pushes go into the `stable` suite. Required secrets:
|
||||
# APT_SIGNING_KEY ASCII-armoured private key for the Etherpad APT
|
||||
# Repository keypair (fingerprint
|
||||
# 6953FA0C6431F30347D65B03AF0CD687D51A6E63).
|
||||
# SITE_DEPLOY_KEY SSH private key matching a deploy key with write
|
||||
# access on ether/ether.github.com. The site repo
|
||||
# holds the public half.
|
||||
name: Publish apt repository to etherpad.org
|
||||
needs: release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout etherpad source (for packaging/apt/key.asc)
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Configure deploy key for ether/ether.github.com
|
||||
env:
|
||||
SITE_DEPLOY_KEY: ${{ secrets.SITE_DEPLOY_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${SITE_DEPLOY_KEY:-}" ]; then
|
||||
echo "::error::SITE_DEPLOY_KEY secret is not set on ether/etherpad."
|
||||
echo "::error::Add an SSH deploy key with write access on ether/ether.github.com and store the private key here."
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "${SITE_DEPLOY_KEY}" > ~/.ssh/id_deploy
|
||||
chmod 600 ~/.ssh/id_deploy
|
||||
ssh-keyscan -t ed25519,rsa github.com >> ~/.ssh/known_hosts 2>/dev/null
|
||||
cat > ~/.ssh/config <<'CFG'
|
||||
Host github.com
|
||||
HostName github.com
|
||||
User git
|
||||
IdentityFile ~/.ssh/id_deploy
|
||||
IdentitiesOnly yes
|
||||
CFG
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
- name: Clone ether/ether.github.com
|
||||
run: git clone --depth 1 git@github.com:ether/ether.github.com.git site
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: dist
|
||||
pattern: etherpad-*-deb
|
||||
merge-multiple: true
|
||||
|
||||
- name: Install apt-utils + gpg
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq apt-utils gnupg
|
||||
|
||||
- name: Import signing key
|
||||
env:
|
||||
APT_SIGNING_KEY: ${{ secrets.APT_SIGNING_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${APT_SIGNING_KEY:-}" ]; then
|
||||
echo "::error::APT_SIGNING_KEY secret is not set; cannot sign Release file."
|
||||
exit 1
|
||||
fi
|
||||
export GNUPGHOME="$(mktemp -d)"
|
||||
chmod 700 "${GNUPGHOME}"
|
||||
echo "GNUPGHOME=${GNUPGHOME}" >>"${GITHUB_ENV}"
|
||||
printf '%s' "${APT_SIGNING_KEY}" | gpg --batch --import
|
||||
# Sanity check: expected long key id.
|
||||
gpg --list-secret-keys --keyid-format=long | grep -q AF0CD687D51A6E63
|
||||
|
||||
- name: Generate apt repo metadata
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REPO=site/public/apt
|
||||
SUITE=stable
|
||||
COMP=main
|
||||
# Wipe any previous repo state so removed versions don't linger
|
||||
# in pool/. Packages.gz is regenerated from whatever is in pool/
|
||||
# right now, so this is the simplest correct option — alternative
|
||||
# is per-version diffing which is fragile.
|
||||
rm -rf "${REPO}"
|
||||
# We ship one architecture-agnostic suite with per-arch pools.
|
||||
# Layout: apt/dists/<suite>/main/binary-{amd64,arm64}/
|
||||
for arch in amd64 arm64; do
|
||||
mkdir -p "${REPO}/pool/main/e/etherpad" "${REPO}/dists/${SUITE}/${COMP}/binary-${arch}"
|
||||
done
|
||||
# Drop the .debs into pool/. The leading-digit pattern
|
||||
# excludes the etherpad-latest_*.deb filename aliases the
|
||||
# release job stages — apt resolves by package name + version,
|
||||
# not filename, so including the alias would create duplicate
|
||||
# Packages entries. (Also defends against any future alias that
|
||||
# accidentally lands on dist/etherpad_<word>_<arch>.deb.)
|
||||
shopt -s nullglob
|
||||
DEBS=(dist/etherpad_[0-9]*_amd64.deb dist/etherpad_[0-9]*_arm64.deb)
|
||||
shopt -u nullglob
|
||||
# Refuse to publish nothing. Without this, a missing or renamed
|
||||
# build artefact would wipe site/public/apt and push an empty,
|
||||
# signed apt repo — breaking `apt update` for every existing
|
||||
# subscriber until the next successful release.
|
||||
if [ ${#DEBS[@]} -lt 2 ]; then
|
||||
echo "::error::Expected per-arch .deb artifacts in dist/, found ${#DEBS[@]}: ${DEBS[*]:-<none>}"
|
||||
echo "::error::Refusing to publish a partial / empty apt repository."
|
||||
exit 1
|
||||
fi
|
||||
cp "${DEBS[@]}" "${REPO}/pool/main/e/etherpad/"
|
||||
# Generate per-arch Packages files.
|
||||
(
|
||||
cd "${REPO}"
|
||||
for arch in amd64 arm64; do
|
||||
apt-ftparchive --arch "${arch}" packages pool/main \
|
||||
> "dists/${SUITE}/${COMP}/binary-${arch}/Packages"
|
||||
gzip -kf "dists/${SUITE}/${COMP}/binary-${arch}/Packages"
|
||||
done
|
||||
# Generate the suite's Release file. The heredoc lines
|
||||
# MUST start at column 1 — apt parsers reject leading
|
||||
# whitespace on header fields (RFC 822 / Debian control).
|
||||
# printf is used over a heredoc to make that contract
|
||||
# impossible to lose to a future re-indent.
|
||||
printf '%s\n' \
|
||||
"Origin: Etherpad" \
|
||||
"Label: Etherpad" \
|
||||
"Suite: ${SUITE}" \
|
||||
"Codename: ${SUITE}" \
|
||||
"Architectures: amd64 arm64" \
|
||||
"Components: ${COMP}" \
|
||||
"Description: Etherpad official apt repository (${SUITE} channel)" \
|
||||
"Date: $(date -Ru)" \
|
||||
> "dists/${SUITE}/Release"
|
||||
# apt-ftparchive appends checksums.
|
||||
apt-ftparchive release "dists/${SUITE}" >> "dists/${SUITE}/Release"
|
||||
# Sign it (clear-signed InRelease + detached Release.gpg).
|
||||
gpg --default-key AF0CD687D51A6E63 --batch --yes \
|
||||
--clearsign -o "dists/${SUITE}/InRelease" "dists/${SUITE}/Release"
|
||||
gpg --default-key AF0CD687D51A6E63 --batch --yes \
|
||||
-abs -o "dists/${SUITE}/Release.gpg" "dists/${SUITE}/Release"
|
||||
)
|
||||
|
||||
- name: Stage public key alongside the site
|
||||
run: |
|
||||
# Users curl this to add our key to their keyring before apt update.
|
||||
cp packaging/apt/key.asc site/public/key.asc
|
||||
|
||||
- name: Commit + push to ether/ether.github.com
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd site
|
||||
git -c user.email=actions@github.com -c user.name='github-actions[bot]' \
|
||||
add public/apt public/key.asc
|
||||
if git diff --cached --quiet; then
|
||||
echo "No apt-repo changes to publish."
|
||||
exit 0
|
||||
fi
|
||||
git -c user.email=actions@github.com -c user.name='github-actions[bot]' \
|
||||
commit -m "apt: publish Etherpad ${TAG}"
|
||||
git push origin HEAD:master
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue