printers low-toner alerts; geenforce display readiness + token scope-binding; air-gap docker deploy kit

This commit is contained in:
2026-07-23 14:37:00 -04:00
parent 73b05e5b1c
commit c355eaae43
32 changed files with 2358 additions and 46 deletions

View File

@@ -0,0 +1,108 @@
<#
.SYNOPSIS
Build the shopdb-flask image on a CONNECTED box and bundle it (plus the MySQL
image) into one tarball for transfer to an air-gapped site.
.DESCRIPTION
Air-gapped sites cannot pip install / npm ci / docker pull. This builds the
fully self-contained application image where connectivity exists, pulls the
MySQL image the stack needs, and `docker save`s both into a single gzipped
tarball with a SHA-256 checksum. Carry the .tar.gz to the site and follow
docs/DEPLOY-AIRGAP.md (docker load + docker-compose.airgap.yml).
Run from the repo root. Requires Docker Desktop. The connected build box needs
clean access to Docker Hub + PyPI + npm. If it is behind GE Zscaler and the
build fails on pip/npm SSL (CERTIFICATE_VERIFY_FAILED), build from a box with
clean internet, or trust the corp root CA in the build - see
docs/DEPLOY-AIRGAP.md "Building behind Zscaler".
.PARAMETER Version
Image tag for shopdb-flask (default 0.7.0). This is the IMAGE_TAG the site
sets in its .env so docker-compose.airgap.yml runs the matching image.
.PARAMETER MysqlImage
MySQL image the stack runs (default mysql:8.0). Must match db.image in
docker-compose.airgap.yml.
.PARAMETER OutDir
Where to write the tarball + checksum (default the repo root).
.PARAMETER SkipBuild
Reuse an already-built shopdb-flask:<Version> image; only pull + save.
.EXAMPLE
pwsh scripts/build-offline-bundle.ps1 -Version 0.7.0
#>
[CmdletBinding()]
param(
[string]$Version = '0.7.0',
[string]$MysqlImage = 'mysql:8.0',
[string]$OutDir = '.',
[switch]$SkipBuild
)
$ErrorActionPreference = 'Stop'
function Assert-LastExit([string]$What) {
if ($LASTEXITCODE -ne 0) { throw "$What failed (exit $LASTEXITCODE)" }
}
# Fail early if docker is missing rather than midway through a long build.
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
throw 'docker not found on PATH. Install Docker Desktop and retry.'
}
$appImage = "shopdb-flask:$Version"
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
Push-Location $repoRoot
try {
if (-not $SkipBuild) {
Write-Host "==> Building $appImage" -ForegroundColor Cyan
docker build -t $appImage .
Assert-LastExit 'docker build'
} else {
Write-Host "==> Skipping build; reusing $appImage" -ForegroundColor Yellow
}
Write-Host "==> Pulling $MysqlImage" -ForegroundColor Cyan
docker pull $MysqlImage
Assert-LastExit 'docker pull'
if (-not (Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null }
$stem = "shopdb-stack-$Version"
$tar = Join-Path $OutDir "$stem.tar"
$gz = Join-Path $OutDir "$stem.tar.gz"
Write-Host "==> Saving $appImage + $MysqlImage" -ForegroundColor Cyan
docker save -o $tar $appImage $MysqlImage
Assert-LastExit 'docker save'
Write-Host "==> Compressing to $gz" -ForegroundColor Cyan
$inStream = [System.IO.File]::OpenRead($tar)
$outStream = [System.IO.File]::Create($gz)
$gzip = New-Object System.IO.Compression.GzipStream($outStream, [System.IO.Compression.CompressionMode]::Compress)
try {
$inStream.CopyTo($gzip)
} finally {
$gzip.Dispose(); $outStream.Dispose(); $inStream.Dispose()
}
Remove-Item $tar
$hash = (Get-FileHash $gz -Algorithm SHA256).Hash.ToLower()
$sumFile = "$gz.sha256"
"$hash $stem.tar.gz" | Set-Content -Path $sumFile -NoNewline
$sizeMb = [math]::Round((Get-Item $gz).Length / 1MB, 1)
Write-Host ''
Write-Host "Bundle ready: $gz ($sizeMb MB)" -ForegroundColor Green
Write-Host "SHA-256: $hash"
Write-Host "Checksum: $sumFile"
Write-Host ''
Write-Host 'At the air-gapped site (verify the checksum first):'
Write-Host " docker load -i $stem.tar.gz"
Write-Host " # set IMAGE_TAG=$Version in .env, then:"
Write-Host ' docker compose -f docker-compose.airgap.yml up -d'
}
finally {
Pop-Location
}