Files
brass-and-sigil/scripts/Build-Pack.ps1
T
Matt Sijbers a1331212cb 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.
2026-05-05 00:19:05 +01:00

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))