Findings F2, F4, F5, F6, F7, F8, F9, F10, F11 and F13 from wj-architectural-review.md. F1/F1a (the non-sargable LOWER pattern) and F3 (the warranty sync holding a pooled connection) are being handled separately and are untouched here. - A collector check-in that raced another for the same PC lost everything: the unique index on the application fingerprint raised, nothing caught it, and the whole check-in rolled back, discarding state, events and hardware, answering the agent with form copy meant for a person. The insert now runs in a savepoint, reconciles what the other transaction already wrote, one row per savepoint so a second collision skips a row rather than the batch, and the check-in commits with a warning. This is the shape the delete side of the same function already had. - Enrollment is deliberately unauthenticated, so it now carries a per-address rate limit and a cap on pending requests, and the cap is a setting an admin can see and change. A flood can no longer bury the approval queue or grow the table without bound. - The fleet alert poll no longer formats debug strings on every request. - BusinessUnit is on the contract surface (0.34.0), so the endpoints plugin stops reaching into a core table with raw SQL. - A test walks every asset type descriptor and proves the endpoint it names really refuses a caller without the declared permission. - The plugin settings routes now need a permission; they were telling anyone which modules a site runs and where its components live. - The connection pool is sized explicitly with a short timeout, and the arithmetic against the server's connection ceiling is written next to the worker count instead of being left to defaults nobody chose. - Fleet alerts carry both the correct lowercase keys and the old camelCase pair. Trays older than 1.7.0 match keys case-sensitively and would have gone quiet on every PC not yet upgraded; the old pair goes when they are. - The dev server proxy points at localhost again, with environment variables for the remote-box workflow, and one developer's address is out of the server certificate page.
11 KiB
ShopDB Local Development Setup (Windows)
Complete guide for setting up ShopDB Flask development environment on Windows.
Prerequisites
- Python 3.13+
- Docker Desktop (for MySQL and testing)
- Git
- Node.js 20+ (for frontend development)
Quick Start
# 1. Clone and enter repo
cd C:/Users/YOUR_USERNAME/Documents/github/flask-shopdb
# 2. Create Python virtual environment
python -m venv venv
./venv/Scripts/activate
pip install -r requirements.txt
# 3. Start MySQL in Docker
docker compose up -d db
# 4. Configure environment
cp .env.docker .env
# Edit .env and set DATABASE_URL (see below)
# 5. Install and enable plugins
export DATABASE_URL="mysql+pymysql://root:root-dev-password-not-for-production@localhost:3307/shopdb_flask_dev"
./venv/Scripts/flask.exe plugin install backups computers employees endpoints geenforce guides knowledgebase machines measuringtools network notifications printedparts printers slides tools usb warranty
./venv/Scripts/flask.exe plugin enable <each-plugin-name>
# 6. Start Flask
./venv/Scripts/python.exe run_dev.py
Visit http://localhost:5001 and login with admin / admin.
Detailed Setup
1. Database Setup
MySQL runs in Docker on port 3307 (not 3306) to avoid conflicts with any local MySQL instance.
# Start MySQL container
docker compose up -d db
# Verify it's running
docker ps --filter name=shopdb-dev-db
# Should show: 127.0.0.1:3307->3306/tcp
# Test connection
docker exec shopdb-dev-db mysql -u root -p'root-dev-password-not-for-production' -e "SELECT 1;"
2. Environment Configuration
Create .env file with:
# Database (port 3307 because Docker maps it there)
DATABASE_URL=mysql+pymysql://root:root-dev-password-not-for-production@localhost:3307/shopdb_flask_dev
# Flask
FLASK_ENV=development
FLASK_DEBUG=1
# Secrets (dev only)
SECRET_KEY=docker-dev-secret-key-change-for-production
JWT_SECRET_KEY=docker-dev-jwt-secret-change-for-production
# CORS
CORS_ORIGINS=http://localhost:5173,http://localhost:5001
# API Port
API_PORT=5001
# ShopDB Label
SHOPDB_ENV_LABEL=LOCAL-DEV
# Log Level
LOG_LEVEL=DEBUG
# Backup Storage (for local dev)
SHOPDB_BACKUPS_ROOT=C:/Users/YOUR_USERNAME/Documents/github/flask-shopdb/backups
Important: Change YOUR_USERNAME to your actual Windows username.
3. Import Database (Optional)
If you have a production database dump:
# Import latest.sql
docker exec -i shopdb-dev-db mysql -u root -p'root-dev-password-not-for-production' shopdb_flask_dev < latest.sql
# Run migrations
export DATABASE_URL="mysql+pymysql://root:root-dev-password-not-for-production@localhost:3307/shopdb_flask_dev"
./venv/Scripts/flask.exe db upgrade
4. Plugin Installation
All 17 plugins must be installed before they can be enabled:
# Set DATABASE_URL for CLI commands
export DATABASE_URL="mysql+pymysql://root:root-dev-password-not-for-production@localhost:3307/shopdb_flask_dev"
# Install all plugins
for plugin in backups computers employees endpoints geenforce guides knowledgebase machines measuringtools network notifications printedparts printers slides tools usb warranty; do
./venv/Scripts/flask.exe plugin install $plugin
done
# Enable all plugins
for plugin in backups computers employees endpoints geenforce guides knowledgebase machines measuringtools network notifications printedparts printers slides tools usb warranty; do
./venv/Scripts/flask.exe plugin enable $plugin
done
Why this is necessary:
- Plugin state is tracked in
instance/plugins.json, not the database - When you import a prod database, it has plugin tables but no plugin registry
- Installing registers plugins in the JSON file
- Enabling makes them active (but requires app restart)
5. Reset Admin Password
If you imported a prod database and don't know the password:
# Generate new password hash
./venv/Scripts/python.exe -c "from werkzeug.security import generate_password_hash; print(generate_password_hash('admin'))"
# Reset via MySQL
docker exec shopdb-dev-db mysql -u root -p'root-dev-password-not-for-production' shopdb_flask_dev -e "
UPDATE users
SET passwordhash = 'PASTE_HASH_HERE',
failedlogins = 0,
lockeduntil = NULL
WHERE username = 'admin';
"
Or use the simpler method:
docker exec shopdb-dev-db mysql -u root -p'root-dev-password-not-for-production' shopdb_flask_dev -e "
UPDATE users
SET passwordhash = 'scrypt:32768:8:1\$B4LhpPrM6qxijInK\$b99b47a0bb5fd45761a1823f401f70a3ceccf71a5d38dc433378aa07426ef8eaf7e679ff3dd7b0ce9772600f83368ac642e9419b9beaa49f87483318bae34547',
failedlogins = 0,
lockeduntil = NULL
WHERE username = 'admin';
"
Password is now: admin / admin
6. Starting Flask
Always use run_dev.py (not flask run directly):
./venv/Scripts/python.exe run_dev.py
This script:
- Loads
.envfile with DATABASE_URL - Sets all environment variables
- Starts Flask without auto-reloader (prevents env var loss)
- Binds to 0.0.0.0:5001
Do NOT use:
flask run- Won't load.envproperlyflask run --reload- Loses environment variables
7. Frontend Development (Optional)
To develop the Vue frontend:
cd frontend
npm ci
npm run dev
Frontend dev server runs on http://localhost:5173 and proxies API requests to Flask on port 5001.
To point the proxy at a backend on another machine, set VITE_DEV_API_TARGET
in your shell instead of editing vite.config.js:
VITE_DEV_API_TARGET=https://10.0.0.5:5001 npm run dev
Certificate verification is off for an https target because those boxes
normally use a self-signed cert; set VITE_DEV_SECURE=true to enforce it.
For production build (served by Flask):
cd frontend
npm run build
# Outputs to frontend/dist/
Troubleshooting
"Can't connect to MySQL on 127.0.0.1:3306"
Cause: Flask is trying port 3306 instead of 3307.
Fix:
- Make sure
.envhasDATABASE_URL=...@localhost:3307/... - Use
run_dev.pyto start Flask (NOTflask run) - If problem persists, restart Flask completely
"Cannot import name '_backups_configured'"
Cause: Merge conflict between wj and wilm backup implementations.
Fix: This was fixed in commit 8a1fe0ada8 - pull latest wj branch.
"Plugin X is not installed"
Cause: Plugin registry (instance/plugins.json) doesn't exist or is incomplete.
Fix: Run plugin installation commands (section 4 above).
"Access is denied: '/backups'"
Cause: Backup system trying to use Linux path on Windows.
Fix:
- Add to
.env:SHOPDB_BACKUPS_ROOT=C:/Users/YOUR_USERNAME/Documents/github/flask-shopdb/backups - Create directory:
mkdir backups - Restart Flask with
run_dev.py
Docker npm install fails (Zscaler proxy)
Cause: Corporate proxy intercepts HTTPS, npm doesn't trust certificate.
Fix: Extract Zscaler root CA certificate:
# Run this PowerShell script
$request = [System.Net.HttpWebRequest]::Create('https://registry.npmjs.org/')
$request.Method = 'HEAD'
$request.Timeout = 10000
$response = $request.GetResponse()
$root = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($request.ServicePoint.Certificate)
$builder = [System.Text.StringBuilder]::new()
[void]$builder.AppendLine('-----BEGIN CERTIFICATE-----')
[void]$builder.AppendLine([System.Convert]::ToBase64String($root.RawData, 'InsertLineBreaks'))
[void]$builder.AppendLine('-----END CERTIFICATE-----')
Set-Content -Path 'C:\Users\270015376\Documents\github\flask-shopdb\corp-root-ca.pem' -Value $builder.ToString() -Encoding ASCII
Then rebuild Docker: docker compose build --no-cache
Common Tasks
Run Tests
pytest
pytest tests/test_specific.py
pytest -v -s # Verbose with print output
Run Migrations
export DATABASE_URL="mysql+pymysql://root:root-dev-password-not-for-production@localhost:3307/shopdb_flask_dev"
./venv/Scripts/flask.exe db upgrade
Create Migration
./venv/Scripts/flask.exe db migrate -m "Description of changes"
Check Plugin Status
./venv/Scripts/flask.exe plugin list
Enable/Disable Plugin
./venv/Scripts/flask.exe plugin enable slides
./venv/Scripts/flask.exe plugin disable slides
Trigger Backup
# Via API (requires admin JWT token)
curl -X POST http://localhost:5001/api/shopdbbackups \
-H "Authorization: Bearer YOUR_TOKEN"
# Or via Docker
docker compose -f docker-compose.backup.yml up backup
Architecture Notes
Why Port 3307?
Docker MySQL maps internal port 3306 to host port 3307 to avoid conflicts with any local MySQL installation. This is configured in docker-compose.yml:
ports:
- "127.0.0.1:3307:3306"
Why run_dev.py?
Flask's flask run command uses an auto-reloader that spawns a subprocess. This subprocess loses environment variables loaded from .env. The run_dev.py wrapper:
- Loads
.envvia python-dotenv BEFORE importing Flask - Uses
--no-reloadflag to prevent subprocess spawning - Ensures DATABASE_URL and other vars persist
Plugin Architecture
- Plugins are Python packages in
plugins/directory - Plugin state tracked in
instance/plugins.json(not database) - Plugin tables tracked via Alembic migration chains
- Each plugin has own migrations in
plugins/<name>/migrations/
Backup System
- Designed for Docker compose deployment
- API queues backups via sentinel file (
.backup-requested) - Actual backup execution requires Docker sidecar container
- On Windows dev, API can queue but won't execute without Docker
File Structure
flask-shopdb/
|-- .env # Local environment config (gitignored)
|-- run_dev.py # Local development launcher
|-- shopdb/ # Flask application
| |-- __init__.py # App factory
| |-- config.py # Configuration classes
| |-- core/ # Core models and APIs
| +-- plugins/ # Plugin manager
|-- plugins/ # Plugin packages
| |-- backups/
| |-- computers/
| +-- ...
|-- frontend/ # Vue 3 SPA
| |-- src/
| +-- dist/ # Production build (gitignored)
|-- instance/ # Runtime state (gitignored)
| |-- plugins.json # Plugin registry
| +-- ...
|-- migrations/ # Core Alembic migrations
|-- scripts/ # Deployment and backup scripts
|-- backups/ # Local backup storage (gitignored)
+-- tests/ # Test suite
Git Workflow
mainbranch - Published/public version (GitHub)wjbranch - Production deployment (West Jefferson site)wilmbranch - Development branch (upstream from benjamin.baker)devbranch - Ops deployment branch
Never commit to main directly - it's published to public wiki.
Additional Resources
- Backup Setup
- Plugin Development
- ADRs
- API Docs (when Flask is running)