# Fractured Launcher (Electron) Windows 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](https://nodejs.org/) 20+ (includes npm) ## Run from source ```bash 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.repo`** in `launcher.json` (see **`default-launcher.json`**). Players need **`GITEA_TOKEN`** (or the env name in **`gitea.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_url`** is empty, **`from_release`** uses the **GitHub** Releases API against **`github.owner` / `github.repo`** (defaults to this **`Fractured`** repo for non-release paths), with optional **`GITHUB_TOKEN`** for private assets. ## Build Windows installers ```bash 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. | ### Baked Gitea channel (non-token) **`npm run pack:win`** runs **`scripts/inject-release-channel.js`** first. It merges **`gitea.base_url`**, **`owner`**, **`repo`**, and optional **`release_tag`** into **`default-launcher.json`** for that build only (then **electron-builder** packs that file). - **GitHub Actions** — **Sync release to Gitea** and **Fractured launcher CI** export **`GITEA_BASE_URL`**, **`GITEA_OWNER`**, **`GITEA_REPO`** (same names as your upload secrets) for the pack step, so installers match the repo you sync to. Nothing embeds **`GITEA_TOKEN`**. - **Local packs** — put the same values in **`fractured-release-channel.json`** (committed or personal copy) **or** export those env vars before **`npm run pack:win`**. First launch still copies **`default-launcher.json`** → **`launcher.json`** beside the exe, so players get the baked **`gitea.*`** without editing unless they override. ## Auto-update behaviour - **Packaged** builds only (`npm run pack:win` output). In `npm start` dev mode, update checks are skipped (button still explains that). - **No implicit GitHub feed:** the app does **not** guess `package.json` → `repository` anymore. 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`** (generic `latest.yml`); or **`gitea`** block filled in + **`GITEA_TOKEN`** when the instance is private (resolves `…/releases/download/{tag}/`); or **`GITHUB_TOKEN`** + **`github.owner` / `github.repo`** for **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`** (or **`LAUNCHER_UPDATE_URL`**) to a **generic** HTTPS base URL where **`latest.yml`** and 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 1. Bump **`version`** in `package.json` on `main` (or your release branch) and merge. 2. Create a **GitHub release** (tag + attach patches / `Wow.exe` if needed) and click **Publish** — **Sync release to Gitea** builds Windows installers and mirrors everything to Gitea. 3. Local check: `npm run pack:win` then **`scripts/upload-release-to-gitea.sh`** with the same **`GITEA_*`** 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: 1. Triggers on **release published** on **`Dawnforger/Fractured`** (or **workflow_dispatch** with a tag). 2. Builds the **Electron** app on Windows using **`tools/fractured-launcher-electron` from the repo default branch** (then overlays onto the tag checkout), so older release tags never ship a launcher that’s missing new **`lib/*.js`** files. 3. Downloads **all assets** attached to that **GitHub** release (MPQs, patched `Wow.exe`, etc.). 4. 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`**: ```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 1. **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). 2. **Draft release** — Must click **Publish release**; drafts do not mirror. 3. **Workflow on default branch** — GitHub runs `release` workflows from the **default branch** (e.g. `main`). Ensure `.github/workflows/gitea-release-sync.yml` is merged there. 4. **Repo name guard** — Jobs use `if: github.repository == 'Dawnforger/Fractured'`. Forks or renames must change that line or runs are skipped. 5. **Secrets** — **`GITEA_BASE_URL`**, **`GITEA_TOKEN`**, **`GITEA_OWNER`**, **`GITEA_REPO`** must be set under **Settings → Secrets and variables → Actions**. A failed “Upload to Gitea” step usually prints which is missing. 6. **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. 7. **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, or `git push` to **`main`**). Optionally set **`GITEA_TARGET_REF`** to match your real default branch if it is not **`main`**. From this repo you can run **`scripts/bootstrap-gitea-repo.sh`** (see script header for `GITEA_*` 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`, …): 1. Ship **`patch-manifest.json`** next to those files on **every** release (Gitea/GitHub attachment). It lists a **`version`** label (any string you bump per release, e.g. `v0.9.0-client`) and a **`sha256`** per **`files[].source`** name. 2. With **`patch_manifest.enabled`**: true (default in **`default-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. 3. After a real download, the launcher **re-hashes** installed files and compares to the manifest; mismatch → clear error. It also writes **`.fractured/patch-state.json`** under 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): ```bash 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 pack uses **`electron-builder … --publish never`** (not `npm run pack:win`, so tagged checkouts never require `GH_TOKEN`). **Actions → Fractured launcher CI → Run workflow** runs it manually. **Sync release to Gitea** (`.github/workflows/gitea-release-sync.yml`) uses the same pack command. 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 (contains `Wow.exe`). - **`update_feed_url`**: optional generic HTTPS base for launcher auto-update. - **`launcher_updates_from_github`**: default **`false`**. Only when **`true`** will a **`GITHUB_TOKEN`** (or **`github.token_env`**) enable **electron-updater**’s GitHub provider against **`github.owner` / `github.repo`**. Leave **`false`** when launcher binaries and **`latest.yml`** live on **Gitea** (use **`gitea`** + token instead) so a stray GitHub token does not produce “No published versions on GitHub”. - **`gitea`**: **`base_url`**, **`owner`**, **`repo`**, **`release_tag`**, **`token_env`** — when **`base_url`** is set (and owner/repo set), **`from_release`** downloads 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 **GitHub** **`from_release`** when **`gitea.base_url`** is empty. - **`patch_manifest`**: **`enabled`**, **`source`** (default `patch-manifest.json`), **`from_release`** — checksum-based skip + verify (see above). - **`files`**, **`realmlist`**, **`auth`**, **`launch`**.