Skip to content
Merged
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 thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

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 thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
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