DEV Community

Cover image for How I set up OpenClaw on NixOS
Danil Borkov
Danil Borkov

Posted on

How I set up OpenClaw on NixOS

Introduction

Every new dialogue with an assistant is a blank slate. It doesn't remember what we talked about in previous sessions. I wasn't okay with that. I started looking for a solution and settled on OpenClaw: declarative configuration, long-term memory via QMD, Telegram bot, GLM-4.7-flash — all reproducible on any machine with a single command.

Architecture

Architecture

There is OpenClaw Gateway. Attached to it are Telegram (communication channel), Workspace (files defining the assistant's personality), LLM (the model generating responses), and QMD (long-term memory via vector search). Everything is described in Nix.

Before NixOS, I was on Arch. Updates regularly broke the system: package versions drifted apart, dependencies didn't match. Plus I had to handle rollbacks manually. With NixOS, you configure it and forget it. If something goes wrong, roll back to a commit or boot a previous generation.

Breaking down flake.nix

{
  description = "NixOS configuration with Hyprland";

  inputs = {
    nixpkgs.url = "nixpkgs";
    home-manager.url = "github:nix-community/home-manager";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
    agenix.url = "github:ryantm/agenix";
    agenix.inputs.nixpkgs.follows = "nixpkgs";
    nix-openclaw.url = "github:openclaw/nix-openclaw";
    openclaw-workspace = {
      url = "path:/home/vokrob/.config/openclaw";
      flake = false;
    };
  };

  outputs = { self, nixpkgs, home-manager, agenix, nix-openclaw, openclaw-workspace, ... }@inputs: {
    nixosConfigurations.vokrob = nixpkgs.lib.nixosSystem {
      specialArgs = { inherit nix-openclaw openclaw-workspace; };
      system = "x86_64-linux";
      modules = [
        home-manager.nixosModules.home-manager
        agenix.nixosModules.default
        ./hosts/nixos
      ];
    };
  };
}
Enter fullscreen mode Exit fullscreen mode
  • nix-openclaw: flake from github:openclaw/nix-openclaw. Pulls in Home Manager modules, a package overlay, and a binary cache on cache.garnix.io. Garnix is useful because building OpenClaw doesn't rebuild dependencies from scratch;
  • openclaw-workspace: just a path, not a flake. A directory with files that define how the assistant behaves;
  • specialArgs: passes external inputs into the Nix module system. Without it, modules won't see openclaw-workspace.

I only have one host, but if I added a server or a laptop, the configuration would stay the same.

Modular architecture

The configuration is split into two levels.

System modules

Located in modules/nixos/default.nix. This includes base.nix (bootloader, kernel), networking.nix (NetworkManager), services.nix (Hyprland and AmneziaWG for me), security.nix (disabled password for power-off via Polkit), and users.nix (vokrob, agenix, zsh).

In base.nix I add the overlay:

nixpkgs.overlays = [
  nix-openclaw.overlays.default
  (import ../../overlays)
];
Enter fullscreen mode Exit fullscreen mode

The overlay adds OpenClaw packages to pkgs, including openclaw-gateway.

User modules

Imported via modules/home/default.nix. The main one is features/openclaw.nix.

Plus in hosts/nixos/default.nix I added:

home-manager.sharedModules = [nix-openclaw.homeManagerModules.openclaw];
Enter fullscreen mode Exit fullscreen mode

sharedModules makes the OpenClaw module available in all Home Manager configurations. If a second host appears, the module is already there.

Breaking down openclaw.nix

The main configuration is described in modules/home/features/openclaw.nix.

Workspace

programs.openclaw.workspace.bootstrapFiles = {
  agents = "${openclaw-workspace}/AGENTS.md";
  soul = "${openclaw-workspace}/SOUL.md";
  tools = "${openclaw-workspace}/TOOLS.md";
  identity = "${openclaw-workspace}/IDENTITY.md";
  user = "${openclaw-workspace}/USER.md";
};
Enter fullscreen mode Exit fullscreen mode

These five files shape the assistant's personality: AGENTS.md defines agent roles and routing, SOUL.md is the base instruction, TOOLS.md describes tools, IDENTITY.md sets communication style, USER.md holds my data.

The files are in ~/.config/openclaw/, and you can change them without rebuilding the system. Just edit SOUL.md, and the assistant will start behaving differently.

Secrets

programs.openclaw.environment = {
  ZHIPU_API_KEY = "/run/agenix/openclaw-zhipu-key";
  OPENCLAW_GATEWAY_TOKEN = "/run/agenix/openclaw-gateway-token";
};
Enter fullscreen mode Exit fullscreen mode

The values are not the keys themselves, but paths to files. OpenClaw reads secrets from files. Nix substitutes paths at build time, and agenix decrypts secrets during configuration activation and places them in /run/agenix/. They never end up in /nix/store/.

The Telegram token is passed separately:

channels.telegram.tokenFile = "/run/agenix/openclaw-telegram-token";
Enter fullscreen mode Exit fullscreen mode

Integrations

config = {
  gateway.mode = "local";

  channels.telegram = {
    tokenFile = "/run/agenix/openclaw-telegram-token";
    allowFrom = [5748618304];
  };
};
Enter fullscreen mode Exit fullscreen mode

The local mode means the gateway runs without being tied to the OpenClaw Cloud. Everything is on my machine. The gateway listens on a local port and authorizes requests via a token from /run/agenix/openclaw-gateway-token. Only I have access to the Telegram bot (the allowFrom list).

GLM-4.7-flash

models.providers.openai = {
  baseUrl = "https://open.bigmodel.cn/api/paas/v4";
  apiKey = {
    source = "env";
    provider = "default";
    id = "ZHIPU_API_KEY";
  };
  models = [{
    name = "glm-4.7-flash";
    id = "glm-4.7-flash";
    api = "openai-completions";
    contextWindow = 200000;
  }];
};
Enter fullscreen mode Exit fullscreen mode

The provider is called openai, but baseUrl points to Z.ai. Z.ai provides an OpenAI-compatible endpoint. Through api = "openai-completions", OpenClaw uses the standard OpenAI SDK. The glm-4.7-flash model is free, with a 200K token context window.

QMD — long-term memory

memory.backend = "qmd";
Enter fullscreen mode Exit fullscreen mode

QMD is a Qdrant-based sidecar that doesn't require a separate server.

Each message and response is vectorized, and embeddings go into QMD with metadata. When I write something new, semantic search picks up fragments from the past and mixes them into the prompt. The assistant remembers what we talked about in previous sessions.

I tested it — it remembers:

Memory

The "remember" and "forget" commands work through MEMORY.md. QMD just adds semantic search over the entire history on top.

Elevated privileges

tools.elevated = {
  enabled = true;
  allowFrom = {
    telegram = [5748618304];
  };
};
Enter fullscreen mode Exit fullscreen mode

This section grants access to dangerous tools via the Telegram bot: executing commands on the host, installing packages, managing processes.

Additional options

agents.defaults = {
  model.primary = "openai/glm-4.7-flash";
  thinkingDefault = "low";
  compaction.reserveTokensFloor = 20000;
};

reloadScript.enable = true;

bundledPlugins = {
  summarize.enable = true;
};
Enter fullscreen mode Exit fullscreen mode
  • thinkingDefault: reasoning depth. Set to low — everyday commands don't need deep thinking;
  • compaction.reserveTokensFloor: reserves 20K tokens for memory and system prompt. When I didn't reserve it, the context got eaten up and the assistant started making silly mistakes;
  • reloadScript: generates a config reload script without restarting the gateway;
  • bundledPlugins.summarize: automatically summarizes URLs and PDFs sent in the chat.

The gateway runs as a systemd user service: it starts when I log in and restarts if it crashes. I check logs via journalctl --user -u openclaw-gateway -f. If something goes wrong, I run systemctl --user restart openclaw-gateway.

Service

Secrets

In secrets/ there are three encrypted files:

  • openclaw-telegram-token.age;
  • openclaw-zhipu-key.age;
  • openclaw-gateway-token.age.

In the config it looks like this:

age.secrets = {
  "openclaw-telegram-token" = {
    file = ../../secrets/openclaw-telegram-token.age;
    owner = "vokrob";
    group = "users";
    mode = "0400";
  };
};
Enter fullscreen mode Exit fullscreen mode

Agenix encrypts files using age. The key is stored locally at ~/.config/agenix/age-key.txt. Decryption only happens during system activation. Secrets never end up in /nix/store/, so they can't be accidentally committed or exposed in the binary cache. Even if someone gains access to the store, they won't see the API keys.

Message lifecycle

I send a message to the Telegram bot. The Telegram bot sends it to the gateway. The gateway first checks allowFrom — if I'm on the list, it proceeds. Then the gateway queries QMD for context from past conversations. It builds a prompt: system instructions + memory context + my message. It sends this to GLM-4.7-flash. The model's response is saved as a new memory fragment by the gateway and sent back to Telegram.

The chain takes anywhere from a few seconds to several minutes due to the queue for the free model.

Problems

Service won't start

The first thing I ran into: the gateway wouldn't start. The reason: agenix didn't apply the activation script. Fixed by running sudo nixos-rebuild switch, then checking /run/agenix/:

ls -la /run/agenix/
Enter fullscreen mode Exit fullscreen mode

If the files aren't there, there's an error in the age.secrets definitions. In my case, I forgot to specify owner and mode, so agenix didn't create the files.

Telegram bot doesn't respond

Two possible causes: either allowFrom is wrong, or the token is invalid. You can find out your Telegram ID via @userinfobot. I made a mistake: I entered the chat ID instead of the user ID.

To check the token:

journalctl --user -u openclaw-gateway | grep -i telegram
Enter fullscreen mode Exit fullscreen mode

QMD doesn't return context

Right after setup, QMD is empty and the assistant responds as if it has no memory. This is normal. Embeddings accumulate as you interact. You just need to keep talking.

Gateway token mismatch

This can happen if you recreate the token without the gateway knowing about it:

agenix -e secrets/openclaw-gateway-token.age
systemctl --user restart openclaw-gateway
Enter fullscreen mode Exit fullscreen mode

Conclusion

It took an evening, but it was worth it. Now I have a reproducible AI assistant that remembers what we talked about and deploys with a single command. The configuration is identical on any machine: clone the repo, run nixos-rebuild, and you're done.

Code on GitHub. Replace the secrets with your own and build.

Top comments (0)