3 Commits
Author SHA1 Message Date
Omar SobhandClaude Sonnet 4.6 3d504ee6b3 feat: add morpheus node config
Build with clawstor cache / Cargo build (clawstor-cached) (push) Failing after 25s
Tertiary dev node (100.123.224.84) — no ZFS, no dedicated NVMe.
Hot tier on root filesystem, snapshot/replicate timers not enabled.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-07-23 06:20:29 +00:00
Omar SobhandClaude Sonnet 4.6 8407c99ba4 fix(Makefile): install-dashboard copies from claw-store/static not dashboard/dist
vite.config.ts sets outDir to ../claw-store/static (relative to dashboard/),
so the built assets land in claw-store/static/, not the standard dashboard/dist/.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-07-23 06:20:29 +00:00
Omar SobhandClaude Sonnet 4.6 088b88b225 fix(cargo_init): preserve non-[build] sections on activate
strip_section now strips only the [build] block before rewriting it,
so existing [alias] tables and other custom sections survive repeated
activate calls. Adds strip_leading_marker to prevent the managed-marker
comment from accumulating on each re-activation.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-07-23 06:20:29 +00:00
3 changed files with 82 additions and 2 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ install-systemd:
install-dashboard: install-dashboard:
cd dashboard && npm ci && npm run build cd dashboard && npm ci && npm run build
install -dm755 $(INSTALL_STATIC) install -dm755 $(INSTALL_STATIC)
cp -r dashboard/dist/. $(INSTALL_STATIC)/ cp -r claw-store/static/. $(INSTALL_STATIC)/
@echo "Dashboard installed to $(INSTALL_STATIC)" @echo "Dashboard installed to $(INSTALL_STATIC)"
## Install node-specific config (NODE=architect|tank) ## Install node-specific config (NODE=architect|tank)
+62 -1
View File
@@ -21,8 +21,14 @@ pub fn write_cargo_config(warm_path: &Path, hot_target_path: &Path) -> Result<()
String::new() String::new()
}; };
// Drop any leading copies of our own marker comment before stripping the
// [build] section — it sits above the [build] header, outside the range
// strip_section tracks, so without this it would survive every
// regenerate cycle and duplicate one more time.
let existing = strip_leading_marker(&existing);
// Remove old [build] block (from its header to the next section or EOF). // Remove old [build] block (from its header to the next section or EOF).
let stripped = strip_section(&existing, "build"); let stripped = strip_section(existing, "build");
let new_content = format!( let new_content = format!(
"# claw-store managed — do not edit manually\n\ "# claw-store managed — do not edit manually\n\
@@ -37,6 +43,21 @@ pub fn write_cargo_config(warm_path: &Path, hot_target_path: &Path) -> Result<()
.with_context(|| format!("writing {}", config_path.display())) .with_context(|| format!("writing {}", config_path.display()))
} }
const MANAGED_MARKER: &str = "# claw-store managed — do not edit manually";
/// Strip leading copies of `MANAGED_MARKER`, one per line, from the start of `src`.
fn strip_leading_marker(src: &str) -> &str {
let mut rest = src;
while let Some(line_end) = rest.find('\n') {
if rest[..line_end].trim() == MANAGED_MARKER {
rest = &rest[line_end + 1..];
} else {
break;
}
}
rest
}
/// Remove a TOML section `[name]` and all its key=value lines from `src`, /// Remove a TOML section `[name]` and all its key=value lines from `src`,
/// stopping at the next `[section]` header or EOF. /// stopping at the next `[section]` header or EOF.
fn strip_section(src: &str, name: &str) -> String { fn strip_section(src: &str, name: &str) -> String {
@@ -102,6 +123,46 @@ mod tests {
assert!(verify_cargo_config(&warm, &hot).unwrap()); assert!(verify_cargo_config(&warm, &hot).unwrap());
} }
#[test]
fn test_write_cargo_config_idempotent_no_duplicate_marker() {
let dir = TempDir::new().unwrap();
let warm = dir.path().join("proj");
std::fs::create_dir_all(&warm).unwrap();
let hot: std::path::PathBuf = "/hot/targets/proj".into();
write_cargo_config(&warm, &hot).unwrap();
write_cargo_config(&warm, &hot).unwrap();
write_cargo_config(&warm, &hot).unwrap();
let content = std::fs::read_to_string(warm.join(".cargo/config.toml")).unwrap();
assert_eq!(content.matches("claw-store managed").count(), 1);
}
#[test]
fn test_write_cargo_config_heals_existing_duplicate_marker() {
let dir = TempDir::new().unwrap();
let warm = dir.path().join("proj");
let cargo_dir = warm.join(".cargo");
std::fs::create_dir_all(&cargo_dir).unwrap();
std::fs::write(
cargo_dir.join("config.toml"),
"# claw-store managed — do not edit manually\n\
[build]\n\
target-dir = \"/hot/targets/proj\"\n\
# claw-store managed — do not edit manually\n\
[env]\n\
FOO = \"bar\"\n",
)
.unwrap();
let hot: std::path::PathBuf = "/hot/targets/proj".into();
write_cargo_config(&warm, &hot).unwrap();
let content = std::fs::read_to_string(cargo_dir.join("config.toml")).unwrap();
assert_eq!(content.matches("claw-store managed").count(), 1);
assert!(content.contains("FOO = \"bar\""));
}
#[test] #[test]
fn test_verify_cargo_config_detects_missing() { fn test_verify_cargo_config_detects_missing() {
let dir = TempDir::new().unwrap(); let dir = TempDir::new().unwrap();
+19
View File
@@ -0,0 +1,19 @@
[node]
name = "morpheus"
role = "secondary"
[hot]
path = "/hot/targets"
max_gb = 80
stale_hours = 48
[warm]
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"
snapshot_retain_hours = 24
snapshot_retain_days = 7
snapshot_retain_weeks = 4
# No cold tier and no replication — morpheus is a standalone dev node.