Skip to content

Running locally

All three layers use uv for Python dependency management. Install it once, then pick a layer.

The client

Single-file Python with PEP 723 inline dependencies — no virtualenv, no project setup.

# Initialise a local store (choose a passphrase when prompted)
uv run client/blindproof.py init ~/.blindproof

# Watch a folder; Ctrl-C when done
uv run client/blindproof.py watch ~/Documents/MyNovel

# Enrol with the backend (or sign in to an existing account)
uv run client/blindproof.py enrol

# Push unsynced snapshots
uv run client/blindproof.py sync

Store location defaults to ~/.blindproof; override with BLINDPROOF_STORE_DIR.

For smoke tests, set BLINDPROOF_PASSPHRASE_INSECURE=<passphrase> to skip the prompt. Do not use this outside development.

The backend

cd backend
uv sync
uv run manage.py migrate
uv run manage.py createsuperuser   # optional, for /admin
uv run manage.py runserver

Defaults to SQLite at db.sqlite3 and a local-filesystem blob store at ./blobs/, which is also how production runs (behind BlobStorage Protocol so Postgres and S3/B2 can slot in later).

Environment overrides — all have sensible dev defaults:

Variable Purpose
DJANGO_SECRET_KEY Session signing. Defaults to a dev key.
DJANGO_DEBUG 1 for dev, unset in production.
DJANGO_ALLOWED_HOSTS Comma-separated. Defaults to localhost,127.0.0.1.
DJANGO_DB_PATH Absolute SQLite path.
BLIND_BLOB_ROOT Blob-store root directory.
BLIND_OTS_MODE fake (default) or real. Never set to real in dev without meaning it — it will hit the public calendars.
BLINDPROOF_SIGNING_KEY Ed25519 secret key hex. Dev generates one if unset.

The desktop GUI

cd desktop
uv sync
uv run pyinstaller blindproof.spec
codesign --deep --force --sign "<your Apple Development cert ID>" dist/BlindProof.app
open dist/BlindProof.app

Framework Python is required. uv's prebuilt CPython is standalone and NSStatusItem / related AppKit menu-bar APIs silently no-op against it. Install Homebrew Python first:

brew install python@3.12

The uv sync in desktop/ picks up Homebrew's framework Python automatically if it's on the path.

To run the GUI without packaging (useful during development):

cd desktop
uv sync
uv run python -m blindproof_gui

The window opens; the watcher runs as soon as onboarding is complete; sync can be triggered via the Sync now button.

End-to-end smoke test

A quick check that the full pipeline still works:

# Terminal 1 — backend
cd backend && uv run manage.py runserver

# Terminal 2 — client, pointed at a scratch folder
export BLINDPROOF_STORE_DIR=/tmp/blindproof-smoke
export BLINDPROOF_PASSPHRASE_INSECURE=test-passphrase
uv run client/blindproof.py init "$BLINDPROOF_STORE_DIR"
uv run client/blindproof.py enrol   # expects backend at http://127.0.0.1:8000
mkdir -p /tmp/blindproof-manuscript
uv run client/blindproof.py watch /tmp/blindproof-manuscript &

# Terminal 3 — modify files
echo "# Chapter one" > /tmp/blindproof-manuscript/ch1.md
echo "# Chapter two" > /tmp/blindproof-manuscript/ch2.md

# Back in terminal 2
uv run client/blindproof.py sync

Confirm:

  1. list_unsynced on the store is empty after sync.
  2. The backend's /admin/api/snapshot/ page shows two rows for the test user.
  3. Running uv run manage.py aggregate_day --today on the backend produces a MerkleRoot row linking both snapshots.

For the full capture → encrypt → sync → aggregate → anchor → bundle chain end-to-end, see test_steps.md in the repo.

Troubleshooting

uv run client/blindproof.py init hangs on passphrase prompt. uv is waiting for stdin. Either type a passphrase, or set BLINDPROOF_PASSPHRASE_INSECURE and re-run.

/api/snapshots returns 401. The token expired or was never saved. Re-run enrol or login.

aggregate_day does nothing. Snapshots haven't been uploaded yet, or you're running it for a day that has no captures. Use --today for testing against just-uploaded snapshots.

submit_ots_receipts errors on RemoteCalendar. BLIND_OTS_MODE=real and you're offline, or a calendar is down. Set BLIND_OTS_MODE=fake for local work.