← Portal
Publishing & Infrastructure

Multi-Tenant Portal Pattern

Updated 2026-05-19

Group: Publishing & Infrastructure · Demonstrated: 2026-05-18

Each client portal (vg, bc, rwp) is a tenant in one shared codebase. A new tenant is a config block + two logo files + a content tree.

The architecture

02_active/<tenant>/published/   ← Drive canonical source
            ↓ (sync_portal.py + watchdog + debounce)
~/Sites/<tenant>-portal/         ← git deploy target
            ↓ (git push origin main)
GitHub randallosp/<tenant>-portal
            ↓ (Netlify deploy hook)
<tenant>.opsstrategypro.com OR <tenant>-portal.netlify.app

Shared infrastructure

Component What it does
portal_index.py Generates index.html per tenant — sidebar + topbar + shell
render_section_hub.py Renders curated section hubs (Skills, How-We-Work, Workflow, etc.)
sync_portal.py Drive → portal repo → GitHub → Netlify, with watchdog FSEvents
templates/portal-shell.html Shared shell template (password gate, sidebar, iframe viewer)
templates/section-hub.html Shared card-hub template
templates/bc-page.html Per-doc page wrapper (used by State, Digest, detail pages)

What's tenant-specific

Per-tenant artifact Where
Tenant config block _tenant_configs() in portal_index.py
Password hash (SHA-256) tenant_password_hash in config block
Logo files templates/_<tenant>-portal-button.b64.txt + _<tenant>-portal-home.b64.txt
Sidebar group label SIDEBAR_GROUP_LABEL in portal_index.py
Section ordering SIDEBAR_ORDER_OVERRIDE in portal_index.py
Section filters SECTION_FILTERS in portal_index.py
Content tree 02_active/<tenant>/published/<section>/*.html

Adding a new tenant — checklist

  1. Pick a tenant slug (e.g., acme).
  2. Pick an access code; SHA-256 hash it; never store cleartext.
  3. Create 02_active/acme/ (engagement folder convention).
  4. Add tenant block to _tenant_configs() in portal_index.py.
  5. Set up ~/Sites/acme-portal/ git repo, push to GitHub.
  6. Create Netlify project, point at the GitHub repo.
  7. (Optional) Configure custom subdomain DNS.
  8. If using custom subdomain: provision the SSL cert in the Netlify dashboard. Domain management → "Verify DNS configuration" → "Provision certificate." Adding a CNAME does NOT auto-provision the cert. Until the cert is issued, the password gate fails silently because crypto.subtle.digest is unavailable in insecure contexts (browser blocks the secure-context API when the cert is missing or the user clicked past an SSL warning). Takes 1–15 minutes.
  9. Drop content into 02_active/acme/published/.
  10. Restart sync daemon.

Where to look