Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/workflows/snap-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Builds and publishes the Etherpad snap on tagged releases.
# Mirrors the trigger pattern from .github/workflows/docker.yml / release.yml
# (tags matching v?X.Y.Z).
#
# One-time maintainer setup:
# 1. `snapcraft register etherpad-lite` claims the name.
# 2. Generate a store credential:
# snapcraft export-login --snaps etherpad-lite \
# --channels edge,stable \
# --acls package_access,package_push,package_release -
# Store the output as repo secret SNAPCRAFT_STORE_CREDENTIALS.
# 3. Create a GitHub Environment called `snap-store-stable` with required
# reviewers so stable promotion is gated.
#
# Ref: https://snapcraft.io/docs/releasing-to-the-snap-store
name: Snap
on:
push:
tags:
- 'v?[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
Comment on lines +17 to +21
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. 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


permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
outputs:
snap-file: ${{ steps.build.outputs.snap }}
steps:
- name: Check out
uses: actions/checkout@v6

- name: Build snap
id: build
uses: snapcore/action-build@v1

- name: Upload snap artifact
uses: actions/upload-artifact@v4
with:
name: etherpad-lite-snap
path: ${{ steps.build.outputs.snap }}
if-no-files-found: error
retention-days: 7

publish-edge:
needs: build
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Download snap artifact
uses: actions/download-artifact@v4
with:
name: etherpad-lite-snap

- name: Publish to edge
uses: snapcore/action-publish@v1
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
with:
snap: ${{ needs.build.outputs.snap-file }}
release: edge

publish-stable:
needs: [build, publish-edge]
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
# Manual gate: promote edge -> stable via GitHub Environments approval.
environment: snap-store-stable
steps:
- name: Download snap artifact
uses: actions/download-artifact@v4
with:
name: etherpad-lite-snap

- name: Publish to stable
uses: snapcore/action-publish@v1
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
with:
snap: ${{ needs.build.outputs.snap-file }}
release: stable
61 changes: 61 additions & 0 deletions snap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Etherpad snap

