# wisp.place ^.^ Decentralized static site hosting on the AT Protocol. [https://wisp.place](https://wisp.place) ## What is this? Host static sites in your AT Protocol repo, served with CDN distribution. Your PDS holds the cryptographically signed manifest and files - the source of truth. Hosting services index and serve them fast. ## Quick Start ```bash # Using the web interface Visit https://wisp.place and sign in # Or use the CLI npm install -g wispctl # or npm create wisp ``` Your site appears at `https://sites.wisp.place/{your-did}/{site-name}` or your custom domain. ## Architecture - **`/apps/main-app`** - Main backend (OAuth, site management, custom domains) - **`/apps/hosting-service`** - Microservice that serves cached sites from disk - **`/apps/firehose-service`** - Watches the firehose and caches sites to S3 - **`/apps/webhook-service`** - Delivers outbound webhooks - **`/cli-rs`** - Rust CLI (`wispctl`) for direct PDS uploads - **`/apps/main-app/public`** - Static frontend plus a Bun-bundled React editor - **`/packages`** - Shared packages ### How it works 1. Sites stored as `place.wisp.fs` records in your AT Protocol repo 2. Files compressed (gzip) and base64-encoded as blobs 3. Hosting service watches firehose, caches sites locally 4. Sites served via custom domains or `*.wisp.place` subdomains ## Development ```bash # bun install will install packages across the monorepo bun install # PostgreSQL, Redis, MinIO, local PLC, and seeded reference PDS bun run dev:infra # Backend bun run dev ``` The local PDS provides `alice.test` and `bob.test` without production credentials. See the [local development network guide](./docs/src/content/docs/local-development.md) for service commands, credentials, and the reserved `*.localhost` Wisp hostnames. .env file for local dev ```env DATABASE_URL=postgres://postgres:postgres@localhost:5432/wisp NODE_ENV=development DOMAIN=https://wisp.place CLIENT_NAME=Wisp.Place LOCAL_DEV=true ``` ### Local Domain XRPC (ServiceAuth via PDS Proxy) `apps/main-app` exposes domain claim/status XRPC endpoints: - `place.wisp.v2.domain.claimSubdomain` (procedure / POST, wisp handles) - `place.wisp.v2.domain.claim` (procedure / POST) - `place.wisp.v2.domain.addSite` (procedure / POST) - `place.wisp.v2.domain.delete` (procedure / POST) - `place.wisp.v2.domain.getList` (query / GET) - `place.wisp.v2.domain.getStatus` (query / GET) - `place.wisp.v2.site.getList` (query / GET) - `place.wisp.v2.site.delete` (procedure / POST) The server validates **serviceAuth JWTs** on `/xrpc/*`. Set these env vars in your active `.env`: ```env SERVICE_DID=did:web:domain SERVICE_IDS="#wisp_xrpc" SERVICE_ENDPOINT=https://domain ``` Notes: - `/.well-known/atproto-did` returns `SERVICE_DID`. - `/.well-known/did.json` publishes the DID doc, including `service` entries from `SERVICE_IDS`. - `SERVICE_IDS` must be quoted when it starts with `#` (otherwise dotenv treats it as a comment). - Service identity keys are stored in `service_identity_keys` in Postgres. If `SERVICE_PUBLIC_KEY_MULTIBASE` and `SERVICE_PRIVATE_KEY_MULTIBASE` are not set, a keypair is generated once and persisted. ```bash # Hosting service bun run hosting:dev # CLI npm install -g wispctl # or: npm create wisp ``` ## Features ### File Filtering and `.wispignore` wisp automatically excludes common files that shouldn't be uploaded to your site (like `.git`, `node_modules`, `.env` files, etc.). You can customize this behavior by creating a `.wispignore` file in your site root. The `.wispignore` file uses the same syntax as `.gitignore`: ``` # Custom ignore patterns *.log temp/ build/ .secret ``` Default patterns include: `.git`, `.github`, `.gitlab`, `.DS_Store`, `node_modules`, `.env`, cache directories, Python virtual environments, editor swap files, `.tangled`, and more. See [File Filtering Documentation](./docs/src/content/docs/file-filtering.md) for details. ### URL Redirects and Rewrites The hosting service supports Netlify-style `_redirects` files for managing URLs. Place a `_redirects` file in your site root to enable: - **301/302 Redirects**: Permanent and temporary URL redirects - **200 Rewrites**: Serve different content without changing the URL - **404 Custom Pages**: Custom error pages for specific paths - **Splats & Placeholders**: Dynamic path matching (`/blog/:year/:month/:day`, `/news/*`) - **Query Parameter Matching**: Redirect based on URL parameters - **Conditional Redirects**: Route by country, language, or cookie presence - **Force Redirects**: Override existing files with redirects Example `_redirects`: ``` # Single-page app routing (React, Vue, etc.) /* /index.html 200 # Simple redirects /home / /old-blog/* /blog/:splat # API proxy /api/* https://api.example.com/:splat 200 # Country-based routing / /us/ 302 Country=us / /uk/ 302 Country=gb ``` ## Limits - Max file size: 100MB (PDS limit) - Max total size: 300MB per site - Max files: 1000 per site ## Tech Stack - Backend: Bun + Elysia + PostgreSQL - Frontend: React 19 + Tailwind 4 + Radix UI - Hosting: Bun microservice using Hono - CLI: Rust + Jacquard (AT Protocol library) - Protocol: AT Protocol OAuth + custom lexicons ## License MIT ## Links - [AT Protocol](https://atproto.com) - [Jacquard Library](https://tangled.org/@nonbinary.computer/jacquard)