diff --git a/.github/workflows/hassio-addon.yml b/.github/workflows/hassio-addon.yml new file mode 100644 index 00000000000..2162e983ad3 --- /dev/null +++ b/.github/workflows/hassio-addon.yml @@ -0,0 +1,67 @@ +name: "HA Add-on Build" + +on: + push: + branches: + - develop + paths: + - "packaging/home-assistant/**" + - ".github/workflows/hassio-addon.yml" + tags: + - "v?[0-9]+.[0-9]+.[0-9]+" + pull_request: + paths: + - "packaging/home-assistant/**" + - ".github/workflows/hassio-addon.yml" + +permissions: + contents: read + packages: write + +jobs: + lint: + name: Lint add-on config + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Run hassio-addon-lint + uses: frenck/action-addon-linter@v2 + with: + path: "./packaging/home-assistant/etherpad" + + build: + name: Build ${{ matrix.arch }} + needs: lint + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + arch: + - amd64 + - aarch64 + steps: + - uses: actions/checkout@v6 + + - name: Read add-on manifest + id: manifest + run: | + version=$(grep -m1 '^version:' packaging/home-assistant/etherpad/config.yaml | sed 's/version: *"\?\([^"]*\)"\?/\1/') + echo "version=${version}" >> "$GITHUB_OUTPUT" + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build (and push on non-PR) + uses: home-assistant/builder@master + with: + args: | + --${{ matrix.arch }} \ + --target /data/packaging/home-assistant/etherpad \ + --image "ghcr.io/ether/home-assistant-addon-etherpad-{arch}" \ + --version "${{ steps.manifest.outputs.version }}" \ + ${{ github.event_name == 'pull_request' && '--test' || '--docker-hub-check' }} diff --git a/packaging/home-assistant/etherpad/CHANGELOG.md b/packaging/home-assistant/etherpad/CHANGELOG.md new file mode 100644 index 00000000000..3846ad1df4e --- /dev/null +++ b/packaging/home-assistant/etherpad/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +## 2.6.1 (initial) + +- Initial Home Assistant add-on wrapping the upstream + `etherpad/etherpad:2.6.1` Docker image. +- Ingress support (requires `trust_proxy: true`). +- Persistent dirty DB under `/data/dirty.db`. +- Exposes `title`, `require_authentication`, `admin_password`, + `default_pad_text`, and DB backend selection as HA options. diff --git a/packaging/home-assistant/etherpad/Dockerfile b/packaging/home-assistant/etherpad/Dockerfile new file mode 100644 index 00000000000..523c21ad5e3 --- /dev/null +++ b/packaging/home-assistant/etherpad/Dockerfile @@ -0,0 +1,64 @@ +ARG BUILD_FROM=ghcr.io/home-assistant/amd64-base:3.19 +ARG ETHERPAD_VERSION=2.6.1 + +# --------------------------------------------------------------------------- +# Stage 1: grab the pre-built Etherpad tree from the official image. +# Both the upstream image and the HA base use Alpine, so /opt/etherpad-lite +# copies across cleanly without reinstalling deps. +# --------------------------------------------------------------------------- +FROM etherpad/etherpad:${ETHERPAD_VERSION} AS upstream + +# --------------------------------------------------------------------------- +# Stage 2: assemble the add-on on top of the HA base (Alpine + s6-overlay v3). +# --------------------------------------------------------------------------- +FROM ${BUILD_FROM} + +ARG ETHERPAD_VERSION +ENV ETHERPAD_VERSION=${ETHERPAD_VERSION} \ + NODE_ENV=production \ + ETHERPAD_PRODUCTION=true \ + EP_DIR=/opt/etherpad-lite + +RUN apk add --no-cache \ + bash \ + ca-certificates \ + curl \ + nodejs \ + npm \ + tini \ + && rm -rf /var/cache/apk/* + +COPY --from=upstream /opt/etherpad-lite /opt/etherpad-lite + +# Upstream uses pnpm as the runtime launcher for `pnpm run prod`. +RUN npm install -g pnpm@10.33.0 + +COPY rootfs/ / + +EXPOSE 9001 + +ARG BUILD_ARCH +ARG BUILD_DATE +ARG BUILD_DESCRIPTION +ARG BUILD_NAME +ARG BUILD_REF +ARG BUILD_REPOSITORY +ARG BUILD_VERSION +LABEL \ + io.hass.name="${BUILD_NAME}" \ + io.hass.description="${BUILD_DESCRIPTION}" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.type="addon" \ + io.hass.version=${BUILD_VERSION} \ + maintainer="Etherpad Foundation " \ + org.opencontainers.image.title="${BUILD_NAME}" \ + org.opencontainers.image.description="${BUILD_DESCRIPTION}" \ + org.opencontainers.image.vendor="Etherpad" \ + org.opencontainers.image.authors="Etherpad Foundation" \ + org.opencontainers.image.licenses="Apache-2.0" \ + org.opencontainers.image.url="https://etherpad.org" \ + org.opencontainers.image.source="https://github.com/ether/etherpad-lite" \ + org.opencontainers.image.documentation="https://etherpad.org/doc" \ + org.opencontainers.image.created=${BUILD_DATE} \ + org.opencontainers.image.revision=${BUILD_REF} \ + org.opencontainers.image.version=${BUILD_VERSION} diff --git a/packaging/home-assistant/etherpad/README.md b/packaging/home-assistant/etherpad/README.md new file mode 100644 index 00000000000..d45ace9811d --- /dev/null +++ b/packaging/home-assistant/etherpad/README.md @@ -0,0 +1,83 @@ +# Home Assistant Add-on: Etherpad + +Realtime collaborative document editor, wrapped as a one-click Home +Assistant add-on. + +## Installation + +> [!NOTE] +> This add-on currently lives in-tree at `packaging/home-assistant/` of +> the main Etherpad repository. Home Assistant's Add-on Store expects +> `repository.yaml` at the **root** of a repo, so the main Etherpad repo +> URL is not directly installable until the add-on is split out into its +> own repo (e.g. `ether/home-assistant-addon-etherpad`) or submitted to +> the community umbrella [`hassio-addons/repository`](https://github.com/hassio-addons/repository). +> Track the publication plan in the PR that introduced this scaffold. + +Once published, installation will be: + +1. In Home Assistant, go to **Settings → Add-ons → Add-on Store**. +2. Click the three-dot menu (top right) → **Repositories**. +3. Add the dedicated add-on repository URL. +4. Find **Etherpad** in the store, click **Install**, then **Start**. +5. Use **Open Web UI** to launch Etherpad through HA ingress, or browse + directly to `http://:9001`. + +## Configuration + +| Option | Description | +| ----------------------- | --------------------------------------------------------------------- | +| `title` | Instance name shown in the browser tab. | +| `require_authentication`| If `true`, all pads require login. | +| `admin_password` | Password for the built-in `admin` user (access to `/admin`). | +| `user_password` | Password for the built-in `user` account. | +| `default_pad_text` | Text inserted into every newly-created pad. | +| `db_type` | One of `dirty` (default, file-backed), `mysql`, `postgres`, `sqlite`. | +| `db_host`/`db_port`/... | Used only when `db_type` is not `dirty`. | +| `trust_proxy` | Leave `true` so Home Assistant ingress works correctly. | +| `log_level` | Etherpad log verbosity. | + +### Data persistence + +When `db_type` is `dirty` (the default), pads are stored in +`/data/dirty.db` inside the add-on's persistent volume. Other DB types +expect an external database you operate yourself. + +### Ingress + +This add-on is ingress-enabled: Home Assistant proxies requests to +Etherpad behind authentication, and Etherpad's `trustProxy` setting +ensures cookies and client IPs work correctly. Keep `trust_proxy` set to +`true` for ingress to function. + +If ingress misbehaves (Etherpad does not currently support a configurable +URL base path), disable it by editing `config.yaml` and use the direct +port 9001 instead. + +## Security notes + +- **Admin passwords are stored in plaintext** in Home Assistant's + supervisor database (the `options.json` that the add-on reads). For + stronger secret handling, install the `ep_hash_auth` Etherpad plugin + and supply a bcrypt hash via a hand-edited `settings.json` (advanced). +- The direct port (9001) bypasses Home Assistant authentication. + Firewall it off, or leave only ingress enabled if you care. + +## Links + +- Etherpad: +- Upstream repo: +- Docker image: +- Report bugs: +- HA add-on docs: + +## Icon and logo + +This scaffold ships without `icon.png` / `logo.png`. Add before +publishing: + +- `icon.png` — 128×128 square, shown in the add-on list. +- `logo.png` — 250×100 wide, shown on the add-on detail page. + +Source: see Etherpad brand assets at +. diff --git a/packaging/home-assistant/etherpad/config.yaml b/packaging/home-assistant/etherpad/config.yaml new file mode 100644 index 00000000000..83d3f7b8331 --- /dev/null +++ b/packaging/home-assistant/etherpad/config.yaml @@ -0,0 +1,57 @@ +name: Etherpad +version: "2.6.1" +slug: etherpad +description: Realtime collaborative document editor +url: https://etherpad.org +# Arch list matches .github/workflows/docker.yml's upstream matrix +# (linux/amd64, linux/arm64). armv7/armhf would require upstream Docker +# CI to build those first. +arch: + - aarch64 + - amd64 +ingress: true +ingress_port: 9001 +ingress_stream: true +panel_icon: mdi:file-document-edit-outline +panel_title: Etherpad +ports: + "9001/tcp": 9001 +ports_description: + "9001/tcp": Etherpad web UI (direct access, bypasses ingress) +map: + - addon_config:rw + - share:rw +options: + title: Etherpad + require_authentication: false + admin_password: "" + user_password: "" + default_pad_text: | + Welcome to Etherpad! + + This pad text is synchronized as you type, so that everyone viewing this + page sees the same text. This allows you to collaborate seamlessly on + documents! + db_type: dirty + db_host: "" + db_port: 0 + db_name: "" + db_user: "" + db_password: "" + trust_proxy: true + log_level: INFO +schema: + title: str + require_authentication: bool + admin_password: password + user_password: password + default_pad_text: str + db_type: list(dirty|mysql|postgres|sqlite) + db_host: str? + db_port: port? + db_name: str? + db_user: str? + db_password: password? + trust_proxy: bool + log_level: list(DEBUG|INFO|WARN|ERROR) +image: ghcr.io/ether/home-assistant-addon-etherpad-{arch} diff --git a/packaging/home-assistant/etherpad/rootfs/etc/cont-init.d/10-etherpad.sh b/packaging/home-assistant/etherpad/rootfs/etc/cont-init.d/10-etherpad.sh new file mode 100755 index 00000000000..76f3ad83946 --- /dev/null +++ b/packaging/home-assistant/etherpad/rootfs/etc/cont-init.d/10-etherpad.sh @@ -0,0 +1,53 @@ +#!/usr/bin/with-contenv bashio +# ============================================================================== +# Home Assistant Add-on: Etherpad +# Renders a shell env file from /data/options.json so that Etherpad's built-in +# ${ENV_VAR:default} substitution in settings.json picks up user config. +# ============================================================================== +set -e + +ENV_FILE=/etc/etherpad/env +mkdir -p "$(dirname "${ENV_FILE}")" + +{ + echo "export TITLE=$(bashio::config 'title' | jq -Rr @sh)" + echo "export REQUIRE_AUTHENTICATION=$(bashio::config 'require_authentication')" + echo "export TRUST_PROXY=$(bashio::config 'trust_proxy')" + echo "export LOGLEVEL=$(bashio::config 'log_level')" + echo "export DEFAULT_PAD_TEXT=$(bashio::config 'default_pad_text' | jq -Rr @sh)" + + admin_pw=$(bashio::config 'admin_password') + if bashio::var.has_value "${admin_pw}"; then + echo "export ADMIN_PASSWORD=$(printf '%s' "${admin_pw}" | jq -Rr @sh)" + else + echo "export ADMIN_PASSWORD=null" + fi + + user_pw=$(bashio::config 'user_password') + if bashio::var.has_value "${user_pw}"; then + echo "export USER_PASSWORD=$(printf '%s' "${user_pw}" | jq -Rr @sh)" + else + echo "export USER_PASSWORD=null" + fi + + db_type=$(bashio::config 'db_type') + echo "export DB_TYPE=${db_type}" + if [ "${db_type}" = "dirty" ]; then + # Persist the dirty DB under /data so pads survive restarts. + echo "export DB_FILENAME=/data/dirty.db" + else + echo "export DB_HOST=$(bashio::config 'db_host')" + echo "export DB_PORT=$(bashio::config 'db_port')" + echo "export DB_NAME=$(bashio::config 'db_name')" + echo "export DB_USER=$(bashio::config 'db_user')" + echo "export DB_PASS=$(bashio::config 'db_password' | jq -Rr @sh)" + fi + + # Ingress: HA proxies through a random base path; Etherpad picks up + # X-Forwarded-* headers when trustProxy is true. + echo "export PORT=9001" + echo "export IP=0.0.0.0" +} > "${ENV_FILE}" + +chmod 0600 "${ENV_FILE}" +bashio::log.info "Etherpad configuration rendered to ${ENV_FILE}" diff --git a/packaging/home-assistant/etherpad/rootfs/etc/services.d/etherpad/finish b/packaging/home-assistant/etherpad/rootfs/etc/services.d/etherpad/finish new file mode 100755 index 00000000000..361289a12a8 --- /dev/null +++ b/packaging/home-assistant/etherpad/rootfs/etc/services.d/etherpad/finish @@ -0,0 +1,12 @@ +#!/usr/bin/with-contenv bashio +# ============================================================================== +# Home Assistant Add-on: Etherpad +# Cleanup when the Etherpad service exits. Exit code 256 = signaled; +# anything else is a real fault — tell s6 to bring the container down so +# the supervisor can restart it cleanly. +# ============================================================================== +if [[ "${1}" -ne 0 ]] && [[ "${1}" -ne 256 ]]; then + bashio::log.warning "Etherpad crashed (exit=${1}), halting add-on." + exec /run/s6/basedir/bin/halt +fi +bashio::log.info "Etherpad service stopped cleanly." diff --git a/packaging/home-assistant/etherpad/rootfs/etc/services.d/etherpad/run b/packaging/home-assistant/etherpad/rootfs/etc/services.d/etherpad/run new file mode 100755 index 00000000000..882e55b8acc --- /dev/null +++ b/packaging/home-assistant/etherpad/rootfs/etc/services.d/etherpad/run @@ -0,0 +1,15 @@ +#!/usr/bin/with-contenv bashio +# ============================================================================== +# Home Assistant Add-on: Etherpad +# Starts Etherpad as a long-running service under s6-overlay. +# ============================================================================== +set -e + +# Load rendered config from cont-init. +# shellcheck source=/dev/null +. /etc/etherpad/env + +cd "${EP_DIR}" + +bashio::log.info "Starting Etherpad ${ETHERPAD_VERSION} on port ${PORT}..." +exec pnpm run prod diff --git a/packaging/home-assistant/repository.yaml b/packaging/home-assistant/repository.yaml new file mode 100644 index 00000000000..1bd4b15b876 --- /dev/null +++ b/packaging/home-assistant/repository.yaml @@ -0,0 +1,3 @@ +name: Etherpad Add-ons +url: https://github.com/ether/etherpad-lite +maintainer: Etherpad Foundation