- Trim input; fail fast if tag contains whitespace (common mistake: pasting release title instead of git tag). - Multiline GITHUB_OUTPUT for tag value safety. - README checklist + input description clarify tag vs title. Co-authored-by: Cursor <cursoragent@cursor.com>
Fractured Launcher (Electron)
Windows and Linux (AppImage) launcher with no extra console window, native Browse folder dialog, Gitea or GitHub release assets + GitHub repo file sync, realmlist, optional auth, Play, and auto-update (via electron-updater). This is the only supported client launcher in this repo.
Requirements
- Node.js 20+ (includes npm)
Run from source
cd tools/fractured-launcher-electron
npm install
npm start
On first run, launcher.json is created next to the app (dev: in this folder).
Where patches download from
- Recommended (self-hosted Gitea): set
gitea.base_url,gitea.owner,gitea.repoinlauncher.json(seedefault-launcher.json). Players needGITEA_TOKEN(or the env name ingitea.token_env) if the Gitea repo is private — same trade-off as any private host (per-player token, SSO proxy, or a read-only deploy token you accept distributing). - Fallback: if
gitea.base_urlis empty,from_releaseuses the GitHub Releases API againstgithub.owner/github.repo(defaults to thisFracturedrepo for non-release paths), with optionalGITHUB_TOKENfor private assets.
Build Windows installers
npm install
npm run pack:win
Produces under dist/:
| Artifact | Purpose |
|---|---|
Fractured-Launcher-${version}-Setup.exe (NSIS) |
Recommended for players — supports seamless auto-update and restart. |
Fractured-Launcher-${version}-Windows-Portable.exe |
No installer; players replace the file manually. Auto-update is less reliable than NSIS. |
Build Linux AppImage
cd tools/fractured-launcher-electron
npm install
npm run pack:linux
Produces dist/Fractured-Launcher-${version}-Linux-x86_64.AppImage. Same lib/baked-gitea-channel.js and default-launcher.json as Windows; run on Linux (or use Fractured launcher CI / Sync release to Gitea, which upload this file to Gitea with the Windows installers).
Quick local test (avoids tag snapshot / CI):
- Linux: from repo root,
bash tools/fractured-launcher-electron/scripts/manual-pack-linux.sh→dist/*.AppImage. - Windows: on a Windows machine,
cd tools/fractured-launcher-electron,npm ci,npm run pack:win→dist/*.exe.
Hardcoded Gitea channel (non-token)
lib/baked-gitea-channel.js exports base_url, owner, repo, release_tag. Set those strings once in the repo (same values you use for CI upload — not secret). At runtime config-store merges them into gitea.* so launcher.json does not need those fields; GITEA_TOKEN (or gitea.token_env) is still only for private Gitea. Leave a field '' in the baked file to fall back to default-launcher.json / user launcher.json for that key.
npm run pack:win is plain electron-builder — no inject step, no extra JSON beside the app.
Auto-update behaviour
- Packaged builds only (
npm run pack:winoutput). Innpm startdev mode, update checks are skipped (button still explains that). - No implicit GitHub feed: the app does not guess
package.json→repositoryanymore. Without configuration you get a clear “skipped” message instead of a 404 on a private repo. - Configured feeds (first match wins):
update_feed_url/LAUNCHER_UPDATE_URL(genericlatest.yml); orgiteablock filled in +GITEA_TOKENwhen the instance is private (resolves…/releases/download/{tag}/); orGITHUB_TOKEN+github.owner/github.repofor private GitHub releases only. - ~5 seconds after launch, then every 6 hours, the app checks when a feed is configured.
- When a download finishes, a dialog offers Restart now (calls
quitAndInstall) or Later. - Manual check: button Check launcher updates in the UI.
Where launcher updates are hosted
npm run publish:win runs electron-builder with --publish never — artifacts stay in dist/; CI uploads them to Gitea when you publish a GitHub release. For ad-hoc uploads, use scripts/upload-release-to-gitea.sh. For launcher auto-update, prefer:
- Set
update_feed_url(orLAUNCHER_UPDATE_URL) to a generic HTTPS base URL wherelatest.ymland the installer files are hosted (often the same Gitea release attachment URLs pattern your reverse proxy exposes), or - Keep publishing to a GitHub release only for
latest.yml+ installers if you accept that small metadata/binary channel there.
Private GitHub updater: set GH_TOKEN / GITHUB_TOKEN / github.token_env as documented in lib/auto-update.js behaviour.
Generic feed: optional Bearer token via the same token envs if your static host checks Authorization.
Publishing a new launcher version
- Bump
versioninpackage.jsononmain(or your release branch) and merge. - Create a GitHub release (tag + attach patches /
Wow.exeif needed) and click Publish — Sync release to Gitea builds Windows + Linux launcher artifacts and mirrors everything to Gitea. - Local check:
npm run pack:win(on Windows) ornpm run pack:linux/scripts/manual-pack-linux.sh, thenscripts/upload-release-to-gitea.shwith the sameGITEA_*env vars as CI if you need a manual upload.
Sync to Gitea (patches + launcher binaries)
CI workflow Sync release to Gitea (.github/workflows/gitea-release-sync.yml) runs on every published GitHub release on this repo:
- Triggers on release published on
Dawnforger/Fractured(or workflow_dispatch with a tag). - Builds Windows (NSIS + portable) and Linux (AppImage) in parallel, each using
tools/fractured-launcher-electronfrom the default branch (overlaid onto the tag checkout), so older release tags never ship a launcher missing newlib/*.jsfiles. - Downloads all assets attached to that GitHub release (MPQs, patched
Wow.exe, etc.). - Merges with the built launcher artifacts and uploads everything to a Gitea release with the same tag (existing attachments on that Gitea release are replaced).
GitHub Actions secrets (repository → Settings → Secrets and variables → Actions):
| Secret | Example |
|---|---|
GITEA_BASE_URL |
https://git.yourdomain.com (no trailing slash) |
GITEA_TOKEN |
Gitea personal access token with permission to manage releases and attachments on the target repo |
GITEA_OWNER |
Organization or username on Gitea |
GITEA_REPO |
Repository name — must already have at least one commit (Gitea returns HTTP 422 “repo is empty” for zero-commit repos; push e.g. a README on main or set GITEA_TARGET_REF to your default branch) |
Optional variable (Settings → Variables): GITEA_TARGET_REF — default branch/commitish used only when the workflow must create a new Gitea release and Gitea needs target_commitish (defaults to main in the upload script if unset).
Player launcher.json: packaged builds should already include gitea.base_url / owner / repo from the bake step above. Players only need to set GITEA_TOKEN (or your token_env) if the Gitea repo is private. To point at another instance, edit gitea in launcher.json:
"gitea": {
"base_url": "https://git.yourdomain.com",
"owner": "myorg",
"repo": "fractured-patches",
"release_tag": "latest",
"token_env": "GITEA_TOKEN"
}
Manual upload: bash scripts/upload-release-to-gitea.sh /path/to/files v1.0.0 with the same env vars as CI.
Sync did not run / Gitea unchanged — checklist
- Git tag ≠ GitHub Release — Only Releases (published on the GitHub Releases page) trigger this workflow. If your teammate only
git push --tags, create a Release from that tag and click Publish (or run Actions → Sync release to Gitea → Run workflow and enter the tag). - Manual run: tag vs title — Run workflow must receive the git tag (e.g.
v0.7.11-paragon-…), copied from the release page’s tag badge. Pasting the release title (long line with spaces/parentheses) breaksgit fetchwithinvalid refspec. - Draft release — Must click Publish release; drafts do not mirror.
- Workflow on default branch — GitHub runs
releaseworkflows from the default branch (e.g.main). Ensure.github/workflows/gitea-release-sync.ymlis merged there. - Repo name guard — Jobs use
if: github.repository == 'Dawnforger/Fractured'. Forks or renames must change that line or runs are skipped. - Secrets —
GITEA_BASE_URL,GITEA_TOKEN,GITEA_OWNER,GITEA_REPOmust be set under Settings → Secrets and variables → Actions. A failed “Upload to Gitea” step usually prints which is missing. - Actions tab — Open the latest Sync release to Gitea run; a red build-electron (old tag without
package-lock.json, etc.) or Upload to Gitea step shows the real error. - HTTP 422
repo is empty— The Gitea repo has no commits yet. Push any initial commit (e.g. Add README in the Gitea web UI, orgit pushtomain). Optionally setGITEA_TARGET_REFto match your real default branch if it is notmain. From this repo you can runscripts/bootstrap-gitea-repo.sh(see script header forGITEA_*env or pass the HTTPS/SSH clone URL as the first argument).
Private Gitea token for players
Do not embed a shared admin PAT in a shipped launcher.json. Prefer read-only tokens scoped to one repo, short-lived tokens, or a small auth service that redirects to signed URLs.
Release asset names must match files[].source when from_release: true. Use release_tag: "latest" or a pinned tag matching both GitHub and Gitea.
Patch versions (same filenames, different bytes)
The launcher does not read Git commits. For turn-key updates when asset names stay fixed (patch-Z.MPQ, Wow-patched.exe, …):
- Ship
patch-manifest.jsonnext to those files on every release (Gitea/GitHub attachment). It lists aversionlabel (any string you bump per release, e.g.v0.9.0-client) and asha256perfiles[].sourcename. - With
patch_manifest.enabled: true (default indefault-launcher.json), Download updates first fetches the manifest from the same release channel. If the files already on disk match those checksums, the player sees “already match build … (nothing to download)” — no redundant downloads. - After a real download, the launcher re-hashes installed files and compares to the manifest; mismatch → clear error. It also writes
.fractured/patch-state.jsonunder the WoW folder so the UI can show “Installed client files: …”.
If patch-manifest.json is missing on a release, the launcher falls back to always downloading all configured files (same as before).
Generate the manifest when you cut a release (paths are your local patch binaries):
cd /path/to/staging
node tools/fractured-launcher-electron/scripts/generate-patch-manifest.js v0.9.0-client patch-Z.MPQ Wow-patched.exe > patch-manifest.json
Attach patch-manifest.json together with the MPQ/exe to the GitHub release (CI sync copies it to Gitea with everything else).
CI
Workflow Fractured launcher CI (.github/workflows/fractured-launcher-ci.yml) runs on pushes/PRs under tools/fractured-launcher-electron/: Windows (npm run pack:win) and Linux (npm run pack:linux) jobs, each electron-builder … --publish never. Actions → Fractured launcher CI → Run workflow runs it manually.
Sync release to Gitea (.github/workflows/gitea-release-sync.yml) uses the same pack commands. If you see GH_TOKEN / GitHubPublisher errors in logs, the job is almost certainly an old Re-run failed jobs — open Actions → Sync release to Gitea → Run workflow, enter the tag, and start a new run instead.
Config
Schema is defined by default-launcher.json (shipped in the app; first run copies to launcher.json beside the executable):
game_dir: WoW 3.3.5a root (containsWow.exe).update_feed_url: optional generic HTTPS base for launcher auto-update.launcher_updates_from_github: defaultfalse. Only whentruewill aGITHUB_TOKEN(orgithub.token_env) enable electron-updater’s GitHub provider againstgithub.owner/github.repo. Leavefalsewhen launcher binaries andlatest.ymllive on Gitea (usegitea+ token instead) so a stray GitHub token does not produce “No published versions on GitHub”.gitea:base_url,owner,repo,release_tag,token_env— whenbase_urlis set (and owner/repo set),from_releasedownloads and (with token if needed) the generic updater feed use Gitea. Required for players if your CI mirrors patches/launchers to Gitea only.github: used for non-release repo paths (from_release: false) and for GitHubfrom_releasewhengitea.base_urlis empty.patch_manifest:enabled,source(defaultpatch-manifest.json),from_release— checksum-based skip + verify (see above).files,realmlist,auth,launch.