Upload via GUI (18 Dateien)

This commit is contained in:
Git Manager GUI
2026-08-01 07:43:21 +02:00
parent 1978358f21
commit afe02f81be
10 changed files with 721 additions and 27 deletions
+115
View File
@@ -0,0 +1,115 @@
'use strict';
/*
* updater.js automatische Updates über die Gitea-Releases
* ---------------------------------------------------------
* Ablauf: Releases abfragen -> neueste Version mit der laufenden vergleichen ->
* bei Bedarf den Windows-Installer herunterladen und starten.
* Ein Klick des Nutzers genügt; Download, Prüfung und Start laufen automatisch.
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const crypto = require('crypto');
const { spawn } = require('child_process');
const API = 'https://git.viper.ipv64.net/api/v1/repos/M_Viper/AeroMc-Launcher/releases';
const UA = 'AeroMC-Launcher';
// "1.2.3" -> [1,2,3]; führendes "v" und Zusätze wie "-beta" werden ignoriert
function parseVersion(v) {
const m = String(v || '').trim().replace(/^v/i, '').match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
if (!m) return null;
return [Number(m[1] || 0), Number(m[2] || 0), Number(m[3] || 0)];
}
// >0 wenn a neuer als b
function compareVersions(a, b) {
const x = parseVersion(a); const y = parseVersion(b);
if (!x || !y) return 0;
for (let i = 0; i < 3; i++) {
if (x[i] !== y[i]) return x[i] - y[i];
}
return 0;
}
// Windows-Installer im Release finden (.exe bevorzugt, sonst .zip)
function pickAsset(release) {
const assets = release.assets || [];
return assets.find((a) => /\.exe$/i.test(a.name))
|| assets.find((a) => /setup.*\.exe$/i.test(a.name))
|| assets.find((a) => /\.zip$/i.test(a.name))
|| null;
}
/*
* Prüft auf Updates.
* -> { available, currentVersion, latestVersion, notes, asset, url, noReleases }
*/
async function checkForUpdate(currentVersion) {
const res = await fetch(API + '?limit=10', { headers: { 'User-Agent': UA, Accept: 'application/json' } });
if (!res.ok) throw new Error('Update-Server antwortet mit HTTP ' + res.status);
const list = await res.json();
const releases = (Array.isArray(list) ? list : []).filter((r) => !r.draft);
if (!releases.length) {
return { available: false, noReleases: true, currentVersion };
}
// neueste anhand der Versionsnummer (nicht nach Datum, damit Nachträge nicht stören)
releases.sort((a, b) => compareVersions(b.tag_name, a.tag_name));
const latest = releases[0];
const available = compareVersions(latest.tag_name, currentVersion) > 0;
const asset = pickAsset(latest);
return {
available,
currentVersion,
latestVersion: String(latest.tag_name || '').replace(/^v/i, ''),
notes: latest.body || '',
published: latest.published_at || latest.created_at || null,
asset: asset ? { name: asset.name, url: asset.browser_download_url, size: asset.size } : null,
url: latest.html_url,
};
}
/*
* Lädt den Installer herunter (mit Fortschritt) und startet ihn.
* onProgress: ({ phase, loaded, total, percent })
*/
async function downloadAndInstall(asset, onProgress) {
if (!asset || !asset.url) throw new Error('Kein Installer im Release gefunden.');
const dir = path.join(os.tmpdir(), 'aeromc-update');
fs.mkdirSync(dir, { recursive: true });
const dest = path.join(dir, asset.name.replace(/[^\w.\-]/g, '_'));
const res = await fetch(asset.url, { headers: { 'User-Agent': UA } });
if (!res.ok) throw new Error('Download fehlgeschlagen: HTTP ' + res.status);
const total = Number(res.headers.get('content-length')) || asset.size || 0;
const chunks = [];
let loaded = 0;
for await (const chunk of res.body) {
chunks.push(Buffer.from(chunk));
loaded += chunk.length;
if (onProgress) {
onProgress({ phase: 'download', loaded, total, percent: total ? Math.round((loaded / total) * 100) : 0 });
}
}
const buf = Buffer.concat(chunks);
if (total && buf.length !== total) throw new Error('Download unvollständig.');
fs.writeFileSync(dest, buf);
const sha256 = crypto.createHash('sha256').update(buf).digest('hex');
if (onProgress) onProgress({ phase: 'ready', loaded: buf.length, total: buf.length, percent: 100 });
return { path: dest, bytes: buf.length, sha256 };
}
// Startet den Installer und löst sich vom Launcher (damit er sich selbst ersetzen kann)
function runInstaller(installerPath) {
const child = spawn(installerPath, [], { detached: true, stdio: 'ignore' });
child.unref();
return true;
}
module.exports = { checkForUpdate, downloadAndInstall, runInstaller, compareVersions, parseVersion };