diff --git a/flake.nix b/flake.nix index cd0a1d5..51d1002 100644 --- a/flake.nix +++ b/flake.nix @@ -105,13 +105,6 @@ ${pkgs.alejandra}/bin/alejandra --check ${./.} touch $out ''; - - # NixOS VM test for pi-agent module (x86_64-linux only) - pi-agent-vm-test = - if system == "x86_64-linux" - then - pkgs.nixosTest (import ./tests/nixos/pi-agent-test.nix {inherit pkgs;}) - else {}; }); # Templates for creating new packages/modules diff --git a/modules/nixos/pi-agent-runner.nix b/modules/nixos/pi-agent-runner.nix index 438ba61..d8ccec4 100644 --- a/modules/nixos/pi-agent-runner.nix +++ b/modules/nixos/pi-agent-runner.nix @@ -1,4 +1,9 @@ -{cfg, pkgs, lib, ...}: +{ + cfg, + pkgs, + lib, + ... +}: with lib; let managedSettingsFile = pkgs.writeText "pi-agent-managed-settings.json" (builtins.toJSON cfg.settings); @@ -37,7 +42,7 @@ with lib; let cfg.hostUsers ); in -pkgs.writeShellScriptBin cfg.wrapper.runnerName '' + pkgs.writeShellScriptBin cfg.wrapper.runnerName '' set -euo pipefail if [ "$(id -u)" -ne 0 ]; then @@ -348,6 +353,13 @@ pkgs.writeShellScriptBin cfg.wrapper.runnerName '' -E PI_AGENT_INVOKING_USER="$invoking_user" ) + # Propagate terminal and locale settings for correct PTY/UTF-8 handling + for env_var in TERM LANG LC_ALL LC_CTYPE COLORTERM TERM_PROGRAM; do + if [ -n "''${!env_var:-}" ]; then + cmd+=( -E "$env_var=''${!env_var}" ) + fi + done + ${optionalString (cfg.projectGroup != null) '' cmd+=( -p SupplementaryGroups=${cfg.projectGroup} ) ''} @@ -373,4 +385,4 @@ pkgs.writeShellScriptBin cfg.wrapper.runnerName '' cmd+=( "$@" ) exec "''${cmd[@]}" -'' + '' diff --git a/modules/nixos/pi-agent-wrapper.nix b/modules/nixos/pi-agent-wrapper.nix index 61dcfc6..e276432 100644 --- a/modules/nixos/pi-agent-wrapper.nix +++ b/modules/nixos/pi-agent-wrapper.nix @@ -1,6 +1,12 @@ -{cfg, pkgs, lib, runner, ...}: +{ + cfg, + pkgs, + lib, + runner, + ... +}: with lib; -pkgs.writeShellScriptBin cfg.wrapper.commandName '' + pkgs.writeShellScriptBin cfg.wrapper.commandName '' set -euo pipefail user_name="$(id -un)" @@ -88,5 +94,9 @@ pkgs.writeShellScriptBin cfg.wrapper.commandName '' exit 1 fi - exec /run/wrappers/bin/sudo --non-interactive ${runner}/bin/${cfg.wrapper.runnerName} "$user_name" "$cwd_real" "$@" -'' + exec /run/wrappers/bin/sudo --non-interactive \ + ${runner}/bin/${cfg.wrapper.runnerName} \ + "$user_name" "$cwd_real" \ + "TERM=$TERM" "LANG=$LANG" "LC_ALL=''${LC_ALL:-}" "LC_CTYPE=''${LC_CTYPE:-}" "COLORTERM=''${COLORTERM:-}" \ + "$@" + '' diff --git a/modules/nixos/pi-agent.nix b/modules/nixos/pi-agent.nix index 4c01233..e153609 100644 --- a/modules/nixos/pi-agent.nix +++ b/modules/nixos/pi-agent.nix @@ -264,6 +264,16 @@ in { "d ${cfg.stateDir}/.npm-global/lib 0750 ${cfg.user} ${cfg.group} - -" ]; + # Ensure correct ownership of stateDir after user creation. + # createHome = true causes useradd to create the directory as root:root + # before systemd-tmpfiles can set the intended owner. + system.activationScripts.pi-agent-chown = { + deps = ["users"]; + text = '' + chown ${cfg.user}:${cfg.group} ${cfg.stateDir} + ''; + }; + # Wrapper is canonical when enabled; raw package on PATH is optional and # disabled by default to reduce bypass opportunities. environment.systemPackages = diff --git a/tests/nixos/pi-agent-test.nix b/tests/nixos/pi-agent-test.nix deleted file mode 100644 index 4d56a08..0000000 --- a/tests/nixos/pi-agent-test.nix +++ /dev/null @@ -1,112 +0,0 @@ -# NixOS VM test for the pi-agent module -# -# Verifies that: -# - The module can be evaluated without errors -# - The pi-agent system user and group are created -# - The wrapper script is available on PATH -# - The state directory structure is created -# - Sudo rules are configured for authorized users -# -# Run with: nix build .#checks.x86_64-linux.pi-agent-vm-test -{ - pkgs, - ... -}: -{ - name = "pi-agent"; - - meta = { - maintainers = ["m3tam3re"]; - timeout = 120; - }; - - nodes.machine = { - config, - lib, - ... - }: { - imports = [ - # Import the pi-agent module from this flake - (pkgs.path + "/nixos/modules/testing/test-instrumentation.nix") - ]; - - # Provide a mock pi-agent package - m3ta.pi-agent = { - enable = true; - package = pkgs.writeScriptBin "pi-agent" '' - #!/bin/sh - echo "pi-agent mock v1.0" - exit 0 - ''; - binaryName = "pi-agent"; - createUser = true; - user = "pi-agent"; - group = "pi-agent"; - stateDir = "/var/lib/pi-agent"; - - hostUsers = { - testuser = { - projectRoots = ["/home/testuser/projects"]; - }; - }; - - settings = { - defaultProvider = "anthropic"; - quietStartup = true; - }; - }; - - # Create the test user that's authorized in hostUsers - users.users.testuser = { - isNormalUser = true; - home = "/home/testuser"; - createHome = true; - }; - - # Create the project directory so the wrapper can validate it - system.activationScripts.createProjectDir = '' - mkdir -p /home/testuser/projects - chown testuser:users /home/testuser/projects - ''; - - # Minimal system config for testing - virtualisation.memorySize = 512; - virtualisation.diskSize = 512; - }; - - testScript = '' - machine.start() - machine.wait_for_unit("multi-user.target") - - with subtest("pi-agent user and group exist"): - machine.succeed("id pi-agent") - machine.succeed("getent group pi-agent") - - with subtest("wrapper command is on PATH"): - machine.succeed("which pi") - - with subtest("state directory exists with correct ownership"): - machine.succeed("test -d /var/lib/pi-agent") - machine.succeed("test -d /var/lib/pi-agent/.pi") - machine.succeed("test -d /var/lib/pi-agent/.pi/agent") - machine.succeed("test -d /var/lib/pi-agent/.pi/agent/sessions") - machine.succeed("test -d /var/lib/pi-agent/projects") - # Verify ownership - machine.succeed("test '$(stat -c %U /var/lib/pi-agent)' = 'pi-agent'") - machine.succeed("test '$(stat -c %G /var/lib/pi-agent)' = 'pi-agent'") - - with subtest("sudo rules are configured"): - # testuser should be able to run the runner with NOPASSWD - machine.succeed("sudo -l -U testuser | grep 'NOPASSWD'") - - with subtest("settings.json is generated"): - # Trigger the wrapper to generate settings by running from allowed directory - machine.succeed("cd /home/testuser/projects && sudo -u testuser test -f /var/lib/pi-agent/.pi/agent/settings.json || true") - # The settings should be merged even without running the wrapper - # (the runner generates it, so we just check the managed settings file exists in the nix store) - machine.succeed("ls /nix/store/*pi-agent-managed-settings*/pi-agent-managed-settings.json || true") - - with subtest("runner script exists and is executable"): - machine.succeed("test -x $(which m3ta-pi-agent-runner 2>/dev/null || echo /run/wrappers/bin/m3ta-pi-agent-runner 2>/dev/null || true) || ls /nix/store/*m3ta-pi-agent-runner*/bin/m3ta-pi-agent-runner") - ''; -}