# Building and releasing the Windows installer For whoever builds releases. If you run a server rather than build releases, see [UPDATES-WINDOWS.md](UPDATES-WINDOWS.md). Every release is one self-contained `.exe`. It carries the Python runtime, a hash-checked wheelhouse, MySQL, the IIS modules and the application, so nothing is fetched from the network at install time. That is the point: sites are air-gapped. ## What you need - A checkout of this repository. - Inno Setup 6.6.0 or newer, on Windows. Compiling needs Windows; staging the bundle does not. - PowerShell, for the bundle lock tools. - `uv`, only when dependencies change. ## The three kinds of change Almost every release is the first kind. ### 1. Application change: bug fix, feature, no new dependency ```bash # bump __version__ in shopdb/__init__.py first ./deploy/windows/installer/build-installer.sh ``` Then on Windows, in `deploy/windows/installer`: ``` iscc ShopDBFlask.iss ``` `build-installer.sh` restages the application, rebuilds the web interface for the chosen feature set, regenerates `plugins.iss` and `version.iss`, and re-verifies the third-party payload against `bundle-lock.json`. The lock is untouched, because nothing third-party changed. ### 2. A dependency is added, removed or moved The only case that touches the lock. ```bash # edit requirements.in, then uv pip compile requirements.in --universal --generate-hashes -o requirements.txt # fetch the wheel for the target runtime, into the wheelhouse pip download == -d deploy/windows/installer/bundle/wheels \ --only-binary=:all: --platform win_amd64 --python-version 314 --no-deps ``` Then regenerate and commit the lock: ```powershell pwsh ./refresh-bundle-lock.ps1 # review the change pwsh ./refresh-bundle-lock.ps1 -Yes # write it ``` **Commit `bundle-lock.json`. That commit is the review.** The lock records every third-party file and its SHA-256; the installer refuses to run if the payload it carries does not match, so an unreviewed substitution cannot reach a server. Two traps, both of which have already cost a release: - `--universal` is not optional. A resolve done only for the host platform drops packages that exist only on Windows, and the install then fails hash checking on a package with no entry. - Declare **every** runtime import in `requirements.in`, including ones that already work locally. `packaging` reached this project only as a test dependency, so the full suite passed while a production virtual environment, which has no test dependencies, could not import the application at all. `tests/test_runtime_dependencies.py` is the gate for this. ### 3. A new plugin Ordinary plugin work, plus three installer-specific steps: 1. **Stage it.** Add the plugin to the profile you build from. `plugins.iss` is generated from what is actually in the bundle, so the wizard offers it with no edit to the installer script. 2. **Decide whether it is ticked by default.** `PluginDefault()` in `ShopDBFlask.iss` is an exclusion list: a new plugin defaults to **ticked** unless you name it there. This is the one manual edit. 3. **Register its migrations.** Update `PLUGIN_TABLE_OWNERS` and `EXPECTED_HEAD_REVISION`, and give the plugin its own migration chain (ADR-008). Existing sites pick up its tables through `plugin upgrade-all`; sites that do not tick it have them pruned (ADR-014). ## Version numbers `build-installer.sh` reads `__version__` from `shopdb/__init__.py` and writes `version.iss` from it. Do not edit `version.iss`: a hardcoded version in the installer script had already drifted two minor versions from the code. Bump the version for every release you hand out. The installer compares versions and: - refuses a build **older** than what is installed, because migrations only go forwards; - warns and continues on an **equal** version, treating it as a repair. Equal-version rebuilds are useful while testing and are a poor idea in the field, since the server cannot then tell you what it is running. Follow ADR-007 for what each part means. ## Do not hand-edit generated files Regenerate these; changes are overwritten without warning: - `version.iss` - from `shopdb/__init__.py` - `plugins.iss` - from the plugins actually present in the bundle - `requirements.txt` - from `requirements.in` via `uv pip compile` - `bundle-lock.json` - via `refresh-bundle-lock.ps1` `waitress` and `tzdata` were once hand-added to `requirements.txt` and vanished on the next compile, taking the Windows runtime with them. ## Before you hand a build out ```bash python -m pytest -q # full suite ./scripts/check-naming-and-style.sh # naming and style ``` The suite includes gates worth knowing about: - `tests/test_runtime_dependencies.py` - every runtime import is declared - `tests/test_bundle_lock.py` - the lock covers the payload - `tests/test_docs_publishable.py` - `docs/` carries no internal references, because it is published `build-installer.sh` refuses to finish if the payload does not match the lock. That check is not advisory; do not work around it. ## Publishing Alongside the `.exe`, publish: - a `.sha256` file, so the operator can verify what they received; - release notes covering what changed and anything needing attention; - the version in the filename, so a server's build is identifiable on sight. The compiled installer stages a CycloneDX inventory (`sbom.cdx.json`) onto every server, which is what answers a security question about a published vulnerability without anyone guessing. ## Known gaps **The installer is not code-signed.** Every install shows an unknown-publisher warning, and the SHA-256 is the only integrity check. This is the significant remaining gap before wider distribution: a checksum published next to the file protects against corruption, not against someone who can write to that location. **Compiling requires Windows.** `build-installer.ps1` exists so the whole process can run on a Windows workstation. Nothing about it runs in CI, so a release is a deliberate act by a person. ## See also - [UPDATES-WINDOWS.md](UPDATES-WINDOWS.md) - what operators should expect - [INSTALL-WINDOWS.md](INSTALL-WINDOWS.md) - installing a new site - [OPERATE-WINDOWS.md](OPERATE-WINDOWS.md) - running a site - `docs/adr/` - ADR-007 versioning, ADR-008 plugin migrations, ADR-013 and ADR-014 lean per-site builds