Developer guide
How ParallelPath is built, how to run it locally, and how to contribute. The complete source running on this server is browsable at /source.
Design goals
- Trustworthy records - immutable messages, versioned everything, hash-chained audit, verifiable reports.
- Private by default - envelope encryption of all family content, no third-party requests, admins can't read content.
- Light - one Python process, PostgreSQL, nginx. Runs on a 1 GB VPS. No JavaScript build step, no frameworks in the browser.
- Easy to self-host - one interactive installer; plays nicely next to existing sites.
- Accessible - WCAG 2.2 AA target.
Stack
| Layer | Technology |
|---|---|
| Web framework | FastAPI on Uvicorn, server-rendered Jinja2 templates |
| Database | PostgreSQL 16+ via SQLAlchemy 2 + psycopg 3 (SQLite for development/tests) |
| Crypto | cryptography (AES-256-GCM, HKDF), argon2-cffi, pyotp |
| Realtime | WebSockets (in-process hub) for chat, notifications and WebRTC signalling |
| Calls | Browser WebRTC + coturn TURN relay |
| Reports | ReportLab (PDF), csv, json |
| Front end | Hand-written CSS (static/css/app.css) and vanilla JS (static/js/*.js), no dependencies |
| Ops | systemd (sandbox, encrypted credentials, timers), nginx, AppArmor, fail2ban, ufw, LUKS2 |
Repository layout
install.sh interactive installer (install / upgrade / tls / uninstall)
deploy/ systemd units, nginx/coturn templates, AppArmor, fail2ban, ppctl, root helper
pp-sysd.py root helper: metrics, fail2ban bridge, encrypted backups/restore
app/
requirements.txt
parallelpath/
main.py app + security middleware + error handling
config.py settings from env / systemd credentials
db.py, models.py SQLAlchemy models (columns ending _enc are ciphertext)
schema.py table creation + PostgreSQL triggers and least-privilege grants
crypto.py envelope encryption, chunked file encryption, key rotation
audit.py hash-chained audit trail + entity revisions
security.py passwords, TOTP, sessions, rate limiting
web.py request context (Ctx), permissions, templates, helpers
tone.py Tone Check: local heuristics + Ollama/OpenAI-compatible/Claude providers
realtime.py WebSocket hub
storage.py encrypted blob store
sitecfg.py admin-editable settings
mailer.py optional SMTP
cli.py ppctl commands
routes/ one module per area (auth, family, messages, calendar, files, expenses,
records, calls, reports, home, admin)
templates/ Jinja2 templates (admin/ is the separate admin portal)
static/ css, js, icons, wiki screenshots, service worker
wiki/ these pages (Markdown)
tests/ pytest end-to-end, security and unit tests
tools/screenshots.py seeds a demo instance and captures the wiki screenshots
Running locally
Requires Python 3.12+.
python -m venv .venv
. .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r app/requirements.txt pytest pytest-xdist
cd app
PP_DEV=1 uvicorn parallelpath.main:app --reload --port 8000
PP_DEV=1 uses SQLite in ./devdata, auto-generates a development master key, and allows plain-HTTP cookies. Create accounts with:
PP_DEV=1 python -m parallelpath.cli create-admin
Then sign in at http://localhost:8000/admin/login and invite a person.
Tests
cd app
python -m pytest tests -n auto --dist loadfile
The suite covers the whole user journey (two parents and a professional), access control between families, portal separation, CSRF blocking, security headers, brute-force limits, user-enumeration resistance, audit tamper detection, the source secret-scanner, DST-safe repeating events and schedule parsing.
Screenshots
python tools/screenshots.py
Seeds fictional demo data into a temporary instance and uses a local headless Edge/Chrome over the DevTools protocol to regenerate static/wiki/*.png and the app icons.
Key concepts
Request context and permissions
Every family route depends on app_ctx, which loads the signed-in person, their active family membership, the family's cipher, members and children. ctx.can("expenses.view") / ctx.require(...) implement roles: parents can do everything; others get explicit scopes (messages, calendar.view, files.view, ...). Admin routes use admin_ctx and a different cookie.
Encrypting a field
obj.title_enc = ctx.cipher.enc(title, f"events.title:{obj.id}") # AAD = table.column:row-id
title = ctx.cipher.dec(obj.title_enc, f"events.title:{obj.id}")
Always set id=uid() explicitly before encrypting so the AAD can include it.
Changing anything
Every update path: require a reason (need_reason), bump version, then call audit.revise(...) with a full snapshot. This writes an encrypted revision and an audit event. Never DELETE - set deleted_at. The PostgreSQL triggers in schema.py will reject updates/deletes on evidence tables, so tests on SQLite alone aren't enough for schema work - run against PostgreSQL too.
Audit trail
audit.record(db, action, actor=..., family_id=..., target_type=..., target_id=..., reason=..., details=...) inside the request's transaction. Reasons/details are encrypted (family or system key) and the row hash covers their ciphertext.
Tone Check providers
Add a provider by writing _name(cfg, text, timeout) -> dict in tone.py and registering it in PROVIDERS. It must return score, labels, flagged, suggestion. Failures fall through to the next provider and finally to local_check.
Front end
No build step. app.js handles preferences, dialogs, the WebSocket, Tone Check, geolocation and PWA install; files.js the explorer; call.js WebRTC; thread.js, forms.js, admin.js small page helpers. Inline scripts and on*= attributes are blocked by the CSP - use data-* hooks.
Contributing
- Keep it light: no new heavy dependencies; no external requests from the browser.
- Every feature must keep the audit/immutability guarantees.
- Add tests (
app/tests) and run the full suite. - Keep accessibility: labels, keyboard support, no colour-only meaning.
- Licence: contributions are accepted under AGPL-3.0.
Licence
GNU Affero General Public License v3.0 - if you run a modified version as a public service, you must offer your users its source. ParallelPath does this automatically via /source.
Built by DarkCipher0x at DarkCipher Systems.