Expand description
§solid-pod-rs-server
Drop-in Solid Pod server binary wrapping
solid-pod-rs with
actix-web. This crate is both a
library (for integration-test reuse) and a binary.
§Public types
AppState— Shared actix-web application state (storage, dotfile policy, body cap).build_app— Builds the fully-configuredactix_web::Appwith all routes and middleware.NodeInfoMeta— NodeInfo 2.1 metadata inputs.PathTraversalGuard— Middleware that rejects..path-traversal attempts.DotfileGuard— Middleware that enforces the dotfile allowlist.ErrorLoggingMiddleware— Middleware that logs 5xx responses with full error chains.body_cap_from_env— ReadsJSS_MAX_REQUEST_BODYfrom the environment.cli— CLI argument definitions (clap derive).
§Route table
| Method | Path | Handler |
|---|---|---|
| GET/HEAD | /{tail:.*} | handle_get |
| GET | /{folder}/* | Glob merged Turtle |
| PUT | /{tail:.*} | handle_put |
| PUT | /{tail:.*}/ + Link: BasicContainer | Container creation |
| POST | /{tail:.*}/ | handle_post |
| PATCH | /{tail:.*} | handle_patch |
| DELETE | /{tail:.*} | handle_delete |
| COPY | /{tail:.*} + Source header | handle_copy |
| OPTIONS | /{tail:.*} | handle_options |
| POST | /api/accounts/new | Pod provisioning |
| GET | /pods/check/{name} | Pod existence check |
| POST | /login/password | Credentials login |
| POST | /account/password/reset | Password reset |
| POST | /account/password/change | Password change |
| GET | /.well-known/solid | Solid discovery |
| GET | /.well-known/webfinger | WebFinger JRD |
| GET | /.well-known/nodeinfo | NodeInfo discovery |
| GET | /.well-known/nodeinfo/2.1 | NodeInfo 2.1 |
| GET | /.well-known/did/nostr/{pubkey}.json | DID:nostr document |
| GET | /pay/.info | Payment discovery |
| GET | /pay/.balance | Web-Ledger balance |
| POST | /pay/.deposit | TXO + MRC20 deposit |
| GET | /pay/.address | Tweaked deposit addr |
| GET/POST | /pay/.offers .sell .swap .pool | Order book + AMM |
| POST | /pay/.buy .withdraw .withdraw-sats | Token mint/voucher |
| GET | /{pod}/{path}.prov.ttl | PROV-O git-mark sidecar |
| GET | /{pod}/_prov/{commit_sha} | Resolve a git-mark |
| POST | /{pod}/_prov/anchor | Upgrade to Bitcoin anchor |
| GET/POST | /{pod}/info/refs …/git-{upload,receive}-pack | Git smart-HTTP (WAC-gated) |
The /pay/* HTTP-402 economy routes (handlers::pay) wire the
solid-pod-rs Web-Ledger / order-book / AMM core onto actix; the _prov
routes (handlers::prov, --features git) expose the git-mark +
block-trail provenance API (ADR-059). Block-trail anchor verification and
broadcast go through the native mempool client (mempool.space testnet4
by default), with trail persistence via trail_store. Every LDP
PUT/POST/PATCH to a git-backed pod additionally fires the always-on
git-mark write hook (git_mark_write, when built with --features git).
§Middleware stack (applied in order)
NormalizePath– collapse//and decode %-encoded segments.PathTraversalGuard– defence-in-depth..re-check.DotfileGuard– rejects.envetc unless on the allowlist.PayloadConfig– enforcesJSS_MAX_REQUEST_BODYbody cap.ErrorLoggingMiddleware– structured 5xx logging.- WAC-on-write – PUT/POST/PATCH/DELETE require a write/append grant.
§solid-pod-rs-server
Binary distribution of solid-pod-rs — a drop-in
JSS replacement that runs as a single static-ish Rust binary.
§Install
Once published to crates.io (target: v0.4.0):
cargo install solid-pod-rs-server
solid-pod-rs-server --config config.jsonUntil then, build from source:
cargo build --release -p solid-pod-rs-server
./target/release/solid-pod-rs-server --help§Architecture
This crate is a thin binary shell over solid-pod-rs.
Per ADR-056 §D3 (F7 library-server split):
solid-pod-rs— pure library. No#[tokio::main], noactix-web::HttpServer. Framework-agnostic.solid-pod-rs-server(this crate) — owns the actix-web HTTP server, the tokio runtime, clap CLI, the F6 layered config loader, and signal handling. Depends on the library and wires itsPodService-style primitives into concrete HTTP routes.
§Configuration
Configuration is loaded by solid_pod_rs::config::ConfigLoader
(F6, PRD §F6). Precedence (later overrides earlier):
Defaults < File < EnvVars < CLI flagsSee crates/solid-pod-rs/src/config/sources.rs
for the full JSS_* environment variable table.
§Mashlib / SolidOS data browser
Enable the mashlib data browser to render RDF resources in the browser:
# CDN mode (zero config — loads from unpkg.com)
solid-pod-rs-server --mashlib
# CDN with a specific version
solid-pod-rs-server --mashlib --mashlib-cdn 2.1.0
# ES module mode (LOSOS shell)
solid-pod-rs-server --mashlib-module https://host/path/to/mashlib.jsWhen enabled, browser navigation (Accept: text/html) to RDF
resources returns an HTML wrapper that loads mashlib client-side.
The resource’s JSON-LD is embedded inline as a data island (up to
256 KiB) for a zero-network-roundtrip render. XHR / fetch()
requests (Sec-Fetch-Dest: empty) still receive raw RDF.
| Env var | CLI flag | Default |
|---|---|---|
JSS_MASHLIB | --mashlib | off |
JSS_MASHLIB_CDN | --mashlib-cdn | 2.0.0 |
JSS_MASHLIB_MODULE | --mashlib-module | — |
§RDF content negotiation on GET
GET transcodes a stored RDF resource into the syntax the client names in
Accept. KG resources persist as N-Triples; agents and the elevation
extractor can read the same graph as Turtle or JSON-LD without a separate
conversion step. Recognised concrete media types:
Accept value | Served as |
|---|---|
text/turtle | Turtle (N-Triples is a syntactic subset, emitted verbatim) |
application/n-triples | N-Triples |
application/ld+json | JSON-LD (expanded form) |
Transcoded responses carry Content-Type of the negotiated format plus
Vary: Accept. The body is served verbatim when:
- the client names no concrete RDF type (
*/*,application/*, empty); - the requested format already equals the stored format;
- the stored body does not parse as N-Triples (fails soft, never destroys);
- the target is
application/rdf+xml(no serialiser — declined, served as-is).
Non-RDF resources are always served verbatim. PATCH likewise seeds its
working graph from the existing N-Triples body, so an N3/SPARQL-Update
mutation is applied on top of pre-existing triples rather than replacing
them; a body that is neither empty nor parseable N-Triples is refused with
409 rather than silently overwritten.
§Agent write path (NIP-98)
Write verbs (PUT / POST / PATCH / DELETE / COPY) authenticate the
caller via NIP-98: extract_pubkey reconstructs the signed request URL from
actix connection_info, honouring X-Forwarded-Proto so a did:nostr agent
signing https:// behind TLS or a reverse proxy is not rejected with a
spurious 401 URL mismatch. The authenticated pubkey resolves to a
did:nostr:{pubkey} WebID that WAC evaluates against the effective ACL.
On an unauthenticated write the WWW-Authenticate challenge advertises
Nostr realm="Solid", DPoP realm="Solid", Bearer realm="Solid", so an agent
discovers the NIP-98 scheme the write path actually accepts. WAC denial of an
authenticated caller returns 403; a missing credential returns 401.
§Admin API and Native Pod Mesh (alpha.15+)
§Provision endpoint
POST /_admin/provision/{pubkey} creates a new pod for a Nostr pubkey in one
atomic step: pod directory, owner-only .acl, and a git init that sets
receive.denyCurrentBranch=updateInstead so the pod is immediately pushable
over HTTP via /_git/{pubkey}/.
curl -X POST https://pods.example.com/_admin/provision/<hex-pubkey> \
-H "X-Pod-Admin-Key: $SOLID_ADMIN_KEY"
# → { "podUrl": "https://pods.example.com/<hex-pubkey>/", "ok": true }This endpoint is the CF Workers ↔ agentbox handshake: auth-worker calls it
during WebAuthn registration to atomically provision a Solid pod alongside the
Nostr identity. The PSK (SOLID_ADMIN_KEY / --admin-key) must be set for the
endpoint to be active; it returns 403 unconditionally when unset.
Generate a key with:
openssl rand -hex 32§CORS allowlist for the forum git client
The forum’s Source Control panel (components/git_panel.rs) drives
/_git/{pubkey}/ over HTTP from a cross-origin browser context.
SOLID_ALLOWED_ORIGINS / --allowed-origins is a comma-separated list of
origins that will receive Access-Control-Allow-Origin headers.
# Production — lock to known origins
SOLID_ALLOWED_ORIGINS=https://dreamlab-ai.com,https://pods.dreamlab-ai.com
# Development default — empty = wildcard (*)OPTIONS preflights for /_git/{pubkey}/** are handled automatically
(feature git required, which is on by default in this binary).
§Deployment
For the full agentbox mesh deployment (solid-pod-rs-server alongside
auth-worker, R2, and the forum client) see:
docker-compose.solid-pods.yml # in the dreamlab-ai-website agentbox repoThat compose file wires SOLID_ADMIN_KEY, SOLID_ALLOWED_ORIGINS,
JSS_STORAGE_ROOT, and the CF Worker PROVISION_URL binding together.
§MCP server (Model Context Protocol)
POST /mcp exposes the pod as a Model Context Protocol 2025-03-26 tool
surface over the Streamable HTTP transport. Requests are JSON-RPC 2.0;
responses are single-shot JSON, with an SSE upgrade for the streaming
subscribe tool. The endpoint is off by default:
solid-pod-rs-server --mcp # enable
solid-pod-rs-server --no-mcp # force off (overrides JSS_MCP)
JSS_MCP=1 solid-pod-rs-server # enable via envIdentity reuses the pod’s NIP-98 verifier, so every tool call receives
the same WAC treatment as the equivalent REST request; an unauthenticated
/mcp call runs as the anonymous principal. Sixteen tools are exposed:
| Group | Tools |
|---|---|
| Resources | list_resources, read_resource, write_resource, create_resource, delete_resource, head_resource |
| Access control | read_acl, write_acl |
| Skills & docs | list_skills, get_skill, get_pod_skill, list_docs, read_docs |
| Pod & federation | pod_info, subscribe, call_remote_pod |
call_remote_pod is gated to /private/federation/ for did:nostr
identities with a depth-3 recursion cap, so an agent cannot fan out an
unbounded pod-to-pod call graph. Built-in docs and skills are embedded at
compile time via include_dir.
| Env var | CLI flag | Default |
|---|---|---|
JSS_MCP | --mcp / --no-mcp | off |
§install subcommand — push a Solid app into a pod
solid-pod-rs-server install <app-spec>... clones one or more Solid apps
and pushes them into a pod over the git smart protocol (the same
/_git/{pubkey}/ path the forum git client uses). The app-spec grammar
mirrors JSS src/cli/install.js:
<name> → https://github.com/solid-apps/<name>
<org>/<repo> → https://github.com/<org>/<repo>
<full-git-url> → used verbatim (https / git@ / ssh://)
<spec>#<ref> → clone a specific branch/tag/commit
<spec>=<dest> → rename the destination directory# Install the bundled mashlib browser into the default pod
NOSTR_PRIVKEY=<hex> solid-pod-rs-server install mashlib
# Pin a ref, rename the destination, target an explicit pod
solid-pod-rs-server install \
solid/contacts#v2=address-book \
--pod https://pods.example.com/<hex-pubkey>/ \
--nostr-privkey <hex>
# Preview without pushing
solid-pod-rs-server install mashlib --dry-runEach app is pushed to both HEAD:main and HEAD:gh-pages so apps
that publish from either branch land correctly. Authentication uses a
single NIP-98 token minted over the destination repo URL with a *
method wildcard, injected via git http.extraHeader so it covers the
multi-request smart protocol with one static header. A --token bearer
fallback is accepted when NIP-98 signing is unavailable.
The scratch clone is created under the platform temp directory
(std::env::temp_dir(), which honours $TMPDIR) and removed on both
success and failure, so install works on Linux/macOS/Windows and
sandboxed environments such as Termux without a hardcoded /tmp — the
Rust equivalent of JSS #518.
| Flag | Env var | Default |
|---|---|---|
--pod | JSS_POD | http://localhost:4443 |
--nostr-privkey | NOSTR_PRIVKEY | — |
--token | JSS_BEARER_TOKEN | — |
--branches | — | main,gh-pages |
--dry-run | — | off |
NIP-98 minting requires the install cargo feature (which pulls in
solid-pod-rs/nip98-schnorr):
cargo build --release -p solid-pod-rs-server --features installWithout that feature the subcommand still compiles, but only the
--token bearer path can authenticate; the NIP-98 path returns a clear
runtime error telling the operator to rebuild with --features install.
§Feature flags
This binary enables the following solid-pod-rs features by default:
| Feature | Purpose |
|---|---|
fs-backend | Filesystem storage (JSS default) |
memory-backend | In-memory storage (test / dev) |
config-loader | F6 layered config loader |
legacy-notifications | F3 solid-0.1 WS notifications adapter |
Other feature flags (oidc, dpop-replay-cache, nip98-schnorr,
s3-backend) can be opted into by the operator via a custom build.
§Licence
AGPL-3.0-only. See LICENSE. Operating this binary as a
network service triggers AGPL §13 source-disclosure obligations.
§Sibling crates (all functional)
solid-pod-rs-activitypub— ActivityPub federation (4,453 LOC)solid-pod-rs-git— Git HTTP backend (1,685 LOC)solid-pod-rs-idp— Solid-OIDC identity provider (6,160 LOC)solid-pod-rs-nostr— did:nostr + embedded Nostr relay (2,177 LOC)solid-pod-rs-didkey— did:key (Ed25519/P-256/secp256k1) + JWT (1,167 LOC)
Modules§
- cli
- CLI argument definitions (clap derive structs). Operator CLI subcommands — Sprint 11 rows 138, 163, 168.
- mempool
- Native mempool.space REST client (provenance-upgrade Phase 3). Concrete
solid_pod_rs::mrc20::MempoolLookup+ the verify-sidesolid_pod_rs::provenance::BlockAnchorer. Server-side only (builds areqwest::Client); wasm consumers implement the trait overfetch. Native mempool.space REST client — the read-side of block-trail anchors. - trail_
store - MRC20 trail persistence (provenance-upgrade Phase 4). Loads/saves a
token’s Bitcoin-anchored state chain at
/.well-known/token/{ticker}.jsonvia the pod’ssolid_pod_rs::storage::Storagebackend (JSStoken.js:189-208). Native-only; holds the issuer secret off the publicsolid_pod_rs::mrc20::Mrc20Trailtype. MRC20 trail persistence — the server-side load/save for a token’s Bitcoin-anchored state chain (ADR-059 Phase 4; JSStoken.js:189-208).
Structs§
- AppState
- Actix-web shared state.
- Cors
Headers - Adds the same CORS envelope JSS emits from its global
onRequesthook. - Cors
Headers Middleware - Per-request service instance produced by
CorsHeaders. - Dotfile
Guard - Actix middleware that blocks dotfile paths unless they appear on the allowlist.
- Dotfile
Guard Middleware - Per-request service instance produced by
DotfileGuard. - Error
Logging Middleware - Observes outbound responses and logs 5xx results with the full
error chain. Pass-through on 2xx/3xx/4xx. Shaped as an actix
Transformso it slots into the middleware stack inbuild_app. - Error
Logging Middleware Service - Per-request service instance produced by
ErrorLoggingMiddleware. - Node
Info Meta - NodeInfo 2.1 body inputs. Kept here so tests can override them.
- Path
Traversal Guard - Actix middleware that rejects requests containing
..path-traversal sequences. - Path
Traversal Guard Middleware - Per-request service instance produced by
PathTraversalGuard. - PodCreate
Limiter - In-process sliding-window limiter for JSS-compatible
POST /.pods.
Constants§
- DEFAULT_
BODY_ CAP - Discover the body cap from the environment. Accepts values like
50MB,1.5GB, or a bare integer (bytes). Falls back to 50 MiB. - DEFAULT_
PROXY_ BYTE_ CAP - Default byte cap for proxied responses (50 MiB).
Functions§
- body_
cap_ from_ env - Read
JSS_MAX_REQUEST_BODYand parse viaparse_size. On any failure, returnsDEFAULT_BODY_CAP. - build_
app - Build the complete actix
Appfor the Solid Pod server. Both the binary (main.rs) and the workspace integration tests call this.