INSTALL-WINDOWS-IIS.md has said MySQL 8.4 LTS is standard for new installs since 8.0 reached end of life in April 2026, while both compose files and the offline bundler still pinned 8.0. A site reading the Windows runbook and a site reading the Docker one were being told to run different servers, and the migration page written this week sent people onto the dead one. Verified against a real server rather than by editing a tag: 8.4.11, core chain plus five plugin chains applied clean, 66 tables at a single utf8mb4_unicode_ci collation, six alembic version tables. The image's PyMySQL authenticates against 8.4's caching_sha2_password, which is what requirements.in already pins cryptography for. Existing servers need one thing done FIRST: 8.4 removes mysql_native_password, so an account created on 5.6 or 5.7 must be moved to caching_sha2_password before the upgrade or it cannot authenticate afterwards. In-place also has no downgrade path, and 5.7 cannot reach 8.4 in one hop. For databases this size a dump into a fresh 8.4 server is the better trade: same outage, and the old server stays as the rollback. Air-gapped sites need a fresh offline bundle, because the tarball carries the MySQL image alongside the app image. Also here, found by having it bite during that verification: the db healthcheck pinged over the unix socket, and the entrypoint's init pass answers on the socket while running the server with --skip-networking. The probe therefore reported healthy DURING init, which is what `depends_on: service_healthy` gates api and migrate on. A ping passed at 8 seconds and the next query failed because the server was mid-restart. Probing 127.0.0.1 keeps it red until the real server is listening.
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
|
|
}
|