Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
334cd068d2 | ||
|
|
f30ea04ab6 | ||
|
|
bb24c77676 | ||
|
|
75d822d0f2 | ||
|
|
2f7eabf034 | ||
|
|
7902c2e395 | ||
|
|
dd2b90872a | ||
|
|
5c9bc7eb9c | ||
|
|
5c1d962bf2 | ||
|
|
a3fe1d147c |
+10
-10
@@ -7,7 +7,7 @@ use crate::manifest::Manifest;
|
|||||||
use crate::snapshot;
|
use crate::snapshot;
|
||||||
use crate::sync::{SyncQueue, drain_sync_queue};
|
use crate::sync::{SyncQueue, drain_sync_queue};
|
||||||
use crate::zfs::SystemZfs;
|
use crate::zfs::SystemZfs;
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
|
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
|
||||||
use tokio::time::{interval, Duration};
|
use tokio::time::{interval, Duration};
|
||||||
@@ -36,7 +36,14 @@ pub async fn run(cfg: Config, mut manifest: Manifest) -> Result<()> {
|
|||||||
let hot_dir = cfg.hot.path.clone();
|
let hot_dir = cfg.hot.path.clone();
|
||||||
let hot_max_bytes = cfg.hot.max_gb.saturating_mul(1024 * 1024 * 1024);
|
let hot_max_bytes = cfg.hot.max_gb.saturating_mul(1024 * 1024 * 1024);
|
||||||
let blob_root = cluster_cfg.blob_store_root.clone();
|
let blob_root = cluster_cfg.blob_store_root.clone();
|
||||||
match ClusterServices::start(
|
// Fail fast rather than degrade silently: a bind failure here is
|
||||||
|
// almost always a boot-time race against DHCP/network-online
|
||||||
|
// (the bind address isn't assigned to the interface yet). The
|
||||||
|
// systemd unit has `Restart=on-failure`; exiting lets it retry
|
||||||
|
// a few seconds later once the network is actually up, instead
|
||||||
|
// of leaving the daemon running indefinitely with no gossip,
|
||||||
|
// RPC, or Prometheus endpoint and no visible failure state.
|
||||||
|
let svc = ClusterServices::start(
|
||||||
cluster_cfg,
|
cluster_cfg,
|
||||||
cfg.node.name.clone(),
|
cfg.node.name.clone(),
|
||||||
hot_dir,
|
hot_dir,
|
||||||
@@ -44,8 +51,7 @@ pub async fn run(cfg: Config, mut manifest: Manifest) -> Result<()> {
|
|||||||
blob_root,
|
blob_root,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
.context("starting cluster services")?;
|
||||||
Ok(svc) => {
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
rpc_enabled = svc.rpc_enabled(),
|
rpc_enabled = svc.rpc_enabled(),
|
||||||
blob_store_enabled = svc.blob_store_enabled(),
|
blob_store_enabled = svc.blob_store_enabled(),
|
||||||
@@ -54,12 +60,6 @@ pub async fn run(cfg: Config, mut manifest: Manifest) -> Result<()> {
|
|||||||
);
|
);
|
||||||
Some(svc)
|
Some(svc)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(error = %e, "cluster services failed to start; continuing without cluster");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
tracing::info!("no [cluster] section in config; running standalone");
|
tracing::info!("no [cluster] section in config; running standalone");
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -91,9 +91,30 @@ impl V2State {
|
|||||||
.join("aggregator-sessions.json");
|
.join("aggregator-sessions.json");
|
||||||
let sessions = SessionStore::load(sessions_path)
|
let sessions = SessionStore::load(sessions_path)
|
||||||
.map_err(|e| anyhow::anyhow!("loading session store: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("loading session store: {e}"))?;
|
||||||
|
// Bug fix 2026-07-31: the fleet view previously never included
|
||||||
|
// the node actually serving the dashboard — `cluster.peers` is
|
||||||
|
// by definition every *other* node, so hitting a given node's
|
||||||
|
// `/api/v2/fleet` directly silently dropped that node from its
|
||||||
|
// own view (looked like "node X is missing" from the UI, even
|
||||||
|
// though X was perfectly healthy — it just never queried
|
||||||
|
// itself). Fix: synthesize a self `PeerEntry` from our own
|
||||||
|
// gossip bind address and include it in the fan-out list, same
|
||||||
|
// as any other peer. `peer_rpc_addr` derives the RPC port from
|
||||||
|
// `lan_addr`/`tailscale_addr` via the fleet's +1 convention, so
|
||||||
|
// this resolves to the same `bind_rpc_lan`/`bind_rpc_tailscale`
|
||||||
|
// the daemon actually listens on.
|
||||||
|
let self_peer = PeerEntry {
|
||||||
|
name: cfg.node.name.clone(),
|
||||||
|
zone: cluster.zone.clone(),
|
||||||
|
lan_addr: cluster.bind_lan,
|
||||||
|
tailscale_addr: cluster.bind_tailscale,
|
||||||
|
};
|
||||||
|
let mut peers = cluster.peers.clone();
|
||||||
|
peers.push(self_peer);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
aggregator_name: cfg.node.name.clone(),
|
aggregator_name: cfg.node.name.clone(),
|
||||||
peers: cluster.peers.clone(),
|
peers,
|
||||||
client: std::sync::Arc::new(client),
|
client: std::sync::Arc::new(client),
|
||||||
default_rpc_port_offset: 1,
|
default_rpc_port_offset: 1,
|
||||||
api_token: cfg.api_token.clone(),
|
api_token: cfg.api_token.clone(),
|
||||||
|
|||||||
@@ -19,6 +19,33 @@ archive_path = "/data/archive"
|
|||||||
zfs_dataset = "data/archive"
|
zfs_dataset = "data/archive"
|
||||||
retain_weeks = 12
|
retain_weeks = 12
|
||||||
|
|
||||||
|
[cluster]
|
||||||
|
zone = "fabric-10g"
|
||||||
|
bind_lan = "10.0.0.13:7701"
|
||||||
|
prom_bind = "0.0.0.0:7703"
|
||||||
|
bind_rpc_lan = "10.0.0.13:7702"
|
||||||
|
bind_rpc_tailscale = "100.104.171.32:7702"
|
||||||
|
blob_store_root = "/home/osobh/clawstor-deploy/data"
|
||||||
|
|
||||||
|
[[cluster.peers]]
|
||||||
|
name = "tank"
|
||||||
|
zone = "fabric-10g"
|
||||||
|
lan_addr = "10.0.0.14:7701"
|
||||||
|
rpc_lan_addr = "10.10.0.10:7702"
|
||||||
|
tailscale_addr = "100.108.129.81:7702"
|
||||||
|
|
||||||
|
[[cluster.peers]]
|
||||||
|
name = "morpheus"
|
||||||
|
zone = "lan-1g"
|
||||||
|
lan_addr = "10.0.0.5:7701"
|
||||||
|
rpc_lan_addr = "10.0.0.5:7702"
|
||||||
|
tailscale_addr = "100.123.224.84:7702"
|
||||||
|
|
||||||
|
[cluster.tls]
|
||||||
|
ca_cert = "/home/osobh/clawstor-deploy/tls/ca.crt"
|
||||||
|
node_cert = "/home/osobh/clawstor-deploy/tls/node.crt"
|
||||||
|
node_key = "/home/osobh/clawstor-deploy/tls/node.key"
|
||||||
|
|
||||||
[replication]
|
[replication]
|
||||||
receive_from_peer = true
|
receive_from_peer = true
|
||||||
peer_user = "osobh"
|
peer_user = "osobh"
|
||||||
|
|||||||
+27
-3
@@ -9,11 +9,35 @@ stale_hours = 48
|
|||||||
|
|
||||||
[warm]
|
[warm]
|
||||||
projects_path = "/slab/projects"
|
projects_path = "/slab/projects"
|
||||||
# No ZFS on morpheus — zfs_dataset is set so the config parses, but snapshot
|
|
||||||
# timers are not enabled and snapshot commands will log errors and continue.
|
|
||||||
zfs_dataset = "none"
|
zfs_dataset = "none"
|
||||||
snapshot_retain_hours = 24
|
snapshot_retain_hours = 24
|
||||||
snapshot_retain_days = 7
|
snapshot_retain_days = 7
|
||||||
snapshot_retain_weeks = 4
|
snapshot_retain_weeks = 4
|
||||||
|
|
||||||
# No cold tier and no replication — morpheus is a standalone dev node.
|
[cluster]
|
||||||
|
zone = "lan-1g"
|
||||||
|
# Morpheus has no direct 10G to Architect/Tank — use main LAN for all traffic
|
||||||
|
bind_lan = "10.0.0.5:7701"
|
||||||
|
prom_bind = "0.0.0.0:7703"
|
||||||
|
bind_rpc_lan = "10.0.0.5:7702"
|
||||||
|
bind_rpc_tailscale = "100.123.224.84:7702"
|
||||||
|
blob_store_root = "/home/osobh/clawstor-deploy/data"
|
||||||
|
|
||||||
|
[[cluster.peers]]
|
||||||
|
name = "architect"
|
||||||
|
zone = "fabric-10g"
|
||||||
|
lan_addr = "10.0.0.13:7701"
|
||||||
|
rpc_lan_addr = "10.0.0.13:7702"
|
||||||
|
tailscale_addr = "100.104.171.32:7702"
|
||||||
|
|
||||||
|
[[cluster.peers]]
|
||||||
|
name = "tank"
|
||||||
|
zone = "fabric-10g"
|
||||||
|
lan_addr = "10.0.0.14:7701"
|
||||||
|
rpc_lan_addr = "10.0.0.14:7702"
|
||||||
|
tailscale_addr = "100.108.129.81:7702"
|
||||||
|
|
||||||
|
[cluster.tls]
|
||||||
|
ca_cert = "/home/osobh/clawstor-deploy/tls/ca.crt"
|
||||||
|
node_cert = "/home/osobh/clawstor-deploy/tls/node.crt"
|
||||||
|
node_key = "/home/osobh/clawstor-deploy/tls/node.key"
|
||||||
|
|||||||
+27
-5
@@ -14,13 +14,35 @@ snapshot_retain_hours = 24
|
|||||||
snapshot_retain_days = 7
|
snapshot_retain_days = 7
|
||||||
snapshot_retain_weeks = 4
|
snapshot_retain_weeks = 4
|
||||||
|
|
||||||
|
[cluster]
|
||||||
|
zone = "fabric-10g"
|
||||||
|
bind_lan = "10.0.0.14:7701"
|
||||||
|
prom_bind = "0.0.0.0:7703"
|
||||||
|
bind_rpc_lan = "10.0.0.14:7702"
|
||||||
|
bind_rpc_tailscale = "100.108.129.81:7702"
|
||||||
|
blob_store_root = "/home/osobh/clawstor-deploy/data"
|
||||||
|
|
||||||
|
[[cluster.peers]]
|
||||||
|
name = "architect"
|
||||||
|
zone = "fabric-10g"
|
||||||
|
lan_addr = "10.0.0.13:7701"
|
||||||
|
rpc_lan_addr = "10.10.0.9:7702"
|
||||||
|
tailscale_addr = "100.104.171.32:7702"
|
||||||
|
|
||||||
|
[[cluster.peers]]
|
||||||
|
name = "morpheus"
|
||||||
|
zone = "lan-1g"
|
||||||
|
lan_addr = "10.0.0.5:7701"
|
||||||
|
rpc_lan_addr = "10.0.0.5:7702"
|
||||||
|
tailscale_addr = "100.123.224.84:7702"
|
||||||
|
|
||||||
|
[cluster.tls]
|
||||||
|
ca_cert = "/home/osobh/clawstor-deploy/tls/ca.crt"
|
||||||
|
node_cert = "/home/osobh/clawstor-deploy/tls/node.crt"
|
||||||
|
node_key = "/home/osobh/clawstor-deploy/tls/node.key"
|
||||||
|
|
||||||
[replication]
|
[replication]
|
||||||
# 10.10.0.9 is architect-fab-tank — the dedicated 10G fabric link between
|
|
||||||
# the two nodes. Intentionally used for replication to maximise bandwidth;
|
|
||||||
# architect's primary LAN address is 10.0.0.13.
|
|
||||||
send_to_host = "10.10.0.9"
|
send_to_host = "10.10.0.9"
|
||||||
send_to_user = "osobh"
|
send_to_user = "osobh"
|
||||||
cold_dataset_on_peer = "data/archive/tank-projects"
|
cold_dataset_on_peer = "data/archive/tank-projects"
|
||||||
# nightly_at is reserved for future use; replication schedule is currently
|
|
||||||
# controlled by the claw-store-replicate.timer systemd unit.
|
|
||||||
nightly_at = "03:30"
|
nightly_at = "03:30"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"root":["./src/app.tsx","./src/main.tsx","./src/components/nodecard.tsx","./src/components/projectspanel.tsx","./src/components/stattile.tsx","./src/components/storagebar.tsx","./src/lib/api.ts","./src/pages/commandcenter.tsx","./src/pages/nodedetail.tsx","./src/pages/reftrackingpage.tsx","./src/pages/storagebrowser.tsx"],"version":"6.0.3"}
|
{"root":["./src/App.tsx","./src/main.tsx","./src/components/NodeCard.tsx","./src/components/ProjectsPanel.tsx","./src/components/StatTile.tsx","./src/components/StorageBar.tsx","./src/lib/api.ts","./src/pages/CommandCenter.tsx","./src/pages/NodeDetail.tsx","./src/pages/RefTrackingPage.tsx","./src/pages/StorageBrowser.tsx"],"version":"6.0.3"}
|
||||||
@@ -6,13 +6,15 @@ import react from '@vitejs/plugin-react';
|
|||||||
// During local dev the daemon proxies /api/v2/* on :7700 so
|
// During local dev the daemon proxies /api/v2/* on :7700 so
|
||||||
// `vite dev` on :5173 can hit it via server.proxy.
|
// `vite dev` on :5173 can hit it via server.proxy.
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// Absolute base tied to the deploy path. Prior try was `./`
|
// Absolute base matching the backend's actual mount point
|
||||||
// (fully relative) which broke when the user hit `/clawstor`
|
// (`serve.rs` nests the v2 static dir at `/v2` via
|
||||||
// without trailing slash — browser resolves `./assets/…`
|
// `nest_service("/v2", …)`). A prior `/clawstor/` base assumed a
|
||||||
// against `/clawstor` treated as a file, gives `/assets/…`,
|
// Tailscale Serve path mapping that was never actually configured
|
||||||
// 404 from Tailscale. Absolute `/clawstor/` sidesteps the
|
// on any node (checked `tailscale serve status` on tank +
|
||||||
// slash / no-slash ambiguity.
|
// architect: neither proxies a `/clawstor` path) — that base
|
||||||
base: '/clawstor/',
|
// silently broke direct `:7700/v2/` access, the only access
|
||||||
|
// pattern that's actually live.
|
||||||
|
base: '/v2/',
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=clawstor FUSE mount
|
||||||
|
After=claw-store.service
|
||||||
|
Requires=claw-store.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=osobh
|
||||||
|
Environment=PATH=/home/osobh/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
ExecStartPre=/bin/mkdir -p /home/osobh/clawstor-mount
|
||||||
|
ExecStart=/usr/local/bin/claw-fuse --data-dir /home/osobh/clawstor-deploy/data --mount /home/osobh/clawstor-mount
|
||||||
|
ExecStop=/bin/fusermount -u /home/osobh/clawstor-mount
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -11,7 +11,7 @@ Wants=network-online.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=osobh
|
User=osobh
|
||||||
ExecStart=/usr/local/bin/claw-store serve --port 7700 --static-dir /usr/share/claw-store/static
|
ExecStart=/usr/local/bin/claw-store serve --port 7700 --static-dir /usr/share/claw-store/static --v2-static-dir /usr/share/claw-store/v2
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=15
|
RestartSec=15
|
||||||
Environment=RUST_LOG=info
|
Environment=RUST_LOG=info
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=claw-store fleet storage daemon
|
Description=clawstor cluster daemon (gossip + QUIC blob store + ZFS snapshots)
|
||||||
After=zfs-mount.service network.target
|
After=network-online.target
|
||||||
Wants=zfs-mount.service
|
Wants=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=osobh
|
User=osobh
|
||||||
|
Environment=PATH=/home/osobh/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
Environment=RUST_LOG=info
|
||||||
ExecStart=/usr/local/bin/claw-store daemon
|
ExecStart=/usr/local/bin/claw-store daemon
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=30
|
RestartSec=15
|
||||||
Environment=RUST_LOG=info
|
TimeoutStopSec=60
|
||||||
|
ProtectSystem=strict
|
||||||
|
ReadWritePaths=/var/lib/claw-store /hot/targets /slab/projects /home/osobh/clawstor-deploy
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Reference in New Issue
Block a user