Console errors on the live dashboard (architect:7700) turned out to
be two separate problems layered together:
1. The deployed static bundle wasn't built from this repo at all --
it called /api/v2/hot-refs, /api/v2/anomalies, and
/api/v2/node/*/metrics-history, none of which exist anywhere in
this codebase on any branch (checked via `git log --all -S`).
Someone built and shipped a frontend straight to
/usr/share/claw-store/v2 without ever committing the source.
2. Separately, this repo's own committed vite.config.ts had a latent
bug: `base: '/clawstor/'`, contradicting its own comment ("served
by claw-store serve under /v2/*") and the actual backend mount in
serve.rs (`nest_service("/v2", ...)`). Checked `tailscale serve
status` on tank + architect -- neither has ever proxied a
/clawstor path, so that base would have 404'd every asset the
moment anyone rebuilt and redeployed from source.
Fix: base = '/v2/', matching the real mount. Rebuilt and redeployed
to tank + architect (orphaned build backed up to
/usr/share/claw-store/v2.bak-orphaned on both).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
V2State::from_config built its peer fan-out list solely from
[[cluster.peers]], which by definition never includes the local
node. Result: hitting a given node's /api/v2/fleet directly always
omitted that node from its own fleet view, even when perfectly
healthy -- looked like "node X is missing" from the dashboard when
X just never queried itself.
Fix: synthesize a self PeerEntry from the node's own gossip bind
address and include it in the fan-out, same as any other peer.
peer_rpc_addr()'s existing +1 port convention resolves it to the
same bind_rpc_lan/bind_rpc_tailscale the daemon already listens on.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Bind failures at startup are almost always a boot-time race against
DHCP/network-online (bind address not yet assigned to the interface).
Previously the daemon caught the error and kept running in a degraded
state with no gossip, RPC, or Prometheus endpoint and no visible
failure signal. Now it propagates the error so the process exits and
systemd's Restart=on-failure retries once the network is actually up.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
serve_v2::peer_rpc_addr derives each peer's RPC socket as lan_addr.ip():port+1.
Architect and Tank were bound on their 10G fabric NICs (10.10.0.9/10.10.0.10)
but the peer entries only carry the main-LAN gossip address, causing the
aggregator to time out when probing Tank and present a stale cert to Morpheus.
Changing bind_rpc_lan to the main-LAN addresses (10.0.0.13/10.0.0.14) fixes both.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- All three node configs (architect/tank/morpheus) now include [cluster]
section with correct 10G fabric IPs, mTLS TLS paths, and blob store root.
Architect binds gossip on 10.0.0.13:7701 and RPC on 10.10.0.9:7702 (10G
to Tank); Morpheus uses LAN 10.0.0.5 (no direct 10G).
- claw-store.service: updated description, adds clawstor-deploy to
ReadWritePaths, removes NoNewPrivileges (needed for sudo zfs snapshot).
- claw-store-serve.service: adds --v2-static-dir /usr/share/claw-store/v2
so the aggregator serves dashboard-v2 ("clawstor · command center") at /v2/.
- claw-fuse.service: new unit, uses correct --data-dir + --mount flags.
Deployed to Architect, Tank, Morpheus. Fleet CA re-initialized; new certs
signed for all three nodes and distributed. Dashboard accessible at
http://100.104.171.32:7700/v2/ aggregating Tank + Morpheus via /api/v2/fleet.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Wire RpcRouter::with_repo_root at daemon startup in services.rs
using <blob_store_root>/repos. Nodes with no blob_store_root
still return NotConfigured (unchanged).
- Add end-to-end ensure→cached→release integration test that seeds
a bare git repo in a tempdir and exercises the real git-clone
path. Marked #[ignore] so CI runners without git skip silently;
runs green locally.
Layers HTTP over the R1a per-peer primitive so external callers
(clawmates, gitea runners, ops tooling) speak one URL to the
aggregator instead of dialing every peer.
Endpoints (require v2 auth, same middleware as tags/sessions):
POST /api/v2/repos/ensure {url, git_ref, workspace?}
POST /api/v2/repos/release {url, git_ref, workspace?}
Namespaced tokens are pinned to their own workspace (workspace omitted
in body → derived from token; explicit mismatch → 403). Admin/open
callers must supply workspace explicitly.
Reply shape mirrors the tag fan-out (FanoutReply) with per-peer
{peer, ok, path?, head_sha?, cached?, removed?, error?}. all_ok is
true iff every peer succeeded.
Also adds client wrappers call_repo_ensure/call_repo_release in
cluster/rpc/client.rs used by the aggregator's fan-out.
New per-peer RPCs to shallow-clone a (url, git_ref) under a caller-
provided workspace namespace, and to release the checkout. Fleet
fan-out via the aggregator ships separately in R1b.
- src/cluster/repo_ensure.rs: request/reply types + derive_path
(traversal-safe, blake3-hashed url segment), ensure_repo (cache
hit → rev-parse HEAD, else remove-and-reclone with 5-min timeout),
release_repo (idempotent rm)
- src/cluster/rpc.rs: Method::RepoEnsure=0x1e, RepoRelease=0x1f,
RpcRouter::with_repo_root builder, two dispatch arms returning
NotConfigured when repo_root is unset
- src/cluster.rs: pub mod repo_ensure
- src/actions.rs: fix pre-existing test-only Config init missing the
aggregator field (unblocks lib tests)
Tests: 6 unit tests covering path derivation determinism, ref/url
independence, traversal safety, and sanitizer edge cases. All pass.
2026-07-15 03:42:27 -07:00
10 changed files with 157 additions and 40 deletions
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.