feat(launcher): Linux AppImage 1.0.2, Gitea sync + CI, manual pack script

- Add pack:linux (AppImage x64), linux/appImage artifact names in package.json.
- Gitea sync: parallel build-electron-linux, merge Windows+Linux into Gitea upload;
  rename Windows artifact to electron-dist-windows.
- Fractured launcher CI: electron-launcher-windows + electron-launcher-linux jobs.
- scripts/manual-pack-linux.sh for local test builds from current tree.
- Normalize Gitea base_url (prepend https if missing); baked channel uses full URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Docker Build
2026-05-10 20:50:06 -05:00
parent 48826e21d6
commit 2a3107a78d
7 changed files with 148 additions and 20 deletions
+30 -1
View File
@@ -22,7 +22,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
electron-launcher: electron-launcher-windows:
runs-on: windows-latest runs-on: windows-latest
timeout-minutes: 45 timeout-minutes: 45
defaults: defaults:
@@ -50,3 +50,32 @@ jobs:
tools/fractured-launcher-electron/dist/*.exe tools/fractured-launcher-electron/dist/*.exe
tools/fractured-launcher-electron/dist/latest.yml tools/fractured-launcher-electron/dist/latest.yml
tools/fractured-launcher-electron/dist/*.blockmap tools/fractured-launcher-electron/dist/*.blockmap
electron-launcher-linux:
runs-on: ubuntu-latest
timeout-minutes: 45
defaults:
run:
working-directory: tools/fractured-launcher-electron
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: tools/fractured-launcher-electron/package-lock.json
- name: Install and pack (AppImage)
run: |
npm ci
npm run pack:linux
- uses: actions/upload-artifact@v4
with:
name: fractured-launcher-electron-linux-${{ github.run_id }}
if-no-files-found: warn
path: |
tools/fractured-launcher-electron/dist/*.AppImage
tools/fractured-launcher-electron/dist/*.yml
tools/fractured-launcher-electron/dist/*.blockmap
+62 -7
View File
@@ -13,8 +13,8 @@
# Release on github.com (Releases → Draft/new release → Publish). The workflow # Release on github.com (Releases → Draft/new release → Publish). The workflow
# definition must exist on the repo DEFAULT branch (GitHub runs it from there). # definition must exist on the repo DEFAULT branch (GitHub runs it from there).
# #
# Steps: build Electron using launcher sources from DEFAULT BRANCH (so tags never miss # Steps: Windows (NSIS+portable) + Linux (AppImage) in parallel, launcher from DEFAULT BRANCH
# launcher files), checkout tag only for a consistent tree → download release attachments → Gitea. # overlay on tag checkout → merge with GitHub release assets → upload all to Gitea.
# #
# Secrets: GITEA_BASE_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO # Secrets: GITEA_BASE_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO
# Optional variable: GITEA_TARGET_REF (see tools/fractured-launcher-electron/README.md) # Optional variable: GITEA_TARGET_REF (see tools/fractured-launcher-electron/README.md)
@@ -102,11 +102,61 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: electron-dist name: electron-dist-windows
path: launcher-publish/ path: launcher-publish/
build-electron-linux:
needs: meta
if: github.repository == 'Dawnforger/Fractured'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.meta.outputs.tag }}
- name: Overlay launcher from default branch
shell: bash
run: |
set -euo pipefail
DB="${{ github.event.repository.default_branch }}"
git fetch --no-tags --depth=1 origin "+refs/heads/${DB}:refs/remotes/origin/${DB}"
git checkout "origin/${DB}" -- tools/fractured-launcher-electron
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: tools/fractured-launcher-electron/package-lock.json
- name: Install and pack (AppImage)
working-directory: tools/fractured-launcher-electron
run: |
npm ci
npm run pack:linux
- name: Stage Linux launcher for upload
shell: bash
run: |
set -euo pipefail
mkdir -p launcher-linux-publish
shopt -s nullglob
cp -f tools/fractured-launcher-electron/dist/*.AppImage launcher-linux-publish/ 2>/dev/null || true
cp -f tools/fractured-launcher-electron/dist/*.yml launcher-linux-publish/ 2>/dev/null || true
cp -f tools/fractured-launcher-electron/dist/*.blockmap launcher-linux-publish/ 2>/dev/null || true
ls -la launcher-linux-publish/
if ! compgen -G "launcher-linux-publish/*.AppImage" > /dev/null; then
echo "No AppImage under dist/ — electron-builder linux target failed" >&2
exit 1
fi
- uses: actions/upload-artifact@v4
with:
name: electron-dist-linux
path: launcher-linux-publish/
sync-gitea: sync-gitea:
needs: [meta, build-electron] needs: [meta, build-electron, build-electron-linux]
if: github.repository == 'Dawnforger/Fractured' if: github.repository == 'Dawnforger/Fractured'
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@@ -126,8 +176,13 @@ jobs:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
name: electron-dist name: electron-dist-windows
path: /tmp/electron path: /tmp/electron-win
- uses: actions/download-artifact@v4
with:
name: electron-dist-linux
path: /tmp/electron-linux
- name: Merge GitHub release assets + Electron build - name: Merge GitHub release assets + Electron build
env: env:
@@ -150,7 +205,7 @@ jobs:
cat /tmp/dl.err || true cat /tmp/dl.err || true
fi fi
shopt -s nullglob shopt -s nullglob
for f in /tmp/electron/*; do for f in /tmp/electron-win/* /tmp/electron-linux/*; do
if [ -f "$f" ]; then if [ -f "$f" ]; then
cp -f "$f" combined/ cp -f "$f" combined/
fi fi
+18 -6
View File
@@ -1,6 +1,6 @@
# Fractured Launcher (Electron) # 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. **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 ## Requirements
@@ -35,6 +35,18 @@ Produces under **`dist/`**:
| `Fractured-Launcher-${version}-Setup.exe` (NSIS) | **Recommended for players** — supports seamless **auto-update** and restart. | | `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. | | `Fractured-Launcher-${version}-Windows-Portable.exe` | No installer; players replace the file manually. Auto-update is **less reliable** than NSIS. |
## Build Linux AppImage
```bash
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):** from repo root, **`bash tools/fractured-launcher-electron/scripts/manual-pack-linux.sh`** — uses your **current** checkout, then install/run the AppImage from **`dist/`**.
### Hardcoded Gitea channel (non-token) ### 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. **`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.
@@ -64,15 +76,15 @@ Produces under **`dist/`**:
### Publishing a new launcher version ### Publishing a new launcher version
1. Bump **`version`** in `package.json` on `main` (or your release branch) and merge. 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. 2. Create a **GitHub release** (tag + attach patches / `Wow.exe` if needed) and click **Publish****Sync release to Gitea** builds **Windows + Linux** launcher artifacts 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. 3. Local check: **`npm run pack:win`** (on Windows) or **`npm run pack:linux`** / **`scripts/manual-pack-linux.sh`**, 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) ## 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: 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). 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 thats missing new **`lib/*.js`** files. 2. Builds **Windows** (NSIS + portable) and **Linux** (AppImage) in parallel, each using **`tools/fractured-launcher-electron` from the default branch** (overlaid onto the tag checkout), so older release tags never ship a launcher missing new **`lib/*.js`** files.
3. Downloads **all assets** attached to that **GitHub** release (MPQs, patched `Wow.exe`, etc.). 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). 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).
@@ -138,9 +150,9 @@ Attach **`patch-manifest.json`** together with the MPQ/exe to the GitHub release
## CI ## 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. 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 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. **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 ## Config
@@ -6,8 +6,9 @@
* Token stays in env: GITEA_TOKEN or launcher.json → gitea.token_env. * Token stays in env: GITEA_TOKEN or launcher.json → gitea.token_env.
*/ */
module.exports = { module.exports = {
base_url: '', // Scheme optional — gitea-release normalizes to https:// if missing.
owner: '', base_url: 'https://brassnet.ddns.net:33983',
repo: '', owner: 'Dawnsorrow',
repo: 'Fractured-Distro',
release_tag: 'latest', release_tag: 'latest',
}; };
@@ -2,8 +2,15 @@
const { downloadBodyToFile } = require('./http-download'); const { downloadBodyToFile } = require('./http-download');
function normalizeGiteaBaseUrl(raw) {
let b = String(raw || '').trim().replace(/\/+$/, '');
if (!b) return '';
if (!/^https?:\/\//i.test(b)) b = `https://${b}`;
return b;
}
function giteaApiBase(cfg) { function giteaApiBase(cfg) {
const base = String(cfg.gitea.base_url || '').trim().replace(/\/+$/, ''); const base = normalizeGiteaBaseUrl(cfg.gitea.base_url);
return `${base}/api/v1`; return `${base}/api/v1`;
} }
@@ -87,7 +94,7 @@ async function getGiteaUpdaterFeedBase(cfg) {
const rel = await res.json(); const rel = await res.json();
const tagName = rel.tag_name; const tagName = rel.tag_name;
if (!tagName || typeof tagName !== 'string') return null; if (!tagName || typeof tagName !== 'string') return null;
const root = String(cfg.gitea.base_url || '').trim().replace(/\/+$/, ''); const root = normalizeGiteaBaseUrl(cfg.gitea.base_url);
const url = `${root}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/download/${encodeURIComponent(tagName)}/`; const url = `${root}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/download/${encodeURIComponent(tagName)}/`;
return { url, token }; return { url, token };
} }
+14 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "fractured-launcher-electron", "name": "fractured-launcher-electron",
"version": "1.0.1", "version": "1.0.2",
"description": "Fractured WoW launcher (Electron) — no console window, native folder picker, auto-update", "description": "Fractured WoW launcher (Electron) — no console window, native folder picker, auto-update",
"main": "main.js", "main": "main.js",
"repository": { "repository": {
@@ -10,6 +10,7 @@
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
"pack:win": "electron-builder --win nsis portable --x64 --publish never", "pack:win": "electron-builder --win nsis portable --x64 --publish never",
"pack:linux": "electron-builder --linux AppImage --x64 --publish never",
"publish:win": "electron-builder --win nsis portable --x64 --publish never" "publish:win": "electron-builder --win nsis portable --x64 --publish never"
}, },
"author": "", "author": "",
@@ -59,6 +60,18 @@
}, },
"portable": { "portable": {
"artifactName": "Fractured-Launcher-${version}-Windows-Portable.${ext}" "artifactName": "Fractured-Launcher-${version}-Windows-Portable.${ext}"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
}
],
"category": "Game"
},
"appImage": {
"artifactName": "Fractured-Launcher-${version}-Linux-x86_64.${ext}"
} }
} }
} }
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Local Linux AppImage build (uses current tree — no tag snapshot). Run from repo root or this dir.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
echo "==> npm ci"
npm ci
echo "==> npm run pack:linux (AppImage x64)"
npm run pack:linux
echo "==> dist/:"
ls -la dist/