75e3b59442
Co-authored-by: Cursor <cursoragent@cursor.com>
149 lines
11 KiB
Markdown
149 lines
11 KiB
Markdown
# 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. |
|
|
|
|
## 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 from that tag on Windows.
|
|
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`:** set **`gitea`** so **`from_release`** files resolve on your instance:
|
|
|
|
```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.
|
|
- **`gitea`**: **`base_url`**, **`owner`**, **`repo`**, **`release_tag`**, **`token_env`** — when **`base_url`** is set (and owner/repo set), **`from_release`** downloads use the **Gitea** API.
|
|
- **`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`**.
|