Files
JQC_multi_tenant/control
2026-06-26 17:31:45 -04:00
..
2026-06-26 16:19:48 -04:00
2026-06-26 16:19:48 -04:00
2026-06-26 16:19:48 -04:00
2026-06-26 16:19:48 -04:00
2026-06-26 16:19:48 -04:00
2026-06-26 16:19:48 -04:00
2026-06-26 17:31:45 -04:00
2026-06-26 16:19:48 -04:00
2026-06-26 16:19:48 -04:00
2026-06-26 16:19:48 -04:00

Control Plane (MT-0)

Tenant registry, plans, domains, provisioning state, and superadmin accounts for multi-tenant JQC. Self-contained and decoupled from app/ — the existing single-tenant application is unaffected by this package.

See ../MULTI_TENANT_PLAN.md for the full architecture and roadmap.

Layout

control/
├── __init__.py        # package docs
├── base.py            # ControlBase + engine/session (from CONTROL_DATABASE_URL)
├── crypto.py          # Fernet encrypt/decrypt for tenant DB passwords
├── time_utils.py      # now_eastern() mirror (no app import)
├── models.py          # Plan, PlanFeature, Tenant, TenantDomain,
│                       #   Superadmin, ProvisioningJob, TenantAudit
├── seed.py            # idempotent baseline-plan seeder
├── cli.py             # seed / create-superadmin / list-plans
└── migrations/        # standalone Alembic chain (control{N}_…)
    └── versions/control0001_init.py   ← HEAD

Environment variables (control plane only)

Variable Purpose
CONTROL_DATABASE_URL e.g. mysql+pymysql://jqc_control:pw@localhost/jqc_control
CONTROL_FERNET_KEY Fernet key for encrypting tenant DB passwords

Generate a Fernet key:

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Deploy — MT-0 bootstrap (run once)

Migrations are independent of the tenant chain and the data plane. The existing app does not need to be touched or restarted for MT-0.

# 1. Create the control database + its MySQL user (run as a MySQL admin)
mysql -e "CREATE DATABASE jqc_control CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -e "CREATE USER 'jqc_control'@'localhost' IDENTIFIED BY '<pw>';"
mysql -e "GRANT ALL PRIVILEGES ON jqc_control.* TO 'jqc_control'@'localhost'; FLUSH PRIVILEGES;"

# 2. Export the env vars (add to .env or the systemd unit for the control panel later)
export CONTROL_DATABASE_URL='mysql+pymysql://jqc_control:<pw>@localhost/jqc_control'
export CONTROL_FERNET_KEY='<generated key>'

# 3. Apply the control schema
alembic -c control/migrations/alembic.ini upgrade head

# 4. Seed baseline plans (Free / Starter / Pro / Enterprise)
python -m control.cli seed

# 5. Create the first superadmin
python -m control.cli create-superadmin --username admin --email you@example.com

Verify:

alembic -c control/migrations/alembic.ini current   # → control0001_init (head)
python -m control.cli list-plans

Rollback

alembic -c control/migrations/alembic.ini downgrade base   # drops all control tables

Notes

  • The migration uses INFORMATION_SCHEMA existence checks (Rule 14) — safe to re-run.
  • Plan seeding is idempotent (upsert by code) — re-running updates in place.
  • Tenant DB passwords are stored Fernet-encrypted in tenants.db_password_enc; Tenant.db_uri decrypts on demand. Provisioning that creates per-tenant MySQL users/grants lands in MT-3.
  • Control-panel write auditing (tenant_audit) is wired in MT-4; the bootstrap CLI logs to stdout only.