Skip to content

ci(packaging): publish signed apt repository to etherpad.org/apt (closes #7610)#7624

Merged
JohnMcLear merged 2 commits intoether:developfrom
JohnMcLear:ci/apt-publish
Apr 28, 2026
Merged

ci(packaging): publish signed apt repository to etherpad.org/apt (closes #7610)#7624
JohnMcLear merged 2 commits intoether:developfrom
JohnMcLear:ci/apt-publish

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

@JohnMcLear JohnMcLear commented Apr 28, 2026

Summary

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 behavior change).

Why etherpad.org/apt and not ether.github.io/etherpad/apt

The original draft pushed to a gh-pages branch on ether/etherpad.
That doesn't work in this repo: GitHub Pages here is configured as
build-from-workflow on develop with CNAME docs.etherpad.org, and
a repo can only have one Pages source. Pushing to gh-pages would
either be ignored, 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, with a public/ directory served verbatim.
Cross-pushing the apt repo into public/apt/ lands it at the
canonical Etherpad URL, doesn't touch docs.etherpad.org, and reuses
existing build infrastructure.

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.
    • 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 (already used to produce the key currently in APT_SIGNING_KEY). Documented for future key rotation.
  4. packaging/README.md — apt-repo install recipe is now the recommended path.

Required secrets before the next tagged release

The apt-publish job needs two secrets on ether/etherpad:

  1. 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.
  2. SITE_DEPLOY_KEY — SSH private key. The public half must be registered as a deploy key with write access on ether/ether.github.com (Settings → Deploy keys → "Allow write access").

Setup steps for the deploy key (maintainer):

ssh-keygen -t ed25519 -f /tmp/etherpad-site-deploy -N ''
# Paste /tmp/etherpad-site-deploy.pub on ether/ether.github.com (write access)
# Paste /tmp/etherpad-site-deploy as SITE_DEPLOY_KEY secret on ether/etherpad
shred -u /tmp/etherpad-site-deploy*

If either secret is missing, the job fails fast with a clear error — it won't silently skip.

What this PR does not change

  • The existing release job still attaches both versioned (etherpad_<v>_<arch>.deb) and stable-aliased (etherpad-latest_<arch>.deb) artefacts to the GitHub Release. Anyone currently pulling from releases/latest/download/etherpad-latest_amd64.deb keeps working.
  • The smoke-test in the build job (start the service under systemd, hit /health, purge) is unchanged — every PR still validates the package end-to-end.
  • docs.etherpad.org is untouched; this PR never pushes to ether/etherpad's gh-pages (no such branch exists here).

Test plan

  • YAML parses
  • Build matrix (Build .deb amd64 / arm64) passes
  • Smoke test in the build job exercises systemd start + /health
  • After merge: tag a release, confirm apt-publish succeeds, that etherpad.org/apt/ and etherpad.org/key.asc resolve, and that the install one-liner above produces a working install on a fresh Debian/Ubuntu container.

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Publish signed apt repository to GitHub Pages

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds apt-publish workflow job to publish signed Debian repository
• Generates Packages.gz and signs Release/InRelease files with GPG
• Publishes repository to gh-pages branch served at https://ether.github.io/etherpad/apt/
• Provides 3-line end-user installation recipe with automatic apt upgrade support
Diagram
flowchart LR
  A["Release Job"] -->|produces .deb artifacts| B["apt-publish Job"]
  B -->|generates Packages.gz| C["Signed Repository"]
  B -->|signs Release/InRelease| C
  C -->|pushes to gh-pages| D["GitHub Pages"]
  D -->|serves at /apt/| E["End Users"]
  E -->|curl key.asc + apt install| F["Etherpad Installed"]
Loading

Grey Divider

File Changes

1. packaging/apt/generate-signing-key.sh 📝 Documentation +90/-0

APT repository signing key generation helper

• One-time helper script to generate Ed25519 GPG keypair for apt repository signing
• Creates private and public key files with configurable name, email, and expiry
• Uses temporary GNUPGHOME to avoid polluting user's keyring
• Outputs instructions for uploading private key as GitHub secret and committing public key

packaging/apt/generate-signing-key.sh


2. .github/workflows/deb-package.yml ✨ Enhancement +129/-0

Add apt repository publishing workflow job

• Adds new apt-publish job that runs after release on version tags
• Initializes gh-pages branch if missing and downloads .deb artifacts
• Generates apt repository structure with per-architecture Packages files using apt-ftparchive
• Signs Release metadata with GPG (clear-signed InRelease + detached Release.gpg)
• Stages public key and commits/pushes repository to gh-pages branch

.github/workflows/deb-package.yml


3. packaging/README.md 📝 Documentation +19/-1

Document apt repository installation method

• Adds recommended installation method via signed apt repository with 3-line recipe
• Documents GPG key import and sources.list configuration for Debian/Ubuntu/Mint
• Explains automatic apt upgrade support going forward
• Preserves existing single .deb installation as alternative method

packaging/README.md


View more (1)
4. packaging/apt/key.asc ⚙️ Configuration changes +14/-0

Add APT repository public signing key

• Commits public key for Etherpad APT Repository (Ed25519, fingerprint
 6953FA0C6431F30347D65B03AF0CD687D51A6E63)
• Served at https://ether.github.io/etherpad/key.asc for end-user GPG keyring import
• Corresponds to private key stored in APT_SIGNING_KEY GitHub secret

packaging/apt/key.asc


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Apr 28, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Latest alias duplicated 🐞 Bug ≡ Correctness
Description
apt-publish claims to skip etherpad-latest_*.deb aliases, but the glob it uses will include
them, so apt-ftparchive will emit duplicate entries for the same package/version and can break
apt update for repo users.
Code

.github/workflows/deb-package.yml[R326-332]

+          # Drop the .debs into pool/. Skip the etherpad-latest_*.deb
+          # filename aliases — apt resolves by package name + version,
+          # not filename, and including the alias would create a
+          # duplicate Packages entry.
+          shopt -s nullglob
+          DEBS=(dist/etherpad_*_amd64.deb dist/etherpad_*_arm64.deb)
+          shopt -u nullglob
Evidence
The release job explicitly creates dist/etherpad-latest_{amd64,arm64}.deb, and apt-publish
later collects dist/etherpad_*_{amd64,arm64}.deb, which matches both the versioned files and the
etherpad-latest_*.deb aliases, contradicting the comment and leading to duplicates in Packages
metadata.

.github/workflows/deb-package.yml[211-222]
.github/workflows/deb-package.yml[326-332]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The apt repo publish job is intended to exclude `etherpad-latest_*.deb` alias files, but the current glob includes them, which will generate duplicate package entries in `Packages`.
### Issue Context
The `release` job creates stable aliases (`etherpad-latest_amd64.deb`, `etherpad-latest_arm64.deb`). The `apt-publish` job then builds an apt repository by globbing `dist/etherpad_*_amd64.deb` and `dist/etherpad_*_arm64.deb`, which also matches the alias files.
### Fix Focus Areas
- .github/workflows/deb-package.yml[211-222]
- .github/workflows/deb-package.yml[326-342]
### Suggested approach
Update the `DEBS=(...)` selection to only include versioned artifacts (for example, explicitly exclude `dist/etherpad-latest_*.deb`, or use a tighter pattern such as `dist/etherpad_[0-9]*_amd64.deb` / `dist/etherpad_[0-9]*_arm64.deb`). Ensure the job still fails fast if either arch artifact is missing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Indented Release headers 🐞 Bug ≡ Correctness
Description
The Release file header is written via a heredoc whose field lines are indented, so the resulting
dists/stable/Release will contain leading spaces before Origin:, Suite:, etc., which can make
the Release metadata unparsable/invalid for apt clients.
Code

.github/workflows/deb-package.yml[R352-361]

+            cat > "dists/${SUITE}/Release" <<RELEASE
+          Origin: Etherpad
+          Label: Etherpad
+          Suite: ${SUITE}
+          Codename: ${SUITE}
+          Architectures: amd64 arm64
+          Components: ${COMP}
+          Description: Etherpad official apt repository (${SUITE} channel)
+          Date: $(date -Ru)
+          RELEASE
Evidence
The heredoc content lines are prefixed with spaces and the heredoc uses <<RELEASE (not
<<-RELEASE), so those spaces will be written verbatim into the Release file, potentially breaking
the expected RFC822-like field format used by apt metadata.

.github/workflows/deb-package.yml[352-361]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow writes `dists/${SUITE}/Release` with leading whitespace on each header line, which can cause apt parsers to treat the fields as malformed.
### Issue Context
Bash heredocs preserve indentation unless using `<<-` (tabs-only stripping) or emitting unindented lines. The current heredoc is indented.
### Fix Focus Areas
- .github/workflows/deb-package.yml[352-361]
### Suggested approach
Emit the Release header lines starting at column 1. Options:
- Remove indentation inside the heredoc.
- Or use `<<-RELEASE` and convert indentation to tabs (not spaces) so bash strips it.
After the change, keep the `apt-ftparchive release ... >> Release` line unchanged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Publishes empty apt repo🐞 Bug ☼ Reliability
Description
The apt repo generation step never asserts that any .deb artifacts were actually copied into the
pool, so a tagged run can wipe site/public/apt/ and publish an empty repository if artifact names
change or downloads fail silently. This can break installs/upgrades for all apt users until the next
successful publish.
Code

.github/workflows/deb-package.yml[R330-332]

+          for deb in dist/etherpad_*_amd64.deb dist/etherpad_*_arm64.deb; do
+            [ -f "${deb}" ] && cp "${deb}" "${REPO}/pool/main/e/etherpad/"
+          done
Evidence
The workflow conditionally copies matching .deb files but continues even if none exist, then
generates Packages/Release metadata and signs it. There is no guard that the pool contains at least
one .deb before publishing.

.github/workflows/deb-package.yml[310-339]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow can publish an empty apt repository because the copy loop skips missing globs without failing.
### Issue Context
A tagged release run wipes `site/public/apt` and regenerates it; if no `.deb` files are present, the generated repo will be empty but still signed and pushed.
### Fix Focus Areas
- .github/workflows/deb-package.yml[310-339]
### Suggested change
After downloading artifacts (or after the copy loop), assert at least one `.deb` exists and fail otherwise. For example:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Artifact glob exits early🐞 Bug ☼ Reliability
Description
The Resolve artefact paths step runs ls ... | head under set -euo pipefail, so if the glob
matches nothing the step exits before reaching the explicit empty-check and custom error message.
This can break the release publish job with a non-obvious failure mode whenever the artifact names
or download step change.
Code

.github/workflows/deb-package.yml[R250-260]

+        run: |
+          set -euo pipefail
+          # Pick the first non-"latest" deb per arch — those are the
+          # versioned files (etherpad_<v>_amd64.deb). The release job
+          # also stages etherpad-latest_<arch>.deb aliases, but we
+          # publish under stable filenames anyway, so either works.
+          AMD64=$(ls dist/etherpad_*_amd64.deb 2>/dev/null | head -n1)
+          ARM64=$(ls dist/etherpad_*_arm64.deb 2>/dev/null | head -n1)
+          if [ -z "${AMD64}" ] || [ -z "${ARM64}" ]; then
+            echo "::error::Missing per-arch .deb artefact (amd64='${AMD64}' arm64='${ARM64}')"
+            exit 1
Evidence
The step enables set -euo pipefail and then captures AMD64/ARM64 via ls ... | head -n1; with
pipefail, a non-zero ls exit (no matches) terminates the step immediately, so the subsequent `if
[ -z ... ] guard and its ::error::Missing...` message will never run.

.github/workflows/deb-package.yml[250-260]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Resolve artefact paths` uses `ls ... | head` under `set -euo pipefail`. If no files match, `ls` exits non-zero and the step terminates before the intended `-z` checks and friendly `::error::...` message.
### Issue Context
This job is release-gated (`refs/tags/v*`) and is expected to fail with a clear message if artifacts are missing. Current behavior fails earlier and more opaquely.
### Fix Focus Areas
- .github/workflows/deb-package.yml[250-263]
### Suggested change
Replace the `ls | head` pipelines with a non-fatal glob match, for example:
- `AMD64=$(ls ... 2>/dev/null | head -n1 || true)` (and same for ARM64), or
- use `shopt -s nullglob` and pick from an array, or
- use `compgen -G 'dist/etherpad_*_amd64.deb'` to test existence before `ls`.
Ensure the step reaches the explicit missing-artifact error path when no matches exist.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Key fetch breaks on tags🐞 Bug ☼ Reliability
Description
The apt-publish job downloads packaging/apt/key.asc from raw.githubusercontent.com using `${{
github.sha }}`, which can be an annotated tag object SHA and therefore not resolve to repository
contents, causing the key download (and publish) to fail on release tag runs.
Code

.github/workflows/deb-package.yml[R340-342]

+          cp packaging/apt/key.asc apt-repo/key.asc 2>/dev/null \
+            || curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/packaging/apt/key.asc \
+                  -o apt-repo/key.asc
Evidence
The workflow only runs on refs/tags/v* and its fallback fetch for key.asc uses `${{ github.sha
}}` in the raw.githubusercontent.com URL. The repository’s release tooling creates annotated tags
(git tag -a), so the pushed tag ref can point to a tag object instead of a commit/tree;
raw.githubusercontent.com URLs require a commit SHA or ref name that resolves to a tree, so this
fetch can fail (404) during release publishing.

.github/workflows/deb-package.yml[230-243]
.github/workflows/deb-package.yml[337-343]
bin/release.ts[176-178]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`apt-publish` fetches `packaging/apt/key.asc` using a raw.githubusercontent.com URL built with `${{ github.sha }}`. On release runs, Etherpad creates **annotated tags**, so `${{ github.sha }}` can be a **tag object SHA** that does not resolve to repository contents on raw.githubusercontent.com, causing the key download to fail.
### Issue Context
The job is gated to tag pushes (`refs/tags/v*`) and includes a fallback `curl` to fetch `packaging/apt/key.asc` because only `gh-pages` is checked out.
### Fix
Prefer a ref that resolves to a tree, e.g. `${{ github.ref_name }}` (the tag name) in the raw URL, or add a second checkout of the current ref (in a different path) and copy `packaging/apt/key.asc` from that checkout.
### Fix Focus Areas
- .github/workflows/deb-package.yml[337-343]
- .github/workflows/deb-package.yml[230-243]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

6. writeToPad fix lacks test 📘 Rule violation ☼ Reliability
Description
The PR changes writeToPad() to use keyboard.insertText() to address Firefox dropping characters,
but it does not add a targeted regression test that would fail if this fix were reverted. Without
such a test, the flakiness/bug can silently return and only be caught sporadically in CI.
Code

src/tests/frontend-new/helper/padHelper.ts[R145-159]

+  // Use insertText (single input event) instead of keyboard.type
+  // (one keydown/keyup per char). Firefox under WITH_PLUGINS load
+  // racily drops characters from per-key events; insertText delivers
+  // each chunk in one event, which Etherpad's incorporateUserChanges
+  // pipeline handles atomically.
+  //
+  // insertText does not translate \n into a real Enter keystroke
+  // (it concatenates "One\nTwo" into "OneTwo"), so split on newlines
+  // and press Enter between segments to preserve multi-line input.
+  const lines = text.split('\n');
+  for (let i = 0; i < lines.length; i++) {
+    if (lines[i]) await page.keyboard.insertText(lines[i]);
+    if (i < lines.length - 1) await page.keyboard.press('Enter');
+  }
+  await expect(body).toContainText(lines[lines.length - 1] || lines[0]);
Evidence
PR Compliance ID 1 requires a regression test for bug fixes. The changed helper explicitly describes
a Firefox character-drop bug and implements a workaround, but the diff contains no accompanying
spec/test changes to lock in the behavior with a failing test if reverted.

src/tests/frontend-new/helper/padHelper.ts[145-159]
Best Practice: Repository guidelines

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`writeToPad()` was changed to work around a Firefox character-drop bug, but there is no dedicated regression test that would fail if the implementation reverted back to `keyboard.type()`.
## Issue Context
This change is intended to prevent CI flakiness and lost characters under Firefox/WITH_PLUGINS load. A targeted test should reliably catch the old failure mode (e.g., type a long string / multi-line chunk and assert the full content is present).
## Fix Focus Areas
- src/tests/frontend-new/helper/padHelper.ts[142-160]
- src/tests/frontend-new/specs/collab_client.spec.ts[24-95]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Key URL inconsistency🐞 Bug ⚙ Maintainability
Description
generate-signing-key.sh documents the public key URL as ether.github.io/etherpad/key.asc, but the
workflow/README in this PR publish and instruct users to fetch it from etherpad.org/key.asc. This
inconsistency will cause confusion and failed setup if someone follows the script comments.
Code

packaging/apt/generate-signing-key.sh[R11-14]

+#   2. Hand the *public* key contents to whoever is wiring up the apt
+#      workflow; it gets committed at packaging/apt/key.asc so end users
+#      can pull it from https://ether.github.io/etherpad/key.asc.
+#   3. Note the printed long key ID — the workflow uses it as
Evidence
The new workflow explicitly documents and stages the key at https://etherpad.org/key.asc, and the
packaging README instructs users to curl that URL, but the new helper script still references the
old ether.github.io URL in its comments.

packaging/apt/generate-signing-key.sh[11-15]
.github/workflows/deb-package.yml[230-237]
.github/workflows/deb-package.yml[361-365]
packaging/README.md[56-67]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Docs/comments disagree on where `key.asc` is published.
### Issue Context
- Workflow comments and the staging step indicate `site/public/key.asc` → `https://etherpad.org/key.asc`.
- README installation steps use `https://etherpad.org/key.asc`.
- The new key generation helper script references `https://ether.github.io/etherpad/key.asc`.
### Fix Focus Areas
- packaging/apt/generate-signing-key.sh[11-15]
- packaging/README.md[56-67]
- .github/workflows/deb-package.yml[230-237]
### Suggested change
Update the helper script comment to match the actual published URL (`https://etherpad.org/key.asc`). Ensure all references across these files use the same canonical URL.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Unpinned SSH host key 🐞 Bug ⛨ Security
Description
The workflow adds GitHub's SSH host key via ssh-keyscan at runtime, which trusts whatever key is
returned on the network path and weakens SSH host authenticity for the cross-repo push. This is a
defense-in-depth risk for a job that publishes user-consumable artifacts to etherpad.org.
Code

.github/workflows/deb-package.yml[279]

+          ssh-keyscan -t ed25519,rsa github.com >> ~/.ssh/known_hosts 2>/dev/null
Evidence
The job configures an SSH deploy key and then clones/pushes over SSH to github.com; the only host
verification is a runtime ssh-keyscan append to known_hosts, which does not validate that the
fetched key is the expected GitHub host key.

.github/workflows/deb-package.yml[265-287]
.github/workflows/deb-package.yml[297-311]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow populates `~/.ssh/known_hosts` using `ssh-keyscan github.com`, which does not authenticate the host key. For a publish job that pushes release artifacts, it’s safer to pin the expected GitHub SSH host key(s).
### Issue Context
This job uses a deploy key (`SITE_DEPLOY_KEY`) to push to `ether/ether.github.com`. Host key pinning reduces exposure to network-path tampering.
### Fix Focus Areas
- .github/workflows/deb-package.yml[275-287]
### Suggested change
Replace `ssh-keyscan github.com >> known_hosts` with a pinned `known_hosts` entry (or entries) committed in-repo or embedded in the workflow (for the key types you intend to allow), and write that into `~/.ssh/known_hosts`.
Optionally, restrict to a single key type (e.g., ed25519) instead of accepting multiple.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
9. Hard-coded https:// in docs 📘 Rule violation ⚙ Maintainability
Description
New installation instructions hard-code https:// URLs instead of using protocol-independent URLs
where they could work under both HTTP and HTTPS. This may reduce compatibility for environments
expecting protocol-relative links per the compliance requirement.
Code

packaging/README.md[R58-67]

+The release workflow publishes a signed apt repository at
+`https://ether.github.io/etherpad/apt/` (gh-pages branch). Three lines
+on any Debian/Ubuntu/Mint:
+
+```sh
+curl -fsSL https://ether.github.io/etherpad/key.asc \
+  | sudo gpg --dearmor -o /usr/share/keyrings/etherpad.gpg
+echo "deb [signed-by=/usr/share/keyrings/etherpad.gpg] https://ether.github.io/etherpad/apt stable main" \
+  | sudo tee /etc/apt/sources.list.d/etherpad.list
+sudo apt update && sudo apt install etherpad
Evidence
PR Compliance ID 8 requires protocol-independent URLs where applicable; the added README install
instructions introduce multiple hard-coded https:// URLs for the apt repo and key download.

packaging/README.md[58-67]
Best Practice: Repository guidelines

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new README instructions hard-code `https://` URLs where protocol-independent URLs are expected by the compliance checklist.
## Issue Context
PR Compliance ID 8 requires protocol-independent URLs (e.g., `//host/path`) where applicable to support both HTTP and HTTPS deployments.
## Fix Focus Areas
- packaging/README.md[58-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

10. OUT_DIR override ignored 🐞 Bug ⚙ Maintainability
Description
generate-signing-key.sh tells users they can set OUT_DIR, but it unconditionally sets OUT_DIR to
$(pwd), so the advertised override cannot work. This makes the script harder to re-run safely when
output files already exist.
Code

packaging/apt/generate-signing-key.sh[R23-31]

+OUT_DIR="$(pwd)"
+PRIV="${OUT_DIR}/etherpad-apt-private.asc"
+PUB="${OUT_DIR}/etherpad-apt-public.asc"
+
+if [[ -e "${PRIV}" || -e "${PUB}" ]]; then
+  echo "!! Output files already exist in ${OUT_DIR}:" >&2
+  ls -la "${PRIV}" "${PUB}" 2>/dev/null >&2 || true
+  echo "   Move/delete them first, or set OUT_DIR to a clean directory." >&2
+  exit 1
Evidence
The script assigns OUT_DIR="$(pwd)" and later suggests setting OUT_DIR as a workaround, which is
impossible because the assignment overwrites any pre-set value.

packaging/apt/generate-signing-key.sh[23-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The script overwrites `OUT_DIR` even if the user exports it.
### Issue Context
The error message suggests OUT_DIR can be set, implying the script intended to allow redirecting outputs.
### Fix Focus Areas
- packaging/apt/generate-signing-key.sh[23-31]
### Suggested change
Change:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread .github/workflows/deb-package.yml Outdated
@JohnMcLear
Copy link
Copy Markdown
Member Author

/review

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Apr 28, 2026

Persistent review updated to latest commit 1ea6403

Comment thread .github/workflows/deb-package.yml Outdated
Comment on lines +250 to +260
run: |
set -euo pipefail
# Pick the first non-"latest" deb per arch — those are the
# versioned files (etherpad_<v>_amd64.deb). The release job
# also stages etherpad-latest_<arch>.deb aliases, but we
# publish under stable filenames anyway, so either works.
AMD64=$(ls dist/etherpad_*_amd64.deb 2>/dev/null | head -n1)
ARM64=$(ls dist/etherpad_*_arm64.deb 2>/dev/null | head -n1)
if [ -z "${AMD64}" ] || [ -z "${ARM64}" ]; then
echo "::error::Missing per-arch .deb artefact (amd64='${AMD64}' arm64='${ARM64}')"
exit 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Artifact glob exits early 🐞 Bug ☼ Reliability

The Resolve artefact paths step runs ls ... | head under set -euo pipefail, so if the glob
matches nothing the step exits before reaching the explicit empty-check and custom error message.
This can break the release publish job with a non-obvious failure mode whenever the artifact names
or download step change.
Agent Prompt
### Issue description
`Resolve artefact paths` uses `ls ... | head` under `set -euo pipefail`. If no files match, `ls` exits non-zero and the step terminates before the intended `-z` checks and friendly `::error::...` message.

### Issue Context
This job is release-gated (`refs/tags/v*`) and is expected to fail with a clear message if artifacts are missing. Current behavior fails earlier and more opaquely.

### Fix Focus Areas
- .github/workflows/deb-package.yml[250-263]

### Suggested change
Replace the `ls | head` pipelines with a non-fatal glob match, for example:
- `AMD64=$(ls ... 2>/dev/null | head -n1 || true)` (and same for ARM64), or
- use `shopt -s nullglob` and pick from an array, or
- use `compgen -G 'dist/etherpad_*_amd64.deb'` to test existence before `ls`.
Ensure the step reaches the explicit missing-artifact error path when no matches exist.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@JohnMcLear JohnMcLear changed the title ci(packaging): publish signed apt repository to gh-pages (closes #7610) ci(packaging): publish signed apt repository to etherpad.org/apt (closes #7610) Apr 28, 2026
@JohnMcLear
Copy link
Copy Markdown
Member Author

/review

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Apr 28, 2026

Persistent review updated to latest commit cb675c5

Comment thread .github/workflows/deb-package.yml Outdated
@JohnMcLear
Copy link
Copy Markdown
Member Author

/review

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Apr 28, 2026

Persistent review updated to latest commit 6ef9483

@JohnMcLear
Copy link
Copy Markdown
Member Author

/review

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Apr 28, 2026

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@JohnMcLear
Copy link
Copy Markdown
Member Author

/review

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Apr 28, 2026

Persistent review updated to latest commit bf7980e

Comment thread .github/workflows/deb-package.yml Outdated
Comment on lines +326 to +332
# Drop the .debs into pool/. Skip the etherpad-latest_*.deb
# filename aliases — apt resolves by package name + version,
# not filename, and including the alias would create a
# duplicate Packages entry.
shopt -s nullglob
DEBS=(dist/etherpad_*_amd64.deb dist/etherpad_*_arm64.deb)
shopt -u nullglob
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Latest alias duplicated 🐞 Bug ≡ Correctness

apt-publish claims to skip etherpad-latest_*.deb aliases, but the glob it uses will include
them, so apt-ftparchive will emit duplicate entries for the same package/version and can break
apt update for repo users.
Agent Prompt
### Issue description
The apt repo publish job is intended to exclude `etherpad-latest_*.deb` alias files, but the current glob includes them, which will generate duplicate package entries in `Packages`.

### Issue Context
The `release` job creates stable aliases (`etherpad-latest_amd64.deb`, `etherpad-latest_arm64.deb`). The `apt-publish` job then builds an apt repository by globbing `dist/etherpad_*_amd64.deb` and `dist/etherpad_*_arm64.deb`, which also matches the alias files.

### Fix Focus Areas
- .github/workflows/deb-package.yml[211-222]
- .github/workflows/deb-package.yml[326-342]

### Suggested approach
Update the `DEBS=(...)` selection to only include versioned artifacts (for example, explicitly exclude `dist/etherpad-latest_*.deb`, or use a tighter pattern such as `dist/etherpad_[0-9]*_amd64.deb` / `dist/etherpad_[0-9]*_arm64.deb`). Ensure the job still fails fast if either arch artifact is missing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread .github/workflows/deb-package.yml Outdated
Comment on lines +352 to +361
cat > "dists/${SUITE}/Release" <<RELEASE
Origin: Etherpad
Label: Etherpad
Suite: ${SUITE}
Codename: ${SUITE}
Architectures: amd64 arm64
Components: ${COMP}
Description: Etherpad official apt repository (${SUITE} channel)
Date: $(date -Ru)
RELEASE
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Indented release headers 🐞 Bug ≡ Correctness

The Release file header is written via a heredoc whose field lines are indented, so the resulting
dists/stable/Release will contain leading spaces before Origin:, Suite:, etc., which can make
the Release metadata unparsable/invalid for apt clients.
Agent Prompt
### Issue description
The workflow writes `dists/${SUITE}/Release` with leading whitespace on each header line, which can cause apt parsers to treat the fields as malformed.

### Issue Context
Bash heredocs preserve indentation unless using `<<-` (tabs-only stripping) or emitting unindented lines. The current heredoc is indented.

### Fix Focus Areas
- .github/workflows/deb-package.yml[352-361]

### Suggested approach
Emit the Release header lines starting at column 1. Options:
- Remove indentation inside the heredoc.
- Or use `<<-RELEASE` and convert indentation to tabs (not spaces) so bash strips it.
After the change, keep the `apt-ftparchive release ... >> Release` line unchanged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@JohnMcLear
Copy link
Copy Markdown
Member Author

Addressed both findings in cebd1cb:

1. Indented release headers — confirmed real bug. The YAML-scoped heredoc was producing 10-space-prefixed field lines. Replaced with printf '%s\n' ... so the indentation is entirely under workflow control.

2. Latest alias duplicated — the analysis as written is incorrect: the existing glob etherpad_*_amd64.deb does not match etherpad-latest_amd64.deb (different separator at position 8: _ vs -). But tightening to etherpad_[0-9]*_amd64.deb makes the contract explicit and defends against any future alias that lands on etherpad_<word>_<arch>.deb. Took the suggested defensive change.

JohnMcLear and others added 2 commits April 28, 2026 18:57
 ether#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>
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>
@JohnMcLear JohnMcLear merged commit 0a2facb into ether:develop Apr 28, 2026
20 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant