From a595445bd2e6fc73e7f690e86326c83206a90a03 Mon Sep 17 00:00:00 2001 From: ashisgreat22 Date: Wed, 18 Mar 2026 01:35:02 +0100 Subject: [PATCH] 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 --- braveapi.py | 64 +++++++++++++++++++ configuration.nix | 53 +++++++++++++++- modules/default.nix | 2 +- modules/openclaw-config.json | 96 ----------------------------- modules/openclaw-podman.nix | 58 ++++++++++++++++++ modules/openclaw.nix | 115 ----------------------------------- 6 files changed, 173 insertions(+), 215 deletions(-) create mode 100644 braveapi.py delete mode 100644 modules/openclaw-config.json create mode 100644 modules/openclaw-podman.nix delete mode 100644 modules/openclaw.nix diff --git a/braveapi.py b/braveapi.py new file mode 100644 index 0000000..d5adda0 --- /dev/null +++ b/braveapi.py @@ -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 diff --git a/configuration.nix b/configuration.nix index 5ef63cd..8bc2907 100644 --- a/configuration.nix +++ b/configuration.nix @@ -88,10 +88,10 @@ }; # === OpenClaw === - myModules.openclaw = { + myModules.openclaw-podman = { enable = true; port = 18789; - environmentFile = config.sops.templates."openclaw.env".path; + domain = "openclaw.ashisgreat.xyz"; }; # OpenClaw secrets @@ -99,10 +99,57 @@ sops.secrets.openclaw_zai_api_key = { }; sops.templates."openclaw.env" = { - owner = "openclaw"; content = '' DISCORD_TOKEN=${config.sops.placeholder.openclaw_discord_token} 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; } + ]; + }; + }; + }; + }; + }; } diff --git a/modules/default.nix b/modules/default.nix index 1abbb44..1e8d13b 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -5,6 +5,6 @@ ./podman.nix ./nginx.nix ./searxng.nix - ./openclaw.nix + ./openclaw-podman.nix ]; } diff --git a/modules/openclaw-config.json b/modules/openclaw-config.json deleted file mode 100644 index bdf93a7..0000000 --- a/modules/openclaw-config.json +++ /dev/null @@ -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 - } - ] - } - } - } -} diff --git a/modules/openclaw-podman.nix b/modules/openclaw-podman.nix new file mode 100644 index 0000000..6052eda --- /dev/null +++ b/modules/openclaw-podman.nix @@ -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" + ]; + }; + }; +} diff --git a/modules/openclaw.nix b/modules/openclaw.nix deleted file mode 100644 index 4b24b0f..0000000 --- a/modules/openclaw.nix +++ /dev/null @@ -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; - }) - ]; - }; - }; -}