Assign printers to a MACHINE and let whichever PC controls it install them, with the observed half kept strictly apart from the assigned half: a collector report can never write an assignment row. Adoption is explicit. Adds a singular relationship type so defaultprinter can mean exactly one, client scripts that converge a bay's queues and correct a drifted one in place, and a wave gate so a fleet-wide driver rollout cannot stampede its own share. On the container side: the compose stack never persisted the instance directory, so a rebuild discarded plugins.json and every upload. It is a volume now, the image ships instance/ owned by the app user so the mountpoint is writable, and the database moves to MySQL 8.4 LTS, which the Windows runbook has recommended since 8.0 went end of life. The image also could not build at all: the frontend stage never copied the plugin staging script it runs. Adds a topology migration guide covering IIS to Docker and back.
109 lines
3.9 KiB
PowerShell
109 lines
3.9 KiB
PowerShell
<#
|
|
.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.4). 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.4',
|
|
[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
|
|
}
|