Watch
0
0
Fork
You've already forked hyperhive
0

overlay config .git only when the source has one; record a failed start in the op output

This commit is contained in:
atlas 2026-11-01 12:17:31 +01:00 • committed by mara
commit 23b0c4cd55

View file

@ -345,12 +345,22 @@ fn validate_bind_path(path: &str) -> Result<()> {
/// flags so nspawn mounts them on top of the already-mounted trees.
/// Config mounts are matched by shape, not by a name list: the set is
/// dynamic, growing with each child bound into a parent.
///
/// Only a bind whose host source has a `.git` entry gets the overlay: nspawn
/// must create the mount point inside the read-only bind, which fails
/// (`EROFS`) where there is nothing to mount over. An archetype agent's
/// config is an empty directory. The entry is looked up without following a
/// symlink, and a symlink named `.git` counts as present, so a path that
/// leads somewhere else is still hidden.
fn git_overlay_flags(binds: &[BindMount]) -> Vec<String> {
binds
.iter()
.map(|b| b.container_path.as_str())
.filter(|p| *p == "/knowledge" || (p.starts_with("/agents/") && p.ends_with("/config")))
.map(|p| format!("--tmpfs={p}/.git"))
.filter(|b| {
let p = b.container_path.as_str();
p == "/knowledge" || (p.starts_with("/agents/") && p.ends_with("/config"))
})
.filter(|b| std::fs::symlink_metadata(Path::new(&b.host_path).join(".git")).is_ok())
.map(|b| format!("--tmpfs={}/.git", b.container_path))
.collect()
}
@ -641,6 +651,20 @@ mod tests {
}
}
/// A source dir holding a `.git` directory, as a repo checkout does.
fn repo_source() -> String {
let dir = scratch();
std::fs::create_dir(dir.join(".git")).unwrap();
dir.to_string_lossy().into_owned()
}
fn bind_from(host_path: &str, container_path: &str) -> BindMount {
BindMount {
host_path: host_path.to_owned(),
..bind(container_path)
}
}
/// Every bound git repo gets its `.git` overlaid — the knowledge tree
/// and *each* config mount, an agent's own plus every child's.
///
@ -649,10 +673,11 @@ mod tests {
/// silently stop covering new ones.
#[test]
fn every_bound_git_repo_gets_its_dot_git_hidden() {
let src = repo_source();
let flags = git_overlay_flags(&[
bind("/knowledge"),
bind("/agents/atlas/config"),
bind("/agents/kiddo/config"),
bind_from(&src, "/knowledge"),
bind_from(&src, "/agents/atlas/config"),
bind_from(&src, "/agents/kiddo/config"),
]);
assert_eq!(
flags,
@ -669,15 +694,41 @@ mod tests {
/// keeps working clones of its own.
#[test]
fn non_repo_mounts_are_left_alone() {
let src = repo_source();
let flags = git_overlay_flags(&[
bind("/agents/atlas/state"),
bind("/work"),
bind("/applied"),
bind("/agents/atlas/config-notes"),
bind_from(&src, "/agents/atlas/state"),
bind_from(&src, "/work"),
bind_from(&src, "/applied"),
bind_from(&src, "/agents/atlas/config-notes"),
]);
assert!(flags.is_empty(), "overlaid a non-repo mount: {flags:?}");
}
/// A config mount whose source has no `.git` (an archetype agent's empty
/// directory) gets no overlay: nspawn cannot create the mount point in a
/// read-only bind. The sibling with a `.git` is the presence control.
#[test]
fn a_source_without_dot_git_gets_no_overlay() {
let empty = scratch().to_string_lossy().into_owned();
let flags = git_overlay_flags(&[
bind_from(&empty, "/agents/updo/config"),
bind_from(&empty, "/knowledge"),
bind_from(&repo_source(), "/agents/atlas/config"),
]);
assert_eq!(flags, ["--tmpfs=/agents/atlas/config/.git"]);
}
/// A symlink named `.git` is not followed: it counts as present, even
/// dangling, so the path it leads to is hidden rather than skipped.
#[test]
fn a_symlinked_dot_git_is_not_followed() {
let dir = scratch();
std::os::unix::fs::symlink("/nonexistent-target", dir.join(".git")).unwrap();
let src = dir.to_string_lossy().into_owned();
let flags = git_overlay_flags(&[bind_from(&src, "/agents/atlas/config")]);
assert_eq!(flags, ["--tmpfs=/agents/atlas/config/.git"]);
}
/// The whole rendered body, pinned literally — including the values,
/// so changing the restart policy is a visible test edit rather than a
/// silent one.