Feature work from the 2026-07 session: Settings IA - Replace the flat 27-card settings hub with a persistent two-pane shell (SettingsLayout.vue): grouped, searchable left rail + content pane. - Nest all settings/* routes under the shell via router post-processing; shared nav catalog in settingsNav.js. Group by asset class (PCs, Printers, Equipment, Network) so per-type settings stop scattering. Custom fields (core) - customfields + customfieldvalues tables (migration 7d14), CRUD API at /api/customfields, per-asset value get/save. - Settings management page + reusable CustomFieldsSection (detail) and CustomFieldsInputs (form) wired into all four asset types. Warranty (new plugin) - plugins/warranty: warranties + warrantyassets (migration 7d15), derived coverage status, provider abstraction (manual now; Dell/Lenovo/HP stubs). - API CRUD + per-asset panel + report buckets; WarrantyPanel on all four detail pages; Warranties management page; Warranty report + Reports card. - Seed warranty.* permissions. Printer drivers - printerdrivers table (migration 7d13) linked to printer models; drivers now surface on the matching printer's detail page. Other - PCDetail rebalanced (Network + Status + Warranty + custom fields on the right). - Rename PCs list "Features" column to "Remote Access"; fix badge hover underline. - Drop equipment islocationonly field. - Centralize asset-type label/route maps into utils/assetTypes.js. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
173 lines
7.2 KiB
Markdown
173 lines
7.2 KiB
Markdown
# Per-Site Deployment Runbook
|
|
|
|
shopdb-flask is single-tenant per ADR-004. Each adopting facility runs its own stack: own DB, own users, own enabled plugins, own secrets. This document is the runbook for a fresh site deploy.
|
|
|
|
## Prerequisites
|
|
|
|
- Docker 24+ and Docker Compose v2 (or equivalent container runtime)
|
|
- A reverse proxy with TLS termination (nginx, traefik, Caddy, GE corporate LB) -- the framework does not terminate TLS itself
|
|
- A MySQL backup destination (offsite recommended)
|
|
- Access to the GE Aerospace Gitea or a clone of the repo
|
|
|
|
## Step 1: Clone and configure
|
|
|
|
```bash
|
|
git clone https://gitea.proudtech.net/ge-aerospace/shopdb-flask.git
|
|
cd shopdb-flask
|
|
cp .env.example .env
|
|
```
|
|
|
|
Edit `.env`:
|
|
|
|
| Variable | Required | Notes |
|
|
|----------|----------|-------|
|
|
| `FLASK_ENV` | Yes | `production` for live sites |
|
|
| `SECRET_KEY` | Yes | `python -c "import secrets; print(secrets.token_urlsafe(64))"` |
|
|
| `JWT_SECRET_KEY` | Yes | Same generation, different value |
|
|
| `DATABASE_URL` | Yes | `mysql+pymysql://shopdb:PASSWORD@db:3306/shopdb_flask` (matches docker-compose) |
|
|
| `CORS_ORIGINS` | Yes | Comma-separated explicit origins. Wildcard rejected. |
|
|
| `MYSQL_ROOT_PASSWORD` | Yes | Container only |
|
|
| `MYSQL_PASSWORD` | Yes | Container only, must match `DATABASE_URL` password |
|
|
| `MYSQL_PORT` | No | Default 3306 |
|
|
| `API_PORT` | No | Default 5001 |
|
|
| `LOG_LEVEL` | No | Default INFO |
|
|
| `ZABBIX_URL`, `ZABBIX_TOKEN` | No | Only if printers plugin uses Zabbix |
|
|
| `COLLECTOR_API_KEY` | No | Shared key for `/api/collector/*` ingest. Required only if unattended collectors push data. Endpoint fails closed (denies) when unset. |
|
|
| `COLLECTOR_API_KEY_<PLUGIN>` | No | Per-plugin override (e.g. `COLLECTOR_API_KEY_COMPUTERS`), checked before the shared key (ADR-006) |
|
|
| `EMPLOYEE_DB_HOST/USER/PASSWORD/NAME` | No | Read-only HR directory for notifications + kiosks. No safe default for the password. |
|
|
|
|
## Step 2: Bring up the stack
|
|
|
|
```bash
|
|
docker compose build
|
|
docker compose up -d
|
|
```
|
|
|
|
The MySQL container initializes its volume on first run. The API container waits for `db` to be healthy via `healthcheck`. Check logs:
|
|
|
|
```bash
|
|
docker compose logs -f api
|
|
```
|
|
|
|
If `ProductionConfig.validate()` raises, the container exits with the offending env-var named in the log. Fix `.env` and `docker compose up -d` again.
|
|
|
|
## Step 3: Initialize the database schema
|
|
|
|
```bash
|
|
docker compose exec api flask db upgrade
|
|
```
|
|
|
|
This applies the baseline migration (creates all tables) and any subsequent migrations. Re-running is idempotent.
|
|
|
|
**Charset:** the schema is utf8mb4 (`utf8mb4_unicode_ci`). The docker-compose `db` service sets `--character-set-server=utf8mb4`, so the auto-created `shopdb_flask` database is utf8mb4. If you point at an external MySQL instead of the bundled container, create the database as utf8mb4 first, or it inherits the server default (often latin1) and the schema silently drifts:
|
|
|
|
```sql
|
|
CREATE DATABASE shopdb_flask CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
```
|
|
|
|
`DATABASE_URL` must keep `?charset=utf8mb4` so the connection matches. On MySQL older than 5.7 also enable `innodb_large_prefix=ON` + `innodb_file_format=Barracuda`, or the utf8mb4 indexes exceed the 767-byte prefix limit (error 1071). MySQL 5.7+ and 8.0 need no extra config.
|
|
|
|
## Step 4: Seed reference data
|
|
|
|
```bash
|
|
docker compose exec api flask seed reference-data
|
|
```
|
|
|
|
Creates: default `Vendor`, `Location`, `BusinessUnit`, `OperatingSystem`, `AssetStatus`, `RelationshipType` rows seeded with the platform contract values (`partof`, `controls`, `connectedto`).
|
|
|
|
## Step 5: Pick plugins to enable
|
|
|
|
The image bundles all six plugins (computers, equipment, network, notifications, printers, usb). Only enabled plugins are loaded.
|
|
|
|
```bash
|
|
docker compose exec api flask plugin list
|
|
docker compose exec api flask plugin install computers
|
|
docker compose exec api flask plugin install equipment
|
|
# ... repeat for each plugin the site tracks
|
|
```
|
|
|
|
To install a sister-site or third-party plugin (per ADR-003), drop its directory into `<repo>/plugins/<name>/` (the docker-compose mounts this read-only into the container) and run `flask plugin install <name>`.
|
|
|
|
## Step 6: Create the admin user
|
|
|
|
```bash
|
|
docker compose exec api flask seed admin --username admin --email admin@facility.example.com
|
|
# Password is generated and printed once. Store in your password manager.
|
|
```
|
|
|
|
Subsequent users are managed through the UI.
|
|
|
|
## Step 7: Front the API with TLS
|
|
|
|
The Flask container listens on `5001/tcp` over plain HTTP. Production exposure must go through a reverse proxy that terminates TLS:
|
|
|
|
```nginx
|
|
server {
|
|
listen 443 ssl;
|
|
server_name shopdb.facility-a.example.com;
|
|
|
|
ssl_certificate /etc/ssl/certs/shopdb.crt;
|
|
ssl_certificate_key /etc/ssl/private/shopdb.key;
|
|
|
|
location / {
|
|
proxy_pass http://localhost:5001;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
}
|
|
```
|
|
|
|
The framework reads `X-Forwarded-For` for audit logging.
|
|
|
|
## Step 8: Backups
|
|
|
|
Per-site MySQL backups are the site's responsibility. Recommended: nightly `mysqldump` to offsite storage with 14-day retention.
|
|
|
|
```bash
|
|
docker compose exec -T db mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" shopdb_flask | gzip > backup-$(date +%F).sql.gz
|
|
```
|
|
|
|
Verify a restore quarterly.
|
|
|
|
## Step 9: Updates
|
|
|
|
```bash
|
|
git pull origin main
|
|
docker compose build api
|
|
docker compose up -d api
|
|
docker compose exec api flask db upgrade
|
|
```
|
|
|
|
The framework's `__contract_version__` may have moved. Check `docs/adr/` for any new ADRs since the last update. If an ADR introduces a breaking change, the upgrade may require coordinated work; the ADR's "Consequences" section documents it.
|
|
|
|
## Common issues
|
|
|
|
| Symptom | Cause | Fix |
|
|
|---------|-------|-----|
|
|
| `ConfigError: SECRET_KEY is required in production` | `.env` missing or blank | Set `SECRET_KEY` in `.env`, re-up |
|
|
| `ConfigError: CORS_ORIGINS must be a comma-separated allowlist` | `.env` has `*` | Set explicit origins |
|
|
| `PluginVersionError: requires core_version X but framework is Y` | Plugin pinned a too-narrow range | Update `manifest.json` `core_version` or pin framework version |
|
|
| 500s after `flask db upgrade` | Migration ran but app cached old schema | `docker compose restart api` |
|
|
| Cannot reach API after restart | Reverse proxy not pointing at the container's exposed port | Confirm `API_PORT` and proxy config |
|
|
|
|
## Health check
|
|
|
|
```bash
|
|
curl -s -X POST -H "Content-Type: application/json" \
|
|
-d '{}' http://localhost:5001/api/auth/login \
|
|
| jq .
|
|
# Expect: {"status": "error", "data": {"error": {"code": "VALIDATION_ERROR", ...}}}
|
|
```
|
|
|
|
If this returns a 500 or no JSON, the container is unhealthy. Check `docker compose logs api`.
|
|
|
|
## References
|
|
|
|
- [docs/adr/ADR-004-deployment-topology.md](adr/ADR-004-deployment-topology.md) - per-site instances rationale
|
|
- [docs/adr/ADR-003-plugin-distribution.md](adr/ADR-003-plugin-distribution.md) - bundled vs external plugins
|
|
- [docs/adr/ADR-006-collector-contract.md](adr/ADR-006-collector-contract.md) - per-plugin collector endpoints
|
|
- [docs/PLUGIN-QUICKSTART.md](PLUGIN-QUICKSTART.md) - building a custom plugin for your site
|
|
- [shopdb/config.py](../shopdb/config.py) - all the env-vars in one place
|