harness: write claude configs to systemd RuntimeDirectory + chown ~/.claude on activation (#658 fixup)
This commit is contained in:
parent
113e64e481
commit
b8647cf7dc
6 changed files with 52 additions and 24 deletions
|
|
@ -23,6 +23,22 @@ pub fn state_dir() -> PathBuf {
|
||||||
PathBuf::from(format!("/agents/{label}/state"))
|
PathBuf::from(format!("/agents/{label}/state"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Per-turn config dir for the regenerated claude-{mcp-config,settings,
|
||||||
|
/// system-prompt} files the harness drops before each turn. Set by
|
||||||
|
/// systemd via `RuntimeDirectory = "hive-config"` (#658 fixup): a
|
||||||
|
/// per-service runtime dir owned by the agent unix user, auto-cleared
|
||||||
|
/// on stop. Kept separate from `/run/hive` (the host-owned mcp.sock
|
||||||
|
/// bind) so the harness owns its own write surface and we don't have
|
||||||
|
/// to chown a bind-mounted dir. Overridable via `HYPERHIVE_CONFIG_DIR`
|
||||||
|
/// for dev / test scenarios.
|
||||||
|
#[must_use]
|
||||||
|
pub fn config_dir() -> PathBuf {
|
||||||
|
if let Some(p) = std::env::var_os("HYPERHIVE_CONFIG_DIR") {
|
||||||
|
return PathBuf::from(p);
|
||||||
|
}
|
||||||
|
PathBuf::from("/run/hive-config")
|
||||||
|
}
|
||||||
|
|
||||||
/// Claude credentials directory for the current agent. `$HOME/.claude`
|
/// Claude credentials directory for the current agent. `$HOME/.claude`
|
||||||
/// matches what the `claude` CLI reads at runtime — both binaries see
|
/// matches what the `claude` CLI reads at runtime — both binaries see
|
||||||
/// the same `$HOME` set by the per-service systemd `environment`
|
/// the same `$HOME` set by the per-service systemd `environment`
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,9 @@ fn parse_close_marker(line: &str) -> Option<&str> {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error if the system prompt file cannot be written.
|
/// Returns an error if the system prompt file cannot be written.
|
||||||
pub async fn write_system_prompt(socket: &Path, label: &str, flavor: Flavor) -> Result<PathBuf> {
|
pub async fn write_system_prompt(_socket: &Path, label: &str, flavor: Flavor) -> Result<PathBuf> {
|
||||||
let parent = socket.parent().unwrap_or_else(|| Path::new("/run/hive"));
|
let parent = crate::paths::config_dir();
|
||||||
tokio::fs::create_dir_all(parent).await.ok();
|
tokio::fs::create_dir_all(&parent).await.ok();
|
||||||
let pronouns = std::env::var("HIVE_OPERATOR_PRONOUNS").unwrap_or_else(|_| "she/her".to_owned());
|
let pronouns = std::env::var("HIVE_OPERATOR_PRONOUNS").unwrap_or_else(|_| "she/her".to_owned());
|
||||||
let template_path = hive_sh4re::assets::prompt_template();
|
let template_path = hive_sh4re::assets::prompt_template();
|
||||||
let template = tokio::fs::read_to_string(&template_path)
|
let template = tokio::fs::read_to_string(&template_path)
|
||||||
|
|
|
||||||
|
|
@ -136,8 +136,8 @@ impl TurnFiles {
|
||||||
///
|
///
|
||||||
/// Returns an error if the config file cannot be written.
|
/// Returns an error if the config file cannot be written.
|
||||||
pub async fn write_mcp_config(socket: &Path) -> Result<PathBuf> {
|
pub async fn write_mcp_config(socket: &Path) -> Result<PathBuf> {
|
||||||
let parent = socket.parent().unwrap_or_else(|| Path::new("/run/hive"));
|
let parent = crate::paths::config_dir();
|
||||||
tokio::fs::create_dir_all(parent).await.ok();
|
tokio::fs::create_dir_all(&parent).await.ok();
|
||||||
let path = parent.join("claude-mcp-config.json");
|
let path = parent.join("claude-mcp-config.json");
|
||||||
let exe = std::env::current_exe()
|
let exe = std::env::current_exe()
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -155,9 +155,9 @@ pub async fn write_mcp_config(socket: &Path) -> Result<PathBuf> {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error if the settings file cannot be written.
|
/// Returns an error if the settings file cannot be written.
|
||||||
pub async fn write_settings(socket: &Path) -> Result<PathBuf> {
|
pub async fn write_settings(_socket: &Path) -> Result<PathBuf> {
|
||||||
let parent = socket.parent().unwrap_or_else(|| Path::new("/run/hive"));
|
let parent = crate::paths::config_dir();
|
||||||
tokio::fs::create_dir_all(parent).await.ok();
|
tokio::fs::create_dir_all(&parent).await.ok();
|
||||||
let path = parent.join("claude-settings.json");
|
let path = parent.join("claude-settings.json");
|
||||||
// #555: source-of-truth is `$HIVE_ASSETS_DIR/prompts/claude-settings.json`;
|
// #555: source-of-truth is `$HIVE_ASSETS_DIR/prompts/claude-settings.json`;
|
||||||
// copy through the per-agent runtime dir so claude reads it from the
|
// copy through the per-agent runtime dir so claude reads it from the
|
||||||
|
|
|
||||||
|
|
@ -38,14 +38,14 @@ in
|
||||||
ExecStart = "${pkgs.hyperhive}/bin/hive-ag3nt serve";
|
ExecStart = "${pkgs.hyperhive}/bin/hive-ag3nt serve";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = 2;
|
RestartSec = 2;
|
||||||
# `/run/hive` is bind-mounted from the host root-owned 0755
|
# `/run/hive-config/` is a per-service runtime dir owned by
|
||||||
# (hive-c0re's `set_nspawn_flags`). Post-#658 the harness
|
# the agent user (`User=` below), auto-cleared by systemd on
|
||||||
# runs as the per-agent user and needs to drop mcp.sock +
|
# stop. The harness writes its regenerated
|
||||||
# claude-{mcp-config,settings,system-prompt} files there.
|
# claude-{mcp-config,settings,system-prompt} files there
|
||||||
# `+` runs ExecStartPre as root (before the User= drop) so
|
# (see `paths::config_dir`). Kept separate from `/run/hive`
|
||||||
# we can chown the bind onto the agent user every start —
|
# — that bind comes in root-owned from the host and holds
|
||||||
# robust against activation-script timing on first boot.
|
# hive-c0re's `mcp.sock` we only connect to (#658 fixup).
|
||||||
ExecStartPre = "+${pkgs.coreutils}/bin/chown -R ${userName}:${userName} /run/hive";
|
RuntimeDirectory = "hive-config";
|
||||||
# Run the harness as the per-agent user (#658). claude itself
|
# Run the harness as the per-agent user (#658). claude itself
|
||||||
# spawned by the harness then runs as that user too — drops
|
# spawned by the harness then runs as that user too — drops
|
||||||
# root inside the container while sudo (`NOPASSWD: ALL` by
|
# root inside the container while sudo (`NOPASSWD: ALL` by
|
||||||
|
|
|
||||||
|
|
@ -687,6 +687,18 @@ in
|
||||||
[ -d "$stateDir" ] || continue
|
[ -d "$stateDir" ] || continue
|
||||||
chown -hR "$userName:$userName" "$stateDir" 2>/dev/null || true
|
chown -hR "$userName:$userName" "$stateDir" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
|
# Same treatment for the bind-mounted `~/.claude/` dir. Pre-#658
|
||||||
|
# the harness ran as root and `claude` wrote `.credentials.json`
|
||||||
|
# there 0600 root:root; post-#658 the harness reads
|
||||||
|
# `~/.claude/` as the agent user to decide Online vs
|
||||||
|
# NeedsLogin (`login::has_session`), and the host-side bind
|
||||||
|
# source is still root-owned 0700 from those legacy writes.
|
||||||
|
# Chown recursively so the existing credentials are readable
|
||||||
|
# under the new identity instead of getting silently treated
|
||||||
|
# as "no session" and re-prompting login every boot.
|
||||||
|
if [ -d "$homeDir/.claude" ]; then
|
||||||
|
chown -hR "$userName:$userName" "$homeDir/.claude" 2>/dev/null || true
|
||||||
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Auto-inject the matrix MCP entry when matrix is enabled (#548
|
# Auto-inject the matrix MCP entry when matrix is enabled (#548
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,14 @@ in
|
||||||
ExecStart = "${pkgs.hyperhive}/bin/hive-m1nd serve";
|
ExecStart = "${pkgs.hyperhive}/bin/hive-m1nd serve";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = 2;
|
RestartSec = 2;
|
||||||
# `/run/hive` is bind-mounted from the host root-owned 0755
|
# `/run/hive-config/` is a per-service runtime dir owned by
|
||||||
# (hive-c0re's `set_nspawn_flags`). Post-#658 the harness
|
# the agent user (`User=` below), auto-cleared by systemd on
|
||||||
# runs as the per-agent user and needs to drop mcp.sock +
|
# stop. The harness writes its regenerated
|
||||||
# claude-{mcp-config,settings,system-prompt} files there.
|
# claude-{mcp-config,settings,system-prompt} files there
|
||||||
# `+` runs ExecStartPre as root (before the User= drop) so
|
# (see `paths::config_dir`). Kept separate from `/run/hive`
|
||||||
# we can chown the bind onto the agent user every start —
|
# — that bind comes in root-owned from the host and holds
|
||||||
# robust against activation-script timing on first boot.
|
# hive-c0re's `mcp.sock` we only connect to (#658 fixup).
|
||||||
ExecStartPre = "+${pkgs.coreutils}/bin/chown -R ${userName}:${userName} /run/hive";
|
RuntimeDirectory = "hive-config";
|
||||||
# Same drop-from-root as agent-base.nix (#658). Manager
|
# Same drop-from-root as agent-base.nix (#658). Manager
|
||||||
# interactions with the host (rebuild approvals, config
|
# interactions with the host (rebuild approvals, config
|
||||||
# writes) still happen via the dedicated unix sockets
|
# writes) still happen via the dedicated unix sockets
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue