From f1e6e20b032b53f35dc84687a7368d9aebc80b72 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 19 Apr 2026 17:55:38 +0100 Subject: [PATCH 1/2] feat(packaging): add Debian (.deb) build via nfpm with systemd unit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First-class Debian packaging for Etherpad, producing signed-ready etherpad-lite__.deb artefacts for amd64 and arm64 from a single nfpm manifest. Installing the package gives users: - /opt/etherpad-lite with a prebuilt, self-contained node_modules/ — no pnpm required at runtime, just `nodejs (>= 20)`. - etherpad system user/group, created via `adduser` in preinst. - /etc/etherpad-lite/settings.json seeded from the template on first install, preserved across upgrades, removed on `purge`. - /var/lib/etherpad-lite owned by etherpad:etherpad, with the default dirty-DB retargeted there so ProtectSystem=strict works. - /lib/systemd/system/etherpad-lite.service — hardened unit (NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, RestrictAddressFamilies) with Restart=on-failure. - /usr/bin/etherpad-lite CLI wrapper running `node --import tsx/esm`. CI (.github/workflows/deb-package.yml) triggers on v* tags, builds both arches via native runners (ubuntu-latest + ubuntu-24.04-arm), smoke-tests the amd64 package end-to-end (install → systemctl start → curl /health → purge → confirm user removed), and attaches the artefacts to the GitHub Release. Publishing to an APT repo (Cloudsmith, Launchpad PPA, self-hosted reprepro) is intentionally out of scope — needs a governance decision on who holds the signing key. Recipes are documented in packaging/README.md. Refs #7529 Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/deb-package.yml | 151 ++++++++++++++++++++++++ packaging/README.md | 89 ++++++++++++++ packaging/bin/etherpad-lite | 18 +++ packaging/nfpm.yaml | 112 ++++++++++++++++++ packaging/scripts/postinstall.sh | 68 +++++++++++ packaging/scripts/postremove.sh | 43 +++++++ packaging/scripts/preinstall.sh | 28 +++++ packaging/scripts/preremove.sh | 20 ++++ packaging/systemd/etherpad-lite.default | 7 ++ packaging/systemd/etherpad-lite.service | 48 ++++++++ 10 files changed, 584 insertions(+) create mode 100644 .github/workflows/deb-package.yml create mode 100644 packaging/README.md create mode 100755 packaging/bin/etherpad-lite create mode 100644 packaging/nfpm.yaml create mode 100755 packaging/scripts/postinstall.sh create mode 100755 packaging/scripts/postremove.sh create mode 100755 packaging/scripts/preinstall.sh create mode 100755 packaging/scripts/preremove.sh create mode 100644 packaging/systemd/etherpad-lite.default create mode 100644 packaging/systemd/etherpad-lite.service diff --git a/.github/workflows/deb-package.yml b/.github/workflows/deb-package.yml new file mode 100644 index 00000000000..df12e58aa12 --- /dev/null +++ b/.github/workflows/deb-package.yml @@ -0,0 +1,151 @@ +name: Debian package +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-*' + workflow_dispatch: + inputs: + ref: + description: 'Git ref to package (defaults to current)' + required: false + +permissions: + contents: write # attach release assets + +env: + NFPM_VERSION: v2.43.0 + +jobs: + build: + name: Build .deb (${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-latest + - arch: arm64 + runner: ubuntu-24.04-arm + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Resolve version + id: v + run: | + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then + VERSION="${GITHUB_REF_NAME#v}" + else + VERSION="$(node -p "require('./package.json').version")" + fi + echo "version=${VERSION}" >>"$GITHUB_OUTPUT" + echo "Packaging version: ${VERSION}" + + - uses: pnpm/action-setup@v6 + with: + version: 10 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build UI + admin + run: pnpm run build:etherpad + + - name: Install nfpm + run: | + set -e + NFPM_ARCH=amd64 + [ "${{ matrix.arch }}" = "arm64" ] && NFPM_ARCH=arm64 + curl -fsSL -o /tmp/nfpm.deb \ + "https://github.com/goreleaser/nfpm/releases/download/${NFPM_VERSION}/nfpm_${NFPM_VERSION#v}_${NFPM_ARCH}.deb" + sudo dpkg -i /tmp/nfpm.deb + + - name: Stage tree for packaging + run: | + set -eux + STAGE=staging/opt/etherpad-lite + mkdir -p "${STAGE}" + + # Production footprint = src/ + bin/ + node_modules/ + metadata. + cp -a src bin package.json pnpm-workspace.yaml README.md LICENSE \ + node_modules "${STAGE}/" + + # Make pnpm-workspace.yaml production-only (same trick Dockerfile uses). + printf 'packages:\n - src\n - bin\n' > "${STAGE}/pnpm-workspace.yaml" + + mkdir -p packaging/etc + cp settings.json.template packaging/etc/settings.json.dist + + # Purge test fixtures and dev caches from node_modules to shrink size. + find "${STAGE}/node_modules" -type d \ + \( -name test -o -name tests -o -name '__tests__' \ + -o -name example -o -name examples -o -name docs \) \ + -prune -exec rm -rf {} + 2>/dev/null || true + find "${STAGE}/node_modules" -type f \ + \( -name '*.md' -o -name '*.ts.map' -o -name '*.map' \ + -o -name 'CHANGELOG*' -o -name 'HISTORY*' \) \ + -delete 2>/dev/null || true + + - name: Build .deb + env: + VERSION: ${{ steps.v.outputs.version }} + ARCH: ${{ matrix.arch }} + run: | + mkdir -p dist + nfpm package --packager deb -f packaging/nfpm.yaml --target dist/ + + - name: Smoke-test the package (amd64 only) + if: matrix.arch == 'amd64' + run: | + set -eux + sudo apt-get update + sudo apt-get install -y nodejs + sudo dpkg -i dist/*.deb || sudo apt-get install -f -y + test -x /usr/bin/etherpad-lite + test -f /etc/etherpad-lite/settings.json + test -L /opt/etherpad-lite/settings.json + id etherpad + systemctl cat etherpad-lite.service + sudo systemctl start etherpad-lite + for i in $(seq 1 30); do + curl -fsS http://127.0.0.1:9001/health && break || sleep 2 + done + sudo systemctl stop etherpad-lite + sudo dpkg --purge etherpad-lite + ! id etherpad 2>/dev/null + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: etherpad-lite-${{ steps.v.outputs.version }}-${{ matrix.arch }}-deb + path: dist/*.deb + if-no-files-found: error + + release: + name: Attach to GitHub Release + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + pattern: etherpad-lite-*-deb + merge-multiple: true + - name: Attach .deb files to release + uses: softprops/action-gh-release@v2 + with: + files: dist/*.deb + fail_on_unmatched_files: true diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 00000000000..20b5b5f7beb --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,89 @@ +# 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-lite # /usr/bin launcher + scripts/ # preinst / postinst / prerm / postrm + systemd/etherpad-lite.service + systemd/etherpad-lite.default + etc/settings.json.dist # populated in CI from settings.json.template +``` + +Built artefacts land in `./dist/`. + +## Building locally + +Prereqs: Node 22, pnpm 10+, nfpm. + +```sh +pnpm install --frozen-lockfile +pnpm run build:etherpad + +# Stage the tree the way CI does: +STAGE=staging/opt/etherpad-lite +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/ +``` + +## Installing + +```sh +sudo apt install ./dist/etherpad-lite_2.6.1_amd64.deb +sudo systemctl start etherpad-lite +curl http://localhost:9001/health +``` + +`apt` will pull in `nodejs (>= 20)`; on Ubuntu 22.04 add NodeSource first: + +```sh +curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - +``` + +## Configuration + +- Edit `/etc/etherpad-lite/settings.json`, then + `sudo systemctl restart etherpad-lite`. +- Environment overrides: `/etc/default/etherpad-lite`. +- Logs: `journalctl -u etherpad-lite -f`. +- Data (dirty-DB default): `/var/lib/etherpad-lite/`. + +## Upgrading + +`dpkg --install etherpad-lite_.deb` (or `apt install`) replaces the +app tree under `/opt/etherpad-lite` while preserving +`/etc/etherpad-lite/*` and `/var/lib/etherpad-lite/*`. The service is +restarted automatically. + +## Removing + +- `sudo apt remove etherpad-lite` — keeps config and data. +- `sudo apt purge etherpad-lite` — 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-lite/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. diff --git a/packaging/bin/etherpad-lite b/packaging/bin/etherpad-lite new file mode 100755 index 00000000000..20143154ef8 --- /dev/null +++ b/packaging/bin/etherpad-lite @@ -0,0 +1,18 @@ +#!/bin/sh +# /usr/bin/etherpad-lite - thin wrapper that runs Etherpad in production mode. +# Invoked by the etherpad-lite.service systemd unit. +set -e + +APP_DIR="${ETHERPAD_DIR:-/opt/etherpad-lite}" +cd "${APP_DIR}" + +: "${NODE_ENV:=production}" +export NODE_ENV +export ETHERPAD_PRODUCTION=true + +# Run the server through tsx's ESM loader (shipped in node_modules). +# No pnpm needed at runtime. +exec node \ + --import "file://${APP_DIR}/src/node_modules/tsx/dist/esm/index.mjs" \ + "${APP_DIR}/src/node/server.ts" \ + "$@" diff --git a/packaging/nfpm.yaml b/packaging/nfpm.yaml new file mode 100644 index 00000000000..4a8a5b12f55 --- /dev/null +++ b/packaging/nfpm.yaml @@ -0,0 +1,112 @@ +# nfpm configuration for etherpad-lite Debian/RPM/APK packages. +# Build with: nfpm package --packager deb --target dist/ +# See: https://nfpm.goreleaser.com/configuration/ + +name: etherpad-lite +arch: ${ARCH} # amd64 | arm64 (exported by CI) +platform: linux +version: ${VERSION} # e.g. 2.6.1, stripped of leading "v" +version_schema: semver +release: "1" +section: web +priority: optional +maintainer: "Etherpad Foundation " +description: | + Etherpad is a real-time collaborative editor for the web. + This package installs Etherpad as a systemd service running + from /opt/etherpad-lite with configuration in /etc/etherpad-lite. +vendor: "Etherpad Foundation" +homepage: https://etherpad.org +license: Apache-2.0 + +depends: + - nodejs (>= 20) + - adduser + - ca-certificates + +recommends: + - libreoffice # enables DOC/DOCX/PDF/ODT export + - curl + +suggests: + - postgresql-client + - mariadb-client + +conflicts: + - etherpad # legacy bin/buildDebian.sh package name + +replaces: + - etherpad + +provides: + - etherpad + +# --------------------------------------------------------------------------- +# Contents. staging/ is populated by CI before invoking nfpm: +# staging/opt/etherpad-lite/ -- source + node_modules + built assets +# --------------------------------------------------------------------------- +contents: + - src: ./staging/opt/etherpad-lite + dst: /opt/etherpad-lite + type: tree + file_info: + mode: 0755 + owner: root + group: root + + - src: ./packaging/systemd/etherpad-lite.service + dst: /lib/systemd/system/etherpad-lite.service + file_info: + mode: 0644 + + # Default environment file (conffile: preserved on upgrade) + - src: ./packaging/systemd/etherpad-lite.default + dst: /etc/default/etherpad-lite + type: config|noreplace + file_info: + mode: 0644 + + - src: ./packaging/bin/etherpad-lite + dst: /usr/bin/etherpad-lite + file_info: + mode: 0755 + + # Template used by postinstall to seed /etc/etherpad-lite/settings.json. + # Intentionally NOT a conffile: postinstall creates the real settings.json + # once on first install and never touches it again, so upgrades don't + # prompt with dpkg merge dialogs. + - src: ./packaging/etc/settings.json.dist + dst: /usr/share/etherpad-lite/settings.json.dist + file_info: + mode: 0644 + + - dst: /etc/etherpad-lite + type: dir + file_info: + mode: 0755 + - dst: /var/lib/etherpad-lite + type: dir + file_info: + mode: 0750 + - dst: /var/log/etherpad-lite + type: dir + file_info: + mode: 0750 + +scripts: + preinstall: ./packaging/scripts/preinstall.sh + postinstall: ./packaging/scripts/postinstall.sh + preremove: ./packaging/scripts/preremove.sh + postremove: ./packaging/scripts/postremove.sh + +overrides: + deb: + depends: + - nodejs (>= 20) + - adduser + - ca-certificates + rpm: + depends: + - nodejs >= 20 + - shadow-utils + - ca-certificates diff --git a/packaging/scripts/postinstall.sh b/packaging/scripts/postinstall.sh new file mode 100755 index 00000000000..a1be523244b --- /dev/null +++ b/packaging/scripts/postinstall.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# postinstall - runs after files have been unpacked. +# Debian actions: configure | abort-upgrade | abort-remove | abort-deconfigure +set -e + +ETC_DIR=/etc/etherpad-lite +VAR_DIR=/var/lib/etherpad-lite +LOG_DIR=/var/log/etherpad-lite +APP_DIR=/opt/etherpad-lite +DIST_SETTINGS=/usr/share/etherpad-lite/settings.json.dist +ACTIVE_SETTINGS="${ETC_DIR}/settings.json" + +case "$1" in + configure) + mkdir -p "${ETC_DIR}" "${VAR_DIR}" "${LOG_DIR}" + chown root:etherpad "${ETC_DIR}" + chmod 0750 "${ETC_DIR}" + chown etherpad:etherpad "${VAR_DIR}" "${LOG_DIR}" + chmod 0750 "${VAR_DIR}" "${LOG_DIR}" + + if [ ! -e "${ACTIVE_SETTINGS}" ]; then + cp "${DIST_SETTINGS}" "${ACTIVE_SETTINGS}" + # Point the default dirty-DB at /var/lib so ProtectSystem=strict works. + sed -i \ + 's|"filename": "var/dirty.db"|"filename": "/var/lib/etherpad-lite/dirty.db"|' \ + "${ACTIVE_SETTINGS}" + chown root:etherpad "${ACTIVE_SETTINGS}" + chmod 0640 "${ACTIVE_SETTINGS}" + fi + + # Etherpad reads settings.json from CWD (/opt/etherpad-lite). Expose + # the /etc copy there via symlink. + ln -sfn "${ACTIVE_SETTINGS}" "${APP_DIR}/settings.json" + + if [ -d "${APP_DIR}/var" ]; then + chown -R etherpad:etherpad "${APP_DIR}/var" || true + fi + + if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then + systemctl daemon-reload || true + # Enable on first install; leave state alone on upgrade. + if [ -z "$2" ]; then + systemctl enable etherpad-lite.service >/dev/null 2>&1 || true + fi + # Restart on upgrade to pick up new code (skip on fresh install -- + # admin may want to configure first). + if [ -n "$2" ]; then + systemctl try-restart etherpad-lite.service >/dev/null 2>&1 || true + fi + fi + + cat <&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/scripts/postremove.sh b/packaging/scripts/postremove.sh new file mode 100755 index 00000000000..0b5688097ee --- /dev/null +++ b/packaging/scripts/postremove.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# postremove - runs after files are removed. +# Debian actions: remove | purge | upgrade | failed-upgrade | abort-install | +# abort-upgrade | disappear +set -e + +APP_DIR=/opt/etherpad-lite + +case "$1" in + remove) + [ -L "${APP_DIR}/settings.json" ] && rm -f "${APP_DIR}/settings.json" || true + if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then + systemctl daemon-reload || true + fi + ;; + + purge) + rm -rf /etc/etherpad-lite + rm -rf /var/lib/etherpad-lite + rm -rf /var/log/etherpad-lite + + if getent passwd etherpad >/dev/null 2>&1; then + deluser --system etherpad >/dev/null 2>&1 || true + fi + if getent group etherpad >/dev/null 2>&1; then + delgroup --system etherpad >/dev/null 2>&1 || true + fi + + if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then + systemctl daemon-reload || true + fi + ;; + + upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postremove called with unknown argument: $1" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/scripts/preinstall.sh b/packaging/scripts/preinstall.sh new file mode 100755 index 00000000000..02bd1d5fb27 --- /dev/null +++ b/packaging/scripts/preinstall.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# preinstall - runs before files are unpacked. +# Debian actions: install | upgrade | abort-upgrade +set -e + +case "$1" in + install|upgrade) + if ! getent group etherpad >/dev/null 2>&1; then + addgroup --system etherpad + fi + if ! getent passwd etherpad >/dev/null 2>&1; then + adduser --system --ingroup etherpad \ + --home /var/lib/etherpad-lite \ + --no-create-home \ + --shell /usr/sbin/nologin \ + --gecos "Etherpad service user" \ + etherpad + fi + ;; + abort-upgrade) + ;; + *) + echo "preinstall called with unknown argument: $1" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/scripts/preremove.sh b/packaging/scripts/preremove.sh new file mode 100755 index 00000000000..efc5a444a74 --- /dev/null +++ b/packaging/scripts/preremove.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# preremove - runs before files are removed. +# Debian actions: remove | upgrade | deconfigure | failed-upgrade +set -e + +case "$1" in + remove|upgrade|deconfigure) + if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then + systemctl stop etherpad-lite.service >/dev/null 2>&1 || true + fi + ;; + failed-upgrade) + ;; + *) + echo "preremove called with unknown argument: $1" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/systemd/etherpad-lite.default b/packaging/systemd/etherpad-lite.default new file mode 100644 index 00000000000..3ad92b0cd44 --- /dev/null +++ b/packaging/systemd/etherpad-lite.default @@ -0,0 +1,7 @@ +# /etc/default/etherpad-lite +# Environment overrides for the etherpad-lite systemd service. +# Any variable referenced by ${VAR:default} in settings.json can be set here. + +NODE_ENV=production +# PORT=9001 +# NODE_OPTIONS=--max-old-space-size=2048 diff --git a/packaging/systemd/etherpad-lite.service b/packaging/systemd/etherpad-lite.service new file mode 100644 index 00000000000..f13d7d6c6ca --- /dev/null +++ b/packaging/systemd/etherpad-lite.service @@ -0,0 +1,48 @@ +[Unit] +Description=Etherpad - real-time collaborative editor +Documentation=https://etherpad.org https://github.com/ether/etherpad-lite +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=etherpad +Group=etherpad +WorkingDirectory=/opt/etherpad-lite +EnvironmentFile=-/etc/default/etherpad-lite +ExecStart=/usr/bin/etherpad-lite +Restart=on-failure +RestartSec=5s +TimeoutStopSec=20s + +StandardOutput=journal +StandardError=journal +SyslogIdentifier=etherpad-lite + +# --- Sandboxing --------------------------------------------------------- +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectKernelLogs=true +ProtectControlGroups=true +ProtectHostname=true +ProtectClock=true +RestrictRealtime=true +RestrictSUIDSGID=true +RestrictNamespaces=true +LockPersonality=true +MemoryDenyWriteExecute=false # Node's JIT needs W+X mappings +SystemCallArchitectures=native +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK +UMask=0027 + +ReadWritePaths=/var/lib/etherpad-lite /var/log/etherpad-lite /etc/etherpad-lite + +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target From a69d79a2b6e707569a80c81f43050966b7296b72 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 19 Apr 2026 18:34:54 +0100 Subject: [PATCH 2/2] fix(deb): fail smoke test on /health timeout, tighten default-file perms, 2-space indent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Qodo review feedback on #7559: 1. Smoke test false-positive: the `for` loop polling /health never failed the job if the endpoint stayed down — `curl && break || sleep 2` keeps returning 0 from the trailing `sleep`, so `set -e` never trips. CI could attach a broken .deb to a release. Fix: track success explicitly and exit 1 (plus dump journald logs for diagnostics) when the service never becomes healthy. 2. /etc/default/etherpad-lite was world-readable (0644). systemd loads it via `EnvironmentFile=…`, and Etherpad supports ${ENV_VAR}-substitution for secrets (DB_PASSWORD etc.), so any local user could read anything admins drop there. Fix: install the conffile as root:etherpad 0640 — only root and the service user can read it. 3. Indentation: reflow maintainer scripts from 4-space to 2-space to match the repo style rule. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/deb-package.yml | 12 ++++- packaging/nfpm.yaml | 9 +++- packaging/scripts/postinstall.sh | 82 +++++++++++++++---------------- packaging/scripts/postremove.sh | 62 +++++++++++------------ packaging/scripts/preinstall.sh | 38 +++++++------- packaging/scripts/preremove.sh | 22 ++++----- 6 files changed, 120 insertions(+), 105 deletions(-) diff --git a/.github/workflows/deb-package.yml b/.github/workflows/deb-package.yml index df12e58aa12..81f64f61eaf 100644 --- a/.github/workflows/deb-package.yml +++ b/.github/workflows/deb-package.yml @@ -117,9 +117,19 @@ jobs: id etherpad systemctl cat etherpad-lite.service sudo systemctl start etherpad-lite + ok= for i in $(seq 1 30); do - curl -fsS http://127.0.0.1:9001/health && break || sleep 2 + if curl -fsS http://127.0.0.1:9001/health; then + ok=1 + break + fi + sleep 2 done + if [ -z "${ok}" ]; then + # Attach logs so the failing run is diagnosable. + sudo journalctl -u etherpad-lite --no-pager -n 200 || true + exit 1 + fi sudo systemctl stop etherpad-lite sudo dpkg --purge etherpad-lite ! id etherpad 2>/dev/null diff --git a/packaging/nfpm.yaml b/packaging/nfpm.yaml index 4a8a5b12f55..eaf8ffce8c9 100644 --- a/packaging/nfpm.yaml +++ b/packaging/nfpm.yaml @@ -59,12 +59,17 @@ contents: file_info: mode: 0644 - # Default environment file (conffile: preserved on upgrade) + # Default environment file (conffile: preserved on upgrade). + # Mode 0640 + group=etherpad so passwords/secrets admins drop in here + # are only readable by root and the etherpad service user — /etc/default + # is world-readable by default (0644), which would leak DB creds etc. - src: ./packaging/systemd/etherpad-lite.default dst: /etc/default/etherpad-lite type: config|noreplace file_info: - mode: 0644 + mode: 0640 + owner: root + group: etherpad - src: ./packaging/bin/etherpad-lite dst: /usr/bin/etherpad-lite diff --git a/packaging/scripts/postinstall.sh b/packaging/scripts/postinstall.sh index a1be523244b..9ae2d122b2c 100755 --- a/packaging/scripts/postinstall.sh +++ b/packaging/scripts/postinstall.sh @@ -11,58 +11,58 @@ DIST_SETTINGS=/usr/share/etherpad-lite/settings.json.dist ACTIVE_SETTINGS="${ETC_DIR}/settings.json" case "$1" in - configure) - mkdir -p "${ETC_DIR}" "${VAR_DIR}" "${LOG_DIR}" - chown root:etherpad "${ETC_DIR}" - chmod 0750 "${ETC_DIR}" - chown etherpad:etherpad "${VAR_DIR}" "${LOG_DIR}" - chmod 0750 "${VAR_DIR}" "${LOG_DIR}" + configure) + mkdir -p "${ETC_DIR}" "${VAR_DIR}" "${LOG_DIR}" + chown root:etherpad "${ETC_DIR}" + chmod 0750 "${ETC_DIR}" + chown etherpad:etherpad "${VAR_DIR}" "${LOG_DIR}" + chmod 0750 "${VAR_DIR}" "${LOG_DIR}" - if [ ! -e "${ACTIVE_SETTINGS}" ]; then - cp "${DIST_SETTINGS}" "${ACTIVE_SETTINGS}" - # Point the default dirty-DB at /var/lib so ProtectSystem=strict works. - sed -i \ - 's|"filename": "var/dirty.db"|"filename": "/var/lib/etherpad-lite/dirty.db"|' \ - "${ACTIVE_SETTINGS}" - chown root:etherpad "${ACTIVE_SETTINGS}" - chmod 0640 "${ACTIVE_SETTINGS}" - fi + if [ ! -e "${ACTIVE_SETTINGS}" ]; then + cp "${DIST_SETTINGS}" "${ACTIVE_SETTINGS}" + # Point the default dirty-DB at /var/lib so ProtectSystem=strict works. + sed -i \ + 's|"filename": "var/dirty.db"|"filename": "/var/lib/etherpad-lite/dirty.db"|' \ + "${ACTIVE_SETTINGS}" + chown root:etherpad "${ACTIVE_SETTINGS}" + chmod 0640 "${ACTIVE_SETTINGS}" + fi - # Etherpad reads settings.json from CWD (/opt/etherpad-lite). Expose - # the /etc copy there via symlink. - ln -sfn "${ACTIVE_SETTINGS}" "${APP_DIR}/settings.json" + # Etherpad reads settings.json from CWD (/opt/etherpad-lite). Expose + # the /etc copy there via symlink. + ln -sfn "${ACTIVE_SETTINGS}" "${APP_DIR}/settings.json" - if [ -d "${APP_DIR}/var" ]; then - chown -R etherpad:etherpad "${APP_DIR}/var" || true - fi + if [ -d "${APP_DIR}/var" ]; then + chown -R etherpad:etherpad "${APP_DIR}/var" || true + fi - if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then - systemctl daemon-reload || true - # Enable on first install; leave state alone on upgrade. - if [ -z "$2" ]; then - systemctl enable etherpad-lite.service >/dev/null 2>&1 || true - fi - # Restart on upgrade to pick up new code (skip on fresh install -- - # admin may want to configure first). - if [ -n "$2" ]; then - systemctl try-restart etherpad-lite.service >/dev/null 2>&1 || true - fi - fi + if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then + systemctl daemon-reload || true + # Enable on first install; leave state alone on upgrade. + if [ -z "$2" ]; then + systemctl enable etherpad-lite.service >/dev/null 2>&1 || true + fi + # Restart on upgrade to pick up new code (skip on fresh install -- + # admin may want to configure first). + if [ -n "$2" ]; then + systemctl try-restart etherpad-lite.service >/dev/null 2>&1 || true + fi + fi - cat <&2 - exit 1 - ;; + *) + echo "postinstall called with unknown argument: $1" >&2 + exit 1 + ;; esac exit 0 diff --git a/packaging/scripts/postremove.sh b/packaging/scripts/postremove.sh index 0b5688097ee..b9ed40808d6 100755 --- a/packaging/scripts/postremove.sh +++ b/packaging/scripts/postremove.sh @@ -7,37 +7,37 @@ set -e APP_DIR=/opt/etherpad-lite case "$1" in - remove) - [ -L "${APP_DIR}/settings.json" ] && rm -f "${APP_DIR}/settings.json" || true - if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then - systemctl daemon-reload || true - fi - ;; - - purge) - rm -rf /etc/etherpad-lite - rm -rf /var/lib/etherpad-lite - rm -rf /var/log/etherpad-lite - - if getent passwd etherpad >/dev/null 2>&1; then - deluser --system etherpad >/dev/null 2>&1 || true - fi - if getent group etherpad >/dev/null 2>&1; then - delgroup --system etherpad >/dev/null 2>&1 || true - fi - - if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then - systemctl daemon-reload || true - fi - ;; - - upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) - ;; - - *) - echo "postremove called with unknown argument: $1" >&2 - exit 1 - ;; + remove) + [ -L "${APP_DIR}/settings.json" ] && rm -f "${APP_DIR}/settings.json" || true + if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then + systemctl daemon-reload || true + fi + ;; + + purge) + rm -rf /etc/etherpad-lite + rm -rf /var/lib/etherpad-lite + rm -rf /var/log/etherpad-lite + + if getent passwd etherpad >/dev/null 2>&1; then + deluser --system etherpad >/dev/null 2>&1 || true + fi + if getent group etherpad >/dev/null 2>&1; then + delgroup --system etherpad >/dev/null 2>&1 || true + fi + + if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then + systemctl daemon-reload || true + fi + ;; + + upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postremove called with unknown argument: $1" >&2 + exit 1 + ;; esac exit 0 diff --git a/packaging/scripts/preinstall.sh b/packaging/scripts/preinstall.sh index 02bd1d5fb27..d85c69054d9 100755 --- a/packaging/scripts/preinstall.sh +++ b/packaging/scripts/preinstall.sh @@ -4,25 +4,25 @@ set -e case "$1" in - install|upgrade) - if ! getent group etherpad >/dev/null 2>&1; then - addgroup --system etherpad - fi - if ! getent passwd etherpad >/dev/null 2>&1; then - adduser --system --ingroup etherpad \ - --home /var/lib/etherpad-lite \ - --no-create-home \ - --shell /usr/sbin/nologin \ - --gecos "Etherpad service user" \ - etherpad - fi - ;; - abort-upgrade) - ;; - *) - echo "preinstall called with unknown argument: $1" >&2 - exit 1 - ;; + install|upgrade) + if ! getent group etherpad >/dev/null 2>&1; then + addgroup --system etherpad + fi + if ! getent passwd etherpad >/dev/null 2>&1; then + adduser --system --ingroup etherpad \ + --home /var/lib/etherpad-lite \ + --no-create-home \ + --shell /usr/sbin/nologin \ + --gecos "Etherpad service user" \ + etherpad + fi + ;; + abort-upgrade) + ;; + *) + echo "preinstall called with unknown argument: $1" >&2 + exit 1 + ;; esac exit 0 diff --git a/packaging/scripts/preremove.sh b/packaging/scripts/preremove.sh index efc5a444a74..851fbb8b864 100755 --- a/packaging/scripts/preremove.sh +++ b/packaging/scripts/preremove.sh @@ -4,17 +4,17 @@ set -e case "$1" in - remove|upgrade|deconfigure) - if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then - systemctl stop etherpad-lite.service >/dev/null 2>&1 || true - fi - ;; - failed-upgrade) - ;; - *) - echo "preremove called with unknown argument: $1" >&2 - exit 1 - ;; + remove|upgrade|deconfigure) + if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then + systemctl stop etherpad-lite.service >/dev/null 2>&1 || true + fi + ;; + failed-upgrade) + ;; + *) + echo "preremove called with unknown argument: $1" >&2 + exit 1 + ;; esac exit 0