- Rust 1.90+ (stable)
- Docker (optional, for integration tests)
- PostgreSQL (optional, for Postgres backend development)
# Build all crates
cargo build --workspace
# Run tests
cargo test --workspace
# Format and lint
cargo fmt --all
cargo clippy --workspace --all-targets --all-features
# Build Docker images
docker build -f server.Dockerfile -t zopp-server .
docker build -f operator.Dockerfile -t zopp-operator .
docker build -f cli.Dockerfile -t zopp-cli .cargo run --bin zopp-server serve# Start PostgreSQL
docker run --name zopp-pg -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:16
# Run server
DATABASE_URL=postgres://postgres:postgres@localhost/postgres cargo run --bin zopp-server serve# After server is running
cargo run --bin zopp -- workspace create acme
cargo run --bin zopp -- secret set FOO barThe web UI requires additional tooling and runs alongside the server.
# Install trunk (Rust WASM bundler)
cargo install trunk
# Install wasm-pack
cargo install wasm-pack
# Add WASM target
rustup target add wasm32-unknown-unknown
# Install node dependencies (for Tailwind/DaisyUI)
cd apps/zopp-web && npm install# Terminal 1: Start server + Envoy proxy (without web service for local dev)
docker compose -f docker/docker-compose.dev.yaml up zopp-server envoy
# Terminal 2: Build WASM crypto module (one-time)
wasm-pack build --target web --out-dir apps/zopp-web/pkg crates/zopp-crypto-wasm
# Terminal 3: Start web UI locally (faster iteration)
cd apps/zopp-web
trunk serve# Terminal 1: Start zopp-server
cargo run --bin zopp-server serve
# Terminal 2: Start Envoy (needed for gRPC-web translation)
# Note: Uses envoy-grpc-web-local.yaml which connects to host.docker.internal
docker run -v $(pwd)/docker/envoy-grpc-web-local.yaml:/etc/envoy/envoy.yaml \
--add-host=host.docker.internal:host-gateway \
-p 8080:8080 envoyproxy/envoy:v1.28-latest
# Terminal 3: Build WASM crypto (one-time, rebuild after changes to zopp-crypto)
wasm-pack build --target web --out-dir apps/zopp-web/pkg crates/zopp-crypto-wasm
# Terminal 4: Start web UI with hot reload
cd apps/zopp-web
trunk serveThe web UI will be available at http://localhost:3000
To test the UI, you first need to bootstrap a user:
# Step 1: Create a bootstrap invite from the SERVER (not CLI)
# If using Docker Compose:
docker exec docker-zopp-server-1 zopp-server invite create --db /data/zopp.db --expires-hours 48
# Or if running server locally:
cargo run --bin zopp-server invite create --expires-hours 48
# Step 2: Use the invite token to join
# Option A: Via Web UI - go to http://localhost:3000/invite and paste the inv_xxx token
# Option B: Via CLI - cargo run --bin zopp -- join <inv_xxxxx> your@email.com
# Step 3: Once joined, you can create workspaces
cargo run --bin zopp -- workspace create my-workspace
# Step 4: Create workspace invites for others
cargo run --bin zopp -- invite create -w my-workspace
# Share the inv_xxxx token - others can use it at http://localhost:3000/inviteThe first invite must come from the server itself (bootstrap invite). After that, users with appropriate permissions can create workspace invites via the CLI or web UI.
Each storage backend lives in its own crate and implements the Store trait from zopp-storage. See individual crate READMEs for backend-specific details:
crates/zopp-store-sqlite/- SQLite implementation
Create a new crate that implements the Store trait. The implementation can use any approach - SQL database, key-value store, file system, etc.
use zopp_storage::Store;
pub struct MyStore { /* ... */ }
#[async_trait::async_trait]
impl Store for MyStore {
// Implement required methods
}