chore: update .gitignore, remove tracked .pi-lens files, and sync pending changes #10
376
modules/nixos/pi-agent-runner.nix
Normal file
376
modules/nixos/pi-agent-runner.nix
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
{cfg, pkgs, lib, ...}:
|
||||||
|
with lib; let
|
||||||
|
managedSettingsFile = pkgs.writeText "pi-agent-managed-settings.json" (builtins.toJSON cfg.settings);
|
||||||
|
|
||||||
|
managedEnvFile =
|
||||||
|
pkgs.writeText "pi-agent-managed.env"
|
||||||
|
(concatStringsSep "\n" (mapAttrsToList (k: v: "${k}=${v}") cfg.environment));
|
||||||
|
|
||||||
|
runtimePath = concatStringsSep ":" (
|
||||||
|
[
|
||||||
|
"${cfg.package}/bin"
|
||||||
|
"${pkgs.nodejs}/bin"
|
||||||
|
"${pkgs.git}/bin"
|
||||||
|
"${pkgs.coreutils}/bin"
|
||||||
|
"${pkgs.findutils}/bin"
|
||||||
|
"${pkgs.gnugrep}/bin"
|
||||||
|
"${pkgs.gnused}/bin"
|
||||||
|
"${pkgs.util-linux}/bin"
|
||||||
|
"/run/current-system/sw/bin"
|
||||||
|
]
|
||||||
|
++ map (p: "${p}/bin") cfg.extraPackages
|
||||||
|
);
|
||||||
|
|
||||||
|
userPolicyCase = concatStringsSep "\n" (
|
||||||
|
mapAttrsToList (
|
||||||
|
user: userCfg: ''
|
||||||
|
${escapeShellArg user})
|
||||||
|
USER_CONFIG_PATH=${escapeShellArg (
|
||||||
|
if userCfg.configPath != null
|
||||||
|
then userCfg.configPath
|
||||||
|
else cfg.wrapper.hostConfigPath
|
||||||
|
)}
|
||||||
|
USER_ROOTS=(${concatStringsSep " " (map escapeShellArg userCfg.projectRoots)})
|
||||||
|
;;
|
||||||
|
''
|
||||||
|
)
|
||||||
|
cfg.hostUsers
|
||||||
|
);
|
||||||
|
in
|
||||||
|
pkgs.writeShellScriptBin cfg.wrapper.runnerName ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "${cfg.wrapper.runnerName} must run as root" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$#" -lt 2 ]; then
|
||||||
|
echo "Usage: ${cfg.wrapper.runnerName} <invoking-user> <cwd> [pi-args...]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
invoking_user="$1"
|
||||||
|
shift
|
||||||
|
cwd="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
resolve_user_policy() {
|
||||||
|
local user="$1"
|
||||||
|
USER_CONFIG_PATH=""
|
||||||
|
USER_ROOTS=()
|
||||||
|
case "$user" in
|
||||||
|
${userPolicyCase}
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! resolve_user_policy "$invoking_user"; then
|
||||||
|
echo "User '$invoking_user' is not allowed to use ${cfg.wrapper.commandName}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
user_home="$(eval echo "~$invoking_user")"
|
||||||
|
if [ -z "$user_home" ] || [ "$user_home" = "~$invoking_user" ]; then
|
||||||
|
echo "Unable to determine home directory for user '$invoking_user'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expand_home_path() {
|
||||||
|
local input="$1"
|
||||||
|
if [ "$input" = "~" ]; then
|
||||||
|
printf '%s\n' "$user_home"
|
||||||
|
elif ${pkgs.gnugrep}/bin/grep -q '^~/' <<<"$input"; then
|
||||||
|
printf '%s\n' "$user_home/''${input:2}"
|
||||||
|
elif ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$input"; then
|
||||||
|
printf '%s\n' "$input"
|
||||||
|
else
|
||||||
|
# Bare relative path → resolve from user's home
|
||||||
|
printf '%s\n' "$user_home/$input"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cwd_real="$(${pkgs.coreutils}/bin/realpath -m "$cwd")"
|
||||||
|
|
||||||
|
resolved_roots=()
|
||||||
|
skipped_roots=()
|
||||||
|
is_allowed_cwd=0
|
||||||
|
for configured_root in "''${USER_ROOTS[@]}"; do
|
||||||
|
expanded_root="$(expand_home_path "$configured_root")"
|
||||||
|
resolved_root="$(${pkgs.coreutils}/bin/realpath -m "$expanded_root")"
|
||||||
|
if [ ! -d "$resolved_root" ]; then
|
||||||
|
skipped_roots+=("$resolved_root")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
resolved_roots+=("$resolved_root")
|
||||||
|
case "$cwd_real/" in
|
||||||
|
"$resolved_root"/*)
|
||||||
|
is_allowed_cwd=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "''${#resolved_roots[@]}" -eq 0 ]; then
|
||||||
|
echo "Denied: no valid existing project roots are configured for user '$invoking_user'." >&2
|
||||||
|
if [ "''${#skipped_roots[@]}" -gt 0 ]; then
|
||||||
|
echo "Configured but missing roots:" >&2
|
||||||
|
for root in "''${skipped_roots[@]}"; do
|
||||||
|
echo " - $root" >&2
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$is_allowed_cwd" -ne 1 ]; then
|
||||||
|
echo "Denied: '$cwd_real' is outside allowed project roots for user '$invoking_user'." >&2
|
||||||
|
echo "Allowed roots:" >&2
|
||||||
|
for root in "''${resolved_roots[@]}"; do
|
||||||
|
echo " - $root" >&2
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/install -d -m 0750 -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} \
|
||||||
|
${escapeShellArg cfg.stateDir} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.pi"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.pi/agent"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.pi/agent/sessions"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.project-mounts"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/projects"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.npm"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.npm-global"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.npm-global/bin"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.npm-global/lib"}
|
||||||
|
|
||||||
|
config_source="$USER_CONFIG_PATH"
|
||||||
|
if ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$config_source"; then
|
||||||
|
source_dir="$config_source"
|
||||||
|
else
|
||||||
|
source_dir="$(expand_home_path "$config_source")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ "${
|
||||||
|
if cfg.wrapper.syncConfigFromHost
|
||||||
|
then "1"
|
||||||
|
else "0"
|
||||||
|
}" = "1" ] && [ -d "$source_dir" ]; then
|
||||||
|
${pkgs.rsync}/bin/rsync -a --delete \
|
||||||
|
--exclude='auth.json' \
|
||||||
|
--exclude='mcp-oauth' \
|
||||||
|
--exclude='sessions' \
|
||||||
|
--exclude='bin' \
|
||||||
|
--exclude='mcp-cache.json' \
|
||||||
|
"$source_dir/" ${escapeShellArg "${cfg.stateDir}/.pi/agent/"}
|
||||||
|
${pkgs.coreutils}/bin/chown -R ${escapeShellArg "${cfg.user}:${cfg.group}"} ${escapeShellArg "${cfg.stateDir}/.pi/agent"}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Merge host settings.json (if any) with Nix-managed settings.
|
||||||
|
# Precedence: host settings first, Nix-managed keys override recursively.
|
||||||
|
settings_target=${escapeShellArg "${cfg.stateDir}/.pi/agent/settings.json"}
|
||||||
|
${pkgs.python3}/bin/python3 - "$settings_target" ${escapeShellArg managedSettingsFile} <<'PY_PI_SETTINGS_MERGE'
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def load_obj(path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return data if isinstance(data, dict) else {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def deep_merge(base, override):
|
||||||
|
if isinstance(base, dict) and isinstance(override, dict):
|
||||||
|
out = dict(base)
|
||||||
|
for key, value in override.items():
|
||||||
|
out[key] = deep_merge(out.get(key), value)
|
||||||
|
return out
|
||||||
|
return override
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
target = sys.argv[1]
|
||||||
|
managed = sys.argv[2]
|
||||||
|
base_obj = load_obj(target)
|
||||||
|
managed_obj = load_obj(managed)
|
||||||
|
merged = deep_merge(base_obj, managed_obj)
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(target), exist_ok=True)
|
||||||
|
tmp = f"{target}.tmp"
|
||||||
|
with open(tmp, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(merged, f, indent=2, sort_keys=True)
|
||||||
|
f.write("\n")
|
||||||
|
os.replace(tmp, target)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
PY_PI_SETTINGS_MERGE
|
||||||
|
${pkgs.coreutils}/bin/chown ${escapeShellArg "${cfg.user}:${cfg.group}"} "$settings_target"
|
||||||
|
${pkgs.coreutils}/bin/chmod 0640 "$settings_target"
|
||||||
|
|
||||||
|
# Merge environment into isolated .env with precedence:
|
||||||
|
# 1) synced host env (source_dir/.env)
|
||||||
|
# 2) Nix-managed environment attrset
|
||||||
|
# 3) Nix-managed environmentFiles (appended in declaration order)
|
||||||
|
env_target=${escapeShellArg "${cfg.stateDir}/.pi/.env"}
|
||||||
|
${pkgs.coreutils}/bin/install -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} -m 0640 /dev/null "$env_target"
|
||||||
|
|
||||||
|
if [ -f "$source_dir/.env" ]; then
|
||||||
|
${pkgs.coreutils}/bin/cat "$source_dir/.env" >> "$env_target"
|
||||||
|
printf '\n' >> "$env_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f ${escapeShellArg managedEnvFile} ]; then
|
||||||
|
${pkgs.coreutils}/bin/cat ${escapeShellArg managedEnvFile} >> "$env_target"
|
||||||
|
printf '\n' >> "$env_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
${concatStringsSep "\n" (map (f: ''
|
||||||
|
if [ -f ${escapeShellArg f} ]; then
|
||||||
|
${pkgs.coreutils}/bin/cat ${escapeShellArg f} >> "$env_target"
|
||||||
|
printf '\n' >> "$env_target"
|
||||||
|
fi
|
||||||
|
'')
|
||||||
|
cfg.environmentFiles)}
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/chown ${escapeShellArg "${cfg.user}:${cfg.group}"} "$env_target"
|
||||||
|
${pkgs.coreutils}/bin/chmod 0640 "$env_target"
|
||||||
|
|
||||||
|
npm_prefix=${escapeShellArg "${cfg.stateDir}/.npm-global"}
|
||||||
|
runtime_path=${escapeShellArg runtimePath}
|
||||||
|
|
||||||
|
project_mount_dir=${escapeShellArg "${cfg.stateDir}/.project-mounts"}
|
||||||
|
project_links_dir=${escapeShellArg "${cfg.stateDir}/projects"}
|
||||||
|
project_bind_pairs=()
|
||||||
|
|
||||||
|
matched_root=""
|
||||||
|
matched_mount=""
|
||||||
|
project_index=0
|
||||||
|
|
||||||
|
for root in "''${resolved_roots[@]}"; do
|
||||||
|
if [ ! -d "$root" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
root_slug="$(printf '%s' "$root" | ${pkgs.gnused}/bin/sed 's#^/##; s#/#-#g; s#-\{2,\}#-#g; s#-$##; s#^$#root#')"
|
||||||
|
root_slug="''${project_index}-''${root_slug}"
|
||||||
|
project_index=$((project_index + 1))
|
||||||
|
|
||||||
|
mount_point="''${project_mount_dir}/''${root_slug}"
|
||||||
|
link_path="''${project_links_dir}/''${root_slug}"
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/install -d -m 0750 -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} "$mount_point"
|
||||||
|
${pkgs.coreutils}/bin/ln -sfn "$mount_point" "$link_path"
|
||||||
|
|
||||||
|
project_bind_pairs+=("$root:$mount_point")
|
||||||
|
|
||||||
|
case "$cwd_real/" in
|
||||||
|
"$root"/*)
|
||||||
|
if [ -z "$matched_root" ] || [ "''${#root}" -gt "''${#matched_root}" ]; then
|
||||||
|
matched_root="$root"
|
||||||
|
matched_mount="$mount_point"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$matched_root" ]; then
|
||||||
|
echo "Failed to map cwd '$cwd_real' to an allowed root." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$cwd_real" = "$matched_root" ]; then
|
||||||
|
mapped_cwd="$matched_mount"
|
||||||
|
else
|
||||||
|
rel_path="''${cwd_real#"$matched_root/"}"
|
||||||
|
mapped_cwd="$matched_mount/$rel_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pi_bin=${escapeShellArg "${cfg.package}/bin/${cfg.binaryName}"}
|
||||||
|
|
||||||
|
if [ ! -x "$pi_bin" ]; then
|
||||||
|
for candidate in pi pi-agent; do
|
||||||
|
alt=${escapeShellArg "${cfg.package}/bin"}/$candidate
|
||||||
|
if [ -x "$alt" ]; then
|
||||||
|
pi_bin="$alt"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$pi_bin" ]; then
|
||||||
|
echo "Pi binary not found or not executable: $pi_bin" >&2
|
||||||
|
echo "Available executables in ${cfg.package}/bin:" >&2
|
||||||
|
${pkgs.coreutils}/bin/ls -1 ${escapeShellArg "${cfg.package}/bin"} >&2 || true
|
||||||
|
exit 127
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmd=(
|
||||||
|
${pkgs.systemd}/bin/systemd-run
|
||||||
|
--collect
|
||||||
|
--wait
|
||||||
|
--pty
|
||||||
|
--service-type=exec
|
||||||
|
-p User=${cfg.user}
|
||||||
|
-p Group=${cfg.group}
|
||||||
|
-p WorkingDirectory="$mapped_cwd"
|
||||||
|
-p NoNewPrivileges=yes
|
||||||
|
-p PrivateTmp=yes
|
||||||
|
-p ProtectSystem=strict
|
||||||
|
-p ProtectHome=false
|
||||||
|
-p ProtectControlGroups=yes
|
||||||
|
-p ProtectKernelTunables=yes
|
||||||
|
-p ProtectKernelModules=yes
|
||||||
|
-p RestrictSUIDSGID=yes
|
||||||
|
-p LockPersonality=yes
|
||||||
|
-p RestrictRealtime=yes
|
||||||
|
-p RestrictNamespaces=yes
|
||||||
|
-p MemoryDenyWriteExecute=no
|
||||||
|
-p UMask=0007
|
||||||
|
-p ReadWritePaths=${cfg.stateDir}
|
||||||
|
-p EnvironmentFile=${cfg.stateDir}/.pi/.env
|
||||||
|
-E HOME=${cfg.stateDir}
|
||||||
|
-E PI_HOME=${cfg.stateDir}/.pi
|
||||||
|
-E MESSAGING_CWD="$mapped_cwd"
|
||||||
|
-E PATH="$runtime_path"
|
||||||
|
-E NPM_CONFIG_CACHE=${cfg.stateDir}/.npm
|
||||||
|
-E NPM_CONFIG_PREFIX="$npm_prefix"
|
||||||
|
-E PI_AGENT_INVOKING_USER="$invoking_user"
|
||||||
|
)
|
||||||
|
|
||||||
|
${optionalString (cfg.projectGroup != null) ''
|
||||||
|
cmd+=( -p SupplementaryGroups=${cfg.projectGroup} )
|
||||||
|
''}
|
||||||
|
|
||||||
|
# Only mark existing top-level paths inaccessible; systemd fails namespace
|
||||||
|
# setup if InaccessiblePaths points to a non-existent path on this host.
|
||||||
|
for p in /home /root /mnt /media /srv; do
|
||||||
|
if [ -e "$p" ]; then
|
||||||
|
cmd+=( -p "InaccessiblePaths=$p" )
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for pair in "''${project_bind_pairs[@]}"; do
|
||||||
|
src="''${pair%%:*}"
|
||||||
|
dst="''${pair#*:}"
|
||||||
|
cmd+=( -p "BindPaths=$src:$dst" )
|
||||||
|
done
|
||||||
|
|
||||||
|
${concatStringsSep "\n" (mapAttrsToList (name: value: ''cmd+=( -E ${escapeShellArg "${name}=${value}"} )'') cfg.wrapper.extraEnvironment)}
|
||||||
|
|
||||||
|
cmd+=( "$pi_bin" )
|
||||||
|
${concatStringsSep "\n" (map (arg: ''cmd+=( ${escapeShellArg arg} )'') cfg.wrapper.extraRunArgs)}
|
||||||
|
cmd+=( "$@" )
|
||||||
|
|
||||||
|
exec "''${cmd[@]}"
|
||||||
|
''
|
||||||
92
modules/nixos/pi-agent-wrapper.nix
Normal file
92
modules/nixos/pi-agent-wrapper.nix
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
{cfg, pkgs, lib, runner, ...}:
|
||||||
|
with lib;
|
||||||
|
pkgs.writeShellScriptBin cfg.wrapper.commandName ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
user_name="$(id -un)"
|
||||||
|
user_home="$(eval echo "~$user_name")"
|
||||||
|
if [ -z "$user_home" ] || [ "$user_home" = "~$user_name" ]; then
|
||||||
|
user_home="$HOME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolve_user_policy() {
|
||||||
|
local user="$1"
|
||||||
|
USER_ROOTS=()
|
||||||
|
case "$user" in
|
||||||
|
${concatStringsSep "\n" (
|
||||||
|
mapAttrsToList (
|
||||||
|
user: userCfg: ''
|
||||||
|
${escapeShellArg user})
|
||||||
|
USER_ROOTS=(${concatStringsSep " " (map escapeShellArg userCfg.projectRoots)})
|
||||||
|
;;
|
||||||
|
''
|
||||||
|
)
|
||||||
|
cfg.hostUsers
|
||||||
|
)}
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! resolve_user_policy "$user_name"; then
|
||||||
|
echo "User '$user_name' is not allowed to use ${cfg.wrapper.commandName}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expand_home_path() {
|
||||||
|
local input="$1"
|
||||||
|
if [ "$input" = "~" ]; then
|
||||||
|
printf '%s\n' "$user_home"
|
||||||
|
elif ${pkgs.gnugrep}/bin/grep -q '^~/' <<<"$input"; then
|
||||||
|
printf '%s\n' "$user_home/''${input:2}"
|
||||||
|
elif ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$input"; then
|
||||||
|
printf '%s\n' "$input"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$user_home/$input"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cwd_real="$(${pkgs.coreutils}/bin/realpath -m "$PWD")"
|
||||||
|
|
||||||
|
is_allowed_cwd=0
|
||||||
|
resolved_roots=()
|
||||||
|
skipped_roots=()
|
||||||
|
for configured_root in "''${USER_ROOTS[@]}"; do
|
||||||
|
expanded_root="$(expand_home_path "$configured_root")"
|
||||||
|
resolved_root="$(${pkgs.coreutils}/bin/realpath -m "$expanded_root")"
|
||||||
|
if [ ! -d "$resolved_root" ]; then
|
||||||
|
skipped_roots+=("$resolved_root")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
resolved_roots+=("$resolved_root")
|
||||||
|
case "$cwd_real/" in
|
||||||
|
"$resolved_root"/*)
|
||||||
|
is_allowed_cwd=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "''${#resolved_roots[@]}" -eq 0 ]; then
|
||||||
|
echo "Denied: no valid existing project roots are configured for user '$user_name'." >&2
|
||||||
|
if [ "''${#skipped_roots[@]}" -gt 0 ]; then
|
||||||
|
echo "Configured but missing roots:" >&2
|
||||||
|
for root in "''${skipped_roots[@]}"; do
|
||||||
|
echo " - $root" >&2
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$is_allowed_cwd" -ne 1 ]; then
|
||||||
|
echo "Denied: '$cwd_real' is outside allowed project roots for user '$user_name'." >&2
|
||||||
|
echo "Allowed roots:" >&2
|
||||||
|
for root in "''${resolved_roots[@]}"; do
|
||||||
|
echo " - $root" >&2
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec /run/wrappers/bin/sudo --non-interactive ${runner}/bin/${cfg.wrapper.runnerName} "$user_name" "$cwd_real" "$@"
|
||||||
|
''
|
||||||
@@ -17,471 +17,8 @@ with lib; let
|
|||||||
|
|
||||||
hostUserNames = attrNames cfg.hostUsers;
|
hostUserNames = attrNames cfg.hostUsers;
|
||||||
|
|
||||||
managedSettingsFile = pkgs.writeText "pi-agent-managed-settings.json" (builtins.toJSON cfg.settings);
|
runner = import ./pi-agent-runner.nix {inherit cfg pkgs lib;};
|
||||||
|
wrapper = import ./pi-agent-wrapper.nix {inherit cfg pkgs lib runner;};
|
||||||
managedEnvFile =
|
|
||||||
pkgs.writeText "pi-agent-managed.env"
|
|
||||||
(concatStringsSep "\n" (mapAttrsToList (k: v: "${k}=${v}") cfg.environment));
|
|
||||||
|
|
||||||
runtimePath = concatStringsSep ":" (
|
|
||||||
[
|
|
||||||
"${cfg.package}/bin"
|
|
||||||
"${pkgs.nodejs}/bin"
|
|
||||||
"${pkgs.git}/bin"
|
|
||||||
"${pkgs.coreutils}/bin"
|
|
||||||
"${pkgs.findutils}/bin"
|
|
||||||
"${pkgs.gnugrep}/bin"
|
|
||||||
"${pkgs.gnused}/bin"
|
|
||||||
"${pkgs.util-linux}/bin"
|
|
||||||
"/run/current-system/sw/bin"
|
|
||||||
]
|
|
||||||
++ map (p: "${p}/bin") cfg.extraPackages
|
|
||||||
);
|
|
||||||
|
|
||||||
userPolicyCase = concatStringsSep "\n" (
|
|
||||||
mapAttrsToList (
|
|
||||||
user: userCfg: ''
|
|
||||||
${escapeShellArg user})
|
|
||||||
USER_CONFIG_PATH=${escapeShellArg (
|
|
||||||
if userCfg.configPath != null
|
|
||||||
then userCfg.configPath
|
|
||||||
else cfg.wrapper.hostConfigPath
|
|
||||||
)}
|
|
||||||
USER_ROOTS=(${concatStringsSep " " (map escapeShellArg userCfg.projectRoots)})
|
|
||||||
;;
|
|
||||||
''
|
|
||||||
)
|
|
||||||
cfg.hostUsers
|
|
||||||
);
|
|
||||||
|
|
||||||
runner = pkgs.writeShellScriptBin cfg.wrapper.runnerName ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
|
||||||
echo "${cfg.wrapper.runnerName} must run as root" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$#" -lt 2 ]; then
|
|
||||||
echo "Usage: ${cfg.wrapper.runnerName} <invoking-user> <cwd> [pi-args...]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
invoking_user="$1"
|
|
||||||
shift
|
|
||||||
cwd="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
resolve_user_policy() {
|
|
||||||
local user="$1"
|
|
||||||
USER_CONFIG_PATH=""
|
|
||||||
USER_ROOTS=()
|
|
||||||
case "$user" in
|
|
||||||
${userPolicyCase}
|
|
||||||
*)
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! resolve_user_policy "$invoking_user"; then
|
|
||||||
echo "User '$invoking_user' is not allowed to use ${cfg.wrapper.commandName}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
user_home="$(eval echo "~$invoking_user")"
|
|
||||||
if [ -z "$user_home" ] || [ "$user_home" = "~$invoking_user" ]; then
|
|
||||||
echo "Unable to determine home directory for user '$invoking_user'" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
expand_home_path() {
|
|
||||||
local input="$1"
|
|
||||||
if [ "$input" = "~" ]; then
|
|
||||||
printf '%s\n' "$user_home"
|
|
||||||
elif ${pkgs.gnugrep}/bin/grep -q '^~/' <<<"$input"; then
|
|
||||||
printf '%s\n' "$user_home/''${input:2}"
|
|
||||||
elif ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$input"; then
|
|
||||||
printf '%s\n' "$input"
|
|
||||||
else
|
|
||||||
# Bare relative path → resolve from user's home
|
|
||||||
printf '%s\n' "$user_home/$input"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cwd_real="$(${pkgs.coreutils}/bin/realpath -m "$cwd")"
|
|
||||||
|
|
||||||
resolved_roots=()
|
|
||||||
skipped_roots=()
|
|
||||||
is_allowed_cwd=0
|
|
||||||
for configured_root in "''${USER_ROOTS[@]}"; do
|
|
||||||
expanded_root="$(expand_home_path "$configured_root")"
|
|
||||||
resolved_root="$(${pkgs.coreutils}/bin/realpath -m "$expanded_root")"
|
|
||||||
if [ ! -d "$resolved_root" ]; then
|
|
||||||
skipped_roots+=("$resolved_root")
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
resolved_roots+=("$resolved_root")
|
|
||||||
case "$cwd_real/" in
|
|
||||||
"$resolved_root"/*)
|
|
||||||
is_allowed_cwd=1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "''${#resolved_roots[@]}" -eq 0 ]; then
|
|
||||||
echo "Denied: no valid existing project roots are configured for user '$invoking_user'." >&2
|
|
||||||
if [ "''${#skipped_roots[@]}" -gt 0 ]; then
|
|
||||||
echo "Configured but missing roots:" >&2
|
|
||||||
for root in "''${skipped_roots[@]}"; do
|
|
||||||
echo " - $root" >&2
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$is_allowed_cwd" -ne 1 ]; then
|
|
||||||
echo "Denied: '$cwd_real' is outside allowed project roots for user '$invoking_user'." >&2
|
|
||||||
echo "Allowed roots:" >&2
|
|
||||||
for root in "''${resolved_roots[@]}"; do
|
|
||||||
echo " - $root" >&2
|
|
||||||
done
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
${pkgs.coreutils}/bin/install -d -m 0750 -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} \
|
|
||||||
${escapeShellArg cfg.stateDir} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/.pi"} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/.pi/agent"} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/.pi/agent/sessions"} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/.project-mounts"} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/projects"} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/.npm"} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/.npm-global"} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/.npm-global/bin"} \
|
|
||||||
${escapeShellArg "${cfg.stateDir}/.npm-global/lib"}
|
|
||||||
|
|
||||||
config_source="$USER_CONFIG_PATH"
|
|
||||||
if ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$config_source"; then
|
|
||||||
source_dir="$config_source"
|
|
||||||
else
|
|
||||||
source_dir="$(expand_home_path "$config_source")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [ "${
|
|
||||||
if cfg.wrapper.syncConfigFromHost
|
|
||||||
then "1"
|
|
||||||
else "0"
|
|
||||||
}" = "1" ] && [ -d "$source_dir" ]; then
|
|
||||||
${pkgs.rsync}/bin/rsync -a --delete \
|
|
||||||
--exclude='auth.json' \
|
|
||||||
--exclude='mcp-oauth' \
|
|
||||||
--exclude='sessions' \
|
|
||||||
--exclude='bin' \
|
|
||||||
--exclude='mcp-cache.json' \
|
|
||||||
"$source_dir/" ${escapeShellArg "${cfg.stateDir}/.pi/agent/"}
|
|
||||||
${pkgs.coreutils}/bin/chown -R ${escapeShellArg "${cfg.user}:${cfg.group}"} ${escapeShellArg "${cfg.stateDir}/.pi/agent"}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Merge host settings.json (if any) with Nix-managed settings.
|
|
||||||
# Precedence: host settings first, Nix-managed keys override recursively.
|
|
||||||
settings_target=${escapeShellArg "${cfg.stateDir}/.pi/agent/settings.json"}
|
|
||||||
${pkgs.python3}/bin/python3 - "$settings_target" ${escapeShellArg managedSettingsFile} <<'PY_PI_SETTINGS_MERGE'
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def load_obj(path):
|
|
||||||
if not os.path.exists(path):
|
|
||||||
return {}
|
|
||||||
try:
|
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
return data if isinstance(data, dict) else {}
|
|
||||||
except Exception:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def deep_merge(base, override):
|
|
||||||
if isinstance(base, dict) and isinstance(override, dict):
|
|
||||||
out = dict(base)
|
|
||||||
for key, value in override.items():
|
|
||||||
out[key] = deep_merge(out.get(key), value)
|
|
||||||
return out
|
|
||||||
return override
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
target = sys.argv[1]
|
|
||||||
managed = sys.argv[2]
|
|
||||||
base_obj = load_obj(target)
|
|
||||||
managed_obj = load_obj(managed)
|
|
||||||
merged = deep_merge(base_obj, managed_obj)
|
|
||||||
|
|
||||||
os.makedirs(os.path.dirname(target), exist_ok=True)
|
|
||||||
tmp = f"{target}.tmp"
|
|
||||||
with open(tmp, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(merged, f, indent=2, sort_keys=True)
|
|
||||||
f.write("\n")
|
|
||||||
os.replace(tmp, target)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
PY_PI_SETTINGS_MERGE
|
|
||||||
${pkgs.coreutils}/bin/chown ${escapeShellArg "${cfg.user}:${cfg.group}"} "$settings_target"
|
|
||||||
${pkgs.coreutils}/bin/chmod 0640 "$settings_target"
|
|
||||||
|
|
||||||
# Merge environment into isolated .env with precedence:
|
|
||||||
# 1) synced host env (source_dir/.env)
|
|
||||||
# 2) Nix-managed environment attrset
|
|
||||||
# 3) Nix-managed environmentFiles (appended in declaration order)
|
|
||||||
env_target=${escapeShellArg "${cfg.stateDir}/.pi/.env"}
|
|
||||||
${pkgs.coreutils}/bin/install -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} -m 0640 /dev/null "$env_target"
|
|
||||||
|
|
||||||
if [ -f "$source_dir/.env" ]; then
|
|
||||||
${pkgs.coreutils}/bin/cat "$source_dir/.env" >> "$env_target"
|
|
||||||
printf '\n' >> "$env_target"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f ${escapeShellArg managedEnvFile} ]; then
|
|
||||||
${pkgs.coreutils}/bin/cat ${escapeShellArg managedEnvFile} >> "$env_target"
|
|
||||||
printf '\n' >> "$env_target"
|
|
||||||
fi
|
|
||||||
|
|
||||||
${concatStringsSep "\n" (map (f: ''
|
|
||||||
if [ -f ${escapeShellArg f} ]; then
|
|
||||||
${pkgs.coreutils}/bin/cat ${escapeShellArg f} >> "$env_target"
|
|
||||||
printf '\n' >> "$env_target"
|
|
||||||
fi
|
|
||||||
'')
|
|
||||||
cfg.environmentFiles)}
|
|
||||||
|
|
||||||
${pkgs.coreutils}/bin/chown ${escapeShellArg "${cfg.user}:${cfg.group}"} "$env_target"
|
|
||||||
${pkgs.coreutils}/bin/chmod 0640 "$env_target"
|
|
||||||
|
|
||||||
npm_prefix=${escapeShellArg "${cfg.stateDir}/.npm-global"}
|
|
||||||
runtime_path=${escapeShellArg runtimePath}
|
|
||||||
|
|
||||||
project_mount_dir=${escapeShellArg "${cfg.stateDir}/.project-mounts"}
|
|
||||||
project_links_dir=${escapeShellArg "${cfg.stateDir}/projects"}
|
|
||||||
project_bind_pairs=()
|
|
||||||
|
|
||||||
matched_root=""
|
|
||||||
matched_mount=""
|
|
||||||
project_index=0
|
|
||||||
|
|
||||||
for root in "''${resolved_roots[@]}"; do
|
|
||||||
if [ ! -d "$root" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
root_slug="$(printf '%s' "$root" | ${pkgs.gnused}/bin/sed 's#^/##; s#/#-#g; s#-\{2,\}#-#g; s#-$##; s#^$#root#')"
|
|
||||||
root_slug="''${project_index}-''${root_slug}"
|
|
||||||
project_index=$((project_index + 1))
|
|
||||||
|
|
||||||
mount_point="''${project_mount_dir}/''${root_slug}"
|
|
||||||
link_path="''${project_links_dir}/''${root_slug}"
|
|
||||||
|
|
||||||
${pkgs.coreutils}/bin/install -d -m 0750 -o ${escapeShellArg cfg.user} -g ${escapeShellArg cfg.group} "$mount_point"
|
|
||||||
${pkgs.coreutils}/bin/ln -sfn "$mount_point" "$link_path"
|
|
||||||
|
|
||||||
project_bind_pairs+=("$root:$mount_point")
|
|
||||||
|
|
||||||
case "$cwd_real/" in
|
|
||||||
"$root"/*)
|
|
||||||
if [ -z "$matched_root" ] || [ "''${#root}" -gt "''${#matched_root}" ]; then
|
|
||||||
matched_root="$root"
|
|
||||||
matched_mount="$mount_point"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$matched_root" ]; then
|
|
||||||
echo "Failed to map cwd '$cwd_real' to an allowed root." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$cwd_real" = "$matched_root" ]; then
|
|
||||||
mapped_cwd="$matched_mount"
|
|
||||||
else
|
|
||||||
rel_path="''${cwd_real#"$matched_root/"}"
|
|
||||||
mapped_cwd="$matched_mount/$rel_path"
|
|
||||||
fi
|
|
||||||
|
|
||||||
pi_bin=${escapeShellArg "${cfg.package}/bin/${cfg.binaryName}"}
|
|
||||||
|
|
||||||
if [ ! -x "$pi_bin" ]; then
|
|
||||||
for candidate in pi pi-agent; do
|
|
||||||
alt=${escapeShellArg "${cfg.package}/bin"}/$candidate
|
|
||||||
if [ -x "$alt" ]; then
|
|
||||||
pi_bin="$alt"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -x "$pi_bin" ]; then
|
|
||||||
echo "Pi binary not found or not executable: $pi_bin" >&2
|
|
||||||
echo "Available executables in ${cfg.package}/bin:" >&2
|
|
||||||
${pkgs.coreutils}/bin/ls -1 ${escapeShellArg "${cfg.package}/bin"} >&2 || true
|
|
||||||
exit 127
|
|
||||||
fi
|
|
||||||
|
|
||||||
cmd=(
|
|
||||||
${pkgs.systemd}/bin/systemd-run
|
|
||||||
--collect
|
|
||||||
--wait
|
|
||||||
--pty
|
|
||||||
--service-type=exec
|
|
||||||
-p User=${cfg.user}
|
|
||||||
-p Group=${cfg.group}
|
|
||||||
-p WorkingDirectory="$mapped_cwd"
|
|
||||||
-p NoNewPrivileges=yes
|
|
||||||
-p PrivateTmp=yes
|
|
||||||
-p ProtectSystem=strict
|
|
||||||
-p ProtectHome=false
|
|
||||||
-p ProtectControlGroups=yes
|
|
||||||
-p ProtectKernelTunables=yes
|
|
||||||
-p ProtectKernelModules=yes
|
|
||||||
-p RestrictSUIDSGID=yes
|
|
||||||
-p LockPersonality=yes
|
|
||||||
-p RestrictRealtime=yes
|
|
||||||
-p RestrictNamespaces=yes
|
|
||||||
-p MemoryDenyWriteExecute=no
|
|
||||||
-p UMask=0007
|
|
||||||
-p ReadWritePaths=${cfg.stateDir}
|
|
||||||
-p EnvironmentFile=${cfg.stateDir}/.pi/.env
|
|
||||||
-E HOME=${cfg.stateDir}
|
|
||||||
-E PI_HOME=${cfg.stateDir}/.pi
|
|
||||||
-E MESSAGING_CWD="$mapped_cwd"
|
|
||||||
-E PATH="$runtime_path"
|
|
||||||
-E NPM_CONFIG_CACHE=${cfg.stateDir}/.npm
|
|
||||||
-E NPM_CONFIG_PREFIX="$npm_prefix"
|
|
||||||
-E PI_AGENT_INVOKING_USER="$invoking_user"
|
|
||||||
)
|
|
||||||
|
|
||||||
${optionalString (cfg.projectGroup != null) ''
|
|
||||||
cmd+=( -p SupplementaryGroups=${cfg.projectGroup} )
|
|
||||||
''}
|
|
||||||
|
|
||||||
# Only mark existing top-level paths inaccessible; systemd fails namespace
|
|
||||||
# setup if InaccessiblePaths points to a non-existent path on this host.
|
|
||||||
for p in /home /root /mnt /media /srv; do
|
|
||||||
if [ -e "$p" ]; then
|
|
||||||
cmd+=( -p "InaccessiblePaths=$p" )
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
for pair in "''${project_bind_pairs[@]}"; do
|
|
||||||
src="''${pair%%:*}"
|
|
||||||
dst="''${pair#*:}"
|
|
||||||
cmd+=( -p "BindPaths=$src:$dst" )
|
|
||||||
done
|
|
||||||
|
|
||||||
${concatStringsSep "\n" (mapAttrsToList (name: value: ''cmd+=( -E ${escapeShellArg "${name}=${value}"} )'') cfg.wrapper.extraEnvironment)}
|
|
||||||
|
|
||||||
cmd+=( "$pi_bin" )
|
|
||||||
${concatStringsSep "\n" (map (arg: ''cmd+=( ${escapeShellArg arg} )'') cfg.wrapper.extraRunArgs)}
|
|
||||||
cmd+=( "$@" )
|
|
||||||
|
|
||||||
exec "''${cmd[@]}"
|
|
||||||
'';
|
|
||||||
|
|
||||||
wrapper = pkgs.writeShellScriptBin cfg.wrapper.commandName ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
user_name="$(id -un)"
|
|
||||||
user_home="$(eval echo "~$user_name")"
|
|
||||||
if [ -z "$user_home" ] || [ "$user_home" = "~$user_name" ]; then
|
|
||||||
user_home="$HOME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
resolve_user_policy() {
|
|
||||||
local user="$1"
|
|
||||||
USER_ROOTS=()
|
|
||||||
case "$user" in
|
|
||||||
${concatStringsSep "\n" (
|
|
||||||
mapAttrsToList (
|
|
||||||
user: userCfg: ''
|
|
||||||
${escapeShellArg user})
|
|
||||||
USER_ROOTS=(${concatStringsSep " " (map escapeShellArg userCfg.projectRoots)})
|
|
||||||
;;
|
|
||||||
''
|
|
||||||
)
|
|
||||||
cfg.hostUsers
|
|
||||||
)}
|
|
||||||
*)
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! resolve_user_policy "$user_name"; then
|
|
||||||
echo "User '$user_name' is not allowed to use ${cfg.wrapper.commandName}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
expand_home_path() {
|
|
||||||
local input="$1"
|
|
||||||
if [ "$input" = "~" ]; then
|
|
||||||
printf '%s\n' "$user_home"
|
|
||||||
elif ${pkgs.gnugrep}/bin/grep -q '^~/' <<<"$input"; then
|
|
||||||
printf '%s\n' "$user_home/''${input:2}"
|
|
||||||
elif ${pkgs.gnugrep}/bin/grep -q '^/' <<<"$input"; then
|
|
||||||
printf '%s\n' "$input"
|
|
||||||
else
|
|
||||||
printf '%s\n' "$user_home/$input"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cwd_real="$(${pkgs.coreutils}/bin/realpath -m "$PWD")"
|
|
||||||
|
|
||||||
is_allowed_cwd=0
|
|
||||||
resolved_roots=()
|
|
||||||
skipped_roots=()
|
|
||||||
for configured_root in "''${USER_ROOTS[@]}"; do
|
|
||||||
expanded_root="$(expand_home_path "$configured_root")"
|
|
||||||
resolved_root="$(${pkgs.coreutils}/bin/realpath -m "$expanded_root")"
|
|
||||||
if [ ! -d "$resolved_root" ]; then
|
|
||||||
skipped_roots+=("$resolved_root")
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
resolved_roots+=("$resolved_root")
|
|
||||||
case "$cwd_real/" in
|
|
||||||
"$resolved_root"/*)
|
|
||||||
is_allowed_cwd=1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "''${#resolved_roots[@]}" -eq 0 ]; then
|
|
||||||
echo "Denied: no valid existing project roots are configured for user '$user_name'." >&2
|
|
||||||
if [ "''${#skipped_roots[@]}" -gt 0 ]; then
|
|
||||||
echo "Configured but missing roots:" >&2
|
|
||||||
for root in "''${skipped_roots[@]}"; do
|
|
||||||
echo " - $root" >&2
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$is_allowed_cwd" -ne 1 ]; then
|
|
||||||
echo "Denied: '$cwd_real' is outside allowed project roots for user '$user_name'." >&2
|
|
||||||
echo "Allowed roots:" >&2
|
|
||||||
for root in "''${resolved_roots[@]}"; do
|
|
||||||
echo " - $root" >&2
|
|
||||||
done
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec /run/wrappers/bin/sudo --non-interactive ${runner}/bin/${cfg.wrapper.runnerName} "$user_name" "$cwd_real" "$@"
|
|
||||||
'';
|
|
||||||
in {
|
in {
|
||||||
options.m3ta.pi-agent = {
|
options.m3ta.pi-agent = {
|
||||||
enable = mkEnableOption "isolated Pi execution with dedicated system user and policy-enforced wrapper";
|
enable = mkEnableOption "isolated Pi execution with dedicated system user and policy-enforced wrapper";
|
||||||
|
|||||||
Reference in New Issue
Block a user