Files
shopdb-flask/docs/deployment/LOCAL-DEV-SETUP.md
cproudlock 066a19052b
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / gates (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / lean-build (push) Has been cancelled
Act on the wj architectural review: races, limits, pool and contract gaps
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.
2026-09-16 08:57:04 -04:00

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 .env file 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 .env properly
  • flask 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:

  1. Make sure .env has DATABASE_URL=...@localhost:3307/...
  2. Use run_dev.py to start Flask (NOT flask run)
  3. 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:

  1. Add to .env: SHOPDB_BACKUPS_ROOT=C:/Users/YOUR_USERNAME/Documents/github/flask-shopdb/backups
  2. Create directory: mkdir backups
  3. 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:

  1. Loads .env via python-dotenv BEFORE importing Flask
  2. Uses --no-reload flag to prevent subprocess spawning
  3. 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

  • main branch - Published/public version (GitHub)
  • wj branch - Production deployment (West Jefferson site)
  • wilm branch - Development branch (upstream from benjamin.baker)
  • dev branch - Ops deployment branch

Never commit to main directly - it's published to public wiki.

Additional Resources