You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

75 lines
2.6 KiB
Markdown

# pages
A micro static-site host at `pages.elijah.run`, running entirely on
Cloudflare (Workers + R2 + D1) with the Worker written in Rust compiled to
WebAssembly.
## What it does
- Browse all live pages at `pages.elijah.run`.
- Log in with GitHub, upload a single `.html` file or a `.zip` of static
assets (drag & drop), pick a name, and the content is served at
`pages.elijah.run/<name>/`.
- Update a page in place: **Update** on any page you own re-uploads its
content under the same name (the URL never changes, and files the new
upload doesn't include are removed).
- Admins approve which users may upload, and can delete or update any page.
Hosted pages are intended to be **client-side-only single-page apps**. They
are served with a sandboxing Content-Security-Policy (opaque origin): no
cookies, no localStorage — see `docs/plans/2026-07-12-pages-design.md` §2.1.
An admin can mark a specific page, or all of a user's pages, as **trusted**
to opt them out of that sandbox (e.g. for local-first apps that need
`localStorage`) — see the "Trust escape hatch" addendum in §2.1 for the
exact semantics and the security trade-off that comes with it.
## How it works
One Cloudflare Worker routes everything:
- `/` — embedded management UI (list, login, upload, admin)
- `/auth/*` — GitHub OAuth + session cookies (sessions in D1, hashed)
- `/api/*` — JSON API (list/upload/update/delete pages, manage users)
- `/<slug>/*` — page content served from R2 with strict security headers
Uploads are validated hard: strict slug grammar + reserved names, zip
path-traversal rejection, decompression limits (10 MiB compressed,
25 MiB uncompressed, 300 files). See the design doc for the full security
analysis.
## Run locally
```sh
# from pages/
cp .dev.vars.example .dev.vars # fill in a GitHub OAuth app's id/secret
wrangler d1 execute pages --local --file migrations/schema.sql
wrangler dev # builds via worker-build and serves locally
```
## Test
```sh
# from pages/
cargo fmt
cargo check --target wasm32-unknown-unknown
cargo clippy --all-targets -- -D warnings
cargo test
```
## Deploy
Infrastructure (R2 bucket, D1 database, DNS, rate limits) is OpenTofu in
`infra/`; the Worker itself deploys with `wrangler deploy` (see
`infra/README.md`). Secrets via `wrangler secret put GITHUB_CLIENT_SECRET`.
## License
Dual-licensed under [Apache-2.0](../LICENSE-APACHE) and
[MIT](../LICENSE-MIT), like the rest of this repository.
## Disclaimer
This software was written with Claude Code. Design, review and orchestration
by Claude Fable 5 (`claude-fable-5`); implementation by Claude Sonnet
agents with Claude Opus review agents.