| 3e3af55 | | | 1 | # Grove |
| 3e3af55 | | | 2 | |
| 36387cc | | | 3 | **Monorepo-first hosting platform powered by Mononoke.** |
| 3e3af55 | | | 4 | |
| 59985bb | | | 5 | Grove is a GitHub-like web platform for hosting, browsing, reviewing, and building code. Built on Meta's Mononoke source control server, it supports both standard Git and Sapling clients. |
| 59985bb | | | 6 | |
| 59985bb | | | 7 | **Live at [grove.host](https://grove.host)** |
| 3e3af55 | | | 8 | |
| 3e3af55 | | | 9 | --- |
| 3e3af55 | | | 10 | |
| 3e3af55 | | | 11 | ## Architecture |
| 3e3af55 | | | 12 | |
| 3e3af55 | | | 13 | ``` |
| 59985bb | | | 14 | ┌──────────────┐ |
| 59985bb | | | 15 | │ grove.host │ Caddy: auto-TLS + routing |
| 59985bb | | | 16 | └──────┬───────┘ |
| 59985bb | | | 17 | ┌───────────────┼───────────────┐ |
| 59985bb | | | 18 | ▼ ▼ ▼ |
| 59985bb | | | 19 | ┌─────────────┐ ┌────────────┐ ┌──────────────┐ |
| 59985bb | | | 20 | │ Hub API │ │ Grove API │ │ Grove Web │ |
| 59985bb | | | 21 | │ (identity) │ │ (repos/CI) │ │ (Next.js) │ |
| 59985bb | | | 22 | │ :4000 │ │ :4000 │ │ :3000 │ |
| 59985bb | | | 23 | └─────────────┘ └─────┬──────┘ └──────────────┘ |
| 59985bb | | | 24 | │ |
| 59985bb | | | 25 | ┌─────┴──────┐ |
| 59985bb | | | 26 | │ Bridge │ |
| 59985bb | | | 27 | │ :3100 │ |
| 59985bb | | | 28 | └─────┬──────┘ |
| 59985bb | | | 29 | ┌───────────┴───────────┐ |
| 59985bb | | | 30 | ▼ ▼ |
| 59985bb | | | 31 | ┌──────────────┐ ┌──────────────┐ |
| 59985bb | | | 32 | │ Mononoke │ │ Mononoke │ |
| 59985bb | | | 33 | │ SLAPI :8443 │ │ Git :8080 │ |
| 59985bb | | | 34 | └──────────────┘ └──────────────┘ |
| 3e3af55 | | | 35 | ``` |
| 3e3af55 | | | 36 | |
| 3e3af55 | | | 37 | ### Components |
| 3e3af55 | | | 38 | |
| 3e3af55 | | | 39 | | Component | Tech | Port | Purpose | |
| 3e3af55 | | | 40 | |-----------|------|------|---------| |
| 59985bb | | | 41 | | **Caddy** | Go | 80/443 | Reverse proxy, auto-TLS (Let's Encrypt) | |
| 59985bb | | | 42 | | **Hub API** | Node.js/Fastify | 4000 | Users, WebAuthn auth, orgs, JWT issuance | |
| 59985bb | | | 43 | | **Grove API** | Node.js/Fastify | 4000 | Repos, file browsing, diffs, Canopy CI/CD | |
| 59985bb | | | 44 | | **Grove Web** | Next.js 15/React 19 | 3000 | Browser UI | |
| 59985bb | | | 45 | | **Grove Bridge** | Mononoke binary | 3100 | Bridge between API and Mononoke | |
| 59985bb | | | 46 | | **Mononoke SLAPI** | Rust | 8443 | EdenAPI server (Sapling push/pull), mTLS | |
| 59985bb | | | 47 | | **Mononoke Git** | Rust | 8080 | Git HTTP protocol | |
| 59985bb | | | 48 | | **Docker Registry** | registry:2 | 5000 | Local image registry for Canopy CI | |
| 59985bb | | | 49 | |
| 59985bb | | | 50 | ### Product Subdomains |
| 59985bb | | | 51 | |
| 59985bb | | | 52 | | Subdomain | Purpose | |
| 59985bb | | | 53 | |-----------|---------| |
| 59985bb | | | 54 | | `grove.host` | Main app — repos, diffs, settings | |
| 59985bb | | | 55 | | `canopy.grove.host` | CI/CD — pipeline runs, build logs | |
| 59985bb | | | 56 | | `ring.grove.host` | Instance logging and monitoring | |
| 59985bb | | | 57 | |
| 59985bb | | | 58 | --- |
| 59985bb | | | 59 | |
| 59985bb | | | 60 | ## Authentication |
| 59985bb | | | 61 | |
| 59985bb | | | 62 | Grove uses **WebAuthn passkeys** (no passwords). Flow: |
| 59985bb | | | 63 | |
| 59985bb | | | 64 | 1. User registers/logs in with a passkey (biometric, hardware key, etc.) |
| 59985bb | | | 65 | 2. Hub API issues a **JWT** (7-day expiry) signed with a shared `JWT_SECRET` |
| 59985bb | | | 66 | 3. Grove API verifies the JWT on each request |
| 59985bb | | | 67 | 4. **Personal Access Tokens** (PATs) are supported for CLI and API access |
| 59985bb | | | 68 | |
| 59985bb | | | 69 | Auth endpoints live in **hub-api**. Grove API only verifies tokens. |
| 59985bb | | | 70 | |
| 59985bb | | | 71 | --- |
| 59985bb | | | 72 | |
| 59985bb | | | 73 | ## Features |
| 59985bb | | | 74 | |
| 59985bb | | | 75 | ### Code Hosting |
| 59985bb | | | 76 | - Push/pull via **Git** (`git clone http://grove.host:8080/<repo>`) |
| 59985bb | | | 77 | - Push/pull via **Sapling** (`sl clone https://grove.host:8443/edenapi/<repo>`) |
| 59985bb | | | 78 | - File browser with directory navigation and syntax highlighting |
| 59985bb | | | 79 | - Commit history with pagination |
| 59985bb | | | 80 | - Blame view |
| 59985bb | | | 81 | - Branch listing (Mononoke bookmarks) |
| 59985bb | | | 82 | - README rendering (Markdown + syntax highlighting) |
| 59985bb | | | 83 | - Private repos with org-based access control |
| 59985bb | | | 84 | |
| 59985bb | | | 85 | ### Code Review (Diffs) |
| 59985bb | | | 86 | - Create diffs from the web UI (select a branch → opens against default branch) |
| 59985bb | | | 87 | - Conversation view with threaded comments |
| 59985bb | | | 88 | - Changes view with unified diff rendering |
| 59985bb | | | 89 | - Land (merge) diffs via Mononoke pushrebase |
| 59985bb | | | 90 | - Close and reopen diffs |
| 59985bb | | | 91 | - Reviews with approve/request-changes |
| 59985bb | | | 92 | |
| 59985bb | | | 93 | ### Canopy CI/CD |
| 59985bb | | | 94 | - YAML pipeline definitions (`.canopy/` directory) |
| 59985bb | | | 95 | - Docker-based step execution |
| 59985bb | | | 96 | - Real-time log streaming via SSE |
| 59985bb | | | 97 | - Push-triggered pipelines (polls Mononoke bookmarks) |
| 59985bb | | | 98 | - Manual pipeline triggers |
| 59985bb | | | 99 | - Pipeline secrets management |
| 59985bb | | | 100 | - Build history and status indicators |
| 59985bb | | | 101 | |
| 59985bb | | | 102 | ### Organizations |
| 59985bb | | | 103 | - Create organizations, add/remove members |
| 59985bb | | | 104 | - Org-owned repositories |
| 59985bb | | | 105 | - Access control for private repos |
| 59985bb | | | 106 | |
| 59985bb | | | 107 | ### Ring (Instance Logging) |
| 59985bb | | | 108 | - Log ingestion API (JSON + plain text) |
| 59985bb | | | 109 | - Per-repo and global log views |
| 59985bb | | | 110 | - Real-time log polling |
| 59985bb | | | 111 | - Accessible at `ring.grove.host` |
| 59985bb | | | 112 | |
| 59985bb | | | 113 | ### CLI (`grove`) |
| 59985bb | | | 114 | - `grove auth login` — Browser-based WebAuthn login |
| 59985bb | | | 115 | - `grove clone <repo>` — Clone a repository |
| 59985bb | | | 116 | - `grove init <name>` — Create and initialize a new repo |
| 59985bb | | | 117 | - `grove repo list` — List repositories |
| 59985bb | | | 118 | - `grove repo create <name>` — Create a new repo |
| 59985bb | | | 119 | - Config stored in `~/.grove/config` |
| 3e3af55 | | | 120 | |
| 3e3af55 | | | 121 | --- |
| 3e3af55 | | | 122 | |
| 3e3af55 | | | 123 | ## Quick Start |
| 3e3af55 | | | 124 | |
| 3e3af55 | | | 125 | ### Prerequisites |
| 3e3af55 | | | 126 | |
| 3e3af55 | | | 127 | - Docker & Docker Compose |
| 3e3af55 | | | 128 | - Node.js 22+ (for local dev) |
| 3e3af55 | | | 129 | - Git |
| 3e3af55 | | | 130 | |
| 3e3af55 | | | 131 | ### 1. Build Mononoke Docker Image |
| 3e3af55 | | | 132 | |
| 3e3af55 | | | 133 | ```bash |
| 3e3af55 | | | 134 | cd sapling |
| 36387cc | | | 135 | docker build -f ../docker/Dockerfile.mononoke -t grove/mononoke . |
| 3e3af55 | | | 136 | ``` |
| 3e3af55 | | | 137 | |
| 59985bb | | | 138 | > First build takes 1-2+ hours (compiles Mononoke from source). Subsequent builds are cached. |
| 3e3af55 | | | 139 | |
| 3e3af55 | | | 140 | ### 2. Import a Repository |
| 3e3af55 | | | 141 | |
| 3e3af55 | | | 142 | ```bash |
| 36387cc | | | 143 | ./scripts/import-repo.sh https://github.com/your/repo.git myrepo |
| 3e3af55 | | | 144 | ``` |
| 3e3af55 | | | 145 | |
| 3e3af55 | | | 146 | ### 3. Start Everything |
| 3e3af55 | | | 147 | |
| 3e3af55 | | | 148 | ```bash |
| 36387cc | | | 149 | cd docker |
| 3e3af55 | | | 150 | docker compose up |
| 3e3af55 | | | 151 | ``` |
| 3e3af55 | | | 152 | |
| 3e3af55 | | | 153 | ### 4. Access |
| 3e3af55 | | | 154 | |
| 3e3af55 | | | 155 | - **Web UI**: http://localhost:3000 |
| 3e3af55 | | | 156 | - **API**: http://localhost:4000 |
| 3e3af55 | | | 157 | - **Git clone**: `git clone http://localhost:8080/myrepo` |
| 3e3af55 | | | 158 | |
| 3e3af55 | | | 159 | ### Local Development (Without Docker) |
| 3e3af55 | | | 160 | |
| 3e3af55 | | | 161 | ```bash |
| 59985bb | | | 162 | # Hub API |
| 59985bb | | | 163 | cd hub-api && npm install && npm run dev # http://localhost:4001 |
| 59985bb | | | 164 | |
| 59985bb | | | 165 | # Grove API |
| 59985bb | | | 166 | cd api && npm install && npm run dev # http://localhost:4000 |
| 3e3af55 | | | 167 | |
| 3e3af55 | | | 168 | # Web UI |
| 59985bb | | | 169 | cd web && npm install && npm run dev # http://localhost:3000 |
| 3e3af55 | | | 170 | ``` |
| 3e3af55 | | | 171 | |
| 59985bb | | | 172 | ### Local Subdomain Dev (For Canopy/Ring Auth) |
| a33b2b6 | | | 173 | |
| 59985bb | | | 174 | Add to `/etc/hosts`: |
| a33b2b6 | | | 175 | |
| a33b2b6 | | | 176 | ```text |
| a33b2b6 | | | 177 | 127.0.0.1 grove.test |
| a33b2b6 | | | 178 | 127.0.0.1 canopy.grove.test |
| a33b2b6 | | | 179 | 127.0.0.1 ring.grove.test |
| a33b2b6 | | | 180 | ``` |
| a33b2b6 | | | 181 | |
| 59985bb | | | 182 | Then `npm run dev:local` and open: |
| a33b2b6 | | | 183 | - Grove: `http://grove.test:3000` |
| a33b2b6 | | | 184 | - Canopy: `http://canopy.grove.test:3000` |
| a33b2b6 | | | 185 | - Ring: `http://ring.grove.test:3000` |
| a33b2b6 | | | 186 | |
| 3e3af55 | | | 187 | --- |
| 3e3af55 | | | 188 | |
| 59985bb | | | 189 | ## Project Structure |
| 3e3af55 | | | 190 | |
| 3e3af55 | | | 191 | ``` |
| 59985bb | | | 192 | grove/ |
| 59985bb | | | 193 | ├── GROVE.md # This documentation |
| 59985bb | | | 194 | ├── AUDIT.md # Codebase + server audit |
| 59985bb | | | 195 | ├── api/ # Grove API server |
| 59985bb | | | 196 | │ └── src/ |
| 59985bb | | | 197 | │ ├── server.ts # Fastify entry point |
| 59985bb | | | 198 | │ ├── auth/middleware.ts # JWT verification |
| 59985bb | | | 199 | │ ├── routes/ |
| 59985bb | | | 200 | │ │ ├── repos.ts # Repo browsing, CRUD |
| 59985bb | | | 201 | │ │ ├── diffs.ts # Code review (diffs, comments, reviews) |
| 59985bb | | | 202 | │ │ ├── canopy.ts # CI/CD pipeline management |
| 59985bb | | | 203 | │ │ └── ring.ts # Instance log ingestion + viewing |
| 59985bb | | | 204 | │ └── services/ |
| 59985bb | | | 205 | │ ├── database.ts # SQLite schema + migrations |
| 59985bb | | | 206 | │ ├── bridge.ts # Mononoke Bridge client |
| 59985bb | | | 207 | │ ├── canopy-runner.ts # Docker-based CI execution |
| 59985bb | | | 208 | │ ├── canopy-poller.ts # Push event polling |
| 59985bb | | | 209 | │ ├── canopy-events.ts # SSE event bus |
| 59985bb | | | 210 | │ └── mononoke-provisioner.ts # Dynamic repo config |
| 59985bb | | | 211 | ├── hub-api/ # Hub API server (identity) |
| 59985bb | | | 212 | │ └── src/ |
| 59985bb | | | 213 | │ ├── server.ts # Fastify entry point |
| 59985bb | | | 214 | │ └── routes/ |
| 59985bb | | | 215 | │ ├── auth.ts # WebAuthn, JWT, PATs |
| 59985bb | | | 216 | │ ├── instances.ts # Instance management |
| 59985bb | | | 217 | │ └── orgs.ts # Organization CRUD |
| 59985bb | | | 218 | ├── web/ # Next.js frontend |
| 59985bb | | | 219 | │ ├── app/ |
| 59985bb | | | 220 | │ │ ├── layout.tsx, page.tsx # Root layout + dashboard |
| 59985bb | | | 221 | │ │ ├── login/ # WebAuthn login |
| 59985bb | | | 222 | │ │ ├── new/ # Create repository |
| 59985bb | | | 223 | │ │ ├── [owner]/[repo]/ # Repo pages (files, commits, blame, diffs) |
| 59985bb | | | 224 | │ │ ├── canopy/ # CI/CD pages |
| 59985bb | | | 225 | │ │ ├── ring/ # Instance logging pages |
| 59985bb | | | 226 | │ │ └── components/ # UI components |
| 59985bb | | | 227 | │ ├── lib/ |
| 59985bb | | | 228 | │ │ ├── api.ts # Typed API client |
| 59985bb | | | 229 | │ │ ├── auth.tsx # Auth context + hooks |
| 59985bb | | | 230 | │ │ └── theme.tsx # Dark/light mode |
| 59985bb | | | 231 | │ └── middleware.ts # Subdomain routing |
| 59985bb | | | 232 | ├── cli/ # Grove CLI |
| 59985bb | | | 233 | │ └── src/commands/ # Command modules |
| 59985bb | | | 234 | ├── addons/ # Sapling integrations |
| 59985bb | | | 235 | │ ├── isl/ # Interactive Smartlog UI |
| 59985bb | | | 236 | │ ├── isl-server/ # ISL backend |
| 59985bb | | | 237 | │ ├── vscode/ # VS Code extension |
| 59985bb | | | 238 | │ ├── shared/ # Shared utilities |
| 59985bb | | | 239 | │ └── components/ # ISL components library |
| 59985bb | | | 240 | ├── docker/ # Development Docker setup |
| 59985bb | | | 241 | │ ├── docker-compose.yml # Dev stack |
| 59985bb | | | 242 | │ └── Dockerfile.* # Build files |
| 59985bb | | | 243 | ├── hub/ # Production setup |
| 59985bb | | | 244 | │ ├── docker-compose.yml # Prod stack (8 services + Caddy) |
| 59985bb | | | 245 | │ └── Caddyfile # Reverse proxy config |
| 59985bb | | | 246 | └── scripts/ # Utility scripts |
| 59985bb | | | 247 | ├── import-repo.sh # Git → Mononoke importer |
| 59985bb | | | 248 | └── generate-certs.sh # TLS cert generator |
| 3e3af55 | | | 249 | ``` |
| 3e3af55 | | | 250 | |
| 3e3af55 | | | 251 | --- |
| 3e3af55 | | | 252 | |
| 59985bb | | | 253 | ## Database |
| 59985bb | | | 254 | |
| 59985bb | | | 255 | ### Grove API (SQLite: grove.db) |
| 59985bb | | | 256 | |
| 59985bb | | | 257 | | Table | Purpose | |
| 59985bb | | | 258 | |-------|---------| |
| 59985bb | | | 259 | | `users` | Local user records (synced from hub JWT claims) | |
| 59985bb | | | 260 | | `orgs` | Local org records | |
| 59985bb | | | 261 | | `repos` | Repository metadata (owner, name, branches, visibility) | |
| 59985bb | | | 262 | | `diffs` | Code review diffs (title, commits, status) | |
| 59985bb | | | 263 | | `reviews` | Diff reviews (approved/changes requested) | |
| 59985bb | | | 264 | | `comments` | Diff comments (inline or general) | |
| 59985bb | | | 265 | | `pipelines` | CI/CD pipeline definitions | |
| 59985bb | | | 266 | | `pipeline_runs` | Pipeline execution records | |
| 59985bb | | | 267 | | `pipeline_steps` | Individual step records | |
| 59985bb | | | 268 | | `step_logs` | Step output logs | |
| 59985bb | | | 269 | | `canopy_secrets` | Encrypted CI/CD secrets | |
| 59985bb | | | 270 | | `bookmark_state` | Mononoke bookmark tracking | |
| 59985bb | | | 271 | |
| 59985bb | | | 272 | ### Hub API (SQLite: hub.db) |
| 59985bb | | | 273 | |
| 59985bb | | | 274 | | Table | Purpose | |
| 59985bb | | | 275 | |-------|---------| |
| 59985bb | | | 276 | | `users` | User accounts | |
| 59985bb | | | 277 | | `credentials` | WebAuthn passkey credentials | |
| 59985bb | | | 278 | | `orgs` | Organizations | |
| 59985bb | | | 279 | | `org_members` | Org membership | |
| 59985bb | | | 280 | | `instances` | Grove instance registrations | |
| 59985bb | | | 281 | | `repos` | Hub-level repo metadata | |
| 59985bb | | | 282 | | `api_tokens` | Personal access tokens | |
| 3e3af55 | | | 283 | |
| 3e3af55 | | | 284 | --- |
| 3e3af55 | | | 285 | |
| 59985bb | | | 286 | ## Deployment |
| 3e3af55 | | | 287 | |
| 59985bb | | | 288 | ### Production (grove.host) |
| 3e3af55 | | | 289 | |
| 59985bb | | | 290 | ```bash |
| 59985bb | | | 291 | # On server at /opt/grove/ |
| 59985bb | | | 292 | docker compose up -d |
| 3e3af55 | | | 293 | ``` |
| 3e3af55 | | | 294 | |
| 59985bb | | | 295 | Services auto-restart. Caddy handles TLS. Canopy CI rebuilds and deploys on push. |
| 3e3af55 | | | 296 | |
| 59985bb | | | 297 | **Backups**: Daily at 4am UTC via `/opt/grove/backup.sh`. 7-day retention. |
| 3e3af55 | | | 298 | |
| 59985bb | | | 299 | **Firewall**: UFW enabled. SSH restricted to admin IP. Ports 80, 443, 8080, 8443 open. |
| 3e3af55 | | | 300 | |
| 59985bb | | | 301 | ### CI/CD Pipeline |
| 3e3af55 | | | 302 | |
| 59985bb | | | 303 | The Grove repo itself uses Canopy for CI/CD (self-hosting). Push to `main` triggers: |
| 59985bb | | | 304 | 1. Build Docker images for grove-api, grove-web, hub-api |
| 59985bb | | | 305 | 2. Push to local registry |
| 59985bb | | | 306 | 3. `docker compose pull && up -d` |
| 3e3af55 | | | 307 | |
| 3e3af55 | | | 308 | --- |
| 3e3af55 | | | 309 | |
| 3e3af55 | | | 310 | ## Tech Stack |
| 3e3af55 | | | 311 | |
| 3e3af55 | | | 312 | | Layer | Technology | Why | |
| 3e3af55 | | | 313 | |-------|-----------|-----| |
| 59985bb | | | 314 | | Source control | Mononoke (Rust) | Production-proven at Meta scale, Git-compatible | |
| 59985bb | | | 315 | | API servers | Fastify (Node.js/TypeScript) | Fast, typed, same ecosystem as ISL | |
| 59985bb | | | 316 | | Web UI | Next.js 15 + React 19 + Tailwind 4 | SSR, app router, rapid iteration | |
| 59985bb | | | 317 | | Database | SQLite | Zero-config, embedded, sufficient for current scale | |
| 59985bb | | | 318 | | Auth | WebAuthn passkeys + JWT | Passwordless, phishing-resistant | |
| 59985bb | | | 319 | | Reverse proxy | Caddy | Auto-TLS, simple config | |
| 59985bb | | | 320 | | CI/CD | Docker-based (Canopy) | Native, no external dependency | |
| 59985bb | | | 321 | | Extension | VS Code Extension API | Largest IDE market share | |
| 3e3af55 | | | 322 | |
| 3e3af55 | | | 323 | --- |
| 3e3af55 | | | 324 | |
| 3e3af55 | | | 325 | ## Key Design Decisions |
| 3e3af55 | | | 326 | |
| 59985bb | | | 327 | 1. **Monorepo-first**: One repo per user/org. Mononoke excels at this. |
| 59985bb | | | 328 | 2. **Git protocol for clients**: Standard `git clone`/`git push` works. No custom client needed. |
| 59985bb | | | 329 | 3. **Git-based API reads**: Uses bare Git clones + `git ls-tree`/`git show`/`git log` for file browsing. Avoids Mononoke CBOR protocol complexity. |
| 59985bb | | | 330 | 4. **Two-API split**: Hub API (identity) is separate from Grove API (repos). Hub issues JWTs; Grove verifies them. Designed for multi-instance deployments. |
| 59985bb | | | 331 | 5. **WebAuthn-only auth**: No passwords. Passkeys are phishing-resistant and simpler to implement securely. |
| 59985bb | | | 332 | 6. **Canopy CI/CD is native**: Docker-based pipeline execution, integrated into grove-api. No external CI needed. |
| 59985bb | | | 333 | 7. **ISL extracted, not forked**: ISL components are reusable. Full ISL integration is a future phase. |