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:
Matt Sijbers
2026-05-05 00:19:05 +01:00
commit a1331212cb
99 changed files with 12640 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
#requires -Version 5
<#
.SYNOPSIS
Refreshes pack.lock.json by querying Modrinth + CurseForge for the latest
compatible version of each mod listed below.
.DESCRIPTION
THIS SCRIPT MUTATES pack.lock.json. Run only when you intentionally want to
bump versions (and remember to update the server to match).
For non-mutating "what's available?" reports, run Check-Updates.ps1.
.PARAMETER PackName
.PARAMETER PackVersion
.PARAMETER MinecraftVersion
.PARAMETER LoaderType
.PARAMETER LoaderVersion
Override pack metadata. Defaults read from existing lockfile if present.
#>
[CmdletBinding()]
param(
[string]$PackName = "",
[string]$PackVersion = "",
[string]$MinecraftVersion = "1.21.1",
[string]$LoaderType = "neoforge",
[string]$LoaderVersion = ""
)
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$lockPath = Join-Path $here '..\pack\pack.lock.json'
# Read existing lock for defaults
$existing = if (Test-Path $lockPath) { Get-Content $lockPath -Raw | ConvertFrom-Json } else { $null }
if (-not $PackName -and $existing) { $PackName = $existing.name }
if (-not $PackVersion -and $existing) { $PackVersion = $existing.version }
if (-not $LoaderVersion -and $existing) { $LoaderVersion = $existing.loader.version }
if (-not $PackName) { $PackName = "Brass and Sigil" }
if (-not $PackVersion) { $PackVersion = "0.6.0" }
if (-not $LoaderVersion) { $LoaderVersion = "21.1.228" }
# ---------------------------------------------------------------------------
# Mod definitions: edit here when adding/removing mods. allowBeta=true allows
# fallback to beta if no release exists.
# ---------------------------------------------------------------------------
$modrinthMods = @(
@{ slug = "create"; allowBeta = $false }
@{ slug = "create-aeronautics"; allowBeta = $false }
@{ slug = "sable"; allowBeta = $false }
@{ slug = "create-big-cannons"; allowBeta = $false }
@{ slug = "create-tfmg"; allowBeta = $false }
@{ slug = "distanthorizons"; allowBeta = $true }
@{ slug = "sodium"; allowBeta = $false }
@{ slug = "iris"; allowBeta = $false }
@{ slug = "modernfix"; allowBeta = $false }
@{ slug = "ferrite-core"; allowBeta = $false }
@{ slug = "architectury-api"; allowBeta = $false }
@{ slug = "rhino"; allowBeta = $true }
@{ slug = "rpl"; allowBeta = $false }
@{ slug = "kubejs"; allowBeta = $false }
@{ slug = "jei"; allowBeta = $true }
@{ slug = "jade"; allowBeta = $false }
@{ slug = "chunky"; allowBeta = $false }
)
# CurseForge mods aren't auto-resolved (no public API without a key).
# Update fileId/version/filename here when bumping.
$curseforgeMods = @(
@{
slug = "ftb-library"
version = "2101.1.31"
fileId = "7746959"
path = "mods/ftb-library-neoforge-2101.1.31.jar"
url = "https://mediafilez.forgecdn.net/files/7746/959/ftb-library-neoforge-2101.1.31.jar"
}
@{
slug = "ftb-teams"
version = "2101.1.9"
fileId = "7369021"
path = "mods/ftb-teams-neoforge-2101.1.9.jar"
url = "https://mediafilez.forgecdn.net/files/7369/21/ftb-teams-neoforge-2101.1.9.jar"
}
@{
slug = "ftb-chunks"
version = "2101.1.14"
fileId = "7608681"
path = "mods/ftb-chunks-neoforge-2101.1.14.jar"
url = "https://mediafilez.forgecdn.net/files/7608/681/ftb-chunks-neoforge-2101.1.14.jar"
}
)
Add-Type -AssemblyName System.Web
function Invoke-Modrinth {
param([string]$Path)
Invoke-RestMethod -Uri "https://api.modrinth.com/v2$Path" -Headers @{ 'User-Agent' = 'BrassAndSigil-Launcher/0.1 (matt@sijbers.uk)' }
}
function Get-LatestModrinthVersion {
param([string]$Slug, [bool]$AllowBeta)
$encGv = [System.Web.HttpUtility]::UrlEncode('["' + $MinecraftVersion + '"]')
$encL = [System.Web.HttpUtility]::UrlEncode('["' + $LoaderType + '"]')
$versions = Invoke-Modrinth ("/project/$Slug/version?game_versions=$encGv" + "&" + "loaders=$encL")
if (-not $versions -or $versions.Count -eq 0) { return $null }
if ($AllowBeta) {
# Take the newest version regardless of stability (Modrinth orders newest first).
return $versions[0]
}
# Stable-only: latest release version, or null if there's no release at all.
return $versions | Where-Object { $_.version_type -eq 'release' } | Select-Object -First 1
}
$lockedMods = @()
$missing = @()
Write-Host ""
Write-Host "Querying Modrinth for $($modrinthMods.Count) mods (MC $MinecraftVersion / $LoaderType)..."
Write-Host ""
foreach ($mod in $modrinthMods) {
try {
$version = Get-LatestModrinthVersion -Slug $mod.slug -AllowBeta $mod.allowBeta
} catch {
Write-Warning " [error] $($mod.slug) - $($_.Exception.Message)"
$missing += $mod.slug
continue
}
if (-not $version) {
Write-Warning " [missing] $($mod.slug) - no compatible version"
$missing += $mod.slug
continue
}
$primary = $version.files | Where-Object { $_.primary -eq $true } | Select-Object -First 1
if (-not $primary) { $primary = $version.files[0] }
$lockedMods += [ordered]@{
source = "modrinth"
slug = $mod.slug
versionId = $version.id
version = $version.version_number
path = "mods/$($primary.filename)"
url = $primary.url
sha1 = $primary.hashes.sha1
size = $primary.size
}
$tag = if ($version.version_type -eq 'beta') { '[beta] ' } else { '[release]' }
Write-Host (" $tag $($mod.slug.PadRight(22)) $($version.version_number.PadRight(28))")
}
Write-Host ""
Write-Host "Hashing $($curseforgeMods.Count) CurseForge mods (manual entries)..."
$tmpDir = Join-Path $env:TEMP "brassandsigil-cf-cache"
if (-not (Test-Path $tmpDir)) { New-Item -ItemType Directory -Path $tmpDir | Out-Null }
foreach ($cf in $curseforgeMods) {
$fname = Split-Path -Leaf $cf.path
$tmpFile = Join-Path $tmpDir $fname
try {
if (-not (Test-Path $tmpFile)) {
Invoke-WebRequest -Uri $cf.url -OutFile $tmpFile -UseBasicParsing -Headers @{ 'User-Agent' = 'BrassAndSigil-Launcher/0.1 (matt@sijbers.uk)' }
}
$sha1 = (Get-FileHash -Algorithm SHA1 -Path $tmpFile).Hash.ToLowerInvariant()
$size = (Get-Item $tmpFile).Length
$lockedMods += [ordered]@{
source = "curseforge"
slug = $cf.slug
fileId = $cf.fileId
version = $cf.version
path = $cf.path
url = $cf.url
sha1 = $sha1
size = $size
}
Write-Host (" [cforge] $($cf.slug.PadRight(22)) $($cf.version)")
}
catch {
Write-Warning " [error] $fname - $($_.Exception.Message)"
$missing += $fname
}
}
$lock = [ordered]@{
'$schema' = "Brass-and-Sigil pack.lock.json - generated, do not edit by hand unless you know what you are doing"
name = $PackName
version = $PackVersion
minecraft = $MinecraftVersion
loader = [ordered]@{ type = $LoaderType; version = $LoaderVersion }
lockedAt = (Get-Date -Format 'o')
mods = $lockedMods
}
$json = $lock | ConvertTo-Json -Depth 10
[System.IO.File]::WriteAllText($lockPath, $json, [System.Text.UTF8Encoding]::new($false))
Write-Host ""
Write-Host "Lockfile written: $lockPath"
Write-Host (" Total mods: {0}" -f $lockedMods.Count)
if ($missing.Count -gt 0) {
Write-Warning "Missing: $($missing -join ', ')"
}
Write-Host ""
Write-Host "Run Build-Pack.ps1 next to generate the manifest."