Files
userAdityaa 3ed021214a
Docker / build-and-push-image (backend, backend, coracle/caravel-backend) (push) Failing after 4m47s
Docker / build-and-push-image (frontend, frontend, coracle/caravel-frontend) (push) Successful in 2m41s
feat(infra): pass Blossom S3 config to Zooid with schema key prefix (#69)
Reviewed-on: #69
Co-authored-by: userAdityaa <aditya.chaudhary1558@gmail.com>
Co-committed-by: userAdityaa <aditya.chaudhary1558@gmail.com>
2026-05-13 15:47:08 +00:00
..
2026-04-09 14:11:30 -07:00
2026-04-09 14:11:30 -07:00
2026-04-17 13:23:26 -07:00

Backend

Rust backend for Caravel. It manages tenants, relays, invoices, and background workers for relay provisioning and billing.

Tech Stack

  • Rust (Edition 2024)
  • Axum (HTTP API)
  • SQLx + SQLite
  • Tokio (async runtime + workers)
  • Nostr SDK (NIP-98 auth, NIP-17 DMs, NIP-47 wallet connect)

Layout

backend/
  migrations/
    0001_init.sql
  src/
    api.rs       # Axum routes + NIP-98 auth checks
    billing.rs   # Invoice generation + collection worker
    infra.rs     # Zooid sync worker
    main.rs      # App bootstrap
    models.rs    # DB models
    repo.rs      # Data access layer
    robot.rs     # Nostr robot identity + DM sending

Configuration

Environment variables:

Variable Description Default
DATABASE_URL SQLite URL. Relative paths are resolved under backend/. sqlite://<backend>/data/caravel.db
HOST API bind host (also used for NIP-98 u host check) 127.0.0.1
PORT API bind port 2892
ADMINS Comma-separated admin pubkeys (hex) optional
ALLOW_ORIGINS Comma-separated CORS origins. If empty, CORS is permissive. optional
ZOOID_API_URL Zooid API base URL used by infra worker required for infra sync
ZOOID_API_SECRET Nostr secret key used for authentication of requests to the zooid API required
RELAY_DOMAIN Base domain appended to relay subdomains empty
LIVEKIT_URL LiveKit URL sent to zooid when relay livekit is enabled optional
LIVEKIT_API_KEY LiveKit API key sent to zooid optional
LIVEKIT_API_SECRET LiveKit API secret sent to zooid optional
BLOSSOM_S3_ENDPOINT S3-compatible endpoint URL for Blossom; omit for AWS S3 optional
BLOSSOM_S3_REGION S3 region; with bucket, access key, and secret enables S3 for Blossom optional
BLOSSOM_S3_BUCKET S3 bucket name optional
BLOSSOM_S3_ACCESS_KEY S3 access key ID optional
BLOSSOM_S3_SECRET_KEY S3 secret access key optional
NWC_URL Platform NWC URL used to generate BOLT11 invoices required for invoice generation
ENCRYPTION_SECRET Nostr secret key (hex or nsec) used to encrypt tenant NWC URLs at rest required
STRIPE_SECRET_KEY Stripe API secret key used for billing API operations required
STRIPE_WEBHOOK_SECRET Stripe webhook signing secret used to verify Stripe-Signature headers required
STRIPE_PRICE_BASIC Stripe price ID (price_...) backing the Basic plan required for paid plans
STRIPE_PRICE_GROWTH Stripe price ID (price_...) backing the Growth plan required for paid plans
ROBOT_SECRET Robot Nostr secret key required
ROBOT_NAME Robot display name (kind 0) optional
ROBOT_DESCRIPTION Robot description (kind 0) optional
ROBOT_PICTURE Robot picture URL (kind 0) optional
ROBOT_OUTBOX_RELAYS Comma-separated relays published as kind 10002 required
ROBOT_INDEXER_RELAYS Comma-separated relays used for recipient relay discovery required
ROBOT_MESSAGING_RELAYS Comma-separated relays published as kind 10050 required

Relay list env vars are comma-separated and trimmed. If a relay has no ws:// or wss:// scheme, wss:// is prepended.

Schema and Architecture

See spec for more details

API Routes

Most API routes are NIP-98 protected.

Public exceptions:

  • GET /plans

  • GET /plans/:id

  • POST /stripe/webhook (validated with Stripe signatures)

  • GET /identity — get auth identity (pubkey, is_admin); side-effect-free

  • GET /tenants — list tenants (admin)

  • POST /tenants — idempotently ensure a tenant row exists for the current auth pubkey (creates Stripe customer + tenant on first call, returns existing tenant otherwise)

  • GET /tenants/:pubkey — get tenant (admin or same tenant)

  • PUT /tenants/:pubkey — update tenant nwc_url (admin or same tenant)

  • GET /tenants/:pubkey/relays — list tenant relays (admin or same tenant)

  • GET /relays — list relays (admin)

  • POST /relays — create relay (admin or relay tenant)

  • GET /relays/:id — get relay (admin or relay tenant)

  • GET /relays/:id/members — list relay members from zooid (admin or relay tenant)

  • PUT /relays/:id — update relay (admin or relay tenant)

  • GET /relays/:id/activity — list relay activity (admin or relay tenant)

  • POST /relays/:id/deactivate — deactivate relay (admin or relay tenant)

  • POST /relays/:id/reactivate — reactivate relay (admin or relay tenant)

  • GET /tenants/:pubkey/invoices — list tenant invoices (admin or same tenant)

  • GET /invoices/:id — get invoice (admin or same tenant)

  • GET /invoices/:id/bolt11 — get invoice bolt11 (admin or same tenant)

  • GET /tenants/:pubkey/stripe/session — create Stripe customer portal session (admin or same tenant)

API Auth Model

Caravel intentionally uses a session-style variant of NIP-98 for client-to-backend API auth.

  • Frontend signs one kind 27235 event with u = VITE_API_URL and caches that header for about 10 minutes.
  • Backend verifies event kind, signature, and that u contains configured HOST.
  • Backend intentionally does not bind auth to exact request URL/method/query, and does not enforce payload hash, timestamp freshness window, or replay cache.
  • Goal: reduce repeated wallet signing prompts and avoid cookie-based sessions.
  • Tradeoff: this is weaker request-intent binding than strict NIP-98 semantics.