An encrypted personal cloud built on the AT Protocol.

Updated documentation after web addition and config rewrite

sans-self.org 4b46ef58 4c3a4329

Waiting for spindle ...
+99 -17
+1
CLAUDE.md
··· 23 23 5. **Plaintext metadata is opt-in transparency.** Names and tags unencrypted by default for AppView indexing. Full opacity via dummy values + encrypted metadata payload. 24 24 6. **Public keys as PDS records.** atproto DID docs only have signing keys. Opake publishes X25519 encryption public keys as `app.opake.cloud.publicKey/self` singleton records. 25 25 7. **Multi-device: seed phrase** (future). MVP uses plaintext keypair at `~/.config/opake/accounts/<did>/identity.json`. 26 + 8. **Storage trait in opake-core.** Config, Identity, Session types and the `Storage` trait live in core so both CLI (`FileStorage`, filesystem) and web (`IndexedDbStorage`, IndexedDB) share the same contract. Platform-specific I/O is injected, never imported. 26 27 27 28 ## Documentation 28 29
+14 -4
CONTRIBUTING.md
··· 8 8 2. Install Rust 1.75+ via [rustup](https://rustup.rs) 9 9 3. Run the test suite: `cargo test` 10 10 4. Run the linter: `cargo clippy -- -D warnings` 11 + 5. For web frontend work: install [Bun](https://bun.sh), then `cd web && bun install` 11 12 12 13 ## Code style 13 14 ··· 26 27 - XRPC client with automatic token refresh 27 28 - document operations (upload, download, list, delete, resolve) 28 29 - AT Protocol record types and lexicon constants 30 + - Storage trait + config/identity/session types (storage.rs) 29 31 - shared config path resolution (paths.rs) 30 32 31 - opake-cli thin CLI wrapper 33 + opake-cli CLI binary wrapping opake-core 32 34 - clap command definitions 33 - - config/session/identity persistence 35 + - FileStorage (impl Storage over filesystem, TOML + JSON) 34 36 - user interaction (prompts, formatting) 35 37 36 38 opake-appview indexer + REST API for grant/keyring discovery ··· 43 45 - #[derive(RedactedDebug)] with #[redact] field attribute 44 46 - generates Debug impls showing byte length instead of content 45 47 - used by opake-core (ContentKey, Session) and opake-cli (Identity) 48 + 49 + web/ React SPA (Vite + TanStack Router + Tailwind/daisyUI) 50 + - opake-core via WASM (wasm-pack build) 51 + - IndexedDbStorage (impl Storage over Dexie.js/IndexedDB) 52 + - Zustand stores, Web Worker for off-main-thread crypto 53 + - cabinet file browser UI with panel navigation 46 54 ``` 47 55 48 - `opake-core` must never depend on filesystem, stdin, or any platform-specific API. All I/O happens in the binary crates. 56 + `opake-core` must never depend on filesystem, stdin, or any platform-specific API. All I/O goes through the `Storage` trait — `FileStorage` (CLI) and `IndexedDbStorage` (web) are the platform-specific implementations. 49 57 50 58 See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the detailed crate structure and encryption model, and [docs/FLOWS.md](docs/FLOWS.md) for sequence diagrams of every operation. 51 59 ··· 59 67 - The `test-utils` feature flag gates test infrastructure in `opake-core` 60 68 61 69 ```sh 62 - cargo test # all tests 70 + cargo test # all Rust tests 63 71 cargo test -p opake-core # core only 64 72 cargo test -p opake-cli # CLI only 65 73 cargo test -p opake-appview # appview only 66 74 cargo test -- --test-output # show println output 75 + 76 + cd web && bun run test # web frontend tests (Vitest + fake-indexeddb) 67 77 ``` 68 78 69 79 ## Commit messages
+29 -7
README.md
··· 99 99 # revoke a share grant 100 100 opake revoke at://did:plc:abc/app.opake.cloud.grant/tid123 101 101 102 + # check incoming grants (via AppView) 103 + opake inbox --appview https://appview.example.com 104 + opake inbox --long 105 + 106 + # keyring-based group sharing 107 + opake keyring create family-photos 108 + opake keyring ls 109 + opake keyring add-member family-photos alice.example.com 110 + opake upload photo.jpg --keyring family-photos 111 + opake download --keyring-member at://did:plc:abc/app.opake.cloud.document/tid456 112 + opake keyring remove-member family-photos alice.example.com 113 + 102 114 # remove an account 103 115 opake logout bob.other.com 104 116 ``` ··· 115 127 116 128 ## Architecture 117 129 118 - Four crates: 130 + Four crates + a web frontend: 119 131 120 - - **`opake-core`** — platform-agnostic library (compiles to WASM). Encryption, records, XRPC client, document operations. 121 - - **`opake-cli`** — thin CLI wrapper. Config, session, identity persistence. 132 + - **`opake-core`** — platform-agnostic library (compiles to WASM). Encryption, records, XRPC client, document operations, `Storage` trait. 133 + - **`opake-cli`** — CLI binary. `FileStorage` (filesystem-backed), command dispatch. 122 134 - **`opake-appview`** — Axum-based indexer and REST API. Jetstream firehose consumer, SQLite storage, DID-scoped Ed25519 auth. 123 135 - **`opake-derive`** — Proc-macro crate. `RedactedDebug` derive macro for secret-safe Debug output. 136 + - **`web/`** — React SPA (Vite + TanStack Router + Tailwind/daisyUI). Uses `opake-core` via WASM. `IndexedDbStorage` (IndexedDB-backed) implements the same `Storage` trait as the CLI. 124 137 125 138 See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the encryption model, crate structure, and design decisions. See [docs/FLOWS.md](docs/FLOWS.md) for sequence diagrams of every operation. 126 139 ··· 139 152 - [x] AppView indexer (grants + keyrings from firehose) 140 153 - [x] AppView REST API with DID-scoped Ed25519 auth 141 154 - [x] Folder hierarchy (mkdir, tree, path-aware rm/mv/cat/upload) 142 - - [ ] Grant discovery (inbox command — queries AppView) 143 - - [ ] Keyring-based group sharing 144 - - [ ] Web UI (SPA frontend) 155 + - [x] Grant discovery (inbox command — queries AppView) 156 + - [x] Keyring-based group sharing 157 + - [ ] Web UI — cabinet file browser (in progress, auth stubbed) 158 + - [ ] AT Protocol OAuth (DPoP) for browser authentication 159 + - [ ] Seed phrase key derivation for multi-device 145 160 146 161 ## Development 147 162 148 163 ```sh 149 - cargo test # run all tests 164 + cargo test # run all Rust tests 150 165 cargo clippy # lint 151 166 cargo fmt # format 167 + 168 + # web frontend 169 + cd web 170 + bun install # install deps 171 + bun run wasm:build # build opake-core WASM module 172 + bun run dev # start Vite dev server 173 + bun run test # run Vitest suite 152 174 ``` 153 175 154 176 CI runs on [Tangled](https://tangled.org) via `.tangled/workflows/test.yml`.
+55 -6
docs/ARCHITECTURE.md
··· 4 4 5 5 ```mermaid 6 6 graph TB 7 - subgraph Client ["Client (your machine)"] 7 + subgraph Client ["Client (your machine / browser)"] 8 8 CLI["opake CLI"] 9 + Web["Web SPA"] 9 10 Core["opake-core library"] 10 11 Crypto["Client-side crypto<br/>(AES-256-GCM, X25519)"] 11 12 end ··· 23 24 end 24 25 25 26 CLI --> Core 27 + Web -->|WASM| Core 26 28 Core --> Crypto 27 29 Core -->|XRPC / HTTPS| OwnPDS 28 30 Core -->|unauthenticated| OtherPDS 29 31 Core -->|DID resolution| PLC 30 32 CLI -->|inbox query| AppView 33 + Web -->|inbox query| AppView 31 34 32 35 AppView -->|subscribe| Jetstream 33 36 AppView --> SQLite ··· 41 44 style Network fill:#16213e,color:#eee 42 45 ``` 43 46 44 - The CLI talks directly to PDS instances over XRPC. No PDS modifications needed. All encryption and decryption happens on your machine. The AppView is an optional component that indexes grants and keyrings from the firehose for discovery. 47 + Both the CLI and the web frontend talk directly to PDS instances over XRPC. No PDS modifications needed. All encryption and decryption happens client-side — on your machine (CLI) or in the browser (Web via WASM). The AppView is an optional component that indexes grants and keyrings from the firehose for discovery. 45 48 46 49 ## Crate Structure 47 50 ··· 51 54 src/ 52 55 atproto.rs AT-URI parsing, shared AT Protocol primitives 53 56 resolve.rs Handle/DID → PDS → public key resolution pipeline 57 + storage.rs Config, Identity types + Storage trait (cross-platform contract) 54 58 error.rs Typed error hierarchy (thiserror) 55 59 test_utils.rs MockTransport + response queue (behind test-utils feature) 56 60 crypto/ ··· 108 112 opake-cli/ CLI binary wrapping opake-core 109 113 src/ 110 114 main.rs Clap app, command dispatch 111 - config.rs Multi-account config (default DID, account map) 112 - session.rs Per-account session persistence (JWT tokens) 113 - identity.rs Per-account X25519 + Ed25519 keypair persistence 115 + config.rs FileStorage (impl Storage for filesystem), anyhow wrappers 116 + session.rs CommandContext resolution, session persistence 117 + identity.rs Keypair generation, migration, permission checks 114 118 keyring_store.rs Local group key persistence (per-keyring) 115 119 transport.rs reqwest-based Transport implementation 116 120 utils.rs Test harness, env helpers ··· 165 169 opake-derive/ Proc-macro crate (RedactedDebug derive) 166 170 src/ 167 171 lib.rs #[derive(RedactedDebug)] + #[redact] attribute 172 + 173 + web/ React SPA (Vite + TanStack Router + Tailwind + daisyUI) 174 + src/ 175 + lib/ 176 + storage.ts Storage interface (mirrors opake-core Storage trait) 177 + storage-types.ts Config, Identity, Session types (mirrors opake-core) 178 + indexeddb-storage.ts IndexedDbStorage (impl Storage over Dexie.js/IndexedDB) 179 + api.ts API client helpers 180 + crypto-types.ts Crypto type definitions 181 + stores/ 182 + auth.ts Auth state (Zustand) 183 + routes/ 184 + __root.tsx Root layout with auth guard 185 + index.tsx Landing page 186 + login.tsx Login form 187 + cabinet.tsx File cabinet (main UI) 188 + components/cabinet/ 189 + PanelStack.tsx Stacked panel navigation 190 + PanelContent.tsx File grid/list view 191 + Sidebar.tsx Navigation sidebar 192 + TopBar.tsx Header with account switcher 193 + FileGridCard.tsx Grid card with file icon + metadata 194 + FileListRow.tsx List row variant 195 + types.ts Discriminated union types for cabinet state 196 + wasm/opake-wasm/ WASM build of opake-core (via wasm-pack) 197 + workers/ 198 + crypto.worker.ts Web Worker for off-main-thread crypto (Comlink) 199 + tests/ 200 + lib/ 201 + indexeddb-storage.test.ts Storage contract tests (fake-indexeddb) 168 202 ``` 169 203 170 - The boundary is strict: `opake-core` never touches the filesystem, stdin, or any platform-specific API. All I/O happens in the binary crates. This keeps `opake-core` compilable to WASM for the future web UI. 204 + The boundary is strict: `opake-core` never touches the filesystem, stdin, or any platform-specific API. All I/O happens through the `Storage` trait — `FileStorage` (CLI, filesystem) and `IndexedDbStorage` (web, IndexedDB) implement the same contract with platform-specific backends. This keeps `opake-core` compilable to WASM, which the web frontend uses via `wasm-pack`. 171 205 172 206 ## Encryption Model 173 207 ··· 261 295 3. Blob (encrypted file content) 262 296 263 297 All three are unauthenticated reads — AT Protocol records and blobs are public by design. The encryption is the access control, not the transport. 298 + 299 + ## Storage Abstraction 300 + 301 + Config, identity, and session types live in `opake-core/src/storage.rs` alongside the `Storage` trait. This lets both platforms share the same data model and mutation logic (e.g. `Config::add_account`, `Config::remove_account`, `Config::set_default`). 302 + 303 + | Method | Contract | 304 + |--------|----------| 305 + | `load_config` / `save_config` | Read/write the global config (accounts map, default DID) | 306 + | `load_identity` / `save_identity` | Read/write per-account encryption keypairs | 307 + | `load_session` / `save_session` | Read/write per-account JWT tokens | 308 + | `remove_account` | Full cleanup: mutate config + delete identity/session data + persist | 309 + 310 + **CLI (`FileStorage`)** — TOML config at `~/.config/opake/config.toml`, JSON files in per-account directories, unix permissions (0600/0700). 311 + 312 + **Web (`IndexedDbStorage`)** — Dexie.js over IndexedDB with three object stores (`configs`, `identities`, `sessions`). `removeAccount` runs config mutation + data deletion in a single transaction for atomicity. 264 313 265 314 ## Multi-Account Support 266 315