Author SHA1 Message Date
Thore Cimbal fa5dcc5fbc docs(fork): the import is not the v1.12.17 tag, and that changes the graft plan
Adding upstream as a second remote was meant to make updates viewable as a diff. The
first measurement contradicted what this document has claimed since August: diffing
v1.12.17 against the import commit yields 783 files, 115 of which exist only on our
side and 62 only in the tag.

The telling ones are MVVM files - TextualBodyViewModel.tsx and RovingTabIndex.tsx -
absent from the v1.12.17 tag, present in v1.12.26 and present in our import. The
changelog stops at 1.12.17 because it is only written at release time. So the base is
a develop state after the 1.12.17 release, not the tag.

That matters for the plan rather than for the code: grafting onto v1.12.17 would build
every future merge on an invented ancestry. Finding the real ancestor means searching
develop history, which a shallow tag fetch cannot do.

The remote is local configuration, so the setup is written down here instead of
committed.
2026-08-19 12:00:00 +00:00
Thore Cimbal e1e9a19f78 config(desktop): disable_custom_urls here too, not only in the web deployment
Decision sorb. The desktop client loads this config.json rather than the one the web
deployment serves, so setting it in element-values.yaml alone would have hardened the
web client and left the builds that actually get handed out untouched - the same trap
as the themes rollout.

It hides the edit button next to the server name, so the homeserver cannot be changed
through the interface, and it makes the 401/403 login error name the server instead of
staying generic.

Stated in the comment rather than discovered later: this is a surface restriction, not
a technical one. MatrixChat still accepts hs_url from the query string in the mobile
registration and registration-confirmation paths without consulting the setting. It
narrows the way to GHSA-wrcp-5v3v-3j6v, which affects everything below 1.12.22 while
we run 1.12.17, and it does not replace the upstream update that management #0099 is
actually about.

Takes effect with the next desktop build.
2026-08-19 12:00:00 +00:00
Thore CimbalandClaude Opus 5 240021b754 ci: build when .npmrc changes
The previous commit added no pipeline run: .npmrc is not in the changes list that
triggers one. That file decides which registry dependencies come from, so a change
to it deserves a build rather than a wave-through. This commit touches
.gitlab-ci.yml, which is on the list - so it verifies itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 be323edf10 build: pin the @sorb scope to rohana instead of relying on the lockfile
Nothing in this repo said where @sorb/threadnet-call-embedded comes from. It
worked only because pnpm-lock.yaml carries the full tarball URL and CI installs
with --frozen-lockfile; the first version bump drops that cover. Today that ends
in a 404 because the name is still free on npmjs - which makes our only
protection the fact that nobody has claimed @sorb there. Claim it and the same
command resolves silently against a stranger's package, in the exact moment a
fresh download looks normal.

The checked-in .npmrc closes the wrong path instead of documenting it. It holds a
URL and no secret: read access to the registry is anonymous.

Upstream's .gitignore ignores /.npmrc - reasonable where the file holds tokens,
and it would have swallowed this fix without a word. The negation is explicit and
carries the reason, so the next merge does not quietly restore the hole.

Verified in an isolated tree, not assumed: without the file pnpm resolves to
npmjs and fails; with it the scope resolves to rohana at the same integrity hash
the repo lockfile already carries; with rohana unreachable the install fails
outright rather than falling back to npmjs. pnpm install --frozen-lockfile still
passes and leaves the lockfile untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 6cdb1858a2 build(web): pull in threadnet-call-embedded 0.19.2-threadnet.12
Sender verification for the AI filter: after attaching, the fork now checks
that the RTCRtpSender actually carries the processed track and enforces the
swap if LiveKit skipped it silently - the Safari field finding where level
0-100 made no audible difference. The log line states the outcome.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 7e6b08774b build(web): pull in threadnet-call-embedded 0.19.2-threadnet.11
Opens the AI noise suppression gate after the passed two-person acceptance:
checkbox and slider return to the in-call audio settings, clients with the
setting enabled get the filter again. The rollback lever for any regression is
the gate in threadnet-call, not a deployment revert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 51b6691caa build(web): pull in threadnet-call-embedded 0.19.2-threadnet.10
Way B from #0054: the AI filter attaches to the local microphone track after
publication, with its own AudioContext on just that track - no webAudioMix, no
processor key in the capture defaults in any state. The feature gate stays
closed; a single test client opts in via two localStorage keys. For everyone
else this build behaves identically to .9.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 45dc53155b build(web): pull in threadnet-call-embedded 0.19.2-threadnet.9
The .8 widget broke unmuting in production (both with the AI filter on and off);
threadnet-call dcc8643 restores the upstream off-path and hard-gates the feature
until the webAudioMix decision. Verified locally end to end: the installed
package carries the model assets, webpack lands them under
webapp/widgets/element-call/assets/dfn3, and the widget index.html references
the .9 bundle (0.19.2-threadnet.9+dcc86431).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 7abe9023f9 build(web): pull in threadnet-call-embedded 0.19.2-threadnet.8
Brings the AI noise suppression (ADR-0018) into the widget we actually ship.
The .7 package built and ran fine but carried no model assets, because the
embedded Vite config had publicDir off — the filter would have been dead only
inside the widget, and nowhere else.

Verified the whole chain locally rather than trusting the green build: the
23 MB of model and wasm arrive under webapp/widgets/element-call/assets/dfn3,
which is the path the widget requests at runtime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 e7b00288ed docs: add group-rules pointer (CLAUDE.md) and repo-specific AGENTS.md
Field test F-011 found four of five components carried no pointer file, so a
session landing here had no path to the group rules at all. CLAUDE.md is the
one-line pointer the check looks for; AGENTS.md links the canonical rules in the
management repo (with the Gitea mirror URL for readers outside the lab) and
otherwise carries only what is specific and easy to get wrong here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore Cimbal d3bd5b20c2 feat(desktop): give the desktop client a way to obtain its logs
The desktop variant had no bug_report_endpoint_url at all, so the whole section stayed hidden and a desktop user could not produce logs for a report. Setting local shows a Download logs button that writes them to the device and uploads nothing.

Matches what the web client now serves. Takes effect with the next desktop build.

Refs axion1337.chat/ThreadNet-Web#9
2026-08-09 12:00:00 +00:00
Thore Cimbal 3cb43f5149 ci: refuse to overwrite an already published release image
Moving a tag retriggers docker_web, which would rebuild a published release from old source against today's base images and dependencies and push it over the existing one. On 2026-08-09 that nearly happened to v0.4.0 while the rewritten tags were force-pushed; it was averted only because the protected registry variables were unavailable in that window, so docker login failed first.

The job now aborts if the target tag already exists in the registry. latest-ci stays overwritable on purpose - it names the newest state, not a version, and carries no promise.

Closes axion1337.chat/ThreadNet-Web#14
2026-08-09 12:00:00 +00:00
Thore Cimbal 26b6fd8938 ci: do not create a pipeline for docs-only commits
A commit touching only docs/ produced a pipeline with zero jobs, which GitLab marks as failed - pipelines 203 and 204 on 2026-08-06 were both red for nothing. Cause is the needs chain: web is skipped by its changes rule, and desktop_linux/desktop_windows depend on it, which collapses the whole pipeline including the manual jobs.

This matters beyond cosmetics: gitops/CLAUDE.md makes the red pipeline the alarm for the TURN rotation, stating there is no separate reminder. Red that means nothing trains people to stop looking.

The path list is a YAML anchor shared with the web job - two copies would drift. CI_PIPELINE_SOURCE == web stays allowed so the manual maintenance jobs remain reachable via Run pipeline.
2026-08-07 12:00:00 +00:00
Thore Cimbal 965c456dee Doku: Merge-Risiko praeziser fassen - Art des Eingriffs, nicht Umfang
Meine Aussage "ClamAV kostet mehr als das gesamte Branding zusammen" war so nicht haltbar: gemessen sind es ~130 zu ~104 Zeilen, der groesste Einzelpatch ist sogar Branding (HelpUserSettingsTab.tsx, 71 Zeilen).

Der Unterschied liegt woanders: Branding-Patches stehen am Rand und sind nach einem Umbau wiederfindbar, ClamAV-Patches stehen mitten in Entschluesselungs- und Fehlerpfaden. Dazu der eigentliche Haken - verschiebt Upstream die viewmodels/-Dateien, entsteht kein Konflikt, die Zeilen sind schlicht weg und Git meldet nichts. Deshalb ist dort ein Funktionstest maszgeblich, nicht die Merge-Ausgabe.

