John McLear 0a2facb3fc
ci(packaging): publish signed apt repository to etherpad.org/apt (closes #7610) (#7624)
* 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>
2026-04-29 00:20:00 +01:00

140 lines
4.6 KiB
Markdown

# Etherpad Debian / RPM packaging
Produces native `.deb` (and, with the same manifest, `.rpm` / `.apk`)
packages for Etherpad using [nfpm](https://nfpm.goreleaser.com).
## Layout
```
packaging/
nfpm.yaml # nfpm package manifest
bin/etherpad # /usr/bin launcher
scripts/ # preinst / postinst / prerm / postrm
systemd/etherpad.service
systemd/etherpad.default
etc/settings.json.dist # populated in CI from settings.json.template
```
Built artefacts land in `./dist/`.
## Building locally
Prereqs: Node 24 (current LTS; `engines.node` floor is 20), pnpm 10+, nfpm.
```sh
pnpm install --frozen-lockfile
pnpm run build:etherpad
# Stage the tree the way CI does:
STAGE=staging/opt/etherpad
mkdir -p "$STAGE"
cp -a src bin package.json pnpm-workspace.yaml README.md LICENSE \
node_modules "$STAGE/"
printf 'packages:\n - src\n - bin\n' > "$STAGE/pnpm-workspace.yaml"
cp settings.json.template packaging/etc/settings.json.dist
VERSION=$(node -p "require('./package.json').version") \
ARCH=amd64 \
nfpm package --packager deb -f packaging/nfpm.yaml --target dist/
```
## End-to-end test (Docker, no real systemd needed)
`packaging/test-local.sh` builds the `.deb` and runs the same smoke
test the CI workflow does, inside a throwaway systemd-enabled
container:
```sh
packaging/test-local.sh # build + smoke + purge
packaging/test-local.sh --shell # leave the container up so you can poke around
packaging/test-local.sh --build-only # just produce dist/*.deb
```
This is the fastest way to validate that the systemd hardening, plugin
path symlinks, and tsx wrapper actually work together before pushing.
## Installing via the Etherpad apt repository (recommended)
The release workflow publishes a signed apt repository at
`https://etherpad.org/apt/` on every tagged release. Three lines on
any Debian/Ubuntu/Mint:
```sh
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. Repo metadata is signed with the
GPG keypair documented in `packaging/apt/key.asc` (long key id
`AF0CD687D51A6E63`).
## Installing a single .deb directly
The release page publishes both versioned and stable filenames per arch:
```sh
# Stable URL — always points at the most recent release:
curl -fsSL -o etherpad-latest_amd64.deb \
https://github.com/ether/etherpad/releases/latest/download/etherpad-latest_amd64.deb
sudo apt install ./etherpad-latest_amd64.deb
# Or pin to a specific version:
sudo apt install ./dist/etherpad_<version>_amd64.deb
sudo systemctl start etherpad
curl http://localhost:9001/health
```
`apt` will pull in `nodejs (>= 22)` (matches Etherpad's `engines.node`).
Recommended runtime is the current Node.js LTS (24); on distros without a
new enough Node, add NodeSource first:
```sh
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
```
## Configuration
- Edit `/etc/etherpad/settings.json`, then
`sudo systemctl restart etherpad`.
- Environment overrides: `/etc/default/etherpad`.
- Logs: `journalctl -u etherpad -f`.
- Data (sqlite default): `/var/lib/etherpad/etherpad.db`.
The shipped settings template defaults to `dbType: "dirty"`, which the
template itself warns is for testing only. `postinstall` rewrites the
seeded `/etc/etherpad/settings.json` to `sqlite` and points it at
`/var/lib/etherpad/etherpad.db` so fresh installs get an ACID-safe DB
out of the box. Existing `/etc/etherpad/settings.json` is never touched
on upgrade.
## Upgrading
`dpkg --install etherpad_<new>.deb` (or `apt install`) replaces the app
tree under `/opt/etherpad` while preserving `/etc/etherpad/*` and
`/var/lib/etherpad/*`. The service is restarted automatically.
## Removing
- `sudo apt remove etherpad` — keeps config and data.
- `sudo apt purge etherpad` — also removes config, data, and the
`etherpad` system user.
## Publishing to an APT repository (follow-up)
Out of scope here — requires credentials and ownership decisions.
Recipes once a repo is picked:
- **Cloudsmith** (easiest, free OSS tier):
`cloudsmith push deb ether/etherpad/any-distro/any-version dist/*.deb`
- **Launchpad PPA**: requires signed source packages (a `debian/` tree),
which nfpm does not produce — use `debuild` separately.
- **Self-hosted reprepro**:
`reprepro -b /srv/apt includedeb stable dist/*.deb`
Wire the chosen option into `.github/workflows/deb-package.yml` after
the `release` job.