replace pi with openclaw (#60)

This commit is contained in:
Hari 2026-04-03 11:16:16 -04:00 committed by GitHub
parent be2370f406
commit 630ec774ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 160 additions and 114 deletions

View file

@ -35,4 +35,4 @@ The KVM has a declarative service bundle:
- Self hosts Forgejo mirroring to GitHub (git.harivan.sh)
- Self hosts VaultWarden
- betterNAS control-plane and node agent (api.betternas.com)
- pi-coding-agent in dtach (`pa` alias for attaching)
- OpenClaw gateway behind nginx (netty.harivan.sh)

View file

@ -85,9 +85,6 @@ trust_level = "trusted"
[projects."/Users/rathi/Documents/GitHub/agentikube"]
trust_level = "trusted"
[projects."/Users/rathi/Documents/GitHub/companion/pi-mono"]
trust_level = "trusted"
[projects."/Users/rathi/.humanlayer/riptide/artifacts/019cc50f-02a6-75fe-9279-3cdc300448a4"]
trust_level = "trusted"

View file

@ -0,0 +1,3 @@
OPENCLAW_GATEWAY_TOKEN=replace-me-with-a-long-random-token
TELEGRAM_BOT_TOKEN=123456:replace-me
ANTHROPIC_API_KEY=sk-ant-replace-me

View file

@ -0,0 +1,49 @@
{
gateway: {
mode: "local",
bind: "loopback",
port: 2470,
trustedProxies: ["127.0.0.1", "::1"],
controlUi: {
enabled: true,
allowedOrigins: ["https://netty.harivan.sh"],
},
auth: {
mode: "token",
token: "${OPENCLAW_GATEWAY_TOKEN}",
},
},
channels: {
telegram: {
enabled: true,
botToken: "${TELEGRAM_BOT_TOKEN}",
dmPolicy: "pairing",
},
},
agents: {
defaults: {
workspace: "~/.openclaw/workspace",
model: {
primary: "anthropic/claude-sonnet-4-6",
},
sandbox: {
mode: "non-main",
},
},
},
tools: {
profile: "coding",
deny: [
"browser",
"canvas",
"cron",
"gateway",
"nodes",
"sessions_send",
"sessions_spawn",
],
},
}

View file

@ -21,7 +21,7 @@
./mise.nix
./migration.nix
./nvim.nix
./pi.nix
./openclaw.nix
./prompt.nix
./skills.nix
./scripts.nix

35
home/openclaw.nix Normal file
View file

@ -0,0 +1,35 @@
{
config,
lib,
pkgs,
hostConfig,
...
}:
let
openClawStateDir = "${config.home.homeDirectory}/.openclaw";
openClawWorkspaceDir = "${openClawStateDir}/workspace";
npmDir = "${config.xdg.dataHome}/npm";
in
lib.mkIf hostConfig.isLinux {
home.activation.installOpenClaw = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
export PATH="${
lib.makeBinPath [
pkgs.nodejs_22
pkgs.coreutils
]
}:$PATH"
export NPM_CONFIG_USERCONFIG="${config.xdg.configHome}/npm/npmrc"
export XDG_DATA_HOME="${config.xdg.dataHome}"
export XDG_CACHE_HOME="${config.xdg.cacheHome}"
if [ ! -d "${npmDir}/lib/node_modules/openclaw" ]; then
npm install -g openclaw@latest 2>/dev/null || true
fi
'';
home.activation.syncOpenClawState = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
install -d -m 700 "${openClawStateDir}" "${openClawWorkspaceDir}"
install -m 600 ${../config/openclaw/openclaw.json} "${openClawStateDir}/openclaw.json"
install -m 644 ${../config/openclaw/SOUL.md} "${openClawWorkspaceDir}/SOUL.md"
'';
}

View file

@ -1,53 +0,0 @@
{
config,
lib,
pkgs,
hostConfig,
...
}:
let
npmDir = "${config.xdg.dataHome}/npm";
piBin = "${npmDir}/bin/pi";
in
lib.mkIf hostConfig.isLinux {
home.file.".pi/agent/SYSTEM.md".source = ../config/pi/SYSTEM.md;
# Install pi-coding-agent globally via npm at activation time.
home.activation.installPiAgent = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
export PATH="${
lib.makeBinPath [
pkgs.nodejs_22
pkgs.coreutils
]
}:$PATH"
export NPM_CONFIG_USERCONFIG="${config.xdg.configHome}/npm/npmrc"
export XDG_DATA_HOME="${config.xdg.dataHome}"
export XDG_CACHE_HOME="${config.xdg.cacheHome}"
if [ ! -d "${npmDir}/lib/node_modules/@mariozechner/pi-coding-agent" ]; then
npm install -g @mariozechner/pi-coding-agent 2>/dev/null || true
fi
'';
# Install Pi extensions at activation time:
# - @e9n/pi-channels: Telegram/Slack bridge with RPC-based persistent sessions
# - pi-schedule-prompt: cron/interval scheduled prompts
# - pi-subagents: background task delegation with async execution
home.activation.installPiExtensions = lib.hm.dag.entryAfter [ "installPiAgent" ] ''
export PATH="${
lib.makeBinPath [
pkgs.nodejs_22
pkgs.coreutils
pkgs.git
]
}:$PATH"
export NPM_CONFIG_USERCONFIG="${config.xdg.configHome}/npm/npmrc"
export XDG_DATA_HOME="${config.xdg.dataHome}"
export XDG_CACHE_HOME="${config.xdg.cacheHome}"
if [ -x "${piBin}" ]; then
for pkg in "@e9n/pi-channels" "pi-schedule-prompt" "pi-subagents"; do
"${piBin}" install "npm:$pkg" 2>/dev/null || true
done
fi
'';
}

