Files
shopdb-flask/docs/CONFIG.md
cproudlock a846587f39
All checks were successful
CI / backend (push) Successful in 1m23s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Add email sending (service + 3 flows) and a general asset label generator
Email: a stdlib SMTP mailer (settings-first config, graceful no-op when
unconfigured), a test-email endpoint wired to the Email settings page,
forced first-login password change (users.mustchangepassword, migration
7d23, /change-password flow), new-user welcome mail, and on-demand
report/alert delivery (POST /api/reports/email + Email Report buttons)
with an external-cron-with-a-scoped-PAT path documented for automation.
All tests patch smtplib - no network.

Labels: a shared /print/asset-label/<type>/<id> view any asset detail
page opens - card or plain style, QR or barcode, configurable encoding.
Per-type qr_target_* templates plus label_default_style/codetype/encodes
settings on the Printing page. Measuring-tool labels default to encoding
their inspection-operation code (derived from the location name, e.g.
0615), so every tool in an area shares the area code - verified by
decoding the rendered QR. Machine labels default to the machine number;
blank-serial handled gracefully.

808 tests pass; both features verified live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:58:30 -04:00

19 KiB

Configuration Reference

shopdb-flask reads configuration from two places, and the split is deliberate:

  • Environment variables (.env / container env) hold secrets and deploy-time wiring: database credentials, signing keys, CORS origins, ports, API keys. These are read once at boot by shopdb/config.py. Never put a secret in the Settings table.
  • The Settings table (seeded by flask seed settings, edited in the UI under Settings or the setup wizard) holds site preferences: branding, ServiceNow links, floor-map images, search toggles, facility identity. These can change at runtime without a restart and are per-instance.

Rule of thumb: if leaking it would be a security incident, it is an environment variable. If it is a site preference an admin should be able to change in the UI, it is a Setting.


Part 1: Environment variables

Defined in shopdb/config.py. Copy .env.example to .env and fill in values. In production (FLASK_ENV=production), ProductionConfig.validate() refuses to boot if SECRET_KEY, JWT_SECRET_KEY, DATABASE_URL, or CORS_ORIGINS are missing or set to the dev defaults.

Flask core

Variable Required Default Notes
FLASK_APP No wsgi.py Entry point for the flask CLI.
FLASK_ENV Yes development production for live sites (triggers validate()). Other values: development, testing.
SECRET_KEY Yes (prod) dev default Flask session/signing key. Generate: python -c "import secrets; print(secrets.token_urlsafe(64))".
JWT_SECRET_KEY Yes (prod) dev default JWT signing key. Different value from SECRET_KEY.
JWT_ACCESS_TOKEN_EXPIRES No 3600 Access-token TTL in seconds.
JWT_REFRESH_TOKEN_EXPIRES No 2592000 Refresh-token TTL in seconds (30 days).
CORS_ORIGINS Yes (prod) http://localhost:5173 Comma-separated explicit origins. Wildcard * is rejected in production.
LOG_LEVEL No INFO Logging verbosity.

Database

Variable Required Default Notes
DATABASE_URL Yes (prod) dev localhost URL mysql+pymysql://<user>:<pass>@<host>:<port>/<db>?charset=utf8mb4. Keep ?charset=utf8mb4.

Authentication rate limiting

IP-based fixed-window limit on the login endpoint, defense-in-depth atop the per-account lockout. Uses the existing cache extension (per-process, so the limit is approximate across multiple gunicorn workers).

Variable Required Default Notes
AUTH_RATELIMIT_ENABLED No True Set False to disable (TestingConfig disables it).
AUTH_RATELIMIT_MAX No 30 Max login attempts per source IP per window before 429.
AUTH_RATELIMIT_WINDOW_SECONDS No 300 Window length in seconds.

Collector ingest (ADR-006)

