Commit Graph

3 Commits

Author SHA1 Message Date
cproudlock
1bf3cb2e1c feat(installer): refuse to damage an installation it did not create
Some checks failed
CI / backend (push) Failing after 8s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 9s
CI / migrations-mysql (push) Failing after 7s
Two guards for a server deployed by hand, which the West Jefferson production
box is.

An existing venv is reused, which is right for a repair or an upgrade of an
install this made, and wrong when the venv belongs to a different Python. The
wheelhouse is tagged for one minor version, so pip finds no candidate for the
compiled packages and dies partway through - after Python has been installed and
the application tree replaced. The two versions are now compared up front and
the run stops with both numbers and what to do about it.

Switching deployment method removes the other method's IIS artifact. That is
correct when this installer owns both and dangerous when it does not: a wrong
-MountAlias would call Remove-WebApplication on a live mount with no prompt and
no error, and the first sign would be the site returning 404. It now refuses
unless a version stamp shows this installer made the install, or -AdoptExisting
is passed, and the refusal lists exactly what it would have removed.
2026-08-03 11:46:48 -04:00
cproudlock
44237b5cbd feat(installer): bundle URL Rewrite, ask where client IPs come from, verify installs
IIS does not set X-Forwarded-For on its own and HttpPlatformHandler connects
from loopback, so without a rewrite rule every client reads as 127.0.0.1. The
GE-Enforce IP allowlist, the dashboard visitor-location lookup and per-host
login rate limiting all stop working, silently. The rule needed URL Rewrite,
which the installer told operators to download - from an air-gapped server.

URL Rewrite now ships in the bundle, and the wizard asks which case applies,
because the two answers are mutually exclusive. Directly exposed: install it and
set X-Forwarded-For from REMOTE_ADDR, which is what stops a client spoofing its
own. Behind a proxy: leave the rule off, since REMOTE_ADDR is the proxy and
applying it would discard the real client IP.

The rule is enabled by deleting two explicit marker lines rather than by a regex
over the surrounding comment, so editing that prose cannot silently disable it.

An existing web.config is no longer overwritten. It is the one file on a server
that legitimately carries hand-edits, and replacing it reverted them without a
word - on a server where the X-Forwarded-For rule had been enabled by hand, that
alone would have turned the GE-Enforce IP allowlist off. The installer reports
what it found instead.

pip now runs with --require-hashes and --only-binary=:all:. Hash-checking is
requested explicitly rather than inferred from the lockfile, so shipping an
unhashed requirements.txt fails loudly instead of quietly dropping the check.

shopdb-admin.ps1 gains a verify command: which bundle this server was installed
from, and whether the installed packages still match what shipped.

The .iss states its compiler floor. WizardStyle uses the built-in windows11
custom style, which needs Inno Setup 6.6.0; older compilers now fail with that
sentence rather than 'WizardStyle is invalid'.
2026-08-03 11:17:58 -04:00
0fa5f1e910 feat(deploy): add the air-gapped Windows installer
Roughly 2500 lines of tested installer had been living in ~/Downloads and an
untracked folder - nothing was under version control.

It goes here rather than in a repo of its own because it depends on application
internals: the `flask plugin` verbs, site-profile.json, MOUNT_PATH, and the
plugin registry. Versioned separately it would drift out of step with the thing
it installs.

Contents: the read-only preflight, the staged installer (bundled MySQL, runtime,
schema, IIS, verify, uninstall), the operator console, the Inno Setup wizard, the
bundle builder and the artwork generator.

bundle/ and Output/ are ignored - regenerable, and ~220MB. plugins.iss is ignored
because build-installer.sh generates it from the staged payload. The artwork IS
committed so a Windows build box does not need Python and cairosvg.

Verified end to end on Windows Server 2025 against a bundled MySQL 8.0 and an
existing MySQL 5.6: fresh install, upgrade with backup and rollback, re-run
idempotency, uninstall, and both deployment methods including switching between
them. Not yet verified: a hypervisor-level air-gapped run, and any load from a
real browser (every HTTP check so far used curl, which sends no Origin header).
2026-08-03 01:47:34 -04:00