Files
nanobot/DEPLOYING-NIXOS.md

4.7 KiB

Deploying nanobot on NixOS

This documents what it took to get nanobot running on Camellia (NixOS 26.05).

See /etc/nixos/configuration.nix for the full config — every block below has a matching section there.

1. Base system

# Hostname, timezone, SSH, fail2ban
networking.hostName = "Camellia";
time.timeZone = "Asia/Shanghai";
services.openssh = { enable = true; ... };
services.fail2ban = { enable = true; };

2. Required packages

environment.systemPackages = with pkgs; [
  git curl wget vim tmux htop jq fd ripgrep
  python313 uv     # nanobot runtime
  nodejs_22        # for some skills/tools
];

3. Users

Create a dedicated user for nanobot with lingering enabled:

users.users.claw = {
  extraGroups = ["sudo"];
  isNormalUser = true;
  linger = true;   # user services survive logout
};
security.sudo.extraRules = [
  { users = ["claw"]; commands = [{ command = "ALL"; options = ["NOPASSWD"]; }]; }
];

nanobot's exec tool (and many scripts) expect /bin/bash:

systemd.tmpfiles.rules = [
  "L+ /bin/bash - - - - /run/current-system/sw/bin/bash"
  "L+ /usr/bin/env - - - - /run/current-system/sw/bin/env"
];

5. nanobot systemd user service

systemd.user.services.nanobot = {
  description = "nanobot AI assistant gateway";
  wantedBy = ["default.target"];
  serviceConfig = {
    Type = "simple";
    ExecStart = "/home/claw/nanobot-src/.venv/bin/python -m nanobot gateway --foreground --port 18790";
    Restart = "always";
    RestartSec = 5;
    WorkingDirectory = "/home/claw/.nanobot/workspace";
    Environment = "HOME=/home/claw";
  };
};

Use a git checkout (/home/claw/nanobot-src) so you can git checkout known-good to rollback.

6. Watchdog timer

Covers the case where systemd misses a crash:

systemd.user.services.nanobot-watchdog = {
  serviceConfig = {
    Type = "oneshot";
    ExecStart = "${pkgs.systemd}/bin/systemctl --user start nanobot";
  };
};
systemd.user.timers.nanobot-watchdog = {
  timerConfig = { OnUnitActiveSec = "60s"; };
};

7. Reverse proxy (Caddy)

nanobot's gateway binds to 127.0.0.1 by default and rejects non-local connections. For a dedicated VM this is over-cautious, so we use Caddy to reverse-proxy the WebSocket UI externally:

services.caddy = {
  enable = true;
  virtualHosts."http://192.168.122.194".extraConfig = ''
    reverse_proxy 127.0.0.1:8765
  '';
};

8. Proxy for outbound traffic (v2rayA)

If the LLM API and Telegram are blocked:

services.v2raya = { enable = true; };

Then configure proxy env vars in ~/.bashrc:

export http_proxy=http://127.0.0.1:20171
export https_proxy=http://127.0.0.1:20171
export all_proxy=socks5://127.0.0.1:20170

9. OOM protection

nanobot can spike memory. Without this, the kernel OOM killer takes it down:

services.earlyoom = {
  enable = true;
  freeMemThreshold = 5;
  freeSwapThreshold = 10;
};
swapDevices = [{ device = "/swapfile"; size = 2048; }];  # 2 GB

10. Firewall

networking.firewall.allowedTCPPorts = [
  22     # SSH
  80     # Caddy HTTP
  2017   # v2rayA web UI
  20170  # v2rayA SOCKS5
  20171  # v2rayA HTTP proxy
];

11. Kernel hardening

boot.kernel.sysctl = {
  "net.ipv4.tcp_syncookies" = 1;
  "net.ipv4.conf.all.rp_filter" = 1;
  "net.ipv4.conf.all.accept_redirects" = 0;
  "net.ipv6.conf.all.accept_redirects" = 0;
  "kernel.kptr_restrict" = 1;
  "kernel.dmesg_restrict" = 1;
};

12. Nix GC

nix.gc = { automatic = true; dates = "weekly"; options = "--delete-older-than 30d"; };

13. Mirrors (for China)

nix.settings.substituters = ["https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store"];

14. Install nanobot itself

# As claw user
git clone https://github.com/HKUDS/nanobot.git ~/nanobot-src
cd ~/nanobot-src
uv sync
uv run nanobot onboard   # interactive setup wizard
# Populate workspace: SOUL.md, USER.md, memory/MEMORY.md

15. Apply

Use the safe-switch wrapper to rebuild with health checks:

sudo /home/claw/.nanobot/workspace/scripts/safe-switch.sh

This runs nixos-rebuild switch, waits, then verifies the gateway is reachable. If the health check fails, it automatically rolls back.

Known quirks

  • sudo path: Use /run/wrappers/bin/sudo inside scripts, not the default PATH's /run/current-system/sw/bin/sudo (lacks setuid).
  • systemctl from cron/timers: Need XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus.
  • Gateway self-restart: Never systemctl --user restart nanobot from within nanobot's own process — it kills the parent. Use nohup bash -c 'sleep 2 && ...' &.
  • Rollback: cd ~/nanobot-src && git checkout known-good && systemctl --user restart nanobot.