- Gitea release workflow downloads existing Gitea attachments, merges CI-built launchers, uploads merged set (no GitHub release mirror). - Add download-release-from-gitea.sh; clarify release-sync filters. - VPS update: repoint github.com/HighSocietyRaiding/Fractured remote to Gitea before git pull unless skipped via env. - Docs and bootstrap comment; distro workflow uses shared skip helper. 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: dev — next to the app in this folder; Windows packaged — beside the .exe; Linux AppImage / macOS packaged — under Electron app.getPath('userData') (typically under ~/.config/, folder name from the app; AppImage mount is read-only so config cannot live beside the binary).
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 / Gitea release — attach launcher builds, 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 run Gitea release — attach launcher builds (see below). 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/publish the Gitea release for that tag (attach patches /
Wow.exe,patch-manifest.json, etc.). - Run GitHub Actions → Gitea release — attach launcher builds (or publish a GitHub release with the same tag if you still use that trigger) so CI builds Windows + Linux launchers and re-uploads all release files to Gitea (see workflow header).
- 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.
Gitea release + launcher (patches + binaries)
CI workflow Gitea release — attach launcher builds (.github/workflows/gitea-release-sync.yml) attaches CI-built launcher binaries to the Gitea release that already exists for the chosen tag:
- Triggers on release published on
HighSocietyRaiding/Fractured(optional) or workflow_dispatch with a tag (the tag must match a Gitea release onGITEA_OWNER/GITEA_REPO). - 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 existing attachments from that Gitea release (MPQs, patched
Wow.exe, etc.). - Merges with the built launcher artifacts and replaces all attachments on the same Gitea release (the upload script clears prior attachments, then posts the merged set). Launcher installers already on Gitea (
Fractured-Launcher*, case-insensitive) are not re-downloaded — the CI build from the default branch is the only source of launcher binaries.*.blockmapandbuilder-debug.ymlare omitted from the merge and from Gitea uploads.
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 upload script must create a new Gitea release (no release for that tag yet) and Gitea needs target_commitish (defaults to main in the upload script if unset). Prefer creating the release on Gitea before CI so patches ship in the same run.
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.
Legacy “bridge” after changing baked-gitea-channel.js: Players still using the old Gitea URL only receive launcher updates from that host. Build Windows + Linux installers (e.g. download Fractured launcher CI artifacts, or run npm run pack:win / npm run pack:linux), put dist/ contents in one folder if needed, then:
export GITEA_BASE_URL=http://your-old-host:port # legacy base, no trailing slash
export GITEA_TOKEN=... GITEA_OWNER=Dawnsorrow GITEA_REPO=Fractured-Distro
bash tools/fractured-launcher-electron/scripts/gitea-replace-launcher-only.sh \
tools/fractured-launcher-electron/dist latest
That script deletes only Fractured-Launcher*, latest.yml (only if you supply a new latest.yml in dist/), latest-linux.yml (only if supplied), *.blockmap, and builder-debug.yml on the release, then uploads the new files — Wow.exe, MPQs, and patch-manifest.json are left alone. If you only built Linux locally, merge Windows CI dist/ files into the same folder first so latest.yml is not removed without a replacement. Use release tag latest if that is what release_tag points at.
Launcher attach did not run / Gitea unchanged — checklist
- Gitea release missing — The workflow downloads from Gitea first. Create/publish a release on
GITEA_OWNER/GITEA_REPOwith the same tag before running CI (or rely on upload script to create an empty release — you still needGITEA_TARGET_REFif the repo default branch is notmain). - GitHub-only tag — If you use the release published trigger on GitHub, the tag must still exist as a Gitea release with your patch assets. Otherwise use Actions → Gitea release — attach launcher builds → Run workflow after the Gitea release exists.
- Manual run: tag vs title — Run workflow must receive the git tag (e.g.
v0.7.11-paragon-…), not the long human-readable release title (spaces break the tag). - Draft release — On Gitea, publish the release; drafts may not be visible to the API the same way — use a published release.
- 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 == 'HighSocietyRaiding/Fractured'. Forks or renames must change that line or runs are skipped (GitHub Actions only; canonical clone is Gitea). - 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 Gitea release — attach launcher builds run; a red build-electron (old tag without
package-lock.json, etc.), Merge Gitea release assets, 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). sync Wow.exe: fetch failed— Often HTTPS/TLS to Gitea; usehttp://…inlib/baked-gitea-channel.jsif you only serve plain HTTP, or fix certs /NODE_EXTRA_CA_CERTS. EnsureWow-patched.exeexists on the release (release_tag:latestvs pinned). Errors include the failing URL when possible.- Wine + Windows portable — If the folder picker returns
/home/..., the launcher maps it toZ:\home\...(Wine’s Unix root).Wow.exeis matched case-insensitively for Linux-backed folders. Re-save the WoW folder after upgrading if validation still fails. EBUSY/ file locked on Windows — The client (or antivirus) may keep.MPQfiles open. The launcher retries for a short window and downloads the new file before replacing the old one; if sync still fails, exit WoW (and any tool previewing that folder) and run Download updates again..bak-*clutter — When Download updates finishes without error, the launcher removes matching*.bak-YYYYMMDD-HHmmssfiles from earlier runs (same pattern it uses when replacing files). Failed syncs do not delete backups.- Gitea still shows an old launcher version — The workflow overlays
tools/fractured-launcher-electronfrom the default branch, sopackage.jsonthere defines the built version. Ensure launcher changes are merged tomainbefore attaching to the release (or re-run Actions → Gitea release — attach launcher builds after merging). Fractured-Launcher* files already on Gitea are skipped when re-downloading so CI is the only launcher source. - Migrating
baked-gitea-channel.jsto a new host — Publishgitea-replace-launcher-only.sh(see Manual upload above) on the old Gitealatestrelease so auto-update still works until clients move to the new URL.
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 your Gitea releases.
Patch versions (same filenames, different bytes)
The launcher does not read Git commits. For turn-key updates when asset names stay fixed (e.g. Wow-patched.exe — add more files entries for any extra MPQs you ship):
- 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 Wow-patched.exe > patch-manifest.json
Attach patch-manifest.json together with the MPQ/exe to the Gitea release (same files CI merges before upload).
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.
Gitea release — attach launcher builds (.github/workflows/gitea-release-sync.yml) uses the same pack commands. If you see stale behaviour after a workflow edit, start a new run (Actions → Gitea release — attach launcher builds → Run workflow) instead of Re-run failed jobs on an old run.
Config
Schema is defined by default-launcher.json (shipped in the app; first run copies to launcher.json — beside the Windows exe, or under userData on Linux/macOS packaged builds):
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: default[]. Download updates resolves what to pull in order: (1) non-emptyfilesif you set explicitsource→destpairs; (2) else each key inpatch-manifest.jsonon the release (recommended); (3) else release attachments except launcher artifacts (Fractured-Launcher*,*.blockmap,latest*.yml,.AppImage,patch-manifest.json):.MPQ→Data/enUS/<name>.MPQ(extension forced to.MPQcaps for client compatibility), one.exe→launch.exe. Multiple.exeattachments require a manifest. LegacyWow-patched.exeentries are removed when merginglauncher.json.realmlist,auth,launch(**exe**,args). OnlyData/enUS/realmlist.wtfis written: anyrealmlist.pathsentry that is not the enUS file is ignored (soenGBis never created). On Linux, Play never runsWow.exeas a native process (that yields EACCES). Uselaunch.linux_wrapper(default["wine"]) so the launcher runs e.g.wine /path/Wow.exe…args, or setlaunch.linux_steam_urito a Steam URI (e.g.steam://rungameid/…for a non-Steam game shortcut — the number is shown in Steam’s shortcut properties). Optionallinux_steam_binarydefaults tosteam; for Flatpak Steam uselinux_steam_spawn:["flatpak", "run", "com.valvesoftware.Steam"](the URI is appended as the last argument). After a successful Download updates run, the launcher deletes prior*.bak-YYYYMMDD-HHmmssbackup files it created under the WoW folder.