Variable Required Default Notes
COLLECTOR_API_KEY No (empty) Shared key for /api/collector/*. Endpoint fails closed (denies) when unset and no managed token is presented. Sent as the X-API-Key header.
COLLECTOR_API_KEY_<PLUGIN> No (empty) Per-plugin override, e.g. COLLECTOR_API_KEY_COMPUTERS. Checked before the shared key.

The collector endpoints ALSO accept a managed API token (PAT) scoped to the collector.ingest permission, sent in X-API-Key or as an Authorization: Bearer token. Env keys stay supported as a bootstrap/legacy fallback; a managed token is preferred because it is minted, rotated, and revoked from Settings > API Tokens with lastusedat visibility. A collector-scoped token is contained to the collector API and nothing else. See docs/COLLECTOR-INTEGRATION.md (Managed collector tokens).

Zabbix (printer supply monitoring)

Variable Required Default Notes
ZABBIX_ENABLED No false Enable the Zabbix integration.
ZABBIX_URL No (empty) Zabbix API URL.
ZABBIX_TOKEN No (empty) Zabbix API bearer token.

Note: Zabbix can also be configured via the Settings table (zabbix_enabled, zabbix_url, zabbix_token). The environment values are the boot-time wiring; prefer the Settings entries for runtime changes.

Employee directory database (optional, read-only)

Separate HR/employee lookup DB consumed by the notifications plugin and the public kiosks. There is no safe default for the password; an unset password fails loud rather than trying a guessed credential.

Variable Required Default Notes
EMPLOYEE_DB_HOST No localhost HR DB host.
EMPLOYEE_DB_USER No (empty) HR DB user.
EMPLOYEE_DB_PASSWORD No (empty) HR DB password. No safe default.
EMPLOYEE_DB_NAME No wjf_employees HR DB name.

Only used when employee_directory_mode (Setting) is external.

CMMC USB database (optional, read-write)

Separate MySQL DB used by the USB plugin for check-in/out, lockers, and the log.

Variable Required Default Notes
CMMC_USB_DB_HOST No localhost USB DB host.
CMMC_USB_DB_USER No (empty) USB DB user.
CMMC_USB_DB_PASSWORD No (empty) USB DB password. No safe default.
CMMC_USB_DB_NAME No cmmc_usb USB DB name.

Only used when usb_directory_mode (Setting) is external.

docker-compose only

Read by docker-compose.yml, not by the Flask app directly.

Variable Required Default Notes
MYSQL_ROOT_PASSWORD Yes (none) Root password for the bundled MySQL container.
MYSQL_PASSWORD Yes (none) App-user password; must match the DATABASE_URL password.
MYSQL_PORT No 3306 Host port for MySQL. Bound to 127.0.0.1 only.
API_PORT No 5001 Host port for the API container.

Part 2: Settings table keys

Seeded by flask seed settings (idempotent; re-running adds anything missing). Edited in the UI under Settings, or captured in the first-run setup wizard. Values are stored as strings and typed by valuetype. Secrets in this table (anything whose key contains password, token, or secret) are masked when read back through the API.

site

Key Default Notes
setup_complete false Set true once the first-run wizard finishes; gates the /setup route.
employee_directory_mode selfhosted selfhosted (tables in this app) or external (a separate HR database, see EMPLOYEE_DB_*).
usb_directory_mode selfhosted selfhosted or external (a separate cmmc_usb database, see CMMC_USB_DB_*).
site_base_url (empty) Public base URL (scheme + host) for QR codes and absolute links. Blank = use the browsing origin.
facility_name (empty) Facility name in the dashboard header. Blank = frontend falls back to ShopDB.
pc_access_domain device.geaerospace.net Domain appended to a PC hostname for remote-access links. Blank = hostname as-is.
employeeid_pattern ^\d{9}$ Regex a search term must match to be treated as an employee id. Invalid regex falls back to the default and never 500s.
printer_hostname_template Printer-{ip}.printer.geaerospace.net Printer hostname template. {ip} is the dash-separated IP address.
contact_email_domain geaerospace.com Email domain appended to a support contact's SSO to build email (sso@domain) and Teams-chat links. Blank hides the contact action buttons.
dualpath_single_machine true Treat a Dualpath pair (a dual-bay machine with one controller) as a single machine in the machines list, dashboard/report counts, and the floor map (the secondary bay is hidden). The data model always keeps both bay records; detail pages stay per-bay with a sibling banner. false lists and counts both bays separately.

branding

Blank values fall back to the shipped GE default asset so an un-reconfigured install still renders. Upload replacements at Settings > Branding, which saves them under instance/branding/.

Key Default Notes
site_logo /ge-aerospace-logo.svg Header and login-page logo.
qr_logo /ge-monogram.svg Logo composited in printer QR labels. Blank = no overlay.
badge_logo /ge-aerospace-logo.svg Logo on the machine badge print page.
site_favicon (empty) Browser tab favicon. Blank = shipped /favicon.svg.
brand_primary_color (empty) Primary brand color as a CSS color value (maps to --primary). Blank = built-in theme color.
brand_primary_dark_color (empty) Primary hover/active color (maps to --primary-dark). Blank = auto-derived by darkening the primary color ~15%.
brand_accent_color (empty) Accent color for secondary buttons and badges (maps to --secondary). Blank = built-in theme color.
brand_sidebar_color (empty) Sidebar background color (maps to --sidebar-bg). Blank = built-in theme color.

printing

Key Default Notes
qr_target_printer (empty) Custom URL template for printer QR labels. Blank = link to the printer page on this instance. Placeholders: {printerid}, {assetid}, {assetnumber}, {serialnumber}, {ip}, {hostname}.
qr_target_usb (empty) Custom URL template for USB label QR codes. Blank = link to the USB device page. Placeholders: {id}, {serialnumber}, {alias}.
usb_label_style barcode USB mini-label code style: barcode (CODE128 of the serial) or qr (QR code linking to the USB QR target).
qr_target_machine (empty) Custom URL template for machine labels. Blank = link to the machine page. Placeholders: {assetid}, {assetnumber}, {serialnumber}, {name}, {pluginid}.
qr_target_computer (empty) Custom URL template for computer labels. Blank = link to the computer page. Placeholders: {assetid}, {assetnumber}, {serialnumber}, {name}, {pluginid}.
qr_target_network_device (empty) Custom URL template for network-device labels. Blank = link to the device page. Placeholders: {assetid}, {assetnumber}, {serialnumber}, {name}, {pluginid}.
qr_target_measuring_tool (empty) Custom URL template for measuring-tool labels. Blank = link to the tool page. Placeholders: {assetid}, {assetnumber}, {serialnumber}, {name}, {pluginid}, {locationcode}, {locationname}.
label_default_style card Default asset-label layout used when a label first opens: card (badge with image and identity) or plain (just the code and a caption).
label_default_codetype qr Default asset-label code type used when a label first opens: qr (QR code) or barcode (CODE128).
label_default_encodes_machine assetnumber What a machine label encodes by default.
label_default_encodes_computer assetpage What a computer label encodes by default.
label_default_encodes_printer assetpage What a printer label encodes by default.
label_default_encodes_network_device assetpage What a network-device label encodes by default.
label_default_encodes_measuring_tool location What a measuring-tool label encodes by default. Values across these five: assetpage, assetnumber, serialnumber, location (measuring tools only), or custom. Overridable on the label page.

The shared asset-label generator lives at /print/asset-label/<assettype>/<id> (public, like the other /print/* pages; assettype is one of machine, computer, printer, network_device, measuring_tool, and id is the asset's plugin id). It can encode the asset page link, the asset number, the serial number, a custom qr_target_<type> template, or - for measuring tools by default - the asset's inspection location code (the leading token of the location name, e.g. 0615). A measuring tool with no location falls back to its asset page.

map

Key Default Notes
map_blueprint_light /static/images/floorplan-placeholder.svg Floor-map blueprint (light theme). Re-upload your own in Settings > Floor Map.
map_blueprint_dark /static/images/floorplan-placeholder.svg Floor-map blueprint (dark theme).
map_width 3300 Blueprint native width in pixels.
map_height 2550 Blueprint native height in pixels.

integrations

Key Default Notes
servicenow_enabled true Enable ServiceNow ticket recognition and links. Disabled = tickets render as plain text.
servicenow_search_url geaerospaceqa.service-now.com global-search template {ticket} is substituted.
servicenow_ticket_prefixes GEINC,GECHG,GERIT,GESCT Comma-separated prefixes recognized as ServiceNow tickets.
servicenow_incident_url geaerospaceqa.service-now.com global-search template {ticket} is substituted. Replace with a direct incident URL if your instance has one.
servicenow_change_url geaerospaceqa.service-now.com global-search template {ticket} is substituted. Replace with a direct change URL if your instance has one.
zabbix_enabled false Enable Zabbix for printer supply monitoring.
zabbix_url (empty) Zabbix API URL.
zabbix_token (empty) Zabbix API token (masked).
warranty_dell_enabled false Enable Dell warranty (service-tag) lookups.
warranty_dell_clientid (empty) Dell TechDirect API client id.
warranty_dell_clientsecret (empty) Dell TechDirect API client secret (masked).
warranty_dell_tokenurl (empty) Dell OAuth token URL. Blank = Dell default.
warranty_dell_apiurl (empty) Dell warranty API URL. Blank = Dell default.

email

Key Default Notes
smtp_enabled false Enable email notifications and alerts.
smtp_host (empty) SMTP server hostname.
smtp_port 587 SMTP port (587 TLS, 465 SSL, 25 plain).
smtp_username (empty) SMTP auth username.
smtp_password (empty) SMTP auth password (masked).
smtp_use_tls true Use TLS for the SMTP connection.
smtp_from_address (empty) From address for outgoing email.
smtp_from_name ShopDB From name for outgoing email.
alert_recipients (empty) Default alert/report recipients (comma-separated).

Email flows and delivery model

The mail service (shopdb/utils/mailer.py, stdlib smtplib/ssl/email only) reads the keys above settings-first via the cached settings map, with an environment-variable fallback (SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_USE_TLS, SMTP_FROM_ADDRESS, SMTP_FROM_NAME, SMTP_ALERT_RECIPIENTS, SMTP_ENABLED) applied only when any SMTP_* env var is present. When smtp_enabled is false or smtp_host is blank, every send is a graceful no-op that logs a warning and returns without error, so an unconfigured site never crashes. The SMTP password is never logged.

Three flows use it:

  • Welcome email. When an admin creates a user (POST /api/users), the account is flagged mustchangepassword and a best-effort welcome email is sent with the facility name (facility_name), the username, the temporary password, and the sign-in link (site_base_url + /login). Mail is best-effort: the user is created even if the send fails (the response carries a warning). On first login the API returns mustchangepassword: true; the frontend forces the user through /change-password (POST /api/auth/change-password) before the app. Changing the password clears the flag and resets lockout counters. Set sendwelcome: false or mustchangepassword: false in the create body to opt out.

  • Test email. POST /api/settings/test-email (settings.edit) sends a probe to the supplied to (or alert_recipients). The Email / SMTP settings page "Send Test Email" button calls it and shows the result; a real SMTP error is surfaced with the password scrubbed out.

  • Alerts and report delivery (on-demand). POST /api/reports/email (reports.export) takes {subject, columns, rows, intro?, to?} and mails the rows as an HTML table. Recipients default to alert_recipients when to is omitted, so the same endpoint serves both report delivery and alerts. Report pages (Warranty, Toner) carry an "Email report" button that posts the rows they already loaded.

    There is NO scheduler in this app: sending is on-demand. To automate a recurring send (e.g. a nightly warranty digest), point an external cron job at /api/reports/email using an API token (PAT) scoped to reports.export. See docs/IMPORT-API.md for the token model.

audit

Key Default Notes
audit_retention_days 90 Days to retain audit logs (0 = keep forever).

auth

Key Default Notes
saml_enabled false Enable SAML SSO.
saml_idp_metadata_url (empty) SAML IdP metadata URL.
saml_entity_id (empty) SAML SP entity id.
saml_acs_url (empty) SAML Assertion Consumer Service URL.
saml_allow_local_login true Allow local username/password login when SAML is on.
saml_auto_create_users true Auto-create users on first SAML login.
saml_admin_group (empty) SAML group name that grants the admin role.

Personal API tokens. Besides login JWTs and SAML, a user may create personal API tokens (PATs) for scripts and integrations, from Settings > API Tokens (or POST /api/apitokens). A PAT is sent like a JWT (Authorization: Bearer shopdb_pat_...), authenticates as its owning user across the whole API, and does not carry the hourly JWT_ACCESS_TOKEN_EXPIRES limit (it never expires unless an explicit expiry is set). Only the sha256 hash is stored; the secret is shown once at creation. This is the recommended credential for long-running imports (see docs/IMPORT-API.md). There is no env var to configure; PATs are managed entirely through the API/UI.

Creating or managing a PAT requires the apitokens.create permission (admins hold it by default; grant it to other roles from Settings > Users & Roles). By default a PAT is unscoped and acts with the full authority of its owner. A PAT may optionally carry a scopes list (a subset of the owner's permissions, capped at what the owner actually holds): a scoped token grants ONLY those permissions, intersected with the owner's live permissions at use time, and suspends the admin bypass, so it is denied on role-gated (admin-only) endpoints and on import mode. Use an unscoped token for admin-only work and imports.

identifiers (dynamic)

One boolean key per asset identifier per asset type, keyed identifier_<name>_<assettype>_enabled (default true). Admins choose which optional identifiers show on which asset types. See ADR-001. The exact set is generated from IDENTIFIER_LABELS x IDENTIFIER_ASSETTYPES in shopdb/core/api/settings.py.

search (dynamic)

One boolean key per search domain, keyed search_<type>_enabled (default true). Toggles whether a domain appears in global search results. The set is generated from SEARCH_DOMAINS in shopdb/core/api/settings.py.


See also

  • DEPLOY.md - per-site deployment runbook
  • UPGRADE.md - upgrading an existing site
  • BACKUP-RESTORE.md - backup and restore
  • shopdb/config.py - authoritative env-var definitions
  • shopdb/core/api/settings.py (build_default_settings) - authoritative Setting defaults