NixOS as the Immutable Host: Deploying a Sovereign OpenClaw Agent
Setting the Stage: Context for the Curious Book Reader
This entry documents a significant step in establishing sovereign AI infrastructure: the declarative deployment of OpenClaw, a cutting-edge agentic framework, onto a NixOS host. It details the journey from initial conceptualization to a fully functional, systemd-managed agent, highlighting the unique challenges and advantages of building an immutable ‘Forever Machine’ for AI in the Age of AI.
Technical Journal Entry Begins
Okay this is one of the most significant bits of coding I’ve done in my life, I think. Even all my recent AI-coding has been meticulous and measured. In the early days of AI-assisted coding I fell into many of the pitfalls and lost confidence in their ability to do almost anything without cornering them from almost every avenue of things going wrong. And here we are with agentic frameworks taking off and I have to get on that bandwagon. There are a number of contenders, but my gut tells me to forge ahead with OpenClaw. And so I did a pretty comprehensive article setting the stage. The sage advice from ChatGPT 5.2 was to systematically test things as I moved forward. And so:
The Nix-OpenClaw Flake Inspection
[mike@nixos:~/repos/pipulate]$ cd ../
[mike@nixos:~/repos]$ mkdir openclaw
[mike@nixos:~/repos]$ cd openclaw/
[mike@nixos:~/repos/openclaw]$ nix flake show github:openclaw/nix-openclaw
github:openclaw/nix-openclaw/3cb21cd49ea42e6a09246ffb9286aeb6bb4be92a?narHash=sha256-o3WLXUAi8r/fDVs2sDs/TEI2pmZEfQo%2BUCKlKyVVpME%3D
├───apps
│ ├───aarch64-darwin
│ │ └───openclaw: app: no description
│ └───x86_64-linux
│ └───openclaw: app: no description
├───checks
│ ├───aarch64-darwin
│ │ ├───config-validity omitted (use '--all-systems' to show)
│ │ ├───gateway omitted (use '--all-systems' to show)
│ │ └───package-contents omitted (use '--all-systems' to show)
│ └───x86_64-linux
│ ├───config-options: derivation 'openclaw-config-options-unstable-88caa4b5'
│ ├───config-validity: derivation 'openclaw-config-validity-unstable-88caa4b5'
│ ├───default-instance: derivation 'openclaw-default-instance-1'
│ ├───gateway: derivation 'openclaw-gateway-unstable-88caa4b5' - 'Telegram-first AI gateway (OpenClaw)'
│ ├───gateway-tests: derivation 'openclaw-gateway-tests-unstable-88caa4b5'
│ ├───hm-activation: derivation 'vm-test-run-openclaw-hm-activation'
│ └───package-contents: derivation 'openclaw-package-contents-unstable-88caa4b5'
├───darwinModules: unknown
├───devShells
│ ├───aarch64-darwin
│ │ └───default omitted (use '--all-systems' to show)
│ └───x86_64-linux
│ └───default: development environment 'nix-shell'
├───formatter
│ ├───aarch64-darwin omitted (use '--all-systems' to show)
│ └───x86_64-linux: package 'nixfmt-tree' - 'Official Nix formatter zero-setup starter using treefmt'
├───homeManagerModules: unknown
├───overlays
│ └───default: Nixpkgs overlay
└───packages
├───aarch64-darwin
│ ├───default omitted (use '--all-systems' to show)
│ ├───openclaw omitted (use '--all-systems' to show)
│ ├───openclaw-app omitted (use '--all-systems' to show)
│ ├───openclaw-gateway omitted (use '--all-systems' to show)
│ └───openclaw-tools omitted (use '--all-systems' to show)
└───x86_64-linux
├───default: package 'openclaw-2.0.0-beta5' - 'OpenClaw batteries-included bundle (gateway + app + tools)'
├───openclaw: package 'openclaw-2.0.0-beta5' - 'OpenClaw batteries-included bundle (gateway + app + tools)'
├───openclaw-gateway: package 'openclaw-gateway-unstable-88caa4b5' - 'Telegram-first AI gateway (OpenClaw)'
└───openclaw-tools: package 'openclaw-tools'
[mike@nixos:~/repos/openclaw]$
This nix-openclaw thing is from the official OpenClaw account itself on GitHub
but it has an extreme Nix Home Manager orientation which is something I’ve been
avoiding because of how it makes the wonderfully simple Nix philosophy so much
more complex. But I fear I may be making it more complex by not biting the
bullet and just going with Nix Home Manager. I’ve included the last 2 articles
where I plan this out as part of the context and I’m following ChatGPT 5.2’s
advice in progressing slowly. We’re going to take a baby-step forward each time
and bank our wins. Usually banging a win in this sort of work means a tiny
change to a git repo and the corresponding commit and push, but I haven’t even
gotten to that point yet. This is a bit of blasting out the pipes.
Embracing “The Loophole”: Integrating Agentic Frameworks on NixOS
Gemini 3 Pro: Context verified.
You have successfully established “Ground Truth.” The nix flake show output is the definitive proof we needed.
The Verdict:
- The Package Exists:
packages.x86_64-linux.openclaw-gatewayis right there. You do not need to build it from source. - The Module Gap is Real: You were right. There is
homeManagerModulesbut nonixosModules.
Regarding your fear: “I fear I may be making it more complex by not biting the bullet and just going with Nix Home Manager.”
Counter-point: You are building a Server, not a Desktop Environment.
Home Manager is designed to manage user sessions (dotfiles, themes, user services). OpenClaw, in your “Always-On / Sovereign” philosophy, is infrastructure. It should run whether you are logged in or not. Using configuration.nix (Systemd System Service) is actually the architecturally correct choice for a “Forever Machine” daemon, even if it requires a few extra lines of manual config to replace the Home Manager magic.
The “Pipe Blast” (Immediate Action)
Before you edit a single Nix file, let’s execute the “ChatGPT 5.2 Protocol”: Run the binary ephemerally.
You don’t need to install it to test it. Since you are already in a directory with the flake.lock (implied by your nix flake show command working on the URL, or just use the URL directly), run this:
# Verify the binary runs and check the CLI interface
nix run github:openclaw/nix-openclaw#openclaw-gateway -- --help
What to look for:
- Success: It prints the help menu. This confirms the binary is compatible with your kernel/glibc and isn’t crashing on startup.
- CLI Structure: Check if it says
Usage: openclaw-gateway [OPTIONS]or if it’s the subcommand styleUsage: openclaw gateway. This answers the debate we saw in the blog post about the correct launch command.
If that works, run the “Tools” binary (the CLI):
nix run github:openclaw/nix-openclaw#openclaw -- --help
(The default package usually contains the full bundle).
Next Step (after verification):
If the help menu appears, you are clear to proceed with the Manual Systemd Service plan from your article. You don’t need Home Manager. You just need to point systemd at that binary.
Me:
[mike@nixos:~/repos/openclaw]$ nix run github:openclaw/nix-openclaw#openclaw -- --help
do you want to allow configuration setting 'extra-substituters' to be set to 'https://cache.garnix.io' (y/N)?
Gemini 3 Pro: Type y and hit Enter.
This is standard and safe. The project uses Garnix for Continuous Integration (as seen in the garnix.yaml file in your context).
- If you say Yes: Nix will download the pre-built binaries. It’s fast.
- If you say No: Nix will attempt to compile the entire application (Node.js dependencies, Python environment, etc.) from source on your machine. It will take forever.
Once you hit y, it should pull the cache and then display the help menu. This is the “smoke test” we need to confirm the binary works on your system.
Me:
[mike@nixos:~/repos/openclaw]$ nix run github:openclaw/nix-openclaw#openclaw -- --help
do you want to allow configuration setting 'extra-substituters' to be set to 'https://cache.garnix.io' (y/N)? y
do you want to permanently mark this value as trusted (y/N)? y
do you want to allow configuration setting 'extra-trusted-public-keys' to be set to 'cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=' (y/N)?
Gemini 3 Pro: Type y and hit Enter.
This is the second half of the cache configuration. You just agreed to use the server (Garnix); now you are agreeing to trust its cryptographic signature.
- Why: Without this key, Nix cannot verify that the binaries it downloads from Garnix are legitimate. If you say “No”, it will ignore the cache and force you to compile everything from source (which could take 30+ minutes).
- Safety: The key
cache.garnix.io:CTFPy...matches the one in theflake.nixfile you reviewed earlier. It is safe to trust for this project.
After you hit y, Nix should download the pre-built openclaw binary and display the help menu. This is the moment of truth.
Me:
[mike@nixos:~/repos/openclaw]$ nix run github:openclaw/nix-openclaw#openclaw -- --help
do you want to allow configuration setting 'extra-substituters' to be set to 'https://cache.garnix.io' (y/N)? y
do you want to permanently mark this value as trusted (y/N)? y
do you want to allow configuration setting 'extra-trusted-public-keys' to be set to 'cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=' (y/N)? y
do you want to permanently mark this value as trusted (y/N)? y
warning: ignoring untrusted substituter 'https://cache.garnix.io', you are not a trusted user.
Run `man nix.conf` for more information on the `substituters` configuration option.
warning: ignoring the client-specified setting 'trusted-public-keys', because it is a restricted setting and you are not a trusted user
error: unable to execute '/nix/store/03xs201rmnq4vkw8dhmsikn1bgfvj7yw-openclaw-gateway-unstable-88caa4b5/bin/openclaw-gateway': No such file or directory
[mike@nixos:~/repos/openclaw]$
Gemini 3 Pro: This is a standard NixOS security feature blocking you.
The Problem:
You answered “y” (Yes), but NixOS ignored you because your user (mike) is not in the trusted-users list in configuration.nix.
Because it ignored the binary cache (Garnix), it likely tried to fall back to a local build or a different path, and failed to produce the binary at the expected location (hence No such file or directory).
The Fix: You need to grant yourself “Trusted” status so you can use binary caches. This is essential for OpenClaw, otherwise, you will be compiling Node.js and Python from source for hours.
1. Edit configuration.nix
Add the trusted-users line to your nix.settings block.
File: /home/mike/repos/nixos/configuration.nix
# ... existing config ...
# Enable flakes and nix-command experimental features
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# ⚠️ ADD THIS LINE:
nix.settings.trusted-users = [ "root" "mike" ];
# ... existing config ...
2. Rebuild System
Run your alias:
n
3. Try Again (The Smoke Test)
Now that you are trusted, run the command again. Say “y” to the prompts, and this time Nix will actually listen, download the pre-built binary from Garnix, and run it.
nix run github:openclaw/nix-openclaw#openclaw -- --help
(Note on the command): The error you saw referenced openclaw-gateway, but you ran #openclaw. The #openclaw package is likely a bundle that wraps the gateway. If this command fails again with “No such file”, try targeting the gateway directly to be sure:
nix run github:openclaw/nix-openclaw#openclaw-gateway -- --help
Me: Okay, I did that:
[mike@nixos:~/repos/nixos]$ git --no-pager diff
diff --git a/configuration.nix b/configuration.nix
index d201916..aadc6be 100644
--- a/configuration.nix
+++ b/configuration.nix
@@ -104,6 +104,9 @@ in
# Enable flakes and nix-command experimental features
nix.settings.experimental-features = [ "nix-command" "flakes" ];
+ # Adding for OpenClaw
+ nix.settings.trusted-users = [ "root" "mike" ];
+
# Networking
networking.hostName = "nixos";
networking.networkmanager.enable = true;
[mike@nixos:~/repos/nixos]$
…and:
[mike@nixos:~/repos/openclaw]$ nix run github:openclaw/nix-openclaw#openclaw-gateway -- --help
🦞 OpenClaw 2026.2.15 (unknown) — No $999 stand required.
Usage: openclaw [options] [command]
Options:
--dev Dev profile: isolate state under ~/.openclaw-dev, default gateway port 19001, and shift derived ports (browser/canvas)
-h, --help display help for command
--no-color Disable ANSI colors
--profile <name> Use a named profile (isolates OPENCLAW_STATE_DIR/OPENCLAW_CONFIG_PATH under ~/.openclaw-<name>)
-V, --version output the version number
Commands:
acp Agent Control Protocol tools
agent Agent commands
agents Manage isolated agents
approvals Exec approvals
browser Browser tools
channels Channel management
completion Generate shell completion script
config Config helpers
configure Configure wizard
cron Cron scheduler
daemon Gateway service (legacy alias)
dashboard Open the Control UI with your current token
devices Device pairing + token management
directory Directory commands
dns DNS helpers
docs Docs helpers
doctor Health checks + quick fixes for the gateway and channels
gateway Gateway control
health Gateway health
help display help for command
hooks Hooks tooling
logs Gateway logs
memory Memory commands
message Send, read, and manage messages
models Model configuration
node Node control
nodes Node commands
onboard Onboarding helpers
pairing Pairing helpers
plugins Plugin management
reset Reset local config/state (keeps the CLI installed)
sandbox Sandbox tools
security Security helpers
sessions Session management
setup Setup helpers
skills Skills management
status Gateway status
system System events, heartbeat, and presence
tui Terminal UI
uninstall Uninstall the gateway service + local data (CLI remains)
update CLI update helpers
webhooks Webhook helpers
## First Contact: Unraveling Configuration Schema Errors
Examples:
openclaw channels login --verbose
Link personal WhatsApp Web and show QR + connection logs.
openclaw message send --target +15555550123 --message "Hi" --json
Send via your web session and print JSON result.
openclaw gateway --port 18789
Run the WebSocket Gateway locally.
openclaw --dev gateway
Run a dev Gateway (isolated state/config) on ws://127.0.0.1:19001.
openclaw gateway --force
Kill anything bound to the default gateway port, then start it.
openclaw gateway ...
Gateway control via WebSocket.
openclaw agent --to +15555550123 --message "Run summary" --deliver
Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.
openclaw message send --channel telegram --target @mychat --message "Hi"
Send via your Telegram bot.
Docs: docs.openclaw.ai/cli
[mike@nixos:~/repos/openclaw]$
Gemini 3 Pro: This is the Green Light.
Architecting Sovereignty: The OpenClaw NixOS Module
The smoke test confirms:
- It Works: The binary runs on your NixOS kernel.
- The Command is
openclaw: The usage saysUsage: openclaw [options] [command]. - The Subcommand is
gateway: The help listsgatewayas a command.
We can now proceed with the Manual Systemd Service implementation.
Step 1: Update flake.nix
Add the openclaw input so your system can see the package we just tested.
File: /home/mike/repos/nixos/flake.nix
{
description = "Mike's NixOS Configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
# ⚡ The Sovereign Agent Input
openclaw.url = "github:openclaw/nix-openclaw";
};
outputs = { self, nixpkgs, openclaw, ... }@inputs: {
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
# Pass inputs so configuration.nix can see 'openclaw'
specialArgs = { inherit inputs; };
modules = [ ./configuration.nix ];
};
};
}
Step 2: Create openclaw.nix
This is the bridge. It defines the service, the config, and the permissions.
File: /home/mike/repos/nixos/openclaw.nix
{ config, pkgs, inputs, lib, ... }:
let
# Grab the package we just tested
openclawPkg = inputs.openclaw.packages.${pkgs.system}.default;
# Define the Config (JSON5)
# We put non-secret config here.
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
bind = "127.0.0.1";
port = baz; # Avoiding 8080 (Trilium) and qux (Flatnotes)
auth = { mode = "token"; };
};
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
# REPLACE with your numeric ID from @userinfobot
allowFrom = [ 12345678 ];
configWrites = false;
};
};
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
};
};
};
agents = {
defaults = {
model = {
primary = {
provider = "ollama";
model = "gemma3:latest"; # Ensure you have pulled this!
};
};
workspace = "/home/mike/.openclaw/workspace";
};
};
});
in
{
# Import the module to get necessary groups/users if defined (optional but safe)
# imports = [ inputs.openclaw.nixosModules.default ];
# Note: Since there is no nixosModule, we skip the import and define service manually.
systemd.services.openclaw = {
description = "OpenClaw Sovereign Agent";
after = [ "network.target" "ollama.service" ];
wantedBy = [ "multi-user.target" ];
# Add tools to the path so the agent can find them
path = with pkgs; [
openclawPkg
git
curl
jq
ripgrep
python3
nodejs
];
serviceConfig = {
User = "mike";
Group = "users";
WorkingDirectory = "/home/mike";
# Point to the Nix-generated config
Environment = "OPENCLAW_CONFIG_PATH=${openclawConfig}";
# Load secrets (Tokens) from this unmanaged file
EnvironmentFile = "-/var/lib/openclaw/.env";
# The Command we just verified
ExecStart = "${openclawPkg}/bin/openclaw gateway";
Restart = "always";
RestartSec = "5s";
};
};
# System Integration
environment.systemPackages = [ openclawPkg ];
networking.firewall.allowedTCPPorts = [ baz ];
# Ensure the secrets directory exists
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
}
Step 3: Update services.nix
File: /home/mike/repos/nixos/services.nix
{ config, pkgs, lib, ... }:
{
imports = [
./flatnotes.nix
./openclaw.nix # <--- Add this import
];
# ... existing config ...
services.caddy.virtualHosts."openclaw.nixos.local" = {
extraConfig = ''
reverse_proxy localhost:baz
tls internal
'';
};
}
Step 4: Rebuild & Initialize
- Rebuild:
n - Create Secrets:
# Create the file
sudo touch /var/lib/openclaw/.env
sudo chown mike:users /var/lib/openclaw/.env
sudo chmod 600 /var/lib/openclaw/.env
# Add your secrets
vim /var/lib/openclaw/.env
Paste:
OPENCLAW_GATEWAY_TOKEN=generate_a_random_string_here
TELEGRAM_BOT_TOKEN=your_bot_token_here
- Restart:
systemctl restart openclaw - Connect:
Go to
https://openclaw.nixos.local(orhttp://localhost:bazif Caddy isn’t ready) and enter the token.
Me: When you say “Update your flake.nix” do you mean to update my configuration.nix?
Gemini 3: No, I do not mean your configuration.nix.
They are two different files with two different jobs:
flake.nix: This is the Manifest of your system. It tells Nix where to get software (e.g., “Getnixpkgsfrom GitHub”, “Getopenclawfrom GitHub”).configuration.nix: This is the Blueprint of your system. It tells Nix what to install and how to configure it (e.g., “Install theopenclawpackage that you found in the flake”).
The Crucial Check
Look inside your ~/repos/nixos/ folder.
- Do you see a file named
flake.nix?- YES: You must edit it to add the
openclawinput. You cannot add inputs toconfiguration.nix. - NO: You are using the “Legacy” (Channels) method.
- YES: You must edit it to add the
Path A: “I have a flake.nix” (Most Likely)
You must edit flake.nix to tell it where to find OpenClaw, then edit configuration.nix (or openclaw.nix) to install it.
Path B: “I ONLY have configuration.nix” (The Loophole)
If you strictly use configuration.nix and do not have a flake.nix, you can cheat using builtins.getFlake. This allows you to pull in modern Flake-based packages without converting your whole system.
If you are strictly Path B, here is your modified openclaw.nix:
File: /home/mike/repos/nixos/openclaw.nix
{ config, pkgs, lib, ... }:
let
# ⚡ THE LOOPHOLE: Pull the flake directly without a system flake.nix
openclawSrc = builtins.getFlake "github:openclaw/nix-openclaw";
openclawPkg = openclawSrc.packages.${pkgs.system}.default;
# Define the Config (JSON5) - SAME AS BEFORE
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
bind = "127.0.0.1";
port = baz;
auth = { mode = "token"; };
};
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
allowFrom = [ 12345678 ]; # <--- Update this!
configWrites = false;
};
};
models = {
providers = {
ollama = { baseUrl = "http://127.0.0.1:11434"; };
};
};
agents = {
defaults = {
model = {
primary = { provider = "ollama"; model = "gemma3:latest"; };
};
workspace = "/home/mike/.openclaw/workspace";
};
};
});
in
{
systemd.services.openclaw = {
description = "OpenClaw Sovereign Agent";
after = [ "network.target" "ollama.service" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
openclawPkg git curl jq ripgrep python3 nodejs
];
serviceConfig = {
User = "mike";
Group = "users";
WorkingDirectory = "/home/mike";
Environment = "OPENCLAW_CONFIG_PATH=${openclawConfig}";
EnvironmentFile = "-/var/lib/openclaw/.env";
ExecStart = "${openclawPkg}/bin/openclaw gateway";
Restart = "always";
RestartSec = "5s";
};
};
environment.systemPackages = [ openclawPkg ];
networking.firewall.allowedTCPPorts = [ baz ];
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
}
Which path are you on? (Check your folder).
Me: Oh, I know very well that there’s no flake.nix in my nixos.
[mike@nixos:~/repos/nixos]$ ls -la
total 85800
drwxr-xr-x 4 mike users 4096 Feb 15 11:51 .
drwxr-xr-x 16 mike users 4096 Feb 15 11:26 ..
-rw-r--r-- 1 mike users 2234 Dec 27 17:22 ai-acceleration.nix
-rw-r--r-- 1 mike users 2814 Mar 18 2025 ai.py
-rw-r--r-- 1 mike users 33897 Jan 6 09:07 autognome.py
-rw-r--r-- 1 mike users 9084 Feb 15 11:51 configuration.nix
-rw-r--r-- 1 root root 57851 Nov 17 15:02 configuration.nix.old_big_pile
-rw-r--r-- 1 mike users 3428 Feb 10 04:38 en.utf-8.add
-rw-r--r-- 1 mike users 4825 Feb 10 04:38 en.utf-8.add.spl
-rw-r--r-- 1 mike users 2497 Feb 14 10:09 flatnotes.nix
drwxr-xr-x 8 mike users 4096 Feb 15 06:49 .git
-rw-r--r-- 1 mike users 38 Dec 30 02:04 .gitignore
-rw-r--r-- 1 mike users 1438 Nov 18 05:00 hardware-configuration.nix
-rw-r--r-- 1 root root 1423 Nov 17 14:16 hardware-configuration.nix.old
-rw-r--r-- 1 mike users 13736 Nov 15 09:01 init.lua
-rw-r--r-- 1 mike users 21909869 Nov 6 11:43 json
-rw-r--r-- 1 mike users 2213 Dec 30 01:55 packages.nix
-rw-r--r-- 1 mike users 7511 Nov 30 09:14 prompt.md
drwxr-xr-x 2 mike users 4096 Nov 30 10:11 scripts
-rw-r--r-- 1 mike users 285 Jan 7 03:59 secrets.nix
-rw-r--r-- 1 mike users 5248 Feb 14 11:02 services.nix
-rw-r--r-- 1 mike users 21909875 Nov 6 11:42 subprocess
-rw-r--r-- 1 mike users 21909868 Nov 6 11:43 sys
-rw-r--r-- 1 mike users 21909869 Nov 6 11:43 time
[mike@nixos:~/repos/nixos]$ cd /etc/nixos
nixos/ nixos.bak/
[mike@nixos:~/repos/nixos]$ cd /etc/nixos
[mike@nixos:/etc/nixos]$ ls -la
total 68
drwxr-xr-x 2 root root 4096 Nov 18 05:00 .
drwxr-xr-x 43 root root 4096 Feb 15 11:51 ..
lrwxrwxrwx 1 root root 40 Nov 18 05:00 configuration.nix -> /home/mike/repos/nixos/configuration.nix
-rw-r--r-- 1 root root 57851 Nov 17 16:50 configuration-original.nix
lrwxrwxrwx 1 root root 49 Nov 18 05:00 hardware-configuration.nix -> /home/mike/repos/nixos/hardware-configuration.nix
[mike@nixos:/etc/nixos]$
Gemini 3 Pro: That is the definitive confirmation. You are on Path B (The Loophole).
Since you are symlinking /etc/nixos/configuration.nix to your repo and have no flake.nix, you must use builtins.getFlake to grab OpenClaw. This is the “magic trick” that lets legacy configs use modern flake packages.
Here is your complete implementation plan.
Step 1: Create openclaw.nix
Create this new file in your repo. It fetches the flake directly, extracts the package, generates the config, and sets up the service.
File: /home/mike/repos/nixos/openclaw.nix
{ config, pkgs, lib, ... }:
let
# ⚡ THE LOOPHOLE: Pull the flake directly without a system flake.nix
# We use the specific commit hash or just the main branch.
# Since you are "trusted", Nix will fetch this during rebuild.
openclawSrc = builtins.getFlake "github:openclaw/nix-openclaw";
# Extract the package for your system (x86_64-linux)
openclawPkg = openclawSrc.packages.${pkgs.system}.default;
# Define the Config (JSON5)
# This generates a read-only file in the Nix store.
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
bind = "127.0.0.1";
port = baz; # Avoiding 8080 (Trilium) and qux (Flatnotes)
auth = { mode = "token"; };
};
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
# REPLACE with your actual Telegram User ID!
allowFrom = [ 12345678 ];
configWrites = false;
};
};
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
};
};
};
agents = {
defaults = {
model = {
primary = {
provider = "ollama";
model = "gemma3:latest"; # Ensure you have this pulled in Ollama!
};
};
# Mutable workspace for skills/logs in your home dir
workspace = "/home/mike/.openclaw/workspace";
};
};
});
in
{
systemd.services.openclaw = {
description = "OpenClaw Sovereign Agent";
after = [ "network.target" "ollama.service" ];
wantedBy = [ "multi-user.target" ];
# Add tools to the path so the agent can find them
path = with pkgs; [
openclawPkg
git
curl
jq
ripgrep
python3
nodejs
];
serviceConfig = {
# ⚠️ Run as YOU to access your repos
User = "mike";
Group = "users";
WorkingDirectory = "/home/mike";
# Point to the Nix-generated config
Environment = "OPENCLAW_CONFIG_PATH=${openclawConfig}";
# Load secrets (Tokens) from this unmanaged file
EnvironmentFile = "-/var/lib/openclaw/.env";
# The Binary
ExecStart = "${openclawPkg}/bin/openclaw gateway";
Restart = "always";
RestartSec = "5s";
};
};
# Install the CLI for manual use
environment.systemPackages = [ openclawPkg ];
# Open the firewall (even if strictly unnecessary with 127.0.0.1, good for debugging)
networking.firewall.allowedTCPPorts = [ baz ];
# Ensure the secrets directory exists and is secure
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
}
Step 2: Update services.nix
Import the new file.
File: /home/mike/repos/nixos/services.nix
{ config, pkgs, lib, ... }:
{
imports = [
./flatnotes.nix
./openclaw.nix # <--- Add this line
];
# ... existing config ...
# Optional: Caddy Reverse Proxy
services.caddy.virtualHosts."openclaw.nixos.local" = {
extraConfig = ''
reverse_proxy localhost:baz
tls internal
'';
};
}
Step 3: The “Handshake” (Secrets)
- Rebuild:
n
(Note: This might take a moment as it fetches the flake for the first time).
- Create Secrets:
The service might fail to start initially because the
.envfile is missing. That is expected. ```bashCreate the file
sudo touch /var/lib/openclaw/.env sudo chown mike:users /var/lib/openclaw/.env sudo chmod 600 /var/lib/openclaw/.env
Add your secrets
vim /var/lib/openclaw/.env
**Paste:**
```bash
OPENCLAW_GATEWAY_TOKEN=generate_random_string
TELEGRAM_BOT_TOKEN=your_bot_token
- Restart:
systemctl restart openclaw
You are now running a Flake-based package inside a legacy NixOS configuration. Welcome to the loophole.
Me: Your instructions on editing the services.nix file don’t look correct.
Gemini 3 Pro: Context verified.
You have a sharp eye. You are absolutely correct—my previous instruction to import a Module (openclaw.nix) inside a file that is acting as a Config Set (services.nix) was messy and architecturally inconsistent with your setup.
In your codebase, services.nix treats flatnotes.nix as a Package (using callPackage), whereas openclaw.nix is a System Module (defining systemd.services). Mixing them inside services.nix is confusing.
The cleanest, “Nix-Native” way is to treat openclaw.nix as a first-class sibling to services.nix and ai-acceleration.nix.
Here is the corrected, simplified plan.
1. Create openclaw.nix (The All-In-One Module)
We will put the Service, the Config, and the Caddy Reverse Proxy rule in this file. This makes the agent “Sovereign”—if you delete this file, all traces of the agent (service + web routing) disappear.
File: /home/mike/repos/nixos/openclaw.nix
{ config, pkgs, lib, ... }:
let
# ⚡ THE LOOPHOLE: Pull the flake directly
openclawSrc = builtins.getFlake "github:openclaw/nix-openclaw";
openclawPkg = openclawSrc.packages.${pkgs.system}.default;
# Define the Config (JSON5)
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
bind = "127.0.0.1";
port = baz;
auth = { mode = "token"; };
};
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
allowFrom = [ 12345678 ]; # <--- REPLACE WITH YOUR ID
configWrites = false;
};
};
models = {
providers = {
ollama = { baseUrl = "http://127.0.0.1:11434"; };
};
};
agents = {
defaults = {
model = {
primary = { provider = "ollama"; model = "gemma3:latest"; };
};
workspace = "/home/mike/.openclaw/workspace";
};
};
});
in
{
# 1. The Service
systemd.services.openclaw = {
description = "OpenClaw Sovereign Agent";
after = [ "network.target" "ollama.service" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
openclawPkg git curl jq ripgrep python3 nodejs
];
serviceConfig = {
User = "mike";
Group = "users";
WorkingDirectory = "/home/mike";
Environment = "OPENCLAW_CONFIG_PATH=${openclawConfig}";
EnvironmentFile = "-/var/lib/openclaw/.env";
ExecStart = "${openclawPkg}/bin/openclaw gateway";
Restart = "always";
RestartSec = "5s";
};
};
# 2. The Web Routing (Self-Contained)
# NixOS will merge this with the Caddy config in services.nix
services.caddy.virtualHosts."openclaw.nixos.local" = {
extraConfig = ''
reverse_proxy localhost:baz
tls internal
'';
};
# 3. System Integration
environment.systemPackages = [ openclawPkg ];
networking.firewall.allowedTCPPorts = [ baz ];
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
}
2. Update configuration.nix (The Blueprint)
Add the new module to your main import list. This is where it belongs.
File: /home/mike/repos/nixos/configuration.nix
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
./packages.nix
./services.nix
./ai-acceleration.nix
# ⚡ The New Sovereign Agent
./openclaw.nix
]
# Conditional import: Only import secrets.nix if it exists
++ (if builtins.pathExists ./secrets.nix then [ ./secrets.nix ] else []);
3. Do NOT edit services.nix
Leave it exactly as it is. This isolates the risk.
4. Rebuild & Handshake
- Rebuild:
n - Secrets:
sudo touch /var/lib/openclaw/.env sudo chown mike:users /var/lib/openclaw/.env sudo chmod 600 /var/lib/openclaw/.env # Add OPENCLAW_GATEWAY_TOKEN=... and TELEGRAM_BOT_TOKEN=... vim /var/lib/openclaw/.env - Start:
systemctl restart openclaw
Me: Okay, that was a HUGE build but astoundingly it all built and exited
without an error. I still have this in openclaw.nix and I don’t really know
where to get mine:
allowFrom = [ 12345678 ]; # <--- REPLACE WITH YOUR ID
I only just built it so far and haven’t done the follow-up steps.
Gemini 3 Pro: Congratulations on the build! Getting a custom module with a remote flake input to compile on the first try is a major victory. You are now running a sovereign agent.
Here is how to get that missing number so you can finish the configuration.
How to Get Your Telegram ID
- Open Telegram on your phone or desktop.
- Search for the user
@userinfobot.- (Make sure it’s the one with the verified checkmark or many users, as there are copycats. The real one is very simple).
- Click Start (or type
/start). - It will immediately reply with a small block of text.
- Look for the line that says
Id: 123456789.
- Look for the line that says
- That number is your ID.
The Fix
- Edit the file:
vim /home/mike/repos/nixos/openclaw.nix - Replace the placeholder:
Change
12345678to your real ID. - Rebuild one last time:
n
Then, The Final Handshake
Once the rebuild finishes with your real ID:
- Create the secrets file (if you haven’t yet): ```bash sudo touch /var/lib/openclaw/.env sudo chown mike:users /var/lib/openclaw/.env sudo chmod 600 /var/lib/openclaw/.env
2. **Add the tokens:**
`vim /var/lib/openclaw/.env`
```bash
OPENCLAW_GATEWAY_TOKEN=your_random_string
TELEGRAM_BOT_TOKEN=your_bot_token_from_botfather
- Restart:
systemctl restart openclaw
You are minutes away from talking to your new agent.
Me: Okay, I got and edited my telegram ID into openclaw.nix and rebuilt
which went fine. I am now editing /var/lib/openclaw/.env and I can guess that
I’m supposed to just put random text in for OPENCLAW_GATEWAY_TOKEN or am I
actually supposed to get it from somewhere? And for
TELEGRAM_BOT_TOKEN=your_bot_token_from_botfather I have no idea where the
your_bot_token_from_botfather value is supposed to come from.
Gemini 3 Pro: Great progress. You are past the hard part (building the software). Now you are just configuring the keys to the castle.
Here is exactly where those two values come from.
1. OPENCLAW_GATEWAY_TOKEN (The Web Password)
Your guess was correct. This is a password you invent right now.
- What it is: The “password” you will type into the web interface (
https://openclaw.nixos.local) to log in for the first time. - How to get it: Just make up a long, secure string. You can smash your keyboard or generate one.
- Command to generate one (optional): ```bash openssl rand -hex 32
(Copy the output of that command and use it as your token).
### 2. `TELEGRAM_BOT_TOKEN` (The Bot Identity)
This one comes from Telegram itself. You need to create a "Bot" that OpenClaw will control.
**How to get it:**
1. Open Telegram.
2. Search for **`@BotFather`** (It has a blue checkmark).
3. Click **Start** (or type `/start`).
4. Type or click **`/newbot`**.
5. **Name it:** Give it a display name (e.g., "Mike's Agent").
6. **Username it:** Give it a unique username that ends in `bot` (e.g., `MikeLevAgentBot`).
7. **The Token:** BotFather will reply with a message saying "Done! Congratulations on your new bot."
* Look for the long string of characters under **"Use this token to access the HTTP API:"**.
* It looks like `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`.
**That string is your `TELEGRAM_BOT_TOKEN`.**
### The Final Step
Paste both into your `.env` file:
```bash
# Example
OPENCLAW_GATEWAY_TOKEN=a1b2c3d4e5f6... (your random string)
TELEGRAM_BOT_TOKEN=123456789:ABCdef... (from BotFather)
Save the file, then restart the service:
systemctl restart openclaw
Check if it’s alive:
systemctl status openclaw
If it says “active (running)”, you are live. Go to https://openclaw.nixos.local (or http://localhost:baz) and log in with your Gateway Token.
Me:
[mike@nixos:~/repos/nixos]$ systemctl restart openclaw
[mike@nixos:~/repos/nixos]$ systemctl status openclaw
● openclaw.service - OpenClaw Sovereign Agent
Loaded: loaded (/etc/systemd/system/openclaw.service; enabled; preset: ignored)
Active: activating (auto-restart) (Result: exit-code) since Sun 2026-02-15 12:41:14 EST; 926ms ago
Invocation: 8a9dbd6e492a4b4383e96e33bf475913
Process: 142469 ExecStart=/nix/store/hiw6hkvg4a42as9325pp34dnxgr392g8-openclaw-2.0.0-beta5/bin/openclaw gateway (code=exited, status=1/FAILURE)
Main PID: 142469 (code=exited, status=1/FAILURE)
IP: 0B in, 0B out
IO: 0B read, 0B written
Mem peak: 217.2M
CPU: 4.578s
[mike@nixos:~/repos/nixos]$
Gemini 3 Pro: It crashed, but that’s okay. It ran for 4 seconds (CPU: 4.578s), which means the binary works and it started up, but it hit a configuration or permission wall and exited.
We need the Logs to see why it died. systemctl status only shows that it died, not the error message.
Run this command to see the last 50 lines of the service log:
journalctl -u openclaw -n 50 --no-pager
Paste the output here.
Most likely suspects:
- Schema Mismatch: The config file generated by Nix might have a key the agent doesn’t like.
- Permissions: It might not be able to read the
.envfile or write to theworkspacedirectory. - Ollama: It might be failing to connect to Ollama if the service isn’t fully up.
Show me the logs, and we’ll fix it immediately.
Me:
[mike@nixos:~/repos/nixos]$ journalctl -u openclaw -n 50 --no-pager
Feb 15 12:42:10 nixos openclaw[142623]: ◇ Doctor ──────────────────────────────────────────────╮
Feb 15 12:42:10 nixos openclaw[142623]: │ │
Feb 15 12:42:10 nixos openclaw[142623]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:42:10 nixos openclaw[142623]: │ │
Feb 15 12:42:10 nixos openclaw[142623]: ├───────────────────────────────────────────────────────╯
Feb 15 12:42:10 nixos openclaw[142623]: Config invalid
Feb 15 12:42:10 nixos openclaw[142623]: File: /nix/store/22m743p3nl6az5a2p9n7693sdyx0qc46-openclaw.json5
Feb 15 12:42:10 nixos openclaw[142623]: Problem:
Feb 15 12:42:10 nixos openclaw[142623]: - models.providers.ollama.models: Invalid input: expected array, received undefined
Feb 15 12:42:10 nixos openclaw[142623]: - agents.defaults.model.primary: Invalid input: expected string, received object
Feb 15 12:42:10 nixos openclaw[142623]: - gateway.bind: Invalid input
Feb 15 12:42:10 nixos openclaw[142623]: Run: openclaw doctor --fix
Feb 15 12:42:10 nixos systemd[1]: openclaw.service: Main process exited, code=exited, status=1/FAILURE
Feb 15 12:42:10 nixos systemd[1]: openclaw.service: Failed with result 'exit-code'.
Feb 15 12:42:10 nixos systemd[1]: openclaw.service: Consumed 4.459s CPU time, 217M memory peak.
Feb 15 12:42:15 nixos systemd[1]: openclaw.service: Scheduled restart job, restart counter is at 8.
Feb 15 12:42:15 nixos systemd[1]: Started OpenClaw Sovereign Agent.
Feb 15 12:42:18 nixos openclaw[142638]: Invalid config at /nix/store/22m743p3nl6az5a2p9n7693sdyx0qc46-openclaw.json5:\n- models.providers.ollama.models: Invalid input: expected array, received undefined
Feb 15 12:42:18 nixos openclaw[142638]: - agents.defaults.model.primary: Invalid input: expected string, received object
Feb 15 12:42:18 nixos openclaw[142638]: - gateway.bind: Invalid input
Feb 15 12:42:18 nixos openclaw[142638]: │
Feb 15 12:42:18 nixos openclaw[142638]: ◇ Config ───────────────────────────────────────────────────╮
Feb 15 12:42:18 nixos openclaw[142638]: │ │
Feb 15 12:42:18 nixos openclaw[142638]: │ Config invalid; doctor will run with best-effort config. │
Feb 15 12:42:18 nixos openclaw[142638]: │ │
Feb 15 12:42:18 nixos openclaw[142638]: ├────────────────────────────────────────────────────────────╯
Feb 15 12:42:18 nixos openclaw[142638]: │
Feb 15 12:42:18 nixos openclaw[142638]: ◇ Doctor changes ──────────────────────────────╮
Feb 15 12:42:18 nixos openclaw[142638]: │ │
Feb 15 12:42:18 nixos openclaw[142638]: │ Telegram configured, enabled automatically. │
Feb 15 12:42:18 nixos openclaw[142638]: │ │
Feb 15 12:42:18 nixos openclaw[142638]: ├───────────────────────────────────────────────╯
Feb 15 12:42:18 nixos openclaw[142638]: │
Feb 15 12:42:18 nixos openclaw[142638]: ◇ Doctor ──────────────────────────────────────────────╮
Feb 15 12:42:18 nixos openclaw[142638]: │ │
Feb 15 12:42:18 nixos openclaw[142638]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:42:18 nixos openclaw[142638]: │ │
Feb 15 12:42:18 nixos openclaw[142638]: ├───────────────────────────────────────────────────────╯
Feb 15 12:42:18 nixos openclaw[142638]: Config invalid
Feb 15 12:42:18 nixos openclaw[142638]: File: /nix/store/22m743p3nl6az5a2p9n7693sdyx0qc46-openclaw.json5
Feb 15 12:42:18 nixos openclaw[142638]: Problem:
Feb 15 12:42:18 nixos openclaw[142638]: - models.providers.ollama.models: Invalid input: expected array, received undefined
Feb 15 12:42:18 nixos openclaw[142638]: - agents.defaults.model.primary: Invalid input: expected string, received object
Feb 15 12:42:18 nixos openclaw[142638]: - gateway.bind: Invalid input
Feb 15 12:42:18 nixos openclaw[142638]: Run: openclaw doctor --fix
Feb 15 12:42:18 nixos systemd[1]: openclaw.service: Main process exited, code=exited, status=1/FAILURE
Feb 15 12:42:18 nixos systemd[1]: openclaw.service: Failed with result 'exit-code'.
Feb 15 12:42:18 nixos systemd[1]: openclaw.service: Consumed 4.561s CPU time, 216.5M memory peak.
Feb 15 12:42:23 nixos systemd[1]: openclaw.service: Scheduled restart job, restart counter is at 9.
Feb 15 12:42:23 nixos systemd[1]: Started OpenClaw Sovereign Agent.
[mike@nixos:~/repos/nixos]$
Gemini 3 Pro: The logs are beautiful. They tell us exactly what is wrong.
The OpenClaw configuration schema is very strict (it uses a validator like Zod), and our “best guess” config violated it in three places.
The Errors & The Fixes
- Error:
gateway.bind: Invalid input- Fix: The config validator hates the
bindkey here. We will remove it. The gateway defaults to127.0.0.1anyway, which is what we want.
- Fix: The config validator hates the
- Error:
models.providers.ollama.models: Invalid input: expected array- Fix: We didn’t list the models. You must explicitly whitelist the models you want OpenClaw to see.
- Error:
agents.defaults.model.primary: Invalid input: expected string- Fix: We passed an object (
{ provider = ... }). It just wants the name of the model (e.g.,"gemma3:latest").
- Fix: We passed an object (
1. Edit openclaw.nix
We need to rewrite the openclawConfig block with the corrected schema.
File: /home/mike/repos/nixos/openclaw.nix
{ config, pkgs, lib, ... }:
let
# ⚡ THE LOOPHOLE: Pull the flake directly
openclawSrc = builtins.getFlake "github:openclaw/nix-openclaw";
openclawPkg = openclawSrc.packages.${pkgs.system}.default;
# Define the Config (JSON5)
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
# REMOVED: "bind" caused a schema error. Default is 127.0.0.1.
port = baz;
auth = { mode = "token"; };
};
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
allowFrom = [ 12345678 ]; # <--- ENSURE YOUR ID IS HERE
configWrites = false;
};
};
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
# FIX 1: Explicitly list the models you want to use
models = [ "gemma3:latest" ];
};
};
};
agents = {
defaults = {
model = {
# FIX 2: Pass a string, not an object
primary = "gemma3:latest";
};
workspace = "/home/mike/.openclaw/workspace";
};
};
});
in
{
# 1. The Service
systemd.services.openclaw = {
description = "OpenClaw Sovereign Agent";
after = [ "network.target" "ollama.service" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
openclawPkg git curl jq ripgrep python3 nodejs
];
serviceConfig = {
User = "mike";
Group = "users";
WorkingDirectory = "/home/mike";
Environment = "OPENCLAW_CONFIG_PATH=${openclawConfig}";
EnvironmentFile = "-/var/lib/openclaw/.env";
ExecStart = "${openclawPkg}/bin/openclaw gateway";
Restart = "always";
RestartSec = "5s";
};
};
# 2. The Web Routing
services.caddy.virtualHosts."openclaw.nixos.local" = {
extraConfig = ''
reverse_proxy localhost:baz
tls internal
'';
};
# 3. System Integration
environment.systemPackages = [ openclawPkg ];
networking.firewall.allowedTCPPorts = [ baz ];
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
}
2. Rebuild & Restart
- Rebuild:
n - Restart the service:
systemctl restart openclaw - Check the logs again:
journalctl -u openclaw -n 50 --no-pager
What to look for:
You want to see a line that says Gateway listening on http://127.0.0.1:baz. If you see that, you are live.
Me: Okay, I did that:
[mike@nixos:~/repos/nixos]$ systemctl restart openclaw
[mike@nixos:~/repos/nixos]$ journalctl -u openclaw -n 50 --no-pager
Feb 15 12:46:25 nixos openclaw[144829]: │ │
Feb 15 12:46:25 nixos openclaw[144829]: │ Telegram configured, enabled automatically. │
Feb 15 12:46:25 nixos openclaw[144829]: │ │
Feb 15 12:46:25 nixos openclaw[144829]: ├───────────────────────────────────────────────╯
Feb 15 12:46:25 nixos openclaw[144829]: │
Feb 15 12:46:25 nixos openclaw[144829]: ◇ Doctor ──────────────────────────────────────────────╮
Feb 15 12:46:25 nixos openclaw[144829]: │ │
Feb 15 12:46:25 nixos openclaw[144829]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:46:25 nixos openclaw[144829]: │ │
Feb 15 12:46:25 nixos openclaw[144829]: ├───────────────────────────────────────────────────────╯
Feb 15 12:46:25 nixos openclaw[144829]: Config invalid
Feb 15 12:46:25 nixos openclaw[144829]: File: /nix/store/jr7096wka9xra93zhq3xwzfyavpgl88w-openclaw.json5
Feb 15 12:46:25 nixos openclaw[144829]: Problem:
Feb 15 12:46:25 nixos openclaw[144829]: - models.providers.ollama.models.0: Invalid input: expected object, received string
Feb 15 12:46:25 nixos openclaw[144829]: Run: openclaw doctor --fix
Feb 15 12:46:25 nixos systemd[1]: openclaw.service: Main process exited, code=exited, status=1/FAILURE
Feb 15 12:46:25 nixos systemd[1]: openclaw.service: Failed with result 'exit-code'.
Feb 15 12:46:25 nixos systemd[1]: openclaw.service: Consumed 4.455s CPU time, 213.9M memory peak.
Feb 15 12:46:26 nixos systemd[1]: Stopped OpenClaw Sovereign Agent.
Feb 15 12:46:26 nixos systemd[1]: openclaw.service: Consumed 4.455s CPU time, 213.9M memory peak.
Feb 15 12:46:26 nixos systemd[1]: Started OpenClaw Sovereign Agent.
Feb 15 12:46:29 nixos openclaw[144859]: Invalid config at /nix/store/jr7096wka9xra93zhq3xwzfyavpgl88w-openclaw.json5:\n- models.providers.ollama.models.0: Invalid input: expected object, received string
Feb 15 12:46:29 nixos openclaw[144859]: │
Feb 15 12:46:29 nixos openclaw[144859]: ◇ Config ───────────────────────────────────────────────────╮
Feb 15 12:46:29 nixos openclaw[144859]: │ │
Feb 15 12:46:29 nixos openclaw[144859]: │ Config invalid; doctor will run with best-effort config. │
Feb 15 12:46:29 nixos openclaw[144859]: │ │
Feb 15 12:46:29 nixos openclaw[144859]: ├────────────────────────────────────────────────────────────╯
Feb 15 12:46:29 nixos openclaw[144859]: │
Feb 15 12:46:29 nixos openclaw[144859]: ◇ Doctor changes ──────────────────────────────╮
Feb 15 12:46:29 nixos openclaw[144859]: │ │
Feb 15 12:46:29 nixos openclaw[144859]: │ Telegram configured, enabled automatically. │
Feb 15 12:46:29 nixos openclaw[144859]: │ │
Feb 15 12:46:29 nixos openclaw[144859]: ├───────────────────────────────────────────────╯
Feb 15 12:46:29 nixos openclaw[144859]: │
Feb 15 12:46:29 nixos openclaw[144859]: ◇ Doctor ──────────────────────────────────────────────╮
Feb 15 12:46:29 nixos openclaw[144859]: │ │
Feb 15 12:46:29 nixos openclaw[144859]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:46:29 nixos openclaw[144859]: │ │
Feb 15 12:46:29 nixos openclaw[144859]: ├───────────────────────────────────────────────────────╯
Feb 15 12:46:29 nixos openclaw[144859]: Config invalid
Feb 15 12:46:29 nixos openclaw[144859]: File: /nix/store/jr7096wka9xra93zhq3xwzfyavpgl88w-openclaw.json5
Feb 15 12:46:29 nixos openclaw[144859]: Problem:
Feb 15 12:46:29 nixos openclaw[144859]: - models.providers.ollama.models.0: Invalid input: expected object, received string
Feb 15 12:46:29 nixos openclaw[144859]: Run: openclaw doctor --fix
Feb 15 12:46:29 nixos systemd[1]: openclaw.service: Main process exited, code=exited, status=1/FAILURE
Feb 15 12:46:29 nixos systemd[1]: openclaw.service: Failed with result 'exit-code'.
Feb 15 12:46:29 nixos systemd[1]: openclaw.service: Consumed 4.475s CPU time, 212.5M memory peak.
Feb 15 12:46:34 nixos systemd[1]: openclaw.service: Scheduled restart job, restart counter is at 1.
Feb 15 12:46:34 nixos systemd[1]: Started OpenClaw Sovereign Agent.
[mike@nixos:~/repos/nixos]$
And here’s my diff:
[mike@nixos:~/repos/nixos]$ git --no-pager diff
diff --git a/openclaw.nix b/openclaw.nix
index 78ad0e2..b640757 100644
--- a/openclaw.nix
+++ b/openclaw.nix
@@ -9,27 +9,35 @@ let
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
- bind = "127.0.0.1";
+ # REMOVED: "bind" caused a schema error. Default is 127.0.0.1.
port = baz;
auth = { mode = "token"; };
};
+
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
- allowFrom = [ 12345678 ]; # <--- REPLACE WITH YOUR ID
+ allowFrom = [ ******** ]; # <--- ENSURE YOUR ID IS HERE (hidden for prompt)
configWrites = false;
};
};
+
models = {
providers = {
- ollama = { baseUrl = "http://127.0.0.1:11434"; };
+ ollama = {
+ baseUrl = "http://127.0.0.1:11434";
+ # FIX 1: Explicitly list the models you want to use
+ models = [ "gemma3:latest" ];
+ };
};
};
+
agents = {
defaults = {
model = {
- primary = { provider = "ollama"; model = "gemma3:latest"; };
+ # FIX 2: Pass a string, not an object
+ primary = "gemma3:latest";
};
workspace = "/home/mike/.openclaw/workspace";
};
@@ -60,8 +68,7 @@ in
};
};
- # 2. The Web Routing (Self-Contained)
- # NixOS will merge this with the Caddy config in services.nix
+ # 2. The Web Routing
services.caddy.virtualHosts."openclaw.nixos.local" = {
extraConfig = ''
reverse_proxy localhost:baz
@@ -76,4 +83,4 @@ in
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
-}
\ No newline at end of file
+}
[mike@nixos:~/repos/nixos]$
Gemini 3 Pro: Context verified.
We are incredibly close. The logs show that we fixed 2 out of 3 errors! The bind error is gone, and the primary model error is gone.
The only one left is this:
models.providers.ollama.models.0: Invalid input: expected object, received string
This means OpenClaw expects the models list to contain Objects (e.g., { id = "name"; }), not just plain strings.
The Fix
We need to wrap the model name in a small object structure.
File: /home/mike/repos/nixos/openclaw.nix
Change the models block to looks like this:
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
# FIX: Wrap the model string in an object with an 'id'
models = [ { id = "gemma3:latest"; } ];
};
};
};
Complete File Content (for easy copy/paste)
{ config, pkgs, lib, ... }:
let
# ⚡ THE LOOPHOLE: Pull the flake directly
openclawSrc = builtins.getFlake "github:openclaw/nix-openclaw";
openclawPkg = openclawSrc.packages.${pkgs.system}.default;
# Define the Config (JSON5)
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
port = baz;
auth = { mode = "token"; };
};
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
allowFrom = [ 12345678 ]; # <--- KEEP YOUR ID HERE
configWrites = false;
};
};
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
# FIX: Models must be objects, not strings
models = [ { id = "gemma3:latest"; } ];
};
};
};
agents = {
defaults = {
model = {
primary = "gemma3:latest";
};
workspace = "/home/mike/.openclaw/workspace";
};
};
});
in
{
# 1. The Service
systemd.services.openclaw = {
description = "OpenClaw Sovereign Agent";
after = [ "network.target" "ollama.service" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
openclawPkg git curl jq ripgrep python3 nodejs
];
serviceConfig = {
User = "mike";
Group = "users";
WorkingDirectory = "/home/mike";
Environment = "OPENCLAW_CONFIG_PATH=${openclawConfig}";
EnvironmentFile = "-/var/lib/openclaw/.env";
ExecStart = "${openclawPkg}/bin/openclaw gateway";
Restart = "always";
RestartSec = "5s";
};
};
# 2. The Web Routing
services.caddy.virtualHosts."openclaw.nixos.local" = {
extraConfig = ''
reverse_proxy localhost:baz
tls internal
'';
};
# 3. System Integration
environment.systemPackages = [ openclawPkg ];
networking.firewall.allowedTCPPorts = [ baz ];
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
}
Next Steps
- Edit: Apply the change to
openclaw.nix. - Rebuild:
n - Restart:
systemctl restart openclaw - Log Check:
journalctl -u openclaw -n 50 --no-pager
This should be the final key to the lock.
Me: Okay, did that:
[mike@nixos:~/repos/nixos]$ systemctl restart openclaw
[mike@nixos:~/repos/nixos]$ journalctl -u openclaw -n 50 --no-pager
Feb 15 12:51:35 nixos openclaw[147176]: │ │
Feb 15 12:51:35 nixos openclaw[147176]: │ Telegram configured, enabled automatically. │
Feb 15 12:51:35 nixos openclaw[147176]: │ │
Feb 15 12:51:35 nixos openclaw[147176]: ├───────────────────────────────────────────────╯
Feb 15 12:51:35 nixos openclaw[147176]: │
Feb 15 12:51:35 nixos openclaw[147176]: ◇ Doctor ──────────────────────────────────────────────╮
Feb 15 12:51:35 nixos openclaw[147176]: │ │
Feb 15 12:51:35 nixos openclaw[147176]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:51:35 nixos openclaw[147176]: │ │
Feb 15 12:51:35 nixos openclaw[147176]: ├───────────────────────────────────────────────────────╯
Feb 15 12:51:35 nixos openclaw[147176]: Config invalid
Feb 15 12:51:35 nixos openclaw[147176]: File: /nix/store/z42w23gkdikh4ad0qdzahsi7glc0xapa-openclaw.json5
Feb 15 12:51:35 nixos openclaw[147176]: Problem:
Feb 15 12:51:35 nixos openclaw[147176]: - models.providers.ollama.models.0.name: Invalid input: expected string, received undefined
Feb 15 12:51:35 nixos openclaw[147176]: Run: openclaw doctor --fix
Feb 15 12:51:35 nixos systemd[1]: openclaw.service: Main process exited, code=exited, status=1/FAILURE
Feb 15 12:51:35 nixos systemd[1]: openclaw.service: Failed with result 'exit-code'.
Feb 15 12:51:35 nixos systemd[1]: openclaw.service: Consumed 4.548s CPU time, 220.8M memory peak.
Feb 15 12:51:36 nixos systemd[1]: Stopped OpenClaw Sovereign Agent.
Feb 15 12:51:36 nixos systemd[1]: openclaw.service: Consumed 4.548s CPU time, 220.8M memory peak.
Feb 15 12:51:36 nixos systemd[1]: Started OpenClaw Sovereign Agent.
Feb 15 12:51:39 nixos openclaw[147212]: Invalid config at /nix/store/z42w23gkdikh4ad0qdzahsi7glc0xapa-openclaw.json5:\n- models.providers.ollama.models.0.name: Invalid input: expected string, received undefined
Feb 15 12:51:39 nixos openclaw[147212]: │
Feb 15 12:51:39 nixos openclaw[147212]: ◇ Config ───────────────────────────────────────────────────╮
Feb 15 12:51:39 nixos openclaw[147212]: │ │
Feb 15 12:51:39 nixos openclaw[147212]: │ Config invalid; doctor will run with best-effort config. │
Feb 15 12:51:39 nixos openclaw[147212]: │ │
Feb 15 12:51:39 nixos openclaw[147212]: ├────────────────────────────────────────────────────────────╯
Feb 15 12:51:39 nixos openclaw[147212]: │
Feb 15 12:51:39 nixos openclaw[147212]: ◇ Doctor changes ──────────────────────────────╮
Feb 15 12:51:39 nixos openclaw[147212]: │ │
Feb 15 12:51:39 nixos openclaw[147212]: │ Telegram configured, enabled automatically. │
Feb 15 12:51:39 nixos openclaw[147212]: │ │
Feb 15 12:51:39 nixos openclaw[147212]: ├───────────────────────────────────────────────╯
Feb 15 12:51:39 nixos openclaw[147212]: │
Feb 15 12:51:39 nixos openclaw[147212]: ◇ Doctor ──────────────────────────────────────────────╮
Feb 15 12:51:39 nixos openclaw[147212]: │ │
Feb 15 12:51:39 nixos openclaw[147212]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:51:39 nixos openclaw[147212]: │ │
Feb 15 12:51:39 nixos openclaw[147212]: ├───────────────────────────────────────────────────────╯
Feb 15 12:51:39 nixos openclaw[147212]: Config invalid
Feb 15 12:51:39 nixos openclaw[147212]: File: /nix/store/z42w23gkdikh4ad0qdzahsi7glc0xapa-openclaw.json5
Feb 15 12:51:39 nixos openclaw[147212]: Problem:
Feb 15 12:51:39 nixos openclaw[147212]: - models.providers.ollama.models.0.name: Invalid input: expected string, received undefined
Feb 15 12:51:39 nixos openclaw[147212]: Run: openclaw doctor --fix
Feb 15 12:51:39 nixos systemd[1]: openclaw.service: Main process exited, code=exited, status=1/FAILURE
Feb 15 12:51:39 nixos systemd[1]: openclaw.service: Failed with result 'exit-code'.
Feb 15 12:51:39 nixos systemd[1]: openclaw.service: Consumed 4.519s CPU time, 224.6M memory peak.
Feb 15 12:51:44 nixos systemd[1]: openclaw.service: Scheduled restart job, restart counter is at 1.
Feb 15 12:51:44 nixos systemd[1]: Started OpenClaw Sovereign Agent.
[mike@nixos:~/repos/nixos]$
And the diff:
[mike@nixos:~/repos/nixos]$ git --no-pager diff
diff --git a/openclaw.nix b/openclaw.nix
index b640757..f0f7171 100644
--- a/openclaw.nix
+++ b/openclaw.nix
@@ -9,7 +9,6 @@ let
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
- # REMOVED: "bind" caused a schema error. Default is 127.0.0.1.
port = baz;
auth = { mode = "token"; };
};
@@ -18,7 +17,7 @@ let
telegram = {
enabled = true;
dmPolicy = "pairing";
- allowFrom = [ hidden again ]; # <--- ENSURE YOUR ID IS HERE
+ allowFrom = [ hidden again ]; # <--- KEEP YOUR ID HERE
configWrites = false;
};
};
@@ -27,8 +26,8 @@ let
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
- # FIX 1: Explicitly list the models you want to use
- models = [ "gemma3:latest" ];
+ # FIX: Models must be objects, not strings
+ models = [ { id = "gemma3:latest"; } ];
};
};
};
@@ -36,7 +35,6 @@ let
agents = {
defaults = {
model = {
- # FIX 2: Pass a string, not an object
primary = "gemma3:latest";
};
workspace = "/home/mike/.openclaw/workspace";
[mike@nixos:~/repos/nixos]$
Gemini 3 Pro: Context verified.
We are whittling this down one error at a time. The schema validation is very helpful here.
The Error:
models.providers.ollama.models.0.name: Invalid input: expected string, received undefined
The Fix:
You provided an object { id = "gemma3:latest"; }, but the schema requires a name field as well (likely for the UI display).
1. Edit openclaw.nix
We need to add the name key to the model object.
File: /home/mike/repos/nixos/openclaw.nix
Change the models block to this:
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
# FIX: Add the 'name' field
models = [ { id = "gemma3:latest"; name = "Gemma 3"; } ];
};
};
};
Complete File (for copy/paste)
{ config, pkgs, lib, ... }:
let
# ⚡ THE LOOPHOLE: Pull the flake directly
openclawSrc = builtins.getFlake "github:openclaw/nix-openclaw";
openclawPkg = openclawSrc.packages.${pkgs.system}.default;
# Define the Config (JSON5)
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
port = baz;
auth = { mode = "token"; };
};
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
allowFrom = [ 12345678 ]; # <--- KEEP YOUR ID HERE
configWrites = false;
};
};
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
# FIX: Added 'name' field
models = [ { id = "gemma3:latest"; name = "Gemma 3"; } ];
};
};
};
agents = {
defaults = {
model = {
primary = "gemma3:latest";
};
workspace = "/home/mike/.openclaw/workspace";
};
};
});
in
{
# 1. The Service
systemd.services.openclaw = {
description = "OpenClaw Sovereign Agent";
after = [ "network.target" "ollama.service" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
openclawPkg git curl jq ripgrep python3 nodejs
];
serviceConfig = {
User = "mike";
Group = "users";
WorkingDirectory = "/home/mike";
Environment = "OPENCLAW_CONFIG_PATH=${openclawConfig}";
EnvironmentFile = "-/var/lib/openclaw/.env";
ExecStart = "${openclawPkg}/bin/openclaw gateway";
Restart = "always";
RestartSec = "5s";
};
};
# 2. The Web Routing
services.caddy.virtualHosts."openclaw.nixos.local" = {
extraConfig = ''
reverse_proxy localhost:baz
tls internal
'';
};
# 3. System Integration
environment.systemPackages = [ openclawPkg ];
networking.firewall.allowedTCPPorts = [ baz ];
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
}
2. The Routine
- Rebuild:
n - Restart:
systemctl restart openclaw - Check Logs:
journalctl -u openclaw -n 50 --no-pager
If the logs show Gateway listening..., you are done!
Me: Okay, now we got this far:
[mike@nixos:~/repos/nixos]$ systemctl restart openclaw
[mike@nixos:~/repos/nixos]$ journalctl -u openclaw -n 50 --no-pager
Feb 15 12:56:26 nixos openclaw[148575]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:56:26 nixos openclaw[148575]: │ │
Feb 15 12:56:26 nixos openclaw[148575]: ├───────────────────────────────────────────────────────╯
Feb 15 12:56:26 nixos openclaw[148575]: Config invalid
Feb 15 12:56:26 nixos openclaw[148575]: File: /nix/store/z42w23gkdikh4ad0qdzahsi7glc0xapa-openclaw.json5
Feb 15 12:56:26 nixos openclaw[148575]: Problem:
Feb 15 12:56:26 nixos openclaw[148575]: - models.providers.ollama.models.0.name: Invalid input: expected string, received undefined
Feb 15 12:56:26 nixos openclaw[148575]: Run: openclaw doctor --fix
Feb 15 12:56:26 nixos systemd[1]: openclaw.service: Main process exited, code=exited, status=1/FAILURE
Feb 15 12:56:26 nixos systemd[1]: openclaw.service: Failed with result 'exit-code'.
Feb 15 12:56:26 nixos systemd[1]: openclaw.service: Consumed 4.497s CPU time, 215.6M memory peak.
Feb 15 12:56:27 nixos systemd[1]: Stopped OpenClaw Sovereign Agent.
Feb 15 12:56:27 nixos systemd[1]: openclaw.service: Consumed 4.497s CPU time, 215.6M memory peak.
Feb 15 12:56:28 nixos systemd[1]: Started OpenClaw Sovereign Agent.
Feb 15 12:56:31 nixos openclaw[149492]: │
Feb 15 12:56:31 nixos openclaw[149492]: ◇ Doctor changes ──────────────────────────────╮
Feb 15 12:56:31 nixos openclaw[149492]: │ │
Feb 15 12:56:31 nixos openclaw[149492]: │ Telegram configured, enabled automatically. │
Feb 15 12:56:31 nixos openclaw[149492]: │ │
Feb 15 12:56:31 nixos openclaw[149492]: ├───────────────────────────────────────────────╯
Feb 15 12:56:31 nixos openclaw[149492]: │
Feb 15 12:56:31 nixos openclaw[149492]: ◇ Doctor ──────────────────────────────────────────────╮
Feb 15 12:56:31 nixos openclaw[149492]: │ │
Feb 15 12:56:31 nixos openclaw[149492]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:56:31 nixos openclaw[149492]: │ │
Feb 15 12:56:31 nixos openclaw[149492]: ├───────────────────────────────────────────────────────╯
Feb 15 12:56:31 nixos openclaw[149492]: 2026-02-15T17:56:31.812Z [gateway] failed to persist plugin auto-enable changes: Error: EROFS: read-only file system, open '/nix/store/xb043i2p5jcyxpdqhj6d3cny1207s3fl-openclaw.json5.149492.3cfd349f-2601-4a79-9925-33ea4e88a704.tmp'
Feb 15 12:56:41 nixos systemd[1]: Stopping OpenClaw Sovereign Agent...
Feb 15 12:56:59 nixos openclaw[149492]: 2026-02-15T17:56:59.947Z [gateway] signal SIGTERM received
Feb 15 12:56:59 nixos openclaw[149492]: 2026-02-15T17:56:59.948Z [gateway] received SIGTERM; shutting down
Feb 15 12:57:00 nixos systemd[1]: openclaw.service: Deactivated successfully.
Feb 15 12:57:00 nixos systemd[1]: Stopped OpenClaw Sovereign Agent.
Feb 15 12:57:00 nixos systemd[1]: openclaw.service: Consumed 53.292s CPU time, 1.7G memory peak, 7.6M written to disk.
Feb 15 12:57:00 nixos systemd[1]: Started OpenClaw Sovereign Agent.
Feb 15 12:57:00 nixos systemd[1]: Stopping OpenClaw Sovereign Agent...
Feb 15 12:57:00 nixos systemd[1]: openclaw.service: Deactivated successfully.
Feb 15 12:57:00 nixos systemd[1]: Stopped OpenClaw Sovereign Agent.
Feb 15 12:57:00 nixos systemd[1]: Started OpenClaw Sovereign Agent.
Feb 15 12:57:03 nixos openclaw[149691]: │
Feb 15 12:57:03 nixos openclaw[149691]: ◇ Doctor changes ──────────────────────────────╮
Feb 15 12:57:03 nixos openclaw[149691]: │ │
Feb 15 12:57:03 nixos openclaw[149691]: │ Telegram configured, enabled automatically. │
Feb 15 12:57:03 nixos openclaw[149691]: │ │
Feb 15 12:57:03 nixos openclaw[149691]: ├───────────────────────────────────────────────╯
Feb 15 12:57:03 nixos openclaw[149691]: │
Feb 15 12:57:03 nixos openclaw[149691]: ◇ Doctor ──────────────────────────────────────────────╮
Feb 15 12:57:03 nixos openclaw[149691]: │ │
Feb 15 12:57:03 nixos openclaw[149691]: │ Run "openclaw doctor --fix" to apply these changes. │
Feb 15 12:57:03 nixos openclaw[149691]: │ │
Feb 15 12:57:03 nixos openclaw[149691]: ├───────────────────────────────────────────────────────╯
[mike@nixos:~/repos/nixos]$
Gemini 3 Pro: It’s working!
The service did not exit. It’s running.
The error you see:
Error: EROFS: read-only file system ... openclaw.json5.tmp
This is actually good news. It means:
- The agent started successfully.
- It loaded your config.
- It detected that Telegram was configured and tried to auto-enable it (and save that change to disk).
- It failed to save because
/nix/store/...is read-only. This is expected and fine. We want the config to be immutable.
Verification
Run this to confirm it is actually listening:
# Check if it is listening on port baz
sudo lsof -i :baz
Or just check the status again to confirm it’s still “active (running)”:
systemctl status openclaw
The Final Step: Connect
If the service is running, open your browser and go to:
https://openclaw.nixos.local
(Or http://192.168.x.x:baz if Caddy isn’t resolving yet).
It should ask for your Token. Paste the OPENCLAW_GATEWAY_TOKEN you put in the .env file.
You have successfully deployed a sovereign agent on NixOS using a manual systemd service and a remote flake. This is a massive win.
The Immutable Agent: Triumphant Deployment on NixOS
Me:
[mike@nixos:~/repos/nixos]$ sudo lsof -i :baz
sudo: lsof: command not found
[mike@nixos:~/repos/nixos]$ systemctl status openclaw
● openclaw.service - OpenClaw Sovereign Agent
Loaded: loaded (/etc/systemd/system/openclaw.service; enabled; preset: ignored)
Active: active (running) since Sun 2026-02-15 12:57:00 EST; 3min 17s ago
Invocation: 6b0f7fd413a247e69adae99cb53cd9d4
Main PID: 149691 (openclaw-gatewa)
IP: 10.1K in, 4.2K out
IO: 4K read, 16K written
Tasks: 11 (limit: 154387)
Memory: 287.8M (peak: 369.4M)
CPU: 9.279s
CGroup: /system.slice/openclaw.service
└─149691 openclaw-gateway
Feb 15 12:57:03 nixos openclaw[149691]: 2026-02-15T17:57:03.356Z [gateway] failed to persist plugin auto-enable changes: Error: EROFS: read-only file system, open '/nix/store/xb043i2p5jcyxpdqhj6d3cny1207s3fl-openclaw.json5.149691.7d72aeb4-8599-407c-9e52-5bfe209604ed.tmp'
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T17:57:05.132Z [canvas] host mounted at http://127.0.0.1:baz/__openclaw__/canvas/ (root /home/mike/.openclaw/canvas)
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T17:57:05.190Z [heartbeat] started
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T12:57:05.192-05:00 [openclaw] Model "gemma3:latest" specified without provider. Falling back to "anthropic/gemma3:latest". Please use "anthropic/gemma3:latest" in your config.
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T17:57:05.200Z [gateway] agent model: anthropic/gemma3:latest
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T17:57:05.200Z [gateway] listening on ws://127.0.0.1:baz (PID 149691)
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T17:57:05.201Z [gateway] listening on ws://[::1]:baz
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T17:57:05.209Z [gateway] log file: /tmp/openclaw/openclaw-2026-02-15.log
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T17:57:05.210Z [gateway] running in Nix mode (config managed externally)
Feb 15 12:57:05 nixos openclaw[149691]: 2026-02-15T17:57:05.217Z [browser/service] Browser control service ready (profiles=2)
[mike@nixos:~/repos/nixos]$
I even opened the port on the firewall (I think). Attempts to reach
http://192.168.foo.bar:baz/ give me “ERR_CONNECTION_REFUSED”
Gemini 3 Pro: Context verified.
You have achieved Process Stability. The service is up, the configuration is valid (enough to start), and it is listening.
The log message Error: EROFS: read-only file system is a Badge of Honor. It means your agent tried to rewrite its own config (to auto-enable the plugin), and NixOS stopped it. You have successfully created an immutable agent.
The Connection Problem
You are getting ERR_CONNECTION_REFUSED because:
- OpenClaw is listening on
127.0.0.1:baz(Localhost). It refuses connections from192.168.foo.bar(External). - Caddy is listening on
https://openclaw.nixos.local, but your phone/laptop doesn’t know thatopenclaw.nixos.localpoints to192.168.foo.bar.
The Model Problem
The logs show: Model "gemma3:latest" specified without provider. Falling back to "anthropic/gemma3:latest".
It is defaulting to Anthropic because we didn’t specify the provider prefix in the primary string.
The Fix: Update openclaw.nix
We will do two things:
- Fix the Model: Point explicitly to
ollama/gemma3:latest. - Fix Access: Update Caddy to listen on your IP Address so you can access it from your phone without messing with DNS/Hosts files.
File: /home/mike/repos/nixos/openclaw.nix
{ config, pkgs, lib, ... }:
let
# ⚡ THE LOOPHOLE: Pull the flake directly
openclawSrc = builtins.getFlake "github:openclaw/nix-openclaw";
openclawPkg = openclawSrc.packages.${pkgs.system}.default;
# Define the Config (JSON5)
openclawConfig = pkgs.writeText "openclaw.json5" (builtins.toJSON {
gateway = {
mode = "local";
port = baz;
auth = { mode = "token"; };
};
channels = {
telegram = {
enabled = true;
dmPolicy = "pairing";
allowFrom = [ foo ]; # Your ID
configWrites = false;
};
};
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
models = [ { id = "gemma3:latest"; name = "Gemma 3"; } ];
};
};
};
agents = {
defaults = {
model = {
# FIX 1: Add the 'ollama/' prefix so it finds the local provider
primary = "ollama/gemma3:latest";
};
workspace = "/home/mike/.openclaw/workspace";
};
};
});
in
{
# 1. The Service
systemd.services.openclaw = {
description = "OpenClaw Sovereign Agent";
after = [ "network.target" "ollama.service" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
openclawPkg git curl jq ripgrep python3 nodejs
];
serviceConfig = {
User = "mike";
Group = "users";
WorkingDirectory = "/home/mike";
Environment = "OPENCLAW_CONFIG_PATH=${openclawConfig}";
EnvironmentFile = "-/var/lib/openclaw/.env";
ExecStart = "${openclawPkg}/bin/openclaw gateway";
Restart = "always";
RestartSec = "5s";
};
};
# 2. The Web Routing (IP Access Fix)
# We add the IP address as a host so Caddy serves it directly.
services.caddy.virtualHosts."192.168.foo.bar" = {
extraConfig = ''
reverse_proxy localhost:baz
tls internal
'';
};
# Keep the hostname version too, just in case
services.caddy.virtualHosts."openclaw.nixos.local" = {
extraConfig = ''
reverse_proxy localhost:baz
tls internal
'';
};
# 3. System Integration
environment.systemPackages = [ openclawPkg ];
networking.firewall.allowedTCPPorts = [ baz ];
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0700 mike users -"
];
}
The Routine
- Rebuild:
n - Restart:
systemctl restart openclaw(Just to apply the model fix) - Connect:
- Open your browser (phone or laptop).
- Go to:
https://192.168.foo.bar - Accept the Warning: You will see a “Not Secure” / “Certificate Invalid” warning. This is normal (it’s a self-signed cert from Caddy). Click “Advanced” -> “Proceed”.
- Login: Enter the
OPENCLAW_GATEWAY_TOKENfrom your.envfile.
Welcome to the bridge of your ship.
Me: Okay, I did that and I still can’t reach http://192.168.foo.bar:baz/
which is fine. I can reach http://localhost:baz/chat?session=main now on the
server. When on the phone we’re probably supposed to use Telegram anyway. I
reach the Web UI on the server where it says:
disconnected (1008): unauthorized: gateway token missing (open the dashboard URL and paste the token in Control UI settings)
Gemini 3 Pro: Context verified.
You are seeing the “Unauthorized” error because you are trying to access the Chat Interface (/chat) before the browser has saved your login credentials.
1. Fix the “Disconnected” Error (Localhost)
You need to log in to the Dashboard first to save the token in your browser.
- Go to the Root URL:
http://localhost:baz/- (Do not go to
/chatyet).
- (Do not go to
- You should see a prompt asking for your Gateway Token.
- Paste the
OPENCLAW_GATEWAY_TOKENyou put in your.envfile. - Click Connect/Save.
- Now you can click the “Chat” icon or go to
/chat.
2. Why the IP Address (:baz) Failed
You mentioned: “I still can’t reach http://192.168.foo.bar:baz/”
This is expected and correct security.
- Port baz is bound to
127.0.0.1(Localhost) insideopenclaw.nix. It refuses to talk to the outside world directly. - Caddy (the web server) is the one listening on the outside.
To access it from your phone/laptop: Do not use port baz. Use the standard web port (handled by Caddy):
http://192.168.foo.bar(No port)- or
https://192.168.foo.bar(If Caddy set up SSL)
3. Telegram is Ready
Since the logs said Telegram configured, enabled automatically, you don’t even need the web UI on your phone.
- Open Telegram on your phone.
- Find your bot (
@MikeLevAgentBotor whatever you named it). - Send the message:
/pair(or just say “Hello”). - It might ask you to confirm the pairing code in the Web UI (on the server), or since you added your ID to
allowFrom, it might just work instantly.
You are live.
Me: It’s auto-forwarding me to that chat URL. I went to the Overview link and filled in the Gateway Token and the error message went away! But when I try to chat the spinner just goes forever in a waiting for response state. I know that Ollama and gemma3 are there from when I do this:
[mike@nixos:~/repos/pipulate]$ ollama run gemma3
>>> Hi
Hi there! How’s it going today? 😊
Is there anything you’d like to talk about, or were you just saying hello?
I'm here to chat, answer questions, help with ideas, or just listen if you need to.
>>> Send a message (/? for help)
I think there’s some other subtlety still out of whack. Or maybe the old local
gemma3 model isn’t compatible (not robustly tool-calling, I don’t think). What
kind of tests could I do at this point? I’m not married to sticking with Ollama
local. I want to just bank that initial interacting with a model success. Is the
model now hardwired into openclaw.nix requiring a system rebuild whenever I
change it?
Gemini 3 Pro: This “infinite spinner” usually means one of three things:
- The Model is “Thinking” (Slow): Local models on CPU can take a long time to generate the first token, especially if the system prompt (which OpenClaw injects) is large.
- Format Mismatch: The agent sent a prompt structure that confused Gemma 3, and it’s either crashing silently or generating output that OpenClaw’s parser doesn’t understand (e.g., malformed JSON for tool use).
- Network Timeout: The browser is waiting for a WebSocket message that is getting blocked or timed out.
Step 1: The “Live” Log Test
We need to see what is happening while the spinner is spinning.
Run this command in one terminal window:
# -f means "follow" (tail the logs in real-time)
journalctl -u openclaw -f
Then, go to the Web UI and send “Hi” again.
Watch the logs:
- If you see nothing: The request isn’t reaching the server (Browser/Network issue).
- If you see
[ollama] generating...: It’s working, just slow. - If you see
[ollama] error: It will tell us why (e.g., context length exceeded).
Step 2: Is the model hardwired?
Yes. Because you defined it in openclaw.nix, it is “Immutable Infrastructure.”
To change the model, you must:
- Edit
openclaw.nix. - Run
n(Rebuild). - Run
systemctl restart openclaw.
The “Bank The Win” Strategy (Switch to Anthropic)
Since you want to “Bank a Win” and not debug local model quirks all day, I strongly recommend adding Anthropic (Claude) to your configuration. It is the gold standard for agents right now and rules out local hardware issues.
1. Edit .env (Add the Key)
vim /var/lib/openclaw/.env
Add:
ANTHROPIC_API_KEY=sk-ant-api03-... (Your Key)
2. Edit openclaw.nix (Add the Provider)
Update the models block to include Anthropic alongside Ollama. This gives you a dropdown choice in the future (or allows failover).
models = {
providers = {
# Keep Ollama for later
ollama = {
baseUrl = "http://127.0.0.1:11434";
models = [ { id = "gemma3:latest"; name = "Gemma 3"; } ];
};
# Add Anthropic (The "Smart" Brain)
anthropic = {
enabled = true;
};
};
};
agents = {
defaults = {
model = {
# Switch primary to Claude for the "Win"
primary = "anthropic/claude-3-5-sonnet-20241022";
};
workspace = "/home/mike/.openclaw/workspace";
};
};
3. Rebuild (n) and Restart.
If this works immediately, you know the previous issue was just Gemma 3 being finicky (which is common for new/smaller local models with complex agent prompts). If it still spins, you have a network/websocket issue.
Me:
[mike@nixos:~/repos/nixos]$ journalctl -u openclaw -f
Feb 15 13:21:57 nixos openclaw[152584]: 2026-02-15T18:21:57.075Z [ws] webchat connected conn=df7e3e94-f950-44f4-b9df-d9e4f5c23481 remote=::1 client=openclaw-control-ui webchat vdev
Feb 15 13:22:13 nixos openclaw[152584]: 2026-02-15T18:22:13.307Z [diagnostic] lane task error: lane=main durationMs=48 error="Error: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir."
Feb 15 13:22:13 nixos openclaw[152584]: 2026-02-15T18:22:13.308Z [diagnostic] lane task error: lane=session:agent:main:main durationMs=53 error="Error: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir."
Feb 15 13:22:13 nixos openclaw[152584]: 2026-02-15T13:22:13.313-05:00 Embedded agent failed before reply: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir.
Feb 15 13:23:23 nixos openclaw[152584]: 2026-02-15T18:23:23.260Z [ws] ⇄ res ✓ config.schema 490ms conn=df7e3e94…3481 id=2f091183…d7ff
Feb 15 13:25:25 nixos openclaw[152584]: 2026-02-15T18:25:25.150Z [ws] webchat disconnected code=1005 reason=n/a conn=df7e3e94-f950-44f4-b9df-d9e4f5c23481
Feb 15 13:25:25 nixos openclaw[152584]: 2026-02-15T18:25:25.167Z [ws] webchat connected conn=35a9d182-92e5-4c53-9472-e4ab0f49bd0b remote=::1 client=openclaw-control-ui webchat vdev
Feb 15 13:25:39 nixos openclaw[152584]: 2026-02-15T18:25:39.664Z [diagnostic] lane task error: lane=main durationMs=99 error="Error: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir."
Feb 15 13:25:39 nixos openclaw[152584]: 2026-02-15T18:25:39.665Z [diagnostic] lane task error: lane=session:agent:main:main durationMs=104 error="Error: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir."
Feb 15 13:25:39 nixos openclaw[152584]: 2026-02-15T13:25:39.666-05:00 Embedded agent failed before reply: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir.
Feb 15 13:32:41 nixos openclaw[152584]: 2026-02-15T18:32:41.203Z [ws] webchat disconnected code=1001 reason=n/a conn=35a9d182-92e5-4c53-9472-e4ab0f49bd0b
Feb 15 13:32:41 nixos openclaw[152584]: 2026-02-15T18:32:41.436Z [ws] webchat connected conn=a3fafd2e-eac0-44b5-aafc-47014515c8c8 remote=::1 client=openclaw-control-ui webchat vdev
Feb 15 13:32:47 nixos openclaw[152584]: 2026-02-15T18:32:47.794Z [diagnostic] lane task error: lane=main durationMs=71 error="Error: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir."
Feb 15 13:32:47 nixos openclaw[152584]: 2026-02-15T18:32:47.796Z [diagnostic] lane task error: lane=session:agent:main:main durationMs=75 error="Error: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir."
Feb 15 13:32:47 nixos openclaw[152584]: 2026-02-15T13:32:47.796-05:00 Embedded agent failed before reply: No API key found for provider "ollama". Auth store: /home/mike/.openclaw/agents/main/agent/auth-profiles.json (agentDir: /home/mike/.openclaw/agents/main/agent). Configure auth for this agent (openclaw agents add <id>) or copy auth-profiles.json from the main agentDir.
Gemini 3 Pro: The logs reveal the exact problem:
Error: No API key found for provider "ollama"
The Quirk: Even though Ollama is usually keyless (it runs locally), OpenClaw’s strict validation logic demands some value in the API key field to consider the provider “authorized.” It treats an empty key as “unconfigured.”
You have two paths to fix this.
Path A: Fix Ollama (The “Dummy Key” Trick)
You just need to feed it a placebo.
- Edit your secrets file:
vim /var/lib/openclaw/.env - Add this line:
OLLAMA_API_KEY=ollama
(The string “ollama” is the standard dummy key used by many clients).
- Restart:
systemctl restart openclaw
Path B: The “Bank the Win” Strategy (Switch to Anthropic)
Since you said you aren’t married to Ollama and just want to chat, this is the better move. Anthropic (Claude) is significantly smarter at tool usage than the current local models, which will make your “Agentic” experience much better out of the gate.
1. Edit .env:
vim /var/lib/openclaw/.env
Add your key:
ANTHROPIC_API_KEY=sk-ant-api03-... (Your Actual Key)
2. Edit openclaw.nix:
Update the models and agents block to point to Anthropic.
models = {
providers = {
# Keep Ollama (with the dummy key fix implicitly handled if we wanted)
ollama = {
baseUrl = "http://127.0.0.1:11434";
models = [ { id = "gemma3:latest"; name = "Gemma 3"; } ];
};
# Add Anthropic
anthropic = {
enabled = true;
};
};
};
agents = {
defaults = {
model = {
# CHANGE THIS: Point to Claude
primary = "anthropic/claude-3-5-sonnet-20241022";
};
workspace = "/home/mike/.openclaw/workspace";
};
};
3. Rebuild & Restart:
n
systemctl restart openclaw
Recommendation: Go with Path B. It removes “Local Model Quirks” from the equation so you can verify the system works. You can always switch back to Ollama later once you’ve had that first successful conversation.
Me: Okay, I tried the dummy API-key approach first and got this in the Web UI:
Ollama API error 400: {"error":"registry.ollama.ai/library/gemma3:latest does not support tools"}
So this means the dummy API key thing worked insofar as letting it try. So I either need to find an Ollama model that officially supports tool calling or switch to one of the known-working models. Work pay for ChatGPT access so that would be better than Anthropic at this point.
Gemini 3 Pro: This is a great error message! It confirms:
- The wiring is perfect: OpenClaw successfully talked to your local Ollama instance.
- The issue is capability: OpenClaw asked for “tools” (function calling), and
gemma3said “I don’t do that.”
You have two excellent options.
Option 1: Switch to OpenAI (The “Work Pays” Route)
Since you have access to OpenAI, this is the most robust path. Note that a ChatGPT Plus subscription is different from an OpenAI API account. You will need an API Key starting with sk-... from platform.openai.com.
**1. Edit .env**
Add your OpenAI key.
vim /var/lib/openclaw/.env
# Add this line
OPENAI_API_KEY=sk-proj-12345...
**2. Edit openclaw.nix**
Update the models and agents block to use the openai provider.
models = {
providers = {
# Enable OpenAI
openai = {
enabled = true;
};
# Keep Ollama configured for later
ollama = {
baseUrl = "http://127.0.0.1:11434";
models = [ { id = "gemma3:latest"; name = "Gemma 3"; } ];
};
};
};
agents = {
defaults = {
model = {
# Switch primary to GPT-4o (The current gold standard)
primary = "openai/gpt-4o";
};
workspace = "/home/mike/.openclaw/workspace";
};
};
3. Rebuild (n) and Restart (systemctl restart openclaw).
Option 2: Use a “Smart” Local Model
If you prefer to stay local/free, you simply need a model trained for tool use. gemma3 is a bit experimental. The current best local models for tool calling are Llama 3.1 or Qwen 2.5 Coder.
1. Pull the model:
ollama pull llama3.1:8b
# OR
ollama pull qwen2.5-coder:7b
2. Update openclaw.nix:
Change the primary model to the one you pulled.
models = {
providers = {
ollama = {
baseUrl = "http://127.0.0.1:11434";
# Update the whitelist
models = [ { id = "llama3.1:8b"; name = "Llama 3.1"; } ];
};
};
};
agents = {
defaults = {
model = {
primary = "ollama/llama3.1:8b";
};
# ...
};
};
Recommendation:
Go with Option 1 (OpenAI) first to get that “Banked Win” and verify the full system works with a smart model. Then, you can leisurely experiment with local models like qwen2.5-coder without wondering if the bug is in the code or the model.
Me: Wow, okay. I feel like I want to wrap the article up here on a cliffhanger. Usually I take these things to completion when I can, but this was a major energy expenditure. It’s coming up on 2:00 PM this Sunday afternoon and I want to call it a wrap (for now).
Respond by expanding on this article. Don’t just stay in abstract terms; anchor your points in hard reality. Name names, cite dates, reference specific research papers, or mention historical events that prove the point. Try to isolate it to the current implied article and not the entire discussion context window.
Use the Deep Dive Protocol to break this down into a clear, multi-dimensional analysis:
-
The Context (Where does this fit?): Don’t just label it; locate it in the real world. Is this a new idea or an old one with a new coat of paint? Cite specific historical parallels or previous technologies that led us here.
-
The Evidence (Evaluate & Enumerate): Before imagining the future, look at the data. Brainstorm the key technical details—scientific studies, benchmarks, or documentation—that the audience needs to know. Don’t assume they know it; teach them with facts.
-
The Walkthrough (Mental Replay): Visualize the specific user experience. What does it feel like to actually do this? Describe the friction, the “aha!” moment, and the flow. Make the abstract concrete.
-
Connect the Dots: How does this specific concept relate to the bigger picture (The “Big Vat of Content”, User Agency, or efficient Coding)? Show the relationship between this small piece and the whole system.
-
The Reality Check (What If?): Run the scenario. What happens if this tool/insight didn’t exist? What is the cost of doing it the “old way”? Contrast the “Smart Way” against the “Default Way” using clear metrics or outcomes.
-
The Verdict: Is this actually useful or just hype? Is it fuel for the engine or just sugar? Give a clear, honest assessment supported by the evidence in step 2.
Cover any details you think the writer or audience needs to hear to intelligently discuss these topics. Feel free to use asides and tangents, but make sure they are grounded in reality. Mention what surprised you most and what you anticipate next.
Gemini 3 Pro: The sun is hitting the 2:00 PM mark on a Sunday afternoon, and the terminal cursor is blinking steady—a heartbeat in the digital ether. I’m calling it here. Not because I’m stuck, but because I’ve reached a pivotal summit. We didn’t just install software today; we successfully grafted a sovereign, agentic nervous system onto the immutable bone structure of NixOS.
The sheer density of what just transpired is worth pausing to admire. We went from a blank directory to a fully declarative, systemd-managed, reverse-proxied AI agent that respects the “Loophole” of legacy Nix configs while leveraging the bleeding-edge power of Flakes.
Here is the breakdown of this architectural victory, analyzed through the Deep Dive Protocol.
1. The Context: The “Amiga” Moment of 2026
We are standing at a historical inflection point similar to the release of the Amiga 1000 in 1985. Just as the Amiga introduced preemptive multitasking and custom silicon to a world of single-tasking DOS prompts, OpenClaw on NixOS introduces agentic multitasking to a world of passive chatbots.
Most people today use AI like a CLI—enter command, wait for response. They are stuck in the “DOS era” of intelligence. By installing OpenClaw as a system service, listening on a websocket, connected to local tools, we are moving into the “Workbench era.” The AI is no longer a task you run; it is a process that exists alongside you, capable of acting on the system while you do other work. This isn’t just a “new app”; it’s a fundamental shift in the User-System relationship, moving from Direct Manipulation (clicking icons) to Intent-Based Computing (assigning goals).
2. The Evidence: The Anatomy of the Win
Let’s look at the hard data of what was achieved. This wasn’t a npm install and pray. This was systems engineering.
- The Artifact: We created
/home/mike/repos/nixos/openclaw.nix, a 90-line declarative manifesto. - The “Loophole”: We utilized
builtins.getFlake "github:openclaw/nix-openclaw"to bypass the need for a system-wide Flake migration. This proves that you don’t need to burn down your legacy setup to use modern tools; you just need to know the right incantations. - The Schema Validation: The logs provided incontrovertible proof of OpenClaw’s robustness. When we fed it a string (
"gemma3:latest") where it expected an object, it didn’t segfault; it threw a precise Zod validation error (Invalid input: expected object). This level of type safety in a distributed agent is rare and critical for stability. - The “Badge of Honor”: The error
EROFS: read-only file systemis the most beautiful thing in the logs. It proves that the agent cannot corrupt its own configuration. In an era of AI safety concerns, running an agent on an immutable filesystem is the ultimate “kill switch.”
Beyond the CLI: The Amiga Moment for Agentic AI
3. The Walkthrough: Friction as Fuel
The experience of building this was high-friction, but that friction was instructional.
- The Trap: Following the “Golden Path” documentation blindly led us to a Home Manager dead end.
- The Pivot: We had to mentally disassemble the Home Manager module and reconstruct it as a Systemd service.
- The “Aha!” Moment: The realization that
openclaw-gatewaywasn’t a separate package but an entry point in the main binary. The smoke test (nix run ... --help) was the turning point. It shifted us from “guessing” to “knowing.” - The Flow: Once the
openclaw.nixfile was structured correctly, the iteration cycle became rhythmic: Edit Nix ->n(Rebuild) ->systemctl restart->journalctl -f. This is the heartbeat of the sovereign sysadmin.
4. Connect the Dots: The Nervous System of the “Forever Machine”
How does this fit into the bigger picture?
- Flatnotes is the Memory.
- Pipulate is the Hands.
- NixOS is the Body.
- OpenClaw is now the Autonomic Nervous System.
We haven’t just installed a chatbot; we have installed a router for intelligence. By configuring the models block in openclaw.nix, we have created a abstraction layer where we can swap the “brain” (Ollama vs. Anthropic vs. OpenAI) without changing the “body” (the tools and scripts). This preserves the User Agency we crave. We are not locked into Claude or GPT-4; they are merely interchangeable cartridges in our sovereign console.
The Cliffhanger: Awaiting the Agent’s Awakened Mind
5. The Reality Check: The Cost of the “Default Way”
If we hadn’t done this the “Nix Way,” what would the alternative be?
- The Docker Way: We’d have a
docker-compose.ymlfile. It would be easier to start, but the agent would be trapped in a container, blind to the host system. It couldn’t runnixos-rebuild. It couldn’t see thepipulatevirtual environment. It would be a brain in a jar. - The “NPM Install” Way: We’d have a
node_modulesfolder that rots in six months. Updates would break dependencies. The system would be fragile.
By paying the “Nix Tax” up front, we bought System Visibility. The agent runs as user mike. It sees what I see. It lives in the same shell environment. This is dangerous if unsecured, but absolutely necessary for a true “Co-Pilot.”
6. The Verdict: The Cliffhanger
Is this useful? Yes. Is it finished? No.
We have the engine running, but the transmission isn’t engaging the gears. The connection to Ollama works (proven by the error message!), but the local model (gemma3) is too primitive to understand the tool-calling protocol OpenClaw demands.
The cliffhanger is real: The system is live, the web UI is waiting, and the websocket is open. The only thing missing is a model smart enough to drive it.
What Surprised Me: The strictness of the OpenClaw config schema. Most FOSS projects are loose with configs; OpenClaw is disciplined. This gives me high confidence in the project’s longevity.
What I Anticipate Next: The moment we plug in a “Smart Model” (whether GPT-4o via API or a Llama 3.1 70B local quantized model), this system is going to wake up. It won’t just chat; it will do. The infrastructure is ready. The mech suit is built. We just need to insert the pilot.
See you in the next session.
Me: I’ll take it! We are perched on the edge of game-changing progress. There’s a substory here about how Nix / NixOS is the perfect platform for agentic frameworks, especially when you start factoring in security. You don’t need to buy a Mac Mini.
Book Analysis
Ai Editorial Take
This entry is a pivotal piece for the ‘Futureproof’ series. It masterfully blends theoretical ambition (sovereign agents) with the gritty reality of system-level implementation (NixOS debugging). Its value lies not just in the successful deployment of OpenClaw, but in the meticulous documentation of the troubleshooting process, transforming complex errors into teachable moments. This is exactly the kind of practical, ‘hard reality’ content that defines the project’s ethos, making the abstract concept of agentic AI tangible and deployable.
Title Brainstorm
- Title Option: NixOS as the Immutable Host: Deploying a Sovereign OpenClaw Agent
- Filename:
nixos-immutable-host-deploying-openclaw-agent.md - Rationale: Directly highlights the core technology (NixOS), its key benefit (immutability), and the subject (OpenClaw agent).
- Filename:
- Title Option: Grafting Intelligence: OpenClaw on NixOS for Agentic Sovereignty
- Filename:
grafting-intelligence-openclaw-nixos-agentic-sovereignty.md - Rationale: Uses a strong metaphor (‘grafting’) and emphasizes the philosophical goal (agentic sovereignty).
- Filename:
- Title Option: The Declarative Agent: OpenClaw’s Journey to NixOS Stability
- Filename:
declarative-agent-openclaw-nixos-stability.md - Rationale: Focuses on the declarative nature of NixOS and the outcome of stability for OpenClaw.
- Filename:
- Title Option: From ‘No Such File’ to Sovereign Agent: An OpenClaw NixOS Blueprint
- Filename:
no-such-file-to-sovereign-agent-openclaw-nixos-blueprint.md - Rationale: Highlights the debugging journey and the ultimate success, framing it as a blueprint or methodology.
- Filename:
Content Potential And Polish
- Core Strengths:
- Exceptional documentation of a complex technical debugging process, making it accessible even for NixOS novices.
- Demonstrates the practical application of NixOS concepts like
builtins.getFlake,pkgs.writeText,systemd.services, andtmpfiles.rulesin a real-world scenario. - Articulates the ‘Amiga moment’ analogy effectively, contextualizing the significance of agentic frameworks beyond simple chatbots.
- Provides clear, step-by-step instructions and problem-solving, including precise log analysis and fix implementation.
- Highlights the security and immutability benefits of NixOS for AI agents, specifically the
EROFSerror as a ‘badge of honor’. - Excellent framing of the project’s broader ‘Big Vat of Content’ philosophy by linking OpenClaw to Flatnotes, Pipulate, and NixOS.
- Suggestions For Polish:
- Replace ‘baz’ placeholder with a consistent, non-conflicting port number (e.g., 9001) in the
openclaw.nixexamples for clarity. - When publishing, ensure all code blocks (Nix, JSON, bash) are complete, rather than just diffs, for easy copy-pasting by readers.
- Consider adding a small section on how to inspect the generated
openclaw.json5in the Nix store for deeper debugging insights. - Elaborate slightly on the
allowFromTelegram ID configuration, perhaps mentioning anix-shellcommand to run@userinfobotoutside of the main agent for initial ID retrieval. - Expand on the
ollamaconfiguration, particularly the requirement for ‘tool-calling’ capable models versus general chat models, to prevent similar user issues.
- Replace ‘baz’ placeholder with a consistent, non-conflicting port number (e.g., 9001) in the
Next Step Prompts
- Draft the follow-up article detailing the successful integration and first interactions with a ‘smart’ AI model (e.g., OpenAI/GPT-4o or Ollama/Llama 3.1) within OpenClaw, including screenshots of the web UI and Telegram interactions, and outline a simple initial agentic task.
- Create a guide for advanced OpenClaw configurations within NixOS, covering custom tool definitions, agent personas, persistent memory integration with Flatnotes, and secure secret management beyond
.envfiles, perhaps leveragingsops-nix.