-
-
Notifications
You must be signed in to change notification settings - Fork 51
feat(nixos): add NixOS module for isolated system user #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
What: - Remove hardcoded `/bin/` paths from activation scripts - Use home-manager's `run` function for mkdir and ln commands How: - Change `/bin/mkdir -p` and `/bin/ln -sfn` to `run mkdir -p` and `run ln -sfn` in clawdbotDirs and clawdbotConfigFiles activation blocks Why: - Hardcoded `/bin/mkdir` and `/bin/ln` don't exist on NixOS, where binaries live in the Nix store and are accessed via PATH - Using bare commands (mkdir, ln) works but breaks dry-run mode - `run` is the home-manager 25.11 convention, replacing the deprecated `$DRY_RUN_CMD`. It executes normally but only prints when using `home-manager switch --dry-run` - This matches home-manager's own modules (e.g., xdg-user-dirs.nix) Fixes: moltbot#5
These attributes were missing from defaultInstanceConfig, causing evaluation failures when using the simple configuration style.
Paths like ~/.clawdbot need to be expanded to absolute paths for systemd, which cannot interpret literal ~.
The gateway now expects telegram config under channels.telegram instead of top-level telegram key.
Upstream renamed messages.queue.byProvider to messages.queue.byChannel.
Had this error before: [telegram] handler failed: Error: Missing workspace template: AGENTS.md
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.
Both NixOS and home-manager now default to claude-sonnet-4 which is a sensible default (faster, cheaper). Users wanting opus can override.
This reverts commit adf7cd4.
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.
Rename all NixOS module files and references: - clawdbot.nix → moltbot.nix - services.clawdbot → services.moltbot - /var/lib/clawdbot → /var/lib/moltbot - user/group default: clawdbot → moltbot - Add MOLTBOT_* env vars as primary, keep CLAWDBOT_*/CLAWDIS_* for compat
Update flake.nix and NixOS module integration test to use moltbot naming.
Update PR.md and CLAUDE.md with new repo URLs and naming.
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