View file

@ -18,7 +18,7 @@ in
./vaultwarden.nix
./forgejo.nix
./betternas.nix
./pi-agent.nix
./openclaw-gateway.nix
../../modules/base.nix
(modulesPath + "/profiles/minimal.nix")
(modulesPath + "/profiles/headless.nix")
@ -114,7 +114,6 @@ in
environment.systemPackages = packageSets.extras ++ [
pkgs.chromium
pkgs.dtach
pkgs.php
];

View file

@ -2,7 +2,7 @@
...
}:
let
sandboxDomain = "netty.harivan.sh";
openClawDomain = "netty.harivan.sh";
forgejoDomain = "git.harivan.sh";
vaultDomain = "vault.harivan.sh";
betternasDomain = "api.betternas.com";
@ -19,11 +19,16 @@ in
recommendedTlsSettings = true;
clientMaxBodySize = "512m";
# Reserved for future use - nothing listening on this port yet
virtualHosts.${sandboxDomain} = {
virtualHosts.${openClawDomain} = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://127.0.0.1:2470";
locations."/" = {
proxyPass = "http://127.0.0.1:2470";
proxyWebsockets = true;
extraConfig = ''
proxy_set_header X-Forwarded-For $remote_addr;
'';
};
};
virtualHosts.${forgejoDomain} = {

View file

@ -0,0 +1,61 @@
{
pkgs,
username,
...
}:
let
homeDir = "/home/${username}";
openClawStateDir = "${homeDir}/.openclaw";
openClawConfigPath = "${openClawStateDir}/openclaw.json";
openClawEnvFile = "${openClawStateDir}/.env";
openClawBin = "${homeDir}/.local/share/npm/bin/openclaw";
openClawCheck = pkgs.writeShellScript "openclaw-gateway-check" ''
[ -x "${openClawBin}" ] && [ -f "${openClawConfigPath}" ] && [ -s "${openClawEnvFile}" ]
'';
in
{
systemd.tmpfiles.rules = [
"d ${openClawStateDir} 0700 ${username} users -"
"d ${openClawStateDir}/workspace 0700 ${username} users -"
"z ${openClawEnvFile} 0600 ${username} users -"
"z ${openClawConfigPath} 0600 ${username} users -"
];
systemd.services.openclaw-gateway = {
description = "OpenClaw Gateway";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
nodejs_22
git
coreutils
findutils
gnugrep
gawk
docker
];
environment = {
HOME = homeDir;
NODE_NO_WARNINGS = "1";
OPENCLAW_NIX_MODE = "1";
OPENCLAW_STATE_DIR = openClawStateDir;
OPENCLAW_CONFIG_PATH = openClawConfigPath;
NPM_CONFIG_USERCONFIG = "${homeDir}/.config/npm/npmrc";
XDG_CACHE_HOME = "${homeDir}/.cache";
XDG_CONFIG_HOME = "${homeDir}/.config";
XDG_DATA_HOME = "${homeDir}/.local/share";
};
serviceConfig = {
Type = "simple";
User = username;
Group = "users";
WorkingDirectory = openClawStateDir;
ExecCondition = openClawCheck;
EnvironmentFile = "-${openClawEnvFile}";
ExecStart = "${openClawBin} gateway run";
Restart = "on-failure";
RestartSec = 5;
};
};
}

View file

@ -1,50 +0,0 @@
{
pkgs,
username,
...
}:
let
piAgentEnvFile = "/var/lib/pi-agent/pi-agent.env";
piBin = "/home/${username}/.local/share/npm/bin/pi";
# Start pi inside an interactive login shell so it inherits the full user
# environment (PATH, XDG dirs, etc). dtach provides the PTY that pi needs.
piAgentStart = pkgs.writeShellScript "start-pi-agent" ''
exec ${pkgs.dtach}/bin/dtach -N /run/pi-agent/pi-agent.sock \
/run/current-system/sw/bin/zsh -lic 'exec ${piBin} --chat-bridge'
'';
in
{
systemd.tmpfiles.rules = [
"d /var/lib/pi-agent 0750 ${username} users -"
"z ${piAgentEnvFile} 0600 ${username} users -"
"d /run/pi-agent 0750 ${username} users -"
];
# Pi coding agent running as a Telegram bridge gateway.
# The main process hosts extensions (pi-channels, pi-schedule-prompt,
# pi-subagents) and polls Telegram. Actual prompts run in separate
# pi --mode rpc subprocesses spawned on demand.
#
# Runs as a login shell so the agent has the full user environment
#
# Config: ~/.pi/agent/settings.json (bot token, bridge settings)
# API key: /var/lib/pi-agent/pi-agent.env
systemd.services.pi-agent = {
description = "Pi Telegram Bridge";
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.dtach ];
serviceConfig = {
Type = "simple";
User = username;
Group = "users";
WorkingDirectory = "/home/${username}";
EnvironmentFile = piAgentEnvFile;
ExecStart = piAgentStart;
Restart = "on-failure";
RestartSec = 10;
};
};
}