Initial commit: Brass & Sigil monorepo
Self-hosted Minecraft modpack distribution + administration system.
- launcher/ Avalonia 12 desktop client; single-file win-x64 publish.
Microsoft auth via XboxAuthNet, manifest+SHA-1 mod sync,
portable install path, sidecar settings.
- server/ brass-sigil-server daemon (.NET 8, linux-x64). Wraps the
MC subprocess, embedded Kestrel admin panel with cookie
auth + rate limiting, RCON bridge, scheduled backups,
BlueMap CLI integration with player markers + skin proxy,
friend-side whitelist request flow, world wipe with seed
selection (keep current / random / custom).
- pack/ pack.lock.json (Modrinth + manual CurseForge entries),
data-only tweak source under tweaks/, build outputs in
overrides/ (gitignored).
- scripts/ Build-Pack / Build-Tweaks / Update-Pack / Check-Updates
plus Deploy-Brass.ps1 unified one-shot deploy with
version-bump pre-flight and daemon-state detection.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
using System.Formats.Tar;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BrassAndSigil.Server.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Downloads + extracts Adoptium Temurin JRE 21 to server/java/. Used as a fallback
|
||||
/// when system Java is missing or too old. Adoptium's API gives us a stable
|
||||
/// platform-keyed download URL without needing API keys or auth.
|
||||
/// </summary>
|
||||
public sealed class JavaInstaller
|
||||
{
|
||||
private static readonly HttpClient _http = new() { Timeout = TimeSpan.FromMinutes(15) };
|
||||
|
||||
public string GetJavaInstallDir(string serverDir) => Path.Combine(serverDir, "java");
|
||||
public string GetJavaInstallDir(string serverDir, int majorVersion) =>
|
||||
Path.Combine(serverDir, "java" + majorVersion);
|
||||
|
||||
/// <summary>If a previous install put a java executable under serverDir/java/, return its path.</summary>
|
||||
public string? FindBundledJava(string serverDir)
|
||||
{
|
||||
var javaDir = GetJavaInstallDir(serverDir);
|
||||
if (!Directory.Exists(javaDir)) return null;
|
||||
var exe = OperatingSystem.IsWindows() ? "java.exe" : "java";
|
||||
return Directory.EnumerateFiles(javaDir, exe, SearchOption.AllDirectories).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>Find a Java install for a specific major version (e.g. javaXX/jdk-XX*/bin/java).</summary>
|
||||
public string? FindBundledJava(string serverDir, int majorVersion)
|
||||
{
|
||||
var javaDir = GetJavaInstallDir(serverDir, majorVersion);
|
||||
if (!Directory.Exists(javaDir)) return null;
|
||||
var exe = OperatingSystem.IsWindows() ? "java.exe" : "java";
|
||||
return Directory.EnumerateFiles(javaDir, exe, SearchOption.AllDirectories).FirstOrDefault();
|
||||
}
|
||||
|
||||
public Task<string?> InstallJre21Async(string serverDir, IProgress<string>? progress, CancellationToken ct)
|
||||
=> InstallJreAsync(21, serverDir, GetJavaInstallDir(serverDir), progress, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Download + extract Adoptium Temurin JRE for a specific major version into
|
||||
/// <paramref name="installDir"/>. Used by BlueMap to get JRE 25 alongside the
|
||||
/// JRE 21 we use for Minecraft.
|
||||
/// </summary>
|
||||
public async Task<string?> InstallJreAsync(int majorVersion, string serverDir, string installDir,
|
||||
IProgress<string>? progress, CancellationToken ct)
|
||||
{
|
||||
var javaDir = installDir;
|
||||
Directory.CreateDirectory(javaDir);
|
||||
|
||||
var (url, archiveName, isZip) = PickAdoptiumDownload(majorVersion);
|
||||
if (url is null)
|
||||
{
|
||||
progress?.Report($"[err] No supported Adoptium binary for {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var archivePath = Path.Combine(javaDir, archiveName!);
|
||||
progress?.Report($"Downloading Adoptium Temurin JRE 21 ({(isZip ? "zip" : "tar.gz")})...");
|
||||
|
||||
try
|
||||
{
|
||||
using (var resp = await _http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, ct))
|
||||
{
|
||||
resp.EnsureSuccessStatusCode();
|
||||
await using var src = await resp.Content.ReadAsStreamAsync(ct);
|
||||
await using var dst = File.Create(archivePath);
|
||||
await src.CopyToAsync(dst, ct);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progress?.Report($" [err] Download failed: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
progress?.Report($" Downloaded {new FileInfo(archivePath).Length:N0} bytes");
|
||||
progress?.Report("Extracting...");
|
||||
|
||||
try
|
||||
{
|
||||
if (isZip)
|
||||
{
|
||||
ZipFile.ExtractToDirectory(archivePath, javaDir, overwriteFiles: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await using var fs = File.OpenRead(archivePath);
|
||||
await using var gzip = new GZipStream(fs, CompressionMode.Decompress);
|
||||
await TarFile.ExtractToDirectoryAsync(gzip, javaDir, overwriteFiles: true, ct);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progress?.Report($" [err] Extract failed: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
try { File.Delete(archivePath); } catch { /* best-effort */ }
|
||||
|
||||
var javaExe = FindBundledJava(serverDir);
|
||||
if (javaExe is null)
|
||||
{
|
||||
progress?.Report(" [err] Extracted, but couldn't locate bin/java in the result.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// On Linux/macOS, make sure java is executable. TarFile preserves mode bits in
|
||||
// most setups, but be defensive.
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
try
|
||||
{
|
||||
File.SetUnixFileMode(javaExe,
|
||||
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
|
||||
UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
|
||||
UnixFileMode.OtherRead | UnixFileMode.OtherExecute);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
progress?.Report($"Java {majorVersion} ready: {javaExe}");
|
||||
return javaExe;
|
||||
}
|
||||
|
||||
private static (string? Url, string? ArchiveName, bool IsZip) PickAdoptiumDownload(int majorVersion)
|
||||
{
|
||||
// Adoptium API picks the latest GA release matching our os/arch.
|
||||
// Docs: https://api.adoptium.net/q/swagger-ui/
|
||||
var arch = RuntimeInformation.OSArchitecture switch
|
||||
{
|
||||
Architecture.X64 => "x64",
|
||||
Architecture.Arm64 => "aarch64",
|
||||
_ => null
|
||||
};
|
||||
if (arch is null) return (null, null, false);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return ($"https://api.adoptium.net/v3/binary/latest/{majorVersion}/ga/windows/{arch}/jre/hotspot/normal/eclipse",
|
||||
$"jre{majorVersion}.zip", true);
|
||||
}
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return ($"https://api.adoptium.net/v3/binary/latest/{majorVersion}/ga/linux/{arch}/jre/hotspot/normal/eclipse",
|
||||
$"jre{majorVersion}.tar.gz", false);
|
||||
}
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return ($"https://api.adoptium.net/v3/binary/latest/{majorVersion}/ga/mac/{arch}/jre/hotspot/normal/eclipse",
|
||||
$"jre{majorVersion}.tar.gz", false);
|
||||
}
|
||||
return (null, null, false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user