Convert Openclaw to Podman container
- Use official ghcr.io/openclaw/openclaw image - configure via JSON config file - containerized for better isolation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
11a588a4d9
commit
a595445bd2
6 changed files with 173 additions and 215 deletions
64
braveapi.py
Normal file
64
braveapi.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
"""
|
||||||
|
Search Engine: Brave Search API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
# About the engine
|
||||||
|
about = {
|
||||||
|
"website": "https://brave.com/search/api/",
|
||||||
|
"use_official_api": True,
|
||||||
|
"require_api_key": True,
|
||||||
|
"results": "JSON",
|
||||||
|
}
|
||||||
|
|
||||||
|
categories = ['general', 'web']
|
||||||
|
paging = True
|
||||||
|
max_page = 10
|
||||||
|
page_size = 20
|
||||||
|
|
||||||
|
# API Endpoint
|
||||||
|
base_url = "https://api.search.brave.com/res/v1/web/search"
|
||||||
|
|
||||||
|
def request(query, params):
|
||||||
|
# Get key from environment
|
||||||
|
api_key = os.getenv('BRAVE_API_KEY')
|
||||||
|
|
||||||
|
# Brave expects offset 0-9 for pages
|
||||||
|
pageno = min(params.get('pageno', 1), 10)
|
||||||
|
offset = pageno - 1
|
||||||
|
|
||||||
|
# Simple query string construction with proper quoting
|
||||||
|
# Using 'x-subscription-token' (lowercase) which was verified to work
|
||||||
|
params['url'] = f"{base_url}?q={quote(query)}&count=20&offset={offset}"
|
||||||
|
|
||||||
|
params['headers'] = {
|
||||||
|
'x-subscription-token': api_key,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
params['method'] = 'GET'
|
||||||
|
|
||||||
|
# Clean up SearXNG defaults to prevent interference
|
||||||
|
if 'data' in params: del params['data']
|
||||||
|
if 'params' in params: del params['params']
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
def response(resp):
|
||||||
|
results = []
|
||||||
|
data = json.loads(resp.text)
|
||||||
|
|
||||||
|
# The Brave API returns 'web' results
|
||||||
|
web_results = data.get('web', {}).get('results', [])
|
||||||
|
|
||||||
|
for item in web_results:
|
||||||
|
results.append({
|
||||||
|
'url': item.get('url'),
|
||||||
|
'title': item.get('title'),
|
||||||
|
'content': item.get('description'),
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
@ -88,10 +88,10 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# === OpenClaw ===
|
# === OpenClaw ===
|
||||||
myModules.openclaw = {
|
myModules.openclaw-podman = {
|
||||||
enable = true;
|
enable = true;
|
||||||
port = 18789;
|
port = 18789;
|
||||||
environmentFile = config.sops.templates."openclaw.env".path;
|
domain = "openclaw.ashisgreat.xyz";
|
||||||
};
|
};
|
||||||
|
|
||||||
# OpenClaw secrets
|
# OpenClaw secrets
|
||||||
|
|
@ -99,10 +99,57 @@
|
||||||
sops.secrets.openclaw_zai_api_key = { };
|
sops.secrets.openclaw_zai_api_key = { };
|
||||||
|
|
||||||
sops.templates."openclaw.env" = {
|
sops.templates."openclaw.env" = {
|
||||||
owner = "openclaw";
|
|
||||||
content = ''
|
content = ''
|
||||||
DISCORD_TOKEN=${config.sops.placeholder.openclaw_discord_token}
|
DISCORD_TOKEN=${config.sops.placeholder.openclaw_discord_token}
|
||||||
ZAI_API_KEY=${config.sops.placeholder.openclaw_zai_api_key}
|
ZAI_API_KEY=${config.sops.placeholder.openclaw_zai_api_key}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sops.templates."openclaw_config.json" = {
|
||||||
|
content = builtins.toJSON {
|
||||||
|
gateway = {
|
||||||
|
port = 18789;
|
||||||
|
bind = "0.0.0.0";
|
||||||
|
trustedProxies = ["::1", "127.0.0.1", "10.88.0.0/16", "10.89.0.0/16"];
|
||||||
|
auth = {
|
||||||
|
mode = "none";
|
||||||
|
};
|
||||||
|
controlUi = {
|
||||||
|
dangerouslyAllowHostHeaderOriginFallback = true;
|
||||||
|
allowedOrigins = ["*"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
channels = {
|
||||||
|
discord = {
|
||||||
|
enabled = true;
|
||||||
|
groupPolicy = "open";
|
||||||
|
dmPolicy = "open";
|
||||||
|
allowFrom = ["*"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
agents = {
|
||||||
|
defaults = {
|
||||||
|
model = {
|
||||||
|
primary = "zai/glm-5";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
models = {
|
||||||
|
providers = {
|
||||||
|
zai = {
|
||||||
|
baseUrl = "https://api.z.ai/api/coding/paas/v4";
|
||||||
|
apiKey = "\${ZAI_API_KEY}";
|
||||||
|
api = "openai-completions";
|
||||||
|
models = [
|
||||||
|
{ id = "glm-4.7"; name = "GLM 4.7"; contextWindow = 128000; maxTokens = 131072; }
|
||||||
|
{ id = "glm-5"; name = "GLM 5"; contextWindow = 128000; maxTokens = 131072; }
|
||||||
|
{ id = "glm-5-turbo"; name = "GLM 5 Turbo"; contextWindow = 128000; maxTokens = 131072; }
|
||||||
|
{ id = "glm-4.5-air"; name = "GLM 4.5 Air"; contextWindow = 128000; maxTokens = 131072; }
|
||||||
|
{ id = "glm-4.7-flash"; name = "GLM 4.7 Flash"; contextWindow = 128000; maxTokens = 131072; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,6 @@
|
||||||
./podman.nix
|
./podman.nix
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
./searxng.nix
|
./searxng.nix
|
||||||
./openclaw.nix
|
./openclaw-podman.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
{
|
|
||||||
"gateway": {
|
|
||||||
"port": 18789,
|
|
||||||
"bind": "loopback",
|
|
||||||
"trustedProxies": ["::1", "127.0.0.1", "10.88.0.0/16", "10.89.0.0/16"],
|
|
||||||
"auth": {
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"controlUi": {
|
|
||||||
"dangerouslyAllowHostHeaderOriginFallback": true,
|
|
||||||
"allowedOrigins": ["*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"channels": {
|
|
||||||
"whatsapp": {
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
"discord": {
|
|
||||||
"enabled": true,
|
|
||||||
"groupPolicy": "open",
|
|
||||||
"dmPolicy": "open",
|
|
||||||
"allowFrom": ["*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"agents": {
|
|
||||||
"defaults": {
|
|
||||||
"model": {
|
|
||||||
"primary": "zai/glm-5"
|
|
||||||
},
|
|
||||||
"memorySearch": {
|
|
||||||
"provider": "openai",
|
|
||||||
"model": "nomic-embed-text",
|
|
||||||
"remote": {
|
|
||||||
"baseUrl": "http://localhost:11434/v1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tools": {
|
|
||||||
"exec": {
|
|
||||||
"security": "full",
|
|
||||||
"ask": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"skills": {
|
|
||||||
"entries": {}
|
|
||||||
},
|
|
||||||
"commands": {
|
|
||||||
"native": true,
|
|
||||||
"nativeSkills": "auto",
|
|
||||||
"restart": true,
|
|
||||||
"ownerDisplay": "raw"
|
|
||||||
},
|
|
||||||
"models": {
|
|
||||||
"mode": "merge",
|
|
||||||
"providers": {
|
|
||||||
"zai": {
|
|
||||||
"baseUrl": "https://api.z.ai/api/coding/paas/v4",
|
|
||||||
"apiKey": "${ZAI_API_KEY}",
|
|
||||||
"api": "openai-completions",
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"id": "glm-4.7",
|
|
||||||
"name": "GLM 4.7",
|
|
||||||
"contextWindow": 128000,
|
|
||||||
"maxTokens": 131072
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "glm-5",
|
|
||||||
"name": "GLM 5",
|
|
||||||
"contextWindow": 128000,
|
|
||||||
"maxTokens": 131072
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "glm-5-turbo",
|
|
||||||
"name": "GLM 5 Turbo",
|
|
||||||
"contextWindow": 128000,
|
|
||||||
"maxTokens": 131072
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "glm-4.5-air",
|
|
||||||
"name": "GLM 4.5 Air",
|
|
||||||
"contextWindow": 128000,
|
|
||||||
"maxTokens": 131072
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "glm-4.7-flash",
|
|
||||||
"name": "GLM 4.7 Flash",
|
|
||||||
"contextWindow": 128000,
|
|
||||||
"maxTokens": 131072
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
58
modules/openclaw-podman.nix
Normal file
58
modules/openclaw-podman.nix
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# OpenClaw Podman Module
|
||||||
|
# Provides: AI Agent with Discord integration running in a container
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# myModules.openclaw-podman = {
|
||||||
|
# enable = true;
|
||||||
|
# port = 18789;
|
||||||
|
# domain = "openclaw.example.com";
|
||||||
|
# };
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.myModules.openclaw-podman;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.myModules.openclaw-podman = {
|
||||||
|
enable = lib.mkEnableOption "OpenClaw AI Agent (Podman)";
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 18789;
|
||||||
|
description = "Gateway port for OpenClaw";
|
||||||
|
};
|
||||||
|
|
||||||
|
domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "openclaw.example.com";
|
||||||
|
description = "Public domain for OpenClaw";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
# Enable podman
|
||||||
|
myModules.podman.enable = true;
|
||||||
|
|
||||||
|
# OpenClaw container
|
||||||
|
virtualisation.oci-containers.containers."openclaw" = {
|
||||||
|
image = "ghcr.io/openclaw/openclaw:latest";
|
||||||
|
ports = [ "127.0.0.1:${toString cfg.port}:8080" ];
|
||||||
|
environmentFiles = [
|
||||||
|
config.sops.templates."openclaw.env".path
|
||||||
|
];
|
||||||
|
volumes = [
|
||||||
|
"${config.sops.templates."openclaw_config.json".path}:/root/.openclaw/config.json:ro"
|
||||||
|
"openclaw-data:/root/.openclaw"
|
||||||
|
];
|
||||||
|
extraOptions = [
|
||||||
|
"--network=host"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
# OpenClaw Module
|
|
||||||
# Provides: AI Agent with Discord integration
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# myModules.openclaw = {
|
|
||||||
# enable = true;
|
|
||||||
# port = 18789;
|
|
||||||
# };
|
|
||||||
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.myModules.openclaw;
|
|
||||||
configDir = "/var/lib/openclaw/config";
|
|
||||||
dataDir = "/var/lib/openclaw/data";
|
|
||||||
workspaceDir = "/var/lib/openclaw/workspace";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.myModules.openclaw = {
|
|
||||||
enable = lib.mkEnableOption "OpenClaw AI Agent";
|
|
||||||
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 18789;
|
|
||||||
description = "Gateway port for OpenClaw";
|
|
||||||
};
|
|
||||||
|
|
||||||
environmentFile = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.path;
|
|
||||||
default = null;
|
|
||||||
description = "Path to environment file with API keys";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
# Create user for openclaw
|
|
||||||
users.users.openclaw = {
|
|
||||||
isSystemUser = true;
|
|
||||||
group = "openclaw";
|
|
||||||
home = "/var/lib/openclaw";
|
|
||||||
createHome = true;
|
|
||||||
};
|
|
||||||
users.groups.openclaw = { };
|
|
||||||
|
|
||||||
# Create directories
|
|
||||||
systemd.tmpfiles.settings."10-openclaw" = {
|
|
||||||
"${configDir}".d = {
|
|
||||||
user = "openclaw";
|
|
||||||
group = "openclaw";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
"${dataDir}".d = {
|
|
||||||
user = "openclaw";
|
|
||||||
group = "openclaw";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
"${workspaceDir}".d = {
|
|
||||||
user = "openclaw";
|
|
||||||
group = "openclaw";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Copy config file
|
|
||||||
environment.etc."openclaw/openclaw.json".source = ./openclaw-config.json;
|
|
||||||
|
|
||||||
# Systemd service
|
|
||||||
systemd.services.openclaw = {
|
|
||||||
description = "OpenClaw AI Agent";
|
|
||||||
after = [ "network.target" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
|
|
||||||
serviceConfig = lib.mkMerge [
|
|
||||||
{
|
|
||||||
Type = "simple";
|
|
||||||
User = "openclaw";
|
|
||||||
Group = "openclaw";
|
|
||||||
WorkingDirectory = dataDir;
|
|
||||||
|
|
||||||
Environment = [
|
|
||||||
"NODE_ENV=production"
|
|
||||||
"OPENCLAW_CONFIG_DIR=${configDir}"
|
|
||||||
"OPENCLAW_DATA_DIR=${dataDir}"
|
|
||||||
"OPENCLAW_WORKSPACE_DIR=${workspaceDir}"
|
|
||||||
"PATH=${pkgs.nodejs_22}/bin:${pkgs.git}/bin:${pkgs.bash}/bin:${pkgs.coreutils}/bin"
|
|
||||||
];
|
|
||||||
|
|
||||||
ExecStartPre = [
|
|
||||||
"${pkgs.coreutils}/bin/mkdir -p ${configDir} ${dataDir} ${workspaceDir} /var/lib/openclaw/.openclaw"
|
|
||||||
"${pkgs.bash}/bin/bash -c 'cp -n /etc/openclaw/openclaw.json /var/lib/openclaw/.openclaw/openclaw.json || true'"
|
|
||||||
];
|
|
||||||
|
|
||||||
ExecStart = "${pkgs.nodejs_22}/bin/npx openclaw gateway --port ${toString cfg.port} --allow-unconfigured";
|
|
||||||
|
|
||||||
Restart = "on-failure";
|
|
||||||
RestartSec = "10s";
|
|
||||||
|
|
||||||
# Security
|
|
||||||
PrivateTmp = true;
|
|
||||||
ProtectSystem = "strict";
|
|
||||||
ReadWritePaths = [ "/var/lib/openclaw" "/var/lib/openclaw/.openclaw" configDir dataDir workspaceDir ];
|
|
||||||
NoNewPrivileges = true;
|
|
||||||
}
|
|
||||||
(lib.mkIf (cfg.environmentFile != null) {
|
|
||||||
EnvironmentFile = cfg.environmentFile;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue