Polish: comprehensive docs overhaul #91

Merged
osobh merged 1 commits from polish-docs-overhaul into main 2026-07-14 22:16:56 +00:00
4 changed files with 409 additions and 264 deletions
+15 -11
View File
@@ -160,18 +160,22 @@ Two Gitea Actions replace stock steps:
## Phase plan ## Phase plan
| Phase | Chunk | Weeks | All 8 phases substantially complete as of **2026-07-14**.
|---|---|---|
| 1 | Membership + LAN-first transport (chitchat + quinn) | 2 |
| 2 | Content-addressed blob store (BLAKE3 chunking, put/get) | 2 |
| 3 | Namespace + metadata + vector clocks + CRDTs | 3 |
| 4 | Client mode + WAL + reconnect + pin semantics | 3 |
| 5 | Cargo build-artifact cache integration | 3 |
| 6 | FUSE mount for git worktrees | 2 |
| 7 | Snapshot + repair + scrub + smart-clean + reference tracking | 3-4 |
| 8 | Fleet CA + Tailscale identity for roaming | 1 |
Roughly **18-20 weeks focused work**, ~5-7 months elapsed with other priorities. | Phase | Chunk | State |
|---|---|---|
| 1 | Membership (chitchat SWIM) + QUIC/mTLS transport + Fleet CA | ✅ shipped |
| 2 | Content-addressed blob store + streaming chunk RPC | ✅ shipped |
| 3 | Namespaced Lamport-stamped refs + tags (CRDT merge on put) | ✅ shipped |
| 4 | Pin semantics, TTL sidecars, offline WAL + drain + wal-status | ✅ shipped |
| 5 | claw-cargo build/prefetch/prewarm/pin/status/metrics + Prometheus | ✅ shipped |
| 6 | Read-only FUSE mount over blobs / snapshots / tags / refs | ✅ shipped |
| 7 | Scrub, peer-pull repair, snapshot CRUD + pin protection, ref-tracking + Gitea sweep, smart-clean | ✅ shipped |
| 8 | Fleet CA + Tailscale-aware sign + LAN-first probe + daemon dual-bind | ✅ shipped |
Deployed fleet-wide (tank + architect + morpheus) with daily/weekly
systemd timers (snapshot-rotate, ref-sweep, gc, scrub) driving
maintenance. See [README.md](README.md) for install + daily usage.
## Placement policy ## Placement policy
+336 -188
View File
@@ -1,266 +1,414 @@
# clawstor # clawstor
ZFS-backed fleet storage daemon for the clawverse two-node cluster (architect + tank). Manages a three-tier hot/warm/cold storage model with automatic GC, hourly/daily/weekly ZFS snapshots, incremental cold replication, and a live SSE dashboard. Rust-native distributed storage + fingerprint-keyed cargo build cache for
small trusted fleets. Content-addressed blobs over QUIC+mTLS, gossip
membership, CRDT-merged metadata, FUSE mount, and a claw-cargo wrapper that
turns any peer with the cache into a build accelerator.
**Source:** <https://git.redclaw.dev/clawverse/clawstor>
--- ---
## Architecture ## What you get
``` **Distributed content-addressed storage**
Hot tier /hot/targets/<org>/<repo>/ NVMe — active Cargo target dirs - BLAKE3-hashed blobs, chunk-level dedup (1 MiB chunks).
Warm tier /slab/projects/<org>/<repo> ZFS dataset — canonical git repos - Every peer-to-peer transfer QUIC 0-RTT + mutual TLS 1.3 against a fleet CA.
Cold tier /data/archive/ Nightly incremental ZFS snapshot send - LAN-first probe with tailnet fallback — roaming laptops keep working from anywhere.
``` - Same-fingerprint = same bytes anywhere in the fleet: never re-upload the same cache.
**Hot (NVMe):** `cargo build` target directories symlinked into the NVMe partition. Activated on demand; GC'd when stale or when the hot tier exceeds `max_gb`. **Fingerprint-keyed cargo cache**
- `claw-cargo build` = drop-in `cargo build` with a peer-cache lookup wrapper.
- Fingerprint = deterministic function of `Cargo.lock` + `Cargo.toml` + rustc release + profile + features.
- HIT: download + restore `target/` in seconds. MISS: build + capture + upload.
- Streaming chunk transfer with `--parallel-restore N` for cross-node WAN links.
**Warm (ZFS):** The working checkout of every project, on a ZFS dataset that receives hourly, daily, and weekly snapshots. This is the source of truth for all git history. **Human-friendly primitives**
- **Pins / tags** — `claw-cargo pin --name clawverse:main:latest-cache`. Human handle for a specific blob. Auto-annotates git ref via `--repo` + `--git-ref`.
- **Snapshots** — `cluster-snapshot-create --name release-2026-07`. Immutable named record of every blob live at capture time. Blobs referenced by any snapshot survive LRU eviction automatically.
- **Ref-tracking** — every cache-put records `(repo, git-ref)`. Nightly Gitea sweep finds fingerprints whose refs are gone → deletion candidates.
- **Read-only FUSE mount** — `~/clawstor-mount/{blobs,snapshots,tags,refs}/*` — any POSIX tool sees the whole store.
**Cold (ZFS send):** Tank sends incremental ZFS snapshots to architect nightly over the 10G fabric link (`10.10.0.9 = architect-fab-tank`). The first run is a full send; subsequent runs are incremental (`zfs send -i <prev>`). **Operations**
- **Scrub** — read-only BLAKE3 fsck (weekly).
- **Repair** — peer-pull missing/corrupt chunks with hash re-verify.
- **GC** — orphan-chunk sweep + pin-aware LRU eviction (nightly).
- **Snapshot rotation** — `daily-YYYY-MM-DD` create + 30-day retention (daily).
- All ordered via systemd user timers; log to journalctl.
**Roaming clients (Phase 8)**
- `fleet-ca-tailscale-sign` — mint a leaf cert whose SANs include this node's Tailscale MagicDNS + tailnet IPs. Zero-touch bootstrap for laptops.
- Daemon dual-binds on LAN + tailnet interfaces. Advertised via gossip.
- `--tailscale-addr` on peer-status / repair / ping CLIs enables fallback routing.
**Reliability shape**
- Write-ahead log (Phase 4): `pin --offline` queues mutations; `drain` replays on reconnect.
- CRDT-merged refs + tags (Lamport `(clock, node)` total order) — concurrent writes are conflict-free.
- Non-zero-exit on integrity failures — timers surface via systemd `failed` state.
--- ---
## Nodes ## Architecture (one paragraph)
| Node | Role | IP (LAN) | ZFS pool | Each node runs `claw-store daemon`. Daemons find each other via `chitchat`
|---|---|---|---| SWIM gossip. Content flows over `quinn` (QUIC/TLS 1.3) with mutual auth
| architect | primary | 10.0.0.13 | `slab` | against a fleet CA. Blobs are chunked into content-addressed pieces
| tank | secondary | 10.0.0.14 | `slab` | under `<blob_root>/chunks/<hh>/<hex>` and stitched via
`blobs/<hh>/<blob_id>.manifest.json`. Named handles (tags, refs, snapshots,
ref-tracking annotations) live in sibling directories. Everything under
`<blob_root>` is safely rsync'd — content addressing means restore-from-tar
never corrupts the store.
Architect holds the cold archive. Tank sends to architect. The 10G fabric IP (`10.10.0.9`) is used intentionally for replication bandwidth. Full design: [`ARCHITECTURE-v2.md`](ARCHITECTURE-v2.md).
---
## Fleet layout (reference)
Current production deployment — 3 nodes, all identical:
| Node | Zone | State |
|---|---|---|
| tank | fabric-10g | daemon + FUSE + 4 timers |
| architect | fabric-10g | daemon + FUSE + 4 timers |
| morpheus | lan-1g | daemon + FUSE + 4 timers |
Consistency snapshot: `deploy/scripts/fleet-status.sh`.
--- ---
## Install ## Install
### Prerequisites ### Prereqs (Linux)
- Rust toolchain (`rustup`) ```bash
- Node.js + npm (for dashboard build) # For the daemon + claw-cargo (release build)
- ZFS installed and pools imported rustup default stable
- SSH key from tank → architect configured (see [SSH setup](#ssh-setup))
### Build and install # For the FUSE mount only
sudo apt install libfuse3-dev pkg-config
```sh
# On architect
sudo make deploy NODE=architect
# On tank
sudo make deploy NODE=tank
``` ```
This runs `cargo build --release`, copies the binary to `/usr/local/bin/claw-store`, builds and installs the React dashboard to `/usr/share/claw-store/static`, installs systemd units, and enables all services and timers. ### Build
### Makefile targets ```bash
git clone https://git.redclaw.dev/clawverse/clawstor
cd clawstor
| Target | What it does | # Daemon + CLI wrapper — always available.
|---|---| cargo build --release --bin claw-store --bin claw-cargo
| `make build` | Compile release binary |
| `make install` | Install binary to `/usr/local/bin` |
| `make install-systemd` | Install and reload all systemd units |
| `make install-dashboard` | Build React dashboard and copy to `/usr/share/claw-store/static` |
| `make install-config NODE=architect` | Install node-specific config |
| `make deploy NODE=architect` | Full install + enable all units |
| `make uninstall` | Remove binary and systemd units (preserves config and data) |
| `make clean` | Remove build artifacts |
--- # FUSE mount — feature-gated.
cargo build --release --features fuse --bin claw-fuse
```
## Configuration Binaries land at `target/release/{claw-store,claw-cargo,claw-fuse}`.
Config files live in `config/`. Install the right one with `make install-config NODE=<node>` which copies it to `/etc/claw-store/config.toml`. ### First-time cluster bootstrap (once, on the primary)
### Key options ```bash
# Mint the fleet CA. Keep ca.key on this box only.
claw-store fleet-ca-init --dir /etc/claw-store/ca --cn "clawstor fleet CA"
# Sign a leaf for this node.
claw-store fleet-ca-sign \
--ca-dir /etc/claw-store/ca \
--node tank \
--out-dir /etc/claw-store/tls
# For a Tailscale-attached roaming node, use the tailscale-aware variant
# so the leaf's SANs include the MagicDNS name + tailnet IPs.
claw-store fleet-ca-tailscale-sign \
--ca-dir /etc/claw-store/ca \
--out-dir /etc/claw-store/tls
```
For any additional peer: repeat `fleet-ca-sign --node <name>` on the primary
and copy the three PEMs (`ca.crt`, `node.crt`, `node.key`) to that peer.
`node.key` is `chmod 600` — distribute via secure channel.
### Config template
`/etc/claw-store/config.toml`:
```toml ```toml
[node] [node]
name = "architect" name = "tank"
role = "primary" # primary | secondary role = "primary" # primary | secondary
[hot] [hot]
path = "/hot/targets" path = "/var/lib/claw-store/hot"
max_gb = 200 # LRU evicts unpinned projects above this max_gb = 100
stale_hours = 48 # GC projects inactive for this long
[warm] [cluster]
projects_path = "/slab/projects" zone = "fabric-10g" # any string; peers with the same zone are 'close'
zfs_dataset = "slab/projects" bind_lan = "10.0.0.14:7701" # gossip
snapshot_retain_hours = 24 bind_rpc_lan = "10.0.0.14:7702" # RPC
snapshot_retain_days = 7 bind_rpc_tailscale = "100.108.129.81:7702" # optional — enables roaming
snapshot_retain_weeks = 4 prom_bind = "0.0.0.0:7703"
blob_store_root = "/var/lib/claw-store/data"
[cold] # architect only [[cluster.peers]]
archive_path = "/data/archive" name = "architect"
zfs_dataset = "data/archive" zone = "fabric-10g"
retain_weeks = 12 lan_addr = "10.0.0.13:7701"
[replication] # tank only — sends to architect [[cluster.peers]]
send_to_host = "10.10.0.9" name = "morpheus"
send_to_user = "osobh" zone = "lan-1g"
cold_dataset_on_peer = "data/archive/tank-projects" lan_addr = "10.0.0.5:7701"
[peer] # optional — enables reachability probe [cluster.tls]
host = "10.0.0.13" ca_cert = "/etc/claw-store/tls/ca.crt"
user = "osobh" node_cert = "/etc/claw-store/tls/node.crt"
node_key = "/etc/claw-store/tls/node.key"
# Optional — require Bearer token on all HTTP POST endpoints
# api_token = "your-long-random-token-here"
``` ```
To enable API authentication: ### Systemd (Linux)
```toml User-scoped units live under `deploy/systemd/`. Install pattern:
api_token = "$(openssl rand -hex 32)"
```bash
mkdir -p ~/.config/systemd/user
cp deploy/systemd/*.service deploy/systemd/*.timer ~/.config/systemd/user/
systemctl --user daemon-reload
# Always-on: daemon + FUSE mount.
mkdir -p ~/clawstor-mount # required — the unit no longer mkdir's it
systemctl --user enable --now clawstor-cluster.service clawstor-fuse.service
# Scheduled ops (all four).
systemctl --user enable --now clawstor-snapshot-rotate.timer \
clawstor-ref-sweep.timer clawstor-gc.timer clawstor-scrub.timer
``` ```
All `GET` requests (dashboard, status, project list) are always allowed. `POST` requests (activate, deactivate, gc, sync, snapshot) require `Authorization: Bearer <token>`. Full details + drop-in override examples: [`deploy/systemd/README.md`](deploy/systemd/README.md).
### macOS (ghost, macbook)
macFUSE prereq (one-time, requires admin approval + reboot):
```bash
brew install --cask macfuse
brew install pkg-config
# Reboot, click through System Settings → Privacy & Security → Allow.
cargo build --release --features fuse --bin claw-fuse
```
launchd agent template: [`deploy/macos/claw-fuse.plist`](deploy/macos/claw-fuse.plist).
Full macOS notes: [`deploy/macos/README.md`](deploy/macos/README.md).
--- ---
## Systemd units ## Daily usage
| Unit | Runs | ### `claw-cargo build` (the killer app)
Wrap `cargo build` with a peer-cache lookup:
```bash
claw-cargo build \
--peer tank --peer-addr 10.0.0.14:7702 \
--tls-dir /etc/claw-store/tls \
--repo clawverse/clawstor --git-ref main \
--ref-tracking-dir /var/lib/claw-store/data \
--workspace .
```
Env-driven for CI (all above flags read `CLAWSTOR_*` env fallbacks):
```yaml
# .github/workflows/ci.yml
env:
CLAWSTOR_REPO: ${{ gitea.repository }}
CLAWSTOR_GIT_REF: ${{ gitea.ref_name }}
CLAWSTOR_DATA_DIR: /var/lib/claw-store/data
```
Detailed runner setup: [`docs/runner-integration.md`](docs/runner-integration.md).
### Named pins
```bash
# Pin the current fingerprint's cache blob under a human handle.
claw-cargo pin --name clawverse:main:latest-cache \
--peer tank --peer-addr 10.0.0.14:7702 --tls-dir /etc/claw-store/tls
# List every tag on the peer.
claw-cargo list-tags --peer tank ...
# Restore a specific tag into the local target/ without running cargo.
claw-cargo prefetch --pin clawverse:main:latest-cache --peer tank ...
# Unpin (leaves blob in place; GC reaps it when no refs remain).
claw-cargo unpin --name clawverse:main:latest-cache --peer tank ...
```
### Snapshots (immutable point-in-time records)
```bash
# Capture every blob currently on this node into a named snapshot.
claw-store cluster-snapshot-create --name pre-migration
# List everything.
claw-store cluster-snapshot-list
# See the referenced blob-ids.
claw-store cluster-snapshot-show --name pre-migration
# Delete (removes reference; blob data untouched — will vanish on next
# GC if no other pin holds it).
claw-store cluster-snapshot-delete --name pre-migration
```
Snapshots protect their blobs from LRU eviction automatically. Combined with
the daily rotation timer this gives free 30-day retention.
### Integrity + repair
```bash
# Read-only fsck: BLAKE3 verify every chunk against its manifest.
claw-store cluster-scrub
# If scrub finds corruption / missing chunks: peer-pull repair.
claw-store cluster-repair \
--peer architect --rpc-addr 10.0.0.13:7702 \
--tls-dir /etc/claw-store/tls [--dry-run]
```
### Fleet health at a glance
```bash
deploy/scripts/fleet-status.sh
```
One-line-per-node: daemon state, FUSE mount + layers, blob store size,
every timer's next-fire + last result.
### FUSE mount layout
Once `clawstor-fuse.service` is up:
```
~/clawstor-mount/
├── blobs/<blob-id-hex> # content by hash
├── refs/<fingerprint-hex> # content by cargo fingerprint
├── snapshots/<name>/<hex> # grouped by snapshot capture
└── tags/<sanitized-name> # content by human tag
```
Use with any POSIX tool:
```bash
tar -tvzf ~/clawstor-mount/tags/clawverse:main:latest-cache | head
sha256sum ~/clawstor-mount/blobs/*
find ~/clawstor-mount/snapshots -type f -newer marker
```
---
## CLI reference (summary)
| Command | What |
|---|---| |---|---|
| `claw-store.service` | Background daemon (GC, sync retry) | | `claw-store daemon` | Run the cluster daemon (gossip + RPC + Prometheus) |
| `claw-store-serve.service` | HTTP API + dashboard server (port 3030) | | `claw-store fleet-ca-init` | Mint a fresh fleet root CA |
| `claw-store-snapshot.service` + `.timer` | Hourly snapshot cycle | | `claw-store fleet-ca-sign` | Sign a per-node leaf cert |
| `claw-store-replicate.service` + `.timer` | Nightly cold replication (tank → architect) | | `claw-store fleet-ca-tailscale-sign` | Sign leaf with Tailscale SANs |
| `claw-store cluster-peer-status` | Show a peer's cluster view |
| `claw-store cluster-ping` | Round-trip a payload |
| `claw-store cluster-gc` | Orphan-chunk sweep + optional `--evict-to-gb` |
| `claw-store cluster-scrub [--verbose]` | Read-only integrity fsck |
| `claw-store cluster-repair --peer` | Pull missing/corrupt chunks from a peer |
| `claw-store cluster-snapshot-{create,list,show,delete}` | Snapshot lifecycle |
| `claw-store cluster-ref-sweep --gitea-url [--apply]` | Gitea live-refs → stale-fp report |
| `claw-cargo build` | Cache-lookup-wrapped cargo build |
| `claw-cargo prefetch` | Restore a cached target/ without cargo |
| `claw-cargo prewarm` | Cross-peer cache copy |
| `claw-cargo status` | Fingerprint + peer cache lookup, no build |
| `claw-cargo fingerprint` | Print fingerprint only (local, no network) |
| `claw-cargo pin / unpin / list-tags` | Named-tag lifecycle |
| `claw-cargo drain` | Replay offline WAL against a peer |
| `claw-cargo wal-status` | Show pending offline-WAL entries |
| `claw-cargo smart-clean [--mode {incremental-only,soft,hard}]` | Local target/ cleanup |
| `claw-fuse --data-dir <> --mount <>` | Read-only FUSE mount |
Check status: `claw-store <cmd> --help` for full flags.
```sh
systemctl status claw-store claw-store-serve
journalctl -u claw-store -f
```
--- ---
## CLI reference ## Prometheus metrics
```sh Each daemon exposes `/metrics` on its `prom_bind` port (7703 by convention).
claw-store activate <org/repo> # Link hot target dir, write .cargo/config.toml Sample metric families:
claw-store deactivate <org/repo> # Remove hot target dir, restore .cargo/config.toml
claw-store sync <org/repo> # git push, notify peer via SSH - `clawstor_cache_hits_total` / `_misses_total` — cache-op counters
claw-store gc # Evict stale + LRU hot targets - `clawstor_cache_hit_rate` — 0..1
claw-store snapshot # Run snapshot cycle now - `clawstor_bytes_served_total` / `_ingested_total` — traffic volumes
claw-store replicate # Run cold replication now (tank only) - `clawstor_hot_used_bytes` / `_hot_max_bytes` — tier utilization
claw-store status # Print node status - Peer discovery + gossip health via SWIM state
claw-store list # List all projects and their active state
claw-store serve # Start HTTP API + dashboard server Feed to your existing scrape config.
claw-store daemon # Start background daemon
claw-store restore <snap> <dest> # Clone snapshot into dest directory
claw-store pin <org/repo> # Mark project as pinned (survives all GC)
claw-store unpin <org/repo> # Remove pin
```
--- ---
## HTTP API ## Ongoing operations (systemd timers)
Base URL: `http://<node>:3030` Once installed, each node runs four timers on a per-day/week rhythm. All log
to `journalctl --user -u <unit>`.
| Method | Path | Description | | Timer | When | Purpose |
|---|---|---| |---|---|---|
| GET | `/api/status` | Node status (role, ZFS pool, uptime, hot usage) | | `clawstor-snapshot-rotate.timer` | daily 02:00 | Create `daily-YYYY-MM-DD` + prune `daily-*` > 30 days |
| GET | `/api/projects` | All projects with active/size/branch info | | `clawstor-ref-sweep.timer` | daily 03:15 | Query Gitea for live refs; report stale (or `--apply`) |
| GET | `/api/snapshots` | ZFS snapshot list with kind and timestamp | | `clawstor-gc.timer` | daily 03:30 | Orphan-chunk sweep. Add `--evict-to-gb N` via drop-in for size-cap fleets |
| GET | `/api/hot` | Hot tier entries with size and last-modified | | `clawstor-scrub.timer` | weekly Sun 04:00 | BLAKE3 verify every chunk. Non-zero exit = corruption |
| GET | `/api/sync-queue` | Pending sync retry jobs |
| GET | `/api/events` | SSE stream (status + projects every 5s) | Sequencing: snapshot → ref-sweep → gc → scrub so scrub reads a fresh
| POST | `/api/activate` | `{"project": "org/repo"}` | post-GC layout and eviction respects the fresh snapshot's pins.
| POST | `/api/deactivate` | `{"project": "org/repo"}` |
| POST | `/api/sync` | `{"project": "org/repo"}` |
| POST | `/api/gc` | Trigger GC pass |
| POST | `/api/snapshot` | Trigger snapshot cycle |
--- ---
## SSH setup ## Testing
Tank's replication and peer-probe commands SSH to architect. The daemon runs as root (via systemd), so root's key on tank must be authorized on architect. ```bash
cargo test --bins
```sh
# On tank — generate key if not present
sudo ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""
# Copy to architect
sudo ssh-copy-id [email protected]
# Verify
sudo ssh -o BatchMode=yes [email protected] true && echo "OK"
``` ```
The peer probe in `serve.rs` uses `BatchMode=yes` (no password prompts) with a 3-second connect timeout. Replication SSH calls use `ConnectTimeout=10` with server-alive checks to prevent indefinite hangs. **389 tests** across the blob store, cluster gossip, RPC transport, WAL
replay, streaming chunk protocol, snapshot store, ref-tracking, Gitea
adapter, Tailscale identity, and every CLI handler that isn't pure glue.
Known pre-existing macOS-only failure: `hot::tests::test_project_target_size_bytes`
(du -sh idiom drift on APFS). Non-blocking for Linux CI.
Full suite via CI on every push to `main`; runners are Gitea Actions with
the `clawstor-cache:host` label.
--- ---
## Snapshot schedule ## Design docs
Snapshots are taken by `claw-store-snapshot.timer` which fires hourly. - **[`ARCHITECTURE-v2.md`](ARCHITECTURE-v2.md)** — data tiers, transport, consistency, phase plan, placement policy, cleanup modes.
- **[`docs/runner-integration.md`](docs/runner-integration.md)** — wire `claw-cargo` into your Gitea Actions workflow.
Each run: - **[`deploy/systemd/README.md`](deploy/systemd/README.md)** — install every user unit + drop-in override recipes.
- Always takes an **hourly** snapshot; prunes to `snapshot_retain_hours` - **[`deploy/macos/README.md`](deploy/macos/README.md)** — macFUSE install + launchd agent.
- At midnight (`HHMM = 0000`): also takes a **daily** snapshot; prunes to `snapshot_retain_days`
- At Sunday midnight: also takes a **weekly** snapshot; prunes to `snapshot_retain_weeks`
Snapshot names: `<dataset>@<kind>-<YYYY-MM-DD-HHMM>` e.g. `slab/projects@hourly-2026-06-30-0400`.
--- ---
## Project pinning ## Contributing
Pinned projects survive all GC passes — both the stale-hours sweep and the LRU space-pressure eviction. If every unpinned project has been evicted and the hot tier is still over budget, the daemon logs a warning and stops rather than evict pinned projects. Standard workflow:
```sh 1. Fork or branch off `main`.
claw-store pin myorg/critical-service 2. `cargo test --bins` clean.
claw-store unpin myorg/critical-service 3. `cargo clippy --bins -- -D warnings` clean.
``` 4. Open a PR at <https://git.redclaw.dev/clawverse/clawstor/pulls>.
Pinned status is stored in the manifest (`/var/lib/claw-store/manifest.toml`). Every PR gets auto-merged after CI passes if it's flagged
`polish-*`/`phase-*`; other branches await review.
--- ## License
## Troubleshooting Internal to the clawverse fleet. Not currently open-source.
**Hot tier not shrinking after gc:**
Check `journalctl -u claw-store` for "over budget but every remaining project is pinned". If so, unpin a project or raise `max_gb`.
**Replication not running on tank:**
```sh
systemctl status claw-store-replicate.timer
journalctl -u claw-store-replicate -n 50
sudo claw-store replicate # run manually, check output
```
**Snapshot cycle missed:**
```sh
systemctl status claw-store-snapshot.timer
sudo claw-store snapshot
```
**Dashboard shows uptime 0:**
The serve process reads `/var/lib/claw-store/daemon-started`. If the daemon (`claw-store.service`) isn't running, uptime will show 0.
**Peer shown as unreachable:**
SSH key not installed, or the peer's `claw-store-serve.service` is down. Run the SSH verify command above.
**`activate` fails with "non-UTF-8 path":**
Project path contains non-UTF-8 bytes. All paths under `/slab/projects` should be ASCII.
---
## Development
```sh
# Run all tests
~/.cargo/bin/cargo test --manifest-path claw-store/Cargo.toml
# Lint
~/.cargo/bin/cargo clippy --manifest-path claw-store/Cargo.toml -- -D warnings
# Release build
~/.cargo/bin/cargo build --release --manifest-path claw-store/Cargo.toml
```
Tests cover: manifest atomicity, hot GC (stale + LRU + pinning), ZFS snapshot lifecycle, weekly snapshot scheduling, incremental replication logic, HTTP handler responses, auth middleware, project name validation, and sync queue.
+34 -65
View File
@@ -1,73 +1,42 @@
# React + TypeScript + Vite # clawstor dashboard
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. React + Vite frontend for the (legacy) `claw-store serve` HTTP API.
Displays node status, hot-tier usage, ZFS snapshots, and the sync
queue for the original ZFS-backed deployment.
Currently, two official plugins are available: **Note:** the primary observability path in the current distributed
architecture is Prometheus (`/metrics` on each node's `prom_bind`
port, default `:7703`) + `deploy/scripts/fleet-status.sh`. This
dashboard predates that and is not maintained as the operator's
main pane of glass. Kept in-tree because the underlying HTTP
endpoints on `claw-store serve` still function.
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) ## Build
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler ```bash
cd dashboard
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). npm install
npm run build
## Expanding the ESLint configuration # → dist/ ready to serve from claw-store serve --static-dir <path>
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
``` ```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: ## Dev
```js ```bash
// eslint.config.js npm run dev
import reactX from 'eslint-plugin-react-x' # Vite dev server + HMR against a running `claw-store serve` on :3030.
import reactDom from 'eslint-plugin-react-dom'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
``` ```
## What it shows
- `/api/status` — node role, uptime, hot usage, ZFS pool.
- `/api/projects` — active + branch + size per project.
- `/api/snapshots` — ZFS snapshot list.
- `/api/hot` — hot-tier entries.
- `/api/sync-queue` — pending retries.
- `/api/events` — SSE stream, updates every 5 s.
See the top-level [README](../README.md) for the current
architecture, which centers on the distributed content-addressed
blob store rather than the ZFS project mirror model this dashboard
was originally built for.
+24
View File
@@ -102,6 +102,30 @@ jobs:
Outputs available on the step: `cache-outcome` (HIT | MISS | Outputs available on the step: `cache-outcome` (HIT | MISS |
POPULATED | SKIPPED), `fingerprint`, `elapsed-seconds`. POPULATED | SKIPPED), `fingerprint`, `elapsed-seconds`.
### Ref-tracking (Phase 7f)
For the nightly `cluster-ref-sweep` timer to identify stale cache
entries, `claw-cargo build` needs to record which `(repo, git-ref)`
produced each fingerprint. Set these in the workflow env:
```yaml
jobs:
build:
runs-on: clawstor-cache
env:
CLAWSTOR_REPO: ${{ gitea.repository }}
CLAWSTOR_GIT_REF: ${{ gitea.ref_name }}
CLAWSTOR_DATA_DIR: /var/lib/claw-store/data
steps:
- uses: actions/checkout@v4
- uses: ./.gitea/actions/cargo-cache
```
`claw-cargo` reads these via clap `env=` fallbacks — no flag
plumbing needed. If unset, the build still works, but the
fingerprint won't gain a ref-tracking annotation and won't be
eligible for automatic stale-cache reaping.
## What to expect ## What to expect
- **First push on main** → cache MISS → the runner rebuilds cold, - **First push on main** → cache MISS → the runner rebuilds cold,