Refs axion1337.chat/ThreadNet-Web#7
2026-08-06 12:00:00 +00:00
Thore Cimbal 9ac56d671b Doku: Merge-Reibung gemessen (Arbeitspaket 3 aus #7)
Der wichtigste Befund ist die Grundlage, nicht die Liste: das Repo enthaelt KEINE Upstream-Historie. Element Web 1.12.17 kam am 2026-05-10 als kompletter Baum herein, im selben Commit wie das erste eigene Feature. Es gibt also keinen gemeinsamen Vorfahren mit element-hq/element-web - ein git merge ist nicht moeglich, ein Update heisst neu auftragen.

Gemessen statt geschaetzt: 102 geaenderte Dateien, davon nur 12 Upstream-Quellcode. Fuenf davon Branding (flach, isolierbar), sieben ClamAV in der Medien-Pipeline - und genau dort baut Upstream gerade auf MVVM um. Das Update wird an ClamAV mehr kosten als am gesamten Branding.

Refs axion1337.chat/ThreadNet-Web#7
2026-08-06 12:00:00 +00:00
Thore Cimbal f47baa553a Call-Widget auf 0.19.2-threadnet.7 - heisst jetzt aXion1337.Chat
Erste Widget-Version mit VITE_PRODUCT_NAME: Fehlermeldungen und die Versionszeile im Entwicklermodus sagen nicht mehr "Element Call".

pnpm-lock.yaml von Hand nachgezogen (Version an 6 Stellen, integrity aus dem veroeffentlichten Tarball berechnet) - auf diesem Mac gibt es kein Node. Faellt beim CI-Schritt pnpm install --frozen-lockfile auf, falls etwas nicht stimmt.

Die uebersprungene .6 liegt kaputt in der Registry, siehe axion1337.chat/threadnet-call#4.
2026-08-06 12:00:00 +00:00
Thore Cimbal 1d5a136dff Titelbild ausgetauscht: Alpengluehen statt Elements See
alpenglow.jpg (John Towner, Unsplash) ersetzt lake.jpg als Hintergrund der Login-Seite. Die Unsplash License erlaubt kommerzielle Nutzung und Aenderung ohne Genehmigung und verlangt keine Namensnennung - wir nennen ihn trotzdem, das ist die faire Form.

Die Danksagung ist dabei bewusst nicht uebersetzt: der Schluessel credits|default_cover_photo liegt in 32 Sprachdateien, 31 nennen Elements Fotografen namentlich. Nur en/de anzupassen haette in 29 Sprachen eine falsche Attribution stehen lassen, und diese 29 pflegen wir nicht - sie kommen aus Elements Uebersetzungsdienst.

lake.jpg ist entfernt, weil es durch diese Aenderung unerreichbar wird - 610 KB, die sonst in jedem Image mitfahren.
2026-08-06 12:00:00 +00:00
Thore CimbalandClaude Fable 5 580827f7ca desktop: branding-Block, damit der Client nicht Elements Logo zeigt
Die heute im Web gesetzten branding-Werte fehlten der Desktop-Config. Ohne sie
faellt AuthHeaderLogo.tsx auf themes/element/img/logos/element-logo.svg zurueck -
die Anmeldemaske des Desktop-Clients haette also weiter Elements gruenes Logo
gezeigt, waehrend der Web-Client bereits die ThreadNet-Marke traegt.

vector-icons/512.png liegt im webapp-Bundle des Desktops (geprueft), der Pfad
loest also auch dort auf.

Chirurgisch eingefuegt, nicht neu serialisiert - die 17 Themes und der
Gedankenstrich in der Beschreibung bleiben unangetastet.

Danach gilt: Web-Konfiguration ist eine echte Teilmenge der Desktop-Konfiguration,
ohne einen einzigen Wertkonflikt (verglichen ueber alle verschachtelten
Schluessel).

Refs ThreadNet-Web#1

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-06 12:00:00 +00:00
Thore CimbalandClaude Fable 5 6f33c7277a desktop: macOS baut ohne volles Xcode (BUILD-01, management#22)
Upstream setzt mac.icon auf build/icon.icon - das Icon-Composer-Bundle von
macOS 26. Dessen Verarbeitung ruft actool auf, das es nur mit dem vollen Xcode
gibt (~10 GB, App-Store-Login). Mit blossen CommandLineTools scheitert damit
JEDER macOS-Build:

  ⨯ Failed to check actool version. Is Xcode 26 or higher installed?

Die bisherige Diagnose in management#22 war falsch: Dort stand, nur das
DMG-Target brauche actool und das ZIP baue problemlos. Tatsaechlich trifft es
alle macOS-Targets, weil mac.icon fuer alle gilt - das ZIP scheitert genauso.
Der dort empfohlene Ausweg (DMG per hdiutil bauen) haette ein Problem geloest,
das gar nicht am DMG lag.

Loesung: mac.icon und dmg.badgeIcon auf das klassische build/icon.icns. Damit
baut electron-builder ZIP UND DMG selbst, inklusive Blockmap - verifiziert am
2026-08-06, beide Artefakte in Release v0.4.1.

Umgesetzt als Variantenoption statt als Aenderung an Upstreams Defaults: Die
Werte stehen in unserem axion1337/build.json, electron-builder.ts wird nur
additiv um zwei optionale Schluessel erweitert - dieselbe Machart wie das
bereits vorhandene linux.deb.name. Ein Upstream-Merge erzeugt an der
mac.icon-Zeile damit keinen Konflikt.

Preis: kein macOS-26-Icon-Rendering. Ohne Xcode gaebe es ohnehin keinen Build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-06 12:00:00 +00:00
Thore CimbalandClaude Fable 5 6f1dfedde2 web: Tab-Titel, Favicon, Fehlerseite und Desktop-Hinweis auf ThreadNet
Nachtrag zu v0.4.0. Meine Icon-Arbeit deckte nur vector-icons/ ab; an vier
weiteren Stellen stand weiterhin Element - sichtbar, nicht im Verborgenen.

<title>Element</title> stand statisch in der index.html. Im Browser-Tab stand
also 'Element', unabhaengig vom Icon daneben. Ebenso application-name und
apple-mobile-web-app-title, die beim Ablegen als PWA greifen.

favicon.ico neu: Browser fragen diesen Pfad automatisch ab, unabhaengig von den
<link rel=icon>-Tags. Bisher kam dort ein 404 mit text/html zurueck - derselbe
Fehler, den wir am 2026-08-02 im Docusaurus-Wiki hatten. Sieben Groessen von 16
bis 256, aus derselben zentrierten Quelle wie alle anderen Icons.

Fehlerseite (ErrorView): zeigte Elements element-app-logo.png mit alt='Element'.
Ausgerechnet die Seite, die man sieht, wenn sonst nichts funktioniert.

Desktop-Hinweis (SdkConfig defaults): trug Elements Logo und verlinkte auf
element.io/get-started - also auf fremde Downloads, obwohl wir eigene Builds
ausliefern. Zeigt jetzt auf unsere Releases.

Die drei Bildverweise gehen alle auf vector-icons/512.png statt auf neue Dateien:
webpack kopiert aus res/ nur themes/** und vector-icons/**, und eine zweite
Logo-Datei koennte von den uebrigen abdriften.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-06 12:00:00 +00:00
Thore CimbalandClaude Fable 5 c0774b5f58 desktop: Icons zentriert + About-Attribution im Web-Client (ThreadNet-Web#10)
Zwei Haelften desselben Issues, bewusst in einem Commit, weil sie zusammen
gebaut und ausgeliefert werden.

Desktop-Icons: build/icon.png war byte-identisch mit dem alten, unzentrierten
Web-Icon - das Motiv klebte an der Oberkante (Rand oben 3 %, unten 40 %). macOS
und Windows zeigten also dasselbe schiefe Bild wie der Browser-Tab. Alle vier
Artefakte aus der jetzt zentrierten Quelle neu erzeugt: icon.png, icon.ico
(sieben Groessen von 16 bis 256), das Layer-Asset des macOS-Icon-Composers und
icon.icns ueber iconutil (10 Einzelbilder, 16-512 plus @2x). Alle vier gehen auf
dieselbe Datei zurueck, sie koennen also nicht mehr auseinanderlaufen.

About-Attribution: 'ThreadNet - powered by Element' steht jetzt in Einstellungen
-> Hilfe & Info direkt unter der Client-Version, mit Link auf element.io. Das
war der eigentliche Zweck des Issues - bisher stand die Zeile nur in der
Build-Beschreibung des Desktop-Pakets und war im Client nirgends sichtbar.

Zwei bewusste Entscheidungen dabei, beide im Code kommentiert:

- NICHT in getVersionTextToCopy aufgenommen. Der Text dort landet in
  Fehlerberichten; die Herkunft des Forks ist da nur Rauschen.
- Ohne _t(). Ein Markenhinweis wird nicht uebersetzt, und jeder zusaetzliche
  i18n-Schluessel ist Reibung beim naechsten Upstream-Merge - genau das, was das
  Issue mit 'chirurgisch halten' meint.

Das Stylesheet nutzt nur Variablen, die im Projekt bereits verbreitet sind
(--cpd-space-2x in 43, --cpd-color-text-secondary in 28,
--cpd-font-body-sm-regular in 22 Dateien) - kein Blindflug mit erfundenen Tokens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-06 12:00:00 +00:00
Thore CimbalandClaude Fable 5 4fdad89fa6 web: Icons zentriert, theme_color auf die Markenfarbe (ThreadNet-Web#10)
Die Web-Icons trugen bereits die ThreadNet-Marke - anders als im Issue vermerkt.
Sie waren aber falsch zugeschnitten: Rand oben 3 %, unten 40 %, das Motiv klebte
an der Oberkante. In runden und quadratischen Icon-Slots sitzt es dadurch
sichtbar zu hoch.

Alle sieben Groessen aus dem 1024er neu erzeugt: auf die Motiv-Bounding-Box
beschnitten und bei gleicher Groesse (81 % der Kante) mittig gesetzt - jetzt
21 % Rand oben wie unten. resize() statt thumbnail(), sonst waere nur verkleinert
worden.

theme_color stand auf #76CFA6, Elements Mintgruen. Die Marke ist #ed4f4c
(dominante Farbe des Motivs gemessen, nicht geschaetzt) - das Gruen war ein
Upstream-Rest und faerbte die Browser-/PWA-Leiste falsch ein.

config.sample.json: brand von Element auf aXion1337.Chat, damit die Vorlage
zeigt, was Prod tatsaechlich setzt.

Nicht angefasst: der Favicon-Snapshot-Test - er prueft Canvas-Operationen
(clearRect, Dimensionen), nicht den Bildinhalt, und bleibt gruen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-06 12:00:00 +00:00
Thore CimbalandClaude Fable 5 80fcf6c227 desktop: Themes mit dem Web-Stand gleichgezogen
Die zehn theme-factory-Themes sind jetzt in beiden Configs identisch (gitops
c47b0a3 + Nachfolger). Ohne das haetten Desktop-Clients weiter meine alte
Interpretation der Paletten gezeigt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-02 12:00:00 +00:00
Thore CimbalandClaude Fable 5 f4995e5882 rebrand: auch 'name' auf threadnet-desktop - Linux-Pakete hiessen sonst weiter Element
electron-builder leitet unterschiedliche Dinge aus zwei Feldern ab: productName
bestimmt den App-Namen (macOS .app, Windows Setup.exe), 'name' dagegen den
Linux-Paketnamen, das Binary und den /opt-Pfad. Nach dem Rebrand hiess die App
ueberall ThreadNet - ausser bei .deb und .tar.gz.

Nebenwirkung bewusst in Kauf genommen: apt sieht ein neues Paket, ein
Upgrade-Pfad von element-desktop existiert nicht. Bei einem Rebrand ist das
erwartbar; die appId bleibt unveraendert, damit lokale Daten erhalten bleiben.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-02 12:00:00 +00:00
Thore CimbalandClaude Fable 5 e21a895390 ci: VARIANT_PATH in beiden Desktop-Jobs - Pakete hiessen weiter element-desktop
Der Rebrand-Commit legte apps/desktop/axion1337/build.json an, aber electron-builder
nutzt eine Variante nur, wenn VARIANT_PATH gesetzt ist. Lokal hatte ich sie per
Umgebungsvariable gesetzt, in der CI fehlte sie - deshalb trugen die Linux- und
Windows-Pakete aus Pipeline 147 weiter den Namen 'element-desktop' bzw.
'Element Setup', obwohl die Icons bereits neu waren.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-02 12:00:00 +00:00
Thore CimbalandClaude Fable 5 c51b68194a rebrand: alle Icons ersetzt - Windows-.ico und Web-Icons fehlten noch
Gestern wurden nur icon.png und icon.icns getauscht; Windows (.ico) und der
Web-Client (res/vector-icons/, manifest.json) trugen weiter das Element-Icon.

Dazu ein echter Fehler behoben: die Icons wurden mit PIL.thumbnail() erzeugt -
das skaliert NUR nach unten. Das 277px-Motiv wurde in 1024er-Icons daher nie
vergroessert und fuellte ueberall bloss 54% x 38% der Flaeche. Jetzt mit resize()
aus dem 1024er-Original: 81% Breite statt 54%.

Ersetzt: web/res/vector-icons/{24,120,144,152,180,512,1024}.png, manifest.json
(name/short_name -> ThreadNet), desktop/build/icon.{png,ico,icns}.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-02 12:00:00 +00:00
Thore CimbalandClaude Fable 5 6b0261d66c rebrand: Desktop-Client heisst ThreadNet und traegt die eigene Marke
Erster Schritt aus ThreadNet-Web#10 - der Client hiess bisher 'Element' und
zeigte dessen Icon.

- apps/desktop/axion1337/build.json: eigene Build-Variante (VARIANT_PATH), damit
  element.io/release/build.json unangetastet bleibt und Upstream-Merges nicht
  reiben. productName 'ThreadNet', Beschreibung 'ThreadNet - powered by Element'
  als faire Attribution (Element Web ist AGPL, dies ist ein Fork davon).
- appId bleibt bewusst 'im.riot.app': bestehende Installationen behalten ihre
  lokalen Daten. Ein appId-Wechsel waere ein Datenschnitt und gehoert in die
  Rebranding-Runde, nicht in einen Icon-Commit.
- build/icon.png + icon.icns: ThreadNet-Bildmarke, transparenter Rand entfernt
  (das Motiv fuellte im Original nur 54% x 38% - direkt skaliert verschwindet es).

Verifiziert im Build: CFBundleName/CFBundleDisplayName = ThreadNet, eigenes
Icon, alle 17 Themes im webapp.asar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-02 12:00:00 +00:00
Thore CimbalandClaude Fable 5 6e021ee853 desktop: dieselben 17 Themes wie im Web-Client
Die Desktop-Apps laden ihre eigene config.json und haetten die elf neuen Themes
sonst nicht (aXion1337 Light + zehn Paletten). Aus der bereits deployten
Web-Konfiguration uebernommen, damit beide Clients identisch sind.

Chirurgisch eingefuegt (Lehre vom 2026-08-01, als ein Re-Dump dieser Datei 441
Zeilen umformatierte): 0 entfernte / 427 neue Zeilen, JSON validiert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-02 12:00:00 +00:00
Thore CimbalandClaude Fable 5 5a8c42b5e9 desktop: Rageshake-Entfernung auf Minimal-Diff korrigiert (Formatierung des Vorgaengers wiederhergestellt)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-01 12:00:00 +00:00
Thore CimbalandClaude Fable 5 5505b9cfc1 desktop: Rageshake-Endpoint entfernt - keine Bug-Reports+Logs an element.io
Entscheidung sorb 2026-08-01: Bug-Melden-Funktion entfaellt vorerst;
eigener Rageshake oder Alternative (z.B. Zammad) als [LOW]-Issue notiert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-01 12:00:00 +00:00
Thore CimbalandClaude Fable 5 a2bfaaec8f ci: woechentlicher Trivy-CVE-Scan des Prod-Web-Images (gitops#31)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-08-01 12:00:00 +00:00
Thore CimbalandClaude Fable 5 b656eefdc7 ci: Cache-Layout fuer app-builder-Artefakte korrigiert (gedoppelter Schluessel)
Aus artifactDownloader.go + binDownload.js verifiziert: Schluessel ist
<release>-<release>, Unterverzeichnis = Namensteil vor dem ersten
Bindestrich (nsis-resources daher unter nsis\). Job 431 traf winCodeSign
im Cache, scheiterte aber am nsis-Miss mit einfachem Namen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-07-31 12:00:00 +00:00
Thore CimbalandClaude Fable 5 a55adfdd65 ci: alle app-builder-GitHub-Downloads resumefaehig vorladen
winCodeSign starb an denselben CDN-Resets wie zuvor die Electron-Binary
(Job 424); Prefetch-Skript konsolidiert Electron-Zip + winCodeSign +
NSIS-Pakete mit curl -C - in die persistenten Caches (ELECTRON_CACHE,
ELECTRON_BUILDER_CACHE).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-07-31 12:00:00 +00:00
Thore CimbalandClaude Fable 5 cf66ff018d ci: Electron-Binary resumefaehig vorladen (ELECTRON_CACHE)
GitHub-CDN brach den ~120-MB-Download zweimal mitten im Transfer ab
(Jobs 415/416, wsarecv: connection forcibly closed); app-builder kann
nicht fortsetzen. curl -C - in Schleife + persistenter Cache im Gast.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-07-31 12:00:00 +00:00
Thore CimbalandClaude Fable 5 97e18bbf2f ci: Windows-Provisionierung um Rust/Perl/Python/NASM-PATH ergaenzt
hak/matrix-seshat/check.ts verlangt auf Windows rustc, perl, patch, nasm,
nmake und python - Job 399 scheiterte an fehlendem rustc. Rust landet
maschinenweit unter C:\Rust (SYSTEM-Dienst sieht kein Benutzerprofil),
Strawberry Perl deckt perl+patch ab, NASM-PATH wie im Upstream-Workflow
explizit nachgezogen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKhFj1S3UdD6xL2fbWPeYj
2026-07-31 12:00:00 +00:00
Thore Cimbal c401f54e4d ci: windows_provision Dienst-Neustart via Scheduled Task (Race mit Job-Cleanup, siehe #5) 2026-07-31 12:00:00 +00:00
Thore Cimbal 1eb4968968 ci: desktop_linux nutzt das Build-Image aus der Lab-Registry (OVERMIND-01 abgeschlossen) 2026-07-31 12:00:00 +00:00
Thore CimbalandClaude Fable 5 a8767583da ci: verbliebene GitHub-Workflow-Dateien entfernt (CFGMON-11)
Vollstaendig durch .gitlab-ci.yml ersetzt (Linux verifiziert gruen, Windows
in Arbeit); Gitea Actions ist fuer dieses Repo deaktiviert, auf GitHub lag
das Repo nie. Die Dateien bleiben als Uebersetzungsreferenz in der
Git-Historie erhalten.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 12:00:00 +00:00
Thore Cimbal 0d0c1659c6 ci: desktop_image in die Lab-Registry (OVERMIND-01, CI_JOB_TOKEN statt rohana-Secret) 2026-07-31 12:00:00 +00:00
Thore Cimbal 624274f5f0 ci: windows_provision - core.longpaths + LongPathsEnabled (MAX_PATH-Fix fuer Checkout) 2026-07-31 12:00:00 +00:00
Thore CimbalandClaude Fable 5 9e2f49b3dd ci: schlankes Trigger-Schema - Pushes pruefen, Tags releasen
web laeuft nur noch bei build-relevanten Pfadaenderungen (Doku-Commits
kosten nichts) und immer bei v*-Tags. docker_web und desktop_linux bauen
nur noch bei Release-Tags (Image-Tag = Git-Tag statt sha), desktop_linux
bleibt auf main manuell triggerbar. Windows-/VM-Jobs sind auf main und
Tags manuell verfuegbar. Hintergrund: Deploy ist ohnehin ein bewusster
Tag-Bump im gitops-Repo - Artefakte pro Doku-Commit waren reine
Runner-Verschwendung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 12:00:00 +00:00
Thore Cimbal a414bb8585 ci: windows_provision ohne Checkout (GIT_STRATEGY none) - Henne-Ei mit fehlendem git 2026-07-31 12:00:00 +00:00
Thore Cimbal 8465457a48 ci: windows_provision-Job - idempotente Gast-Werkzeuge via Runner (Runbook-Fix: choco einzeln statt Mehrfachpaket mit --version) 2026-07-31 12:00:00 +00:00
Thore Cimbal 252b16ca71 docs: Repo-Topologie - git.lab kanonisch, rohana als Push-Mirror 2026-07-31 12:00:00 +00:00
Thore CimbalandClaude Fable 5 0d0255cb7f ci: Windows-Strecke - VM-Start/Stop-Jobs + desktop_windows (Issue #5)
start/stop_windows_vm steuern die On-Demand-Build-VM vom Linux-Runner aus
(Docker-Socket). desktop_windows (tags: windows, manuell, unsigniert/nsis)
uebersetzt den x64-Pfad des Upstream-Workflows: frozen-lockfile, webapp +
axion-config vor dem asar, VS-DevShell + build:native mit MSVC-Target.
Wartet in der Queue bis der Windows-Runner (lab-windows-1) online ist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 12:00:00 +00:00
Thore Cimbal bc0769809d ci: desktop_linux automatisch auf main nach erstem gruenen Lauf 2026-07-31 12:00:00 +00:00
Thore CimbalandClaude Fable 5 e21e101004 ci: GitLab pipeline (web build, rohana image push, desktop linux)
First CI for the lab GitLab (git.lab): web job builds the webapp with the
lessons from the Gitea attempts baked in (frozen-lockfile instead of
layered.sh, 6GB node heap), docker_web pushes the canonical
apps/web/Dockerfile image to the rohana registry that Flux pulls from,
and two manual jobs cover the desktop path - a dockerbuild build-image
and the Electron Linux build replicating the manually verified flow.
Also tracks the production client config (apps/desktop/axion1337/) that
previously only existed inside the one-off manual desktop build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 12:00:00 +00:00
Thore CimbalandClaude Sonnet 5 8d46fb33d2 ci: use lockfile install instead of layered.sh, fix always-green gate
layered.sh clones a branch-matching upstream matrix-js-sdk - on this fork
that would override the deliberately pinned js-sdk from pnpm-lock.yaml
(issue #12) with upstream develop, and it also needs jq which the node:20
job container lacks (the actual CI failure). Plain frozen-lockfile install
matches the production docker build.

Also replace the contains(needs.*.result, ...) gate check with explicit
per-job checks: the object-filter syntax evaluated wrongly on Gitea/act,
letting build-complete report success while build_ew had failed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 12:00:00 +00:00
Thore CimbalandClaude Sonnet 5 71ac7c4346 ci: map runs-on to actual runner labels, trim jobs the runner can't serve
Root cause found for jobs never starting: runs were created all along
(visible in the Actions UI), but every job requested ubuntu-24.04 while
builder-1 only offers ubuntu-latest, linux-build and win-wine - a label
mismatch means jobs queue forever, invisible via the /actions/tasks API
which only lists picked-up jobs.

Mapped build/prepare/gate jobs to ubuntu-latest and the Linux desktop
build to linux-build (electronuserland/builder image, amd64/static only -
the only variant buildable on the single x86 runner). Removed jobs the
runner cannot serve instead of letting them starve the queue: web+desktop
playwright tests (need Docker-in-job and display setup, tracked as
follow-up in issue #2) and the native-Windows desktop build (workflow
assumes signtool/MSVC/PowerShell; the win-wine label awaits a dedicated
wine-based workflow).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 12:00:00 +00:00
Thore Cimbal 8faade6c7b ci: test push after git-hook resync 2026-07-30 12:00:00 +00:00
Thore CimbalandClaude Sonnet 5 f2abae85dd ci: remove unused workflow_call trigger (Gitea Actions bug go-gitea/gitea#33238)
push + workflow_call in the same on: block doesn't reliably fire push
events on Gitea Actions 1.27 (reproducible upstream, closed as "not
planned" - https://github.com/go-gitea/gitea/issues/33238). Nothing in
this repo invokes build-and-test.yaml via workflow_call anymore after the
workflow cleanup, so removing the trigger is a safe, targeted fix rather
than a workaround.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 12:00:00 +00:00
Thore Cimbal 0f1d31021c ci: add trigger-test note to force a real push event 2026-07-30 12:00:00 +00:00
Thore Cimbal bd8298db39 ci: trigger first Actions run now that Actions is enabled for this repo 2026-07-30 12:00:00 +00:00
Thore CimbalandClaude Sonnet 5 77ffeb4514 ci: reduce workflows to fork-relevant CI, fix upstream checkouts (Issue #2)
Removed ~37 workflow files that are unmodified upstream tooling this fork
doesn't use (Netlify, SonarCloud, Localazy, release-drafter/backport,
GitHub issue-triage bots, npm-publish, Cloudflare Pages deploy, Docker
push to ghcr.io/element-hq). Enabling Actions for this repo would have
activated all of them simultaneously against a single-capacity shared
runner - most would just fail loudly and starve the one job slot shared
with other repos.

Kept build-and-test.yaml (the actual comprehensive build+test+desktop
workflow) and its build_desktop_*.yaml sub-workflows. Fixed: push trigger
now targets `main` (was `staging`/`master`, neither of which exist here -
push events never fired at all before), 4 checkout steps now check out
this fork instead of element-hq/element-web, and removed the macOS
desktop build job since the registered runner (builder-1) has no macOS
label and would queue forever instead of failing cleanly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 12:00:00 +00:00
Thore Cimbal 45fb329577 docs: add axion1337-fork.md documenting fork-specific changes vs upstream 2026-07-29 12:00:00 +00:00
79 changed files with 1428 additions and 4284 deletions
-34
View File
@@ -1,34 +0,0 @@
name: Backport
on:
# Privilege escalation necessary to enable backporting PRs from forks
# 🚨 We must not execute any checked out code here.
pull_request_target: # zizmor: ignore[dangerous-triggers]
types:
- closed
- labeled
branches:
- develop
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
backport:
name: Backport
runs-on: ubuntu-24.04
# Only react to merged PRs for security reasons.
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
if: >
github.event.pull_request.merged
&& (
github.event.action == 'closed'
|| (
github.event.action == 'labeled'
&& contains(github.event.label.name, 'backport')
)
)
steps:
- uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2
with:
labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>"
# We can't use GITHUB_TOKEN here or CI won't run on the new PR
github_token: ${{ secrets.ELEMENT_BOT_TOKEN }}
@@ -1,48 +0,0 @@
# Triggers after the playwright tests have finished,
# taking the artifact and uploading it to Netlify for easier viewing
name: Upload End to End Test report to Netlify
on:
# Privilege escalation necessary to publish to Netlify
# 🚨 We must not execute any checked out code here.
workflow_run: # zizmor: ignore[dangerous-triggers]
workflows: ["Build & Test"]
types:
- completed
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.run_id }}
cancel-in-progress: ${{ github.event.workflow_run.event == 'pull_request' }}
permissions: {}
jobs:
report:
if: github.event.workflow_run.conclusion != 'cancelled'
name: Report results
runs-on: ubuntu-24.04
environment: Netlify
permissions:
statuses: write
deployments: write
actions: read
steps:
- name: Download HTML report
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: html-report
path: playwright-report
- name: 📤 Deploy to Netlify
uses: matrix-org/netlify-pr-preview@9805cd123fc9a7e421e35340a05e1ebc5dee46b5 # v3
with:
path: playwright-report
owner: ${{ github.event.workflow_run.head_repository.owner.login }}
branch: ${{ github.event.workflow_run.head_branch }}
revision: ${{ github.event.workflow_run.head_sha }}
token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
site_id: ${{ vars.NETLIFY_SITE_ID }}
desc: Playwright Report
deployment_env: EndToEndTests
prefix: "e2e-"
-311
View File
@@ -1,311 +0,0 @@
# builds Element Web
# runs Playwright tests against the built Element Web
# builds Element Desktop using the built Element Web
#
# Tries to use a matching js-sdk branch for the build.
#
# Produces a `webapp` artifact
# Produces multiple Desktop artifacts
# Produces multiple Playwright report artifacts
name: Build & Test
on:
# CRON to run all Projects at 6am UTC
schedule:
- cron: "0 6 * * *"
pull_request: {}
merge_group:
types: [checks_requested]
push:
# We do not build on push to develop as the merge_group check handles that
branches: [staging, master]
# support triggering from other workflows
workflow_call:
inputs:
skip:
type: boolean
required: false
default: false
description: "A boolean to skip the playwright check itself while still creating the passing check. Useful when only running in Merge Queues."
matrix-js-sdk-sha:
type: string
required: false
description: "The Git SHA of matrix-js-sdk to build against. By default, will use a matching branch name if it exists, or develop."
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env:
# fetchdep.sh needs to know our PR number
PR_NUMBER: ${{ github.event.pull_request.number }}
# Use 4 runners in the default case, but only 1 when running on a schedule where we run all 5 projects
NUM_RUNNERS: ${{ github.event_name == 'schedule' && 1 || 4 }}
NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes
permissions: {} # No permissions required
jobs:
build_ew:
name: "Build Element Web"
runs-on: ubuntu-24.04
if: inputs.skip != true
outputs:
num-runners: ${{ env.NUM_RUNNERS }}
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
# Skip pull_request runs on renovate PRs to speed up CI time, delegating to the full run in merge queue
skip: ${{ inputs.skip || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: element-hq/element-web
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
node-version: "lts/*"
- name: Fetch layered build
env:
# tell layered.sh to check out the right sha of the JS-SDK & EW, if they were given one
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
run: scripts/layered.sh
- name: Copy config
working-directory: apps/web
run: cp element.io/develop/config.json config.json
- name: Build
env:
CI_PACKAGE: true
working-directory: apps/web
run: VERSION=$(scripts/get-version-from-git.sh) pnpm run build
- name: Upload Artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: webapp
path: apps/web/webapp
retention-days: 1
- name: Calculate runner variables
id: runner-vars
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
const matrix = Array.from({ length: numRunners }, (_, i) => i + 1);
core.setOutput("matrix", JSON.stringify(matrix));
playwright_ew:
name: "Run Tests [${{ matrix.project }}] ${{ matrix.runner }}/${{ needs.build_ew.outputs.num-runners }}"
needs: build_ew
if: needs.build_ew.outputs.skip == 'false'
runs-on: ubuntu-24.04
permissions:
actions: read
issues: read
pull-requests: read
strategy:
fail-fast: false
matrix:
# Run multiple instances in parallel to speed up the tests
runner: ${{ fromJSON(needs.build_ew.outputs.runners-matrix) }}
project:
- Chrome
- Firefox
- WebKit
- Dendrite
- Pinecone
runAllTests:
- ${{ github.event_name == 'schedule' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests') }}
# Skip the Firefox & Safari runs unless this was a cron trigger or PR has X-Run-All-Tests label
exclude:
- runAllTests: false
project: Firefox
- runAllTests: false
project: WebKit
- runAllTests: false
project: Dendrite
- runAllTests: false
project: Pinecone
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
repository: element-hq/element-web
- name: 📥 Download artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: webapp
path: apps/web/webapp
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
cache-dependency-path: pnpm-lock.yaml
node-version: "lts/*"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup playwright
uses: ./.github/actions/setup-playwright
with:
needs-webkit: ${{ matrix.project == 'WebKit' }}
write-cache: ${{ github.event_name != 'merge_group' }}
# We skip tests tagged with @mergequeue when running on PRs, but run them in MQ and everywhere else
- name: Run Playwright tests
working-directory: apps/web
run: |
pnpm test:playwright \
--shard "$SHARD" \
--project="${{ matrix.project }}" \
${{ (github.event_name == 'pull_request' && matrix.runAllTests == false ) && '--grep-invert @mergequeue' || '' }}
env:
SHARD: ${{ format('{0}/{1}', matrix.runner, needs.build_ew.outputs.num-runners) }}
- name: Upload blob report to GitHub Actions Artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: blob-report-${{ matrix.project }}-${{ matrix.runner }}
path: apps/web/blob-report
retention-days: 1
if-no-files-found: error
downstream-modules:
name: Downstream Playwright tests [element-modules]
needs: build_ew
if: needs.build_ew.outputs.skip == 'false' && github.event_name == 'merge_group'
uses: element-hq/element-modules/.github/workflows/reusable-playwright-tests.yml@main # zizmor: ignore[unpinned-uses]
with:
webapp-artifact: webapp
reporter: blob
prepare_ed:
name: "Prepare Element Desktop"
uses: ./.github/workflows/build_desktop_prepare.yaml
needs: build_ew
if: needs.build_ew.outputs.skip == 'false'
permissions:
contents: read
with:
config: ${{ (github.event.pull_request.base.ref || github.ref_name) == 'develop' && 'element.io/nightly' || 'element.io/release' }}
version: ${{ case((github.event.pull_request.base.ref || github.ref_name) == 'develop' || github.event_name == 'merge_group', 'develop', '') }}
webapp-artifact: webapp
build_ed_windows:
needs: prepare_ed
name: "Desktop Windows"
uses: ./.github/workflows/build_desktop_windows.yaml
# Skip Windows builds on PRs, as the Linux amd64 build is enough of a smoke test and includes the screenshot tests
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests')
strategy:
matrix:
arch: [x64, ia32, arm64]
with:
arch: ${{ matrix.arch }}
blob_report: true
build_ed_linux:
needs: prepare_ed
name: "Desktop Linux"
uses: ./.github/workflows/build_desktop_linux.yaml
strategy:
matrix:
sqlcipher: [system, static]
arch: [amd64, arm64]
runAllTests:
- ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests') }}
exclude:
# We ship static sqlcipher builds, so delegate testing the system builds to the merge queue
- runAllTests: false
sqlcipher: system
# Additionally skip arm64 system builds on PRs, as the amd64 test is enough for a smoke test and includes the screenshot tests
- runAllTests: false
arch: arm64
with:
sqlcipher: ${{ matrix.sqlcipher }}
arch: ${{ matrix.arch }}
blob_report: true
build_ed_macos:
needs: prepare_ed
name: "Desktop macOS"
uses: ./.github/workflows/build_desktop_macos.yaml
# Skip macOS builds on PRs, as the Linux amd64 build is enough of a smoke test and includes the screenshot tests
# and we have a very low limit of concurrent macos runners (5) across the Github org.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'X-Run-All-Tests')
with:
blob_report: true
complete:
name: end-to-end-tests
needs:
- build_ew
- playwright_ew
- downstream-modules
- prepare_ed
- build_ed_windows
- build_ed_linux
- build_ed_macos
if: always()
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
if: needs.build_ew.outputs.skip == 'false'
with:
persist-credentials: false
repository: element-hq/element-web
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
if: needs.build_ew.outputs.skip == 'false'
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
if: needs.build_ew.outputs.skip == 'false'
with:
cache: "pnpm"
node-version: "lts/*"
- name: Install dependencies
if: needs.build_ew.outputs.skip == 'false'
run: pnpm install --frozen-lockfile
- name: Download blob reports from GitHub Actions Artifacts
if: needs.build_ew.outputs.skip == 'false'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: blob-report-*
path: all-blob-reports
merge-multiple: true
- name: Merge into HTML Report
if: needs.build_ew.outputs.skip == 'false'
run: |
pnpm playwright merge-reports \
--config=playwright-merge.config.ts \
./all-blob-reports
env:
# Only pass creds to the flaky-reporter on main branch runs
GITHUB_TOKEN: ${{ github.ref_name == 'develop' && secrets.ELEMENT_BOT_TOKEN || '' }}
PLAYWRIGHT_HTML_TITLE: ${{ case(github.event_name == 'pull_request', format('Playwright Report PR-{0}', env.PR_NUMBER), 'Playwright Report') }}
# Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected
- name: Upload HTML report
if: always() && needs.build_ew.outputs.skip == 'false'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: html-report
path: playwright-report
retention-days: 14
if-no-files-found: error
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1
-76
View File
@@ -1,76 +0,0 @@
name: Build
on:
pull_request: {}
push:
branches: [develop, master]
merge_group:
types: [checks_requested]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# develop pushes and repository_dispatch handled in build_develop.yaml
env:
# This must be set for fetchdep.sh to get the right branch
PR_NUMBER: ${{ github.event.pull_request.number }}
NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes
permissions: {} # No permissions required
jobs:
build:
name: "Build on ${{ matrix.image }}"
# We build on all 3 platforms to ensure we don't have any OS-specific build incompatibilities
strategy:
fail-fast: false
matrix:
image:
- ubuntu-24.04
- windows-2022
- macos-14
isDevelop:
- ${{ github.event_name == 'push' && github.ref_name == 'develop' }}
isPullRequest:
- ${{ github.event_name == 'pull_request' }}
# Skip the ubuntu-24.04 build for the develop branch as the dedicated CD build_develop workflow handles that
# Skip the non-linux builds for pull requests as Windows is awfully slow, so run in merge queue only
exclude:
- isDevelop: true
image: ubuntu-24.04
- isPullRequest: true
image: windows-2022
- isPullRequest: true
image: macos-14
runs-on: ${{ matrix.image }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
# Disable cache on Windows as it is slower than not caching
# https://github.com/actions/setup-node/issues/975
cache: ${{ runner.os != 'Windows' && 'pnpm' || '' }}
node-version: "lts/*"
- name: Fetch layered build
run: ./scripts/layered.sh
- name: Copy config
working-directory: apps/web
run: cp element.io/develop/config.json config.json
- name: Build
working-directory: apps/web
env:
CI_PACKAGE: true
run: VERSION=$(scripts/get-version-from-git.sh) pnpm run build
- name: Upload Artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: webapp-${{ matrix.image }}
path: apps/web/webapp
retention-days: 1
-86
View File
@@ -1,86 +0,0 @@
name: Build Debian package
on:
release:
types: [published]
concurrency: ${{ github.workflow }}
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
build:
name: Build package
environment: packages.element.io
runs-on: ubuntu-24.04
env:
R2_INCOMING_BUCKET: ${{ vars.R2_INCOMING_BUCKET }}
R2_URL: ${{ vars.CF_R2_S3_API }}
VERSION: ${{ github.ref_name }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Download package
working-directory: apps/web
run: |
wget "https://github.com/element-hq/element-web/releases/download/$VERSION/element-$VERSION.tar.gz"
wget "https://github.com/element-hq/element-web/releases/download/$VERSION/element-$VERSION.tar.gz.asc"
- name: Check GPG signature
working-directory: apps/web
run: |
wget "https://packages.element.io/element-release-key.gpg"
gpg --import element-release-key.gpg
gpg --fingerprint "$FINGERPRINT"
gpg --verify "element-$VERSION.tar.gz.asc" "element-$VERSION.tar.gz"
env:
FINGERPRINT: ${{ vars.GPG_FINGERPRINT }}
- name: Prepare
working-directory: apps/web
run: |
mkdir -p debian/tmp/DEBIAN
find debian -maxdepth 1 -type f -exec cp "{}" debian/tmp/DEBIAN/ \;
mkdir -p debian/tmp/usr/share/element-web/ debian/tmp/etc/element-web/
tar -xf "element-$VERSION.tar.gz" -C debian/tmp/usr/share/element-web --strip-components=1 --no-same-owner --no-same-permissions
mv debian/tmp/usr/share/element-web/config.sample.json debian/tmp/etc/element-web/config.json
ln -s /etc/element-web/config.json debian/tmp/usr/share/element-web/config.json
- name: Write changelog
working-directory: apps/web
run: |
VERSION=$(cat package.json | jq -r .version)
TIME=$(date -d "$PUBLISHED_AT" -R)
{
echo "element-web ($VERSION) default; urgency=medium"
echo "$BODY" | sed 's/^##/\n */g;s/^\*/ */g' | perl -pe 's/\[.+?]\((.+?)\)/\1/g'
echo ""
echo " -- $ACTOR <support@element.io> $TIME"
} > debian/tmp/DEBIAN/changelog
env:
ACTOR: ${{ github.actor }}
VERSION: ${{ github.event.release.tag_name }}
BODY: ${{ github.event.release.body }}
PUBLISHED_AT: ${{ github.event.release.published_at }}
- name: Build deb package
working-directory: apps/web
run: |
VERSION=$(cat package.json | jq -r .version)
dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: element-web.deb
path: apps/web/element-web.deb
retention-days: 14
- name: Publish to packages.element.io
if: github.event.release.prerelease == false
uses: element-hq/packages.element.io@master # zizmor: ignore[unpinned-uses]
with:
file: apps/web/element-web.deb
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
bucket-api: ${{ vars.CF_R2_S3_API }}
bucket-key-id: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
bucket-access-key: ${{ secrets.CF_R2_TOKEN }}
@@ -1,313 +0,0 @@
name: Build and Deploy
on:
# Nightly build
schedule:
- cron: "0 9 * * *"
# Release build
release:
types: [published]
# Manual nightly & release
workflow_dispatch:
inputs:
mode:
description: What type of build to trigger. Release builds MUST be ran from the `master` branch.
required: true
default: nightly
type: choice
options:
- nightly
- release
macos:
description: Build macOS
required: true
type: boolean
default: true
windows:
description: Build Windows
required: true
type: boolean
default: true
linux:
description: Build Linux
required: true
type: boolean
default: true
deploy:
description: Deploy artifacts
required: true
type: boolean
default: true
run-name: Element ${{ inputs.mode != 'release' && github.event_name != 'release' && 'Nightly' || 'Desktop' }}
concurrency: ${{ github.workflow }}
env:
R2_BUCKET: ${{ vars.R2_BUCKET }}
permissions: {} # Uses ELEMENT_BOT_TOKEN
jobs:
prepare:
uses: ./.github/workflows/build_desktop_prepare.yaml
permissions:
contents: read
with:
config: element.io/${{ inputs.mode || (github.event_name == 'release' && 'release') || 'nightly' }}
version: ${{ (inputs.mode != 'release' && github.event_name != 'release') && 'develop' || '' }}
nightly: ${{ inputs.mode != 'release' && github.event_name != 'release' }}
deploy: ${{ inputs.deploy || (github.event_name != 'workflow_dispatch' && github.event.release.prerelease != true) }}
secrets:
CF_R2_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
CF_R2_TOKEN: ${{ secrets.CF_R2_TOKEN }}
trigger-pro-pipeline:
name: Trigger Pro pipeline
needs: prepare
runs-on: ubuntu-24.04
steps:
- uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4
with:
repository: element-hq/element-web-pro
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
event-type: trigger-pipeline
client-payload: |-
{
"base-ref": "${{ github.ref_name }}"
}
windows:
if: github.event_name != 'workflow_dispatch' || inputs.windows
needs: prepare
name: Windows ${{ matrix.arch }}
strategy:
matrix:
arch: [x64, arm64]
uses: ./.github/workflows/build_desktop_windows.yaml
secrets: inherit # zizmor: ignore[secrets-inherit]
with:
sign: true
arch: ${{ matrix.arch }}
version: ${{ needs.prepare.outputs.nightly-version }}
macos:
if: github.event_name != 'workflow_dispatch' || inputs.macos
needs: prepare
name: macOS
uses: ./.github/workflows/build_desktop_macos.yaml
secrets: inherit # zizmor: ignore[secrets-inherit]
with:
sign: true
base-url: https://packages.element.io/${{ needs.prepare.outputs.packages-dir }}
version: ${{ needs.prepare.outputs.nightly-version }}
linux:
if: github.event_name != 'workflow_dispatch' || inputs.linux
needs: prepare
name: Linux ${{ matrix.arch }} (sqlcipher ${{ matrix.sqlcipher }})
strategy:
matrix:
arch: [amd64, arm64]
sqlcipher: [static]
uses: ./.github/workflows/build_desktop_linux.yaml
with:
arch: ${{ matrix.arch }}
sqlcipher: ${{ matrix.sqlcipher }}
version: ${{ needs.prepare.outputs.nightly-version }}
deploy:
needs:
- prepare
- macos
- linux
- windows
runs-on: ubuntu-24.04
name: ${{ needs.prepare.outputs.deploy == 'true' && 'Deploy' || 'Deploy (dry-run)' }}
if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
environment: ${{ needs.prepare.outputs.deploy == 'true' && 'packages.element.io' || '' }}
steps:
- name: Download artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
- name: Prepare artifacts for deployment
run: |
set -ex
# Windows
for arch in x64 arm64
do
if [ -d "win-$arch" ]; then
mkdir -p packages.element.io/{install,update}/win32/$arch
mv win-$arch/squirrel-windows*/*.exe "packages.element.io/install/win32/$arch/"
mv win-$arch/squirrel-windows*/*.nupkg "packages.element.io/update/win32/$arch/"
mv win-$arch/squirrel-windows*/RELEASES "packages.element.io/update/win32/$arch/"
fi
done
# macOS
if [ -d macos ]; then
mkdir -p packages.element.io/{install,update}/macos
mv macos/*.dmg packages.element.io/install/macos/
mv macos/*-mac.zip packages.element.io/update/macos/
mv macos/*.json packages.element.io/update/macos/
fi
# Linux
if [ -d linux-amd64-sqlcipher-static ]; then
mkdir -p packages.element.io/install/linux/glibc-x86-64
mv linux-amd64-sqlcipher-static/*.tar.gz packages.element.io/install/linux/glibc-x86-64
fi
if [ -d linux-arm64-sqlcipher-static ]; then
mkdir -p packages.element.io/install/linux/glibc-aarch64
mv linux-arm64-sqlcipher-static/*.tar.gz packages.element.io/install/linux/glibc-aarch64
fi
# We don't wish to store the installer for every nightly ever, so we only keep the latest
- name: "[Nightly] Strip version from installer file"
if: needs.prepare.outputs.nightly-version != ''
run: |
set -ex
# Windows
for arch in x64 arm64
do
if [ -d "win-$arch" ]; then mv packages.element.io/install/win32/$arch/{*,"Element Nightly Setup"}.exe; fi
done
# macOS
if [ -d macos ]; then mv packages.element.io/install/macos/{*,"Element Nightly"}.dmg; fi
# Linux
if [ -d linux-amd64-sqlcipher-static ]; then mv packages.element.io/install/linux/glibc-x86-64/{*,element-desktop-nightly}.tar.gz; fi
if [ -d linux-arm64-sqlcipher-static ]; then mv packages.element.io/install/linux/glibc-aarch64/{*,element-desktop-nightly}.tar.gz; fi
- name: "[Release] Prepare release latest symlink"
if: needs.prepare.outputs.nightly-version == ''
run: |
set -ex
# Windows
for arch in x64 arm64
do
if [ -d "win-$arch" ]; then
pushd packages.element.io/install/win32/$arch
ln -s "$(find . -type f -iname "*.exe" | xargs -0 -n1 -- basename)" "Element Setup.exe"
popd
fi
done
# macOS
if [ -d macos ]; then
pushd packages.element.io/install/macos
ln -s "$(find . -type f -iname "*.dmg" | xargs -0 -n1 -- basename)" "Element.dmg"
popd
fi
# Linux
if [ -d linux-amd64-sqlcipher-static ]; then
pushd packages.element.io/install/linux/glibc-x86-64
ln -s "$(find . -type f -iname "*.tar.gz" | xargs -0 -n1 -- basename)" "element-desktop.tar.gz"
popd
fi
if [ -d linux-arm64-sqlcipher-static ]; then
pushd packages.element.io/install/linux/glibc-aarch64
ln -s "$(find . -type f -iname "*.tar.gz" | xargs -0 -n1 -- basename)" "element-desktop.tar.gz"
popd
fi
- name: Stash packages.element.io
if: needs.prepare.outputs.deploy == 'false'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: packages.element.io
path: packages.element.io
# Checksum algorithm specified as per https://developers.cloudflare.com/r2/examples/aws/aws-cli/
- name: Deploy artifacts
if: needs.prepare.outputs.deploy == 'true'
run: |
set -x
aws s3 cp --recursive packages.element.io/ s3://$R2_BUCKET/$DEPLOYMENT_DIR --endpoint-url $R2_URL --region auto --checksum-algorithm CRC32
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
R2_URL: ${{ vars.CF_R2_S3_API }}
DEPLOYMENT_DIR: ${{ needs.prepare.outputs.packages-dir }}
- name: Notify packages.element.io of new files
if: needs.prepare.outputs.deploy == 'true'
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4
with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
repository: element-hq/packages.element.io
event-type: packages-index
- name: Find debs
id: deb
if: needs.linux.result == 'success'
run: |
set -x
for arch in amd64 arm64
do
echo "$arch=$(ls linux-$arch-sqlcipher-static/*.deb | tail -n1)" >> $GITHUB_OUTPUT
done
- name: Stash debs
if: needs.prepare.outputs.deploy == 'false' && needs.linux.result == 'success'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: debs
path: |
${{ steps.deb.outputs.amd64 }}
${{ steps.deb.outputs.arm64 }}
- name: Publish amd64 deb to packages.element.io
uses: element-hq/packages.element.io@master # zizmor: ignore[unpinned-uses]
if: needs.prepare.outputs.deploy == 'true' && needs.linux.result == 'success'
with:
file: ${{ steps.deb.outputs.amd64 }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
bucket-api: ${{ vars.CF_R2_S3_API }}
bucket-key-id: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
bucket-access-key: ${{ secrets.CF_R2_TOKEN }}
- name: Publish arm64 deb to packages.element.io
uses: element-hq/packages.element.io@master # zizmor: ignore[unpinned-uses]
if: needs.prepare.outputs.deploy == 'true' && needs.linux.result == 'success'
with:
file: ${{ steps.deb.outputs.arm64 }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
bucket-api: ${{ vars.CF_R2_S3_API }}
bucket-key-id: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
bucket-access-key: ${{ secrets.CF_R2_TOKEN }}
deploy-ess:
needs: deploy
runs-on: ubuntu-24.04
name: Deploy builds to ESS
if: needs.prepare.outputs.deploy == 'true' && github.event_name == 'release'
env:
BUCKET_NAME: "element-desktop-msi.onprem.element.io"
AWS_REGION: "eu-central-1"
permissions:
id-token: write # This is required for requesting the JWT
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6
with:
role-to-assume: arn:aws:iam::264135176173:role/Push-ElementDesktop-MSI
role-session-name: githubaction-run-${{ github.run_id }}
aws-region: ${{ env.AWS_REGION }}
- name: Download artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: win-*
- name: Copy files to S3
run: |
set -x
PREFIX="${VERSION%.*}"
for file in win-*/*.msi; do
filename=$(basename "$file")
aws s3 cp "$file" "s3://${{ env.BUCKET_NAME }}/$PREFIX/$filename"
done
env:
VERSION: ${{ github.event.release.tag_name }}
-265
View File
@@ -1,265 +0,0 @@
# This workflow relies on actions/cache to store the hak dependency artifacts as they take a long time to build
# Due to this extra care must be taken to only ever run all build_* scripts against the same branch to ensure
# the correct cache scoping, and additional care must be taken to not run untrusted actions on the develop branch.
on:
workflow_call:
inputs:
ref:
type: string
required: false
description: "The git ref to checkout, defaults to the default branch"
arch:
type: string
required: true
description: "The architecture to build for, one of 'amd64' | 'arm64'"
version:
type: string
required: false
description: "Version string to override the one in package.json, used for non-release builds"
sqlcipher:
type: string
required: true
description: "How to link sqlcipher, one of 'system' | 'static'"
blob_report:
type: boolean
required: false
description: "Whether to run the blob report"
prepare-artifact-name:
type: string
required: false
description: |
The name of the prepare artifact to use, defaults to 'desktop-prepare'.
The artifact must contain the following:
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
+ electronVersion - the version of electron to use for cache keying
+ hakHash - the hash of the .hak directory to use for cache keying
+ changelog.Debian - the changelog file to embed in the Debian package
+ variant.json - the variant configuration to use for the build
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
for example icons in the `build/` directory to override the app icons.
default: "desktop-prepare"
test:
type: boolean
required: false
default: true
description: "Whether to run the test stage after building"
test-args:
type: string
required: false
description: "Additional arguments to pass to playwright"
runs-on:
type: string
required: false
description: "The runner image to use, normally set for you, may be needed for running in private repos."
artifact-prefix:
type: string
required: false
description: "An optional prefix to add to the artifact name, useful for distinguishing builds in private repos."
default: ""
targets:
type: string
required: false
description: "List of targets to build"
default: "tar.gz deb"
env:
SQLCIPHER_BUNDLED: ${{ inputs.sqlcipher == 'static' && '1' || '' }}
MAX_GLIBC: 2.31 # bullseye-era glibc, used by glibc-check.sh
permissions: {} # No permissions required
jobs:
build:
name: Build Linux ${{ inputs.arch }} SQLCipher ${{ inputs.sqlcipher }}
# We build on native infrastructure as matrix-seshat fails to cross-compile properly
# https://github.com/matrix-org/seshat/issues/135
runs-on: ${{ inputs.runs-on || (inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04') }}
env:
HAK_DOCKER_IMAGE: ghcr.io/element-hq/element-web/desktop-build-env:${{ case(github.event_name == 'push', inputs.ref || github.ref_name, github.event_name == 'release', 'staging', 'develop') }}
steps:
- uses: nbucic/variable-mapper@0673f6891a0619ba7c002ecfed0f9f4f39017b6f
id: config
with:
key: "${{ inputs.arch }}"
export_to: output
map: |
{
"amd64": {
"arch": "x86-64"
},
"arm64": {
"arch": "aarch64",
"build-args": "--arm64"
}
}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: element-hq/element-web
ref: ${{ inputs.ref }}
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: ${{ inputs.prepare-artifact-name }}
path: apps/desktop
- name: Cache .hak
id: cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
key: ${{ runner.os }}-${{ github.ref_name }}-${{ inputs.sqlcipher }}-${{ inputs.arch }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion', 'apps/desktop/dockerbuild/*') }}
path: |
apps/desktop/.hak
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: apps/desktop/.node-version
cache: "pnpm"
env:
# Workaround for https://github.com/actions/setup-node/issues/317
FORCE_COLOR: 0
- name: Install Deps
working-directory: apps/desktop
run: "pnpm install --frozen-lockfile --filter element-desktop"
- name: "Get modified files"
id: changed_files
if: steps.cache.outputs.cache-hit != 'true' && github.event_name == 'pull_request' && github.repository == 'element-hq/element-web'
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47
with:
files: |
apps/desktop/dockerbuild/**
# This allows contributors to test changes to the dockerbuild image within a pull request
- name: Build docker image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
if: steps.changed_files.outputs.any_modified == 'true'
with:
file: apps/desktop/dockerbuild/Dockerfile
context: apps/desktop
load: true
platforms: linux/${{ inputs.arch }}
tags: ${{ env.HAK_DOCKER_IMAGE }}
- name: Build Natives
if: steps.cache.outputs.cache-hit != 'true'
run: |
docker run \
-v ${{ github.workspace }}:/work -w /work \
-e SQLCIPHER_BUNDLED \
-e CI=1 \
$HAK_DOCKER_IMAGE \
pnpm -C apps/desktop run build:native
- name: Fix permissions
run: |
# For .hak
sudo chown -R $USER:$USER apps/desktop/.hak
# For node_modules pnpm strict security
sudo chmod +x node_modules/7zip-bin/linux/*/7za
- name: Check native libraries in hak dependencies
working-directory: apps/desktop
run: |
shopt -s globstar
for filename in ./.hak/hakModules/**/*.node; do
./scripts/glibc-check.sh $filename
done
- name: Generate debian files and arguments
working-directory: apps/desktop
run: |
if [ -f changelog.Debian ]; then
echo "ED_DEBIAN_CHANGELOG=changelog.Debian" >> $GITHUB_ENV
fi
- name: Build App
working-directory: apps/desktop
run: pnpm run build --publish never $BUILD_ARGS -l $TARGETS
env:
VARIANT_PATH: variant.json
# Only set for Nightly builds
VERSION: ${{ inputs.version }}
# Workaround for https://github.com/electron-userland/electron-builder/issues/5721
USE_HARD_LINKS: false
BUILD_ARGS: ${{ steps.config.outputs.build-args }}
TARGETS: ${{ inputs.targets }}
- name: Check native libraries
working-directory: apps/desktop
run: |
set -x
shopt -s globstar
FILES=$(file dist/**/*.node)
echo $FILES
! echo "$FILES" | grep -v "$ARCH"
LIBS=$(readelf -d dist/**/*.node | grep NEEDED)
echo "$LIBS"
set +x
assert_contains_string() { [[ "$1" == *"$2"* ]]; }
! assert_contains_string "$LIBS" "libcrypto.so.1.1"
if [ "$SQLCIPHER_BUNDLED" == "1" ]; then
! assert_contains_string "$LIBS" "libsqlcipher.so.0"
else
assert_contains_string "$LIBS" "libsqlcipher.so.0"
fi
./scripts/glibc-check.sh dist/linux-*unpacked/element-desktop*
env:
ARCH: ${{ steps.config.outputs.arch }}
# We exclude *-unpacked as it loses permissions and the tarball contains it with correct permissions
- name: Upload Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact-prefix }}linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
path: |
apps/desktop/dist
!apps/desktop/dist/*-unpacked/**
retention-days: 1
- name: Assert deb is present and valid
if: contains(inputs.targets, 'deb')
working-directory: apps/desktop
run: |
test -f ./dist/element-desktop*$ARCH.deb
DEB_LISTING=$(dpkg-deb --fsys-tarfile ./dist/element-desktop*.deb | tar -tv)
echo "deb listing: "
echo "$DEB_LISTING"
! echo "$DEB_LISTING" | grep '^h'
env:
ARCH: ${{ inputs.arch }}
- name: Assert tar.gz is present
if: contains(inputs.targets, 'tar.gz')
working-directory: apps/desktop
run: |
test -f ./dist/element-desktop*.tar.gz
TAR_GZ_LISTING=$(tar -tvf ./dist/element-desktop*.tar.gz)
echo "tar.gz listing: "
echo "$TAR_GZ_LISTING"
! echo "$TAR_GZ_LISTING" | grep '^h'
test:
name: Test Linux ${{ inputs.arch }} SQLCipher ${{ inputs.sqlcipher }}
needs: build
if: inputs.test && contains(inputs.targets, 'deb')
uses: ./.github/workflows/build_desktop_test.yaml
with:
project: linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
artifact: ${{ inputs.artifact-prefix }}linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }}
runs-on: ${{ inputs.runs-on || (inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04') }}
executable: /opt/Element*/element-desktop*
prepare_cmd: |
sudo apt-get -qq update
sudo apt install ./dist/*.deb
blob_report: ${{ inputs.blob_report }}
args: ${{ inputs.test-args }}
-234
View File
@@ -1,234 +0,0 @@
# This workflow relies on actions/cache to store the hak dependency artifacts as they take a long time to build
# Due to this extra care must be taken to only ever run all build_* scripts against the same branch to ensure
# the correct cache scoping, and additional care must be taken to not run untrusted actions on the develop branch.
on:
workflow_call:
secrets:
APPLE_ID:
required: false
APPLE_ID_PASSWORD:
required: false
APPLE_CSC_KEY_PASSWORD:
required: false
APPLE_CSC_LINK:
required: false
inputs:
ref:
type: string
required: false
description: "The git ref to checkout, defaults to the default branch"
version:
type: string
required: false
description: "Version string to override the one in package.json, used for non-release builds"
sign:
type: string
required: false
description: "Whether to sign & notarise the build, requires 'Desktop Apple' environment"
base-url:
type: string
required: false
description: "The URL to which the output will be deployed."
blob_report:
type: boolean
required: false
description: "Whether to run the blob report"
prepare-artifact-name:
type: string
required: false
description: |
The name of the prepare artifact to use, defaults to 'desktop-prepare'.
The artifact must contain the following:
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
+ electronVersion - the version of electron to use for cache keying
+ hakHash - the hash of the .hak directory to use for cache keying
+ variant.json - the variant configuration to use for the build
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
for example icons in the `build/` directory to override the app icons.
default: "desktop-prepare"
test:
type: boolean
required: false
default: true
description: "Whether to run the test stage after building"
test-args:
type: string
required: false
description: "Additional arguments to pass to playwright"
artifact-prefix:
type: string
required: false
description: "An optional prefix to add to the artifact name, useful for distinguishing builds in private repos."
default: ""
targets:
type: string
required: false
description: "List of targets to build"
default: "dmg zip"
permissions: {} # No permissions required
jobs:
build:
name: Build macOS Universal
runs-on: macos-15 # M1
environment: ${{ inputs.sign && 'Desktop Apple' || '' }}
steps:
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
with:
xcode-version: latest-stable
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: element-hq/element-web
ref: ${{ inputs.ref }}
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: ${{ inputs.prepare-artifact-name }}
path: apps/desktop
- name: Cache .hak
id: cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
key: ${{ runner.os }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }}
path: |
apps/desktop/.hak
- name: Install Rust
if: steps.cache.outputs.cache-hit != 'true'
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
# M1 macos-14 comes without Python preinstalled
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.13"
# Install Quartz for DMG badges
# https://github.com/electron-userland/electron-builder/issues/9511#issuecomment-3774092888
- run: sudo pip3 install pyobjc-framework-Quartz
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: apps/desktop/.node-version
cache: "pnpm"
- name: Install Deps
working-directory: apps/desktop
run: "pnpm install --frozen-lockfile --filter element-desktop"
- name: Build Natives
if: steps.cache.outputs.cache-hit != 'true'
working-directory: apps/desktop
run: pnpm run build:native:universal
# We split these because electron-builder gets upset if we set CSC_LINK even to an empty string
- name: "[Signed] Build App"
if: inputs.sign != ''
working-directory: apps/desktop
run: |
pnpm run build:universal --publish never -m ${TARGETS}
env:
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
VARIANT_PATH: variant.json
# Only set for Nightly builds
VERSION: ${{ inputs.version }}
TARGETS: ${{ inputs.targets }}
- name: Check app was signed & notarised successfully
if: inputs.sign != ''
working-directory: apps/desktop
run: |
hdiutil attach dist/*.dmg -mountpoint /Volumes/Element
codesign -dv --verbose=4 /Volumes/Element/*.app
spctl -a -vvv -t install /Volumes/Element/*.app
hdiutil detach /Volumes/Element
- name: "[Unsigned] Build App"
if: inputs.sign == ''
working-directory: apps/desktop
run: |
pnpm run build:universal --publish never -m ${TARGETS}
env:
CSC_IDENTITY_AUTO_DISCOVERY: false
VARIANT_PATH: variant.json
TARGETS: ${{ inputs.targets }}
- name: Generate releases.json
if: inputs.base-url
working-directory: apps/desktop
run: |
PKG_JSON_VERSION=$(cat package.json | jq -r .version)
LATEST=$(find dist -type f -iname "*-mac.zip" | xargs -0 -n1 -- basename)
# Encode spaces in the URL as Squirrel.Mac complains about bad JSON otherwise
URL="${BASE_URL}/update/macos/${LATEST// /%20}"
jq -n --arg version "${VERSION:-$PKG_JSON_VERSION}" --arg url "$URL" '
{
currentRelease: $version,
releases: [{
version: $version,
updateTo: {
version: $version,
url: $url,
},
}],
}
' > dist/releases.json
jq -n --arg url "$URL" '
{ url: $url }
' > dist/releases-legacy.json
env:
VERSION: ${{ inputs.version }}
BASE_URL: ${{ inputs.base-url }}
# We exclude mac-universal as the unpacked app takes forever to upload and zip and dmg already contains it
- name: Upload Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact-prefix }}macos
path: |
apps/desktop/dist
!apps/desktop/dist/mac-universal/**
retention-days: 1
- name: Assert zip is present
if: contains(inputs.targets, 'zip')
working-directory: apps/desktop
run: |
test -f ./dist/Element*-mac.zip
- name: Assert dmg is present
if: contains(inputs.targets, 'dmg')
working-directory: apps/desktop
run: |
test -f ./dist/Element*.dmg
test:
name: Test macOS Universal
needs: build
if: inputs.test && contains(inputs.targets, 'dmg')
uses: ./.github/workflows/build_desktop_test.yaml
with:
project: macos
artifact: ${{ inputs.artifact-prefix }}macos
runs-on: macos-14
executable: /Users/runner/Applications/Element*.app/Contents/MacOS/Element*
# We need to mount the DMG and copy the app to the Applications folder as a mounted DMG is
# read-only and thus would not allow us to override the fuses as is required for Playwright.
prepare_cmd: |
hdiutil attach ./dist/*.dmg -mountpoint /Volumes/Element &&
rsync -a /Volumes/Element/Element*.app ~/Applications/ &&
hdiutil detach /Volumes/Element
blob_report: ${{ inputs.blob_report }}
args: ${{ inputs.test-args }}
@@ -1,197 +0,0 @@
# This action helps perform common actions before the build_* actions are started in parallel.
on:
workflow_call:
inputs:
config:
type: string
required: true
description: "The config directory to use"
version:
type: string
required: false
description: "The version tag to fetch, or 'develop', will pick automatically if not passed"
nightly:
type: boolean
required: false
default: false
description: "Whether the build is a Nightly and to calculate the version strings new builds should use"
deploy:
type: boolean
required: false
default: false
description: "Whether the build should be deployed to production"
webapp-artifact:
type: string
required: false
description: "Name of the webapp artifact that should be used, will fetch a relevant build if omitted"
secrets:
# Required if `nightly` is set
CF_R2_ACCESS_KEY_ID:
required: false
# Required if `nightly` is set
CF_R2_TOKEN:
required: false
outputs:
nightly-version:
description: "The version string the next Nightly should use, only output for nightly"
value: ${{ jobs.prepare.outputs.nightly-version }}
packages-dir:
description: "The directory non-deb packages for this run should live in within packages.element.io"
value: ${{ inputs.nightly && 'nightly' || 'desktop' }}
# This is just a simple pass-through of the input to simplify reuse of complex inline conditions
deploy:
description: "Whether the build should be deployed to production"
value: ${{ inputs.deploy }}
permissions: {}
jobs:
prepare:
name: Prepare
environment: ${{ inputs.nightly && 'packages.element.io' || '' }}
runs-on: ubuntu-24.04
permissions:
contents: read
outputs:
nightly-version: ${{ steps.versions.outputs.nightly }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
repository: element-hq/element-web
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: apps/desktop/.node-version
cache: "pnpm"
- name: Install Deps
working-directory: apps/desktop
run: "pnpm install --frozen-lockfile --filter element-desktop"
- name: Fetch Element Web (from artifact)
if: inputs.webapp-artifact != ''
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: ${{ inputs.webapp-artifact }}
path: apps/desktop/webapp
- name: Build webapp.asar (from artifact)
if: inputs.webapp-artifact != ''
working-directory: apps/desktop
run: |
cp -f "$CONFIG_DIR/config.json" webapp/config.json
pnpm run asar-webapp
env:
CONFIG_DIR: ${{ inputs.config }}
- name: Fetch Element Web (${{ inputs.version }})
if: inputs.webapp-artifact == ''
working-directory: apps/desktop
run: pnpm run fetch --noverify -d ${CONFIG} ${VERSION}
env:
CONFIG: ${{ inputs.config }}
VERSION: ${{ inputs.version }}
- name: Copy variant config
working-directory: apps/desktop
run: cp "$CONFIG_DIR/build.json" variant.json
env:
CONFIG_DIR: ${{ inputs.config }}
# We split this out to save the build_* scripts having to do it to make use of `hashFiles` in the cache action
- name: Generate cache hash files
working-directory: apps/desktop
run: |
set -ex
# Add --no-sandbox as otherwise it fails because the helper isn't setuid root. It's only getting the version.
pnpm --silent electron --no-sandbox --version > electronVersion
cat package.json | jq -c .hakDependencies | sha1sum > hakHash
find hak -type f -print0 | xargs -0 sha1sum >> hakHash
find scripts/hak -type f -print0 | xargs -0 sha1sum >> hakHash
- name: "[Nightly] Calculate version"
id: versions
if: inputs.nightly
working-directory: apps/desktop
run: |
set -e
# Find all latest Nightly versions
aws s3 cp s3://$R2_BUCKET/nightly/update/macos/releases.json - --endpoint-url $R2_URL --region auto | jq -r .currentRelease >> VERSIONS
aws s3 cp s3://$R2_BUCKET/debian/dists/default/main/binary-amd64/Packages - --endpoint-url $R2_URL --region auto | grep "Package: element-nightly" -A 50 | grep Version -m1 | sed -n 's/Version: //p' >> VERSIONS
aws s3 cp s3://$R2_BUCKET/debian/dists/default/main/binary-arm64/Packages - --endpoint-url $R2_URL --region auto | grep "Package: element-nightly" -A 50 | grep Version -m1 | sed -n 's/Version: //p' >> VERSIONS
aws s3 cp s3://$R2_BUCKET/nightly/update/win32/x64/RELEASES - --endpoint-url $R2_URL --region auto | awk '{print $2}' | cut -d "-" -f 5 | cut -c 8- >> VERSIONS
aws s3 cp s3://$R2_BUCKET/nightly/update/win32/arm64/RELEASES - --endpoint-url $R2_URL --region auto | awk '{print $2}' | cut -d "-" -f 5 | cut -c 8- >> VERSIONS
# Pick the greatest one
VERSION=$(cat VERSIONS | sort -uf | tail -n1)
echo "Found latest nightly version $VERSION"
# Increment it
echo "nightly=$(scripts/generate-nightly-version.ts --latest $VERSION)" >> $GITHUB_OUTPUT
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
R2_BUCKET: ${{ vars.R2_BUCKET }}
R2_URL: ${{ vars.CF_R2_S3_API }}
- name: Check version
id: package
working-directory: apps/desktop
run: |
echo "version=$(cat package.json | jq -r .version)" >> $GITHUB_OUTPUT
- name: "[Release] Fetch release"
id: release
if: ${{ !inputs.nightly && inputs.version != 'develop' }}
uses: cardinalby/git-get-release-action@5172c3a026600b1d459b117738c605fabc9e4e44 # 1.2.5
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tag: v${{ steps.package.outputs.version }}
- name: "[Release] Write changelog"
if: ${{ !inputs.nightly && inputs.version != 'develop' }}
working-directory: apps/desktop
run: |
TIME=$(date -d "$PUBLISHED_AT" -R)
echo "element-desktop ($VERSION) default; urgency=medium" >> changelog.Debian
echo "$BODY" | sed 's/^##/\n */g;s/^\*/ */g' | perl -pe 's/\[.+?]\((.+?)\)/\1/g' >> changelog.Debian
echo "" >> changelog.Debian
echo " -- $ACTOR <support@element.io> $TIME" >> changelog.Debian
env:
ACTOR: ${{ github.actor }}
VERSION: v${{ steps.package.outputs.version }}
BODY: ${{ steps.release.outputs.body }}
PUBLISHED_AT: ${{ steps.release.outputs.published_at }}
- name: "[Nightly] Write summary"
if: inputs.nightly
working-directory: apps/desktop
run: |
set -e
BUNDLE_HASH=$(npx asar l webapp.asar | grep /bundles/ | head -n 1 | sed 's|.*/||')
WEBAPP_VERSION=$(./scripts/get-version.ts)
WEB_VERSION=${WEBAPP_VERSION:0:12}
JS_VERSION=${WEBAPP_VERSION:16:12}
echo "### Nightly build ${NIGHTLY_VERSION}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Component | Version |" >> $GITHUB_STEP_SUMMARY
echo "| ----------- | ------- |" >> $GITHUB_STEP_SUMMARY
echo "| Bundle Hash | $BUNDLE_HASH |" >> $GITHUB_STEP_SUMMARY
echo "| Element Web | [$WEB_VERSION](https://github.com/element-hq/element-web/commit/$WEB_VERSION) |" >> $GITHUB_STEP_SUMMARY
echo "| JS SDK | [$JS_VERSION](https://github.com/matrix-org/matrix-js-sdk/commit/$JS_VERSION) |" >> $GITHUB_STEP_SUMMARY
env:
NIGHTLY_VERSION: ${{ steps.versions.outputs.nightly }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: desktop-prepare
retention-days: 1
path: |
apps/desktop/webapp.asar
apps/desktop/electronVersion
apps/desktop/hakHash
apps/desktop/changelog.Debian
apps/desktop/variant.json
-118
View File
@@ -1,118 +0,0 @@
# This action helps run Playwright tests within one of the build_* stages.
on:
workflow_call:
inputs:
runs-on:
type: string
required: true
description: "The runner image to use"
artifact:
type: string
required: true
description: "The name of the artifact to download"
project:
type: string
required: true
description: "The Playwright project to use for testing"
executable:
type: string
required: true
description: "Path to the executable to test"
prepare_cmd:
type: string
required: false
description: "Command to run to prepare the executable or environment for testing"
blob_report:
type: boolean
default: false
description: "Whether to upload a blob report instead of the HTML report"
args:
type: string
required: false
description: "Additional arguments to pass to playwright, for e.g. skipping specific tests"
permissions: {}
jobs:
test:
name: Test ${{ inputs.project }}
runs-on: ${{ inputs.runs-on }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: element-hq/element-web
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: apps/desktop/.node-version
cache: "pnpm"
- name: Install Deps
run: "pnpm install --frozen-lockfile --filter element-desktop"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: ${{ inputs.artifact }}
path: apps/desktop/dist
- name: Prepare for tests
working-directory: apps/desktop
# This is set by the caller of the reusable workflow, they have the ability to run the command they specify
# directly without our help so this is fine.
run: ${{ inputs.prepare_cmd }} # zizmor: ignore[template-injection]
if: inputs.prepare_cmd
- name: Expand executable path
id: executable
working-directory: apps/desktop
shell: bash
env:
EXECUTABLE: ${{ inputs.executable }}
run: |
FILES=($EXECUTABLE)
echo "path=${FILES[0]}" >> $GITHUB_OUTPUT
# We previously disabled the `EnableNodeCliInspectArguments` fuse, but Playwright requires
# it to be enabled to test Electron apps, so turn it back on.
- name: Set EnableNodeCliInspectArguments fuse enabled
run: $RUN_AS npx @electron/fuses write --app "$EXECUTABLE" EnableNodeCliInspectArguments=on
working-directory: apps/desktop
shell: bash
env:
# We need sudo on Linux as it is installed in /opt/
RUN_AS: ${{ runner.os == 'Linux' && 'sudo' || '' }}
EXECUTABLE: ${{ steps.executable.outputs.path }}
- name: Run tests
timeout-minutes: 20
shell: bash
working-directory: apps/desktop
run: |
$PREFIX pnpm playwright test \
${{ runner.os != 'Linux' && '--ignore-snapshots' || '' }} \
${{ inputs.blob_report == false && '--reporter=html' || '' }} \
$ARGS
env:
PREFIX: ${{ runner.os == 'Linux' && 'xvfb-run' || '' }}
PW_TAG: ${{ inputs.project }}
ELEMENT_DESKTOP_EXECUTABLE: ${{ steps.executable.outputs.path }}
ARGS: ${{ inputs.args }}
DEBUG: pw:browser
- name: Upload blob report
if: always() && inputs.blob_report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: blob-report-${{ inputs.artifact }}
path: apps/desktop/blob-report
retention-days: 1
if-no-files-found: error
- name: Upload HTML report
if: always() && inputs.blob_report == false
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact }}-test
path: apps/desktop/playwright-report
retention-days: 14
if-no-files-found: error
@@ -1,314 +0,0 @@
# This workflow relies on actions/cache to store the hak dependency artifacts as they take a long time to build
# Due to this extra care must be taken to only ever run all build_* scripts against the same branch to ensure
# the correct cache scoping, and additional care must be taken to not run untrusted actions on the develop branch.
# Windows GHA runner by default uses the pwsh shell which breaks codeSigningCert in the workflow
# We always sign using eSignerCKA to ensure it keeps working, but aside from release & nightlies we use demo credentials
# which do not yield trusted signatures.
defaults:
run:
shell: powershell
on:
workflow_call:
secrets:
ESIGNER_USER_NAME:
required: false
ESIGNER_USER_PASSWORD:
required: false
ESIGNER_USER_TOTP:
required: false
inputs:
ref:
type: string
required: false
description: "The git ref to checkout, defaults to the default branch"
arch:
type: string
required: true
description: "The architecture to build for, one of 'x64' | 'ia32' | 'arm64'"
version:
type: string
required: false
description: "Version string to override the one in package.json, used for non-release builds"
sign:
type: string
required: false
description: "Whether to sign & notarise the build, requires 'Desktop eSigner' environment"
blob_report:
type: boolean
required: false
description: "Whether to run the blob report"
prepare-artifact-name:
type: string
required: false
description: |
The name of the prepare artifact to use, defaults to 'desktop-prepare'.
The artifact must contain the following:
+ webapp.asar - the asar archive of the webapp to embed in the desktop app
+ electronVersion - the version of electron to use for cache keying
+ hakHash - the hash of the .hak directory to use for cache keying
+ variant.json - the variant configuration to use for the build
The artifact can also contain any additional files which will be applied as overrides to the checkout root before building,
for example icons in the `build/` directory to override the app icons.
default: "desktop-prepare"
test:
type: boolean
required: false
default: true
description: "Whether to run the test stage after building"
test-runs-on:
type: string
required: false
description: "The runner image to use for testing, normally set for you, may be needed for running in private repos."
test-args:
type: string
required: false
description: "Additional arguments to pass to playwright"
artifact-prefix:
type: string
required: false
description: "An optional prefix to add to the artifact name, useful for distinguishing builds in private repos."
default: ""
targets:
type: string
required: false
description: "List of targets to build"
default: "squirrel msi"
permissions: {} # No permissions required
jobs:
build:
name: Build Windows ${{ inputs.arch }}
runs-on: windows-2025
environment: ${{ inputs.sign && 'Desktop eSigner' || '' }}
env:
SIGNTOOL_PATH: "C:/Program Files (x86)/Windows Kits/10/bin/10.0.26100.0/x86/signtool.exe"
steps:
- uses: nbucic/variable-mapper@0673f6891a0619ba7c002ecfed0f9f4f39017b6f
id: config
with:
key: "${{ inputs.arch }}"
export_to: output
map: |
{
"x64": {
"target": "x86_64-pc-windows-msvc"
},
"arm64": {
"target": "aarch64-pc-windows-msvc",
"build-args": "--arm64",
"arch": "amd64_arm64"
},
"ia32": {
"target": "i686-pc-windows-msvc",
"build-args": "--ia32",
"arch": "x86",
"extra_config": "{\"user_notice\": {\"title\": \"Your desktop environment is unsupported.\",\"description\": \"Support for 32-bit Windows installations has ended. Transition to the web or mobile app for continued access.\"}}"
}
}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: element-hq/element-web
ref: ${{ inputs.ref }}
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: ${{ inputs.prepare-artifact-name }}
path: apps/desktop/
- name: Cache .hak
id: cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
key: ${{ runner.os }}-${{ inputs.arch }}-${{ hashFiles('apps/desktop/hakHash', 'apps/desktop/electronVersion') }}
path: |
apps/desktop/.hak
# ActiveTCL package on choco is from 2015,
# this one is newer but includes more than we need
- name: Choco install tclsh
if: steps.cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
choco install -y magicsplat-tcl-tk --no-progress
echo "${HOME}/AppData/Local/Apps/Tcl86/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Choco install NetWide Assembler
if: steps.cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
choco install -y nasm --no-progress
echo "C:/Program Files/NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install Rust
if: steps.cache.outputs.cache-hit != 'true'
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable
rustup target add $env:TARGET
env:
TARGET: ${{ steps.config.outputs.target }}
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: apps/desktop/.node-version
cache: "pnpm"
- name: Install Deps
working-directory: apps/desktop
run: "pnpm install --frozen-lockfile --filter element-desktop"
- name: Insert config snippet
if: steps.config.outputs.extra_config != ''
working-directory: apps/desktop
shell: bash
run: |
mkdir config-edit
pnpm asar extract webapp.asar config-edit
cd config-edit
mv config.json old-config.json
echo '${{ steps.config.outputs.extra_config }}' | jq -s '.[0] * .[1]' old-config.json - > config.json
rm old-config.json
cd ..
rm webapp.asar
pnpm asar pack config-edit/ webapp.asar
- name: Set up sqlcipher macros
if: steps.cache.outputs.cache-hit != 'true' && contains(inputs.arch, 'arm')
shell: pwsh
run: |
echo "NCC=${{ github.workspace }}\scripts\cl.bat" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Set up build tools
if: steps.cache.outputs.cache-hit != 'true'
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
with:
arch: ${{ steps.config.outputs.arch || inputs.arch }}
- name: Build Natives
if: steps.cache.outputs.cache-hit != 'true'
working-directory: apps/desktop
run: |
refreshenv
pnpm run build:native --target $env:TARGET
env:
TARGET: ${{ steps.config.outputs.target }}
- name: Install and configure eSigner CKA
run: |
Set-StrictMode -Version 'Latest'
# Download, extract, and rename
Invoke-WebRequest -OutFile eSigner_CKA.zip "$env:ESIGNER_URL"
Expand-Archive -Path eSigner_CKA.zip -DestinationPath .
Get-ChildItem -Path * -Include "*_build_*.exe" | Rename-Item -NewName eSigner_CKA.exe
# Install
New-Item -ItemType Directory -Force -Path "$env:INSTALL_DIR"
./eSigner_CKA.exe /CURRENTUSER /VERYSILENT /SUPPRESSMSGBOXES /DIR="${{ env.INSTALL_DIR }}" | Out-Null
# Disable logger
$LogConfig = Get-Content -Path ${{ env.INSTALL_DIR }}/log4net.config
$LogConfig[0] = '<log4net threshold="OFF">'
$LogConfig | Set-Content -Path ${{ env.INSTALL_DIR }}/log4net.config
# Configure - default credentials from https://www.ssl.com/guide/esigner-demo-credentials-and-certificates/
${{ env.INSTALL_DIR }}/eSignerCKATool.exe config `
-mode "$env:ESIGNER_MODE" `
-user "${{ secrets.ESIGNER_USER_NAME || 'esigner_demo' }}" `
-pass "${{ secrets.ESIGNER_USER_PASSWORD || 'esignerDemo#1' }}" `
-totp "${{ secrets.ESIGNER_USER_TOTP || 'RDXYgV9qju+6/7GnMf1vCbKexXVJmUVr+86Wq/8aIGg=' }}" `
-key "${{ env.MASTER_KEY_FILE }}" -r
${{ env.INSTALL_DIR }}/eSignerCKATool.exe unload
${{ env.INSTALL_DIR }}/eSignerCKATool.exe load
# Find certificate
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
echo Certificate: $CodeSigningCert
# Extract thumbprint and subject name
$Thumbprint = $CodeSigningCert.Thumbprint
$SubjectName = ($CodeSigningCert.Subject -replace ", ?", "`n" | ConvertFrom-StringData).CN
echo "ED_SIGNTOOL_THUMBPRINT=$Thumbprint" >> $env:GITHUB_ENV
echo "ED_SIGNTOOL_SUBJECT_NAME=$SubjectName" >> $env:GITHUB_ENV
env:
ESIGNER_MODE: ${{ vars.ESIGNER_MODE || 'sandbox' }}
ESIGNER_URL: https://github.com/SSLcom/eSignerCKA/releases/download/v1.0.6/SSL.COM-eSigner-CKA_1.0.6.zip
INSTALL_DIR: C:\Users\runneradmin\eSignerCKA
MASTER_KEY_FILE: C:\Users\runneradmin\eSignerCKA\master.key
- name: Build App
working-directory: apps/desktop
run: pnpm run build --publish never $BUILD_ARGS -w $TARGETS
shell: bash
env:
VARIANT_PATH: variant.json
# Only set for Nightly builds
# The windows packager relies on parsing this as semver, so we have to make it look like one.
# This will give our update packages really stupid names, but we probably can't change that either
# because squirrel windows parses them for the version too. We don't really care: nobody sees them.
# We just give the installer a static name, so you'll just see this in the 'about' dialog.
# Turns out if you use 0.0.0 here it makes Squirrel windows crash, so we use 0.0.1.
VERSION: ${{ inputs.version && format('0.0.1-nightly.{0}', inputs.version) || '' }}
BUILD_ARGS: ${{ steps.config.outputs.build-args }}
TARGETS: ${{ inputs.targets }}
- name: Trust eSigner sandbox cert
if: inputs.sign == ''
run: |
Set-StrictMode -Version 'Latest'
Import-Certificate -CertStoreLocation Cert:\LocalMachine\Root -FilePath .github/SSLcom-sandbox.crt
- name: Check app was signed successfully
working-directory: apps/desktop
run: |
Set-StrictMode -Version 'Latest'
Get-ChildItem `
-Recurse dist `
-Include *.exe, *.msi `
| ForEach-Object -Process {. $env:SIGNTOOL_PATH verify /pa $_.FullName; if(!$?) { throw }}
- name: Upload Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact-prefix }}win-${{ inputs.arch }}
path: |
apps/desktop/dist
retention-days: 1
- name: Assert executable is present
working-directory: apps/desktop
run: |
Test-Path './dist/win-*unpacked/Element*.exe'
- name: Assert all Squirrel files are present
if: contains(inputs.targets, 'squirrel')
working-directory: apps/desktop
run: |
Test-Path './dist/squirrel-windows*/Element Setup*.exe'
Test-Path './dist/squirrel-windows*/element-desktop-*-full.nupkg'
Test-Path './dist/squirrel-windows*/RELEASES'
- name: Assert MSI is present
if: contains(inputs.targets, 'msi')
working-directory: apps/desktop
run: |
Test-Path './dist/Element*.msi'
test:
name: Test Windows ${{ inputs.arch }}
needs: build
if: inputs.test
uses: ./.github/workflows/build_desktop_test.yaml
with:
project: win-${{ inputs.arch }}
artifact: ${{ inputs.artifact-prefix }}win-${{ inputs.arch }}
runs-on: ${{ inputs.test-runs-on || (inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2022') }}
executable: ./dist/win*-unpacked/Element*.exe
blob_report: ${{ inputs.blob_report }}
args: ${{ inputs.test-args }}
-141
View File
@@ -1,141 +0,0 @@
# Separate to the main build workflow for access to develop
# environment secrets, largely similar to build.yaml.
name: Build and Deploy develop
on:
push:
branches: [develop]
repository_dispatch:
types: [element-web-notify]
concurrency:
group: ${{ github.repository_owner }}-${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true
env:
NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes
permissions: {}
jobs:
build:
name: "Build & Deploy develop.element.io"
# Only respect triggers from our develop branch, ignore that of forks
if: github.repository == 'element-hq/element-web'
runs-on: ubuntu-24.04
environment: develop
permissions:
checks: read
pages: write
deployments: write
env:
R2_BUCKET: "element-web-develop"
R2_URL: ${{ vars.CF_R2_S3_API }}
R2_PUBLIC_URL: "https://element-web-develop.element.io"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
node-version: "lts/*"
- name: Install Dependencies
run: "./scripts/layered.sh"
- name: Build, Package & Upload sourcemaps
working-directory: apps/web
run: "./scripts/ci_package.sh"
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_URL: ${{ secrets.SENTRY_URL }}
SENTRY_ORG: element
SENTRY_PROJECT: riot-web
# We only deploy the latest bundles to Cloudflare Pages and use _redirects to fallback to R2 for
# older ones. This redirect means that 'self' is insufficient in the CSP,
# and we have to add the R2 URL.
# Once Cloudflare redirects support proxying mode we will be able to ditch this.
# See Proxying in support table at https://developers.cloudflare.com/pages/platform/redirects
CSP_EXTRA_SOURCE: ${{ env.R2_PUBLIC_URL }}
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
working-directory: apps/web
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: webapp
path: apps/web/dist/develop.tar.gz
retention-days: 1
- name: Extract webapp
run: |
mkdir _deploy
tar xf apps/web/dist/develop.tar.gz -C _deploy --strip-components=1
- name: Copy config
run: cp apps/web/element.io/develop/config.json _deploy/config.json
- name: Populate 404.html
run: echo "404 Not Found" > _deploy/404.html
- name: Populate _headers
run: cp .github/cfp_headers _deploy/_headers
# Redirect requests for the develop tarball and the historical bundles to R2
# We find the latest 100 bundle.css files and add their bundles to the redirects file
# S3 has no sane way to get the age of a directory as they don't really exist
- name: Populate _redirects
run: |
{
echo "/develop.tar.gz $R2_PUBLIC_URL/develop.tar.gz 301"
aws s3api --region auto --endpoint-url $R2_URL list-objects-v2 --bucket $R2_BUCKET \
--query "sort_by(Contents[?ends_with(Key, '/bundle.css')], &LastModified)[-100:].Key" \
--prefix "bundles/" | jq -r '.[]' | grep -oE '[^\"].*\/\s*' | while read -r path ; do
echo "/${path}* $R2_PUBLIC_URL/${path}:splat 301"
done
} | tee _deploy/_redirects
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
# We may be trying to deploy the same webapp bundles again, we need to ensure that the live bundles
# are not present in the _redirects file and instead accessed directly from Cloudflare Pages.
- name: Trim _redirects
working-directory: _deploy
run: |
find bundles -type d -mindepth 1 -maxdepth 1 -exec sed -i "\:{}:d" _redirects \;
- name: Wait for other steps to succeed
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: ${{ github.sha }}
running-workflow-name: "Build & Deploy develop.element.io"
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages|Upload|Netlify|Report).)*$
# We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier
# as the expires after 24h and requires auth to download.
# Element Desktop's fetch script uses this tarball to fetch latest develop to build Nightlies.
# Checksum algorithm specified as per https://developers.cloudflare.com/r2/examples/aws/aws-cli/
- name: Deploy to R2
run: |
aws s3 cp apps/web/dist/develop.tar.gz s3://$R2_BUCKET/develop.tar.gz --endpoint-url $R2_URL --region=auto --checksum-algorithm CRC32
aws s3 cp _deploy/ s3://$R2_BUCKET/ --recursive --endpoint-url $R2_URL --region=auto --checksum-algorithm CRC32
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
- name: Deploy to Cloudflare Pages
id: cfp
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1
with:
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}
projectName: element-web-develop
directory: _deploy
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
- run: |
echo "Deployed to ${STEPS_CFP_OUTPUTS_URL}" >> $GITHUB_STEP_SUMMARY
env:
STEPS_CFP_OUTPUTS_URL: ${{ steps.cfp.outputs.url }}
-75
View File
@@ -1,75 +0,0 @@
name: CD # Continuous Delivery
on:
push:
branches: [master, staging, develop]
paths:
- "**/Dockerfile"
- "**/dockerbuild"
- "**/docker"
- "**/docker-*"
- "pnpm-lock.yaml"
concurrency: ${{ github.workflow }}-${{ github.ref_name }}
permissions: {}
env:
NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes
jobs:
docker:
name: Docker Bake
runs-on: ubuntu-24.04
permissions:
id-token: write # needed for signing the images with GitHub OIDC Token
packages: write # needed for publishing packages to GHCR
# Needed for nx-set-shas
contents: read
actions: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
- name: Set up Docker Buildx
id: builder
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: package.json
cache: "pnpm"
- name: Install Deps
run: "pnpm install --frozen-lockfile"
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: pnpm nx run-many --nxBail -t docker:build
id: build
env:
INPUT_PUSH: true
INPUT_LOAD: false
INPUT_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INPUT_BUILDER: ${{ steps.builder.outputs.name }}
- name: Sign the images with GitHub OIDC token
run: |
shopt -s globstar
for FILE in ./node_modules/.cache/nx-container/**/metadata; do
TARGET=$(jq -r '(.["image.name"] | split(",") | last) + "@" + .["containerimage.digest"]' "$FILE")
echo "Signing $TARGET..."
cosign sign --yes "$TARGET"
done
-101
View File
@@ -1,101 +0,0 @@
# Manual deploy workflow for deploying to app.element.io & staging.element.io
# Runs automatically for staging.element.io when an RC or Release is published
# Note: Does *NOT* run automatically for app.element.io so that it gets tested on staging.element.io beforehand
name: Deploy release
run-name: Deploy ${{ github.ref_name }} to ${{ inputs.site || 'staging.element.io' }}
on:
release:
types: [published]
workflow_dispatch:
inputs:
site:
description: Which site to deploy to
required: true
default: staging.element.io
type: choice
options:
- staging.element.io
- app.element.io
skip-checks:
description: Skip CI on the tagged commit
required: true
default: false
type: boolean
concurrency: ${{ inputs.site || 'staging.element.io' }}
permissions: {}
jobs:
deploy:
name: "Deploy to Cloudflare Pages"
runs-on: ubuntu-24.04
environment: ${{ inputs.site || 'staging.element.io' }}
permissions:
checks: read
deployments: write
env:
SITE: ${{ inputs.site || 'staging.element.io' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Load GPG key
run: |
curl https://packages.element.io/element-release-key.gpg | gpg --import
gpg -k "$GPG_FINGERPRINT"
env:
GPG_FINGERPRINT: ${{ vars.GPG_FINGERPRINT }}
- name: Check current version on deployment
id: current_version
run: |
version=$(curl -s https://$SITE/version)
echo "version=${version#v}" >> $GITHUB_OUTPUT
# The current version bundle melding dance is skipped if the version we're deploying is the same
# as then we're just doing a re-deploy of the same version with potentially different configs.
- name: Download current version for its old bundles
id: current_download
if: steps.current_version.outputs.version != github.ref_name
uses: ./.github/actions/download-verify-element-tarball
with:
tag: v${{ steps.current_version.outputs.version }}
out-file-path: _current_version
- name: Download target version
uses: ./.github/actions/download-verify-element-tarball
with:
tag: ${{ github.ref_name }}
out-file-path: _deploy
- name: Merge current bundles into target
if: steps.current_download.outcome == 'success'
run: cp -vnpr _current_version/bundles/* _deploy/bundles/
- name: Copy config
run: cp apps/web/element.io/app/config.json _deploy/config.json
- name: Populate 404.html
run: echo "404 Not Found" > _deploy/404.html
- name: Populate _headers
run: cp .github/cfp_headers _deploy/_headers
- name: Wait for other steps to succeed
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
if: inputs.skip-checks != true
with:
ref: ${{ github.sha }}
running-workflow-name: "Deploy to Cloudflare Pages"
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages).)*$
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1
with:
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}
projectName: ${{ env.SITE == 'staging.element.io' && 'element-web-staging' || 'element-web' }}
directory: _deploy
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: main
-181
View File
@@ -1,181 +0,0 @@
name: Docker
on:
workflow_dispatch: {}
push:
tags: [v*]
pull_request: {}
schedule:
# This job can take a while, and we have usage limits, so just publish develop only twice a day
- cron: "0 7/12 * * *"
concurrency: ${{ github.workflow }}-${{ github.ref_name }}
permissions: {}
jobs:
buildx:
name: Docker Buildx
runs-on: ubuntu-24.04
environment: ${{ github.event_name != 'pull_request' && 'dockerhub' || '' }}
permissions:
id-token: write # needed for signing the images with GitHub OIDC Token
packages: write # needed for publishing packages to GHCR
env:
TEST_TAG: vectorim/element-web:test
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0 # needed for docker-package to be able to calculate the version
persist-credentials: false
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
if: github.event_name != 'pull_request'
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
with:
install: true
- name: Build and load
id: test-build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
with:
context: .
file: apps/web/Dockerfile
load: true
- name: Test the image
env:
IMAGEID: ${{ steps.test-build.outputs.imageid }}
timeout-minutes: 2
run: |
set -x
# Make a fake module to test the image
MODULE_PATH="modules/module_name/index.js"
mkdir -p $(dirname $MODULE_PATH)
echo 'alert("Testing");' > $MODULE_PATH
# Spin up a container of the image
ELEMENT_WEB_PORT=8181
CONTAINER_ID=$(
docker run \
--rm \
-e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
-dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
-v $(pwd)/modules:/modules \
"$IMAGEID" \
)
# Run some smoke tests
wget --retry-connrefused --tries=5 -q --wait=3 --spider "http://localhost:$ELEMENT_WEB_PORT/modules/module_name/index.js"
MODULE_0=$(curl "http://localhost:$ELEMENT_WEB_PORT/config.json" | jq -r .modules[0])
test "$MODULE_0" = "/${MODULE_PATH}"
# Check healthcheck
until test "$(docker inspect -f {{.State.Health.Status}} $CONTAINER_ID)" == "healthy"; do
sleep 1
done
# Clean up
docker stop "$CONTAINER_ID"
- name: Docker meta
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
if: github.event_name != 'pull_request'
with:
images: |
vectorim/element-web
ghcr.io/element-hq/element-web
oci-push.vpn.infra.element.io/element-web
tags: |
type=ref,event=branch
type=ref,event=tag
flavor: |
latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
- name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
if: github.event_name != 'pull_request'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Connect to Tailscale
uses: tailscale/github-action@306e68a486fd2350f2bfc3b19fcd143891a4a2d8 # v4
if: github.event_name != 'pull_request'
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
audience: ${{ secrets.TS_AUDIENCE }}
tags: tag:github-actions
- name: Compute vault jwt role name
id: vault-jwt-role
if: github.event_name != 'pull_request'
run: |
echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT"
- name: Get team registry token
id: import-secrets
uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3
if: github.event_name != 'pull_request'
with:
url: https://vault.infra.ci.i.element.dev
role: ${{ steps.vault-jwt-role.outputs.role_name }}
path: service-management/github-actions
jwtGithubAudience: https://vault.infra.ci.i.element.dev
method: jwt
secrets: |
services/web-repositories/secret/data/oci.element.io username | OCI_USERNAME ;
services/web-repositories/secret/data/oci.element.io password | OCI_PASSWORD ;
- name: Login to oci.element.io Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
if: github.event_name != 'pull_request'
with:
registry: oci-push.vpn.infra.element.io
username: ${{ steps.import-secrets.outputs.OCI_USERNAME }}
password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }}
- name: Build and push
id: build-and-push
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
if: github.event_name != 'pull_request'
with:
context: .
file: apps/web/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Sign the images with GitHub OIDC Token
env:
DIGEST: ${{ steps.build-and-push.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
if: github.event_name != 'pull_request'
run: |
images=""
for tag in ${TAGS}; do
images+="${tag}@${DIGEST} "
done
cosign sign --yes ${images}
- name: Update repo description
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
if: github.event_name != 'pull_request'
continue-on-error: true
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: vectorim/element-web
-55
View File
@@ -1,55 +0,0 @@
name: Deploy documentation
on:
push:
branches: [develop]
workflow_dispatch: {}
permissions: {}
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
name: GitHub Pages
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
with:
package_json_file: package.json
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
cache-dependency-path: pnpm-lock.yaml
node-version: "lts/*"
- name: Fetch layered build
run: ./scripts/layered.sh
- name: Build docs
run: pnpm run docs:build
- name: Upload artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
with:
path: ./docs/.vitepress/dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-24.04
permissions:
pages: write
id-token: write
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
-157
View File
@@ -1,157 +0,0 @@
# For duplicate issues, ensure the close type is right (not planned), update it if not
# For all closed (completed) issues, cascade the closure onto any referenced rageshakes
# For all closed (not planned) issues, comment on rageshakes to move them into the canonical issue if one exists
on:
issues:
types: [closed]
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
tidy:
name: Tidy closed issues
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
id: main
with:
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
script: |
const variables = {
owner: context.repo.owner,
name: context.repo.repo,
number: context.issue.number,
};
const query = `query($owner:String!, $name:String!, $number:Int!) {
repository(owner: $owner, name: $name) {
issue(number: $number) {
stateReason,
timelineItems(first: 100, itemTypes: [MARKED_AS_DUPLICATE_EVENT, UNMARKED_AS_DUPLICATE_EVENT, CROSS_REFERENCED_EVENT]) {
edges {
node {
__typename
... on MarkedAsDuplicateEvent {
canonical {
... on Issue {
repository {
nameWithOwner
}
number
}
... on PullRequest {
repository {
nameWithOwner
}
number
}
}
}
... on UnmarkedAsDuplicateEvent {
canonical {
... on Issue {
repository {
nameWithOwner
}
number
}
... on PullRequest {
repository {
nameWithOwner
}
number
}
}
}
... on CrossReferencedEvent {
source {
... on Issue {
repository {
nameWithOwner
}
number
}
... on PullRequest {
repository {
nameWithOwner
}
number
}
}
}
}
}
}
}
}
}`;
const result = await github.graphql(query, variables);
const { stateReason, timelineItems: { edges } } = result.repository.issue;
const RAGESHAKE_OWNER = "matrix-org";
const RAGESHAKE_REPO = "element-web-rageshakes";
const rageshakes = new Set();
const duplicateOf = new Set();
console.log("Edges: ", JSON.stringify(edges));
for (const { node } of edges) {
switch(node.__typename) {
case "MarkedAsDuplicateEvent":
duplicateOf.add(node.canonical.repository.nameWithOwner + "#" + node.canonical.number);
break;
case "UnmarkedAsDuplicateEvent":
duplicateOf.remove(node.canonical.repository.nameWithOwner + "#" + node.canonical.number);
break;
case "CrossReferencedEvent":
if (node.source.repository.nameWithOwner === (RAGESHAKE_OWNER + "/" + RAGESHAKE_REPO)) {
rageshakes.add(node.source.number);
}
break;
}
}
console.log("Duplicate of: ", duplicateOf);
console.log("Found rageshakes: ", rageshakes);
if (duplicateOf.size) {
const body = Array.from(duplicateOf).join("\n");
// Comment on all rageshakes to create relationship to the issue this was closed as duplicate of
for (const rageshake of rageshakes) {
github.rest.issues.createComment({
owner: RAGESHAKE_OWNER,
repo: RAGESHAKE_REPO,
issue_number: rageshake,
body,
});
}
// Duplicate was closed with wrong reason, fix it
if (stateReason === "COMPLETED") {
core.setOutput("closeAsNotPlanned", "true");
}
} else {
// This issue was closed, close all related rageshakes
for (const rageshake of rageshakes) {
github.rest.issues.update({
owner: RAGESHAKE_OWNER,
repo: RAGESHAKE_REPO,
issue_number: rageshake,
state: "closed",
});
}
}
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
name: Close duplicate as Not Planned
if: steps.main.outputs.closeAsNotPlanned
with:
# We do this step separately, and with the default token so as to not re-trigger this workflow when re-closing
script: |
await github.graphql(`mutation($id:ID!) {
closeIssue(input: { issueId:$id, stateReason:NOT_PLANNED }) {
clientMutationId
}
}`, {
id: context.payload.issue.node_id,
});
-14
View File
@@ -1,14 +0,0 @@
name: Localazy Download
on:
workflow_dispatch: {}
schedule:
- cron: "0 6 * * 1,3,5" # Every Monday, Wednesday and Friday at 6am UTC
permissions:
pull-requests: write # needed to auto-approve PRs
jobs:
download:
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_download.yaml@6eda3835118f3bc3fb658a1a3c20b7da9d16ae42
with:
packageManager: pnpm
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
-14
View File
@@ -1,14 +0,0 @@
name: Localazy Upload
on:
workflow_dispatch: {}
push:
branches: [develop]
paths:
- "apps/web/src/i18n/strings/en_EN.json"
- "packages/shared-components/src/i18n/strings/en_EN.json"
permissions: {} # No permissions needed
jobs:
upload:
uses: matrix-org/matrix-web-i18n/.github/workflows/localazy_upload.yaml@6eda3835118f3bc3fb658a1a3c20b7da9d16ae42
secrets:
LOCALAZY_WRITE_KEY: ${{ secrets.LOCALAZY_WRITE_KEY }}
-29
View File
@@ -1,29 +0,0 @@
# Tweaks the behaviour of Merge Queue to skip certain checks
name: Merge Queue tweaks
on:
merge_group:
types: [checks_requested]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
permissions: {}
jobs:
run:
runs-on: ubuntu-24.04
permissions:
statuses: write
steps:
# This is only needed as license/cla at time of writing seems to be extraordinarily flaky
# and Github doesn't support conditional checks between PR & merge queue.
# This is fine to do as a PR won't make it to merge queue until it has license/cla passing.
- name: Skip license/cla on merge queues
uses: guibranco/github-status-action-v2@9bfa8773cdbdc6c185747fd43cd7faa9d7c32f09
with:
authToken: ${{ secrets.GITHUB_TOKEN }}
state: success
context: license/cla
sha: ${{ github.sha }}
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
-53
View File
@@ -1,53 +0,0 @@
# Triggers after the layered build has finished, taking the artifact
# and uploading it to netlify
name: Upload Preview Build to Netlify
on:
# Privilege escalation necessary to publish to Netlify
# 🚨 We must not execute any checked out code here.
workflow_run: # zizmor: ignore[dangerous-triggers]
workflows: ["Build"]
types:
- completed
jobs:
deploy:
if: github.event.workflow_run.conclusion != 'cancelled' && github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-24.04
environment: Netlify
permissions:
actions: read
deployments: write
steps:
- name: 📝 Create Deployment
uses: bobheadxi/deployments@648679e8e4915b27893bd7dbc35cb504dc915bc8 # v1
id: deployment
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: Netlify
ref: ${{ github.event.workflow_run.head_sha }}
desc: |
Do you trust the author of this PR? Maybe this build will steal your keys or give you malware.
Exercise caution. Use test accounts.
- name: 📥 Download artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: webapp-ubuntu-24.04
path: webapp
- name: 📤 Deploy to Netlify
uses: matrix-org/netlify-pr-preview@9805cd123fc9a7e421e35340a05e1ebc5dee46b5 # v3
with:
path: webapp
owner: ${{ github.event.workflow_run.head_repository.owner.login }}
branch: ${{ github.event.workflow_run.head_branch }}
revision: ${{ github.event.workflow_run.head_sha }}
token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
site_id: ${{ vars.NETLIFY_SITE_ID }}
deployment_env: ${{ steps.deployment.outputs.env }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
desc: |
Do you trust the author of this PR? Maybe this build will steal your keys or give you malware.
Exercise caution. Use test accounts.
-47
View File
@@ -1,47 +0,0 @@
name: Publish npm package
run-name: Publish ${{ inputs.package }}
on:
workflow_dispatch:
inputs:
package:
description: Which package to release
required: true
type: choice
options:
- playwright-common
- shared-components
- module-api
concurrency: release
jobs:
publish:
name: "Publish"
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: 🧮 Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- name: 🔧 Set up node environment
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
node-version-file: ".node-version"
registry-url: "https://registry.npmjs.org"
# Ensure npm 11.5.1 or later is installed
- name: Update npm
run: npm install -g npm@latest
- name: 🛠️ Install dependencies
run: pnpm install --frozen-lockfile
- name: 🚀 Publish to npm
working-directory: packages/${{ inputs.package }}
run: npm publish --access public --provenance
-16
View File
@@ -1,16 +0,0 @@
name: Pull Request
on:
# Privilege escalation necessary access members of the review teams
# 🚨 We must not execute any checked out code here, and be careful around use of user-controlled inputs.
pull_request_target: # zizmor: ignore[dangerous-triggers]
types: [opened, edited, labeled, unlabeled, synchronize]
merge_group:
types: [checks_requested]
permissions: {}
jobs:
action:
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop # zizmor: ignore[unpinned-uses]
permissions:
pull-requests: write
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
@@ -1,17 +0,0 @@
name: Pull Request Base Branch
on:
pull_request:
types: [opened, edited, synchronize]
permissions: {} # No permissions required
jobs:
check_base_branch:
name: Check PR base branch
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const baseBranch = context.payload.pull_request.base.ref;
if (!['develop', 'staging'].includes(baseBranch) && !baseBranch.startsWith('feat/')) {
core.setFailed(`Invalid base branch: ${baseBranch}`);
}
-12
View File
@@ -1,12 +0,0 @@
name: Release Drafter
on:
push:
branches: [staging]
workflow_dispatch: {}
concurrency: ${{ github.workflow }}
permissions: {}
jobs:
draft:
permissions:
contents: write
uses: matrix-org/matrix-js-sdk/.github/workflows/release-drafter-workflow.yml@develop # zizmor: ignore[unpinned-uses]
-17
View File
@@ -1,17 +0,0 @@
# Gitflow merge-back master->develop
name: Merge master -> develop
on:
push:
branches: [master]
concurrency: ${{ github.repository }}-${{ github.workflow }}
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
merge:
uses: matrix-org/matrix-js-sdk/.github/workflows/release-gitflow.yml@develop # zizmor: ignore[unpinned-uses]
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
with:
# This relates to the directory in which to reset dependencies, only web needs this
dir: apps/web
dependencies: |
matrix-js-sdk
-68
View File
@@ -1,68 +0,0 @@
name: Release Process
on:
workflow_dispatch:
inputs:
mode:
description: What type of release
required: true
default: rc
type: choice
options:
- rc
- final
concurrency: ${{ github.workflow }}
permissions: {}
jobs:
release:
uses: matrix-org/matrix-js-sdk/.github/workflows/release-make.yml@develop # zizmor: ignore[unpinned-uses]
permissions:
contents: write
issues: write
pull-requests: read
id-token: write
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
with:
final: ${{ inputs.mode == 'final' }}
gpg-fingerprint: ${{ vars.GPG_FINGERPRINT }}
asset-path: dist/*.tar.gz
expected-asset-count: 3
# Desktop has no dist script so we only target web here
dist-dir: apps/web
version-dirs: apps/web apps/desktop
check:
name: Post release checks
needs: release
runs-on: ubuntu-24.04
permissions:
checks: read
steps:
- name: Wait for docker build
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: master
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-name: "Docker Buildx"
allowed-conclusions: success
- name: Wait for debian package
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: master
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-name: Build package
allowed-conclusions: success
- name: Wait for desktop packaging
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: master
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-regexp: Prepare|Linux|macOS|Windows|Deploy|deploy
allowed-conclusions: success
-97
View File
@@ -1,97 +0,0 @@
name: Cut branches
on:
workflow_dispatch:
inputs:
element-web:
description: Prepare element-web
required: true
type: boolean
default: true
matrix-js-sdk:
description: Prepare matrix-js-sdk
required: true
type: boolean
default: true
permissions: {} # Uses ELEMENT_BOT_TOKEN instead
jobs:
checks:
name: Sanity checks
strategy:
matrix:
repo:
- matrix-org/matrix-js-sdk
- element-hq/element-web
uses: matrix-org/matrix-js-sdk/.github/workflows/release-checks.yml@develop # zizmor: ignore[unpinned-uses]
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
with:
repository: ${{ matrix.repo }}
prepare:
runs-on: ubuntu-24.04
needs: checks
env:
# The order is specified bottom-up to avoid any races for allchange
REPOS: matrix-js-sdk element-web
steps:
- name: Checkout Element Web
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
if: inputs.element-web
with:
repository: element-hq/element-web
path: element-web
ref: staging
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
persist-credentials: true
- name: Checkout Matrix JS SDK
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
if: inputs.matrix-js-sdk
with:
repository: matrix-org/matrix-js-sdk
path: matrix-js-sdk
ref: staging
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
persist-credentials: true
- name: Prepare Git
run: |
git config --global user.email "releases@riot.im"
git config --global user.name "RiotRobot"
- name: Merge Element Web
if: inputs.element-web
run: |
git -C "element-web" merge origin/develop
- name: Merge JS SDK
if: inputs.matrix-js-sdk
run: |
git -C "matrix-js-sdk" merge origin/develop
- name: Push staging
run: for REPO in $REPOS; do [ -d "$REPO" ] && git -C "$REPO" push origin staging; done
- name: Wait for matrix-js-sdk draft
if: inputs.matrix-js-sdk
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: staging
repo: matrix-org/matrix-js-sdk
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
wait-interval: 10
check-name: "draft / draft"
allowed-conclusions: success
- name: Wait for element-web draft
if: inputs.element-web
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: staging
repo: element-hq/element-web
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
wait-interval: 10
check-name: "draft / draft"
allowed-conclusions: success
@@ -1,38 +0,0 @@
name: Build shared component storybook
on:
merge_group: {}
pull_request: {}
workflow_call: {}
permissions: {}
jobs:
doc:
name: Build storybook
runs-on: ubuntu-latest
steps:
- name: 🧮 Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- name: 🔧 Pnpm cache
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
node-version-file: package.json
- name: 🔨 Install dependencies
working-directory: packages/shared-components
run: "pnpm install --frozen-lockfile"
- name: 📖 Build Storybook
working-directory: packages/shared-components
run: pnpm build:storybook
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: shared-components-storybook
path: packages/shared-components/storybook-static
retention-days: 1
@@ -1,33 +0,0 @@
name: Publish shared component storybook
on:
workflow_dispatch: {}
push:
branches:
- "develop"
paths:
- "packages/shared-components/**/*"
permissions: {}
jobs:
build:
name: Build storybook
uses: ./.github/workflows/shared-component-storybook-build.yml
publish:
name: Publish storybook
runs-on: ubuntu-latest
needs: build
environment: SharedComponents
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: shared-components-storybook
path: storybook-static
- name: 🚀 Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
with:
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}
command: pages deploy storybook-static --project-name=shared-components-storybook
@@ -1,50 +0,0 @@
# Triggers after the shared component tests have finished,
# It uploads the received images and diffs to netlify, printing the URLs to the console
name: Upload Shared Component Visual Test Diffs
on:
# Privilege escalation necessary to deploy to Netlify
# 🚨 We must not execute any checked out code here.
workflow_run: # zizmor: ignore[dangerous-triggers]
workflows: ["Shared Component Visual Tests"]
types:
- completed
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.run_id }}
cancel-in-progress: ${{ github.event.workflow_run.event == 'pull_request' }}
permissions: {}
jobs:
report:
if: github.event.workflow_run.conclusion == 'failure'
name: Upload Diffs
runs-on: ubuntu-24.04
environment: Netlify
permissions:
actions: read
deployments: write
steps:
- name: Download Diffs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: received-images
path: received-images
- name: Generate Index
run: "cd received-images && tree -L 1 --noreport -H '' -o index.html ."
- name: 📤 Deploy to Netlify
uses: matrix-org/netlify-pr-preview@9805cd123fc9a7e421e35340a05e1ebc5dee46b5 # v3
with:
path: received-images
owner: ${{ github.event.workflow_run.head_repository.owner.login }}
branch: ${{ github.event.workflow_run.head_branch }}
revision: ${{ github.event.workflow_run.head_sha }}
token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
site_id: ${{ vars.NETLIFY_SITE_ID }}
desc: Shared Component Visual Diffs
deployment_env: SharedComponentDiffs
prefix: "diffs-"
@@ -1,60 +0,0 @@
name: Shared Component Visual Tests
on:
pull_request: {}
merge_group:
types: [checks_requested]
push:
branches: [develop, master]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
permissions: {} # No permissions required
jobs:
testStorybook:
name: "Run Visual Tests"
runs-on: ubuntu-24.04
permissions:
actions: read
issues: read
pull-requests: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
repository: element-hq/element-web
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
node-version: "lts/*"
- name: Install dependencies
working-directory: packages/shared-components
run: pnpm install --frozen-lockfile
- name: Setup playwright
uses: ./.github/actions/setup-playwright
with:
write-cache: ${{ github.event_name != 'merge_group' }}
- name: Run Visual tests
working-directory: packages/shared-components
run: "pnpm test:storybook --run"
- name: Detect stale screenshots
run: |
if diff -rq __baselines__ __results__ | grep "^Only in __baselines__"; then
exit 1
fi
working-directory: packages/shared-components/__vis__/linux
- name: Upload received images & diffs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: received-images
path: packages/shared-components/__vis__/linux
-27
View File
@@ -1,27 +0,0 @@
name: SonarQube
on:
# Privilege escalation necessary to call upon SonarCloud
# 🚨 We must not execute any checked out code here.
workflow_run: # zizmor: ignore[dangerous-triggers]
workflows: ["Tests"]
types:
- completed
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: true
permissions: {}
jobs:
sonarqube:
name: 🩻 SonarQube
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'merge_group'
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop # zizmor: ignore[unpinned-uses]
permissions:
actions: read
statuses: write
id-token: write # sonar
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
with:
sharded: true
version-pkg-json-dir: ./apps/web
-133
View File
@@ -1,133 +0,0 @@
name: Static Analysis
on:
pull_request: {}
push:
branches: [develop, master]
merge_group:
types: [checks_requested]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env:
# This must be set for fetchdep.sh to get the right branch
PR_NUMBER: ${{ github.event.pull_request.number }}
NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes
permissions: {} # No permissions required
jobs:
lint:
strategy:
fail-fast: false
matrix:
include:
- name: Typescript Syntax Check
install: layered
command: "lint:types"
- name: Prettier
install: normal
command: "lint:prettier"
- name: ESLint
install: normal
command: "lint:js"
- name: Style Lint
install: normal
command: "lint:style"
- name: Workflow Lint
install: normal
command: "lint:workflows"
- name: Analyse Dead Code
install: normal
command: "lint:knip"
- name: Rethemendex Check
command: "rethemendex"
assert-diff: true
- name: Docs
install: layered
command: "docs:build"
name: ${{ matrix.name }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
if: matrix.install != ''
with:
cache: "pnpm"
node-version: "lts/*"
- name: Install Dependencies (layered)
if: matrix.install == 'layered'
run: "./scripts/layered.sh"
- name: Install Dependencies (normal)
if: matrix.install == 'normal'
run: "pnpm install --frozen-lockfile"
- name: Run ${{ matrix.command }}
run: pnpm --if-present run "$CMD" && pnpm -r --if-present run "$CMD"
env:
CMD: ${{ matrix.command }}
- name: Assert no changes
run: git diff --exit-code
if: matrix.assert-diff
zizmor:
name: Zizmor Github Actions lint
runs-on: ubuntu-24.04
permissions:
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
i18n:
strategy:
fail-fast: false
matrix:
include:
- name: Element Web
path: "apps/web"
allowed-hardcoded-keys: |
console_dev_note
labs|element_call_video_rooms
labs|feature_disable_call_per_sender_encryption
voip|element_call
error|invalid_json
error|misconfigured
welcome|title_element
devtools|settings|elementCallUrl
labs|sliding_sync_description
settings|voip|noise_suppression_description
settings|voip|echo_cancellation_description
- name: Element Desktop
path: "apps/desktop"
- name: Shared Components
path: "packages/shared-components"
name: "i18n Check (${{ matrix.name }})"
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@6eda3835118f3bc3fb658a1a3c20b7da9d16ae42
permissions:
pull-requests: read
with:
hardcoded-words: "Element"
packageManager: pnpm
path: ${{ matrix.path }}
allowed-hardcoded-keys: ${{ matrix.allowed-hardcoded-keys }}
# Dummy job to simplify branch protections
ci:
name: Static Analysis
needs: [lint, i18n, zizmor]
if: always()
runs-on: ubuntu-24.04
steps:
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1
-24
View File
@@ -1,24 +0,0 @@
name: Sync labels
on:
workflow_dispatch: {}
schedule:
- cron: "0 1 * * *" # 1am every day
push:
branches:
- develop
paths:
- .github/labels.yml
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
sync-labels:
uses: element-hq/element-meta/.github/workflows/sync-labels.yml@7f2f93fb9b52ece7a0998f60e64862aa203c1746
with:
LABELS: |
element-hq/element-meta
.github/labels.yml
DELETE: true
WET: true
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
-188
View File
@@ -1,188 +0,0 @@
name: Tests
on:
pull_request: {}
merge_group:
types: [checks_requested]
push:
branches: [develop, master]
workflow_call:
inputs:
disable_coverage:
type: boolean
required: false
description: "Specify true to skip generating and uploading coverage for tests"
matrix-js-sdk-sha:
type: string
required: false
description: "The matrix-js-sdk SHA to use"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
env:
ENABLE_COVERAGE: ${{ github.event_name != 'merge_group' && inputs.disable_coverage != 'true' }}
# fetchdep.sh needs to know our PR number
PR_NUMBER: ${{ github.event.pull_request.number }}
NX_DEFAULT_OUTPUT_STYLE: stream-without-prefixes
permissions: {}
jobs:
jest_ew:
name: Jest (Element Web)
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
# Run multiple instances in parallel to speed up the tests
runner: [1, 2]
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- name: pnpm cache
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "lts/*"
cache: "pnpm"
- name: Install Deps
run: "./scripts/layered.sh"
env:
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
- name: Jest Cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: /tmp/jest_cache
key: ${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Get number of CPU cores
id: cpu-cores
uses: SimenB/github-actions-cpu-cores@97330871fe1b7d3529392ea000e3d2c4b357e403 # v3
- name: Run tests
working-directory: apps/web
run: |
pnpm test \
--coverage=$ENABLE_COVERAGE \
--ci \
--max-workers $MAX_WORKERS \
--shard "$SHARD" \
--cacheDirectory /tmp/jest_cache
env:
JEST_SONAR_UNIQUE_OUTPUT_NAME: true
# tell jest to use coloured output
FORCE_COLOR: true
MAX_WORKERS: ${{ steps.cpu-cores.outputs.count }}
SHARD: ${{ format('{0}/{1}', matrix.runner, strategy.job-total) }}
- name: Move coverage files into place
if: env.ENABLE_COVERAGE == 'true'
working-directory: apps/web
run: mv coverage/lcov.info coverage/$NODE_VERSION-${{ matrix.runner }}.lcov.info
env:
NODE_VERSION: ${{ steps.setupNode.outputs.node-version }}
- name: Upload Artifact
if: env.ENABLE_COVERAGE == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: coverage-jest-${{ matrix.runner }}
path: |
apps/web/coverage
!apps/web/coverage/lcov-report
complete:
name: jest-tests
needs: [jest_ew, vitest]
if: always()
runs-on: ubuntu-24.04
permissions:
statuses: write
steps:
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1
- name: Skip SonarCloud in merge queue
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
uses: guibranco/github-status-action-v2@9bfa8773cdbdc6c185747fd43cd7faa9d7c32f09
with:
authToken: ${{ secrets.GITHUB_TOKEN }}
state: success
description: SonarCloud skipped
context: SonarCloud Code Analysis
sha: ${{ github.sha }}
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
vitest:
name: Vitest
strategy:
matrix:
path:
- apps/desktop
- packages/shared-components
- packages/module-api
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- name: pnpm cache
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "lts/*"
cache: "pnpm"
- name: Install Deps
run: "pnpm install"
- name: Cache storybook & vitest
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
${{ matrix.path }}/node_modules/.cache
${{ matrix.path }}/node_modules/.vite/vitest
key: ${{ matrix.path }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Setup playwright
uses: ./.github/actions/setup-playwright
if: matrix.path == 'packages/shared-components'
with:
write-cache: ${{ github.event_name != 'merge_group' }}
- name: Run tests
working-directory: ${{ matrix.path }}
run: pnpm test:unit --coverage=$ENABLE_COVERAGE
# Dump the disk usage on failure, because this job seems to fail with disk fills sometimes
- name: df
run: df -h && df -i
if: ${{ failure() }}
- name: Calculate artifact name
if: env.ENABLE_COVERAGE == 'true'
id: artifact
run: |
NAME=$(basename "$MATRIX_PATH")
echo "name=$NAME" >> $GITHUB_OUTPUT
env:
MATRIX_PATH: ${{ matrix.path }}
- name: Upload Artifact
if: env.ENABLE_COVERAGE == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: coverage-${{ steps.artifact.outputs.name }}
path: |
${{ matrix.path }}/coverage
!${{ matrix.path }}/coverage/lcov-report
-21
View File
@@ -1,21 +0,0 @@
name: Move issued assigned to specific team members to their boards
on:
issues:
types: [assigned]
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
web-app-team:
runs-on: ubuntu-24.04
if: |
contains(github.event.issue.assignees.*.login, 't3chguy') ||
contains(github.event.issue.assignees.*.login, 'florianduros') ||
contains(github.event.issue.assignees.*.login, 'dbkr') ||
contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/element-hq/projects/67
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
-16
View File
@@ -1,16 +0,0 @@
name: Move new issues into Issue triage board
on:
issues:
types: [opened]
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
automate-project-columns:
runs-on: ubuntu-24.04
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/element-hq/projects/120
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
-178
View File
@@ -1,178 +0,0 @@
name: Move labelled issues to correct projects
on:
issues:
types: [labeled]
workflow_call:
secrets:
ELEMENT_BOT_TOKEN:
required: true
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
apply_Z-Labs_label:
name: Add Z-Labs label for features behind labs flags
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'A-Maths') ||
contains(github.event.issue.labels.*.name, 'A-Location-Sharing') ||
contains(github.event.issue.labels.*.name, 'Z-IA') ||
contains(github.event.issue.labels.*.name, 'A-Jump-To-Date ') ||
contains(github.event.issue.labels.*.name, 'A-Themes-Custom') ||
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
contains(github.event.issue.labels.*.name, 'A-Tags') ||
contains(github.event.issue.labels.*.name, 'A-Video-Rooms') ||
contains(github.event.issue.labels.*.name, 'A-Message-Starring') ||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Z-Labs']
})
apply_Help-Wanted_label:
name: Add "Help Wanted" label to all "good first issue" and Hacktoberfest
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'good first issue') ||
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Help Wanted']
})
move_needs_info_issues:
name: X-Needs-Info issues to Need info column on triage board
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Info')
steps:
- id: add_to_project
uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: ${{ env.PROJECT_URL }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
- id: set_fields
uses: titoportas/update-project-fields@421a54430b3cdc9eefd8f14f9ce0142ab7678751 # v0.1.0
with:
project-url: ${{ env.PROJECT_URL }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
item-id: ${{ steps.add_to_project.outputs.itemId }} # Use the item-id output of the previous step
field-keys: Status
field-values: "Needs info"
env:
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
move_flakey_test_issues:
name: Z-Flaky-Test issues to Sized for maintainer column on triage board
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
steps:
- id: add_to_project
uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: ${{ env.PROJECT_URL }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
- id: set_fields
uses: titoportas/update-project-fields@421a54430b3cdc9eefd8f14f9ce0142ab7678751 # v0.1.0
with:
project-url: ${{ env.PROJECT_URL }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
item-id: ${{ steps.add_to_project.outputs.itemId }} # Use the item-id output of the previous step
field-keys: Status
field-values: "Sized for maintainer"
env:
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
add_priority_design_issues_to_project:
name: P1 X-Needs-Design to Design project board
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
(contains(github.event.issue.labels.*.name, 'S-Critical') &&
(contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
contains(github.event.issue.labels.*.name, 'S-Major') &&
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'A11y'))
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/element-hq/projects/18
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
add_product_issues:
name: X-Needs-Product to product project board
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Product')
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/element-hq/projects/28
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
Search_issues_to_board:
name: Search issues to project board
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'A-New-Search-Experience')
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/element-hq/projects/48
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
voip:
name: Add labelled issues to VoIP project board
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'Team: VoIP')
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/element-hq/projects/41
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
crypto:
name: Add labelled issues to Crypto project
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'Team: Crypto')
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/element-hq/projects/76
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
tech_debt:
name: Add labelled issues to tech debt project
runs-on: ubuntu-24.04
if: >
contains(github.event.issue.labels.*.name, 'A-Developer-Experience') ||
contains(github.event.issue.labels.*.name, 'A-Documentation') ||
contains(github.event.issue.labels.*.name, 'A-Packaging') ||
contains(github.event.issue.labels.*.name, 'A-Technical-Debt') ||
contains(github.event.issue.labels.*.name, 'A-Testing') ||
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/element-hq/projects/101
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
@@ -1,140 +0,0 @@
name: Move pull requests asking for review to the relevant project
on:
pull_request_target:
types: [review_requested]
permissions: {} # Uses ELEMENT_BOT_TOKEN instead
jobs:
add_design_pr_to_project:
name: Move PRs asking for design review to the design board
runs-on: ubuntu-24.04
steps:
- uses: octokit/graphql-action@ddde8ebb2493e79f390e6449c725c21663a67505 # v3.0.2
id: find_team_members
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
query find_team_members($team: String!) {
organization(login: "element-hq") {
team(slug: $team) {
members {
nodes {
login
}
}
}
}
}
team: ${{ env.TEAM }}
env:
TEAM: "design"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
- id: any_matching_reviewers
run: |
# Fetch requested reviewers, and people who are on the team
echo '${{ tojson(fromjson(steps.find_team_members.outputs.data).organization.team.members.nodes[*].login) }}' | tee /tmp/team_members.json
echo '${{ tojson(github.event.pull_request.requested_reviewers[*].login) }}' | tee /tmp/reviewers.json
jq --raw-output .[] < /tmp/team_members.json | sort | tee /tmp/team_members.txt
jq --raw-output .[] < /tmp/reviewers.json | sort | tee /tmp/reviewers.txt
# Fetch requested team reviewers, and the name of the team
echo '${{ tojson(github.event.pull_request.requested_teams[*].slug) }}' | tee /tmp/team_reviewers.json
jq --raw-output .[] < /tmp/team_reviewers.json | sort | tee /tmp/team_reviewers.txt
echo '${{ env.TEAM }}' | tee /tmp/team.txt
# If either a reviewer matches a team member, or a team matches our team, say "true"
if [ $(join /tmp/team_members.txt /tmp/reviewers.txt | wc -l) != 0 ]; then
echo "match=true" >> $GITHUB_OUTPUT
elif [ $(join /tmp/team.txt /tmp/team_reviewers.txt | wc -l) != 0 ]; then
echo "match=true" >> $GITHUB_OUTPUT
else
echo "match=false" >> $GITHUB_OUTPUT
fi
env:
TEAM: "design"
- uses: octokit/graphql-action@ddde8ebb2493e79f390e6449c725c21663a67505 # v3.0.2
id: add_to_project
if: steps.any_matching_reviewers.outputs.match == 'true'
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!, $contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.pull_request.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc0sUA"
TEAM: "design"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
add_product_pr_to_project:
name: Move PRs asking for design review to the design board
runs-on: ubuntu-24.04
steps:
- uses: octokit/graphql-action@ddde8ebb2493e79f390e6449c725c21663a67505 # v3.0.2
id: find_team_members
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
query find_team_members($team: String!) {
organization(login: "element-hq") {
team(slug: $team) {
members {
nodes {
login
}
}
}
}
}
team: ${{ env.TEAM }}
env:
TEAM: "product"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
- id: any_matching_reviewers
run: |
# Fetch requested reviewers, and people who are on the team
echo '${{ tojson(fromjson(steps.find_team_members.outputs.data).organization.team.members.nodes[*].login) }}' | tee /tmp/team_members.json
echo '${{ tojson(github.event.pull_request.requested_reviewers[*].login) }}' | tee /tmp/reviewers.json
jq --raw-output .[] < /tmp/team_members.json | sort | tee /tmp/team_members.txt
jq --raw-output .[] < /tmp/reviewers.json | sort | tee /tmp/reviewers.txt
# Fetch requested team reviewers, and the name of the team
echo '${{ tojson(github.event.pull_request.requested_teams[*].slug) }}' | tee /tmp/team_reviewers.json
jq --raw-output .[] < /tmp/team_reviewers.json | sort | tee /tmp/team_reviewers.txt
echo '${{ env.TEAM }}' | tee /tmp/team.txt
# If either a reviewer matches a team member, or a team matches our team, say "true"
if [ $(join /tmp/team_members.txt /tmp/reviewers.txt | wc -l) != 0 ]; then
echo "match=true" >> $GITHUB_OUTPUT
elif [ $(join /tmp/team.txt /tmp/team_reviewers.txt | wc -l) != 0 ]; then
echo "match=true" >> $GITHUB_OUTPUT
else
echo "match=false" >> $GITHUB_OUTPUT
fi
env:
TEAM: "product"
- uses: octokit/graphql-action@ddde8ebb2493e79f390e6449c725c21663a67505 # v3.0.2
id: add_to_project
if: steps.any_matching_reviewers.outputs.match == 'true'
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!, $contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.pull_request.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AAg6N"
TEAM: "product"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
-29
View File
@@ -1,29 +0,0 @@
name: Close stale issues & PRs
on:
workflow_dispatch: {}
schedule:
- cron: "30 1 * * *"
permissions: {}
jobs:
close:
runs-on: ubuntu-24.04
permissions:
actions: write
issues: write
pull-requests: write
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10
with:
operations-per-run: 100
# Flaky test issue closing
any-of-issue-labels: "Z-Flaky-Test-Chrome,Z-Flaky-Test-Firefox,Z-Flaky-Test-Webkit"
days-before-issue-stale: 14
days-before-issue-close: 0
close-issue-message: "This flaky test issue has not been updated in 14 days. It is being closed as presumed resolved."
exempt-issue-labels: "Z-Flaky-Test-Disabled"
# Stale PR closing
days-before-pr-stale: 180
days-before-pr-close: 0
close-pr-message: "This PR has been automatically closed because it has been stale for 180 days. If you wish to continue working on this PR, please ping a maintainer to reopen it."
-54
View File
@@ -1,54 +0,0 @@
name: Move unlabelled from needs info columns to triaged
on:
issues:
types: [unlabeled]
permissions: {}
jobs:
move_no_longer_needs_info_issues:
name: Move no longer X-Needs-Info issues to Triaged
runs-on: ubuntu-24.04
if: >
!contains(github.event.issue.labels.*.name, 'X-Needs-Info')
steps:
- id: set_fields
uses: nipe0324/update-project-v2-item-field@c4af58452d1c5a788c1ea4f20e073fa722ec4a6b #v2.0.2
with:
project-url: ${{ env.PROJECT_URL }}
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
skip-update-script: |
const isIssue = item.type === 'ISSUE'
const status = item.fieldValues['Status']
return !isIssue || status !== 'Needs info'
field-name: Status
field-value: "Triaged"
env:
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
remove_Z-Labs_label:
name: Remove Z-Labs label when features behind labs flags are removed
runs-on: ubuntu-24.04
if: >
!(contains(github.event.issue.labels.*.name, 'A-Maths') ||
contains(github.event.issue.labels.*.name, 'A-Message-Pinning') ||
contains(github.event.issue.labels.*.name, 'A-Location-Sharing') ||
contains(github.event.issue.labels.*.name, 'Z-IA') ||
contains(github.event.issue.labels.*.name, 'A-Jump-To-Date') ||
contains(github.event.issue.labels.*.name, 'A-Themes-Custom') ||
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
contains(github.event.issue.labels.*.name, 'A-Tags') ||
contains(github.event.issue.labels.*.name, 'A-Video-Rooms') ||
contains(github.event.issue.labels.*.name, 'A-Message-Starring') ||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
contains(github.event.issue.labels.*.name, 'Z-Labs')
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: ['Z-Labs']
})
-37
View File
@@ -1,37 +0,0 @@
# Re-fetches the Jitsi SDK and opens a PR to update it if it's different from what's in the repository
name: Update Jitsi
on:
workflow_dispatch: {}
schedule:
- cron: "0 3 * * 0" # 3am every Sunday
permissions: {} # We use ELEMENT_BOT_TOKEN instead
jobs:
update:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
node-version: "lts/*"
- name: Install Deps
run: "pnpm install --frozen-lockfile"
- name: Fetch Jitsi
working-directory: apps/web
run: "pnpm vendor:jitsi"
- name: Create Pull Request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/jitsi-update
delete-branch: true
title: Jitsi Update
labels: |
T-Task
-119
View File
@@ -1,119 +0,0 @@
name: Update release topics
on:
workflow_dispatch:
inputs:
expected_status:
description: What type of release is the next expected release
required: true
default: RC
type: choice
options:
- RC
- Release
expected_date:
description: Expected release date e.g. July 11th
required: true
type: string
concurrency: ${{ github.workflow }}
permissions: {} # No permissions required
jobs:
bot:
name: Release topic update
runs-on: ubuntu-24.04
environment: Matrix
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
HS_URL: ${{ secrets.BETABOT_HS_URL }}
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}
PUBLIC_DISCUSSION_ROOM_ID: "!xUW4PpAe1CmThA3r2wI8IrgwwsK006-zqWdJCljpd10"
ANNOUNCEMENT_ROOM_ID: "!ars5ndgI6IIYZXECiJ-u8YljHNzShJn3nHdB-3rYI2M"
TOKEN: ${{ secrets.BETABOT_ACCESS_TOKEN }}
RELEASE_STATUS: "Release status: ${{ inputs.expected_status }} expected ${{ inputs.expected_date }}"
with:
script: |
const { HS_URL, TOKEN, RELEASE_STATUS, LOBBY_ROOM_ID, PUBLIC_DISCUSSION_ROOM_ID, ANNOUNCEMENT_ROOM_ID } = process.env;
const repo = context.repo;
const { data } = await github.rest.repos.getLatestRelease({
owner: repo.owner,
repo: repo.repo,
});
console.log("Found latest version: " + data.tag_name);
const releaseTopic = `Stable: ${data.tag_name} | ${RELEASE_STATUS}`;
console.log("Release topic: " + releaseTopic);
const regex = /Stable: v(.+) \| Release status: (\w+) expected (\w+ \d+\w\w)/gm;
async function updateReleaseInTopic(roomId) {
const apiUrl = `${HS_URL}/_matrix/client/v3/rooms/${roomId}/state/m.room.topic/`;
const headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${TOKEN}`,
};
await fetch(`${HS_URL}/_matrix/client/v3/rooms/${roomId}/join`, {
method: "POST",
headers,
body: "{}",
});
let res = await fetch(apiUrl, {
method: "GET",
headers,
});
if (!res.ok) {
console.log(roomId, "failed to fetch", await res.text());
return;
}
const data = await res.json();
console.log(roomId, "got event", data);
if (!regex.test(data.topic)) {
core.setFailed("Topic format is incorrect for room " + roomId);
return;
}
const topic = data.topic.replace(regex, releaseTopic);
if (topic === data.topic) {
console.log(roomId, "nothing to do");
return;
}
if (data["org.matrix.msc3765.topic"]) {
data["org.matrix.msc3765.topic"]?.["m.text"].forEach(d => {
d.body = d.body.replace(regex, releaseTopic);
});
}
if (data["m.topic"]) {
data["m.topic"]?.["m.text"].forEach(d => {
d.body = d.body.replace(regex, releaseTopic);
});
}
res = await fetch(apiUrl, {
method: "PUT",
body: JSON.stringify({
...data,
topic,
}),
headers,
});
if (res.ok) {
const resJson = res.json();
if (resJson.errcode) {
core.setFailed(`Error updating ${roomId}: ${resJson.error}`);
} else {
console.log(roomId, "topic updated:", topic);
}
} else {
const errText = await res.text();
core.setFailed(`Error updating ${roomId}: ${errText}`);
}
}
await updateReleaseInTopic(LOBBY_ROOM_ID);
await updateReleaseInTopic(PUBLIC_DISCUSSION_ROOM_ID);
await updateReleaseInTopic(ANNOUNCEMENT_ROOM_ID);
+5
View File
@@ -5,6 +5,11 @@
/lib
node_modules
/.npmrc
# ThreadNet-Fork: unsere .npmrc IST versioniert. Sie bindet nur den Scope @sorb an
# die Registry auf rohana und enthaelt kein Geheimnis (Lesezugriff ist anonym).
# Ohne diese Ausnahme bliebe die Datei lokal, und CI wie frischer Klon loesten
# @sorb weiter gegen npmjs auf - siehe management#0055.
!/.npmrc
/*.log
package-lock.json
.DS_Store
+329
View File
@@ -0,0 +1,329 @@
# GitLab-CI fuer den ThreadNet-Web-Fork (Lab-GitLab: git.lab).
# Ersetzt die frueheren GitHub/Gitea-Actions-Workflows - Hintergrund: docs/axion1337-fork.md.
#
# Erkenntnisse aus den Gitea-CI-Versuchen (2026-07-30), hier eingeflossen:
# - kein scripts/layered.sh: wuerde den gepinnten matrix-js-sdk-Stand aus pnpm-lock.yaml
# mit Upstream-develop ueberschreiben (und braucht jq) -> frozen-lockfile-Install
# - webpack braucht ~4 GB Heap -> NODE_OPTIONS
# - Desktop-Build laeuft im dockerbuild-Image (rust:bullseye + node, glibc-2.31-Ziel)
# --- Wann ueberhaupt eine Pipeline entsteht ------------------------------------
# Ohne diesen Block erzeugt ein Commit, der nur docs/ anfasst, eine Pipeline mit
# NULL Jobs - und die zaehlt in GitLab als "failed". Real passiert am 2026-08-06
# (Pipelines 203 und 204): zwei Doku-Commits, zweimal rot, nichts kaputt.
#
# Der Grund liegt an der needs-Kette: web laeuft wegen changes: nicht, und
# desktop_linux/desktop_windows haengen per needs daran - damit faellt die ganze
# Pipeline in sich zusammen, auch die manuellen Jobs ohne needs.
#
# ⚠️ Warum das keine Kosmetik ist: In gitops/CLAUDE.md ist die rote Pipeline die
# ALARMANLAGE fuer die TURN-Rotation ("es gibt keinen separaten Reminder"). Rot,
# das nichts bedeutet, gewoehnt einem das Hinsehen ab.
#
# Preis: Nach einem reinen Doku-Commit entsteht keine Pipeline von selbst. Damit
# die manuellen Wartungsjobs (desktop_image, windows_provision) trotzdem
# erreichbar bleiben, laesst die Regel CI_PIPELINE_SOURCE == "web" durch - eine
# ueber "Run pipeline" in der Oberflaeche gestartete Pipeline entsteht immer.
.pfade_mit_pipeline: &pfade_mit_pipeline
- apps/**/*
- packages/**/*
- patches/**/*
- scripts/**/*
- pnpm-lock.yaml
- pnpm-workspace.yaml
# .npmrc bindet den Scope @sorb an rohana. Aendert sich die Datei, aendert
# sich, WOHER Abhaengigkeiten kommen - das gehoert gebaut, nicht durchgewunken.
- .npmrc
- .gitlab-ci.yml
workflow:
rules:
- if: $CI_COMMIT_TAG =~ /^v/
- if: $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_PIPELINE_SOURCE == "web"
- if: $CI_COMMIT_BRANCH
changes: *pfade_mit_pipeline
- when: never
stages:
- build
- package
# --- Windows-Runner-VM-Steuerung (Issue #5) ---------------------------------
# Die Windows-Build-VM (dockur/windows-Fork, siehe git.lab/axion1337.chat/vendor/windows,
# Runbook: docs/axion-runner.md dort) laeuft on-demand auf Overmind. Diese beiden Jobs
# starten/stoppen sie vom Linux-Runner aus ueber den Docker-Socket - der
# desktop_windows-Job wartet danach einfach in der Queue, bis der Runner online ist.
start_windows_vm:
stage: build
image: docker:27-cli
rules:
- if: $CI_COMMIT_TAG =~ /^v/
when: manual
allow_failure: true
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
script:
- docker start windows-runner
stop_windows_vm:
stage: package
image: docker:27-cli
rules:
- if: $CI_COMMIT_TAG =~ /^v/
when: manual
allow_failure: true
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
script:
- docker stop windows-runner
# CVE-Scan des eigenen Prod-Images (gitops#31): woechentlich per Schedule und
# manuell triggerbar. Bricht bewusst nicht hart ab (allow_failure) - Funde landen
# als Artifact + Job-Log, die Bewertung bleibt menschlich. Lab-interne Images
# (desktop-build, windows-vm) brauchen erst CA-Trust im Trivy-Container - notiert
# in gitops#31 als Ausbaustufe.
trivy_scan:
stage: build
image:
name: aquasec/trivy:0.58.2
entrypoint: [""]
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
allow_failure: true
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
variables:
GIT_STRATEGY: none
TRIVY_NO_PROGRESS: "true"
script:
- trivy image --severity HIGH,CRITICAL --format table --output trivy-web.txt rohana.axion1337.de/sorb/threadnet-web:latest-ci
- grep -E "Total|CRITICAL|HIGH" trivy-web.txt | head -20 || true
artifacts:
paths:
- trivy-web.txt
expire_in: 30 days
when: always
web:
stage: build
image: node:24-bullseye
rules:
# Release-Tags bauen immer (docker_web/desktop_* brauchen web als needs)
- if: $CI_COMMIT_TAG =~ /^v/
# Scheduled Pipelines gehoeren dem trivy_scan - kein wochentlicher Leerbuild
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
# Pushes nur, wenn build-relevante Pfade betroffen sind - Doku-Commits kosten nichts
- if: $CI_COMMIT_BRANCH
# derselbe Anker wie im workflow-Block oben - zwei Listen wuerden driften
changes: *pfade_mit_pipeline
variables:
NODE_OPTIONS: "--max-old-space-size=6144"
CI_PACKAGE: "true"
before_script:
- corepack enable
script:
- pnpm install --frozen-lockfile
- cp apps/web/element.io/develop/config.json apps/web/config.json
- VERSION=$(scripts/get-version-from-git.sh) pnpm --dir apps/web build
artifacts:
paths:
- apps/web/webapp
expire_in: 1 day
# Baut das kanonische Web-Image (apps/web/Dockerfile, Kontext = Monorepo-Root) und pusht
# es in die rohana-Registry, aus der Flux/k8s zieht. Deploy bleibt ein manueller Tag-Bump
# im gitops-Repo. Bewusster Doppel-Build (webpack laeuft im web-Job UND im Dockerfile) -
# kanonisch/reproduzierbar vor schnell; Optimierung als Folgearbeit in Issue #2.
docker_web:
stage: package
image: docker:27-cli
needs:
- job: web
artifacts: false
rules:
# Nur bei Release-Tags (v*) - "releasen" ist ein bewusster Akt:
# Tag pushen -> Image entsteht -> Tag-Bump im gitops-Repo deployt es
- if: $CI_COMMIT_TAG =~ /^v/
variables:
IMAGE: rohana.axion1337.de/sorb/threadnet-web
DOCKER_BUILDKIT: "1"
script:
- echo "$REGISTRY_PASSWORD" | docker login rohana.axion1337.de -u "$REGISTRY_USER" --password-stdin
# Ein veroeffentlichtes Release darf sich nicht rueckwirkend aendern.
#
# Am 2026-08-09 hat das Force-Push umgezogener Tags (History-Anonymisierung)
# drei Release-Pipelines neu gestartet - darunter v0.4.0. Ohne diese Sperre
# haette der Lauf das laengst veroeffentlichte Image aus altem Code mit
# heutigen Basis-Images und Abhaengigkeiten neu gebaut und ueberschrieben.
# Dass es damals nicht passierte, lag nur daran, dass die geschuetzten
# Registry-Variablen in dem Moment nicht verfuegbar waren - Glueck, nicht
# Absicht (ThreadNet-Web#14).
#
# Ein Tag ist hier kein Verwaltungseintrag, sondern ein Build-Ausloeser. Wer
# einen Tag verschiebt, loest einen Build aus, ob er will oder nicht. Deshalb
# Unveraenderlichkeit erzwingen statt auf Vorsicht zu hoffen.
- |
if docker manifest inspect "$IMAGE:$CI_COMMIT_TAG" >/dev/null 2>&1; then
echo "FEHLER: $IMAGE:$CI_COMMIT_TAG existiert in der Registry bereits."
echo "Ein veroeffentlichtes Release wird nicht ueberschrieben."
echo "Gewollt? Dann das Image bewusst aus der Registry entfernen oder eine"
echo "neue Version taggen - siehe ThreadNet-Web#14."
exit 1
fi
- docker build -f apps/web/Dockerfile -t "$IMAGE:$CI_COMMIT_TAG" -t "$IMAGE:latest-ci" .
- docker push "$IMAGE:$CI_COMMIT_TAG"
# latest-ci bleibt bewusst ueberschreibbar - der Tag bezeichnet "der neueste
# Stand", nicht eine Version, und traegt deshalb kein Versprechen.
- docker push "$IMAGE:latest-ci"
# Einmalig/selten: Build-Image fuer den Desktop-Build (rust:bullseye + node + tcl/sqlcipher,
# aus apps/desktop/dockerbuild). Manuell ausloesen, wenn sich .node-version oder das
# Dockerfile aendert.
desktop_image:
stage: package
image: docker:27-cli
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
variables:
# Lab-Registry statt rohana (OVERMIND-01): Konsument ist nur die Lab-CI selbst
IMAGE: $CI_REGISTRY_IMAGE/desktop-build
script:
- echo "$CI_JOB_TOKEN" | docker login "$CI_REGISTRY" -u gitlab-ci-token --password-stdin
- docker build -f apps/desktop/dockerbuild/Dockerfile -t "$IMAGE:bullseye" apps/desktop
- docker push "$IMAGE:bullseye"
# Electron-Linux-Build (amd64, static sqlcipher) - repliziert den am 2026-07-29 manuell
# verifizierten Build-Weg. Automatisch bei Release-Tags, auf main manuell triggerbar.
desktop_linux:
stage: package
image: registry.git.lab/axion1337.chat/threadnet-web/desktop-build:bullseye
needs:
- job: web
artifacts: true
rules:
- if: $CI_COMMIT_TAG =~ /^v/
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
variables:
MAX_GLIBC: "2.31"
USE_HARD_LINKS: "false"
SQLCIPHER_BUNDLED: "1"
# Eigene Build-Variante: Produktname ThreadNet statt Element. Ohne das nutzt
# electron-builder element.io/release/build.json und die Pakete heissen weiter
# "element-desktop" (real passiert 2026-08-02).
VARIANT_PATH: axion1337/build.json
script:
- pnpm install --frozen-lockfile --filter element-desktop
- cp -r apps/web/webapp apps/desktop/webapp
# Produktions-Client-Config (getrackt seit diesem Commit, vorher nur im manuellen Build)
- cp apps/desktop/axion1337/config.json apps/desktop/webapp/config.json
- cd apps/desktop
- pnpm run asar-webapp
- pnpm run build:native
# pnpm/npm setzen beim Install kein Executable-Bit auf 7za - bekannter Fix,
# gleicher Schritt wie in der Upstream-CI ("Fix permissions")
- chmod +x ../../node_modules/7zip-bin/linux/*/7za || true
- pnpm run build --publish never -l tar.gz -l deb
artifacts:
paths:
- apps/desktop/dist/*.deb
- apps/desktop/dist/*.tar.gz
expire_in: 1 week
# Windows-Desktop-Build (x64, unsigniert - Signing siehe Issue #5). Laeuft NUR auf dem
# Windows-Runner (tags), manuell: vorher start_windows_vm ausloesen. Uebersetzt aus dem
# Upstream-Workflow build_desktop_windows.yaml (x64-Pfad; arm-only-Schritte entfallen).
desktop_windows:
stage: package
tags:
- windows
needs:
- job: web
artifacts: true
rules:
- if: $CI_COMMIT_TAG =~ /^v/
when: manual
allow_failure: true
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
variables:
SQLCIPHER_BUNDLED: "1"
ELECTRON_CACHE: C:\electron-cache
ELECTRON_BUILDER_CACHE: C:\eb-cache
# Eigene Build-Variante (Produktname ThreadNet) - siehe desktop_linux
VARIANT_PATH: axion1337/build.json
script:
- pnpm install --frozen-lockfile --filter element-desktop
# GitHub-CDN-Downloads (Electron-Binary, winCodeSign, NSIS) reissen im Gast
# reproduzierbar ab und app-builder kann nicht fortsetzen (Jobs 415/416/424) -
# das Skript laedt alles resumefaehig in die persistenten Caches vor.
- powershell -ExecutionPolicy Bypass -File apps\desktop\axion1337\prefetch-buildcache.ps1
- Copy-Item -Recurse apps/web/webapp apps/desktop/webapp
- Copy-Item apps/desktop/axion1337/config.json apps/desktop/webapp/config.json -Force
- cd apps/desktop
- pnpm run asar-webapp
- '& "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64 -SkipAutomaticLocation'
- pnpm run build:native --target x86_64-pc-windows-msvc
- pnpm run build --publish never -w nsis
artifacts:
paths:
- apps/desktop/dist/*.exe
expire_in: 1 week
# Idempotente Werkzeug-Provisionierung im Windows-Gast (laeuft als SYSTEM ueber den
# Runner selbst - kein noVNC/RDP noetig). Hintergrund: choco akzeptiert --version nicht
# bei Mehrfach-Paketen, der urspruengliche Runbook-Einzeiler hat still nichts installiert.
# Startet am Ende den Runner-Dienst verzoegert neu, damit der neue PATH fuer
# Folge-Jobs greift (der Job selbst wird davon nicht mehr unterbrochen).
windows_provision:
stage: build
tags:
- windows
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: true
variables:
# Kein Checkout - der braeuchte git, das dieser Job erst installiert (Henne-Ei)
GIT_STRATEGY: none
script:
- choco install -y git
- choco install -y nasm
- choco install -y magicsplat-tcl-tk
- choco install -y nodejs --version=24.15.0
# Fuer den seshat-hak-Build (hak/matrix-seshat/check.ts): perl+patch (Strawberry
# liefert beides, patch.exe in C:\Strawberry\c\bin), python (node-gyp), rustc.
- choco install -y strawberryperl
- choco install -y python3
# Rust maschinenweit statt Benutzerprofil: der Runner-Dienst laeuft als SYSTEM und
# sieht nur die Maschinen-Umgebung - ein Default-rustup unter %USERPROFILE% waere
# fuer Jobs unsichtbar.
- $env:RUSTUP_HOME='C:\Rust\rustup'; $env:CARGO_HOME='C:\Rust\cargo'; Invoke-WebRequest -UseBasicParsing https://win.rustup.rs/x86_64 -OutFile "$env:TEMP\rustup-init.exe"
- '& "$env:TEMP\rustup-init.exe" -y --profile minimal --default-toolchain stable --default-host x86_64-pc-windows-msvc'
- '[Environment]::SetEnvironmentVariable(''RUSTUP_HOME'',''C:\Rust\rustup'',''Machine'')'
- '[Environment]::SetEnvironmentVariable(''CARGO_HOME'',''C:\Rust\cargo'',''Machine'')'
# choco-nasm traegt sich nicht selbst in den PATH ein (Upstream macht das auch
# explizit via GITHUB_PATH) - zusammen mit cargo\bin hier maschinenweit nachziehen
- $mp=[Environment]::GetEnvironmentVariable('Path','Machine'); if ($mp -notlike '*C:\Rust\cargo\bin*') { [Environment]::SetEnvironmentVariable('Path', "$mp;C:\Rust\cargo\bin;C:\Program Files\NASM", 'Machine') }
- refreshenv; corepack enable
- '& "C:\Program Files\Git\cmd\git.exe" --version'
- '& "C:\Program Files\nodejs\node.exe" --version'
- '& C:\Rust\cargo\bin\rustc.exe --version'
# Windows-260-Zeichen-Pfadlimit: die Visual-Baseline-Pfade des Monorepos sprengen
# MAX_PATH - beide Schalter noetig (git-seitig + OS-Policy)
- '& "C:\Program Files\Git\cmd\git.exe" config --system core.longpaths true'
- Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name LongPathsEnabled -Value 1 -Type DWord
# Dienst-Neustart als Scheduled Task: ueberlebt das Prozessbaum-Cleanup des Runners
# beim Job-Ende (der fruehere Start-Process-Trick wurde dabei gekillt und liess den
# Dienst gestoppt zurueck - Vorfall 2026-07-31, siehe ThreadNet-Web#5).
- schtasks /create /f /tn RunnerRestart /ru SYSTEM /sc once /st ((Get-Date).AddMinutes(1).ToString('HH:mm')) /tr "powershell -Command Restart-Service gitlab-runner -Force"
+15
View File
@@ -0,0 +1,15 @@
# Der Scope @sorb liegt in der Gitea-Registry auf rohana, nicht auf npmjs.
#
# Ohne diese Zeile loest pnpm @sorb/threadnet-call-embedded gegen
# registry.npmjs.org auf. Dass das bisher gutging, lag allein am Lockfile: es
# pinnt die vollstaendige Tarball-URL, und die CI installiert mit
# --frozen-lockfile. Beim ersten Anheben der Version faellt dieser Schutz weg.
#
# Heute endet das in einem 404, weil der Name auf npmjs frei ist. Genau darauf
# darf man sich nicht verlassen: registriert dort jemand @sorb, loest derselbe
# Befehl still gegen ein fremdes Paket auf (dependency confusion) - und zwar in
# dem Moment, in dem ein neuer Download ohnehin erwartet wird.
#
# Kein Geheimnis in dieser Datei: der Lesezugriff auf die Registry ist anonym.
# Siehe management#0055.
@sorb:registry=https://rohana.axion1337.de/api/packages/sorb/npm/
+33
View File
@@ -0,0 +1,33 @@
# AGENTS.md — ThreadNet-Web
> **Die Gruppenregeln sind kanonisch im `management`-Repo:**
> [`AGENTS.md`](https://git.lab/axion1337.chat/management/-/blob/main/AGENTS.md)
> — von außerhalb des Labs über den Gitea-Mirror lesbar:
> `https://rohana.axion1337.de/sorb/management`. Dort stehen Repo-Topologie und
> Mirror-Regeln, das Kanban-Framework (Status-Labels, WIP-Limit 2, ADR-Pflicht),
> Deploy-Übergabe und AAR-Verfahren, Secrets-Handhabung und die
> Karpathy-Leitlinien. Sie gelten für **jede** Session in diesem Repo.
> Hier steht nur, was zusätzlich für dieses Repository gilt.
## Was dieses Repo ist
Fork von **Element Web/Desktop** (Basis `1.12.17`) für axion1337.chat.
⚠️ **`README.md` und der Großteil von `docs/` sind unverändertes Upstream-Material** und
beschreiben diesen Fork ausdrücklich **nicht**. Wer sich danach richtet, übersieht jede
Anpassung.
## Der eine Ort, der zählt
**[`docs/axion1337-fork.md`](docs/axion1337-fork.md)** ist die vollständige Liste dessen, was
dieser Fork gegenüber Upstream ändert — und zugleich die **Portier-Checkliste** für jedes
Upstream-Upgrade: dort stehen die kritischen Dateien, an denen die Merge-Reibung entsteht.
**Jede Änderung am Fork gehört dort hinein.** Eine Anpassung, die nur im Code steht, geht beim
nächsten Rebase verloren — genau dafür existiert die Datei.
## Bauen & Ausrollen
Image wird in die Registry auf `rohana.axion1337.de` publiziert; die laufende Version zieht der
Cluster über den Tag im gitops-Repo (`apps/production/`). Ein Upgrade ist deshalb zweistufig:
erst hier rebasen und bauen, dann dort den Tag anheben.
+1
View File
@@ -0,0 +1 @@
Read AGENTS.md — the canonical instruction file for this repository. All rules live there.
+6
View File
@@ -7,6 +7,12 @@
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=element-web&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=element-web)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=element-web&metric=bugs)](https://sonarcloud.io/summary/new_code?id=element-web)
> **ThreadNet-Web**: this is a fork of Element Web, customized for the self-hosted
> [axion1337.chat](https://axion1337.chat) Matrix homeserver. See
> [docs/axion1337-fork.md](docs/axion1337-fork.md) for everything this fork changes versus
> upstream (Discord-style room list, client-side ClamAV content scanning, build fixes). The
> rest of this README describes upstream Element Web and is intentionally left as-is.
# Element
Element (formerly known as Vector and Riot) is a Matrix web & desktop client built using the [Matrix
+9
View File
@@ -0,0 +1,9 @@
{
"appId": "im.riot.app",
"name": "threadnet-desktop",
"productName": "ThreadNet",
"description": "ThreadNet — powered by Element",
"protocols": ["io.element.desktop", "element"],
"mac.icon": "build/icon.icns",
"dmg.badgeIcon": "build/icon.icns"
}
+656
View File
@@ -0,0 +1,656 @@
{
"brand": "aXion1337.Chat",
"_kommentar_custom_urls": "Entscheidung sorb 2026-08-19 (management #0099). Muss hier SEPARAT stehen: der Desktop-Client laedt diese config.json, nicht die des Web-Deployments - derselbe Fallstrick wie beim Themes-Rollout. Blendet den Bearbeiten-Knopf am Servernamen aus; der hs_url-Query-Parameter bleibt davon unberuehrt. Verengt die Flaeche fuer GHSA-wrcp-5v3v-3j6v, ersetzt das Upstream-Update nicht.",
"disable_custom_urls": true,
"bug_report_endpoint_url": "local",
"branding": {
"auth_header_logo_url": "vector-icons/512.png",
"logo_link_url": "https://axion1337.chat"
},
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.axion1337.chat",
"server_name": "axion1337.chat"
}
},
"default_theme": "aXion1337 Dark",
"element_call": {
"use_exclusively": true
},
"embedded_pages": {
"login_for_welcome": true
},
"features": {
"feature_element_call_video_rooms": true,
"feature_group_calls": true,
"feature_new_room_decoration_ui": true,
"feature_new_room_list": true,
"feature_qr_code_login": true,
"feature_video_rooms": true
},
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
"mobile_guide_app_variant": "element",
"setting_defaults": {
"UIFeature.deactivate": false,
"UIFeature.passwordReset": false,
"UIFeature.registration": false,
"custom_themes": [
{
"name": "aXion1337 Dark true",
"is_dark": true,
"colors": {
"accent-color": "#ffaf0f",
"primary-color": "#ffaf0f",
"secondary-color": "#ffaf0f"
}
},
{
"name": "Deep Purple",
"is_dark": true,
"colors": {
"accent-color": "#6503b3",
"primary-color": "#368bd6",
"warning-color": "#b30356",
"sidebar-color": "#15171B",
"roomlist-background-color": "#22262E",
"roomlist-text-color": "#A1B2D1",
"roomlist-text-secondary-color": "#EDF3FF",
"roomlist-highlights-color": "#343A46",
"roomlist-separator-color": "#a1b2d1",
"timeline-background-color": "#181b21",
"timeline-text-color": "#EDF3FF",
"timeline-text-secondary-color": "#A1B2D1",
"timeline-highlights-color": "#22262E"
}
},
{
"name": "Discord Dark",
"is_dark": true,
"colors": {
"accent-color": "#747ff4",
"accent": "#747ff4",
"primary-color": "#00aff4",
"warning-color": "#faa81ad9",
"alert": "#faa81ad9",
"sidebar-color": "#202225",
"roomlist-background-color": "#2f3136",
"roomlist-text-color": "#dcddde",
"roomlist-text-secondary-color": "#8e9297",
"roomlist-highlights-color": "#4f545c52",
"roomlist-separator-color": "#40444b",
"timeline-background-color": "#36393f",
"timeline-text-color": "#dcddde",
"secondary-content": "#dcddde",
"tertiary-content": "#dcddde",
"timeline-text-secondary-color": "#b9bbbe",
"timeline-highlights-color": "#04040512",
"reaction-row-button-selected-bg-color": "#4752c4",
"menu-selected-color": "#4752c4",
"focus-bg-color": "#4752c4",
"room-highlight-color": "#4752c4",
"other-user-pill-bg-color": "#4752c4",
"togglesw-off-color": "#72767d"
},
"compound": {
"--cpd-color-theme-bg": "#0019ff",
"--cpd-color-bg-canvas-default": "#2f3136",
"--cpd-color-bg-subtle-secondary": "#2f3136",
"--cpd-color-bg-subtle-primary": "#4f545c52",
"--cpd-color-bg-action-primary-rest": "#dcddde",
"--cpd-color-bg-action-secondary-rest": "#2f3136",
"--cpd-color-bg-critical-primary": "#fd3f3c",
"--cpd-color-bg-critical-subtle": "#745862",
"--cpd-color-bg-critical-hovered": "#fd3f3c",
"--cpd-color-bg-accent-rest": "#4cb387",
"--cpd-color-text-primary": "#dcddde",
"--cpd-color-text-secondary": "#b9bbbe",
"--cpd-color-text-action-accent": "#b9bbbe",
"--cpd-color-text-critical-primary": "#fd3f3c",
"--cpd-color-text-success-primary": "#4cb387",
"--cpd-color-icon-primary": "#dcddde",
"--cpd-color-icon-secondary": "#dcddde",
"--cpd-color-icon-tertiary": "#a7a0a7",
"--cpd-color-icon-accent-tertiary": "#4cb387",
"--cpd-color-border-interactive-primary": "#5d6064",
"--cpd-color-border-interactive-secondary": "#5d6064",
"--cpd-color-border-critical-primary": "#fd3f3c",
"--cpd-color-border-success-subtle": "#4cb387"
}
},
{
"name": "Electric Blue",
"is_dark": false,
"colors": {
"accent-color": "#3596fc",
"primary-color": "#368bd6",
"warning-color": "#ff4b55",
"sidebar-color": "#27303a",
"roomlist-background-color": "#f3f8fd",
"roomlist-text-color": "#2e2f32",
"roomlist-text-secondary-color": "#61708b",
"roomlist-highlights-color": "#ffffff",
"roomlist-separator-color": "#e3e8f0",
"timeline-background-color": "#ffffff",
"timeline-text-color": "#2e2f32",
"timeline-text-secondary-color": "#61708b",
"timeline-highlights-color": "#f3f8fd",
"username-colors": [
"#ff0000",
"#ff7f00",
"#ffff00",
"#00ff00",
"#0000ff",
"#4b0082",
"#9400d3",
"#ff1493"
],
"avatar-background-colors": [
"#cc0000",
"#cc6600",
"#cccc00",
"#00cc00",
"#0000cc",
"#3b0066",
"#7a00b3",
"#cc1077"
]
},
"compound": {
"--cpd-color-icon-accent-tertiary": "var(--cpd-color-blue-800)",
"--cpd-color-text-action-accent": "var(--cpd-color-blue-900)"
}
},
{
"name": "Everforest dark hard",
"is_dark": true,
"colors": {
"accent-color": "#a7c080",
"primary-color": "#a7c080",
"warning-color": "#e67e80",
"sidebar-color": "#323d43",
"roomlist-background-color": "#2f383e",
"roomlist-text-color": "#d3c6aa",
"roomlist-text-secondary-color": "#d3c6aa",
"roomlist-highlights-color": "#4b565c",
"roomlist-separator-color": "#4b565c",
"timeline-background-color": "#2b3339",
"timeline-text-color": "#d3c6aa",
"secondary-content": "#d3c6aa",
"tertiary-content": "#d3c6aa",
"timeline-text-secondary-color": "#a7c080",
"timeline-highlights-color": "#4b565c",
"reaction-row-button-selected-bg-color": "#4b565c"
}
},
{
"name": "aXion1337 Dark",
"is_dark": true,
"colors": {
"accent-color": "#bd93f9",
"primary-color": "#fe8019",
"warning-color": "#fb4934",
"sidebar-color": "#282828",
"roomlist-background-color": "#1d2021",
"roomlist-text-color": "#a89984",
"roomlist-text-secondary-color": "#00ff00",
"roomlist-highlights-color": "#00000030",
"roomlist-separator-color": "#4d4d4d90",
"timeline-background-color": "#282828",
"timeline-text-color": "#ebdbb2",
"secondary-content": "#928374",
"tertiary-content": "#928374",
"quinary-content": "#504945",
"timeline-text-secondary-color": "#a89984",
"timeline-highlights-color": "#00000030",
"reaction-row-button-selected-bg-color": "#689d6a",
"menu-selected-color": "#504945",
"icon-button-color": "#928374",
"accent": "#689d6a",
"alert": "#cc241d",
"username-colors": [
"#cc241d",
"#98971a",
"#d79921",
"#458588",
"#b16286",
"#689d6a",
"#a89984",
"#d65d0e"
]
}
},
{
"name": "aXion1337 Light",
"is_dark": false,
"colors": {
"accent-color": "#8f3f71",
"primary-color": "#af3a03",
"warning-color": "#9d0006",
"sidebar-color": "#ebdbb2",
"roomlist-background-color": "#f2e5bc",
"roomlist-text-color": "#665c54",
"roomlist-text-secondary-color": "#427b58",
"roomlist-highlights-color": "#00000012",
"roomlist-separator-color": "#bdae9330",
"timeline-background-color": "#fbf1c7",
"timeline-text-color": "#3c3836",
"secondary-content": "#7c6f64",
"tertiary-content": "#7c6f64",
"quinary-content": "#d5c4a1",
"timeline-text-secondary-color": "#665c54",
"timeline-highlights-color": "#00000012",
"reaction-row-button-selected-bg-color": "#8ec07c",
"menu-selected-color": "#d5c4a1",
"icon-button-color": "#7c6f64",
"accent": "#427b58",
"alert": "#9d0006",
"username-colors": [
"#9d0006",
"#79740e",
"#b57614",
"#076678",
"#8f3f71",
"#427b58",
"#665c54",
"#af3a03"
]
}
},
{
"name": "Ocean Depths",
"is_dark": false,
"colors": {
"accent-color": "#2d8b8b",
"accent": "#2d8b8b",
"primary-color": "#a8dadc",
"warning-color": "#457b9d",
"alert": "#70555a",
"sidebar-color": "#e3ebe0",
"roomlist-background-color": "#eaf2e7",
"roomlist-text-color": "#1a2332",
"roomlist-text-secondary-color": "#656e74",
"roomlist-highlights-color": "#00000010",
"roomlist-separator-color": "#0000001f",
"timeline-background-color": "#f1faee",
"timeline-text-color": "#1a2332",
"secondary-content": "#656e74",
"tertiary-content": "#656e74",
"timeline-text-secondary-color": "#656e74",
"timeline-highlights-color": "#00000010",
"reaction-row-button-selected-bg-color": "#99c8c1",
"menu-selected-color": "#b6d9d0",
"focus-bg-color": "#b6d9d0",
"room-highlight-color": "#b6d9d0",
"other-user-pill-bg-color": "#d4ede7",
"icon-button-color": "#656e74",
"username-colors": [
"#2d8b8b",
"#a8dadc",
"#457b9d",
"#276c70",
"#7da3a9",
"#365c78",
"#6ab2b4",
"#76aabc"
]
}
},
{
"name": "Sunset Boulevard",
"is_dark": true,
"colors": {
"accent-color": "#e76f51",
"accent": "#e76f51",
"primary-color": "#f4a261",
"warning-color": "#e9c46a",
"alert": "#d6453a",
"sidebar-color": "#1b323c",
"roomlist-background-color": "#213c47",
"roomlist-text-color": "#f6efe6",
"roomlist-text-secondary-color": "#adb4b3",
"roomlist-highlights-color": "#ffffff14",
"roomlist-separator-color": "#ffffff26",
"timeline-background-color": "#264653",
"timeline-text-color": "#f6efe6",
"secondary-content": "#adb4b3",
"tertiary-content": "#adb4b3",
"timeline-text-secondary-color": "#adb4b3",
"timeline-highlights-color": "#ffffff14",
"reaction-row-button-selected-bg-color": "#7d5852",
"menu-selected-color": "#605252",
"focus-bg-color": "#605252",
"room-highlight-color": "#605252",
"other-user-pill-bg-color": "#786b59",
"icon-button-color": "#adb4b3",
"username-colors": [
"#e76f51",
"#f4a261",
"#e9c46a",
"#ec957e",
"#f5b989",
"#eed395",
"#ee8859",
"#eeb366"
]
}
},
{
"name": "Forest Canopy",
"is_dark": false,
"colors": {
"accent-color": "#2d4a2b",
"accent": "#2d4a2b",
"primary-color": "#7d8471",
"warning-color": "#a4ac86",
"alert": "#703126",
"sidebar-color": "#ebeae7",
"roomlist-background-color": "#f2f2ef",
"roomlist-text-color": "#22301f",
"roomlist-text-secondary-color": "#6e766a",
"roomlist-highlights-color": "#00000010",
"roomlist-separator-color": "#0000001f",
"timeline-background-color": "#faf9f6",
"timeline-text-color": "#22301f",
"secondary-content": "#6e766a",
"tertiary-content": "#6e766a",
"timeline-text-secondary-color": "#6e766a",
"timeline-highlights-color": "#00000010",
"reaction-row-button-selected-bg-color": "#9eaa9b",
"menu-selected-color": "#bcc4b9",
"focus-bg-color": "#bcc4b9",
"room-highlight-color": "#bcc4b9",
"other-user-pill-bg-color": "#c8cac1",
"icon-button-color": "#6e766a",
"username-colors": [
"#2d4a2b",
"#7d8471",
"#a4ac86",
"#2a4227",
"#626b58",
"#768162",
"#55674e",
"#90987c"
]
}
},
{
"name": "Modern Minimalist",
"is_dark": false,
"colors": {
"accent-color": "#36454f",
"accent": "#36454f",
"primary-color": "#708090",
"warning-color": "#d3d3d3",
"alert": "#752e39",
"sidebar-color": "#f0f0f0",
"roomlist-background-color": "#f7f7f7",
"roomlist-text-color": "#1c252b",
"roomlist-text-secondary-color": "#6b7175",
"roomlist-highlights-color": "#00000010",
"roomlist-separator-color": "#0000001f",
"timeline-background-color": "#ffffff",
"timeline-text-color": "#1c252b",
"secondary-content": "#6b7175",
"tertiary-content": "#6b7175",
"timeline-text-secondary-color": "#6b7175",
"timeline-highlights-color": "#00000010",
"reaction-row-button-selected-bg-color": "#a5abb0",
"menu-selected-color": "#c3c7ca",
"focus-bg-color": "#c3c7ca",
"room-highlight-color": "#c3c7ca",
"other-user-pill-bg-color": "#c6ccd3",
"icon-button-color": "#6b7175",
"username-colors": [
"#36454f",
"#708090",
"#d3d3d3",
"#2e3b44",
"#576572",
"#939698",
"#536270",
"#a2aab2"
]
}
},
{
"name": "Golden Hour",
"is_dark": true,
"colors": {
"accent-color": "#f4a900",
"accent": "#f4a900",
"primary-color": "#c1666b",
"warning-color": "#d4b896",
"alert": "#dd650e",
"sidebar-color": "#352e2a",
"roomlist-background-color": "#403732",
"roomlist-text-color": "#f7f0e6",
"roomlist-text-secondary-color": "#bab2aa",
"roomlist-highlights-color": "#ffffff14",
"roomlist-separator-color": "#ffffff26",
"timeline-background-color": "#4a403a",
"timeline-text-color": "#f7f0e6",
"secondary-content": "#bab2aa",
"tertiary-content": "#bab2aa",
"timeline-text-secondary-color": "#bab2aa",
"timeline-highlights-color": "#ffffff14",
"reaction-row-button-selected-bg-color": "#966f20",
"menu-selected-color": "#7d6029",
"focus-bg-color": "#7d6029",
"room-highlight-color": "#7d6029",
"other-user-pill-bg-color": "#7a4f4e",
"icon-button-color": "#bab2aa",
"username-colors": [
"#f4a900",
"#c1666b",
"#d4b896",
"#f5be45",
"#d18f90",
"#e0ccb2",
"#da8836",
"#ca8f80"
]
}
},
{
"name": "Arctic Frost",
"is_dark": false,
"colors": {
"accent-color": "#4a6fa5",
"accent": "#4a6fa5",
"primary-color": "#d4e4f7",
"warning-color": "#c0c0c0",
"alert": "#804569",
"sidebar-color": "#ebebeb",
"roomlist-background-color": "#f2f2f2",
"roomlist-text-color": "#1e2c40",
"roomlist-text-secondary-color": "#6b7481",
"roomlist-highlights-color": "#00000010",
"roomlist-separator-color": "#0000001f",
"timeline-background-color": "#fafafa",
"timeline-text-color": "#1e2c40",
"secondary-content": "#6b7481",
"tertiary-content": "#6b7481",
"timeline-text-secondary-color": "#6b7481",
"timeline-highlights-color": "#00000010",
"reaction-row-button-selected-bg-color": "#abbbd4",
"menu-selected-color": "#c5d0e0",
"focus-bg-color": "#c5d0e0",
"room-highlight-color": "#c5d0e0",
"other-user-pill-bg-color": "#ebf1f9",
"icon-button-color": "#6b7481",
"username-colors": [
"#4a6fa5",
"#d4e4f7",
"#c0c0c0",
"#3d5b87",
"#9dadc0",
"#878c93",
"#8faace",
"#cad2dc"
]
}
},
{
"name": "Desert Rose",
"is_dark": true,
"colors": {
"accent-color": "#d4a5a5",
"accent": "#d4a5a5",
"primary-color": "#b87d6d",
"warning-color": "#e8d5c4",
"alert": "#cb6369",
"sidebar-color": "#432132",
"roomlist-background-color": "#50283c",
"roomlist-text-color": "#f6ece4",
"roomlist-text-secondary-color": "#c0aaad",
"roomlist-highlights-color": "#ffffff14",
"roomlist-separator-color": "#ffffff26",
"timeline-background-color": "#5d2e46",
"timeline-text-color": "#f6ece4",
"secondary-content": "#c0aaad",
"tertiary-content": "#c0aaad",
"timeline-text-secondary-color": "#c0aaad",
"timeline-highlights-color": "#ffffff14",
"reaction-row-button-selected-bg-color": "#936471",
"menu-selected-color": "#815262",
"focus-bg-color": "#815262",
"room-highlight-color": "#815262",
"other-user-pill-bg-color": "#814e56",
"icon-button-color": "#c0aaad",
"username-colors": [
"#d4a5a5",
"#b87d6d",
"#e8d5c4",
"#debab8",
"#cb9e91",
"#edddcf",
"#c69189",
"#d0a998"
]
}
},
{
"name": "Tech Innovation",
"is_dark": false,
"colors": {
"accent-color": "#0066ff",
"accent": "#0066ff",
"primary-color": "#00ffff",
"warning-color": "#1e1e1e",
"alert": "#57409a",
"sidebar-color": "#f0f0f0",
"roomlist-background-color": "#f7f7f7",
"roomlist-text-color": "#1e1e1e",
"roomlist-text-secondary-color": "#6d6d6d",
"roomlist-highlights-color": "#00000010",
"roomlist-separator-color": "#0000001f",
"timeline-background-color": "#ffffff",
"timeline-text-color": "#1e1e1e",
"secondary-content": "#6d6d6d",
"tertiary-content": "#6d6d6d",
"timeline-text-secondary-color": "#6d6d6d",
"timeline-highlights-color": "#00000010",
"reaction-row-button-selected-bg-color": "#8cbaff",
"menu-selected-color": "#b2d1ff",
"focus-bg-color": "#b2d1ff",
"room-highlight-color": "#b2d1ff",
"other-user-pill-bg-color": "#99ffff",
"icon-button-color": "#6d6d6d",
"username-colors": [
"#0066ff",
"#00ffff",
"#1e1e1e",
"#0950bc",
"#09bcbc",
"#1e1e1e",
"#00b2ff",
"#0f8e8e"
]
}
},
{
"name": "Botanical Garden",
"is_dark": false,
"colors": {
"accent-color": "#4a7c59",
"accent": "#4a7c59",
"primary-color": "#f9a620",
"warning-color": "#b7472a",
"alert": "#804c3f",
"sidebar-color": "#e6e4df",
"roomlist-background-color": "#eeece6",
"roomlist-text-color": "#22331f",
"roomlist-text-secondary-color": "#6c7667",
"roomlist-highlights-color": "#00000010",
"roomlist-separator-color": "#0000001f",
"timeline-background-color": "#f5f3ed",
"timeline-text-color": "#22331f",
"secondary-content": "#6c7667",
"tertiary-content": "#6c7667",
"timeline-text-secondary-color": "#6c7667",
"timeline-highlights-color": "#00000010",
"reaction-row-button-selected-bg-color": "#a8bdaa",
"menu-selected-color": "#c2cfc1",
"focus-bg-color": "#c2cfc1",
"room-highlight-color": "#c2cfc1",
"other-user-pill-bg-color": "#f7d49b",
"icon-button-color": "#6c7667",
"username-colors": [
"#4a7c59",
"#f9a620",
"#b7472a",
"#3e6648",
"#b88420",
"#834026",
"#a2913c",
"#d87625"
]
}
},
{
"name": "Midnight Galaxy",
"is_dark": false,
"colors": {
"accent-color": "#2b1e3e",
"accent": "#2b1e3e",
"primary-color": "#4a4e8f",
"warning-color": "#a490c2",
"alert": "#6e1930",
"sidebar-color": "#d8d8eb",
"roomlist-background-color": "#dfdff2",
"roomlist-text-color": "#241a34",
"roomlist-text-secondary-color": "#686179",
"roomlist-highlights-color": "#00000010",
"roomlist-separator-color": "#0000001f",
"timeline-background-color": "#e6e6fa",
"timeline-text-color": "#241a34",
"secondary-content": "#686179",
"tertiary-content": "#686179",
"timeline-text-secondary-color": "#686179",
"timeline-highlights-color": "#00000010",
"reaction-row-button-selected-bg-color": "#928ca5",
"menu-selected-color": "#aeaac2",
"focus-bg-color": "#aeaac2",
"room-highlight-color": "#aeaac2",
"other-user-pill-bg-color": "#a8a9cf",
"icon-button-color": "#686179",
"username-colors": [
"#2b1e3e",
"#4a4e8f",
"#a490c2",
"#291d3b",
"#3f3e74",
"#776790",
"#3a3666",
"#776fa8"
]
}
}
],
"feature_group_calls": true
},
"show_labs_settings": true,
"sso_redirect_options": {
"immediate": false
}
}
@@ -0,0 +1,52 @@
# Laedt die Build-Artefakte resumefaehig vor, die app-builder sonst selbst (ohne
# Resume) von GitHub laedt: das CDN (185.199.109.x) reisst im Lab-Gast groessere
# Transfers reproduzierbar mitten im Stream ab (Jobs 415/416/424, wsarecv-Reset).
# curl -C - setzt nach jedem Abriss am letzten Byte fort; einmal geladen liegen
# die Artefakte in den persistenten Caches (ELECTRON_CACHE/ELECTRON_BUILDER_CACHE)
# und werden nie erneut geholt.
$ErrorActionPreference = 'Stop'
function Get-Resumable([string]$Url, [string]$Out) {
foreach ($i in 1..8) {
& curl.exe -sSL -C - -o $Out $Url
if ($LASTEXITCODE -eq 0) { return }
Start-Sleep 5
}
throw "Download nach 8 Versuchen gescheitert: $Url"
}
# --- 1. Electron-Binary -> ELECTRON_CACHE ------------------------------------
$pkg = Join-Path $PSScriptRoot '..\package.json'
$ev = (& node -p "require(process.argv[1]).devDependencies.electron" $pkg) -replace '[\^~]', ''
New-Item -ItemType Directory -Force $env:ELECTRON_CACHE | Out-Null
$zip = Join-Path $env:ELECTRON_CACHE "electron-v$ev-win32-x64.zip"
if (-not (Test-Path $zip)) {
Get-Resumable "https://github.com/electron/electron/releases/download/v$ev/electron-v$ev-win32-x64.zip" "$zip.part"
Move-Item -Force "$zip.part" $zip
}
# --- 2. electron-builder-Werkzeuge -> ELECTRON_BUILDER_CACHE -----------------
# Zielstruktur laut app-builder-Quellcode (artifactDownloader.go) + electron-builder
# 26.9.1 (binDownload.js): der Cache-Schluessel ist "<release>-<dateiname-ohne-ext>",
# also GEDOPPELT (z.B. nsis-3.0.4.1-nsis-3.0.4.1), und liegt unter dem Teil des
# Namens VOR dem ersten Bindestrich als Unterverzeichnis - nsis-resources landet
# deshalb ebenfalls unter nsis\. Job 431 bewies den Miss mit einfachem Namen.
$sevenZa = Join-Path $PSScriptRoot '..\..\..\node_modules\7zip-bin\win\x64\7za.exe'
function Get-EbArtifact([string]$Release, [string]$SubDir) {
$dest = Join-Path $env:ELECTRON_BUILDER_CACHE (Join-Path $SubDir "$Release-$Release")
if (Test-Path $dest) { return }
$tmp = Join-Path $env:TEMP "$Release.7z"
Get-Resumable "https://github.com/electron-userland/electron-builder-binaries/releases/download/$Release/$Release.7z" $tmp
New-Item -ItemType Directory -Force $dest | Out-Null
& $sevenZa x -y "-o$dest" $tmp | Out-Null
if ($LASTEXITCODE -ne 0) { Remove-Item -Recurse -Force $dest; throw "Entpacken gescheitert: $tmp" }
}
# Versionen passend zu electron-builder 26.9.1 (winCodeSign aus dem Job-424-Trace,
# nsis/nsis-resources samt Checksummen aus nsisUtil.js). Bei einem kuenftigen
# Mismatch laedt app-builder selbst und der Fehler nennt die richtige Version.
Get-EbArtifact 'winCodeSign-2.6.0' 'winCodeSign'
Get-EbArtifact 'nsis-3.0.4.1' 'nsis'
Get-EbArtifact 'nsis-resources-3.4.1' 'nsis'
Write-Output "prefetch ok: electron v$ev + winCodeSign/nsis in den Caches"
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 395 KiB

+27
View File
@@ -48,6 +48,8 @@ interface Variant extends Metadata {
"appId": string;
"linux.executableName"?: string;
"linux.deb.name"?: string;
"mac.icon"?: string;
"dmg.badgeIcon"?: string;
"protocols": string[];
}
@@ -202,6 +204,31 @@ if (variant["linux.deb.name"]) {
config.deb.fpm.push("--name", variant["linux.deb.name"]);
}
/**
* ThreadNet-Fork: Icon-Pfade fuer macOS ueberschreibbar machen.
*
* Upstream setzt `mac.icon` auf das Icon-Composer-Bundle `build/icon.icon`
* (macOS 26). Dessen Verarbeitung ruft `actool` auf, das es nur mit dem vollen
* Xcode gibt (~10 GB, App-Store-Login) - mit blossen CommandLineTools scheitert
* damit JEDER macOS-Build, nicht nur das DMG:
*
* Failed to check actool version. Is Xcode 26 or higher installed?
*
* Ueber die Variante laesst sich stattdessen das klassische `.icns` waehlen.
* Bewusst hier als Variantenoption statt als Aenderung an den Defaults oben:
* So bleibt Upstreams Wert unberuehrt und ein Upstream-Merge erzeugt keinen
* Konflikt an dieser Zeile.
*
* Preis: kein macOS-26-Icon-Rendering. Ohne Xcode gaebe es ohnehin keinen Build.
*/
if (variant["mac.icon"]) {
config.mac.icon = variant["mac.icon"];
}
if (variant["dmg.badgeIcon"]) {
config.dmg.badgeIcon = variant["dmg.badgeIcon"];
}
/**
* Allow specifying windows signing cert via env vars
* @param {string} process.env.ED_SIGNTOOL_SUBJECT_NAME
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "element-desktop",
"productName": "Element",
"productName": "ThreadNet",
"main": "lib/electron-main.js",
"exports": "./lib/electron-main.js",
"version": "1.12.17",
+1 -1
View File
@@ -13,7 +13,7 @@
"disable_login_language_selector": false,
"disable_3pid_login": false,
"force_verification": false,
"brand": "Element",
"brand": "aXion1337.Chat",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_rest_url": "https://scalar.vector.im/api",
"integrations_widgets_urls": [
+1 -1
View File
@@ -125,7 +125,7 @@
"@babel/preset-react": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@casualbot/jest-sonar-reporter": "2.7.0",
"@sorb/threadnet-call-embedded": "0.19.2-threadnet.5",
"@sorb/threadnet-call-embedded": "0.19.2-threadnet.12",
"@element-hq/element-web-playwright-common": "workspace:*",
"@fetch-mock/jest": "^0.2.20",
"@jest/globals": "^30.2.0",
@@ -10,3 +10,12 @@ Please see LICENSE files in the repository root for full details.
.mx_HelpUserSettingsTab_accessTokenDetails {
width: fit-content;
}
/* ThreadNet-Fork: Attribution unter den Versionsangaben. Zurueckhaltend
gesetzt - sie soll auffindbar sein, nicht um Aufmerksamkeit konkurrieren
mit der Version darueber, die Nutzer hier tatsaechlich suchen. */
.mx_HelpUserSettingsTab_attribution {
margin-top: var(--cpd-space-2x);
color: var(--cpd-color-text-secondary);
font: var(--cpd-font-body-sm-regular);
}
+3 -3
View File
@@ -1,8 +1,8 @@
{
"name": "Element",
"short_name": "Element",
"name": "ThreadNet",
"short_name": "ThreadNet",
"display": "standalone",
"theme_color": "#76CFA6",
"theme_color": "#ed4f4c",
"start_url": "index.html",
"icons": [
{
Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 B

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

+9 -4
View File
@@ -20,7 +20,11 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
branding: {
logo_link_url: "https://element.io",
auth_header_logo_url: "themes/element/img/logos/element-logo.svg",
welcome_background_url: "themes/element/img/backgrounds/lake.jpg",
// ThreadNet-Fork: eigenes Titelbild statt Elements lake.jpg. Steht hier im
// Default und nicht in element-values.yaml, weil die Bilddatei ohnehin nur
// ueber einen Build in den Container kommt - eine zusaetzliche Config-Zeile
// waere eine zweite Stelle, die mitwandern muesste.
welcome_background_url: "themes/element/img/backgrounds/alpenglow.jpg",
},
help_url: "https://element.io/help",
help_encryption_url: "https://element.io/help#encryption",
@@ -44,9 +48,10 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
// be preferred over their config.
desktopBuilds: {
available: true,
// eslint-disable-next-line @typescript-eslint/no-require-imports
logo: require("../res/img/element-desktop-logo.svg").default,
url: "https://element.io/get-started",
// ThreadNet-Fork: eigene Marke und eigener Download-Pfad statt
// element.io - wir liefern eigene Desktop-Builds aus.
logo: "vector-icons/512.png",
url: "https://git.lab/axion1337.chat/ThreadNet-Web/-/releases",
},
feedback: {
@@ -32,8 +32,8 @@ export const ErrorView: React.FC<IProps> = ({ title, messages, footer, children
<img
className="mx_ErrorView_logo"
height="160"
src="themes/element/img/logos/element-app-logo.png"
alt="Element"
src="vector-icons/512.png"
alt="ThreadNet"
/>
<div className="mx_ErrorView_container">
<Heading size="md" weight="semibold">
@@ -106,34 +106,42 @@ export default class HelpUserSettingsTab extends React.Component<EmptyObject, IS
<SettingsSubsection heading={_t("common|credits")}>
<SettingsSubsectionText>
<ul>
{/* ThreadNet-Fork: eigenes Titelbild, deshalb eigene Danksagung.
Bewusst OHNE _t(): der Schluessel credits|default_cover_photo
steckt in 32 Sprachdateien, 31 davon nennen Elements Fotografen
namentlich. Wuerden wir nur en/de anpassen, stuende in 29
Sprachen eine falsche Attribution und die uebrigen 29 koennen
wir nicht pflegen, sie kommen aus Elements Uebersetzungsdienst.
Ein Fotografenname und eine Lizenzbezeichnung werden ohnehin
nicht uebersetzt. Der ungenutzte Schluessel bleibt in den
Sprachdateien stehen ihn aus 32 Dateien zu entfernen waere
Laerm im naechsten Upstream-Merge. */}
<li>
{_t(
"credits|default_cover_photo",
{},
{
photo: (sub) => (
The{" "}
<ExternalLink
href="themes/element/img/backgrounds/lake.jpg"
href="themes/element/img/backgrounds/alpenglow.jpg"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
author: (sub) => (
<ExternalLink href="https://www.flickr.com/golan">{sub}</ExternalLink>
),
terms: (sub) => (
default cover photo
</ExternalLink>{" "}
is by <ExternalLink href="https://unsplash.com/@heytowner">John Towner</ExternalLink> on{" "}
<ExternalLink
href="https://creativecommons.org/licenses/by-sa/4.0/"
href="https://unsplash.com/photos/JgOeRuGD_Y4"
rel="noreferrer noopener"
target="_blank"
>
{sub}
Unsplash
</ExternalLink>
),
},
)}
, used under the{" "}
<ExternalLink
href="https://unsplash.com/license"
rel="noreferrer noopener"
target="_blank"
>
Unsplash License
</ExternalLink>
.
</li>
<li>
{_t(
@@ -259,6 +267,15 @@ export default class HelpUserSettingsTab extends React.Component<EmptyObject, IS
{cryptoVersion}
<br />
</CopyableText>
{/* ThreadNet-Fork: Attribution an Element. Bewusst NICHT in
getVersionTextToCopy der Text dort geht in Fehlerberichte,
dort ist die Herkunft des Forks nur Rauschen. Ebenso bewusst
ohne _t(): ein Markenhinweis wird nicht übersetzt, und jeder
zusätzliche i18n-Schlüssel ist Reibung beim Upstream-Merge. */}
<div className="mx_HelpUserSettingsTab_attribution">
ThreadNet powered by{" "}
<ExternalLink href="https://element.io">Element</ExternalLink>
</div>
{updateButton}
</SettingsSubsectionText>
</SettingsSubsection>
+4 -3
View File
@@ -2,19 +2,20 @@
<html lang="en" style="height: 100%;">
<head>
<meta charset="utf-8">
<title>Element</title>
<title>ThreadNet</title>
<link rel="apple-touch-icon" sizes="120x120" href="<%= require('../../res/vector-icons/120.png') %>">
<link rel="apple-touch-icon" sizes="144x144" href="<%= require('../../res/vector-icons/144.png') %>">
<link rel="apple-touch-icon" sizes="152x152" href="<%= require('../../res/vector-icons/152.png') %>">
<link rel="apple-touch-icon" sizes="180x180" href="<%= require('../../res/vector-icons/180.png') %>">
<link rel="icon" href="vector-icons/favicon.ico" sizes="any">
<link rel="manifest" href="manifest.json">
<meta name="referrer" content="no-referrer">
<link rel="icon" type="image/png" sizes="24x24" href="<%= require('../../res/vector-icons/24.png') %>">
<link rel="icon" type="image/png" sizes="144x144" href="<%= require('../../res/vector-icons/144.png') %>">
<link rel="icon" type="image/png" sizes="512x512" href="<%= require('../../res/vector-icons/512.png') %>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-title" content="Element">
<meta name="application-name" content="Element">
<meta name="apple-mobile-web-app-title" content="ThreadNet">
<meta name="application-name" content="ThreadNet">
<meta name="theme-color" content="#ffffff">
<meta property="og:image" content="<%= og_image_url %>" />
<meta http-equiv="Content-Security-Policy" content="
+216
View File
@@ -0,0 +1,216 @@
# ThreadNet-Web: Fork-Anpassungen für axion1337.chat
Dieses Dokument beschreibt alles, was dieser Fork gegenüber Upstream Element Web ändert. Der
Rest der `docs/`-Ordner-Dateien und das Root-`README.md` sind unverändertes Upstream-Material
und beschreiben absichtlich **nicht** diese Anpassungen - diese Datei ist der zentrale
Anlaufpunkt dafür.
## 1. Discord-Style Room-List (Call-Teilnehmer in der Raumliste)
Zeigt aktive Call-Teilnehmer direkt in der Raumliste an (ähnlich Discords Voice-Channel-UI),
statt nur einen generischen "Call läuft"-Indikator. Betroffene Komponenten:
`apps/web/src/room-list/RoomListItemView/RoomListItemView.tsx` und
`RoomListItemView.module.css` (vertikale Ausrichtung der Teilnehmer-Avatare).
## 2. Client-seitiges ClamAV-Content-Scanning (Issue #19-Erweiterung)
Der Server-seitige Content-Scanner (Synapse-Modul, siehe gitops-Repo
[`docs/deployment-guides/06-moderation-content-scanning.md`](https://rohana.axion1337.de/sorb/axion1337.chat-gitops/src/branch/main/docs/deployment-guides/06-moderation-content-scanning.md))
sieht bei Ende-zu-Ende-verschlüsselten Räumen nur Ciphertext - eine strukturelle Grenze, kein
Bug. Da dieser Client bereits geforkt wird, scannt er stattdessen selbst, auf beiden Seiten:
- **Senden** (`apps/web/src/ContentMessages.ts`, `uploadFile()`): liest die Datei als
ArrayBuffer, scannt sie über `scanContent()` **bevor** verschlüsselt/hochgeladen wird - der
Upload wird bei Treffer gar nicht erst gestartet.
- **Empfangen** (`apps/web/src/utils/DecryptFile.ts`, `decryptFile()`): scannt die
entschlüsselten Bytes direkt nach dem Entschlüsseln, bevor das Blob an die UI zurückgegeben
wird - schützt auch vor Dateien von unveränderten/fremden Matrix-Clients, die diesen Patch
nicht haben.
- Gemeinsame Scan-Logik: `apps/web/src/utils/ContentScanner.ts` - ruft den (im gitops-Repo
deployten) `clamav-http-scanner`-Dienst per `fetch("/_scan", ...)` auf, mit dem eigenen
Matrix-Access-Token als Bearer-Auth. Fail-open bei Netzwerk-/Scanner-Fehlern (blockiert
Uploads/Downloads nicht bei einem Ausfall des Scanners).
- Neuer Fehlertyp `ContentScanRejectedError`, verdrahtet durch die bestehenden
Fehler-Rendering-Pfade in `MImageBody.tsx`, `MAudioBody.tsx`, `VideoBodyViewModel.ts`,
`FileBodyViewModel.ts`.
- Live getestet inkl. Hostile-Sender-Simulation (Datei per rohem API-Call ohne diesen Patch
gesendet, Empfangs-Hook hat trotzdem geblockt).
### ⚠️ Bekannte Lücke: Electron/Desktop (Issue #2)
Dieser Fix ist bestätigt nur für den **Web-Client-Build** wirksam. Der Electron-Desktop-Client
bekommt ihn aktuell **nicht automatisch** - die CI-Workflow-Kette (`build_ew`-Job) checkt
weiterhin `element-hq/element-web` (Upstream) statt diesen Fork aus, und es ist kein GitHub-
Actions-Runner für dieses Repo registriert. Der aktuell veröffentlichte Desktop-Build
(`desktop-v1.12.17-clientscan` Release) wurde manuell gebaut, nicht automatisiert. Details und
Fix-Plan: [Issue #2](https://rohana.axion1337.de/sorb/ThreadNet-Web/issues/2).
## 3. Build-Fixes (historisch, Issue #12)
Zwei Bugs blockierten einen vollständigen `docker build` von Grund auf (mussten für den
Client-Scanning-Rebuild oben behoben werden): (1) mehrere Shell-Skripte waren mit Modus 644
statt 755 committet (nicht ausführbar); (2) der `matrix-js-sdk#develop`-Git-Ref-Pin in
`pnpm-lock.yaml` war veraltet (fehlte `src/oidc/authorize.ts`, das `apps/web` importiert).
Beide behoben.
## 4. Merge-Reibung: was ein Upstream-Update wirklich kostet
*Arbeitspaket 3 aus ThreadNet-Web#7. Gemessen am 2026-08-06, nicht geschätzt.*
### Die unangenehme Grundlage zuerst
**Dieses Repo enthält keine Upstream-Historie.** Am 2026-05-10 wurde ein kompletter
Element-Web-Baum importiert — in `3da3635`, zusammen mit dem ersten eigenen Feature im
selben Commit. Davor liegt nur ein `Initial commit` mit zwei Dateien.
⚠️ **Korrektur 2026-08-19: Der Import ist NICHT der Tag `v1.12.17`.** Gemessen nach
Aufnahme von `upstream` als zweitem Remote (management #0099, Schritt 2):
- `git diff v1.12.17 3da3635` ergibt **783 Dateien, +17443/10371** — weit mehr als
unser damaliges Feature.
- **115 Dateien existieren nur bei uns, 62 nur im Tag.** Darunter MVVM-Dateien wie
`apps/web/src/viewmodels/room/timeline/event-tile/body/TextualBodyViewModel.tsx` und
`packages/shared-components/src/core/roving/RovingTabIndex.tsx`: im Tag `v1.12.17`
**nicht vorhanden**, in `v1.12.26` und in unserem Import **vorhanden**.
- Der `CHANGELOG.md` unseres Imports endet bei 1.12.17 (2026-04-30) — er wird erst
beim Release fortgeschrieben.
Zusammen heißt das: Die Grundlage ist ein **`develop`-Stand nach dem 1.12.17-Release**
(1.12.18 erschien am 2026-05-12), nicht der Tag. Praktische Folge: **Ein Graft auf
`v1.12.17` wäre falsch** und würde jede künftige Zusammenführung auf eine erfundene
Ahnenreihe stellen. Wer den gemeinsamen Vorfahren nachträglich herstellen will, muss
den passenden `develop`-Commit suchen — das braucht die volle Upstream-Historie, ein
flacher Fetch der Tags genügt dafür nicht.
Daraus folgt das Wesentliche: **es gibt keinen gemeinsamen Vorfahren mit
`element-hq/element-web`.** Ein `git merge upstream/develop` ist nicht möglich; mit
`--allow-unrelated-histories` erzwungen, kollidiert praktisch jede Datei. Wer „mal
eben Upstream nachziehen" sagt, meint in diesem Repo also: neuen Upstream-Stand
beschaffen und unsere Änderungen darauf neu auftragen.
Das ist der Grund, warum die Zahl unten überhaupt zählt — sie ist der Aufwand jedes
Updates.
### Unser Delta: 102 Dateien, davon 12 kritische
`git diff --name-only 3da3635..main`:
| Menge | Bereich | Konfliktrisiko |
|---|---|---|
| 46 | CI/Build (`.github/`, `.gitlab-ci.yml`, `dockerbuild/`) | gering — eigene Strecke, Upstreams Workflows brauchen wir nicht |
| 22 | Config, Lockfiles, Upstream-Varianten | mittel — `pnpm-lock.yaml` konfliktet immer, wird aber regeneriert |
| 13 | Web-Assets (`apps/web/res/`) | gering — meist eigene Dateien |
| 4 + 3 | eigene Icons und `apps/desktop/axion1337/` | **keins** — kein Upstream-Pendant |
| **12** | **Upstream-Quellcode** | **hier entsteht die Arbeit** |
### Die 12 Dateien, und warum sie angefasst wurden
**Branding (5)** — flach, gut isolierbar:
- `apps/web/src/SdkConfig.ts` — Defaults für `brand`, `welcome_background_url`, `desktopBuilds`
- `apps/web/src/vector/index.html``<title>`, Favicon-Link, PWA-Namen
- `apps/web/src/async-components/structures/ErrorView.tsx` — Logo der Fehlerseite
- `apps/web/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx` — Attribution + Danksagung
- `apps/web/src/i18n/strings/en_EN.json` — einzelne Strings
**ClamAV-Client-Scanning (7)** — tief in der Medien-Pipeline:
- `apps/web/src/ContentMessages.ts`, `utils/ContentScanner.ts`, `utils/DecryptFile.ts`
- `apps/web/src/components/views/messages/MImageBody.tsx`, `MAudioBody.tsx`
- `apps/web/src/viewmodels/message-body/FileBodyViewModel.ts`, `VideoBodyViewModel.ts`
### Nicht der Umfang entscheidet, sondern die Art des Eingriffs
Gemessen in geänderten Zeilen nehmen sich beide Gruppen wenig: **ClamAV ~130 Zeilen,
Branding ~104**. Der größte Einzelpatch ist sogar Branding
(`HelpUserSettingsTab.tsx`, 71 Zeilen). Wer nur zählt, hält beide für gleich teuer.
Sie sind es nicht, und der Grund ist die Art des Eingriffs:
- **Branding-Patches stehen am Rand.** Ein zusätzlicher Default in `SdkConfig.ts`,
ein `<link>` im `<head>`, ein `<li>` in einer Settings-Liste. Wird die Datei
umgebaut, sieht man sofort, wo das eigene Stück wieder hin muss.
- **ClamAV-Patches stehen mittendrin** — in Entschlüsselungs- und Fehlerpfaden der
Medien-Pipeline, verschränkt mit Upstream-Logik. Ein geänderter Kontrollfluss
bedeutet nicht „Konflikt lösen", sondern „neu verstehen".
⚠️ **Und der eigentliche Haken: `viewmodels/`.** Element baut die Medien-Anzeige
gerade auf MVVM um (`docs/MVVM.md`; v1 ist dort bereits als deprecated markiert —
der Umbau läuft also schon in zweiter Runde). `FileBodyViewModel.ts` und
`VideoBodyViewModel.ts` gab es in älteren Ständen gar nicht. Unsere Änderung darin
ist mit je 6 Zeilen winzig — aber wenn Upstream diese Dateien verschiebt, umbenennt
oder auflöst, entsteht **kein Konflikt**: die Zeilen sind einfach weg, und Git meldet
nichts. Das ist gefährlicher als ein Konflikt, weil es stillschweigend passiert.
Praktische Folge: Nach einem Upstream-Update ist an ClamAV nicht die Merge-Ausgabe
maßgeblich, sondern ein **Funktionstest** — eine verschlüsselte Datei senden und eine
abgelehnte empfangen. Steht so auch in Abschnitt 2.
### Was daraus für künftige Änderungen folgt
1. **Erst prüfen, ob es die Konfiguration schon kann.** Auth-Logo, `logo_link_url`
und `brand` liefen ohne Rebuild über die ConfigMap; das Call-Widget wurde
vollständig über `VITE_PRODUCT_NAME` umbenannt, ohne eine einzige Quelldatei.
Jede so vermiedene Datei ist eine, die beim Update nicht kollidiert.
2. **Eigene Datei schlagen geänderte Datei.** `apps/desktop/axion1337/` und eigene
Assets kosten beim Merge nichts.
3. **Wenn Upstream-Code sein muss: einen Kommentar mit `ThreadNet-Fork:` und der
Begründung dazu.** Beim Neuauftragen auf einen neuen Stand ist die Frage nie
„was steht hier", sondern „warum stand das da" — und die beantwortet sonst
niemand mehr.
## Repo-Topologie (seit 2026-07-31)
**Kanonisch ist `git.lab/axion1337.chat/ThreadNet-Web`** (Homelab-GitLab, nur im Lab
auflösbar) — dort laufen Entwicklung und CI (`.gitlab-ci.yml`). Die Kopie auf
`rohana.axion1337.de/sorb/ThreadNet-Web` ist ein **Push-Mirror** (automatisch, GitLab →
Gitea) und dient als Lesekopie plus Standort für Issues, Container-Registry und Releases.
**Niemals direkt nach rohana pushen** — der Mirror überschreibt divergente Stände.
## Element Call anheben (`@sorb/threadnet-call-embedded`)
Das Call-Widget kommt als npm-Paket aus der **Gitea-Registry auf rohana**, nicht von
npmjs. `.npmrc` im Wurzelverzeichnis bindet den Scope fest:
```
@sorb:registry=https://rohana.axion1337.de/api/packages/sorb/npm/
```
Zum Anheben genügt damit der normale Weg — **kein Zusatzschritt, keine Umgebungsvariable**:
```sh
# Version in apps/web/package.json setzen, dann:
pnpm install --lockfile-only # Lockfile zieht die neue Tarball-URL von rohana
pnpm --dir apps/web build # NICHT --filter web: das endet mit exit 0, ohne zu bauen
```
Danach muss die Tarball-URL im `pnpm-lock.yaml` auf `rohana.axion1337.de` zeigen. Tut sie
das nicht, ist die `.npmrc` verlorengegangen: **ohne sie löst pnpm gegen npmjs auf**, und
das geht heute nur deshalb laut aus (404), weil der Name `@sorb` dort noch frei ist —
verlässt man sich darauf, ist es ein *dependency-confusion*-Weg. Siehe management#0055.
Vor dem Anheben liegt das Paket erst dann in der Registry, wenn in `threadnet-call` der
manuelle Job `publish_npm` gelaufen ist — der ist bewusst manuell und wird von sorb
ausgelöst.
## Upstream als zweiter Remote (seit 2026-08-19)
Der Remote ist **lokale Konfiguration**, er wird nicht mitcommittet. Wer den Vergleich
braucht, richtet ihn einmal ein:
```sh
git remote add upstream https://github.com/element-hq/element-web.git
git fetch --depth=1 upstream tag v1.12.26 # aktueller Stand
git fetch --depth=1 upstream tag v1.12.17 # zum Vergleich
```
Damit lässt sich ein Update **als Diff betrachten**, statt Dateien blind zu kopieren —
zum Beispiel für die zwölf Dateien, die wir angefasst haben:
```sh
git diff v1.12.26 main -- apps/web/src/... # unser Delta gegen Upstream
git diff v1.12.17 v1.12.26 -- <datei> # was Upstream seither geaendert hat
```
⚠️ Ein flacher Fetch liefert **keine Historie**: `git merge-base`, `git log` über
Upstream und ein Graft-Versuch brauchen `git fetch upstream` ohne `--depth`.
+5 -5
View File
@@ -617,8 +617,8 @@ importers:
specifier: ^5.0.0
version: 5.2.1(encoding@0.1.13)(webpack@5.106.2)
'@sorb/threadnet-call-embedded':
specifier: 0.19.2-threadnet.5
version: 0.19.2-threadnet.5
specifier: 0.19.2-threadnet.12
version: 0.19.2-threadnet.12
'@stylistic/eslint-plugin':
specifier: ^5.0.0
version: 5.10.0(eslint@8.57.1)
@@ -5003,8 +5003,8 @@ packages:
'@sinonjs/fake-timers@15.1.1':
resolution: {integrity: sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==}
'@sorb/threadnet-call-embedded@0.19.2-threadnet.5':
resolution: {integrity: sha512-u2TG7i82ibkpHSH/s6xA4Q6BTJqITdYWyrVieTiRwnm2Z+SCn/GmPK8JZIp/uSnpMrl9619yp/6vDVtpDJsKZg==, tarball: https://rohana.axion1337.de/api/packages/sorb/npm/%40sorb%2Fthreadnet-call-embedded/-/0.19.2-threadnet.5/threadnet-call-embedded-0.19.2-threadnet.5.tgz}
'@sorb/threadnet-call-embedded@0.19.2-threadnet.12':
resolution: {integrity: sha512-0x9nzkzGXFFVsXXI3LvLdWNXmjzJFsijh29yVXwrtFv/FQXgzwY8BttyMCCFHnEkp4VXtu7KeQfRCEzJB7rdIQ==, tarball: https://rohana.axion1337.de/api/packages/sorb/npm/%40sorb%2Fthreadnet-call-embedded/-/0.19.2-threadnet.12/threadnet-call-embedded-0.19.2-threadnet.12.tgz}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -17632,7 +17632,7 @@ snapshots:
dependencies:
'@sinonjs/commons': 3.0.1
'@sorb/threadnet-call-embedded@0.19.2-threadnet.5': {}
'@sorb/threadnet-call-embedded@0.19.2-threadnet.12': {}
'@standard-schema/spec@1.1.0': {}