diff --git a/tools/fractured-launcher-electron/README.md b/tools/fractured-launcher-electron/README.md index 962c136..efdb43f 100644 --- a/tools/fractured-launcher-electron/README.md +++ b/tools/fractured-launcher-electron/README.md @@ -125,6 +125,7 @@ CI workflow **Sync release to Gitea** (`.github/workflows/gitea-release-sync.yml 6. **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. 7. **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. 8. **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). +9. **`sync Wow.exe: fetch failed`** (Linux/AppImage especially) — Usually **TLS** (self-signed Gitea cert) or **cannot reach** the host. Use a **trusted certificate** on Gitea, or put your CA in the system trust store, or set **`NODE_EXTRA_CA_CERTS=/path/to/ca.pem`** when launching the AppImage if you must use self-signed HTTPS. If the mirror is LAN-only HTTP, set **`base_url`** in **`lib/baked-gitea-channel.js`** to **`http://…`** (only if acceptable). Ensure **`Wow-patched.exe`** exists on the **same Gitea release** you configured (**`release_tag`**: `latest` vs pinned tag). Newer builds show the failing URL and TLS hints in the error text. ### Private Gitea token for players diff --git a/tools/fractured-launcher-electron/lib/gitea-release.js b/tools/fractured-launcher-electron/lib/gitea-release.js index e2d4a5c..4eddf17 100644 --- a/tools/fractured-launcher-electron/lib/gitea-release.js +++ b/tools/fractured-launcher-electron/lib/gitea-release.js @@ -1,6 +1,6 @@ 'use strict'; -const { downloadBodyToFile } = require('./http-download'); +const { downloadBodyToFile, fetchOrThrow } = require('./http-download'); function normalizeGiteaBaseUrl(raw) { let b = String(raw || '').trim().replace(/\/+$/, ''); @@ -46,7 +46,7 @@ async function downloadGiteaReleaseAsset(cfg, assetName, destPath) { listUrl = `${api}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/tags/${encodeURIComponent(tag)}`; } - const res = await fetch(listUrl, { headers: giteaHeaders(token, true) }); + const res = await fetchOrThrow(listUrl, { headers: giteaHeaders(token, true) }); const text = await res.text(); if (!res.ok) { let hint = ''; @@ -69,7 +69,7 @@ async function downloadGiteaReleaseAsset(cfg, assetName, destPath) { const h = { Accept: 'application/octet-stream' }; if (token) h.Authorization = `token ${token}`; - const dl = await fetch(downloadUrl, { headers: h, redirect: 'follow' }); + const dl = await fetchOrThrow(downloadUrl, { headers: h, redirect: 'follow' }); await downloadBodyToFile(dl, destPath); } @@ -89,7 +89,7 @@ async function getGiteaUpdaterFeedBase(cfg) { } else { listUrl = `${api}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/releases/tags/${encodeURIComponent(tag)}`; } - const res = await fetch(listUrl, { headers: giteaHeaders(token, true) }); + const res = await fetchOrThrow(listUrl, { headers: giteaHeaders(token, true) }); if (!res.ok) return null; const rel = await res.json(); const tagName = rel.tag_name; diff --git a/tools/fractured-launcher-electron/lib/github.js b/tools/fractured-launcher-electron/lib/github.js index cc05077..10c0e51 100644 --- a/tools/fractured-launcher-electron/lib/github.js +++ b/tools/fractured-launcher-electron/lib/github.js @@ -4,7 +4,7 @@ const path = require('path'); const fs = require('fs').promises; const { githubToken } = require('./github-token'); const { downloadGiteaReleaseAsset, useGiteaReleases } = require('./gitea-release'); -const { fetchToFile, downloadBodyToFile } = require('./http-download'); +const { fetchToFile, downloadBodyToFile, fetchOrThrow } = require('./http-download'); function encodeRepoPath(repoPath) { let p = String(repoPath || '').replace(/\\/g, '/').replace(/^\/+|\/+$/g, ''); @@ -35,7 +35,7 @@ async function downloadGitHubRepoFile(cfg, repoPath, destPath) { } const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${enc}?ref=${encodeURIComponent(ref)}`; - const res = await fetch(apiUrl, { headers: ghHeaders(token, true) }); + const res = await fetchOrThrow(apiUrl, { headers: ghHeaders(token, true) }); const body = await res.text(); if (!res.ok) { throw new Error(`GitHub contents API ${res.status}: ${body.slice(0, 800)}`); @@ -78,7 +78,7 @@ async function downloadReleaseAsset(cfg, assetName, destPath) { } else { listUrl = `https://api.github.com/repos/${owner}/${repo}/releases/tags/${encodeURIComponent(tag)}`; } - const res = await fetch(listUrl, { headers: ghHeaders(token, true) }); + const res = await fetchOrThrow(listUrl, { headers: ghHeaders(token, true) }); const text = await res.text(); if (!res.ok) { let hint = ''; @@ -114,7 +114,7 @@ async function downloadReleaseAsset(cfg, assetName, destPath) { h.Authorization = `Bearer ${token}`; h['X-GitHub-Api-Version'] = '2022-11-28'; } - const dl = await fetch(assetURL, { headers: h, redirect: 'follow' }); + const dl = await fetchOrThrow(assetURL, { headers: h, redirect: 'follow' }); await downloadBodyToFile(dl, destPath); } diff --git a/tools/fractured-launcher-electron/lib/http-download.js b/tools/fractured-launcher-electron/lib/http-download.js index ab6d205..d0dd943 100644 --- a/tools/fractured-launcher-electron/lib/http-download.js +++ b/tools/fractured-launcher-electron/lib/http-download.js @@ -6,6 +6,42 @@ const { createWriteStream } = require('fs'); const { pipeline } = require('stream/promises'); const { Readable } = require('stream'); +function safeUrlForLog(url) { + try { + const u = new URL(url); + return `${u.origin}${u.pathname}`; + } catch { + return String(url || '').split('?')[0].slice(0, 200); + } +} + +function explainFetchFailure(err, url) { + const msg = err && err.message ? err.message : String(err); + const cause = err && err.cause; + const code = cause && cause.code ? cause.code : ''; + const combined = `${msg} ${code}`; + const hints = []; + if (/CERT|TLS|SSL|UNABLE_TO_VERIFY|SELF_SIGNED|certificate|unknown ca|unable to verify/i.test(combined)) { + hints.push( + 'TLS certificate not trusted — install a valid cert on Gitea, or trust your CA system-wide, or set NODE_EXTRA_CA_CERTS to a .pem bundle (self-signed mirrors)' + ); + } + if (/ECONNREFUSED/.test(combined)) hints.push('connection refused (wrong host/port or server down)'); + if (/ENOTFOUND|EAI_AGAIN/.test(combined)) hints.push('DNS lookup failed'); + if (/ETIMEDOUT|TIMEOUT/i.test(combined)) hints.push('connection timed out'); + const hintStr = hints.length ? ` ${hints.join(' ')}` : ''; + return new Error(`${msg}${hintStr} — ${safeUrlForLog(url)}`); +} + +/** Wrap global fetch with clearer errors for TLS/DNS/refused (Electron reports bare "fetch failed"). */ +async function fetchOrThrow(url, init) { + try { + return await fetch(url, init); + } catch (e) { + throw explainFetchFailure(e, url); + } +} + async function downloadBodyToFile(res, destPath) { if (!res.ok) { const errText = await res.text().catch(() => ''); @@ -30,11 +66,11 @@ async function downloadBodyToFile(res, destPath) { } async function fetchToFile(url, headers, destPath) { - const res = await fetch(url, { + const res = await fetchOrThrow(url, { headers, redirect: 'follow', }); await downloadBodyToFile(res, destPath); } -module.exports = { fetchToFile, downloadBodyToFile }; +module.exports = { fetchToFile, downloadBodyToFile, fetchOrThrow, safeUrlForLog }; diff --git a/tools/fractured-launcher-electron/package.json b/tools/fractured-launcher-electron/package.json index 1d99b41..63ba9d5 100644 --- a/tools/fractured-launcher-electron/package.json +++ b/tools/fractured-launcher-electron/package.json @@ -1,6 +1,6 @@ { "name": "fractured-launcher-electron", - "version": "1.0.4", + "version": "1.0.5", "description": "Fractured WoW launcher (Electron) — no console window, native folder picker, auto-update", "main": "main.js", "repository": {