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,183 @@
|
||||
#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))
|
||||
@@ -0,0 +1,115 @@
|
||||
#requires -Version 5
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Builds every tweak source folder under ..\pack\tweaks\ into a data-only
|
||||
mod jar and drops it into ..\pack\overrides\mods\.
|
||||
|
||||
.DESCRIPTION
|
||||
Each subfolder of ..\pack\tweaks\ is treated as one self-contained
|
||||
"data-only" NeoForge mod. Its modId, version, and display name come from
|
||||
the folder's META-INF\neoforge.mods.toml. The script:
|
||||
|
||||
1. Reads modId + version from neoforge.mods.toml.
|
||||
2. Zips the folder contents (data\, META-INF\, pack.mcmeta) into a jar
|
||||
named "<modId>-<version>.jar".
|
||||
3. Removes any older jars for the same modId from the output folder so
|
||||
stale versions don't get bundled into the manifest.
|
||||
|
||||
Run this before Build-Pack.ps1, or let Build-Pack.ps1 invoke it for you.
|
||||
|
||||
.PARAMETER TweaksRoot
|
||||
Folder containing tweak source subfolders. Defaults to ..\pack\tweaks\.
|
||||
|
||||
.PARAMETER OutputRoot
|
||||
Where built jars go. Defaults to ..\pack\overrides\mods\. This path
|
||||
becomes Build-Pack.ps1's -LocalPackSource (one level up).
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$TweaksRoot = "",
|
||||
[string]$OutputRoot = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
if (-not $TweaksRoot) { $TweaksRoot = Join-Path $here '..\pack\tweaks' }
|
||||
if (-not $OutputRoot) { $OutputRoot = Join-Path $here '..\pack\overrides\mods' }
|
||||
|
||||
if (-not (Test-Path $TweaksRoot)) {
|
||||
Write-Host "No tweaks folder at $TweaksRoot -- nothing to build."
|
||||
return
|
||||
}
|
||||
|
||||
if (-not (Test-Path $OutputRoot)) {
|
||||
New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null
|
||||
}
|
||||
|
||||
# Read modId + version out of a neoforge.mods.toml. We do this with a tiny regex
|
||||
# parser instead of a full TOML library because we only need two scalars and we
|
||||
# control the file format. Lines like: modId = "foo" version = "1.2.3"
|
||||
function Get-ModMeta {
|
||||
param([string]$TomlPath)
|
||||
if (-not (Test-Path $TomlPath)) {
|
||||
throw "Missing $TomlPath -- every tweak folder needs META-INF\neoforge.mods.toml."
|
||||
}
|
||||
$content = Get-Content $TomlPath -Raw
|
||||
|
||||
$modIdMatch = [regex]::Match($content, '(?m)^\s*modId\s*=\s*"([^"]+)"')
|
||||
$versionMatch = [regex]::Match($content, '(?m)^\s*version\s*=\s*"([^"]+)"')
|
||||
|
||||
if (-not $modIdMatch.Success) { throw "Could not parse modId from $TomlPath" }
|
||||
if (-not $versionMatch.Success) { throw "Could not parse version from $TomlPath" }
|
||||
|
||||
return [pscustomobject]@{
|
||||
ModId = $modIdMatch.Groups[1].Value
|
||||
Version = $versionMatch.Groups[1].Value
|
||||
}
|
||||
}
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
||||
$tweakDirs = Get-ChildItem -Path $TweaksRoot -Directory
|
||||
|
||||
if ($tweakDirs.Count -eq 0) {
|
||||
Write-Host "No tweak folders found under $TweaksRoot."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Building tweak jars from $TweaksRoot"
|
||||
Write-Host ""
|
||||
|
||||
foreach ($dir in $tweakDirs) {
|
||||
$tomlPath = Join-Path $dir.FullName 'META-INF\neoforge.mods.toml'
|
||||
$meta = Get-ModMeta -TomlPath $tomlPath
|
||||
|
||||
$jarName = "$($meta.ModId)-$($meta.Version).jar"
|
||||
$jarPath = Join-Path $OutputRoot $jarName
|
||||
|
||||
# Wipe stale jars for this modId so old versions don't get bundled into
|
||||
# the manifest alongside the new one.
|
||||
Get-ChildItem -Path $OutputRoot -Filter "$($meta.ModId)-*.jar" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -ne $jarName } |
|
||||
ForEach-Object {
|
||||
Write-Host (" removing stale: {0}" -f $_.Name)
|
||||
Remove-Item $_.FullName -Force
|
||||
}
|
||||
|
||||
if (Test-Path $jarPath) { Remove-Item $jarPath -Force }
|
||||
|
||||
[System.IO.Compression.ZipFile]::CreateFromDirectory(
|
||||
$dir.FullName,
|
||||
$jarPath,
|
||||
[System.IO.Compression.CompressionLevel]::Optimal,
|
||||
$false
|
||||
)
|
||||
|
||||
$size = (Get-Item $jarPath).Length
|
||||
Write-Host (" built {0,-40} {1,8:N0} bytes" -f $jarName, $size)
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Tweak jars are in: $OutputRoot"
|
||||
@@ -0,0 +1,101 @@
|
||||
#requires -Version 5
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Read-only update checker. Diffs the locked Modrinth versions against the
|
||||
latest available on Modrinth. Prints a report. Does not change anything.
|
||||
|
||||
.DESCRIPTION
|
||||
Run this periodically to see what mod updates are available without
|
||||
committing to anything. Decide which to bump, then either edit pack.lock.json
|
||||
by hand or re-run Update-Pack.ps1 to refresh everything.
|
||||
|
||||
CurseForge mods (FTB family) aren't auto-checked here -- CF requires an API
|
||||
key for proper version listing. Manually monitor those at:
|
||||
https://www.curseforge.com/minecraft/mc-mods/ftb-chunks-forge
|
||||
https://www.curseforge.com/minecraft/mc-mods/ftb-library-forge
|
||||
https://www.curseforge.com/minecraft/mc-mods/ftb-teams-forge
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$lockPath = Join-Path $here '..\pack\pack.lock.json'
|
||||
|
||||
if (-not (Test-Path $lockPath)) {
|
||||
throw "pack.lock.json not found. Run Update-Pack.ps1 first to bootstrap it."
|
||||
}
|
||||
|
||||
$lock = Get-Content $lockPath -Raw | ConvertFrom-Json
|
||||
$mc = $lock.minecraft
|
||||
$lt = $lock.loader.type
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Checking Modrinth for newer versions..."
|
||||
Write-Host (" Pack: $($lock.name) v$($lock.version)")
|
||||
Write-Host (" MC: $mc ($lt $($lock.loader.version))")
|
||||
Write-Host (" Locked: $($lock.lockedAt)")
|
||||
Write-Host ""
|
||||
|
||||
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)' }
|
||||
}
|
||||
|
||||
$updatesAvailable = 0
|
||||
$upToDate = 0
|
||||
$skipped = 0
|
||||
|
||||
foreach ($mod in $lock.mods) {
|
||||
if ($mod.source -ne "modrinth") {
|
||||
Write-Host (" [cforge] $($mod.slug.PadRight(22)) $($mod.version.PadRight(34)) (manual check)")
|
||||
$skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
$encGv = [System.Web.HttpUtility]::UrlEncode('["' + $mc + '"]')
|
||||
$encL = [System.Web.HttpUtility]::UrlEncode('["' + $lt + '"]')
|
||||
$versions = Invoke-Modrinth ("/project/$($mod.slug)/version?game_versions=$encGv" + "&" + "loaders=$encL")
|
||||
} catch {
|
||||
Write-Warning " [error] $($mod.slug) - $($_.Exception.Message)"
|
||||
continue
|
||||
}
|
||||
if (-not $versions -or $versions.Count -eq 0) {
|
||||
Write-Warning " [gone] $($mod.slug) - no versions found on Modrinth"
|
||||
continue
|
||||
}
|
||||
|
||||
$latest = $versions[0]
|
||||
$latestVer = $latest.version_number
|
||||
$latestType = $latest.version_type
|
||||
|
||||
if ($latest.id -eq $mod.versionId) {
|
||||
Write-Host (" [ok] $($mod.slug.PadRight(22)) $($mod.version.PadRight(34)) (current)")
|
||||
$upToDate++
|
||||
}
|
||||
else {
|
||||
$tag = if ($latestType -eq 'beta') { '[update*]' } else { '[update] ' }
|
||||
Write-Host (" $tag $($mod.slug.PadRight(22)) $($mod.version.PadRight(34)) -> $latestVer") -ForegroundColor Yellow
|
||||
$updatesAvailable++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Summary:"
|
||||
Write-Host (" Up to date: $upToDate")
|
||||
$updateColor = if ($updatesAvailable -gt 0) { 'Yellow' } else { 'White' }
|
||||
Write-Host (" Updates: $updatesAvailable") -ForegroundColor $updateColor
|
||||
Write-Host (" Manual check: $skipped (CurseForge)")
|
||||
Write-Host ""
|
||||
if ($updatesAvailable -gt 0) {
|
||||
Write-Host "[update*] = beta release. Bump only if you specifically want it."
|
||||
Write-Host ""
|
||||
Write-Host "To take all updates: run Update-Pack.ps1 (then Build-Pack.ps1 + deploy server)."
|
||||
Write-Host "To pick selectively: edit pack.lock.json by hand, then run Build-Pack.ps1."
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
#requires -Version 5
|
||||
<#
|
||||
.SYNOPSIS
|
||||
One-shot deploy: build launcher + server, regenerate manifest, mirror the
|
||||
deploy share, scp the server binary.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads `deploy.config.ps1` (sibling file, gitignored) for local paths +
|
||||
SSH details. Stages run in order; -Stage limits which stages run.
|
||||
|
||||
The script does NOT auto-restart the production daemon. After a server
|
||||
binary deploy it prompts you to do that yourself.
|
||||
|
||||
.PARAMETER Stage
|
||||
All | Launcher | Server | Pack. Defaults to All.
|
||||
|
||||
Launcher = build launcher + regenerate manifest + push to deploy share
|
||||
Server = build server + scp binary (atomic swap)
|
||||
Pack = regenerate manifest + mirror pack/overrides/* to share
|
||||
All = everything, in order
|
||||
|
||||
.PARAMETER SkipBuild
|
||||
Skip dotnet publish steps. Use when you've already built and just want
|
||||
to push artifacts.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Print each action without executing. No files copied, no SSH, no build.
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet('All','Launcher','Server','Pack')]
|
||||
[string]$Stage = 'All',
|
||||
[switch]$SkipBuild,
|
||||
[switch]$DryRun,
|
||||
# Skip the version-bump check. Use only for cosmetic/internal-only changes
|
||||
# where you're SURE clients don't need to re-sync. The default is to refuse
|
||||
# if the local pack/launcher version matches what's already deployed.
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
# ─── Resolve repo + load config ────────────────────────────────────────────
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repoRoot = Resolve-Path (Join-Path $here '..')
|
||||
$cfgPath = Join-Path $here 'deploy.config.ps1'
|
||||
if (-not (Test-Path $cfgPath)) {
|
||||
throw "Missing $cfgPath. Copy deploy.config.template.ps1 -> deploy.config.ps1 and fill in real values."
|
||||
}
|
||||
. $cfgPath
|
||||
|
||||
# Sanity-check required vars actually got set (template ships with CHANGE_ME).
|
||||
foreach ($v in 'DeployShare','ServerSshHost','ServerSshKey','ServerBinaryRemote') {
|
||||
$val = Get-Variable -Name $v -ValueOnly -ErrorAction Stop
|
||||
if ($val -match 'CHANGE_ME' -or [string]::IsNullOrWhiteSpace($val)) {
|
||||
throw "deploy.config.ps1 has placeholder/empty `$$v. Fill it in."
|
||||
}
|
||||
}
|
||||
|
||||
$shouldRunLauncher = $Stage -in @('All','Launcher')
|
||||
$shouldRunServer = $Stage -in @('All','Server')
|
||||
$shouldRunPack = $Stage -in @('All','Launcher','Pack') # manifest needs launcher exe meta
|
||||
|
||||
# ─── Pre-flight: version-bump check ────────────────────────────────────────
|
||||
# The launcher caches the pack by version: a client that already synced
|
||||
# pack v0.9.2 will short-circuit at "already on 0.9.2" if you re-deploy
|
||||
# under the same version, even if the file list / SHAs changed. Same idea
|
||||
# applies to launcherVersion (drives the in-launcher upgrade banner).
|
||||
# We fetch the currently-deployed manifest and refuse to deploy if the
|
||||
# matching version field hasn't been bumped. Use -Force to override (e.g.
|
||||
# for cosmetic-only changes where re-sync isn't needed).
|
||||
if (($shouldRunPack -or $shouldRunLauncher) -and -not $DryRun -and -not $Force) {
|
||||
if (-not $ManifestPublicUrl -or $ManifestPublicUrl -match 'CHANGE_ME') {
|
||||
throw "deploy.config.ps1 is missing `$ManifestPublicUrl. Set it to the deployed manifest URL (e.g. https://example.com/pack/manifest.json)."
|
||||
}
|
||||
$deployed = $null
|
||||
try {
|
||||
$deployed = Invoke-RestMethod -Uri $ManifestPublicUrl -TimeoutSec 8
|
||||
} catch {
|
||||
Write-Host "Pre-flight: couldn't fetch deployed manifest at $ManifestPublicUrl -- version check skipped (probably first deploy)." -ForegroundColor DarkGray
|
||||
}
|
||||
if ($deployed) {
|
||||
$errs = @()
|
||||
# Pack version check applies only when the user is explicitly deploying
|
||||
# pack content (Stage = All or Pack). A launcher-only deploy intentionally
|
||||
# leaves pack content alone, so the pack version SHOULD stay constant.
|
||||
$strictPackCheck = $Stage -in @('All','Pack')
|
||||
if ($strictPackCheck) {
|
||||
$lock = Get-Content (Join-Path $repoRoot 'pack\pack.lock.json') -Raw | ConvertFrom-Json
|
||||
if ($deployed.version -eq $lock.version) {
|
||||
$errs += "Pack version is unchanged ($($lock.version)). Clients cached at that version will SKIP the sync -- they won't pick up your pack changes. Bump 'version' in pack/pack.lock.json before deploying."
|
||||
}
|
||||
}
|
||||
if ($shouldRunLauncher) {
|
||||
$csprojPath = Join-Path $repoRoot 'launcher\ModpackLauncher.csproj'
|
||||
[xml]$csproj = Get-Content $csprojPath
|
||||
$localLauncherVersion = ($csproj.Project.PropertyGroup.Version | Where-Object { $_ }) | Select-Object -First 1
|
||||
# Manifest stores 4-part FileVersion (e.g. "0.4.4.0"); csproj <Version> is 3-part ("0.4.4"). Compare normalised.
|
||||
$deployedNorm = ($deployed.launcherVersion -replace '\.0+$','')
|
||||
$localNorm = ($localLauncherVersion -replace '\.0+$','')
|
||||
if ($deployedNorm -eq $localNorm -and $localNorm) {
|
||||
$errs += "Launcher version is unchanged ($localLauncherVersion). Existing 0.4.x installs won't see an upgrade prompt -- bump <Version> in launcher/ModpackLauncher.csproj before deploying."
|
||||
}
|
||||
}
|
||||
if ($errs.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "VERSION-BUMP CHECK FAILED:" -ForegroundColor Red
|
||||
foreach ($e in $errs) { Write-Host " - $e" -ForegroundColor Red }
|
||||
Write-Host ""
|
||||
Write-Host "If you're re-deploying without any user-visible changes, pass -Force to skip this check." -ForegroundColor DarkGray
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Pre-flight: was the daemon already running? ───────────────────────────
|
||||
# We don't auto-stop it (kicks active players to fix a problem that isn't
|
||||
# actually broken -- the atomic swap is safe with the daemon running). But
|
||||
# knowing the state up front lets us tailor the final "next steps" message
|
||||
# so a deploy success doesn't silently leave you on the old code.
|
||||
$daemonWasRunning = $false
|
||||
if ($shouldRunServer) {
|
||||
Write-Host "Pre-flight: checking daemon state on $ServerSshHost..." -ForegroundColor DarkGray
|
||||
# Single-quoted PS string so PowerShell doesn't try to interpret the
|
||||
# bash-side metacharacters. The remote shell sees the literal pgrep
|
||||
# command; the trailing $ anchors so we don't match run.sh wrappers.
|
||||
$remoteCmd = 'pgrep -f /brass-sigil-server$ 2>/dev/null'
|
||||
$pgrepOut = & ssh -i $ServerSshKey -o ConnectTimeout=5 -o BatchMode=yes $ServerSshHost $remoteCmd 2>$null
|
||||
$daemonWasRunning = -not [string]::IsNullOrWhiteSpace($pgrepOut)
|
||||
if ($daemonWasRunning) {
|
||||
Write-Host " Daemon is RUNNING. Atomic swap is safe -- but you'll need to" -ForegroundColor Yellow
|
||||
Write-Host " stop+start it after deploy for the new code to take effect." -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " Daemon is stopped. New binary will run as soon as you start it." -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Helpers ───────────────────────────────────────────────────────────────
|
||||
$stepNum = 0
|
||||
function Step($desc, [scriptblock]$body) {
|
||||
$script:stepNum++
|
||||
$start = Get-Date
|
||||
Write-Host ""
|
||||
Write-Host ("[{0}] {1}" -f $script:stepNum, $desc) -ForegroundColor Cyan
|
||||
if ($DryRun) {
|
||||
Write-Host " (dry-run, skipping)" -ForegroundColor DarkGray
|
||||
return
|
||||
}
|
||||
& $body
|
||||
$elapsed = (Get-Date) - $start
|
||||
Write-Host (" done in {0:N1}s" -f $elapsed.TotalSeconds) -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
# ─── Stage 1: build launcher ───────────────────────────────────────────────
|
||||
$launcherExe = Join-Path $repoRoot (Join-Path $LauncherPublishDir $LauncherExeName)
|
||||
if ($shouldRunLauncher -and -not $SkipBuild) {
|
||||
Step "Build launcher (dotnet publish launcher\)" {
|
||||
Push-Location (Join-Path $repoRoot 'launcher')
|
||||
try { dotnet publish -c Release -nologo | Out-Host }
|
||||
finally { Pop-Location }
|
||||
if (-not (Test-Path $launcherExe)) { throw "Launcher publish didn't produce $launcherExe" }
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Stage 2: build server ─────────────────────────────────────────────────
|
||||
$serverExe = Join-Path $repoRoot (Join-Path $ServerPublishDir $ServerExeName)
|
||||
if ($shouldRunServer -and -not $SkipBuild) {
|
||||
Step "Build server (dotnet publish server\ -r linux-x64)" {
|
||||
Push-Location (Join-Path $repoRoot 'server')
|
||||
try { dotnet publish -c Release -r linux-x64 --self-contained true -nologo | Out-Host }
|
||||
finally { Pop-Location }
|
||||
if (-not (Test-Path $serverExe)) { throw "Server publish didn't produce $serverExe" }
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Stage 3: regenerate manifest ──────────────────────────────────────────
|
||||
$manifestPath = Join-Path $here 'manifest.json'
|
||||
if ($shouldRunPack) {
|
||||
Step "Regenerate manifest (Build-Pack.ps1)" {
|
||||
$args = @{ OutputPath = $manifestPath }
|
||||
if ($shouldRunLauncher -and (Test-Path $launcherExe)) { $args.LauncherExePath = $launcherExe }
|
||||
& (Join-Path $here 'Build-Pack.ps1') @args | Out-Host
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Stage 4: mirror pack overrides to share ──────────────────────────────
|
||||
$overridesLocal = Join-Path $repoRoot 'pack\overrides'
|
||||
$shareFiles = Join-Path $DeployShare 'files'
|
||||
if ($shouldRunPack -and (Test-Path $overridesLocal)) {
|
||||
Step "Mirror pack/overrides/ -> $shareFiles" {
|
||||
# /MIR makes destination match source (deletes orphan files in $shareFiles).
|
||||
# /XJ skips junctions, /R:1 /W:1 keeps retry behaviour sane on flaky shares.
|
||||
robocopy $overridesLocal $shareFiles /MIR /XJ /R:1 /W:1 /NFL /NDL /NJH /NJS /NP | Out-Host
|
||||
# Robocopy returns 0-7 for success-with-info; 8+ is real failure.
|
||||
if ($LASTEXITCODE -ge 8) { throw "robocopy failed with exit $LASTEXITCODE" }
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Stage 5: deploy launcher exe + manifest to share ──────────────────────
|
||||
if ($shouldRunLauncher) {
|
||||
Step "Copy launcher.exe + manifest to $DeployShare" {
|
||||
Copy-Item $launcherExe (Join-Path $DeployShare $LauncherDeployedAs) -Force
|
||||
Copy-Item $manifestPath (Join-Path $DeployShare 'manifest.json') -Force
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Stage 6: scp + atomic swap server binary ──────────────────────────────
|
||||
if ($shouldRunServer) {
|
||||
Step "scp server binary -> $ServerSshHost`:$ServerBinaryRemote (atomic swap)" {
|
||||
$remoteNew = "$ServerBinaryRemote.new"
|
||||
& scp -i $ServerSshKey -o ConnectTimeout=15 $serverExe "$ServerSshHost`:$remoteNew"
|
||||
if ($LASTEXITCODE -ne 0) { throw "scp failed with exit $LASTEXITCODE" }
|
||||
$cmd = "chmod +x '$remoteNew' && mv '$remoteNew' '$ServerBinaryRemote' && md5sum '$ServerBinaryRemote'"
|
||||
& ssh -i $ServerSshKey -o ConnectTimeout=10 $ServerSshHost $cmd
|
||||
if ($LASTEXITCODE -ne 0) { throw "ssh swap failed with exit $LASTEXITCODE" }
|
||||
}
|
||||
Write-Host ""
|
||||
if ($daemonWasRunning) {
|
||||
Write-Host "Server binary swapped on disk, but the daemon was running before this" -ForegroundColor Yellow
|
||||
Write-Host "deploy and is still on the OLD code in memory (Linux preserves the running" -ForegroundColor Yellow
|
||||
Write-Host "inode through rename). Stop + start the daemon to pick up the new build." -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "Server binary swapped on disk. Daemon was stopped -- start it whenever" -ForegroundColor Green
|
||||
Write-Host "you're ready and it'll run the new build." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Deploy finished." -ForegroundColor Green
|
||||
@@ -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."
|
||||
@@ -0,0 +1,37 @@
|
||||
# Local deploy configuration. Copy this file to `deploy.config.ps1` and fill in
|
||||
# real values. `deploy.config.ps1` is gitignored so your local paths and the
|
||||
# server hostname never end up in version control.
|
||||
#
|
||||
# Deploy-Brass.ps1 dot-sources this file. Every variable below is required.
|
||||
|
||||
# ─── Public file hosting ───────────────────────────────────────────────────
|
||||
# Local path that maps (via SMB or similar) to the public docroot that hosts
|
||||
# `manifest.json` and the launcher .exe. Files copied here become reachable at
|
||||
# the public URLs embedded in the manifest (`launcherUrl`, file URLs, etc.).
|
||||
# Example: 'Z:\www\html\example.com\public\pack'
|
||||
$DeployShare = 'CHANGE_ME'
|
||||
|
||||
# Public URL of the deployed manifest. Used for the version-bump pre-flight:
|
||||
# Deploy-Brass.ps1 fetches this before deploying so it can refuse if you
|
||||
# changed pack content but forgot to bump pack/pack.lock.json's version
|
||||
# (clients with a cached pack at that version would skip the sync).
|
||||
$ManifestPublicUrl = 'https://CHANGE_ME/pack/manifest.json'
|
||||
|
||||
# ─── Server (brass-sigil-server daemon host) ───────────────────────────────
|
||||
# user@host for the Linux box running the daemon.
|
||||
$ServerSshHost = 'user@CHANGE_ME'
|
||||
|
||||
# Path to the local SSH private key authorised on the server.
|
||||
$ServerSshKey = "$env:USERPROFILE\.ssh\id_ed25519"
|
||||
|
||||
# Absolute path on the Linux box where the brass-sigil-server binary lives.
|
||||
# `Deploy-Brass.ps1` uploads to "<this>.new" then `mv` over for atomic swap.
|
||||
$ServerBinaryRemote = '/home/user/brass-sigil-server/brass-sigil-server'
|
||||
|
||||
# ─── Build outputs (don't normally need to change) ─────────────────────────
|
||||
$LauncherPublishDir = 'launcher\bin\Release\net8.0-windows\win-x64\publish'
|
||||
$LauncherExeName = 'ModpackLauncher.exe' # what dotnet publish produces
|
||||
$LauncherDeployedAs = 'BrassAndSigil-Launcher.exe' # filename on the public host
|
||||
|
||||
$ServerPublishDir = 'server\bin\Release\net8.0\linux-x64\publish'
|
||||
$ServerExeName = 'brass-sigil-server'
|
||||
Reference in New Issue
Block a user