fix(backend): APK-Download nutzt den erreichbaren Gitea-Host statt der ROOT_URL

Gitea baut browser_download_url aus seiner eigenen ROOT_URL (hier
192.168.178.153:3000), die vom Backend-Container nicht erreichbar ist. Der
API-Call gelang, der Asset-Download nicht — der stille Fallback auf die nicht
vorhandene lokale Datei meldete dann "Keine APK hinterlegt". Asset- und
Redirect-URLs werden jetzt auf GITEA_URL umgeschrieben; scheitert der Weg
trotzdem, sagen Log und Fehlermeldung welcher der beiden Wege fehlt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-02 16:21:31 +02:00
1 parent 043ec3a108
commit 3e8897b551
2 files changed
+35 -6

No files matched your search

+4 -1
View File
@@ -18,7 +18,10 @@ TICKETMASTER_API_KEY=
# ── Android APK download button + in-app updater ─────────────────────────────
# The backend proxies the fixed Gitea release ("apk-latest") over the LAN, so Gitea can stay internal.
GITEA_URL=http://192.168.2.51:3000 # internal Gitea base reachable from the backend container
GITEA_URL=http://192.168.2.51:3000 # Gitea-Basis, die der BACKEND-CONTAINER erreicht.
# Muss nicht Giteas eigene ROOT_URL sein — das Backend baut
# die Asset-URL auf diesen Host um (Test aus dem Container:
# curl -sI $GITEA_URL/api/v1/version).
GITEA_REPO=Docker/NovaSonic
GITEA_TOKEN= # read token (only needed for a private repo)
GITEA_APK_TAG=apk-latest
+31 -5
View File
@@ -503,6 +503,19 @@ function _giteaApiJson(pathAndQuery) {
});
}
// Gitea baut seine browser_download_url aus der eigenen ROOT_URL — die kann eine ganz andere Adresse
// sein als die, unter der das Backend Gitea erreicht (interne IP vs. Reverse-Proxy-Domain). Deshalb
// Schema+Host+Port immer durch GITEA_URL ersetzen und nur den Pfad übernehmen.
function _viaGiteaHost(urlStr) {
if (!urlStr || !GITEA_URL) return urlStr;
try {
const base = new URL(GITEA_URL);
const u = new URL(urlStr);
if (u.origin === base.origin) return urlStr;
return new URL(u.pathname + u.search, base).toString();
} catch { return urlStr; }
}
// The .apk asset of the fixed release, or null if unavailable.
async function _giteaApkAsset() {
const rel = await _giteaApiJson(`/releases/tags/${encodeURIComponent(GITEA_APK_TAG)}`);
@@ -513,7 +526,7 @@ async function _giteaApkAsset() {
const vcMatch = /versionCode\s*[=:]\s*(\d+)/i.exec(rel.body || '');
return {
size: asset.size,
url: asset.browser_download_url,
url: _viaGiteaHost(asset.browser_download_url),
version: rel.name || rel.tag_name || null,
versionCode: vcMatch ? parseInt(vcMatch[1], 10) : null,
updatedAt: new Date(asset.created_at || rel.published_at || Date.now()).getTime(),
@@ -523,7 +536,13 @@ async function _giteaApkAsset() {
function _serveLocalApk(res) {
if (res.headersSent) return;
fs.stat(APK_PATH, (err) => {
if (err) return res.status(404).json({ error: 'Keine APK hinterlegt' });
// Ohne lokale Datei UND ohne erreichbares Gitea-Asset: sagen, welcher der beiden Wege fehlt,
// sonst sucht man die APK an der falschen Stelle (die Logs oben nennen den Grund).
if (err) return res.status(404).json({
error: (GITEA_URL && GITEA_REPO)
? `APK-Release nicht erreichbar (${GITEA_URL}, Tag ${GITEA_APK_TAG}) und keine lokale Datei unter ${APK_PATH}`
: 'Keine APK hinterlegt',
});
res.setHeader('Content-Type', 'application/vnd.android.package-archive');
res.setHeader('Content-Disposition', 'attachment; filename="NovaSonic.apk"');
res.sendFile(APK_PATH);
@@ -539,14 +558,21 @@ function _streamApkFromUrl(urlStr, res, redirectsLeft = 3) {
lib.get(u, { headers }, up => {
if ([301, 302, 303, 307, 308].includes(up.statusCode) && up.headers.location && redirectsLeft > 0) {
up.resume();
return _streamApkFromUrl(new URL(up.headers.location, u).toString(), res, redirectsLeft - 1);
return _streamApkFromUrl(_viaGiteaHost(new URL(up.headers.location, u).toString()), res, redirectsLeft - 1);
}
if (up.statusCode !== 200) {
up.resume();
console.warn(`[apk] Gitea-Asset lieferte HTTP ${up.statusCode} (${u.origin}) — fallback auf ${APK_PATH}`);
return _serveLocalApk(res);
}
if (up.statusCode !== 200) { up.resume(); return _serveLocalApk(res); }
res.setHeader('Content-Type', 'application/vnd.android.package-archive');
res.setHeader('Content-Disposition', 'attachment; filename="NovaSonic.apk"');
if (up.headers['content-length']) res.setHeader('Content-Length', up.headers['content-length']);
up.pipe(res);
}).on('error', () => _serveLocalApk(res));
}).on('error', (e) => {
console.warn(`[apk] Gitea-Asset nicht erreichbar (${u.origin}): ${e.message} — fallback auf ${APK_PATH}`);
_serveLocalApk(res);
});
}
app.get('/api/apk/info', async (req, res) => {