ci: add HealthChat desktop build pipeline

This commit is contained in:
2026-06-06 15:13:59 +02:00
parent 2fb8a2c92c
commit 5f09f58a82
5 changed files with 364 additions and 0 deletions
@@ -0,0 +1,140 @@
name: Build HealthChat Desktop
# Builds the HealthChat desktop (Electron) app: the fork's element-web webapp with
# the HealthChat plugin (healthchat-module.js + healthchat-elements.js) injected,
# packaged via the existing element-desktop build pipeline.
#
# This mirrors build-healthchat.yaml (web). It first builds the element-plugin from
# healthchat-workspace, builds the fork webapp with the plugin injected, then feeds it
# into build_desktop_prepare.yaml via its `webapp-artifact` input and packages it with
# build_desktop_linux.yaml.
#
# IMPORTANT CAVEATS (see apps/desktop/element.io/healthchat/README):
# - build_desktop_prepare.yaml / build_desktop_linux.yaml currently `actions/checkout`
# `element-hq/element-web` (upstream). The HealthChat webapp + branding come from the
# uploaded artifact + variant.json, but the Electron shell (electron-main, icons) is
# upstream Element's. To ship a fully fork-branded shell, point those reusable
# workflows at this fork (change their `repository:` / add a `ref` input).
# - Plugin loading depends on element-main.ts rewriting `/modules/*` -> `/webapp/modules/*`.
# Verify that rewrite exists in whichever desktop checkout is used.
# - macOS/Windows builds need Element's signing/notarization secrets and are intentionally
# omitted here; add build_desktop_macos.yaml / build_desktop_windows.yaml once those are
# available.
# - Confirm `ch.healthchat.api_base_url` in apps/desktop/element.io/healthchat/config.json
# points at the real production API origin (assumed https://healthchat.ch).
on:
workflow_dispatch:
inputs:
version:
description: "Version (e.g. 1.4.0)."
required: true
type: string
workspace_ref:
description: "healthchat-workspace ref (defaults to .healthchat-workspace-version)."
required: false
type: string
permissions: {}
concurrency:
group: build-healthchat-desktop-${{ github.event.inputs.version }}
cancel-in-progress: false
jobs:
webapp:
name: Build webapp + plugin
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Resolve workspace ref
id: workspace
env:
OVERRIDE: ${{ github.event.inputs.workspace_ref }}
run: |
set -euo pipefail
if [[ -n "$OVERRIDE" ]]; then
REF="$OVERRIDE"
elif [[ -f .healthchat-workspace-version ]]; then
REF=$(head -n1 .healthchat-workspace-version | tr -d '[:space:]')
[[ -n "$REF" ]] || { echo "::error::.healthchat-workspace-version is empty"; exit 1; }
else
echo "::error::.healthchat-workspace-version missing and no workspace_ref override provided"
exit 1
fi
echo "ref=$REF" >> "$GITHUB_OUTPUT"
- name: Checkout healthchat-workspace
uses: actions/checkout@v4
with:
repository: Novaloop-AG/healthchat-workspace
path: healthchat-workspace
token: ${{ secrets.HEALTHCHAT_WORKSPACE_TOKEN }}
ref: ${{ steps.workspace.outputs.ref }}
persist-credentials: false
- uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install monorepo deps
run: pnpm install --frozen-lockfile
- name: Build element-plugin
run: |
cd healthchat-workspace/element-plugin
npm ci
npm run build
cd module
npm ci
npm run build
- name: Stage plugin artifacts
run: |
mkdir -p plugin-dist/elements
cp healthchat-workspace/element-plugin/module/dist/healthchat-module.js plugin-dist/healthchat-module.js
cp healthchat-workspace/element-plugin/dist/elements/browser/main.js plugin-dist/elements/healthchat-elements.js
- name: Build webapp with plugin injected
env:
VERSION: ${{ github.event.inputs.version }}
run: ./apps/desktop/scripts/build-healthchat-webapp.sh
- uses: actions/upload-artifact@v4
with:
name: healthchat-webapp
retention-days: 1
path: apps/desktop/webapp
prepare:
name: Prepare desktop asar
needs: webapp
permissions:
contents: read
uses: ./.github/workflows/build_desktop_prepare.yaml
with:
config: element.io/healthchat
webapp-artifact: healthchat-webapp
nightly: false
deploy: false
linux:
name: Linux ${{ matrix.arch }}
needs: prepare
strategy:
matrix:
arch: [amd64, arm64]
uses: ./.github/workflows/build_desktop_linux.yaml
with:
arch: ${{ matrix.arch }}
sqlcipher: static
version: ${{ github.event.inputs.version }}
test: false
+62
View File
@@ -0,0 +1,62 @@
HealthChat desktop variant
===========================
This directory is the `--cfgdir` / build config for HealthChat desktop builds:
config.json Element Web runtime config baked into webapp.asar. Mirrors
apps/web/config.json (HealthChat homeserver, theme, branding) and
loads the HealthChat plugin:
- "modules": ["/modules/healthchat-module.js"]
- "ch.healthchat.elements_base_url": "/webapp/modules/elements"
build.json electron-builder variant (appId, productName "HealthChat", protocols).
Why the paths differ from web
-----------------------------
The desktop app serves the webapp from `vector://vector/webapp/`, so root-relative
paths need a `/webapp` prefix:
- The `modules` array is rewritten automatically by apps/desktop/src/electron-main.ts
(`/modules/x` -> `/webapp/modules/x`), so it stays as `/modules/healthchat-module.js`.
- `ch.healthchat.elements_base_url` is NOT auto-rewritten (the elements bundle is loaded
by a <script> tag the plugin injects, not by the module loader), so it is set explicitly
to `/webapp/modules/elements` here. On web it is `/modules/elements`.
The plugin artifacts must live inside the asar at `modules/healthchat-module.js` and
`modules/elements/healthchat-elements.js`.
Building locally
----------------
# 1. Build the element-plugin (from the healthchat-workspace checkout) and stage it:
(cd ../healthchat-workspace/element-plugin && npm ci && npm run build)
(cd ../healthchat-workspace/element-plugin/module && npm ci && npm run build)
mkdir -p plugin-dist/elements
cp ../healthchat-workspace/element-plugin/module/dist/healthchat-module.js plugin-dist/healthchat-module.js
cp ../healthchat-workspace/element-plugin/dist/elements/browser/main.js plugin-dist/elements/healthchat-elements.js
# 2. Build the fork webapp with the plugin injected:
./apps/desktop/scripts/build-healthchat-webapp.sh
# 3. Pack and run:
cp apps/desktop/element.io/healthchat/config.json apps/desktop/webapp/config.json
pnpm --dir apps/desktop run asar-webapp
pnpm --dir apps/desktop start
CI
--
.github/workflows/build-healthchat-desktop.yaml builds the plugin + webapp artifact and
feeds it into build_desktop_prepare.yaml (`config: element.io/healthchat`,
`webapp-artifact: healthchat-webapp`) and build_desktop_linux.yaml.
Open items before a production release
--------------------------------------
- api_base_url: confirm the production API origin in config.json (currently assumed
https://healthchat.ch). On desktop this MUST be absolute (vector:// origin can't reach
a relative /api).
- Fork vs upstream shell: build_desktop_prepare.yaml / build_desktop_linux.yaml check out
element-hq/element-web (upstream). The webapp + variant are HealthChat's, but the Electron
shell (electron-main, icons) is upstream. Point those workflows at this fork to fully brand
the shell, and to guarantee the `/modules` -> `/webapp/modules` rewrite is present.
- macOS/Windows: need signing/notarization secrets; add the macos/windows reusable workflows
once configured.
- Deploy target: the upstream deploy job pushes to packages.element.io / Cloudflare R2 — wire
up a HealthChat distribution target instead.
@@ -0,0 +1,7 @@
{
"appId": "ch.healthchat.desktop",
"name": "healthchat-desktop",
"productName": "HealthChat",
"description": "HealthChat: secure communication for healthcare",
"protocols": ["ch.healthchat.desktop", "healthchat"]
}
@@ -0,0 +1,104 @@
{
"brand": "HealthChat",
"branding": {
"auth_footer_links": null,
"auth_header_logo_url": "themes/element/img/logos/element-logo.png",
"welcome_background_url": null
},
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"default_country_code": "GB",
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.healthchat.ch",
"server_name": "healthchat.ch"
},
"m.identity_server": {
"base_url": ""
}
},
"default_theme": "custom-HealthChat Light",
"disable_custom_urls": true,
"disable_guests": true,
"element_call": {},
"embedded_pages": {
"homeUrl": ""
},
"enable_presence_by_hs_url": "",
"features": {},
"integrations_jitsi_widget_url": "https://scalar.vector.im/api/widgets/jitsi.html",
"integrations_rest_url": "https://scalar.vector.im/api",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_widgets_urls": ["https://scalar.vector.im/api"],
"jitsi": {
"preferred_domain": "jitsi.healthchat.ch"
},
"logout_redirect_url": "",
"permalink_prefix": "https://matrix.to",
"room_directory": {
"servers": ["matrix.org"]
},
"sso_redirect_options": {
"immediate": true,
"on_welcome_page": true,
"on_login_page": true
},
"setting_defaults": {
"UIFeature.deactivate": false,
"UIFeature.advancedEncryption": false,
"UIFeature.thirdPartyId": false,
"UIFeature.identityServer": false,
"UIFeature.shareSocial": false,
"UIFeature.advancedSettings": false,
"UIFeature.feedback": false,
"custom_themes": [
"HealthChat Light",
{
"name": "HealthChat Light",
"is_dark": false,
"colors": {
"accent-color": "#1f4d85",
"primary-color": "#1f4d85",
"alert": "#df8e1d",
"warning-color": "#d20f39",
"sidebar-color": "#12325c",
"roomlist-background-color": "#f5f5f5",
"roomlist-highlights-color": "#d9d9d9",
"roomlist-separator-color": "#9b9898",
"roomlist-text-color": "#473e3e",
"roomlist-text-secondary-color": "#9b9898",
"timeline-background-color": "#ffffff",
"timeline-highlights-color": "#d9d9d9",
"timeline-text-color": "#473e3e",
"timeline-text-secondary-color": "#9b9898",
"secondary-content": "#9b9898",
"tertiary-content": "#9b9898",
"focus-bg-color": "#d9d9d9",
"menu-selected-color": "#d9d9d9",
"other-user-pill-bg-color": "#376A91",
"room-highlight-color": "#376A91",
"reaction-row-button-selected-bg-color": "#d9d9d9",
"togglesw-off-color": "#9b9898",
"avatar-background-colors": ["#12325c", "#1f4d85", "#376A91"],
"username-colors": ["#12325c", "#2a4063", "#1f4d85", "#376A91", "#473e3e", "#1f4d85", "#2a4063", "#12325c"]
},
"fonts": {
"general": "\"Century Gothic\", \"Futura\", \"Trebuchet MS\", \"Arial\", sans-serif"
},
"compound": {
"--cpd-color-text-action-accent": "#1f4d85",
"--cpd-color-bg-action-primary-rest": "#1f4d85",
"--cpd-color-bg-action-primary-hovered": "#2a4063",
"--cpd-color-bg-action-primary-pressed": "#12325c",
"--cpd-color-text-link-external": "#1f4d85",
"--cpd-color-icon-accent-tertiary": "#376A91"
}
}
]
},
"show_labs_settings": false,
"modules": ["/modules/healthchat-module.js"],
"ch.healthchat": {
"api_base_url": "https://healthchat.ch",
"elements_base_url": "/webapp/modules/elements"
}
}
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
#
# Build the HealthChat desktop webapp directory: the fork's element-web build with
# the HealthChat plugin artifacts injected at modules/. The result is written to
# apps/desktop/webapp so it can either be:
# - packed locally with `pnpm --dir apps/desktop run asar-webapp` (after copying
# element.io/healthchat/config.json over webapp/config.json), or
# - uploaded as the `webapp-artifact` consumed by build_desktop_prepare.yaml
# (which overlays element.io/healthchat/config.json and packs the asar for you).
#
# Plugin artifacts are taken from $PLUGIN_DIST (default: ./plugin-dist at repo root),
# expecting:
# $PLUGIN_DIST/healthchat-module.js
# $PLUGIN_DIST/elements/healthchat-elements.js
# Build them first from the healthchat-workspace element-plugin:
# (cd element-plugin && npm ci && npm run build)
# (cd element-plugin/module && npm ci && npm run build)
# then stage main.js -> healthchat-elements.js (see build-healthchat-desktop.yaml).
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
cd "$REPO_ROOT"
PLUGIN_DIST="${PLUGIN_DIST:-plugin-dist}"
WEBAPP_SRC="apps/web/webapp"
WEBAPP_DEST="apps/desktop/webapp"
if [ ! -f "$PLUGIN_DIST/healthchat-module.js" ] || [ ! -f "$PLUGIN_DIST/elements/healthchat-elements.js" ]; then
echo "error: missing plugin artifacts under '$PLUGIN_DIST'." >&2
echo " expected:" >&2
echo " $PLUGIN_DIST/healthchat-module.js" >&2
echo " $PLUGIN_DIST/elements/healthchat-elements.js" >&2
exit 1
fi
echo "Building fork element-web webapp..."
VERSION="${VERSION:-$(git describe --abbrev=0 --tags 2>/dev/null || echo 0.0.0)}"
VERSION="$VERSION" pnpm --dir apps/web build
echo "Injecting HealthChat plugin into $WEBAPP_DEST/modules ..."
rm -rf "$WEBAPP_DEST"
cp -R "$WEBAPP_SRC" "$WEBAPP_DEST"
mkdir -p "$WEBAPP_DEST/modules/elements"
cp "$PLUGIN_DIST/healthchat-module.js" "$WEBAPP_DEST/modules/healthchat-module.js"
cp "$PLUGIN_DIST/elements/healthchat-elements.js" "$WEBAPP_DEST/modules/elements/healthchat-elements.js"
echo "Done -> $WEBAPP_DEST"
echo
echo "To produce webapp.asar locally:"
echo " cp apps/desktop/element.io/healthchat/config.json $WEBAPP_DEST/config.json"
echo " pnpm --dir apps/desktop run asar-webapp"