Skip to main content

Crate solid_pod_rs_server

Crate solid_pod_rs_server 

Source
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-configured actix_web::App with 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 — Reads JSS_MAX_REQUEST_BODY from the environment.
  • cli — CLI argument definitions (clap derive).

§Route table

MethodPathHandler
GET/HEAD/{tail:.*}handle_get
GET/{folder}/*Glob merged Turtle
PUT/{tail:.*}handle_put
PUT/{tail:.*}/ + Link: BasicContainerContainer creation
POST/{tail:.*}/handle_post
PATCH/{tail:.*}handle_patch
DELETE/{tail:.*}handle_delete
COPY/{tail:.*} + Source headerhandle_copy
OPTIONS/{tail:.*}handle_options
POST/api/accounts/newPod provisioning
GET/pods/check/{name}Pod existence check
POST/login/passwordCredentials login
POST/account/password/resetPassword reset
POST/account/password/changePassword change
GET/.well-known/solidSolid discovery
GET/.well-known/webfingerWebFinger JRD
GET/.well-known/nodeinfoNodeInfo discovery
GET/.well-known/nodeinfo/2.1NodeInfo 2.1
GET/.well-known/did/nostr/{pubkey}.jsonDID:nostr document

§Middleware stack (applied in order)

  1. NormalizePath – collapse // and decode %-encoded segments.
  2. PathTraversalGuard – defence-in-depth .. re-check.
  3. DotfileGuard – rejects .env etc unless on the allowlist.
  4. PayloadConfig – enforces JSS_MAX_REQUEST_BODY body cap.
  5. ErrorLoggingMiddleware – structured 5xx logging.
  6. 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.json

Until 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], no actix-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 its PodService-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 flags

See 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.js

When 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 varCLI flagDefault
JSS_MASHLIB--mashliboff
JSS_MASHLIB_CDN--mashlib-cdn2.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 valueServed as
text/turtleTurtle (N-Triples is a syntactic subset, emitted verbatim)
application/n-triplesN-Triples
application/ld+jsonJSON-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 repo

That 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 env

Identity 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:

GroupTools
Resourceslist_resources, read_resource, write_resource, create_resource, delete_resource, head_resource
Access controlread_acl, write_acl
Skills & docslist_skills, get_skill, get_pod_skill, list_docs, read_docs
Pod & federationpod_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 varCLI flagDefault
JSS_MCP--mcp / --no-mcpoff

§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-run

Each 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.

FlagEnv varDefault
--podJSS_PODhttp://localhost:4443
--nostr-privkeyNOSTR_PRIVKEY
--tokenJSS_BEARER_TOKEN
--branchesmain,gh-pages
--dry-runoff

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 install

Without 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:

FeaturePurpose
fs-backendFilesystem storage (JSS default)
memory-backendIn-memory storage (test / dev)
config-loaderF6 layered config loader
legacy-notificationsF3 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)

Modules§

cli
CLI argument definitions (clap derive structs). Operator CLI subcommands — Sprint 11 rows 138, 163, 168.

Structs§

AppState
Actix-web shared state.
CorsHeaders
Adds the same CORS envelope JSS emits from its global onRequest hook.
CorsHeadersMiddleware
Per-request service instance produced by CorsHeaders.
DotfileGuard
Actix middleware that blocks dotfile paths unless they appear on the allowlist.
DotfileGuardMiddleware
Per-request service instance produced by DotfileGuard.
ErrorLoggingMiddleware
Observes outbound responses and logs 5xx results with the full error chain. Pass-through on 2xx/3xx/4xx. Shaped as an actix Transform so it slots into the middleware stack in build_app.
ErrorLoggingMiddlewareService
Per-request service instance produced by ErrorLoggingMiddleware.
NodeInfoMeta
NodeInfo 2.1 body inputs. Kept here so tests can override them.
PathTraversalGuard
Actix middleware that rejects requests containing .. path-traversal sequences.
PathTraversalGuardMiddleware
Per-request service instance produced by PathTraversalGuard.
PodCreateLimiter
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_BODY and parse via parse_size. On any failure, returns DEFAULT_BODY_CAP.
build_app
Build the complete actix App for the Solid Pod server. Both the binary (main.rs) and the workspace integration tests call this.