The Electron auto-updater (Squirrel.Mac / Squirrel.Windows) was already implemented but switched off for HealthChat because the variant config omitted update_base_url, and CI published only installers (no update feed), to a private GitHub Release that can't serve end users or the updater. - config.json: set update_base_url to https://healthchat.ch/packages/desktop/update/ so electron-main starts the updater. - build-healthchat-desktop.yaml: pass base-url to the macOS job so releases.json is generated; extend the release job to stage the update feed + public installers and mirror them to healthchat.ch over FTPS (lftp, no-delete to preserve prior nupkgs; password via LFTP_PASSWORD). - Document the feed/install layout, FTP secrets, and the two caveats (first install is manual; Windows auto-update needs the Setup.exe path, not .msi) in the healthchat README and the releasing-healthchat skill. Requires repo secrets FTP_SERVER / FTP_USERNAME / FTP_PASSWORD.
11 KiB
name, description
| name | description |
|---|---|
| releasing-healthchat | Cut a versioned HealthChat release of element-web by tagging release/healthchat. Use when the user asks to release, publish, tag, or cut a new HealthChat version, or asks about the release/deployment process for HealthChat element-web. |
Releasing HealthChat (element-web)
Releases are driven by git tags of the form healthchat-v<semver> pushed to
origin. The tag triggers two workflows in parallel:
.github/workflows/build-healthchat.yaml(web) — builds the HealthChat Docker image, publishes it to ghcr.io underhealthchat-web:<version>,:stable, and:<sha>, and creates a GitHub Release whose body is the matching section ofHEALTHCHAT_CHANGES.md..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.) Thereleasejob additionally mirrors the Squirrel update feed and the public installers tohttps://healthchat.ch/packages/desktop/over FTPS (the private GitHub Release can serve neither end users nor the auto-updater). This is what makes installed apps self-update; seeapps/desktop/element.io/healthchat/READMEfor the feed layout and 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.
The desktop app version is taken from apps/desktop/package.json (the per-job
version input cannot set it — Windows squirrel mangles it into a nightly string,
and on macOS it only feeds the update manifest). So apps/desktop/package.json
must be bumped to the release version in the release commit to keep the About
dialog and installer filenames matching the tag. This is no longer optional: the
desktop workflow's "Assert desktop app version matches release" step fails the build
if apps/desktop/package.json drifts from the tag's MAJOR.MINOR.PATCH (any
-rc1/-hotfix1 suffix on the tag is ignored — squirrel needs a clean numeric
version, so set 1.4.0 even when tagging healthchat-v1.4.0-rc1).
Preflight checklist
Verify each item before tagging. If any fail, fix and commit first.
- On branch
release/healthchatwith a clean working tree (git status). HEALTHCHAT_CHANGES.mdhas a section## Changes in <new-version> (<date>)at the top of the file. The pipeline's awk extractor matches this exact heading format.apps/desktop/package.jsonversionis set to the release'sMAJOR.MINOR.PATCH(no suffix). Required — the desktop build asserts this and fails otherwise. This is what the desktop apps report as their version..healthchat-workspace-versionpoints at the healthchat-workspace tag or ref that should ship with this release. Ask the user whether to bump it to the latest plugin release or keep the currently pinned ref (see "Choosing the plugin version" below). Commit any bump in the same commit as the changelog entry.- Root
package.jsonversion can optionally be bumped to match (cosmetic — the web bundle version is injected from the tag at build time, and the git tag is the source of truth for the build). - No uncommitted changes to
.github/workflows/build-healthchat.yaml,Dockerfile, ordocker/that haven't been manually sanity-checked.
Choosing the plugin version
The HealthChat plugin (healthchat-workspace) ref that ships with the release is
pinned in .healthchat-workspace-version (a single line, e.g. v0.5.0-rc.4).
Before tagging, always ask the user which they want:
-
Update to the latest plugin release — check the available releases:
gh release list --repo Novaloop-AG/healthchat-workspace --limit 10If they choose a newer ref, write it to
.healthchat-workspace-versionand commit it alongside the changelog entry. -
Keep the currently pinned ref — leave
.healthchat-workspace-versionuntouched (show them the current value so the choice is informed).
Do not silently change the plugin ref; the user decides per release. The
workspace_ref workflow_dispatch input can override it for a one-off build
without committing, but the committed file is the source of truth for tag builds.
Cutting the release (normal path)
# from release/healthchat, fully committed:
git tag healthchat-v<MAJOR>.<MINOR>.<PATCH>
git push origin healthchat-v<MAJOR>.<MINOR>.<PATCH>
Examples:
git tag healthchat-v1.0.0git tag healthchat-v1.4.0-rc1git tag healthchat-v1.4.0-hotfix1
The workflow triggers automatically. Watch it via gh run watch or GitHub
Actions UI.
Manual / retry path (workflow_dispatch)
Use when retrying a failed run, testing the pipeline, or rebuilding an image without moving tags.
- GitHub → Actions → "Release HealthChat" → Run workflow.
- Fill in:
- version (required): e.g.
1.4.0. Same regex validation as tag path. - workspace_ref (optional): overrides
.healthchat-workspace-version. - release_notes_override (optional): replaces the
HEALTHCHAT_CHANGES.mdlookup.
- version (required): e.g.
- Run. Docker image is pushed. No GitHub Release is created on the dispatch path — only the tag-push path creates Releases (keeps manual retries from clobbering the release history).
The desktop workflow ("Build HealthChat Desktop") has its own workflow_dispatch
with the same inputs; like the web path, its release job only publishes on a
real tag push, so a manual dispatch builds and uploads run artifacts but does not
attach anything to a Release.
Tag format
healthchat-v<MAJOR>.<MINOR>.<PATCH>[-<suffix>]
Suffix conventions (informational; regex accepts any
[0-9A-Za-z.-]+):
-rc1,-rc2— release candidates-hotfix1— post-release fixes on an existing minor-dryrun1,-renew-test— one-off pipeline tests
The healthchat- prefix keeps these tags distinct from upstream
element-hq v<semver> tags if they are ever fetched into the repo.
Regex used for validation (must match):
^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$
Release notes
HEALTHCHAT_CHANGES.md is the single source. The workflow extracts the
section matching the version being released. Format:
## Changes in 1.4.0 (2026-04-21)
Short summary line (optional).
🙌 Improvements
- Bullet one.
- Bullet two.
If no matching section exists, the pipeline falls back to
HealthChat <version> — automated build from <ref>. and emits a workflow
warning. Prefer fixing the changelog over relying on the fallback.
After the release
- Workflow run is green in GitHub Actions.
docker pull ghcr.io/<owner>/healthchat-web:<version>succeeds.- GitHub Release exists at
https://github.com/<owner>/<repo>/releases/tag/healthchat-v<version>with the correct body. - The
:stabletag on ghcr.io now points at the new digest (docker buildx imagetools inspect ghcr.io/<owner>/healthchat-web:stable). - The desktop workflow run ("Build HealthChat Desktop") is green and the GitHub Release has the desktop installers attached (.dmg, -mac.zip, .msi, " Setup.exe").
- The update feed + installers published to healthchat.ch:
curl -fsIhttps://healthchat.ch/packages/desktop/update/macos/releases.json,.../update/win32/x64/RELEASES, and the public installer URLs.../install/macos/HealthChat.dmg/.../install/win32/x64/HealthChat-Setup.exeall return 200. - Smoke-check signing: macOS
spctl -a -vvv -t install <app>is accepted; Windows installer shows a valid publisher (no SmartScreen block).
Required secrets
Configured in repo Settings → Secrets and variables → Actions:
| Secret | Purpose |
|---|---|
HEALTHCHAT_WORKSPACE_TOKEN |
PAT with read access to Novaloop-AG/healthchat-workspace. Needed by actions/checkout for the private plugin repo. |
GITHUB_TOKEN |
Auto-provided. Used for ghcr.io login and GitHub Release creation. No manual setup required. |
FTP_SERVER / FTP_USERNAME / FTP_PASSWORD |
Credentials for the healthchat.ch FTP(S) account. The desktop release job uses them to publish the update feed + public installers under /public_html/healthchat.ch/packages/desktop/. FTP_SERVER is the hostname only (explicit FTPS / AUTH TLS). Without them the FTP publish step fails (but the GitHub Release still gets the installers). |
Desktop signing additionally requires two GitHub environments (Settings → Environments). Without them the desktop builds still run but produce unsigned/untrusted output:
| Environment | Secrets / variables |
|---|---|
Desktop Apple |
secrets APPLE_ID, APPLE_ID_PASSWORD (app-specific), APPLE_CSC_LINK (base64 .p12), APPLE_CSC_KEY_PASSWORD; variable APPLE_TEAM_ID |
Windows code-signing reuses the org-level SSL.com eSigner secrets shared with the
other Novaloop apps (ESIGNER_USERNAME, ESIGNER_PASSWORD, ESIGNER_TOTP_SECRET);
the windows job in build-healthchat-desktop.yaml maps them to the CKA names the
reusable expects (no credential_id needed). Set variable ESIGNER_MODE=prod for a
trusted signature. These org secrets are scoped to private/internal repos, so the
fork must stay internal (not public). Add branch/environment protection so only
release/healthchat + healthchat-v* runs can reach the signing credentials.
Rollback
- Bad image, notes fine → fix the cause, commit, tag
healthchat-v<ver>-hotfix1. - Bad release notes → edit
HEALTHCHAT_CHANGES.mdand the GitHub Release body manually (GitHub UI). The ghcr.io tag is immutable, don't try to move it. - Completely wrong tag → delete the GitHub Release (UI). Do not delete the ghcr.io image tag — treat it as burned. Re-tag with a new version.
Known branch divergence on rebase
These files are HealthChat-only and will conflict cleanly when rebasing
release/healthchat onto upstream. Always keep the HealthChat side (--ours
during rebase):
.github/workflows/build-healthchat.yaml.github/workflows/build-healthchat-desktop.yamlHEALTHCHAT_CHANGES.md.healthchat-workspace-version.claude/skills/releasing-healthchat/SKILL.mdapps/desktop/build/icon.png,apps/desktop/build/icon.ico,apps/desktop/build/icon.icon/(HealthChat icons)
The four shared desktop reusables carry small HealthChat additions (a repository
input + product-name-agnostic asserts). These should merge cleanly, but if they
conflict on rebase, re-apply the HealthChat changes rather than blindly taking
either side:
.github/workflows/build_desktop_prepare.yaml.github/workflows/build_desktop_linux.yaml(still in the tree, but no longer invoked bybuild-healthchat-desktop.yaml— the fork doesn't build Linux).github/workflows/build_desktop_macos.yaml.github/workflows/build_desktop_windows.yaml