hive-priv copies a stopped container's root to the agent's work dir and renames it aside before passing nspawn --ephemeral; the agent's nix state moves to the persistent profiles bind. Adds the agent-ephemeral-rootfs vm test and a module-eval case for the nix state redirect.
81 lines
3.5 KiB
Nix
81 lines
3.5 KiB
Nix
# `checks.module-eval-agent-user` — see ./lib.nix for the shared
|
|
# rationale (why this suite exists, naming convention, "evaluates
|
|
# not executes").
|
|
{
|
|
pkgs,
|
|
lib,
|
|
self,
|
|
nixosSystem,
|
|
}:
|
|
let
|
|
inherit
|
|
(import ./lib.nix {
|
|
inherit
|
|
pkgs
|
|
lib
|
|
self
|
|
nixosSystem
|
|
;
|
|
})
|
|
agentWith
|
|
runGroup
|
|
;
|
|
|
|
# A named agent, so the rendered script carries a name no default supplies.
|
|
migrate =
|
|
(agentWith { services.hyperhive.agent.user.name = "iris"; })
|
|
.system.activationScripts.hive-agent-user-migrate.text;
|
|
# The tmpfiles rules of the same named agent.
|
|
rules = (agentWith { services.hyperhive.agent.user.name = "iris"; }).systemd.tmpfiles.rules;
|
|
cases = [
|
|
{
|
|
# hive-c0re creates the host work dir as hive-core; this activation is
|
|
# the only thing that lets the agent write to it.
|
|
name = "the agent's activation hands its own work dir to the agent user";
|
|
ok =
|
|
lib.hasInfix ''if [ "$(stat -c %U "/agents/$userName/work")" != "$userName" ]; then'' migrate
|
|
&& lib.hasInfix ''heal_own_dir "/agents/$userName/work"'' migrate;
|
|
}
|
|
{
|
|
# The helper is the only recursive chown of these dirs; it must hand the
|
|
# tree to the agent user and log a failure rather than discard it.
|
|
name = "the agent's activation hands state, harness and .claude to the agent user, logging failures";
|
|
ok =
|
|
lib.hasInfix ''chown -hR "$userName:$userName" "$1" 2>&1'' migrate
|
|
&& lib.hasInfix "could not chown $1 to $userName" migrate
|
|
&& lib.hasInfix ''heal_own_dir "/agents/$userName/state"'' migrate
|
|
&& lib.hasInfix ''heal_own_dir "/agents/$userName/harness"'' migrate
|
|
&& lib.hasInfix ''heal_own_dir "$homeDir/.claude"'' migrate;
|
|
}
|
|
{
|
|
# hive-priv creates the host socket dir `0751 root` and never chowns it,
|
|
# so this activation is the only thing that lets the harness bind there.
|
|
name = "the agent's activation hands its socket dir to the agent user at 0751";
|
|
ok =
|
|
lib.hasInfix "userName=${lib.escapeShellArg "iris"}" migrate
|
|
&& lib.hasInfix ''socketDir="/run/hive-agent/$userName"'' migrate
|
|
&& lib.hasInfix ''chown -h "$userName:$userName" "$socketDir"'' migrate
|
|
&& lib.hasInfix ''chmod 0751 "$socketDir"'' migrate;
|
|
}
|
|
{
|
|
# `test -d` and `chmod` follow symlinks, so without this refusal ahead
|
|
# of the chown branch a link at the socket dir is chmodded at its target.
|
|
name = "the agent's activation refuses a symlinked or non-directory socket dir";
|
|
ok =
|
|
lib.hasInfix ''if [ -L "$socketDir" ] || { [ -e "$socketDir" ] && [ ! -d "$socketDir" ]; }; then'' migrate
|
|
&& lib.hasInfix ''elif [ -d "$socketDir" ]; then'' migrate;
|
|
}
|
|
{
|
|
# The container root is rebuilt at every start; nix keeps a non-root
|
|
# user's profile in the home, so these rules are the only thing that puts
|
|
# it in the persistent per-container profiles bind. The `d` and `L` rules
|
|
# are each other's control: a misspelled path in one still finds the other.
|
|
name = "the agent's tmpfiles rules keep nix's per-user state in the persistent profiles bind";
|
|
ok =
|
|
lib.elem "d /nix/var/nix/profiles/per-user/iris 0755 iris iris - -" rules
|
|
&& lib.elem "L /home/iris/.local/state/nix - - - - /nix/var/nix/profiles/per-user/iris" rules
|
|
&& lib.elem "L /home/iris/.nix-profile - - - - /home/iris/.local/state/nix/profiles/profile" rules;
|
|
}
|
|
];
|
|
in
|
|
runGroup "agent-user" cases
|