a1331212cb
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.
113 lines
3.8 KiB
C#
113 lines
3.8 KiB
C#
using System.Diagnostics;
|
|
|
|
namespace BrassAndSigil.Server.Services;
|
|
|
|
/// <summary>
|
|
/// Downloads NeoForge's official server installer JAR and runs it with --installServer
|
|
/// to produce run.sh/run.bat + the server library tree. Handles Java invocation and
|
|
/// streams installer output via a progress callback.
|
|
/// </summary>
|
|
public sealed class NeoForgeInstaller
|
|
{
|
|
private static readonly HttpClient _http = new() { Timeout = TimeSpan.FromMinutes(10) };
|
|
|
|
public bool IsAlreadyInstalled(string serverDir)
|
|
{
|
|
return File.Exists(Path.Combine(serverDir, OperatingSystem.IsWindows() ? "run.bat" : "run.sh"));
|
|
}
|
|
|
|
public async Task<bool> InstallAsync(string version, string serverDir, string javaPath,
|
|
IProgress<string>? progress, CancellationToken ct)
|
|
{
|
|
Directory.CreateDirectory(serverDir);
|
|
|
|
// 1. Download installer
|
|
var installerName = $"neoforge-{version}-installer.jar";
|
|
var installerPath = Path.Combine(serverDir, installerName);
|
|
var url = $"https://maven.neoforged.net/releases/net/neoforged/neoforge/{version}/{installerName}";
|
|
|
|
if (!File.Exists(installerPath))
|
|
{
|
|
progress?.Report($"Downloading NeoForge {version} installer...");
|
|
var bytes = await _http.GetByteArrayAsync(url, ct);
|
|
await File.WriteAllBytesAsync(installerPath, bytes, ct);
|
|
progress?.Report($" Saved {bytes.Length:N0} bytes to {installerName}");
|
|
}
|
|
else
|
|
{
|
|
progress?.Report($"NeoForge installer already present, skipping download.");
|
|
}
|
|
|
|
// 2. Run installer
|
|
progress?.Report("Running NeoForge installer (java -jar ... --installServer)...");
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = javaPath,
|
|
WorkingDirectory = serverDir,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true,
|
|
};
|
|
psi.ArgumentList.Add("-jar");
|
|
psi.ArgumentList.Add(installerName);
|
|
psi.ArgumentList.Add("--installServer");
|
|
|
|
Process? proc;
|
|
try
|
|
{
|
|
proc = Process.Start(psi);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
progress?.Report($" [error] Could not start java: {ex.Message}");
|
|
return false;
|
|
}
|
|
if (proc is null)
|
|
{
|
|
progress?.Report(" [error] Failed to start java.");
|
|
return false;
|
|
}
|
|
|
|
var stdoutTask = StreamLines(proc.StandardOutput, line => progress?.Report($" {line}"), ct);
|
|
var stderrTask = StreamLines(proc.StandardError, line => progress?.Report($" [err] {line}"), ct);
|
|
|
|
await proc.WaitForExitAsync(ct);
|
|
await Task.WhenAll(stdoutTask, stderrTask);
|
|
|
|
if (proc.ExitCode != 0)
|
|
{
|
|
progress?.Report($" [error] NeoForge installer exited with code {proc.ExitCode}");
|
|
return false;
|
|
}
|
|
|
|
// 3. Verify run script exists
|
|
if (!IsAlreadyInstalled(serverDir))
|
|
{
|
|
progress?.Report(" [error] NeoForge installer ran but run.sh/run.bat is missing.");
|
|
return false;
|
|
}
|
|
|
|
progress?.Report($"NeoForge {version} installed.");
|
|
|
|
// 4. Clean up the installer JAR (large, no longer needed)
|
|
try { File.Delete(installerPath); } catch { /* best-effort */ }
|
|
|
|
return true;
|
|
}
|
|
|
|
private static async Task StreamLines(StreamReader reader, Action<string> onLine, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
while (!reader.EndOfStream)
|
|
{
|
|
var line = await reader.ReadLineAsync(ct);
|
|
if (line is null) break;
|
|
onLine(line);
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|