Merge pull request 'fix: pi settings sync' (#8) from fix/pi-sync-settings into master
Some checks failed
Update Nix Packages with nix-update / nix-update (push) Failing after 3m21s
Some checks failed
Update Nix Packages with nix-update / nix-update (push) Failing after 3m21s
Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
2
.pi-lens/cache/jscpd.meta.json
vendored
2
.pi-lens/cache/jscpd.meta.json
vendored
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"timestamp": "2026-04-14T15:35:06.339Z"
|
"timestamp": "2026-04-15T09:30:34.459Z"
|
||||||
}
|
}
|
||||||
2
.pi-lens/cache/knip.meta.json
vendored
2
.pi-lens/cache/knip.meta.json
vendored
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"timestamp": "2026-04-14T15:35:07.218Z"
|
"timestamp": "2026-04-15T09:30:35.667Z"
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"timestamp": "2026-04-14T05:13:57.102Z"
|
"timestamp": "2026-04-15T09:28:51.987Z"
|
||||||
}
|
}
|
||||||
2
.pi-lens/cache/todo-baseline.meta.json
vendored
2
.pi-lens/cache/todo-baseline.meta.json
vendored
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"timestamp": "2026-04-14T05:11:47.088Z"
|
"timestamp": "2026-04-15T09:28:16.965Z"
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"files": {},
|
"files": {},
|
||||||
"turnCycles": 0,
|
"turnCycles": 0,
|
||||||
"maxCycles": 3,
|
"maxCycles": 3,
|
||||||
"lastUpdated": "2026-04-14T15:35:07.218Z"
|
"lastUpdated": "2026-04-15T09:30:35.668Z"
|
||||||
}
|
}
|
||||||
98
PLAN.md
98
PLAN.md
@@ -1,98 +0,0 @@
|
|||||||
# PLAN
|
|
||||||
|
|
||||||
## Context
|
|
||||||
- Target implementation is confirmed as `m3ta.pi-agent` (no container mode).
|
|
||||||
- You want a **fresh-from-scratch rewrite** of `modules/nixos/pi-agent.nix` and to ignore prior behavior as design baseline.
|
|
||||||
- Required behavior:
|
|
||||||
- dedicated isolated Unix user/group for Pi (`pi-agent` defaults)
|
|
||||||
- host UX stays `pi`
|
|
||||||
- bypass prevention (wrapper should be the canonical executable path)
|
|
||||||
- per-host-user project root policy (different roots per user)
|
|
||||||
- no writable/access scope beyond isolated Pi home/state + explicitly allowed project roots
|
|
||||||
- isolated environment must include user Pi config from HM (`modules/home-manager/coding/agents/pi.nix`) and support Nix-managed settings/env merging.
|
|
||||||
- Repo findings:
|
|
||||||
- `modules/nixos/default.nix` + `flake.nix` already import/export `pi-agent` module.
|
|
||||||
- `modules/home-manager/coding/agents/pi.nix` already renders Pi config files under a configurable relative path (`coding.agents.pi.path`, default `.pi/agent`).
|
|
||||||
|
|
||||||
## Approach
|
|
||||||
- Fully replace `modules/nixos/pi-agent.nix` with a new design centered on:
|
|
||||||
1. **Dedicated runtime identity** (`user/group/createUser/stateDir`).
|
|
||||||
2. **Policy-driven wrapper flow** (`pi` -> privileged runner -> isolated execution).
|
|
||||||
3. **Per-user project allowlists** (cwd must be under roots assigned to invoking host user).
|
|
||||||
4. **Config + env convergence**:
|
|
||||||
- sync user HM Pi config directory (e.g. `~/.pi/agent`) into isolated state,
|
|
||||||
- merge Nix-managed Pi settings into isolated `settings.json`,
|
|
||||||
- merge Nix-managed env vars + env files into isolated runtime env source,
|
|
||||||
- make merged results visible to the isolated runtime every invocation (without container recreation semantics).
|
|
||||||
5. **Hard isolation defaults** with `systemd-run` sandboxing and explicit bind/read-write paths only for state + allowed projects.
|
|
||||||
- Keep wrapper command as `pi`, and avoid exposing direct package binary on PATH when wrapper mode is enabled.
|
|
||||||
|
|
||||||
## Files to modify
|
|
||||||
- `modules/nixos/pi-agent.nix` (full rewrite)
|
|
||||||
- `modules/nixos/default.nix` (only if import list changes)
|
|
||||||
- `flake.nix` (only if output export attrs change)
|
|
||||||
- `docs/guides/pi-agent-isolation.md` (update option model + merge behavior)
|
|
||||||
- `docs/guides/using-modules.md` (update examples/options)
|
|
||||||
|
|
||||||
## Reuse
|
|
||||||
- Module/user/service patterns:
|
|
||||||
- `modules/nixos/mem0.nix`
|
|
||||||
- `templates/nixos-module/default.nix`
|
|
||||||
- Pi config rendering contract to consume/sync:
|
|
||||||
- `modules/home-manager/coding/agents/pi.nix` (`coding.agents.pi.path`, `settings.json`, `mcp.json`, agent docs)
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
- [ ] Define the new `m3ta.pi-agent` option schema for fresh module behavior, including:
|
|
||||||
- base runtime options (`package`, `binaryName`, `user`, `group`, `createUser`, `stateDir`),
|
|
||||||
- wrapper controls (`enable`, `commandName`, runner name, hide-direct-binary behavior),
|
|
||||||
- per-user policy map (allowed users and each user’s allowed project roots),
|
|
||||||
- host-config sync knobs (source path relative/absolute),
|
|
||||||
- Nix-managed settings/env options for merge.
|
|
||||||
- [ ] Implement new wrapper script:
|
|
||||||
- identify invoking user,
|
|
||||||
- validate user exists in policy map,
|
|
||||||
- expand/resolve that user’s roots,
|
|
||||||
- deny out-of-policy cwd,
|
|
||||||
- escalate only to the dedicated runner.
|
|
||||||
- [ ] Implement new privileged runner script:
|
|
||||||
- enforce root-only execution,
|
|
||||||
- resync host Pi config into isolated config dir,
|
|
||||||
- merge managed settings into isolated settings file,
|
|
||||||
- merge managed env + env files into isolated env file/export source,
|
|
||||||
- prepare deterministic project mount aliases under isolated home,
|
|
||||||
- launch Pi through hardened transient `systemd-run` unit as isolated user.
|
|
||||||
- [ ] Apply hardening policy in execution profile:
|
|
||||||
- `ProtectSystem=strict`, `ProtectHome=yes`, `NoNewPrivileges=yes`,
|
|
||||||
- explicit `ReadWritePaths` limited to state + mounted allowed projects,
|
|
||||||
- bounded runtime PATH and writable tool/cache locations under `stateDir`.
|
|
||||||
- [ ] Add assertions for misconfiguration (e.g., empty per-user roots, wrapper enabled without authorized users).
|
|
||||||
- [ ] Add tightly scoped sudoers rule for runner command only.
|
|
||||||
- [ ] Ensure bypass prevention in packaging/PATH behavior when wrapper mode is enabled.
|
|
||||||
- [ ] Update docs with new option examples (per-user roots + settings/env merge + HM sync expectations).
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
- Static/eval:
|
|
||||||
- `nix flake check`
|
|
||||||
- host config eval/build with new module options.
|
|
||||||
- Policy checks:
|
|
||||||
- authorized user in authorized root: succeeds
|
|
||||||
- authorized user outside authorized root: denied
|
|
||||||
- unauthorized user: denied
|
|
||||||
- Isolation checks:
|
|
||||||
- runtime identity is isolated service user (`pi-agent`)
|
|
||||||
- no unintended write access outside `stateDir` + allowed project binds
|
|
||||||
- direct binary bypass unavailable when wrapper mode is enabled
|
|
||||||
- Merge checks:
|
|
||||||
- HM-rendered Pi files are present in isolated config dir
|
|
||||||
- Nix-managed settings are merged into effective isolated `settings.json`
|
|
||||||
- env values from declarative attrs + env files are present in isolated runtime environment.
|
|
||||||
|
|
||||||
## Open questions
|
|
||||||
- None.
|
|
||||||
|
|
||||||
## Resolved decisions
|
|
||||||
- Merge precedence is confirmed as:
|
|
||||||
1) synced host Pi config/env,
|
|
||||||
2) Nix-managed settings/env override synced values,
|
|
||||||
3) wrapper/runtime shell env does not implicitly override managed values.
|
|
||||||
- Per-user host config source defaults to `.pi/agent` for all users, with optional per-user override support in the policy map.
|
|
||||||
@@ -154,6 +154,7 @@ with lib; let
|
|||||||
${escapeShellArg cfg.stateDir} \
|
${escapeShellArg cfg.stateDir} \
|
||||||
${escapeShellArg "${cfg.stateDir}/.pi"} \
|
${escapeShellArg "${cfg.stateDir}/.pi"} \
|
||||||
${escapeShellArg "${cfg.stateDir}/.pi/agent"} \
|
${escapeShellArg "${cfg.stateDir}/.pi/agent"} \
|
||||||
|
${escapeShellArg "${cfg.stateDir}/.pi/agent/sessions"} \
|
||||||
${escapeShellArg "${cfg.stateDir}/.project-mounts"} \
|
${escapeShellArg "${cfg.stateDir}/.project-mounts"} \
|
||||||
${escapeShellArg "${cfg.stateDir}/projects"} \
|
${escapeShellArg "${cfg.stateDir}/projects"} \
|
||||||
${escapeShellArg "${cfg.stateDir}/.npm"} \
|
${escapeShellArg "${cfg.stateDir}/.npm"} \
|
||||||
@@ -174,7 +175,13 @@ with lib; let
|
|||||||
then "1"
|
then "1"
|
||||||
else "0"
|
else "0"
|
||||||
}" = "1" ] && [ -d "$source_dir" ]; then
|
}" = "1" ] && [ -d "$source_dir" ]; then
|
||||||
${pkgs.rsync}/bin/rsync -a --delete "$source_dir/" ${escapeShellArg "${cfg.stateDir}/.pi/agent/"}
|
${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"}
|
${pkgs.coreutils}/bin/chown -R ${escapeShellArg "${cfg.user}:${cfg.group}"} ${escapeShellArg "${cfg.stateDir}/.pi/agent"}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -346,7 +353,7 @@ with lib; let
|
|||||||
-p RestrictRealtime=yes
|
-p RestrictRealtime=yes
|
||||||
-p RestrictNamespaces=yes
|
-p RestrictNamespaces=yes
|
||||||
-p MemoryDenyWriteExecute=no
|
-p MemoryDenyWriteExecute=no
|
||||||
-p UMask=0077
|
-p UMask=0007
|
||||||
-p ReadWritePaths=${cfg.stateDir}
|
-p ReadWritePaths=${cfg.stateDir}
|
||||||
-p EnvironmentFile=${cfg.stateDir}/.pi/.env
|
-p EnvironmentFile=${cfg.stateDir}/.pi/.env
|
||||||
-E HOME=${cfg.stateDir}
|
-E HOME=${cfg.stateDir}
|
||||||
@@ -358,6 +365,10 @@ with lib; let
|
|||||||
-E PI_AGENT_INVOKING_USER="$invoking_user"
|
-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
|
# Only mark existing top-level paths inaccessible; systemd fails namespace
|
||||||
# setup if InaccessiblePaths points to a non-existent path on this host.
|
# setup if InaccessiblePaths points to a non-existent path on this host.
|
||||||
for p in /home /root /mnt /media /srv; do
|
for p in /home /root /mnt /media /srv; do
|
||||||
@@ -596,6 +607,19 @@ in {
|
|||||||
description = "Extra packages added to isolated runtime PATH.";
|
description = "Extra packages added to isolated runtime PATH.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
projectGroup = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
When set, the pi-agent user is added to this group and the group is
|
||||||
|
passed as SupplementaryGroups to the systemd-run sandbox. This allows
|
||||||
|
pi-agent to write to project directories that grant group write access.
|
||||||
|
The user must ensure project directories have appropriate group ownership
|
||||||
|
and permissions (e.g. setgid + group write).
|
||||||
|
'';
|
||||||
|
example = "users";
|
||||||
|
};
|
||||||
|
|
||||||
wrapper = {
|
wrapper = {
|
||||||
enable = mkOption {
|
enable = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
@@ -682,6 +706,7 @@ in {
|
|||||||
"${cfg.user}" = {
|
"${cfg.user}" = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
group = cfg.group;
|
group = cfg.group;
|
||||||
|
extraGroups = mkIf (cfg.projectGroup != null) [cfg.projectGroup];
|
||||||
description = "Isolated Pi agent user";
|
description = "Isolated Pi agent user";
|
||||||
home = cfg.stateDir;
|
home = cfg.stateDir;
|
||||||
createHome = true;
|
createHome = true;
|
||||||
@@ -693,6 +718,7 @@ in {
|
|||||||
"d ${cfg.stateDir} 0750 ${cfg.user} ${cfg.group} - -"
|
"d ${cfg.stateDir} 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
"d ${cfg.stateDir}/.pi 0750 ${cfg.user} ${cfg.group} - -"
|
"d ${cfg.stateDir}/.pi 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
"d ${cfg.stateDir}/.pi/agent 0750 ${cfg.user} ${cfg.group} - -"
|
"d ${cfg.stateDir}/.pi/agent 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
|
"d ${cfg.stateDir}/.pi/agent/sessions 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
"d ${cfg.stateDir}/.project-mounts 0750 ${cfg.user} ${cfg.group} - -"
|
"d ${cfg.stateDir}/.project-mounts 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
"d ${cfg.stateDir}/projects 0750 ${cfg.user} ${cfg.group} - -"
|
"d ${cfg.stateDir}/projects 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
"d ${cfg.stateDir}/.npm 0750 ${cfg.user} ${cfg.group} - -"
|
"d ${cfg.stateDir}/.npm 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
nix-update-script,
|
nix-update-script,
|
||||||
}: let
|
}: let
|
||||||
pname = "eigent";
|
pname = "eigent";
|
||||||
version = "0.0.89";
|
version = "0.0.90";
|
||||||
src = fetchurl {
|
src = fetchurl {
|
||||||
url = "https://github.com/eigent-ai/eigent/releases/download/v${version}/Eigent-${version}.AppImage";
|
url = "https://github.com/eigent-ai/eigent/releases/download/v${version}/Eigent-${version}.AppImage";
|
||||||
hash = "sha256-9KuiFjegfXhCu1W/FCinWX4ae/DsNPudeBcXFfW18Hc=";
|
hash = "sha256-mwCBx+D6mgGqQa8bDuUpo3h49EwFVkwasJwaYc6aXFE=";
|
||||||
};
|
};
|
||||||
appimageContents = appimageTools.extractType2 {inherit pname version src;};
|
appimageContents = appimageTools.extractType2 {inherit pname version src;};
|
||||||
in
|
in
|
||||||
|
|||||||
Reference in New Issue
Block a user