diff --git a/CHANGELOG.md b/CHANGELOG.md index c40e4a4..8cded22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,28 @@ ADR-007 and ADR-002. its own hostname, the gate runs BEFORE the manifest is read (the manifest is on that share too), and it fails closed on an unparseable date. +### Changed + +- **MySQL 8.4 LTS** in both compose files, and as the default in + `build-offline-bundle.ps1`. 8.0 reached end of life in April 2026, which + `INSTALL-WINDOWS-IIS.md` already said while the stack still pinned it; the two + halves of the product disagreed about which server a site should run. + Verified against a real 8.4.11 server rather than by changing a tag: the core + chain and five plugin chains applied clean, 66 tables at a single + `utf8mb4_unicode_ci` collation, and the image's PyMySQL authenticates against + 8.4's `caching_sha2_password` (which is why `cryptography` is pinned). + Sites upgrading an EXISTING server should note that 8.4 removes + `mysql_native_password`: an account created on 5.6 or 5.7 must be moved to + `caching_sha2_password` before the upgrade or it cannot log in afterwards. + Air-gapped sites need a fresh offline bundle, since the tarball carries the + MySQL image. +- **The database healthcheck probes over TCP** rather than the unix socket. The + entrypoint's init pass answers on the socket while running the server with + `--skip-networking`, so a socket ping reported healthy DURING init and + `depends_on: service_healthy` released `api` and `migrate` against a server + that was about to restart. Found by having it happen: a probe passed at 8s and + the next query failed because the server was mid-restart. + ### Fixed - **The image did not build.** `npm run build` fires a `prebuild` hook that runs diff --git a/docker-compose.airgap.yml b/docker-compose.airgap.yml index a13386e..7354ccb 100644 --- a/docker-compose.airgap.yml +++ b/docker-compose.airgap.yml @@ -35,7 +35,7 @@ x-app-env: &app-env services: db: - image: mysql:8.0 + image: mysql:8.4 command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci restart: unless-stopped environment: @@ -48,7 +48,12 @@ services: ports: - "127.0.0.1:${MYSQL_PORT:-3306}:3306" healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] + # 127.0.0.1, not localhost: localhost means the unix socket, and the + # entrypoint's init pass answers on the socket while running the server + # with --skip-networking. A socket ping therefore reports healthy DURING + # init, and the api/migrate services start against a server that is about + # to restart. Over TCP the probe stays red until the real server listens. + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 5 diff --git a/docker-compose.yml b/docker-compose.yml index bd3d15a..5f591ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: db: - image: mysql:8.0 + image: mysql:8.4 # utf8mb4 server-wide so the auto-created MYSQL_DATABASE is utf8mb4, not the # image default. Keeps every site's schema on the same charset/collation. command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci @@ -36,7 +36,12 @@ services: ports: - "127.0.0.1:${MYSQL_PORT:-3306}:3306" healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] + # 127.0.0.1, not localhost: localhost means the unix socket, and the + # entrypoint's init pass answers on the socket while running the server + # with --skip-networking. A socket ping therefore reports healthy DURING + # init, and the api/migrate services start against a server that is about + # to restart. Over TCP the probe stays red until the real server listens. + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] interval: 10s timeout: 5s retries: 5 diff --git a/docs/DEPLOY-AIRGAP.md b/docs/DEPLOY-AIRGAP.md index 26cd17c..1b9e869 100644 --- a/docs/DEPLOY-AIRGAP.md +++ b/docs/DEPLOY-AIRGAP.md @@ -27,7 +27,7 @@ pwsh scripts/build-offline-bundle.ps1 -Version 0.7.0 ``` This builds `shopdb-flask:0.7.0` (frontend + all Python deps baked in), pulls -`mysql:8.0`, and writes: +`mysql:8.4`, and writes: - `shopdb-stack-0.7.0.tar.gz` - both images in one archive - `shopdb-stack-0.7.0.tar.gz.sha256` - checksum to verify after transfer diff --git a/docs/DEVELOPMENT-SETUP.md b/docs/DEVELOPMENT-SETUP.md index 1f5a6f3..e8bd9b3 100644 --- a/docs/DEVELOPMENT-SETUP.md +++ b/docs/DEVELOPMENT-SETUP.md @@ -25,7 +25,7 @@ sane, then use manual for day-to-day work. | --- | --- | --- | | Python | 3.14 (64-bit) - matches CI, the container image and the Windows installer wheelhouse | `python --version` | | Node.js | 18+ | `node --version` | -| MySQL | 8.0 (or Docker, below) | `mysql --version` | +| MySQL | 8.4 LTS (or Docker, below) | `mysql --version` | | Git | any recent | `git --version` | On Windows, install all of them with winget (accept each license, then diff --git a/docs/MIGRATE-TOPOLOGY.md b/docs/MIGRATE-TOPOLOGY.md index b59ec60..ded6265 100644 --- a/docs/MIGRATE-TOPOLOGY.md +++ b/docs/MIGRATE-TOPOLOGY.md @@ -57,7 +57,7 @@ image with the matching `VITE_BASE_PATH`. ### 3. MySQL version and character set The Windows runbook supports 5.6, 5.7 and 8.4. `docker-compose.yml` runs -`mysql:8.0` and forces `utf8mb4` / `utf8mb4_unicode_ci` server-wide so every +`mysql:8.4` and forces `utf8mb4` / `utf8mb4_unicode_ci` server-wide so every site shares one collation. A dump from an older server can carry `latin1` or 3-byte `utf8` table diff --git a/scripts/build-offline-bundle.ps1 b/scripts/build-offline-bundle.ps1 index 5e61367..0371a21 100644 --- a/scripts/build-offline-bundle.ps1 +++ b/scripts/build-offline-bundle.ps1 @@ -21,7 +21,7 @@ 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 + MySQL image the stack runs (default mysql:8.4). Must match db.image in docker-compose.airgap.yml. .PARAMETER OutDir @@ -36,7 +36,7 @@ [CmdletBinding()] param( [string]$Version = '0.7.0', - [string]$MysqlImage = 'mysql:8.0', + [string]$MysqlImage = 'mysql:8.4', [string]$OutDir = '.', [switch]$SkipBuild )