diff --git a/flake.lock b/flake.lock index 03e3bf6..b6eeeab 100644 --- a/flake.lock +++ b/flake.lock @@ -116,13 +116,93 @@ "type": "github" } }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "nix-openclaw", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1767909183, + "narHash": "sha256-u/bcU0xePi5bgNoRsiqSIwaGBwDilKKFTz3g0hqOBAo=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "cd6e96d56ed4b2a779ac73a1227e0bb1519b3509", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "nix-openclaw": { + "inputs": { + "flake-utils": "flake-utils_2", + "home-manager": "home-manager", + "nix-steipete-tools": "nix-steipete-tools", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1771657318, + "narHash": "sha256-xFDNFFN5U9wtMcj1iACmoL6W4PWJeg9C0Pk2+BoY09s=", + "owner": "openclaw", + "repo": "nix-openclaw", + "rev": "fbef2087190ccfca375b351cdaad49bcbaea721a", + "type": "github" + }, + "original": { + "owner": "openclaw", + "repo": "nix-openclaw", + "type": "github" + } + }, + "nix-steipete-tools": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1771639217, + "narHash": "sha256-eidzES1s+0/Ngkw0fmLGdZ+NSN6P7RwKD0lPLYGqZoU=", + "owner": "openclaw", + "repo": "nix-steipete-tools", + "rev": "95ebfa73f4421144173f7060433c510a7d2d014a", + "type": "github" + }, + "original": { + "owner": "openclaw", + "repo": "nix-steipete-tools", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1771207753, - "narHash": "sha256-b9uG8yN50DRQ6A7JdZBfzq718ryYrlmGgqkRm9OOwCE=", + "lastModified": 1767364772, + "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d1c15b7d5806069da59e819999d70e1cec0760bf", + "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", "type": "github" }, "original": { @@ -147,19 +227,36 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1771207753, + "narHash": "sha256-b9uG8yN50DRQ6A7JdZBfzq718ryYrlmGgqkRm9OOwCE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d1c15b7d5806069da59e819999d70e1cec0760bf", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "agentd": "agentd", "dagger": "dagger", "flake-parts": "flake-parts", - "nixpkgs": "nixpkgs", + "nix-openclaw": "nix-openclaw", + "nixpkgs": "nixpkgs_2", "stereosd": "stereosd" } }, "stereosd": { "inputs": { "dagger": "dagger_2", - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "nixpkgs": [ "nixpkgs" ] @@ -207,6 +304,21 @@ "repo": "default", "type": "github" } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index bcee426..c46af68 100644 --- a/flake.nix +++ b/flake.nix @@ -16,6 +16,11 @@ url = "github:papercomputeco/stereosd"; inputs.nixpkgs.follows = "nixpkgs"; }; + + nix-openclaw = { + url = "github:openclaw/nix-openclaw"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = inputs@{ flake-parts, ... }: @@ -72,6 +77,11 @@ features = [ ./mixtapes/full/base.nix ]; }; + openclaw-mixtape = stereos-lib.mkMixtape { + name = "openclaw-mixtape"; + features = [ ./mixtapes/openclaw/base.nix ]; + }; + # -- Dev configurations (SSH key injection) -------------------------- opencode-mixtape-dev = stereos-lib.mkMixtape { name = "opencode-mixtape"; @@ -96,6 +106,12 @@ features = [ ./mixtapes/full/base.nix ]; extraModules = [ ./profiles/dev.nix ]; }; + + openclaw-mixtape-dev = stereos-lib.mkMixtape { + name = "openclaw-mixtape"; + features = [ ./mixtapes/openclaw/base.nix ]; + extraModules = [ ./profiles/dev.nix ]; + }; }; }; }; diff --git a/lib/default.nix b/lib/default.nix index fc2f417..7043e4d 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -10,7 +10,7 @@ # Build a complete NixOS system configuration ("mixtape") from: # - The shared stereOS module tree (modules/) # - A profile (profiles/base.nix) - # - External flake modules (agentd, stereosd) + overlays + # - External flake modules (agentd, stereosd, nix-openclaw) + overlays # - Mixtape-specific feature modules # # For dev builds, include profiles/dev.nix via extraModules to get @@ -28,16 +28,20 @@ inputs.nixpkgs.lib.nixosSystem { inherit system; modules = [ - # External flake NixOS modules -- provide services.agentd and - # services.stereosd options + baseline systemd units. + # External flake NixOS modules -- provide services.agentd, + # services.stereosd, and services.openclaw-gateway options + + # baseline systemd units. inputs.agentd.nixosModules.default inputs.stereosd.nixosModules.default + inputs.nix-openclaw.nixosModules.openclaw-gateway - # Apply overlays so pkgs.agentd and pkgs.stereosd are available. + # Apply overlays so pkgs.agentd, pkgs.stereosd, and + # pkgs.openclaw-gateway are available. ({ ... }: { nixpkgs.overlays = [ inputs.agentd.overlays.default inputs.stereosd.overlays.default + inputs.nix-openclaw.overlays.default ]; }) diff --git a/mixtapes/full/base.nix b/mixtapes/full/base.nix index 83c2fdf..b53d19d 100644 --- a/mixtapes/full/base.nix +++ b/mixtapes/full/base.nix @@ -10,5 +10,6 @@ ../opencode/base.nix ../claude-code/base.nix ../gemini-cli/base.nix + ../openclaw/base.nix ]; } diff --git a/mixtapes/openclaw/base.nix b/mixtapes/openclaw/base.nix new file mode 100644 index 0000000..b6d45ea --- /dev/null +++ b/mixtapes/openclaw/base.nix @@ -0,0 +1,39 @@ +# mixtapes/openclaw/base.nix +# +# OpenClaw mixtape — self-hosted, open-source personal AI assistant. +# +# OpenClaw routes messages from 15+ chat platforms through a local Gateway +# to any LLM backend, executing real-world tasks autonomously. The entire +# runtime is a single long-lived Node.js process (the Gateway) listening on +# ws://127.0.0.1:18789 by default. +# +# Package: from github:openclaw/nix-openclaw flake (overlay: pkgs.openclaw-gateway) +# Binary: openclaw (Gateway daemon + CLI) +# Service: openclaw-gateway.service (systemd, runs as dedicated 'openclaw' user) +# +# Required environment variables at runtime: +# ANTHROPIC_API_KEY or OPENAI_API_KEY (depending on configured provider) +# Plus channel tokens as needed (TELEGRAM_BOT_TOKEN, DISCORD_BOT_TOKEN, etc.) +# +# Secrets are injected at boot by stereosd into /run/stereos/secrets/ and +# loaded via the systemd EnvironmentFile mechanism. + +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./service.nix + ]; + + # Add the openclaw CLI to the agent's restricted PATH so the agent can + # interact with the running Gateway over WebSocket. + stereos.agent.extraPackages = [ pkgs.openclaw-gateway ]; + + # Also make it available system-wide (for admin use / debugging) + environment.systemPackages = [ pkgs.openclaw-gateway ]; + + # Disable auto-update and self-mutation flows — Nix manages the package. + # When this is set, openclaw skips update checks and shows Nix-specific + # remediation messages in the Control UI. + environment.variables.OPENCLAW_NIX_MODE = "1"; +} diff --git a/mixtapes/openclaw/service.nix b/mixtapes/openclaw/service.nix new file mode 100644 index 0000000..566c76c --- /dev/null +++ b/mixtapes/openclaw/service.nix @@ -0,0 +1,71 @@ +# mixtapes/openclaw/service.nix +# +# OpenClaw Gateway systemd service for stereOS. +# +# Runs the Gateway daemon as a dedicated 'openclaw' system user with state +# in /var/lib/openclaw. The agent user interacts with the Gateway over its +# WebSocket API (ws://127.0.0.1:18789) or via the `openclaw` CLI. +# +# This module uses the NixOS service options from the nix-openclaw flake +# (services.openclaw-gateway.*) which handles: +# - System user/group creation +# - State directory tmpfiles rules +# - Config file generation from Nix attrsets +# - systemd service definition with logging +# +# Secret injection: +# API keys and channel tokens are injected at boot by stereosd into +# /run/stereos/secrets/. The service loads them via EnvironmentFile. +# Example secrets file (/run/stereos/secrets/openclaw.env): +# ANTHROPIC_API_KEY=sk-ant-... +# TELEGRAM_BOT_TOKEN=123456:ABC-... +# OPENCLAW_GATEWAY_TOKEN= + +{ config, lib, pkgs, ... }: + +{ + services.openclaw-gateway = { + enable = true; + port = 18789; + + user = "openclaw"; + group = "openclaw"; + stateDir = "/var/lib/openclaw"; + + # Gateway configuration — generates /etc/openclaw/openclaw.json + config = { + gateway = { + mode = "local"; + bind = "loopback"; + }; + + # Memory uses SQLite with FTS5 + sqlite-vec for search. + # Embedding model auto-selects: local GGUF -> OpenAI -> Gemini -> BM25. + # No additional config needed — defaults are sensible. + }; + + # Load secrets from stereosd-injected environment file. + # The leading '-' tells systemd to silently skip if the file is absent + # (e.g. during first boot before stereosd has injected secrets). + environmentFiles = [ + "-/run/stereos/secrets/openclaw.env" + ]; + + # Extra packages available to the Gateway process at runtime + servicePath = with pkgs; [ + git + curl + ]; + }; + + # -- Service ordering: start after stereosd has injected secrets ----------- + systemd.services.openclaw-gateway = { + after = [ "stereosd.service" ]; + wants = [ "stereosd.service" ]; + }; + + # -- Firewall: Gateway is loopback-only, no ports to open ----------------- + # The WebSocket endpoint (127.0.0.1:18789) is only reachable from within + # the VM. If remote access is needed in the future, a reverse proxy + # (Caddy/nginx) with TLS should front the Gateway. +}