feat(packaging): publish Etherpad as a Snap#7558
feat(packaging): publish Etherpad as a Snap#7558JohnMcLear wants to merge 3 commits intoether:developfrom
Conversation
Adds first-class Snap packaging so Ubuntu / snapd users can install via `sudo snap install etherpad-lite`. - snap/snapcraft.yaml — core24, strict confinement, builds with pnpm against a pinned Node.js 22 runtime. Version is auto-derived from src/package.json so `snap info` tracks upstream release numbering. - snap/local/bin/etherpad-service — launch wrapper that seeds $SNAP_COMMON/etc/settings.json on first run (rewriting the default dirty-DB path to a writable $SNAP_COMMON location) and execs Etherpad via `node --import tsx/esm`. - snap/local/bin/etherpad-healthcheck-wrapper — HTTP probe for external supervisors, falling back to Node if curl isn't staged. - snap/local/bin/etherpad-cli — thin passthrough to Etherpad's bin/ scripts (importSqlFile, checkPad, etc.). - snap/hooks/configure — exposes `snap set etherpad-lite port=<n>` and `ip=<addr>` with validation, restarts the service when running. - snap/README.md — build / install / configure / publish instructions. - .github/workflows/snap-publish.yml — builds on every v* tag, uploads a short-lived artifact, publishes to `edge`, and then promotes to `stable` through a manually-approved GitHub Environment. Requires a one-time `snapcraft register etherpad-lite` plus provisioning of the `SNAPCRAFT_STORE_CREDENTIALS` repo secret (instructions inline). Pad data (dirty DB, logs) lives in /var/snap/etherpad-lite/common/ and survives snap refreshes. The read-only $SNAP squashfs is never written to at runtime. Refs ether#7529 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review Summary by QodoAdd Snap packaging with automated Store publishing
WalkthroughsDescription• Adds first-class Snap packaging for Ubuntu/snapd users • Implements automated CI/CD pipeline for Snap Store publishing • Provides configuration hooks for port and IP binding • Includes health check and CLI wrapper utilities Diagramflowchart LR
A["Source Code"] -->|snapcraft.yaml| B["Build Snap"]
B -->|tag push| C["GitHub Actions"]
C -->|build| D["Snap Artifact"]
D -->|publish| E["Edge Channel"]
E -->|manual approval| F["Stable Channel"]
G["snap set config"] -->|configure hook| H["Port/IP Override"]
H -->|etherpad-service| I["Running Service"]
J["Health Check"] -->|HTTP probe| I
File Changes1. snap/snapcraft.yaml
|
Code Review by Qodo
1. Tag filter never matches
|
Addresses Qodo review feedback on ether#7558: 1. Settings file ignored: Etherpad's Settings loader reads `argv.settings`, not the `EP_SETTINGS` env var. Without `--settings`, the launcher's seeded $SNAP_COMMON/etc/settings.json is never loaded; Etherpad falls back to <install-root>/settings.json, which lives on the read-only squashfs — so the default dirty-DB path ends up unwritable and the daemon fails to persist pads. Fix: pass `--settings "${SETTINGS}"` to node; drop the EP_SETTINGS export. 2. `snap set` overrides were no-ops: the seeded settings.json carries the template's literal `"ip": "0.0.0.0"` / `"port": 9001` values, which override the env-based defaults Etherpad exposes via ${…} substitution. Users following the README saw the listener stay put after `snap set etherpad-lite port=…`. Fix: after copying the template on first run, rewrite the top-level `ip` and `port` lines to `"${IP:0.0.0.0}"` / `"${PORT:9001}"`. Use `0,/…/` anchors so the `dbSettings.port` entry further down stays literal. 3. Indentation: reflow the new shell scripts from 4-space to 2-space to match the repo style rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Qodo's |
settings.json.template's own comment says dirty is for testing only. A Snap install is the "not testing" case — shipping it by default means every `sudo snap install etherpad-lite` starts on a DB the project explicitly recommends against. Rewrite the postinstall sed to switch dbType: "dirty" → "sqlite" and point filename at $SNAP_COMMON/var/etherpad.db. sqlite is already shipped in-tree via ueberdb2 → rusty-store-kv (prebuilt napi-rs binary, no build deps), so this works under strict confinement with zero snap.yaml changes. Only affects first-run seeding; existing $SNAP_COMMON/etc/settings.json is never touched on refresh. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/review |
|
Persistent review updated to latest commit 0db0063 |
| on: | ||
| push: | ||
| tags: | ||
| - 'v?[0-9]+.[0-9]+.[0-9]+' | ||
| workflow_dispatch: |
There was a problem hiding this comment.
1. Tag filter never matches 🐞 Bug ☼ Reliability
In .github/workflows/snap-publish.yml the tag filter uses a regex-like pattern (v?[0-9]+.[0-9]+.[0-9]+) but GitHub Actions uses glob matching, so + is treated literally and typical release tags like v2.6.1 will not match. As a result the Snap publish workflow will not trigger on release tags and nothing will be built/published automatically.
Agent Prompt
### Issue description
The workflow tag trigger pattern is written like a regex, but GitHub Actions uses glob matching for `on.push.tags`. The current pattern will not match normal semver tags like `v2.6.1`, so the workflow will not run on releases.
### Issue Context
The workflow intends to run on tags matching `v?X.Y.Z` (optional leading `v`).
### Fix Focus Areas
- .github/workflows/snap-publish.yml[17-21]
### Suggested change
Replace the single regex-like entry with glob patterns, for example:
- `v[0-9]*.[0-9]*.[0-9]*`
- `[0-9]*.[0-9]*.[0-9]*`
(or whichever variant matches your actual tagging scheme).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| SCRIPT_NAME="$1"; shift | ||
| SCRIPT_PATH="${APP_DIR}/bin/${SCRIPT_NAME}" | ||
| [ -f "${SCRIPT_PATH}" ] || { echo "no such script: ${SCRIPT_NAME}"; exit 2; } | ||
|
|
||
| case "${SCRIPT_PATH}" in | ||
| *.sh) exec "${SCRIPT_PATH}" "$@" ;; | ||
| *.ts) exec "${NODE_BIN}" --import tsx/esm "${SCRIPT_PATH}" "$@" ;; | ||
| esac |
There was a problem hiding this comment.
2. Cli path traversal exec 🐞 Bug ⛨ Security
snap/local/bin/etherpad-cli builds SCRIPT_PATH from unvalidated user input, so a caller can pass
a value containing / or .. to escape the intended ${APP_DIR}/bin directory and execute
arbitrary .ts/.sh files shipped in the snap. Additionally, there is no default case branch, so
if a file exists but is not .ts or .sh the command silently does nothing and exits successfully.
Agent Prompt
### Issue description
The snap CLI wrapper allows path traversal via the `<bin-script>` argument and can execute unintended files. It also silently succeeds for unsupported extensions.
### Issue Context
This command is meant to be a thin, safe passthrough to scripts under `$SNAP/opt/etherpad-lite/bin`.
### Fix Focus Areas
- snap/local/bin/etherpad-cli[17-24]
### Suggested change
- Reject any `SCRIPT_NAME` that contains `/` or `..` (or normalize to `basename` and compare).
- Optionally enforce an allowlist derived from `$APP_DIR/bin`.
- Add a default `*)` case that prints an error like `unsupported script type` and exits non-zero.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
Adds first-class Snap packaging so Ubuntu / snapd users can install with
sudo snap install etherpad-lite.Part of #7529 — top-3 deployment targets (Snap, Apt, Home Assistant).
snap/snapcraft.yaml— core24, strict confinement, pnpm + pinned Node.js 22. Version auto-derived fromsrc/package.json.snap/local/bin/etherpad-service— launch wrapper; seedssettings.jsoninto$SNAP_COMMONon first run, rewrites the shipped dirty default to sqlite at$SNAP_COMMON/var/etherpad.db, execs vianode --import tsx/esm.snap/local/bin/etherpad-healthcheck-wrapper— HTTP/healthprobe for external supervisors.snap/local/bin/etherpad-cli— passthrough tobin/scripts (importSqlFile, checkPad, …).snap/hooks/configure— exposessnap set etherpad-lite port=<n>/ip=<addr>with validation.snap/README.md— build / install / configure / publish instructions..github/workflows/snap-publish.yml— tag-triggered build →edge→ gatedstablevia GitHub Environment approval.Default DB is sqlite, not dirty
settings.json.templateships withdbType: "dirty", and the template itself warns "You shouldn't use 'dirty' for anything else than testing". A Snap install is exactly the "not testing" case, so the launch wrapper's first-run sed now switches the seeded config to sqlite ($SNAP_COMMON/var/etherpad.db). sqlite is already in-tree viaueberdb2→rusty-store-kv(prebuilt napi-rs binary), so strict confinement works with zerosnap.yamlchanges. Existing seededsettings.jsonfiles are never touched on refresh.Pad data (sqlite DB, logs) lives in
/var/snap/etherpad-lite/common/and survivessnap refresh. The read-only$SNAPsquashfs is never written to at runtime.Maintainer action required (one-time)
snapcraft register etherpad-lite— claims the name.SNAPCRAFT_STORE_CREDENTIALS:snap-store-stablewith required reviewers so stable promotion is gated.See https://snapcraft.io/docs/releasing-to-the-snap-store.
Test plan
snapcraftbuilds locally (LXD provider)sudo snap install --dangerous etherpad-lite_*.snapinstalls cleanly on Ubuntu 24.04curl http://127.0.0.1:9001/healthreturns 200 aftersudo snap start etherpad-litegrep sqlite /var/snap/etherpad-lite/common/etc/settings.json(sanity: wrapper actually rewrote to sqlite)/var/snap/etherpad-lite/common/var/etherpad.dbis created by the servicesudo snap set etherpad-lite port=9100 && sudo snap restart etherpad-literelocates the listener/var/snap/etherpad-lite/commonRefs #7529
🤖 Generated with Claude Code