feat(nixos): add NixOS module for isolated system user#24
Draft
jeanlucthumm wants to merge 34 commits intoopenclaw:mainfrom
Draft
feat(nixos): add NixOS module for isolated system user#24jeanlucthumm wants to merge 34 commits intoopenclaw:mainfrom
jeanlucthumm wants to merge 34 commits intoopenclaw:mainfrom
Conversation
zenware
reviewed
Jan 27, 2026
There was a problem hiding this comment.
It's a bit odd that the name of the file on-disk is CLAUDE.md while the name of the file in it's own heading is "AGENTS.md"
Author
There was a problem hiding this comment.
Just copy pasted it so I can use it with Claude Code.
I haven't seen a good way to have both coexist in a repo, might delete before merge but open to suggestions.
Paths like ~/.clawdbot need to be expanded to absolute paths for systemd, which cannot interpret literal ~.
Automatically update version strings in clawdbot-gateway.nix and check derivations when running update-pins.sh, keeping them in sync with the upstream release tag.
Duplicated option definitions from home-manager module, adapted for NixOS system service: - Namespace: services.clawdbot (not programs.clawdbot) - Default paths: /var/lib/clawdbot (not ~/.clawdbot) - Added: user, group options for system user - Removed: launchd.*, app.*, appDefaults.* (macOS-specific) - Skills default to "copy" mode (no user home for symlinks) Marked with TODO for future consolidation with home-manager options.
Implements the core NixOS module: - System user/group creation (static, not DynamicUser) - Systemd service with comprehensive hardening: - ProtectHome, ProtectSystem=strict, PrivateTmp - Capability restrictions (empty CapabilityBoundingSet) - System call filtering (@System-service) - Network restrictions (AF_INET/AF_INET6/AF_UNIX only) - Namespace restrictions - Note: MemoryDenyWriteExecute disabled for Node.js JIT - Gateway wrapper script for credential loading - Config generation (mirrored from home-manager patterns) - tmpfiles rules for state directories - Config files in /etc/clawdbot/ with symlinks to state dir
Tests: - Service starts successfully - User/group created (clawdbot:clawdbot) - State directories exist with correct ownership - Config file symlinked to /var/lib/clawdbot - Hardening: ProtectHome hides /home from service - Service runs as clawdbot user Run with: nix build .#checks.x86_64-linux.nixos-module -L Interactive: nix build .#checks.x86_64-linux.nixos-module.driverInteractive
The /run/agenix and /run/secrets paths were causing service startup failure when they don't exist. With ProtectSystem=strict, /run/* is already readable, so these explicit paths are unnecessary.
Adds providers.anthropic.oauthCredentialsDir option that bind-mounts the user's ~/.claude directory into the service's sandbox. This allows using Claude Pro/Max subscription via OAuth while maintaining isolation. The service: - Still has ProtectHome=true (can't see /home generally) - Gets read-write access to ONLY the specified .claude dir via BindPaths - Can refresh OAuth tokens as needed Example: services.clawdbot.providers.anthropic.oauthCredentialsDir = "/home/user/.claude";
Adds second test node (oauth) that verifies: - Service starts with oauthCredentialsDir configured - Credentials are accessible via bind-mount at /var/lib/clawdbot/.claude - Other files in /home remain inaccessible (ProtectHome still works)
The gateway uses os.networkInterfaces() which requires AF_NETLINK sockets to enumerate interfaces. Without this, the service fails with: uv_interface_addresses returned Unknown system error 97
System services aren't exposed externally, so disable gateway auth by default. Users can override via configOverrides if needed.
Match home-manager behavior - don't set gateway.auth.mode in the module. Users can configure via configOverrides based on their use case.
Upstream now requires gateway authentication by default (c4a80f4ed).
Add gateway.auth.{mode,tokenFile,passwordFile} options to support this.
Users can either:
- Set tokenFile/passwordFile to load credentials from files at runtime
- Use configOverrides to set gateway.auth.token directly in the config
The wrapper script loads credentials from files and sets the appropriate
environment variables (CLAWDBOT_GATEWAY_TOKEN/CLAWDBOT_GATEWAY_PASSWORD).
The @mariozechner/clipboard native module (transitive dependency from pi-tui) needs resource syscalls that were blocked by ~@resources filter, causing SIGSYS crashes on headless systems.
Remove ~@PRIVILEGED and ~@resources filters - Node.js with native modules needs these syscalls. Security is maintained through: - CapabilityBoundingSet = "" (no capabilities) - NoNewPrivileges = true - ProtectHome, ProtectSystem = strict - RestrictNamespaces, PrivateDevices, PrivateTmp
Instance-level providers.telegram and providers.anthropic options now default to the top-level cfg values instead of hardcoded defaults. This ensures that when users set top-level providers and only override specific instance options (like configOverrides), the provider settings are still inherited.
BindPaths can't access /home when ProtectHome=true, so disable it when oauthCredentialsDir is configured (typically points to ~/.claude).
The short-lived OAuth tokens from Claude CLI's .credentials.json aren't practical for server deployments. Replace with oauthTokenFile which loads a long-lived token from `claude setup-token`. - Remove oauthCredentialsDir option and bind mount logic - Add oauthTokenFile option (sets ANTHROPIC_OAUTH_TOKEN env var) - Restore ProtectHome=true now that we don't need /home access - Update module header with new example usage
The OAuth bind-mount test referenced oauthCredentialsDir which was removed in ddc7bd5. The new oauthTokenFile approach doesn't need bind mounts - tokens are loaded via env vars from paths outside /home.
These options were defined but never wired up to the config generation. Rather than ship dead options, remove them and add a note pointing to the home-manager module which has the full implementation.
Add documents-skills.nix with parallel implementation to home-manager: - Documents: copies AGENTS.md, SOUL.md, TOOLS.md to workspace with appended Nix-managed tools report - Skills: supports copy and inline modes (symlink omitted for system service where it doesn't make sense) - Uses systemd-tmpfiles for installation The implementation is kept separate to ease future consolidation with the home-manager module.
The `config` option (using generatedConfigOptions from upstream schema) was defined but never wired into config generation. Users setting `inst.config.*` would see no effect, which is confusing. The `configOverrides` option remains as the escape hatch for arbitrary JSON config. If typed schema options are needed later, they can be added with actual implementation.
Fail early with clear messages when: - Neither apiKeyFile nor oauthTokenFile is set for Anthropic - gateway.auth.tokenFile is missing when mode is "token" - gateway.auth.passwordFile is missing when mode is "password" Update test to provide dummy tokens to satisfy assertions.
The instances option was defined in both options.nix and clawdbot.nix. The one in clawdbot.nix always overrode the one in options.nix, making the latter dead code.
Update PR.md and CLAUDE.md with new repo URLs and naming.
|
It would be great if the anthropic provider was optional. I know it's the default, but I have my openrouter subscription. |
|
I would say the the nixosModule should apply the overlay automatically |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a NixOS module (
nixosModules.clawdbot) that runs the gateway as an isolated system user with systemd hardening.Closes #22
Important
Depends on #10 - This PR is based on cherry-picked commits from #10 (NixOS and aarch64-linux support). It should be rebased once #10 is merged.
Security Motivation
Currently the home-manager module runs the gateway as the user's personal account, giving the LLM full access to SSH keys, credentials, personal files, etc. Running as a dedicated locked-down system user contains the blast radius.
Features
clawdbotsystem user with minimal privilegesProtectHome=true,ProtectSystem=strictPrivateTmp=true,PrivateDevices=trueNoNewPrivileges=trueCapabilityBoundingSet=""(no capabilities)SystemCallFilter=@system-serviceinstances.<name>Usage
Test plan
nix build .#checks.x86_64-linux.nixos-clawdbot)Todos