From 5606afe150555c9d5cf1a99a03852a5a4a06b253 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 19 Apr 2026 18:21:36 +0100 Subject: [PATCH 1/2] feat(ci): open a downstream-bump tracking issue on every release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a single source of truth for every downstream distribution of Etherpad (Docker Hub, Snap, Debian, Home Assistant, Umbrel, TrueCharts, Proxmox, Cloudron, YunoHost, CasaOS, BigBlueButton, Unraid, Sandstorm, Nextcloud Ownpad) at docs/downstreams.yml, plus a workflow that, on every GitHub release publish, opens a single tracking issue with a checklist grouped by how each downstream is kept current: ๐Ÿš€ Automatic โ€” this repo's CI handles it on tag push ๐Ÿงฉ Manual bump in-repo โ€” someone edits a file here, CI does the rest ๐Ÿค– Externally automated โ€” a Renovate-like bot on the downstream side โœ‰๏ธ Needs a PR we send โ€” a maintainer files a bump PR ๐Ÿ“จ Needs an issue we file ๐Ÿค Maintained externally โ€” we have no lever; poke if stale โš ๏ธ Known stale โ€” kept for visibility, no action Motivation: without this, external catalogs like CasaOS, TrueCharts, Bigbluebutton's `bbb-etherpad.placeholder.sh`, and the Sandstorm market listing accumulate years of drift. Turning "remember every downstream" into a per-release checklist is the lightest-touch fix that scales. The renderer is a standalone Python script so the issue format can be tweaked and dry-run locally: python3 .github/scripts/render-downstream-tracker.py \ docs/downstreams.yml 2.6.1 ether/etherpad-lite A `workflow_dispatch` trigger with a manual `version` input is included so the tracker can be smoke-tested before the next real release. Refs #7529 Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/scripts/render-downstream-tracker.py | 109 ++++++++++++ .github/workflows/release-downstreams.yml | 71 ++++++++ docs/downstreams.yml | 172 +++++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100755 .github/scripts/render-downstream-tracker.py create mode 100644 .github/workflows/release-downstreams.yml create mode 100644 docs/downstreams.yml diff --git a/.github/scripts/render-downstream-tracker.py b/.github/scripts/render-downstream-tracker.py new file mode 100755 index 00000000000..5624f4d6cb8 --- /dev/null +++ b/.github/scripts/render-downstream-tracker.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Render the downstream-bump tracking issue body from docs/downstreams.yml. + +Called from .github/workflows/release-downstreams.yml. Kept as a +standalone script (rather than inline yaml-munging in the workflow) so +the format is easy to tweak without re-running CI to eyeball it โ€” run +locally with: + + python3 .github/scripts/render-downstream-tracker.py \\ + docs/downstreams.yml 2.6.1 ether/etherpad-lite + +Usage: render-downstream-tracker.py +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import yaml + +GROUPS: list[tuple[str, str]] = [ + ("automatic", "๐Ÿš€ Automatic (this repo handles it)"), + ("manual_ci", "๐Ÿงฉ Manual bump in this repo"), + ("external_auto", "๐Ÿค– Externally automated"), + ("external_pr", "โœ‰๏ธ Needs a PR we send"), + ("external_issue", "๐Ÿ“จ Needs an issue we file"), + ("external_maintainer", "๐Ÿค Maintained externally โ€” poke if stale"), + ("stale", "โš ๏ธ Known stale โ€” informational only"), +] + + +def render(catalog_path: Path, version: str, repo: str) -> str: + with catalog_path.open() as f: + catalog = yaml.safe_load(f) + items = catalog.get("downstreams", []) + + out: list[str] = [] + out.append(f"## Downstream distribution checklist for `{version}`\n") + out.append( + "Auto-opened by `.github/workflows/release-downstreams.yml` on " + "release publish.\n" + ) + out.append( + f"Source of truth: [`docs/downstreams.yml`](https://github.com/" + f"{repo}/blob/develop/docs/downstreams.yml).\n" + ) + out.append( + "Tick items as you verify them. Anything still unchecked a week " + "after release is a candidate for follow-up.\n" + ) + + for update_type, heading in GROUPS: + matches = [i for i in items if i.get("update_type") == update_type] + if not matches: + continue + out.append(f"\n### {heading}\n") + for item in matches: + out.append(_render_item(item, repo)) + + return "\n".join(out) + + +def _render_item(item: dict, repo: str) -> str: + name = item["name"] + target_repo = item.get("repo") + # `path` and `file` are aliases that point at a specific file/dir + # inside the downstream repo (or inside this repo for `manual_ci`). + path = item.get("path") or item.get("file") + workflow = item.get("workflow") + notes = item.get("notes", "").strip() + + # Primary link: deep-link to the file/dir if we know one, otherwise + # to the repo root. `HEAD` avoids pinning to a stale default-branch + # name (`main` vs `master` vs `develop`). + link = "" + if target_repo: + base = f"https://github.com/{target_repo}" + if path: + link = f" โ€” [`{target_repo}/{path}`]({base}/blob/HEAD/{path})" + else: + link = f" โ€” [`{target_repo}`]({base})" + if workflow: + workflow_url = f"https://github.com/{repo}/blob/develop/{workflow}" + link += f" ยท [workflow]({workflow_url})" + + lines = [f"- [ ] **{name}**{link}"] + if notes: + # Indent notes under the checkbox so GitHub renders them as part + # of the list item rather than a sibling paragraph. + for note_line in notes.splitlines(): + lines.append(f" {note_line}") + lines.append("") + return "\n".join(lines) + + +def main() -> int: + if len(sys.argv) != 4: + print(__doc__, file=sys.stderr) + return 2 + catalog_path = Path(sys.argv[1]) + version = sys.argv[2] + repo = sys.argv[3] + print(render(catalog_path, version, repo)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/release-downstreams.yml b/.github/workflows/release-downstreams.yml new file mode 100644 index 00000000000..9a4a6c36527 --- /dev/null +++ b/.github/workflows/release-downstreams.yml @@ -0,0 +1,71 @@ +name: Release โ€” downstream bump tracker + +# Opens a single tracking issue on every release that lists every +# downstream distribution of Etherpad (see docs/downstreams.yml) with +# hints on whether they update automatically or need a manual PR. +# +# Rationale: external catalogs (CasaOS, TrueCharts, BBB, Unraid, โ€ฆ) go +# stale because nobody remembers to poke them at release time. This +# workflow turns "remember to update every downstream" into a checklist +# we can close methodically. +# +# To test before a real release: run `Actions โ†’ Release โ€” downstream +# bump tracker โ†’ Run workflow`, supply a version (e.g. `2.6.1-test`). + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: Version string to use (e.g. 2.6.1). Defaults to the release tag. + required: true + +permissions: + contents: read + issues: write + +jobs: + open-tracking-issue: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v6 + + - name: Resolve version + id: v + env: + TAG: ${{ github.event.release.tag_name }} + INPUT: ${{ inputs.version }} + run: | + VERSION="${TAG:-$INPUT}" + VERSION="${VERSION#v}" + if [ -z "${VERSION}" ]; then + echo "Could not determine version." >&2 + exit 1 + fi + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Render issue body + id: render + env: + VERSION: ${{ steps.v.outputs.version }} + run: | + python3 -m pip install --quiet pyyaml + BODY=$(mktemp) + python3 .github/scripts/render-downstream-tracker.py \ + docs/downstreams.yml \ + "$VERSION" \ + "$GITHUB_REPOSITORY" \ + > "$BODY" + echo "body-path=$BODY" >> "$GITHUB_OUTPUT" + + - name: Open tracking issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh issue create \ + --repo "$GITHUB_REPOSITORY" \ + --title "Downstream bumps for ${{ steps.v.outputs.version }}" \ + --label "release,downstream" \ + --body-file '${{ steps.render.outputs.body-path }}' diff --git a/docs/downstreams.yml b/docs/downstreams.yml new file mode 100644 index 00000000000..a9b2ba9757c --- /dev/null +++ b/docs/downstreams.yml @@ -0,0 +1,172 @@ +# Downstream distribution catalog for Etherpad. +# +# This file is the single source of truth for every place Etherpad is +# packaged or listed outside this repository. It is consumed by +# .github/workflows/release-downstreams.yml, which opens a tracking issue +# on every release so maintainers can work through the list and keep +# downstream catalogs from going stale. +# +# When you add a new downstream, put it here first โ€” the release +# workflow will then remind us to update it on the next tag. +# +# update_type values: +# automatic โ€” handled by a workflow in this repo on tag push +# (Docker Hub, Snap, Debian .deb, Home Assistant +# add-on image). No manual step required; the +# checklist item is a smoke-test verification. +# manual_ci โ€” controlled by a file in this repo that a human +# needs to bump (e.g. the HA add-on config.yaml +# `version:` field). The workflow hints which +# file to edit. +# external_auto โ€” a downstream repo's own automation (Renovate, +# a similar bot, or a runtime `check_for_gh_release` +# call) detects new Docker/GitHub tags. No action +# required unless the bot falls behind. +# external_pr โ€” a downstream repo requires a manual PR to bump +# a pinned version. A maintainer opens that PR. +# external_issue โ€” a downstream repo accepts issues rather than PRs +# for version bumps (maintainer-driven). +# external_maintainer โ€” entirely out of our hands; the downstream has an +# owner we can ping but not patch. Keep the entry +# as a reminder to poke them. +# stale โ€” the downstream hasn't tracked upstream in a long +# time. Kept in the list so nobody re-discovers it +# and assumes it's current. + +downstreams: + # ---- In-repo, fully automated ----------------------------------------- + - name: Official Docker image + repo: ether/etherpad-lite + update_type: automatic + workflow: .github/workflows/docker.yml + notes: | + Smoke test: `docker pull etherpad/etherpad:` works and the + container exposes `/health` on 9001 after boot. + + - name: Snap (snapcraft.io) + repo: ether/etherpad-lite + update_type: automatic + workflow: .github/workflows/snap-publish.yml + notes: | + Tag triggers edge publish; stable promotion requires a manual + approval in the `snap-store-stable` GitHub Environment. Smoke test: + `snap info etherpad-lite` shows the new version on the `stable` + channel within 24h. + + - name: Debian .deb (GitHub Releases) + repo: ether/etherpad-lite + update_type: automatic + workflow: .github/workflows/deb-package.yml + notes: | + Tag triggers build; artefacts are attached to the GitHub Release. + Smoke test: `dpkg -i etherpad-lite__amd64.deb` on a clean + Ubuntu 24.04 VM, `curl /health` returns 200. + + - name: Home Assistant add-on (GHCR images) + repo: ether/etherpad-lite + update_type: manual_ci + file: packaging/home-assistant/etherpad/config.yaml + workflow: .github/workflows/hassio-addon.yml + notes: | + Bump the `version:` field in `packaging/home-assistant/etherpad/config.yaml` + and merge to develop. The workflow multi-arch builds and pushes to + GHCR. If the upstream Docker image changes base OS, verify the + add-on Dockerfile's `COPY --from=upstream` still works. + + # ---- External, auto-tracking ------------------------------------------ + - name: Umbrel App Store + repo: getumbrel/umbrel-apps + path: etherpad + update_type: external_auto + notes: | + Renovate tracks the Docker image tag. No action needed if the app + bumps within a week of release; otherwise file an issue. + + - name: TrueCharts + repo: trueforge-org/truecharts + path: charts/stable/etherpad + update_type: external_auto + notes: | + Renovate tracks the official `docker.io/etherpad/etherpad` image + (after PR 47234 merged). Verify the chart catalog shows the new + `appVersion:` within a week. + + - name: Proxmox VE Helper-Script + repo: community-scripts/ProxmoxVED + path: ct/etherpad.sh + update_type: external_auto + notes: | + The script uses `check_for_gh_release` against `ether/etherpad-lite` + at user runtime, so there's no per-release bump โ€” users re-running + the helper script pick up the latest GitHub release automatically. + + # ---- External, maintainer-driven -------------------------------------- + - name: Cloudron + repo: cloudron-io/etherpad.cloudronapp + update_type: external_maintainer + notes: | + Cloudron's automation typically publishes within a week. If the + store page lags by more than 2 weeks, post on + . + + - name: YunoHost + repo: YunoHost-Apps/etherpad_ynh + update_type: external_maintainer + notes: | + Actively maintained; usually bumps automatically. If no release + appears within 2 weeks, file an issue on the repo tagging the + current maintainer. + + - name: Nextcloud Ownpad + repo: otetard/ownpad + update_type: external_maintainer + notes: | + External integration plugin; generally forward-compatible with new + Etherpad versions. Only relevant if a breaking API change lands. + + # ---- External, per-release PR needed ---------------------------------- + - name: CasaOS App Store + repo: IceWhaleTech/CasaOS-AppStore + path: Apps/Etherpad/docker-compose.yml + update_type: external_pr + notes: | + Docker image tag is pinned in the compose file. File a PR bumping + `image: etherpad/etherpad:` and the `version` string in + the `x-casaos` metadata. + + - name: BigBlueButton + repo: bigbluebutton/bigbluebutton + path: bbb-etherpad.placeholder.sh + update_type: external_pr + notes: | + Pinned to a specific tag in the placeholder clone script. Major + version bumps may require additional changes in + `build/packages-template/bbb-etherpad/build.sh` (npm โ†’ pnpm, + Node engine bump). File a PR or issue against `develop`. + + - name: Unraid Community Apps + repo: selfhosters/unRAID-CA-templates + update_type: external_maintainer + notes: | + Community-maintained XML templates. If the template falls behind, + file an issue or PR on the template repo, or contact the + maintainer in the Unraid forum thread for Etherpad. + + # ---- Known stale / low-signal ----------------------------------------- + - name: Sandstorm + repo: sandstorm-io/sandstorm + update_type: stale + notes: | + Market listing hasn't moved since 2015. A full repack requires + vagga-based Sandstorm packaging. Noted here so nobody tries the + app expecting it to be current; removing the listing is a separate + conversation with the Sandstorm team. + + - name: TrueNAS SCALE (legacy) + repo: trueforge-org/truecharts + update_type: stale + notes: | + The TrueNAS SCALE app catalog (iXsystems) was historically sourced + from TrueCharts; both have since diverged. TrueCharts' own + catalog is the authoritative source now; iXsystems ships their + own apps independently. From 04410549329694116b1e6931a4177905588a935b Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 19 Apr 2026 18:35:55 +0100 Subject: [PATCH 2/2] fix(ci): use github.event.inputs.version in release trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Qodo feedback on #7561: reading `inputs.version` on a `release: published` event can yield an empty string or a context-evaluation failure depending on runtime. `inputs` only populates on workflow_dispatch. Switch to `github.event.inputs.version` which is typed as the dispatch payload directly, and add the event name to the error message for easier debugging when neither tag nor input is set. Python 4-space indent is left as-is โ€” that's PEP 8, and the 2-space repo style rule Qodo references applies to the shell/YAML/JS tree, not to standalone Python scripts. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release-downstreams.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-downstreams.yml b/.github/workflows/release-downstreams.yml index 9a4a6c36527..a08a7e8ea8f 100644 --- a/.github/workflows/release-downstreams.yml +++ b/.github/workflows/release-downstreams.yml @@ -36,12 +36,18 @@ jobs: id: v env: TAG: ${{ github.event.release.tag_name }} - INPUT: ${{ inputs.version }} + # `inputs.version` only exists on workflow_dispatch; reading it on + # release events can yield an empty string or a context-evaluation + # error depending on GitHub Actions behavior. Pull the dispatch + # payload directly via github.event.inputs so release runs stay + # clean. + INPUT: ${{ github.event.inputs.version }} + EVENT: ${{ github.event_name }} run: | VERSION="${TAG:-$INPUT}" VERSION="${VERSION#v}" if [ -z "${VERSION}" ]; then - echo "Could not determine version." >&2 + echo "Could not determine version (event=${EVENT})." >&2 exit 1 fi echo "version=${VERSION}" >> "$GITHUB_OUTPUT"