diff --git a/.claude/skills/releasing-healthchat/SKILL.md b/.claude/skills/releasing-healthchat/SKILL.md index 8dd5b25def..46f690f7ae 100644 --- a/.claude/skills/releasing-healthchat/SKILL.md +++ b/.claude/skills/releasing-healthchat/SKILL.md @@ -19,16 +19,19 @@ Releases are driven by git tags of the form `healthchat-v` pushed to `:`, so you can deploy them deliberately by overriding the image ref (see "Deploying the release"), but they can never become production by accident. - `.github/workflows/build-healthchat-desktop.yaml` (desktop) — builds the Electron - app for macOS (universal, signed+notarised) and Windows (x64/arm64, signed), and - attaches the installers (.dmg/.zip/.exe/.msi) to the **same** GitHub Release. The two - workflows create-if-missing then upload, so they race safely. (Linux is intentionally - not built — the upstream Linux reusable pulls a build-env Docker image keyed by commit - SHA that only exists in the upstream element-hq registry, so it cannot succeed in this - fork.) The `release` job also **stages** the Squirrel update feed and public installers and - attaches them to the Release as `healthchat-desktop-feed-.tar.gz` — but it - **does not publish them**. See "Deploying the desktop release" below. + app for macOS (universal, signed+notarised) and Windows (x64/arm64, signed) and + publishes to the **same** GitHub Release as the web workflow (both create-if-missing, + so they race safely). (Linux is intentionally not built — the upstream Linux reusable + pulls a build-env Docker image keyed by commit SHA that only exists in the upstream + element-hq registry, so it cannot succeed in this fork.) The `release` job **stages** + the Squirrel update feed and public installers and attaches them to the Release as + split `healthchat-desktop-feed-.tar.gz.part*` assets (the compressed feed + exceeds GitHub's 2 GiB per-asset cap) — but it **does not publish them**. Installer + binaries are not attached individually; the Release notes get a "## Downloads" + section linking their public URLs on packages.healthchat.ch, which resolve once the + version is deployed. See "Deploying the desktop release" below. - `.github/workflows/deploy-healthchat-desktop.yaml` (desktop deploy) — **manual - dispatch only.** Downloads that tarball and mirrors it to + dispatch only.** Downloads and reassembles those feed parts and mirrors the feed to `https://packages.healthchat.ch/desktop/` over FTPS (the private GitHub Release can serve neither end users nor the auto-updater), plus the legacy `https://healthchat.ch/packages/desktop/` path while clients built <= 1.5.0 are still @@ -37,8 +40,8 @@ Releases are driven by git tags of the form `healthchat-v` pushed to the two caveats (first install is manual; Windows auto-update needs the Setup.exe path, not the .msi). -Publish target: **ghcr.io Docker image + GitHub Release (web bundle + desktop -installers).** No npm, no mobile fan-out. +Publish target: **ghcr.io Docker image + GitHub Release (web bundle + desktop feed +parts, with installer download links).** No npm, no mobile fan-out. ## Release ≠ deploy @@ -186,7 +189,8 @@ run or `:latest` had not moved yet. ## Deploying the desktop release -The desktop build attaches `healthchat-desktop-feed-.tar.gz` to the Release but +The desktop build attaches the split `healthchat-desktop-feed-.tar.gz.part*` +assets to the Release but leaves `https://packages.healthchat.ch/desktop/` untouched. Until you run this, installed apps keep whatever version they have and the public download links still serve the old installers. @@ -319,9 +323,10 @@ warning. Prefer fixing the changelog over relying on the fallback. For an `-rc`/`-hotfix`/`-dryrun` tag the opposite is expected: `:latest` must be **unchanged**. - [ ] The desktop workflow run ("Build HealthChat Desktop") is green and the - GitHub Release has the desktop installers attached (.dmg, *-mac.zip, *.msi, - "* Setup*.exe") **plus** `healthchat-desktop-feed-.tar.gz`. Without that - tarball the desktop deploy has nothing to publish. + GitHub Release carries the split feed assets + `healthchat-desktop-feed-.tar.gz.part*` plus a "## Downloads" section + in its notes linking the public installers. Without those parts the desktop + deploy has nothing to publish. - [ ] Confirm the live feed is still on the **previous** version — releasing must not have moved it. `curl -fsS https://packages.healthchat.ch/desktop/update/macos/releases.json` - [ ] Smoke-check signing before shipping to users: macOS `spctl -a -vvv -t install ` diff --git a/.github/workflows/build-healthchat-desktop.yaml b/.github/workflows/build-healthchat-desktop.yaml index 36a479ddf8..b1e92ef4ce 100644 --- a/.github/workflows/build-healthchat-desktop.yaml +++ b/.github/workflows/build-healthchat-desktop.yaml @@ -10,10 +10,11 @@ name: Build HealthChat Desktop # (via `webapp-artifact`) and the per-platform build_desktop_{macos,windows}.yaml reusables. # # Notes: -# - This workflow RELEASES but does not DEPLOY. It attaches the installers and a staged update-feed -# tarball to the GitHub Release; the live auto-update feed at packages.healthchat.ch/desktop/ is -# only touched by the manually dispatched deploy-healthchat-desktop.yaml. So cutting a tag never -# pushes an update to already-installed apps until a human decides to ship it. +# - This workflow RELEASES but does not DEPLOY. It attaches a staged update-feed tarball (split +# into <2 GiB parts) to the GitHub Release and links the public installer URLs in the Release +# notes; the live auto-update feed at packages.healthchat.ch/desktop/ is only touched by the +# manually dispatched deploy-healthchat-desktop.yaml. So cutting a tag never pushes an update +# to already-installed apps until a human decides to ship it. # - The reusable build workflows are pointed at THIS fork (repository/ref inputs) so the Electron # shell, icons (apps/desktop/build/icon.*) and the healthchat:// deep-link handler # (apps/desktop/src/protocol.ts) are all fork-branded. Branding values come from @@ -246,19 +247,12 @@ jobs: with: path: dist-artifacts - - name: Collect installers - run: | - set -euo pipefail - mkdir -p release-files - # macOS: .dmg installer + .zip (Squirrel.Mac update payload) - find dist-artifacts -type f \( -iname '*.dmg' -o -iname '*-mac.zip' \) -exec cp -v {} release-files/ \; - # Windows: per-machine MSI + Squirrel "Setup" installer - find dist-artifacts -type f \( -iname '*.msi' -o -iname '* Setup*.exe' \) -exec cp -v {} release-files/ \; - echo "--- release files ---" - ls -la release-files - test -n "$(ls -A release-files)" || { echo "::error::No installer files collected"; exit 1; } - - - name: Upload to GitHub Release + # Installers are NOT attached to the Release: they ship inside the feed tarball and are + # served from packages.healthchat.ch after the deploy — the Release body links to them + # there (see "Link public downloads in the Release notes" below). Attaching them here + # would duplicate ~1 GiB per release and the x64/arm64 Setup.exe share a filename, so + # they'd clobber each other as flat Release assets anyway. + - name: Ensure GitHub Release exists env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} @@ -266,11 +260,10 @@ jobs: run: | set -euo pipefail # The web workflow (build-healthchat.yaml) also creates this release for the same tag. - # Create-if-missing (without touching existing notes) then upload --clobber so the two - # workflows race safely regardless of ordering. + # Create-if-missing (without touching existing notes) so the two workflows race + # safely regardless of ordering. gh release view "$TAG" >/dev/null 2>&1 || \ - gh release create "$TAG" --title "$TAG" --notes "HealthChat desktop installers for $TAG" - gh release upload "$TAG" release-files/* --clobber + gh release create "$TAG" --title "$TAG" --notes "HealthChat $TAG" # The GitHub Release above is private (internal fork), so it can't serve end users or the # Squirrel auto-updater. Those are served from packages.healthchat.ch/desktop/, which is @@ -356,3 +349,38 @@ jobs: ls -lh "${TARBALL}".part* gh release upload "$TAG" "${TARBALL}".part* --clobber echo "::notice::Update feed attached as ${TARBALL}.part*. The live feed at packages.healthchat.ch is UNCHANGED — run the 'Deploy HealthChat Desktop' workflow to publish it." + + # The Release carries no installer binaries (see "Ensure GitHub Release exists"), so + # point readers at the public download URLs instead. The links are derived from the + # staged feed/install tree, so they stay correct if electron-builder ever renames the + # artifacts. They resolve once "Deploy HealthChat Desktop" has published this version. + - name: Link public downloads in the Release notes + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + BASE="https://packages.healthchat.ch/desktop" + BODY=$(gh release view "$TAG" --json body --jq .body) + if [[ "$BODY" == *"## Downloads"* ]]; then + echo "Release notes already carry a Downloads section — leaving them untouched." + exit 0 + fi + { + printf '%s\n\n' "$BODY" + echo "## Downloads" + echo "" + echo "Public installers, live once this version is deployed via \"Deploy HealthChat Desktop\":" + echo "" + for f in feed/install/macos/* feed/install/win32/x64/* feed/install/win32/arm64/*; do + name=$(basename "$f") + # Skip the stable "latest" aliases — they track whatever is deployed, not this version. + case "$name" in HealthChat.dmg|HealthChat-Setup.exe) continue ;; esac + dir=$(dirname "${f#feed/}") + enc=$(jq -rn --arg s "$name" '$s|@uri') + echo "- [$name]($BASE/$dir/$enc)" + done + } > release-body.md + gh release edit "$TAG" --notes-file release-body.md + echo "::notice::Release notes now link the public installers under $BASE/install/." diff --git a/apps/desktop/element.io/healthchat/README b/apps/desktop/element.io/healthchat/README index df335ee4ee..505eb4aa44 100644 --- a/apps/desktop/element.io/healthchat/README +++ b/apps/desktop/element.io/healthchat/README @@ -49,7 +49,9 @@ feeds it into build_desktop_prepare.yaml (`config: element.io/healthchat`, reusables. It runs on every `healthchat-v*` tag (alongside the web build) and via workflow_dispatch. The reusables are pointed at THIS fork (`repository`/`ref` inputs) so the Electron shell, icons and the healthchat:// deep-link handler are fork-branded. On a tag push the -`release` job attaches the .dmg / .zip / .exe / .msi to the GitHub Release. +`release` job attaches the staged update feed as split healthchat-desktop-feed-.tar.gz.part* +assets (the compressed feed exceeds GitHub's 2 GiB per-asset cap) and links the public installer +URLs on packages.healthchat.ch in the Release notes — installer binaries are not attached. (Linux is not built: the upstream build_desktop_linux.yaml needs a build-env Docker image keyed by commit SHA that only exists in the upstream element-hq registry, so it can't run here.)