Merge origin/main into align-wilm-main (align wilm with Cameron's latest)

Bring main's endpoint hardening, operator-report validation, launch-path
policy, and theme-variable CSS into wilm. Channel model reconciled to this
repo's shipping (stable=release tag, edge=main trunk) while keeping wilm's
remote-BuildKit publish step and GHCR pull-based launcher.
This commit is contained in:
benjamin.baker
2026-09-02 17:41:03 -04:00
33 changed files with 1539 additions and 117 deletions

View File

@@ -6,7 +6,7 @@
Produces ONE self-contained installer package for the requested channel:
stable -> built from 'main', no DEV badge (SHOPDB_ENV_LABEL empty).
edge -> built from 'dev-be', red DEV badge (SHOPDB_ENV_LABEL=DEV).
edge -> built from 'main' (the trunk), red DEV badge (SHOPDB_ENV_LABEL=DEV).
This is the distributable counterpart to scripts/dev-bootstrap.ps1 (which
stands a dev box up FROM the repo). Here the operator has no repo: the script
@@ -28,14 +28,14 @@
by branch, image tag, and label - all data, not code.
.PARAMETER Channel
'stable' (main) or 'edge' (dev-be). Required.
'stable' (the newest release tag) or 'edge' (main, the trunk). Required.
.PARAMETER Version
Image tag / bundle version. Defaults to a channel-prefixed date stamp so two
channels built the same day never collide (e.g. 'stable-2024.06.01').
.PARAMETER Ref
Override the git ref to build. Defaults to 'main' for stable, 'dev-be' for
Override the git ref to build. Defaults to the newest v* tag for stable, 'main' for
edge. Lets a release engineer cut an installer from a tag or fix branch.
.PARAMETER MysqlImage
@@ -80,12 +80,28 @@ param(
$ErrorActionPreference = 'Stop'
# Channel -> (branch, badge label). The single source of channel truth, mirrored
# Channel -> (ref, badge label). The single source of channel truth, mirrored
# from dev-bootstrap.ps1 so the two entry points never disagree about what a
# channel means.
#
# stable has NO fixed ref: a release here is a tag, not a branch, so it is
# resolved below from the newest v* tag. Writing 'main' here would build the
# trunk and hand it to a site under a word that promises a release - the same
# mislabel this repo's channel mapping was corrected for, missed in this file
# the first time round.
$channels = @{
stable = @{ Ref = 'main'; EnvLabel = '' }
edge = @{ Ref = 'dev-be'; EnvLabel = 'DEV' }
stable = @{ Ref = $null; EnvLabel = '' }
edge = @{ Ref = 'main'; EnvLabel = 'DEV' }
}
function Resolve-StableRef {
# Newest release tag by version sort. Falls back to main with a warning
# rather than silently building something and calling it stable.
$tag = (git tag --list 'v*' --sort='-v:refname' | Select-Object -First 1)
if ($tag) { return $tag }
Write-Warning 'No v* tag in this repo; building stable from main.'
return 'main'
}
}
function Assert-LastExit([string]$What) {
@@ -100,7 +116,11 @@ if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
}
$config = $channels[$Channel]
$branch = if ($Ref) { $Ref } else { $config.Ref }
# An explicit -Ref always wins. Otherwise the channel decides: edge is the
# trunk, stable resolves the newest release tag.
$branch = if ($Ref) { $Ref }
elseif ($config.Ref) { $config.Ref }
else { Resolve-StableRef }
if (-not $Version) { $Version = "$Channel-$(Get-Date -Format 'yyyy.MM.dd')" }
$appImage = "shopdb-flask:$Version"
@@ -271,8 +291,15 @@ Write-Host "==> Loading image bundle" -ForegroundColor Cyan
docker load -i $bundle.FullName
if ($LASTEXITCODE -ne 0) { throw "docker load failed ($LASTEXITCODE)." }
# -p is NOT optional here. Without it compose names the project after the
# containing folder, which is shopdb-<channel>-<version> - so installing the
# next version creates a NEW project with new empty named volumes, and the site
# comes up looking wiped. The old data is still there under the old project's
# volumes, but nothing carries it across. Pin the project to the channel so an
# upgrade lands on the same volumes.
$composeArgs = @(
'compose', '--env-file', '.env',
'-p', 'shopdb-__CHANNEL__',
'-f', 'docker-compose.airgap.yml',
'-f', 'docker-compose.channel.yml'
)
@@ -289,6 +316,11 @@ Write-Host "ShopDB is up: https://localhost:$apiPort" -ForegroundColor Green
Write-Host "Seed the first admin (once):"
Write-Host " docker compose --env-file .env -f docker-compose.airgap.yml exec api flask seed admin <user> <email> <password>"
'@
# The here-string above is single-quoted so nothing in it interpolates - that
# is deliberate, the installer is full of $ that must survive verbatim. The
# compose project name is the one value that has to be baked per channel, so
# substitute it here rather than switching the whole block to interpolation.
$installer = $installer.Replace('__CHANNEL__', $Channel)
Set-Content -Path (Join-Path $pkgDir 'Install-ShopDB.ps1') -Value $installer -Encoding ASCII
# --- 6. README ----------------------------------------------------------