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.
184 lines
7.5 KiB
PowerShell
184 lines
7.5 KiB
PowerShell
#requires -Version 5
|
|
<#
|
|
.SYNOPSIS
|
|
Generates manifest.json deterministically from pack.lock.json.
|
|
|
|
.DESCRIPTION
|
|
The lockfile (pack.lock.json) is the source of truth for every mod's exact
|
|
version, URL, SHA-1, and size. Running this script does not change versions.
|
|
|
|
To intentionally bump versions, use Update-Pack.ps1 with the -Refresh flag.
|
|
To see what new versions are available, use Check-Updates.ps1.
|
|
|
|
Workflow:
|
|
1. Edit pack.lock.json (manually or via Update-Pack.ps1 -Refresh)
|
|
2. Run Build-Pack.ps1 -OutputPath ...\manifest.json
|
|
3. Update the server to match
|
|
4. Deploy manifest to your hosted URL
|
|
|
|
.PARAMETER OutputPath
|
|
Where to write manifest.json.
|
|
|
|
.PARAMETER LocalPackSource
|
|
Optional folder containing local override files (configs, resourcepacks, etc.).
|
|
|
|
.PARAMETER SelfHostBaseUrl
|
|
Public URL prefix for self-hosted files (only used when LocalPackSource is set).
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$OutputPath = ".\manifest.json",
|
|
[string]$LocalPackSource = "",
|
|
[string]$SelfHostBaseUrl = "https://sijbers.uk/pack/files",
|
|
# Optional -- point at a published launcher .exe to embed launcherVersion + launcherUrl
|
|
# in the manifest. The launcher displays a "newer version available" banner when
|
|
# its embedded version is lower than this. Skip entirely to leave both fields out.
|
|
[string]$LauncherExePath = "",
|
|
[string]$LauncherPublicUrl = "https://sijbers.uk/pack/BrassAndSigil-Launcher.exe",
|
|
# Skip the auto-invoke of Build-Tweaks.ps1 (only touch this if you're debugging).
|
|
[switch]$SkipTweaks
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
|
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$lockPath = Join-Path $here '..\pack\pack.lock.json'
|
|
|
|
# Build any data-only tweak jars first, then fold the resulting overrides
|
|
# folder into the manifest. Set -SkipTweaks to bypass. The "overrides" name
|
|
# matches CurseForge/Modrinth modpack conventions: files that override or
|
|
# augment the standard mod set, hosted by us.
|
|
$tweaksRoot = Join-Path $here '..\pack\tweaks'
|
|
$overridesRoot = Join-Path $here '..\pack\overrides'
|
|
if (-not $SkipTweaks -and (Test-Path $tweaksRoot)) {
|
|
& (Join-Path $here 'Build-Tweaks.ps1') -TweaksRoot $tweaksRoot -OutputRoot (Join-Path $overridesRoot 'mods')
|
|
}
|
|
if (-not $LocalPackSource -and (Test-Path $overridesRoot)) {
|
|
$LocalPackSource = $overridesRoot
|
|
}
|
|
|
|
if (-not (Test-Path $lockPath)) {
|
|
throw "pack.lock.json not found at $lockPath. Run Update-Pack.ps1 -Refresh first to bootstrap it."
|
|
}
|
|
|
|
$lock = Get-Content $lockPath -Raw | ConvertFrom-Json
|
|
|
|
Write-Host ""
|
|
Write-Host "Building manifest from pack.lock.json:"
|
|
Write-Host (" Pack: {0} v{1}" -f $lock.name, $lock.version)
|
|
Write-Host (" MC: {0} ({1} {2})" -f $lock.minecraft, $lock.loader.type, $lock.loader.version)
|
|
Write-Host (" Locked: {0}" -f $lock.lockedAt)
|
|
Write-Host (" Mods: {0}" -f $lock.mods.Count)
|
|
Write-Host ""
|
|
|
|
$files = @()
|
|
$totalBytes = 0L
|
|
|
|
foreach ($mod in $lock.mods) {
|
|
$files += [pscustomobject]@{
|
|
path = $mod.path
|
|
url = $mod.url
|
|
sha1 = $mod.sha1
|
|
size = $mod.size
|
|
}
|
|
$totalBytes += $mod.size
|
|
Write-Host (" [{0}] {1,-26} {2,-22} {3,8:N0} KB" -f $mod.source.PadRight(7).Substring(0,7), $mod.slug, $mod.version, ($mod.size/1KB))
|
|
}
|
|
|
|
# Local overrides (configs, custom files not on Modrinth/CurseForge)
|
|
if ($LocalPackSource -and (Test-Path $LocalPackSource)) {
|
|
Write-Host ""
|
|
Write-Host "Adding local overrides from $LocalPackSource..."
|
|
$managedRoots = @('mods', 'config', 'resourcepacks', 'shaderpacks', 'kubejs', 'defaultconfigs')
|
|
$base = $SelfHostBaseUrl.TrimEnd('/')
|
|
$sourceFull = (Resolve-Path $LocalPackSource).Path.TrimEnd('\','/')
|
|
foreach ($root in $managedRoots) {
|
|
$rootDir = Join-Path $LocalPackSource $root
|
|
if (-not (Test-Path $rootDir)) { continue }
|
|
Get-ChildItem -Path $rootDir -Recurse -File | ForEach-Object {
|
|
$rel = $_.FullName.Substring($sourceFull.Length).TrimStart('\','/') -replace '\\','/'
|
|
$sha1 = (Get-FileHash -Algorithm SHA1 -Path $_.FullName).Hash.ToLowerInvariant()
|
|
$files += [pscustomobject]@{
|
|
path = $rel
|
|
url = "$base/$rel"
|
|
sha1 = $sha1
|
|
size = $_.Length
|
|
}
|
|
$totalBytes += $_.Length
|
|
Write-Host (" [local] {0}" -f $rel)
|
|
}
|
|
}
|
|
}
|
|
|
|
$manifest = [ordered]@{
|
|
name = $lock.name
|
|
version = $lock.version
|
|
minecraft = [ordered]@{ version = $lock.minecraft }
|
|
loader = [ordered]@{ type = $lock.loader.type; version = $lock.loader.version }
|
|
files = $files
|
|
}
|
|
|
|
# Optional: copy a defaultServer block through from the lockfile so the launcher
|
|
# can pre-populate friends' multiplayer list. Lockfile schema:
|
|
# "defaultServer": { "name": "Brass & Sigil", "ip": "bns.sijbers.uk" }
|
|
if ($lock.PSObject.Properties.Name -contains "defaultServer" -and $lock.defaultServer) {
|
|
$manifest.defaultServer = [ordered]@{
|
|
name = $lock.defaultServer.name
|
|
ip = $lock.defaultServer.ip
|
|
}
|
|
Write-Host (" Default server: {0} ({1})" -f $lock.defaultServer.name, $lock.defaultServer.ip)
|
|
}
|
|
|
|
# Optional: shader pack to enable by default on fresh installs. Lockfile schema:
|
|
# "defaultShader": "ComplementaryReimagined_r5.7.1.zip"
|
|
if ($lock.PSObject.Properties.Name -contains "defaultShader" -and $lock.defaultShader) {
|
|
$manifest.defaultShader = $lock.defaultShader
|
|
Write-Host (" Default shader: {0}" -f $lock.defaultShader)
|
|
}
|
|
|
|
# Optional: public URL of the brass-sigil-server panel. Used by the launcher
|
|
# to send friend-side whitelist requests. Lockfile schema:
|
|
# "panelUrl": "https://bns-admin.sijbers.uk"
|
|
if ($lock.PSObject.Properties.Name -contains "panelUrl" -and $lock.panelUrl) {
|
|
$manifest.panelUrl = $lock.panelUrl
|
|
Write-Host (" Panel URL: {0}" -f $lock.panelUrl)
|
|
}
|
|
|
|
# Optional launcher metadata: when -LauncherExePath is supplied, read the exe's
|
|
# FileVersion (set via <Version> in ModpackLauncher.csproj) and embed it. The
|
|
# launcher compares its assembly version to this value and shows an upgrade
|
|
# banner pointing at LauncherPublicUrl when older.
|
|
if ($LauncherExePath) {
|
|
if (-not (Test-Path $LauncherExePath)) {
|
|
throw "LauncherExePath '$LauncherExePath' does not exist."
|
|
}
|
|
$launcherFile = Get-Item $LauncherExePath
|
|
$launcherVersion = $launcherFile.VersionInfo.FileVersion
|
|
if ([string]::IsNullOrWhiteSpace($launcherVersion)) {
|
|
throw "Launcher exe has no FileVersion -- set <Version> in ModpackLauncher.csproj and republish."
|
|
}
|
|
# FileVersion is the four-component form (e.g. "0.1.0.0" for csproj <Version>0.1.0</Version>).
|
|
# Embed as-is -- the launcher's Version.Parse handles it directly and avoids ambiguous
|
|
# comparisons between trimmed and untrimmed forms.
|
|
$manifest.launcherVersion = $launcherVersion
|
|
$manifest.launcherUrl = $LauncherPublicUrl
|
|
|
|
Write-Host ""
|
|
Write-Host ("Launcher metadata embedded:")
|
|
Write-Host (" Version: {0}" -f $launcherVersion)
|
|
Write-Host (" Url: {0}" -f $LauncherPublicUrl)
|
|
Write-Host (" Source: {0}" -f $launcherFile.FullName)
|
|
}
|
|
|
|
$json = $manifest | ConvertTo-Json -Depth 10
|
|
$outDir = Split-Path -Parent $OutputPath
|
|
if ($outDir -and -not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir | Out-Null }
|
|
[System.IO.File]::WriteAllText($OutputPath, $json, [System.Text.UTF8Encoding]::new($false))
|
|
|
|
Write-Host ""
|
|
Write-Host "Manifest written: $OutputPath"
|
|
Write-Host (" Files: {0}" -f $files.Count)
|
|
Write-Host (" Total: {0:N1} MB (download size)" -f ($totalBytes / 1MB))
|