The bundled-database option could not actually be built. Stage 0 looks for mysql\mysql-8.0.x-winx64.msi, and Oracle no longer publishes a standalone server MSI for 8.0 - every 8.0.x returns 404. What remains for 8.0 is the MySQL Installer bundle, which is an installer-manager: 'msiexec /i INSTALLDIR=' would install THAT rather than a database, and stage 0 would then fail on a missing mysqld.exe. MySQL 8.0 also reached end of life in April 2026, so bundling it would have put an unsupported database on every new site. 8.4 LTS still ships the standalone MSI (129MB, which is what the '125MB' note in stage 0 was written against) and is supported into 2032. Defaults follow it: install root MySQL Server 8.4, service MySQL84. The operator console still looks for an 8.0 install path as a fallback, for sites already running one. Also bundles mysqlclient\ - mysql.exe and mysqldump.exe with the two OpenSSL DLLs they actually import, 20MB rather than the 51MB of debug and auth-plugin libraries the archive ships. Stage 2 stages it onto the server, so a site whose database is on ANOTHER host can still take the pre-upgrade backup that every upgrade depends on. That was the gap the preflight had started warning about. Bundle is now 221MB.
141 lines
6.4 KiB
Bash
Executable File
141 lines
6.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# Stage a lean per-site bundle next to ShopDBFlask.iss, ready for Inno Setup.
|
|
#
|
|
# The bundle is built FOR ONE SITE from its plugin profile (ADR-013): plugins the
|
|
# site did not choose are absent from the payload entirely. Build one installer
|
|
# per site, not one universal installer.
|
|
#
|
|
# Usage: build-installer.sh <site-profile.json> [repo-path]
|
|
#
|
|
# The wheelhouse cannot be built here. Wheels are cp314 win_amd64 and must be
|
|
# produced ON Windows with the matching Python:
|
|
# pip download -r requirements.txt -d wheels --only-binary=:all:
|
|
# Copy that wheels\ directory in before compiling.
|
|
set -euo pipefail
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROFILE="${1:?usage: build-installer.sh <site-profile.json> [repo-path]}"
|
|
REPO="${2:-$HOME/projects/shopdb-flask}"
|
|
BUNDLE="$HERE/bundle"
|
|
|
|
[ -f "$PROFILE" ] || { echo "profile not found: $PROFILE"; exit 1; }
|
|
[ -d "$REPO" ] || { echo "repo not found: $REPO"; exit 1; }
|
|
|
|
echo "==> Staging lean app tree from $PROFILE"
|
|
rm -rf "$BUNDLE/app"
|
|
bash "$REPO/scripts/build-site.sh" "$PROFILE" "$BUNDLE/app"
|
|
|
|
# build-site.sh emits the SPA as frontend-dist; the installer's web.config and
|
|
# static route expect frontend\dist.
|
|
if [ -d "$BUNDLE/app/frontend-dist" ]; then
|
|
mkdir -p "$BUNDLE/app/frontend"
|
|
rm -rf "$BUNDLE/app/frontend/dist"
|
|
mv "$BUNDLE/app/frontend-dist" "$BUNDLE/app/frontend/dist"
|
|
fi
|
|
# The /shopdb-based build, used when the operator picks the subpath deployment.
|
|
if [ -d "$BUNDLE/app/frontend-dist-subpath" ]; then
|
|
rm -rf "$BUNDLE/app/frontend/dist-subpath"
|
|
mv "$BUNDLE/app/frontend-dist-subpath" "$BUNDLE/app/frontend/dist-subpath"
|
|
fi
|
|
|
|
# Tell the .iss which plugins this bundle actually carries, so the wizard's
|
|
# plugin page always matches the payload instead of a hand-maintained list.
|
|
echo "==> Writing plugins.iss"
|
|
PLUGINS=$(ls "$BUNDLE/app/plugins" 2>/dev/null | tr '\n' ',' | sed 's/,$//')
|
|
# The subpath SPA is built with its base path compiled in, so whether the wizard
|
|
# can OFFER a subpath install is a property of the bundle, not a runtime choice.
|
|
SUBPATH_ALIAS_BUILT=""
|
|
if [ -f "$BUNDLE/app/frontend/dist-subpath/.alias" ]; then
|
|
SUBPATH_ALIAS_BUILT="$(cat "$BUNDLE/app/frontend/dist-subpath/.alias")"
|
|
fi
|
|
|
|
cat > "$HERE/plugins.iss" <<EOF
|
|
; GENERATED by build-installer.sh - do not edit.
|
|
; The plugins present in bundle\\app\\plugins at build time.
|
|
#define AvailablePlugins "$PLUGINS"
|
|
; The alias the subpath SPA was built for, or empty if this bundle has no
|
|
; subpath build - in which case the wizard must not offer that option.
|
|
#define SubpathAlias "$SUBPATH_ALIAS_BUILT"
|
|
EOF
|
|
echo " $PLUGINS"
|
|
|
|
# The product version, read from the code rather than restated here. A hardcoded
|
|
# AppVersion in the .iss had drifted two minor versions from shopdb/__init__.py.
|
|
echo "==> Writing version.iss"
|
|
APPVERSION=$(sed -n "s/^__version__ = '\\(.*\\)'/\\1/p" "$REPO/shopdb/__init__.py" | head -1)
|
|
[ -n "$APPVERSION" ] || { echo "could not read __version__ from shopdb/__init__.py"; exit 1; }
|
|
cat > "$HERE/version.iss" <<EOF
|
|
; GENERATED by build-installer.sh from shopdb/__init__.py - do not edit.
|
|
#define AppVersion "$APPVERSION"
|
|
EOF
|
|
echo " $APPVERSION"
|
|
|
|
# From THIS directory, which is the reviewed copy under version control. These
|
|
# used to be copied from $HOME/Downloads, so the installer logic that shipped was
|
|
# not the logic that was committed, and the build only worked on one machine.
|
|
echo "==> Copying installer scripts"
|
|
mkdir -p "$BUNDLE"
|
|
for f in shopdb-install.ps1 shopdb-preflight.ps1 bundle-lock.ps1; do
|
|
[ -f "$HERE/$f" ] || { echo "installer script missing from the repo: $f"; exit 1; }
|
|
cp "$HERE/$f" "$BUNDLE/"
|
|
done
|
|
|
|
echo ""
|
|
echo "Bundle staged at: $BUNDLE"
|
|
for d in app wheels python httpplatformhandler urlrewrite mysqlclient mysql; do
|
|
if [ -d "$BUNDLE/$d" ]; then
|
|
printf ' %-20s %s\n' "$d" "$(du -sh "$BUNDLE/$d" | cut -f1)"
|
|
else
|
|
printf ' %-20s absent\n' "$d"
|
|
fi
|
|
done
|
|
echo ""
|
|
echo " plugins shipped: $(ls "$BUNDLE/app/plugins" 2>/dev/null | tr '\n' ' ')"
|
|
|
|
# --- payload verification ---------------------------------------------------
|
|
# The third-party payload is the part git does not record: the wheels, the Python
|
|
# installer and the MSIs that run as SYSTEM on the target server. It must be
|
|
# EXACTLY what bundle-lock.json describes - no missing file, no stale extra wheel
|
|
# left over from a previous build, no changed content - or this is not a bundle
|
|
# anyone reviewed. Previously a missing wheelhouse printed MISSING and the script
|
|
# still exited 0, so an empty bundle compiled into a shippable installer and the
|
|
# failure surfaced on an air-gapped server with no way to fix it.
|
|
#
|
|
# ALLOW_UNLOCKED=1 downgrades this to a warning, for assembling a bundle before
|
|
# its lock exists. A bundle built that way must not be shipped.
|
|
echo ""
|
|
echo "==> Verifying the third-party payload against bundle-lock.json"
|
|
if python3 "$HERE/verify_bundle_lock.py" "$BUNDLE" "$HERE/bundle-lock.json"; then
|
|
echo " payload matches the lock"
|
|
# Ships WITH the bundle: the installer re-checks the payload on the target
|
|
# server before running any of it, so tampering between build and install is
|
|
# caught too.
|
|
cp "$HERE/bundle-lock.json" "$BUNDLE/"
|
|
elif [ "${ALLOW_UNLOCKED:-0}" = "1" ]; then
|
|
echo ""
|
|
echo " ALLOW_UNLOCKED=1: continuing anyway. DO NOT SHIP this bundle."
|
|
else
|
|
echo ""
|
|
echo " The bundle is not what the lock describes."
|
|
echo ""
|
|
echo " Add the missing pieces by hand:"
|
|
echo " wheels/ pip download -r requirements.txt --only-binary=:all: \\"
|
|
echo " --platform win_amd64 --python-version 314 \\"
|
|
echo " --implementation cp --abi cp314 -d wheels"
|
|
echo " python/ python-3.14.x-amd64.exe"
|
|
echo " httpplatformhandler/ httpPlatformHandler_amd64.msi"
|
|
echo " urlrewrite/ rewrite_amd64.msi (client-IP rule; see README)"
|
|
echo " mysql/ mysql-8.4.x-winx64.msi (bundled-database option only)"
|
|
echo ""
|
|
echo " If the payload changed ON PURPOSE, regenerate and COMMIT the lock:"
|
|
echo " pwsh ./refresh-bundle-lock.ps1 # review the diff"
|
|
echo " pwsh ./refresh-bundle-lock.ps1 -Yes # write it"
|
|
echo ""
|
|
echo " To stage a bundle before its lock exists: ALLOW_UNLOCKED=1 $0 ..."
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "Then compile on Windows: iscc ShopDBFlask.iss"
|
|
echo "(Inno Setup 6.6.0 or newer - the wizard uses the windows11 custom style.)"
|