Packages Etherpad as a [Snap](https://snapcraft.io/) for publishing to the
Snap Store.

## Build locally

```
sudo snap install --classic snapcraft
sudo snap install lxd && sudo lxd init --auto
snapcraft # from repo root; uses LXD by default
```

Output: `etherpad-lite_<version>_<arch>.snap`.

## Install the local build

```
sudo snap install --dangerous ./etherpad-lite_*.snap
sudo snap start etherpad-lite
curl http://127.0.0.1:9001/health
```

Logs: `sudo snap logs etherpad-lite -f`.

## Configure

The snap seeds `$SNAP_COMMON/etc/settings.json` from the upstream
template on first run. Edit that file to customise Etherpad, then:

```
sudo snap restart etherpad-lite
```

A few values are exposed as snap config for convenience:

| Key | Default | Notes |
| ----------------------------------- | --------- | --------------- |
| `snap set etherpad-lite port=9001` | `9001` | Listen port |
| `snap set etherpad-lite ip=0.0.0.0` | `0.0.0.0` | Bind address |

Pad data (dirty DB, logs) lives in `/var/snap/etherpad-lite/common/` and
survives `snap refresh`.

## Publish to the Snap Store

Maintainers only. See
[Releasing to the Snap Store](https://snapcraft.io/docs/releasing-to-the-snap-store).

One-time setup:

```
snapcraft register etherpad-lite
snapcraft export-login --snaps etherpad-lite \
--channels edge,stable \
--acls package_access,package_push,package_release -
```

Store the printed credential in the repo secret
`SNAPCRAFT_STORE_CREDENTIALS`. CI (`.github/workflows/snap-publish.yml`)
handles the rest on every `v*` tag.
24 changes: 24 additions & 0 deletions snap/hooks/configure
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Validates values set via `snap set etherpad-lite key=value`.
# Supported keys:
# port : integer 1-65535 (default 9001). Ports <1024 require AppArmor override.
# ip : bind address (default 0.0.0.0)
set -euo pipefail

PORT="$(snapctl get port || true)"
if [ -n "${PORT}" ]; then
if ! [[ "${PORT}" =~ ^[0-9]+$ ]] || [ "${PORT}" -lt 1 ] || [ "${PORT}" -gt 65535 ]; then
echo "port must be an integer 1-65535" >&2
exit 1
fi
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
Outdated
fi

IP="$(snapctl get ip || true)"
if [ -n "${IP}" ] && ! [[ "${IP}" =~ ^[0-9a-fA-F.:]+$ ]]; then
echo "ip must be a valid IPv4/IPv6 address" >&2
exit 1
fi

if snapctl services etherpad-lite.etherpad-lite 2>/dev/null | grep -q active; then
snapctl restart etherpad-lite.etherpad-lite
fi
24 changes: 24 additions & 0 deletions snap/local/bin/etherpad-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Thin passthrough to Etherpad's bin/ scripts.
# Usage: etherpad-lite.etherpad <bin-script> [args...]
set -euo pipefail

APP_DIR="${SNAP}/opt/etherpad-lite"
NODE_BIN="${SNAP}/opt/node/bin/node"
export PATH="${SNAP}/opt/node/bin:${PATH}"

if [ "$#" -eq 0 ]; then
echo "Usage: etherpad-lite.etherpad <bin-script> [args...]"
echo "Available scripts:"
ls "${APP_DIR}/bin" | grep -E '\.(ts|sh)$' | sed 's/^/ /'
exit 2
fi

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
Comment on lines +17 to +24
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. 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

20 changes: 20 additions & 0 deletions snap/local/bin/etherpad-healthcheck-wrapper
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# HTTP healthcheck. Returns 0 if /health returns 200.
set -euo pipefail

PORT="$(snapctl get port 2>/dev/null || true)"
: "${PORT:=9001}"

if command -v curl >/dev/null 2>&1; then
exec curl --fail --silent --show-error --max-time 5 \
"http://127.0.0.1:${PORT}/health"
fi

NODE_BIN="${SNAP}/opt/node/bin/node"
exec "${NODE_BIN}" -e '
const http = require("http");
http.get("http://127.0.0.1:'"${PORT}"'/health", r => {
if (r.statusCode === 200) process.exit(0);
console.error("HTTP " + r.statusCode); process.exit(1);
}).on("error", e => { console.error(e.message); process.exit(1); });
'
43 changes: 43 additions & 0 deletions snap/local/bin/etherpad-service
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
# Launch wrapper for the Etherpad snap daemon.
#
# 1. On first run, copy settings.json.template -> $SNAP_COMMON/etc/settings.json
# so the admin can edit it outside the read-only squashfs.
# 2. Create writable data dirs under $SNAP_COMMON.
# 3. Apply `snap set` overrides (port, ip) via env vars — settings.json
# uses ${PORT:9001}-style substitution natively.
# 4. Exec Node with tsx loader to run server.ts.
set -euo pipefail

APP_DIR="${SNAP}/opt/etherpad-lite"
NODE_BIN="${SNAP}/opt/node/bin/node"

export PATH="${SNAP}/opt/node/bin:${SNAP}/usr/bin:${SNAP}/bin:${PATH}"

ETC_DIR="${SNAP_COMMON}/etc"
VAR_DIR="${SNAP_COMMON}/var"
LOG_DIR="${SNAP_COMMON}/logs"
mkdir -p "${ETC_DIR}" "${VAR_DIR}" "${LOG_DIR}"

SETTINGS="${ETC_DIR}/settings.json"
if [ ! -f "${SETTINGS}" ]; then
echo "[etherpad-snap] bootstrapping ${SETTINGS} from template"
cp "${APP_DIR}/settings.json.template" "${SETTINGS}"
# Rewrite the dirty DB filename to a writable absolute path.
sed -i \
-e 's|"filename": "var/dirty.db"|"filename": "'"${VAR_DIR}"'/dirty.db"|' \
"${SETTINGS}"
fi

PORT_OVERRIDE="$(snapctl get port || true)"
IP_OVERRIDE="$(snapctl get ip || true)"
: "${PORT_OVERRIDE:=9001}"
: "${IP_OVERRIDE:=0.0.0.0}"
export PORT="${PORT_OVERRIDE}"
export IP="${IP_OVERRIDE}"
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

cd "${APP_DIR}"
export EP_SETTINGS="${SETTINGS}"
export NODE_ENV=production

exec "${NODE_BIN}" --import tsx/esm src/node/server.ts "$@"
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
Outdated
Loading
Loading