diff --git a/docs/BACKUP-RESTORE.md b/docs/BACKUP-RESTORE.md index 197a16e..393ac9f 100644 --- a/docs/BACKUP-RESTORE.md +++ b/docs/BACKUP-RESTORE.md @@ -166,10 +166,53 @@ Two Windows-specific notes: using a remote MySQL needs `mysqlclient\` in its installer bundle, or the pre-upgrade backup is skipped. `shopdb-admin.ps1 check` reports this. -Restoring is the standard `mysql < dump.sql`, then -`.\shopdb-admin.ps1 restart`. Also restore `C:\shopdb-flask\instance\` if you are -rebuilding a server - it holds uploaded branding and map blueprints, which the -database does not. +### Restoring on Windows, in order + +Order matters. Restoring the database under running code that expects a +different schema is how a restore turns into a second incident. + +```powershell +cd C:\shopdb-flask + +# 1. Stop serving. The pool, not the whole site: other applications on this +# IIS server are unaffected. +.\shopdb-admin.ps1 stop + +# 2. Restore the database. Use the dump taken closest BEFORE the problem, +# not the newest one - the newest may already contain it. +mysql -u root -p shopdb_flask < C:\ProgramData\ShopDB-Flask\backups\shopdb_flask-pre-upgrade-20260814-0730.sql + +# 3. Restore instance\ if you are rebuilding a server rather than just +# reverting data. It holds uploaded branding, map blueprints, application +# images and warranty proofs - none of which are in the database. +robocopy D:\backups\instance C:\shopdb-flask\instance /MIR + +# 4. Start, then prove it. +.\shopdb-admin.ps1 start +.\shopdb-admin.ps1 check -Json +``` + +`check -Json` reports version, publishing method, IIS and pool state, HTTP +reachability, database reachability, Python version and installed plugins. If it +passes, the restore worked; if the version it reports is not the version you +expect, see "Rolling back a release" in [UPDATES-WINDOWS.md](UPDATES-WINDOWS.md), +because a restored database and newer code is the one combination the installer +cannot fix for you. + +### What is lost if `.env` is lost + +`.env` is not in the database dump, and rebuilding it is not simply retyping it: + +| Value | If it is lost | +|---|---| +| `SECRET_KEY`, `JWT_SECRET_KEY` | Every issued token and session becomes invalid. Users log in again; managed API tokens must be reissued, which means every collector and GE-Enforce client needs its key replaced. Recoverable, but it is a fleet-wide job. | +| `DATABASE_URL` password | Recoverable: reset the MySQL user's password and write the new one in. | +| `MYSQL_ROOT_PASSWORD` | Recoverable through MySQL's own reset procedure, which requires stopping the server. | +| `ZABBIX_TOKEN` and similar integration tokens | Reissue at the far end. Nothing else breaks. | + +So back it up with the database, to somewhere as protected as the dump - it is +ACL'd to Administrators and SYSTEM on the server for the same reason. A dump +without its `.env` restores the data and locks everyone out of it. See [OPERATE-WINDOWS.md](OPERATE-WINDOWS.md). diff --git a/docs/FLEET-ARCHITECTURE.md b/docs/FLEET-ARCHITECTURE.md index 1f2a5a1..c891307 100644 --- a/docs/FLEET-ARCHITECTURE.md +++ b/docs/FLEET-ARCHITECTURE.md @@ -68,6 +68,25 @@ takes everything over HTTPS, because those PCs have no share. credentials at all, because a screensaver and a wall display have nowhere to keep one. They are read-only and carry nothing a floor visitor should not see. +## Where the credentials live + +Three documents each named a different place, which is what happens when a +credential is described from whichever end the author was working on. Both ends +are here. + +| Credential | On the server | On the PC | Rotate by | +|---|---|---|---| +| Collector key | A managed token scoped to `collector.ingest` only. `COLLECTOR_API_KEY` in `.env` also works and is simpler for a pilot, but cannot be revoked per fleet. | `HKLM:\SOFTWARE\GE\ShopDB` value `CollectorKey` | Replacing the token file the fleet reads from, so every PC picks it up on its next cycle. Never put it in the manifest - manifests sync broadly. | +| GE-Enforce fetch token | A managed service token scoped to the GE-Enforce fetch scope | `HKLM:\SOFTWARE\GE\ShopDB` value `ApiToken` | Same path as the collector key | +| Server URL | n/a | `HKLM:\SOFTWARE\GE\ShopDB` value `BaseUrl` | Setting it once at install; nothing should overwrite a bay's own value | +| Application secrets | `.env`: `SECRET_KEY`, `JWT_SECRET_KEY`, `DATABASE_URL`, integration tokens | n/a | See [BACKUP-RESTORE](BACKUP-RESTORE.md) - losing these invalidates every issued token | + +Two rules behind that table. A shop-floor PC is not a trusted place, so what it +holds is scoped to exactly what it does: a collector key that leaks buys the +reader the ability to file a PC report, not to read the asset register. And a +credential is delivered, never typed - a value that has to be entered per machine +is a value that is wrong on some machine. + ## What owns what The question that decides most arguments: **who is allowed to change this?** diff --git a/docs/INSTALL-WINDOWS.md b/docs/INSTALL-WINDOWS.md index 13d325c..902e019 100644 --- a/docs/INSTALL-WINDOWS.md +++ b/docs/INSTALL-WINDOWS.md @@ -27,6 +27,47 @@ Installing IIS, if it is missing (this needs no internet): Install-WindowsFeature -Name Web-Server -IncludeManagementTools ``` +### Sizing the server + +Small, and the reasons are worth stating so a site does not over-provision. +ShopDB serves one plant's own staff, not the internet: a busy instance handles a +few dozen concurrent users and a fleet reporting a few hundred PCs a day, and +both are trivial loads. What actually consumes resources is MySQL and the +uploads. + +| | Minimum | Comfortable | Why | +|---|---|---|---| +| Disk | 5 GB free (the installer refuses below this) | 40 GB | The application and Python are about 1 GB. The rest is MySQL, config-backup revisions, and uploads - branding, floor-plan blueprints, application installers and warranty proofs, which are the only ones that grow without bound. | +| RAM | 4 GB | 8 GB | MySQL wants most of it. The application itself is a Python worker per request. | +| CPU | 2 cores | 4 cores | Reports and the map are the heaviest reads and are cached. | + +A VM is entirely normal. The one thing worth insisting on is that the database +directory and the backup directory are not on the same disk as anything you +would be sad to fill up. + +### Getting the installer + +The `.exe` is a release asset on the GitHub repository, not a file in it - it is +around 240 MB, which is well past what a repository accepts and well inside what +a release asset does. Take it from the release page for the version you want, +and keep it: you need the previous one to roll a release back, and you cannot +download it from your own server. + +Every build is stamped with its version and ships a CycloneDX SBOM onto the +server, so `shopdb-admin.ps1 verify -Path ` answers "does this server +carry component X" without a network call - which is the question that arrives +on a Friday afternoon after a CVE announcement. + +### If you get stuck + +Raise an issue on the repository. Include the output of +`shopdb-admin.ps1 check -Json`, which reports version, IIS and pool state, +database reachability, Python version and installed plugins, and contains no +secrets - it is the single most useful thing you can attach, and it is designed +to be pasted. + +--- + The installer **checks all of this before it changes anything**, and it will not let you continue until the check passes. You do not have to get it right first time. diff --git a/docs/UPDATES-WINDOWS.md b/docs/UPDATES-WINDOWS.md index 366b0c4..3f2eed3 100644 --- a/docs/UPDATES-WINDOWS.md +++ b/docs/UPDATES-WINDOWS.md @@ -83,8 +83,45 @@ Installing an older build over a newer one is blocked outright. Once migrations have moved the schema forward, older code cannot read it, and the failures are difficult to unpick. -To go back you restore a backup taken before the update. Keep the previous -`.exe` until you are satisfied with a release. +Keep the previous `.exe` until you are satisfied with a release. You cannot +download it again from the server, and it is what a rollback needs. + +### Rolling back a release + +Rolling back is restoring a matched pair: the code AND the database it expects. +Doing one without the other is the state the installer refuses to create, and it +is worth understanding why - newer code against an older schema fails at the +first query that touches a new column, while older code against a newer schema +often appears to work until something silently misreads a column that changed +meaning. + +The update takes its own backup before touching the schema, so the pair you need +already exists. + +```powershell +cd C:\shopdb-flask +.\shopdb-admin.ps1 stop + +# 1. Database first, from the pre-upgrade dump the update took. +mysql -u root -p shopdb_flask < C:\ProgramData\ShopDB-Flask\backups\shopdb_flask-pre-upgrade-.sql + +# 2. Then the code: run the PREVIOUS installer .exe. It is an install over a +# restored older schema, which is the ordinary case, not a downgrade. +.\ShopDBFlask_Installer_.exe + +# 3. Prove the pair matches. +.\shopdb-admin.ps1 check -Json +``` + +If a migration failed mid-update, the installer has already restored its own +backup and there is nothing to roll back - read the log it names and fix +forward. Rollback is for a release that installed cleanly and then behaved +badly, which is a different situation and the only one this procedure is for. + +**Uploaded files are not in the dump.** If the release you are leaving behind +accepted uploads, restore `instance\` from your own backup as well, or those +files stay while the rows referring to them do not. See +[BACKUP-RESTORE.md](BACKUP-RESTORE.md). ## Will an update affect other sites on the same IIS server?