style: nix fmt (nixfmt-tree) + exclude generated config options
This commit is contained in:
parent
f8681411dd
commit
c3d3be60ac
@ -6,11 +6,18 @@
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in {
|
||||
in
|
||||
{
|
||||
packages.default = pkgs.buildGoModule {
|
||||
pname = "hello-world";
|
||||
version = "0.1.0";
|
||||
@ -27,8 +34,8 @@
|
||||
skills = [ ./skills/hello-world ];
|
||||
packages = [ self.packages.${system}.default ];
|
||||
needs = {
|
||||
stateDirs = [];
|
||||
requiredEnv = [];
|
||||
stateDirs = [ ];
|
||||
requiredEnv = [ ];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@ -51,7 +51,11 @@
|
||||
};
|
||||
in
|
||||
{
|
||||
formatter = pkgs.nixfmt-tree;
|
||||
formatter = pkgs.nixfmt-tree.override {
|
||||
settings = {
|
||||
global.excludes = [ "nix/generated/openclaw-config-options.nix" ];
|
||||
};
|
||||
};
|
||||
|
||||
packages = packageSetStable // {
|
||||
default = packageSetStable.openclaw;
|
||||
|
||||
@ -1,86 +1,97 @@
|
||||
{ lib, pkgs, stdenv, nodejs_22, openclawGateway }:
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
stdenv,
|
||||
nodejs_22,
|
||||
openclawGateway,
|
||||
}:
|
||||
|
||||
let
|
||||
stubModule = { lib, ... }: {
|
||||
options = {
|
||||
assertions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.attrs;
|
||||
default = [];
|
||||
};
|
||||
stubModule =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options = {
|
||||
assertions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.attrs;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
home.homeDirectory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/tmp";
|
||||
};
|
||||
home.homeDirectory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/tmp";
|
||||
};
|
||||
|
||||
home.packages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.anything;
|
||||
default = [];
|
||||
};
|
||||
home.packages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.anything;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
home.file = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
};
|
||||
home.file = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
home.activation = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
};
|
||||
home.activation = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
launchd.agents = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
};
|
||||
launchd.agents = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
systemd.user.services = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
};
|
||||
systemd.user.services = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
programs.git.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
programs.git.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
lib = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
lib = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
moduleEval = lib.evalModules {
|
||||
modules = [
|
||||
stubModule
|
||||
../modules/home-manager/openclaw.nix
|
||||
({ lib, ... }: {
|
||||
config = {
|
||||
home.homeDirectory = "/tmp";
|
||||
programs.git.enable = false;
|
||||
lib.file.mkOutOfStoreSymlink = path: path;
|
||||
programs.openclaw = {
|
||||
enable = true;
|
||||
launchd.enable = false;
|
||||
systemd.enable = false;
|
||||
instances.default = {};
|
||||
config = {
|
||||
gateway = {
|
||||
bind = "tailnet";
|
||||
auth = {
|
||||
mode = "token";
|
||||
token = "test-token";
|
||||
};
|
||||
reload = {
|
||||
mode = "hot";
|
||||
debounceMs = 500;
|
||||
(
|
||||
{ lib, ... }:
|
||||
{
|
||||
config = {
|
||||
home.homeDirectory = "/tmp";
|
||||
programs.git.enable = false;
|
||||
lib.file.mkOutOfStoreSymlink = path: path;
|
||||
programs.openclaw = {
|
||||
enable = true;
|
||||
launchd.enable = false;
|
||||
systemd.enable = false;
|
||||
instances.default = { };
|
||||
config = {
|
||||
gateway = {
|
||||
bind = "tailnet";
|
||||
auth = {
|
||||
mode = "token";
|
||||
token = "test-token";
|
||||
};
|
||||
reload = {
|
||||
mode = "hot";
|
||||
debounceMs = 500;
|
||||
};
|
||||
};
|
||||
discovery.mdns.mode = "minimal";
|
||||
};
|
||||
discovery.mdns.mode = "minimal";
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
}
|
||||
)
|
||||
];
|
||||
specialArgs = { inherit pkgs; };
|
||||
};
|
||||
|
||||
@ -1,71 +1,80 @@
|
||||
{ lib, pkgs, stdenv }:
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
stdenv,
|
||||
}:
|
||||
|
||||
let
|
||||
stubModule = { lib, ... }: {
|
||||
options = {
|
||||
assertions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.attrs;
|
||||
default = [];
|
||||
};
|
||||
stubModule =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options = {
|
||||
assertions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.attrs;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
home.homeDirectory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/tmp";
|
||||
};
|
||||
home.homeDirectory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/tmp";
|
||||
};
|
||||
|
||||
home.packages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.anything;
|
||||
default = [];
|
||||
};
|
||||
home.packages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.anything;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
home.file = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
};
|
||||
home.file = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
home.activation = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
};
|
||||
home.activation = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
launchd.agents = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
};
|
||||
launchd.agents = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
systemd.user.services = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
};
|
||||
systemd.user.services = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
programs.git.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
programs.git.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
lib = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
lib = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
eval = lib.evalModules {
|
||||
modules = [
|
||||
stubModule
|
||||
../modules/home-manager/openclaw.nix
|
||||
({ lib, ... }: {
|
||||
config = {
|
||||
home.homeDirectory = "/tmp";
|
||||
programs.git.enable = false;
|
||||
lib.file.mkOutOfStoreSymlink = path: path;
|
||||
programs.openclaw = {
|
||||
enable = true;
|
||||
launchd.enable = false;
|
||||
systemd.enable = true;
|
||||
(
|
||||
{ lib, ... }:
|
||||
{
|
||||
config = {
|
||||
home.homeDirectory = "/tmp";
|
||||
programs.git.enable = false;
|
||||
lib.file.mkOutOfStoreSymlink = path: path;
|
||||
programs.openclaw = {
|
||||
enable = true;
|
||||
launchd.enable = false;
|
||||
systemd.enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
}
|
||||
)
|
||||
];
|
||||
specialArgs = { inherit pkgs; };
|
||||
};
|
||||
|
||||
@ -8,7 +8,8 @@ in
|
||||
pkgs.testers.nixosTest {
|
||||
name = "openclaw-hm-activation";
|
||||
|
||||
nodes.machine = { ... }:
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [ home-manager.nixosModules.home-manager ];
|
||||
|
||||
@ -23,7 +24,8 @@ pkgs.testers.nixosTest {
|
||||
home-manager = {
|
||||
useGlobalPkgs = true;
|
||||
useUserPackages = true;
|
||||
users.alice = { lib, ... }:
|
||||
users.alice =
|
||||
{ lib, ... }:
|
||||
{
|
||||
imports = [ openclawModule ];
|
||||
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
{ lib, stdenv, openclawGateway }:
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
openclawGateway,
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
pname = "openclaw-package-contents";
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
openclawLib = import ./lib.nix { inherit config lib pkgs; };
|
||||
@ -19,7 +24,7 @@ let
|
||||
launchd = cfg.launchd;
|
||||
systemd = cfg.systemd;
|
||||
plugins = openclawLib.effectivePlugins;
|
||||
config = {};
|
||||
config = { };
|
||||
appDefaults = {
|
||||
enable = true;
|
||||
attachExistingOnly = true;
|
||||
@ -32,20 +37,38 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
instances = if cfg.instances != {}
|
||||
then cfg.instances
|
||||
else lib.optionalAttrs cfg.enable { default = defaultInstance; };
|
||||
instances =
|
||||
if cfg.instances != { } then
|
||||
cfg.instances
|
||||
else
|
||||
lib.optionalAttrs cfg.enable { default = defaultInstance; };
|
||||
|
||||
enabledInstances = lib.filterAttrs (_: inst: inst.enable) instances;
|
||||
|
||||
plugins = import ./plugins.nix { inherit lib pkgs openclawLib enabledInstances; };
|
||||
|
||||
files = import ./files.nix {
|
||||
inherit config lib pkgs openclawLib enabledInstances plugins;
|
||||
plugins = import ./plugins.nix {
|
||||
inherit
|
||||
lib
|
||||
pkgs
|
||||
openclawLib
|
||||
enabledInstances
|
||||
;
|
||||
};
|
||||
|
||||
stripNulls = value:
|
||||
if value == null then null
|
||||
files = import ./files.nix {
|
||||
inherit
|
||||
config
|
||||
lib
|
||||
pkgs
|
||||
openclawLib
|
||||
enabledInstances
|
||||
plugins
|
||||
;
|
||||
};
|
||||
|
||||
stripNulls =
|
||||
value:
|
||||
if value == null then
|
||||
null
|
||||
else if builtins.isAttrs value then
|
||||
lib.filterAttrs (_: v: v != null) (builtins.mapAttrs (_: stripNulls) value)
|
||||
else if builtins.isList value then
|
||||
@ -59,154 +82,183 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
mkInstanceConfig = name: inst: let
|
||||
gatewayPackage =
|
||||
if inst.gatewayPath != null then
|
||||
pkgs.callPackage ../../packages/openclaw-gateway.nix {
|
||||
gatewaySrc = builtins.path {
|
||||
path = inst.gatewayPath;
|
||||
name = "openclaw-gateway-src";
|
||||
};
|
||||
pnpmDepsHash = inst.gatewayPnpmDepsHash;
|
||||
}
|
||||
else
|
||||
inst.package;
|
||||
pluginPackages = plugins.pluginPackagesFor name;
|
||||
pluginEnvAll = plugins.pluginEnvAllFor name;
|
||||
mergedConfig0 = stripNulls (lib.recursiveUpdate (lib.recursiveUpdate baseConfig cfg.config) inst.config);
|
||||
existingWorkspace = (((mergedConfig0.agents or {}).defaults or {}).workspace or null);
|
||||
mergedConfig =
|
||||
if (cfg.workspace.pinAgentDefaults or true) && existingWorkspace == null then
|
||||
lib.recursiveUpdate mergedConfig0 { agents = { defaults = { workspace = inst.workspaceDir; }; }; }
|
||||
else
|
||||
mergedConfig0;
|
||||
configJson = builtins.toJSON mergedConfig;
|
||||
configFile = pkgs.writeText "openclaw-${name}.json" configJson;
|
||||
gatewayWrapper = pkgs.writeShellScriptBin "openclaw-gateway-${name}" ''
|
||||
set -euo pipefail
|
||||
|
||||
if [ -n "${lib.makeBinPath pluginPackages}" ]; then
|
||||
export PATH="${lib.makeBinPath pluginPackages}:$PATH"
|
||||
fi
|
||||
|
||||
${lib.concatStringsSep "\n" (map (entry:
|
||||
let
|
||||
isFile = lib.hasSuffix "_FILE" entry.key;
|
||||
in ''
|
||||
if [ -f "${entry.value}" ]; then
|
||||
if ${if isFile then "true" else "false"}; then
|
||||
export ${entry.key}="${entry.value}"
|
||||
else
|
||||
rawValue="$("${lib.getExe' pkgs.coreutils "cat"}" "${entry.value}")"
|
||||
if [ "''${rawValue#${entry.key}=}" != "$rawValue" ]; then
|
||||
export ${entry.key}="''${rawValue#${entry.key}=}"
|
||||
else
|
||||
export ${entry.key}="$rawValue"
|
||||
fi
|
||||
fi
|
||||
mkInstanceConfig =
|
||||
name: inst:
|
||||
let
|
||||
gatewayPackage =
|
||||
if inst.gatewayPath != null then
|
||||
pkgs.callPackage ../../packages/openclaw-gateway.nix {
|
||||
gatewaySrc = builtins.path {
|
||||
path = inst.gatewayPath;
|
||||
name = "openclaw-gateway-src";
|
||||
};
|
||||
pnpmDepsHash = inst.gatewayPnpmDepsHash;
|
||||
}
|
||||
else
|
||||
export ${entry.key}="${entry.value}"
|
||||
inst.package;
|
||||
pluginPackages = plugins.pluginPackagesFor name;
|
||||
pluginEnvAll = plugins.pluginEnvAllFor name;
|
||||
mergedConfig0 = stripNulls (
|
||||
lib.recursiveUpdate (lib.recursiveUpdate baseConfig cfg.config) inst.config
|
||||
);
|
||||
existingWorkspace = (((mergedConfig0.agents or { }).defaults or { }).workspace or null);
|
||||
mergedConfig =
|
||||
if (cfg.workspace.pinAgentDefaults or true) && existingWorkspace == null then
|
||||
lib.recursiveUpdate mergedConfig0 {
|
||||
agents = {
|
||||
defaults = {
|
||||
workspace = inst.workspaceDir;
|
||||
};
|
||||
};
|
||||
}
|
||||
else
|
||||
mergedConfig0;
|
||||
configJson = builtins.toJSON mergedConfig;
|
||||
configFile = pkgs.writeText "openclaw-${name}.json" configJson;
|
||||
gatewayWrapper = pkgs.writeShellScriptBin "openclaw-gateway-${name}" ''
|
||||
set -euo pipefail
|
||||
|
||||
if [ -n "${lib.makeBinPath pluginPackages}" ]; then
|
||||
export PATH="${lib.makeBinPath pluginPackages}:$PATH"
|
||||
fi
|
||||
'') pluginEnvAll)}
|
||||
|
||||
exec "${gatewayPackage}/bin/openclaw" "$@"
|
||||
'';
|
||||
appDefaults = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.appDefaults.enable) {
|
||||
attachExistingOnly = inst.appDefaults.attachExistingOnly;
|
||||
gatewayPort = inst.gatewayPort;
|
||||
nixMode = inst.appDefaults.nixMode;
|
||||
};
|
||||
${lib.concatStringsSep "\n" (
|
||||
map (
|
||||
entry:
|
||||
let
|
||||
isFile = lib.hasSuffix "_FILE" entry.key;
|
||||
in
|
||||
''
|
||||
if [ -f "${entry.value}" ]; then
|
||||
if ${if isFile then "true" else "false"}; then
|
||||
export ${entry.key}="${entry.value}"
|
||||
else
|
||||
rawValue="$("${lib.getExe' pkgs.coreutils "cat"}" "${entry.value}")"
|
||||
if [ "''${rawValue#${entry.key}=}" != "$rawValue" ]; then
|
||||
export ${entry.key}="''${rawValue#${entry.key}=}"
|
||||
else
|
||||
export ${entry.key}="$rawValue"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
export ${entry.key}="${entry.value}"
|
||||
fi
|
||||
''
|
||||
) pluginEnvAll
|
||||
)}
|
||||
|
||||
appInstall = if !(pkgs.stdenv.hostPlatform.isDarwin && inst.app.install.enable && appPackage != null) then
|
||||
null
|
||||
else {
|
||||
name = lib.removePrefix "${homeDir}/" inst.app.install.path;
|
||||
value = {
|
||||
source = "${appPackage}/Applications/OpenClaw.app";
|
||||
recursive = true;
|
||||
force = true;
|
||||
exec "${gatewayPackage}/bin/openclaw" "$@"
|
||||
'';
|
||||
appDefaults = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.appDefaults.enable) {
|
||||
attachExistingOnly = inst.appDefaults.attachExistingOnly;
|
||||
gatewayPort = inst.gatewayPort;
|
||||
nixMode = inst.appDefaults.nixMode;
|
||||
};
|
||||
};
|
||||
|
||||
package = gatewayPackage;
|
||||
in {
|
||||
homeFile = {
|
||||
name = openclawLib.toRelative inst.configPath;
|
||||
value = { text = configJson; };
|
||||
};
|
||||
configFile = configFile;
|
||||
configPath = inst.configPath;
|
||||
appInstall =
|
||||
if !(pkgs.stdenv.hostPlatform.isDarwin && inst.app.install.enable && appPackage != null) then
|
||||
null
|
||||
else
|
||||
{
|
||||
name = lib.removePrefix "${homeDir}/" inst.app.install.path;
|
||||
value = {
|
||||
source = "${appPackage}/Applications/OpenClaw.app";
|
||||
recursive = true;
|
||||
force = true;
|
||||
};
|
||||
};
|
||||
|
||||
dirs = [ inst.stateDir inst.workspaceDir (builtins.dirOf inst.logPath) ];
|
||||
package = gatewayPackage;
|
||||
in
|
||||
{
|
||||
homeFile = {
|
||||
name = openclawLib.toRelative inst.configPath;
|
||||
value = {
|
||||
text = configJson;
|
||||
};
|
||||
};
|
||||
configFile = configFile;
|
||||
configPath = inst.configPath;
|
||||
|
||||
launchdAgent = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.enable) {
|
||||
"${inst.launchd.label}" = {
|
||||
enable = true;
|
||||
config = {
|
||||
Label = inst.launchd.label;
|
||||
ProgramArguments = [
|
||||
"${gatewayWrapper}/bin/openclaw-gateway-${name}"
|
||||
"gateway"
|
||||
"--port"
|
||||
"${toString inst.gatewayPort}"
|
||||
];
|
||||
RunAtLoad = true;
|
||||
KeepAlive = true;
|
||||
WorkingDirectory = inst.stateDir;
|
||||
StandardOutPath = inst.logPath;
|
||||
StandardErrorPath = inst.logPath;
|
||||
EnvironmentVariables = {
|
||||
HOME = homeDir;
|
||||
OPENCLAW_CONFIG_PATH = inst.configPath;
|
||||
OPENCLAW_STATE_DIR = inst.stateDir;
|
||||
OPENCLAW_IMAGE_BACKEND = "sips";
|
||||
OPENCLAW_NIX_MODE = "1";
|
||||
dirs = [
|
||||
inst.stateDir
|
||||
inst.workspaceDir
|
||||
(builtins.dirOf inst.logPath)
|
||||
];
|
||||
|
||||
launchdAgent = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.enable) {
|
||||
"${inst.launchd.label}" = {
|
||||
enable = true;
|
||||
config = {
|
||||
Label = inst.launchd.label;
|
||||
ProgramArguments = [
|
||||
"${gatewayWrapper}/bin/openclaw-gateway-${name}"
|
||||
"gateway"
|
||||
"--port"
|
||||
"${toString inst.gatewayPort}"
|
||||
];
|
||||
RunAtLoad = true;
|
||||
KeepAlive = true;
|
||||
WorkingDirectory = inst.stateDir;
|
||||
StandardOutPath = inst.logPath;
|
||||
StandardErrorPath = inst.logPath;
|
||||
EnvironmentVariables = {
|
||||
HOME = homeDir;
|
||||
OPENCLAW_CONFIG_PATH = inst.configPath;
|
||||
OPENCLAW_STATE_DIR = inst.stateDir;
|
||||
OPENCLAW_IMAGE_BACKEND = "sips";
|
||||
OPENCLAW_NIX_MODE = "1";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemdService = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && inst.systemd.enable) {
|
||||
"${inst.systemd.unitName}" = {
|
||||
Unit = {
|
||||
Description = "OpenClaw gateway (${name})";
|
||||
};
|
||||
Service = {
|
||||
ExecStart = "${gatewayWrapper}/bin/openclaw-gateway-${name} gateway --port ${toString inst.gatewayPort}";
|
||||
WorkingDirectory = inst.stateDir;
|
||||
Restart = "always";
|
||||
RestartSec = "1s";
|
||||
Environment = [
|
||||
"HOME=${homeDir}"
|
||||
"OPENCLAW_CONFIG_PATH=${inst.configPath}"
|
||||
"OPENCLAW_STATE_DIR=${inst.stateDir}"
|
||||
"OPENCLAW_NIX_MODE=1"
|
||||
];
|
||||
StandardOutput = "append:${inst.logPath}";
|
||||
StandardError = "append:${inst.logPath}";
|
||||
systemdService = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && inst.systemd.enable) {
|
||||
"${inst.systemd.unitName}" = {
|
||||
Unit = {
|
||||
Description = "OpenClaw gateway (${name})";
|
||||
};
|
||||
Service = {
|
||||
ExecStart = "${gatewayWrapper}/bin/openclaw-gateway-${name} gateway --port ${toString inst.gatewayPort}";
|
||||
WorkingDirectory = inst.stateDir;
|
||||
Restart = "always";
|
||||
RestartSec = "1s";
|
||||
Environment = [
|
||||
"HOME=${homeDir}"
|
||||
"OPENCLAW_CONFIG_PATH=${inst.configPath}"
|
||||
"OPENCLAW_STATE_DIR=${inst.stateDir}"
|
||||
"OPENCLAW_NIX_MODE=1"
|
||||
];
|
||||
StandardOutput = "append:${inst.logPath}";
|
||||
StandardError = "append:${inst.logPath}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
appDefaults = appDefaults;
|
||||
appInstall = appInstall;
|
||||
package = package;
|
||||
};
|
||||
appDefaults = appDefaults;
|
||||
appInstall = appInstall;
|
||||
package = package;
|
||||
};
|
||||
|
||||
instanceConfigs = lib.mapAttrsToList mkInstanceConfig enabledInstances;
|
||||
appInstalls = lib.filter (item: item != null) (map (item: item.appInstall) instanceConfigs);
|
||||
|
||||
appDefaults = lib.foldl' (acc: item: lib.recursiveUpdate acc item.appDefaults) {} instanceConfigs;
|
||||
appDefaults = lib.foldl' (acc: item: lib.recursiveUpdate acc item.appDefaults) { } instanceConfigs;
|
||||
appDefaultsEnabled = lib.filterAttrs (_: inst: inst.appDefaults.enable) enabledInstances;
|
||||
|
||||
in {
|
||||
config = lib.mkIf (cfg.enable || cfg.instances != {}) {
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (cfg.enable || cfg.instances != { }) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = lib.length (lib.attrNames appDefaultsEnabled) <= 1;
|
||||
message = "Only one OpenClaw instance may enable appDefaults.";
|
||||
}
|
||||
] ++ files.documentsAssertions ++ files.skillAssertions ++ plugins.pluginAssertions ++ plugins.pluginSkillAssertions;
|
||||
]
|
||||
++ files.documentsAssertions
|
||||
++ files.skillAssertions
|
||||
++ plugins.pluginAssertions
|
||||
++ plugins.pluginSkillAssertions;
|
||||
|
||||
home.packages = lib.unique (
|
||||
(map (item: item.package) instanceConfigs)
|
||||
@ -242,12 +294,20 @@ in {
|
||||
);
|
||||
|
||||
home.activation.openclawDirs = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " (lib.concatMap (item: item.dirs) instanceConfigs)}
|
||||
${lib.optionalString (plugins.pluginStateDirsAll != []) "run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " plugins.pluginStateDirsAll}"}
|
||||
run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${
|
||||
lib.concatStringsSep " " (lib.concatMap (item: item.dirs) instanceConfigs)
|
||||
}
|
||||
${lib.optionalString (plugins.pluginStateDirsAll != [ ])
|
||||
"run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " plugins.pluginStateDirsAll}"
|
||||
}
|
||||
'';
|
||||
|
||||
home.activation.openclawConfigFiles = lib.hm.dag.entryAfter [ "openclawDirs" ] ''
|
||||
${lib.concatStringsSep "\n" (map (item: "run --quiet ${lib.getExe' pkgs.coreutils "ln"} -sfn ${item.configFile} ${item.configPath}") instanceConfigs)}
|
||||
${lib.concatStringsSep "\n" (
|
||||
map (
|
||||
item: "run --quiet ${lib.getExe' pkgs.coreutils "ln"} -sfn ${item.configFile} ${item.configPath}"
|
||||
) instanceConfigs
|
||||
)}
|
||||
'';
|
||||
|
||||
home.activation.openclawPluginGuard = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
@ -255,14 +315,22 @@ in {
|
||||
${plugins.pluginGuards}
|
||||
'';
|
||||
|
||||
home.activation.openclawAppDefaults = lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != {}) (
|
||||
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
# Nix mode + app defaults (OpenClaw.app)
|
||||
/usr/bin/defaults write ai.openclaw.mac openclaw.nixMode -bool ${lib.boolToString (appDefaults.nixMode or true)}
|
||||
/usr/bin/defaults write ai.openclaw.mac openclaw.gateway.attachExistingOnly -bool ${lib.boolToString (appDefaults.attachExistingOnly or true)}
|
||||
/usr/bin/defaults write ai.openclaw.mac gatewayPort -int ${toString (appDefaults.gatewayPort or 18789)}
|
||||
''
|
||||
);
|
||||
home.activation.openclawAppDefaults =
|
||||
lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != { })
|
||||
(
|
||||
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
# Nix mode + app defaults (OpenClaw.app)
|
||||
/usr/bin/defaults write ai.openclaw.mac openclaw.nixMode -bool ${
|
||||
lib.boolToString (appDefaults.nixMode or true)
|
||||
}
|
||||
/usr/bin/defaults write ai.openclaw.mac openclaw.gateway.attachExistingOnly -bool ${
|
||||
lib.boolToString (appDefaults.attachExistingOnly or true)
|
||||
}
|
||||
/usr/bin/defaults write ai.openclaw.mac gatewayPort -int ${
|
||||
toString (appDefaults.gatewayPort or 18789)
|
||||
}
|
||||
''
|
||||
);
|
||||
|
||||
home.activation.openclawLaunchdRelink = lib.mkIf pkgs.stdenv.hostPlatform.isDarwin (
|
||||
lib.hm.dag.entryAfter [ "linkGeneration" ] ''
|
||||
|
||||
@ -1,8 +1,19 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
(lib.mkRenamedOptionModule [ "programs" "openclaw" "firstParty" ] [ "programs" "openclaw" "bundledPlugins" ])
|
||||
(lib.mkRenamedOptionModule [ "programs" "openclaw" "plugins" ] [ "programs" "openclaw" "customPlugins" ])
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "programs" "openclaw" "firstParty" ]
|
||||
[ "programs" "openclaw" "bundledPlugins" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "programs" "openclaw" "plugins" ]
|
||||
[ "programs" "openclaw" "customPlugins" ]
|
||||
)
|
||||
./options.nix
|
||||
./config.nix
|
||||
];
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
{ config, lib, pkgs, openclawLib, enabledInstances, plugins }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
openclawLib,
|
||||
enabledInstances,
|
||||
plugins,
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = openclawLib.cfg;
|
||||
@ -8,7 +15,8 @@ let
|
||||
documentsEnabled = cfg.documents != null;
|
||||
instanceWorkspaceDirs = map (inst: resolvePath inst.workspaceDir) (lib.attrValues enabledInstances);
|
||||
|
||||
renderSkill = skill:
|
||||
renderSkill =
|
||||
skill:
|
||||
let
|
||||
frontmatterLines = [
|
||||
"---"
|
||||
@ -24,15 +32,18 @@ let
|
||||
frontmatter = lib.concatStringsSep "\n" frontmatterLines;
|
||||
body = if skill ? body then skill.body else "";
|
||||
in
|
||||
"${frontmatter}\n\n${body}\n";
|
||||
"${frontmatter}\n\n${body}\n";
|
||||
|
||||
skillAssertions =
|
||||
let
|
||||
names = map (skill: skill.name) cfg.skills;
|
||||
nameCounts = lib.foldl' (acc: name: acc // { "${name}" = (acc.${name} or 0) + 1; }) {} names;
|
||||
nameCounts = lib.foldl' (acc: name: acc // { "${name}" = (acc.${name} or 0) + 1; }) { } names;
|
||||
duplicateNames = lib.attrNames (lib.filterAttrs (_: v: v > 1) nameCounts);
|
||||
in
|
||||
if duplicateNames == [] then [] else [
|
||||
if duplicateNames == [ ] then
|
||||
[ ]
|
||||
else
|
||||
[
|
||||
{
|
||||
assertion = false;
|
||||
message = "programs.openclaw.skills has duplicate names: ${lib.concatStringsSep ", " duplicateNames}";
|
||||
@ -41,48 +52,56 @@ let
|
||||
|
||||
skillFiles =
|
||||
let
|
||||
entriesForInstance = instName: inst:
|
||||
entriesForInstance =
|
||||
instName: inst:
|
||||
let
|
||||
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
|
||||
entryFor = skill:
|
||||
entryFor =
|
||||
skill:
|
||||
let
|
||||
mode = skill.mode or "symlink";
|
||||
source = if skill ? source && skill.source != null then resolvePath skill.source else null;
|
||||
in
|
||||
if mode == "inline" then
|
||||
{
|
||||
name = "${base}/${skill.name}/SKILL.md";
|
||||
value = { text = renderSkill skill; };
|
||||
}
|
||||
else if mode == "copy" then
|
||||
{
|
||||
name = "${base}/${skill.name}";
|
||||
value = {
|
||||
source = builtins.path {
|
||||
name = "openclaw-skill-${skill.name}";
|
||||
path = source;
|
||||
};
|
||||
recursive = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
name = "${base}/${skill.name}";
|
||||
value = {
|
||||
source = config.lib.file.mkOutOfStoreSymlink source;
|
||||
recursive = true;
|
||||
};
|
||||
if mode == "inline" then
|
||||
{
|
||||
name = "${base}/${skill.name}/SKILL.md";
|
||||
value = {
|
||||
text = renderSkill skill;
|
||||
};
|
||||
pluginEntriesFor = p:
|
||||
}
|
||||
else if mode == "copy" then
|
||||
{
|
||||
name = "${base}/${skill.name}";
|
||||
value = {
|
||||
source = builtins.path {
|
||||
name = "openclaw-skill-${skill.name}";
|
||||
path = source;
|
||||
};
|
||||
recursive = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
name = "${base}/${skill.name}";
|
||||
value = {
|
||||
source = config.lib.file.mkOutOfStoreSymlink source;
|
||||
recursive = true;
|
||||
};
|
||||
};
|
||||
pluginEntriesFor =
|
||||
p:
|
||||
map (skillPath: {
|
||||
name = "${base}/${builtins.baseNameOf skillPath}";
|
||||
value = { source = skillPath; recursive = true; };
|
||||
value = {
|
||||
source = skillPath;
|
||||
recursive = true;
|
||||
};
|
||||
}) p.skills;
|
||||
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [];
|
||||
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [ ];
|
||||
in
|
||||
(map entryFor cfg.skills) ++ (lib.flatten (map pluginEntriesFor pluginsForInstance));
|
||||
(map entryFor cfg.skills) ++ (lib.flatten (map pluginEntriesFor pluginsForInstance));
|
||||
in
|
||||
lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances));
|
||||
lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances));
|
||||
|
||||
documentsRequiredFiles = [
|
||||
"AGENTS.md"
|
||||
@ -103,9 +122,9 @@ let
|
||||
let
|
||||
extra = lib.filter (file: builtins.pathExists (cfg.documents + "/${file}")) documentsOptionalFiles;
|
||||
in
|
||||
documentsRequiredFiles ++ extra
|
||||
documentsRequiredFiles ++ extra
|
||||
else
|
||||
[];
|
||||
[ ];
|
||||
|
||||
documentsAssertions = lib.optionals documentsEnabled [
|
||||
{
|
||||
@ -126,77 +145,74 @@ let
|
||||
}
|
||||
];
|
||||
|
||||
documentsGuard =
|
||||
lib.optionalString documentsEnabled (
|
||||
let
|
||||
guardLine = file: ''
|
||||
if [ -e "${file}" ] && [ ! -L "${file}" ]; then
|
||||
echo "OpenClaw documents are managed by Nix. Please adopt ${file} into your documents directory and re-run." >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
guardForDir = dir: ''
|
||||
${lib.concatStringsSep "\n" (map (name: guardLine "${dir}/${name}") documentsFileNames)}
|
||||
'';
|
||||
in
|
||||
lib.concatStringsSep "\n" (map guardForDir instanceWorkspaceDirs)
|
||||
);
|
||||
documentsGuard = lib.optionalString documentsEnabled (
|
||||
let
|
||||
guardLine = file: ''
|
||||
if [ -e "${file}" ] && [ ! -L "${file}" ]; then
|
||||
echo "OpenClaw documents are managed by Nix. Please adopt ${file} into your documents directory and re-run." >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
guardForDir = dir: ''
|
||||
${lib.concatStringsSep "\n" (map (name: guardLine "${dir}/${name}") documentsFileNames)}
|
||||
'';
|
||||
in
|
||||
lib.concatStringsSep "\n" (map guardForDir instanceWorkspaceDirs)
|
||||
);
|
||||
|
||||
toolsReport =
|
||||
if documentsEnabled then
|
||||
let
|
||||
toolNames = toolSets.toolNames or [];
|
||||
renderPkgName = pkg:
|
||||
if pkg ? pname then pkg.pname else lib.getName pkg;
|
||||
renderPlugin = plugin:
|
||||
let
|
||||
pkgNames = map renderPkgName (lib.filter (p: p != null) plugin.packages);
|
||||
pkgSuffix =
|
||||
if pkgNames == []
|
||||
then ""
|
||||
else " — " + (lib.concatStringsSep ", " pkgNames);
|
||||
in
|
||||
"- " + plugin.name + pkgSuffix + " (" + plugin.source + ")";
|
||||
pluginLinesFor = instName: inst:
|
||||
let
|
||||
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [];
|
||||
lines = if pluginsForInstance == [] then [ "- (none)" ] else map renderPlugin pluginsForInstance;
|
||||
in
|
||||
[
|
||||
""
|
||||
"### Instance: ${instName}"
|
||||
] ++ lines;
|
||||
reportLines =
|
||||
toolNames = toolSets.toolNames or [ ];
|
||||
renderPkgName = pkg: if pkg ? pname then pkg.pname else lib.getName pkg;
|
||||
renderPlugin =
|
||||
plugin:
|
||||
let
|
||||
pkgNames = map renderPkgName (lib.filter (p: p != null) plugin.packages);
|
||||
pkgSuffix = if pkgNames == [ ] then "" else " — " + (lib.concatStringsSep ", " pkgNames);
|
||||
in
|
||||
"- " + plugin.name + pkgSuffix + " (" + plugin.source + ")";
|
||||
pluginLinesFor =
|
||||
instName: inst:
|
||||
let
|
||||
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [ ];
|
||||
lines = if pluginsForInstance == [ ] then [ "- (none)" ] else map renderPlugin pluginsForInstance;
|
||||
in
|
||||
[
|
||||
"<!-- BEGIN NIX-REPORT -->"
|
||||
""
|
||||
"## Nix-managed tools"
|
||||
""
|
||||
"### Built-in toolchain"
|
||||
"### Instance: ${instName}"
|
||||
]
|
||||
++ (if toolNames == [] then [ "- (none)" ] else map (name: "- " + name) toolNames)
|
||||
++ [
|
||||
""
|
||||
"## Nix-managed plugin report"
|
||||
""
|
||||
"Plugins enabled per instance (last-wins on name collisions):"
|
||||
]
|
||||
++ lib.concatLists (lib.mapAttrsToList pluginLinesFor enabledInstances)
|
||||
++ [
|
||||
""
|
||||
"Tools: batteries-included toolchain + plugin-provided CLIs."
|
||||
""
|
||||
"<!-- END NIX-REPORT -->"
|
||||
];
|
||||
++ lines;
|
||||
reportLines = [
|
||||
"<!-- BEGIN NIX-REPORT -->"
|
||||
""
|
||||
"## Nix-managed tools"
|
||||
""
|
||||
"### Built-in toolchain"
|
||||
]
|
||||
++ (if toolNames == [ ] then [ "- (none)" ] else map (name: "- " + name) toolNames)
|
||||
++ [
|
||||
""
|
||||
"## Nix-managed plugin report"
|
||||
""
|
||||
"Plugins enabled per instance (last-wins on name collisions):"
|
||||
]
|
||||
++ lib.concatLists (lib.mapAttrsToList pluginLinesFor enabledInstances)
|
||||
++ [
|
||||
""
|
||||
"Tools: batteries-included toolchain + plugin-provided CLIs."
|
||||
""
|
||||
"<!-- END NIX-REPORT -->"
|
||||
];
|
||||
reportText = lib.concatStringsSep "\n" reportLines;
|
||||
in
|
||||
pkgs.writeText "openclaw-tools-report.md" reportText
|
||||
pkgs.writeText "openclaw-tools-report.md" reportText
|
||||
else
|
||||
null;
|
||||
|
||||
toolsWithReport =
|
||||
if documentsEnabled then
|
||||
pkgs.runCommand "openclaw-tools-with-report.md" {} ''
|
||||
pkgs.runCommand "openclaw-tools-with-report.md" { } ''
|
||||
cat ${cfg.documents + "/TOOLS.md"} > $out
|
||||
echo "" >> $out
|
||||
cat ${toolsReport} >> $out
|
||||
@ -207,7 +223,8 @@ let
|
||||
documentsFiles =
|
||||
if documentsEnabled then
|
||||
let
|
||||
mkDocFiles = dir:
|
||||
mkDocFiles =
|
||||
dir:
|
||||
let
|
||||
mkDoc = name: {
|
||||
name = toRelative (dir + "/${name}");
|
||||
@ -216,18 +233,20 @@ let
|
||||
};
|
||||
};
|
||||
in
|
||||
lib.listToAttrs (map mkDoc documentsFileNames);
|
||||
lib.listToAttrs (map mkDoc documentsFileNames);
|
||||
in
|
||||
lib.mkMerge (map mkDocFiles instanceWorkspaceDirs)
|
||||
lib.mkMerge (map mkDocFiles instanceWorkspaceDirs)
|
||||
else
|
||||
{};
|
||||
{ };
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
inherit
|
||||
documentsEnabled
|
||||
documentsAssertions
|
||||
documentsGuard
|
||||
documentsFiles
|
||||
skillAssertions
|
||||
skillFiles;
|
||||
skillFiles
|
||||
;
|
||||
}
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
{ config, lib, pkgs }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.programs.openclaw;
|
||||
@ -9,59 +13,62 @@ let
|
||||
toolNamesOverride = cfg.toolNames;
|
||||
excludeToolNames = effectiveExcludeTools;
|
||||
};
|
||||
toolOverridesEnabled = cfg.toolNames != null || effectiveExcludeTools != [];
|
||||
toolOverridesEnabled = cfg.toolNames != null || effectiveExcludeTools != [ ];
|
||||
toolSets = import ../../../tools/extended.nix ({ inherit pkgs; } // toolOverrides);
|
||||
defaultPackage =
|
||||
if toolOverridesEnabled && cfg.package == pkgs.openclaw
|
||||
then (pkgs.openclawPackages.withTools toolOverrides).openclaw
|
||||
else cfg.package;
|
||||
if toolOverridesEnabled && cfg.package == pkgs.openclaw then
|
||||
(pkgs.openclawPackages.withTools toolOverrides).openclaw
|
||||
else
|
||||
cfg.package;
|
||||
appPackage = if cfg.appPackage != null then cfg.appPackage else defaultPackage;
|
||||
generatedConfigOptions = import ../../../generated/openclaw-config-options.nix { lib = lib; };
|
||||
|
||||
bundledPluginSources = let
|
||||
stepieteRev = "983210e3b6e9285780e87f48ce9354b51a270e95";
|
||||
stepieteNarHash = "sha256-fY8t41kMSHu2ovf89mIdvC7vkceroCwKxw/MKVn4rsE=";
|
||||
stepiete = tool:
|
||||
"github:openclaw/nix-steipete-tools?dir=tools/${tool}&rev=${stepieteRev}&narHash=${stepieteNarHash}";
|
||||
in {
|
||||
summarize = stepiete "summarize";
|
||||
peekaboo = stepiete "peekaboo";
|
||||
oracle = stepiete "oracle";
|
||||
poltergeist = stepiete "poltergeist";
|
||||
sag = stepiete "sag";
|
||||
camsnap = stepiete "camsnap";
|
||||
gogcli = stepiete "gogcli";
|
||||
goplaces = stepiete "goplaces";
|
||||
bird = stepiete "bird";
|
||||
sonoscli = stepiete "sonoscli";
|
||||
imsg = stepiete "imsg";
|
||||
};
|
||||
|
||||
bundledPlugins = lib.filter (p: p != null) (lib.mapAttrsToList (name: source:
|
||||
bundledPluginSources =
|
||||
let
|
||||
pluginCfg = cfg.bundledPlugins.${name};
|
||||
stepieteRev = "983210e3b6e9285780e87f48ce9354b51a270e95";
|
||||
stepieteNarHash = "sha256-fY8t41kMSHu2ovf89mIdvC7vkceroCwKxw/MKVn4rsE=";
|
||||
stepiete =
|
||||
tool:
|
||||
"github:openclaw/nix-steipete-tools?dir=tools/${tool}&rev=${stepieteRev}&narHash=${stepieteNarHash}";
|
||||
in
|
||||
if (pluginCfg.enable or false) then {
|
||||
inherit source;
|
||||
config = pluginCfg.config or {};
|
||||
} else null
|
||||
) bundledPluginSources);
|
||||
{
|
||||
summarize = stepiete "summarize";
|
||||
peekaboo = stepiete "peekaboo";
|
||||
oracle = stepiete "oracle";
|
||||
poltergeist = stepiete "poltergeist";
|
||||
sag = stepiete "sag";
|
||||
camsnap = stepiete "camsnap";
|
||||
gogcli = stepiete "gogcli";
|
||||
goplaces = stepiete "goplaces";
|
||||
bird = stepiete "bird";
|
||||
sonoscli = stepiete "sonoscli";
|
||||
imsg = stepiete "imsg";
|
||||
};
|
||||
|
||||
bundledPlugins = lib.filter (p: p != null) (
|
||||
lib.mapAttrsToList (
|
||||
name: source:
|
||||
let
|
||||
pluginCfg = cfg.bundledPlugins.${name};
|
||||
in
|
||||
if (pluginCfg.enable or false) then
|
||||
{
|
||||
inherit source;
|
||||
config = pluginCfg.config or { };
|
||||
}
|
||||
else
|
||||
null
|
||||
) bundledPluginSources
|
||||
);
|
||||
|
||||
effectivePlugins = cfg.customPlugins ++ bundledPlugins;
|
||||
|
||||
resolvePath = p:
|
||||
if lib.hasPrefix "~/" p then
|
||||
"${homeDir}/${lib.removePrefix "~/" p}"
|
||||
else
|
||||
p;
|
||||
resolvePath = p: if lib.hasPrefix "~/" p then "${homeDir}/${lib.removePrefix "~/" p}" else p;
|
||||
|
||||
toRelative = p:
|
||||
if lib.hasPrefix "${homeDir}/" p then
|
||||
lib.removePrefix "${homeDir}/" p
|
||||
else
|
||||
p;
|
||||
toRelative = p: if lib.hasPrefix "${homeDir}/" p then lib.removePrefix "${homeDir}/" p else p;
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
inherit
|
||||
cfg
|
||||
homeDir
|
||||
@ -75,5 +82,6 @@ in {
|
||||
bundledPlugins
|
||||
effectivePlugins
|
||||
resolvePath
|
||||
toRelative;
|
||||
toRelative
|
||||
;
|
||||
}
|
||||
|
||||
@ -17,9 +17,11 @@
|
||||
|
||||
stateDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if name == "default"
|
||||
then "${openclawLib.homeDir}/.openclaw"
|
||||
else "${openclawLib.homeDir}/.openclaw-${name}";
|
||||
default =
|
||||
if name == "default" then
|
||||
"${openclawLib.homeDir}/.openclaw"
|
||||
else
|
||||
"${openclawLib.homeDir}/.openclaw-${name}";
|
||||
description = "State directory for this OpenClaw instance (logs, sessions, config).";
|
||||
};
|
||||
|
||||
@ -37,9 +39,11 @@
|
||||
|
||||
logPath = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if name == "default"
|
||||
then "/tmp/openclaw/openclaw-gateway.log"
|
||||
else "/tmp/openclaw/openclaw-gateway-${name}.log";
|
||||
default =
|
||||
if name == "default" then
|
||||
"/tmp/openclaw/openclaw-gateway.log"
|
||||
else
|
||||
"/tmp/openclaw/openclaw-gateway-${name}.log";
|
||||
description = "Log path for this OpenClaw gateway instance.";
|
||||
};
|
||||
|
||||
@ -62,26 +66,28 @@
|
||||
};
|
||||
|
||||
plugins = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
options = {
|
||||
source = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Plugin source pointer (e.g., github:owner/repo or path:/...).";
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
source = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Plugin source pointer (e.g., github:owner/repo or path:/...).";
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
description = "Plugin-specific configuration (env/files/etc).";
|
||||
};
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
description = "Plugin-specific configuration (env/files/etc).";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
default = openclawLib.effectivePlugins;
|
||||
description = "Plugins enabled for this instance (includes first-party toggles).";
|
||||
};
|
||||
|
||||
config = lib.mkOption {
|
||||
type = lib.types.submodule { options = openclawLib.generatedConfigOptions; };
|
||||
default = {};
|
||||
default = { };
|
||||
description = "OpenClaw config (schema-typed).";
|
||||
};
|
||||
|
||||
@ -93,9 +99,11 @@
|
||||
|
||||
launchd.label = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if name == "default"
|
||||
then "com.steipete.openclaw.gateway"
|
||||
else "com.steipete.openclaw.gateway.${name}";
|
||||
default =
|
||||
if name == "default" then
|
||||
"com.steipete.openclaw.gateway"
|
||||
else
|
||||
"com.steipete.openclaw.gateway.${name}";
|
||||
description = "launchd label for this instance.";
|
||||
};
|
||||
|
||||
@ -107,9 +115,7 @@
|
||||
|
||||
systemd.unitName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if name == "default"
|
||||
then "openclaw-gateway"
|
||||
else "openclaw-gateway-${name}";
|
||||
default = if name == "default" then "openclaw-gateway" else "openclaw-gateway-${name}";
|
||||
description = "systemd user service unit name for this instance.";
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
openclawLib = import ./lib.nix { inherit config lib pkgs; };
|
||||
@ -30,7 +35,11 @@ let
|
||||
description = "Optional openclaw metadata for the skill frontmatter.";
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.enum [ "symlink" "copy" "inline" ];
|
||||
type = lib.types.enum [
|
||||
"symlink"
|
||||
"copy"
|
||||
"inline"
|
||||
];
|
||||
default = "symlink";
|
||||
description = "Install mode for the skill (symlink/copy/inline).";
|
||||
};
|
||||
@ -42,7 +51,8 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
options.programs.openclaw = {
|
||||
enable = lib.mkEnableOption "OpenClaw (batteries-included)";
|
||||
|
||||
@ -60,7 +70,7 @@ in {
|
||||
|
||||
excludeTools = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = "Tool names to remove from the built-in toolchain.";
|
||||
};
|
||||
|
||||
@ -104,54 +114,66 @@ in {
|
||||
|
||||
skills = lib.mkOption {
|
||||
type = lib.types.listOf mkSkillOption;
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = "Declarative skills installed into each instance workspace.";
|
||||
};
|
||||
|
||||
customPlugins = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
options = {
|
||||
source = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Plugin source pointer (e.g., github:owner/repo or path:/...).";
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
source = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Plugin source pointer (e.g., github:owner/repo or path:/...).";
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
description = "Plugin-specific configuration (env/files/etc).";
|
||||
};
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
description = "Plugin-specific configuration (env/files/etc).";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
}
|
||||
);
|
||||
default = [ ];
|
||||
description = "Custom/community plugins (merged with bundled plugin toggles).";
|
||||
};
|
||||
|
||||
bundledPlugins = let
|
||||
mkPlugin = { name, defaultEnable ? false }: {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = defaultEnable;
|
||||
description = "Enable the ${name} plugin (bundled).";
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
description = "Bundled plugin configuration passed through to ${name} (env/settings).";
|
||||
bundledPlugins =
|
||||
let
|
||||
mkPlugin =
|
||||
{
|
||||
name,
|
||||
defaultEnable ? false,
|
||||
}:
|
||||
{
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = defaultEnable;
|
||||
description = "Enable the ${name} plugin (bundled).";
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
description = "Bundled plugin configuration passed through to ${name} (env/settings).";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
summarize = mkPlugin { name = "summarize"; };
|
||||
peekaboo = mkPlugin { name = "peekaboo"; };
|
||||
oracle = mkPlugin { name = "oracle"; };
|
||||
poltergeist = mkPlugin { name = "poltergeist"; };
|
||||
sag = mkPlugin { name = "sag"; };
|
||||
camsnap = mkPlugin { name = "camsnap"; };
|
||||
gogcli = mkPlugin { name = "gogcli"; };
|
||||
goplaces = mkPlugin {
|
||||
name = "goplaces";
|
||||
defaultEnable = true;
|
||||
};
|
||||
bird = mkPlugin { name = "bird"; };
|
||||
sonoscli = mkPlugin { name = "sonoscli"; };
|
||||
imsg = mkPlugin { name = "imsg"; };
|
||||
};
|
||||
in {
|
||||
summarize = mkPlugin { name = "summarize"; };
|
||||
peekaboo = mkPlugin { name = "peekaboo"; };
|
||||
oracle = mkPlugin { name = "oracle"; };
|
||||
poltergeist = mkPlugin { name = "poltergeist"; };
|
||||
sag = mkPlugin { name = "sag"; };
|
||||
camsnap = mkPlugin { name = "camsnap"; };
|
||||
gogcli = mkPlugin { name = "gogcli"; };
|
||||
goplaces = mkPlugin { name = "goplaces"; defaultEnable = true; };
|
||||
bird = mkPlugin { name = "bird"; };
|
||||
sonoscli = mkPlugin { name = "sonoscli"; };
|
||||
imsg = mkPlugin { name = "imsg"; };
|
||||
};
|
||||
|
||||
launchd.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
@ -179,7 +201,7 @@ in {
|
||||
|
||||
instances = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule instanceModule);
|
||||
default = {};
|
||||
default = { };
|
||||
description = "Named OpenClaw instances (prod/test).";
|
||||
};
|
||||
|
||||
@ -199,7 +221,7 @@ in {
|
||||
|
||||
config = lib.mkOption {
|
||||
type = lib.types.submodule { options = openclawLib.generatedConfigOptions; };
|
||||
default = {};
|
||||
default = { };
|
||||
description = "OpenClaw config (schema-typed).";
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,104 +1,124 @@
|
||||
{ lib, pkgs, openclawLib, enabledInstances }:
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
openclawLib,
|
||||
enabledInstances,
|
||||
}:
|
||||
|
||||
let
|
||||
resolvePath = openclawLib.resolvePath;
|
||||
toRelative = openclawLib.toRelative;
|
||||
|
||||
resolvePlugin = plugin: let
|
||||
flake = builtins.getFlake plugin.source;
|
||||
system = pkgs.stdenv.hostPlatform.system;
|
||||
openclawPluginRaw =
|
||||
if flake ? openclawPlugin then flake.openclawPlugin
|
||||
else throw "openclawPlugin missing in ${plugin.source}";
|
||||
openclawPlugin =
|
||||
if builtins.isFunction openclawPluginRaw
|
||||
then openclawPluginRaw system
|
||||
else openclawPluginRaw;
|
||||
resolvedPlugin =
|
||||
if openclawPlugin == null
|
||||
then throw "openclawPlugin is null in ${plugin.source} for ${system}"
|
||||
else openclawPlugin;
|
||||
needs = resolvedPlugin.needs or {};
|
||||
in {
|
||||
source = plugin.source;
|
||||
name = resolvedPlugin.name or (throw "openclawPlugin.name missing in ${plugin.source}");
|
||||
skills = resolvedPlugin.skills or [];
|
||||
packages = resolvedPlugin.packages or [];
|
||||
needs = {
|
||||
stateDirs = needs.stateDirs or [];
|
||||
requiredEnv = needs.requiredEnv or [];
|
||||
};
|
||||
config = plugin.config or {};
|
||||
};
|
||||
|
||||
resolvedPluginsByInstance =
|
||||
lib.mapAttrs (instName: inst:
|
||||
let
|
||||
resolved = map resolvePlugin inst.plugins;
|
||||
counts = lib.foldl' (acc: p:
|
||||
acc // { "${p.name}" = (acc.${p.name} or 0) + 1; }
|
||||
) {} resolved;
|
||||
duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts);
|
||||
byName = lib.foldl' (acc: p: acc // { "${p.name}" = p; }) {} resolved;
|
||||
ordered = lib.attrValues byName;
|
||||
in
|
||||
if duplicates == []
|
||||
then ordered
|
||||
else lib.warn
|
||||
"programs.openclaw.instances.${instName}: duplicate plugin names detected (${lib.concatStringsSep ", " duplicates}); last entry wins."
|
||||
ordered
|
||||
) enabledInstances;
|
||||
|
||||
pluginPackagesFor = instName:
|
||||
lib.flatten (map (p: p.packages) (resolvedPluginsByInstance.${instName} or []));
|
||||
|
||||
pluginPackagesAll =
|
||||
lib.flatten (map pluginPackagesFor (lib.attrNames enabledInstances));
|
||||
|
||||
pluginStateDirsFor = instName:
|
||||
resolvePlugin =
|
||||
plugin:
|
||||
let
|
||||
dirs = lib.flatten (map (p: p.needs.stateDirs) (resolvedPluginsByInstance.${instName} or []));
|
||||
flake = builtins.getFlake plugin.source;
|
||||
system = pkgs.stdenv.hostPlatform.system;
|
||||
openclawPluginRaw =
|
||||
if flake ? openclawPlugin then
|
||||
flake.openclawPlugin
|
||||
else
|
||||
throw "openclawPlugin missing in ${plugin.source}";
|
||||
openclawPlugin =
|
||||
if builtins.isFunction openclawPluginRaw then openclawPluginRaw system else openclawPluginRaw;
|
||||
resolvedPlugin =
|
||||
if openclawPlugin == null then
|
||||
throw "openclawPlugin is null in ${plugin.source} for ${system}"
|
||||
else
|
||||
openclawPlugin;
|
||||
needs = resolvedPlugin.needs or { };
|
||||
in
|
||||
map (dir: resolvePath ("~/" + dir)) dirs;
|
||||
{
|
||||
source = plugin.source;
|
||||
name = resolvedPlugin.name or (throw "openclawPlugin.name missing in ${plugin.source}");
|
||||
skills = resolvedPlugin.skills or [ ];
|
||||
packages = resolvedPlugin.packages or [ ];
|
||||
needs = {
|
||||
stateDirs = needs.stateDirs or [ ];
|
||||
requiredEnv = needs.requiredEnv or [ ];
|
||||
};
|
||||
config = plugin.config or { };
|
||||
};
|
||||
|
||||
pluginStateDirsAll =
|
||||
lib.flatten (map pluginStateDirsFor (lib.attrNames enabledInstances));
|
||||
|
||||
pluginEnvFor = instName:
|
||||
resolvedPluginsByInstance = lib.mapAttrs (
|
||||
instName: inst:
|
||||
let
|
||||
entries = resolvedPluginsByInstance.${instName} or [];
|
||||
toPairs = p:
|
||||
resolved = map resolvePlugin inst.plugins;
|
||||
counts = lib.foldl' (acc: p: acc // { "${p.name}" = (acc.${p.name} or 0) + 1; }) { } resolved;
|
||||
duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts);
|
||||
byName = lib.foldl' (acc: p: acc // { "${p.name}" = p; }) { } resolved;
|
||||
ordered = lib.attrValues byName;
|
||||
in
|
||||
if duplicates == [ ] then
|
||||
ordered
|
||||
else
|
||||
lib.warn "programs.openclaw.instances.${instName}: duplicate plugin names detected (${lib.concatStringsSep ", " duplicates}); last entry wins." ordered
|
||||
) enabledInstances;
|
||||
|
||||
pluginPackagesFor =
|
||||
instName: lib.flatten (map (p: p.packages) (resolvedPluginsByInstance.${instName} or [ ]));
|
||||
|
||||
pluginPackagesAll = lib.flatten (map pluginPackagesFor (lib.attrNames enabledInstances));
|
||||
|
||||
pluginStateDirsFor =
|
||||
instName:
|
||||
let
|
||||
dirs = lib.flatten (map (p: p.needs.stateDirs) (resolvedPluginsByInstance.${instName} or [ ]));
|
||||
in
|
||||
map (dir: resolvePath ("~/" + dir)) dirs;
|
||||
|
||||
pluginStateDirsAll = lib.flatten (map pluginStateDirsFor (lib.attrNames enabledInstances));
|
||||
|
||||
pluginEnvFor =
|
||||
instName:
|
||||
let
|
||||
entries = resolvedPluginsByInstance.${instName} or [ ];
|
||||
toPairs =
|
||||
p:
|
||||
let
|
||||
env = (p.config.env or {});
|
||||
env = (p.config.env or { });
|
||||
required = p.needs.requiredEnv;
|
||||
in
|
||||
map (k: { key = k; value = env.${k} or ""; plugin = p.name; }) required;
|
||||
map (k: {
|
||||
key = k;
|
||||
value = env.${k} or "";
|
||||
plugin = p.name;
|
||||
}) required;
|
||||
in
|
||||
lib.flatten (map toPairs entries);
|
||||
lib.flatten (map toPairs entries);
|
||||
|
||||
pluginEnvAllFor = instName:
|
||||
pluginEnvAllFor =
|
||||
instName:
|
||||
let
|
||||
entries = resolvedPluginsByInstance.${instName} or [];
|
||||
toPairs = p:
|
||||
let env = (p.config.env or {});
|
||||
in map (k: { key = k; value = env.${k}; plugin = p.name; }) (lib.attrNames env);
|
||||
entries = resolvedPluginsByInstance.${instName} or [ ];
|
||||
toPairs =
|
||||
p:
|
||||
let
|
||||
env = (p.config.env or { });
|
||||
in
|
||||
map (k: {
|
||||
key = k;
|
||||
value = env.${k};
|
||||
plugin = p.name;
|
||||
}) (lib.attrNames env);
|
||||
in
|
||||
lib.flatten (map toPairs entries);
|
||||
lib.flatten (map toPairs entries);
|
||||
|
||||
pluginAssertions =
|
||||
lib.flatten (lib.mapAttrsToList (instName: inst:
|
||||
pluginAssertions = lib.flatten (
|
||||
lib.mapAttrsToList (
|
||||
instName: inst:
|
||||
let
|
||||
plugins = resolvedPluginsByInstance.${instName} or [];
|
||||
envFor = p: (p.config.env or {});
|
||||
missingFor = p:
|
||||
lib.filter (req: !(builtins.hasAttr req (envFor p))) p.needs.requiredEnv;
|
||||
configMissingStateDir = p:
|
||||
(p.config.settings or {}) != {} && (p.needs.stateDirs or []) == [];
|
||||
mkAssertion = p:
|
||||
plugins = resolvedPluginsByInstance.${instName} or [ ];
|
||||
envFor = p: (p.config.env or { });
|
||||
missingFor = p: lib.filter (req: !(builtins.hasAttr req (envFor p))) p.needs.requiredEnv;
|
||||
configMissingStateDir = p: (p.config.settings or { }) != { } && (p.needs.stateDirs or [ ]) == [ ];
|
||||
mkAssertion =
|
||||
p:
|
||||
let
|
||||
missing = missingFor p;
|
||||
in {
|
||||
assertion = missing == [];
|
||||
in
|
||||
{
|
||||
assertion = missing == [ ];
|
||||
message = "programs.openclaw.instances.${instName}: plugin ${p.name} missing required env: ${lib.concatStringsSep ", " missing}";
|
||||
};
|
||||
mkConfigAssertion = p: {
|
||||
@ -106,75 +126,86 @@ let
|
||||
message = "programs.openclaw.instances.${instName}: plugin ${p.name} provides settings but declares no stateDirs (needed for config.json).";
|
||||
};
|
||||
in
|
||||
(map mkAssertion plugins) ++ (map mkConfigAssertion plugins)
|
||||
) enabledInstances);
|
||||
(map mkAssertion plugins) ++ (map mkConfigAssertion plugins)
|
||||
) enabledInstances
|
||||
);
|
||||
|
||||
pluginSkillsFiles =
|
||||
let
|
||||
entriesForInstance = instName: inst:
|
||||
entriesForInstance =
|
||||
instName: inst:
|
||||
let
|
||||
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
|
||||
skillEntriesFor = p:
|
||||
skillEntriesFor =
|
||||
p:
|
||||
map (skillPath: {
|
||||
name = "${base}/${builtins.baseNameOf skillPath}";
|
||||
value = { source = skillPath; recursive = true; };
|
||||
value = {
|
||||
source = skillPath;
|
||||
recursive = true;
|
||||
};
|
||||
}) p.skills;
|
||||
plugins = resolvedPluginsByInstance.${instName} or [];
|
||||
plugins = resolvedPluginsByInstance.${instName} or [ ];
|
||||
in
|
||||
lib.flatten (map skillEntriesFor plugins);
|
||||
lib.flatten (map skillEntriesFor plugins);
|
||||
in
|
||||
lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances));
|
||||
lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances));
|
||||
|
||||
pluginConfigFiles =
|
||||
let
|
||||
entryFor = instName: inst:
|
||||
entryFor =
|
||||
instName: inst:
|
||||
let
|
||||
plugins = resolvedPluginsByInstance.${instName} or [];
|
||||
mkEntries = p:
|
||||
plugins = resolvedPluginsByInstance.${instName} or [ ];
|
||||
mkEntries =
|
||||
p:
|
||||
let
|
||||
cfg = p.config.settings or {};
|
||||
dir =
|
||||
if (p.needs.stateDirs or []) == []
|
||||
then null
|
||||
else lib.head (p.needs.stateDirs or []);
|
||||
cfg = p.config.settings or { };
|
||||
dir = if (p.needs.stateDirs or [ ]) == [ ] then null else lib.head (p.needs.stateDirs or [ ]);
|
||||
in
|
||||
if cfg == {} then
|
||||
[]
|
||||
else
|
||||
(if dir == null then
|
||||
if cfg == { } then
|
||||
[ ]
|
||||
else
|
||||
(
|
||||
if dir == null then
|
||||
throw "plugin ${p.name} provides settings but no stateDirs are defined"
|
||||
else [
|
||||
{
|
||||
name = toRelative (resolvePath ("~/" + dir + "/config.json"));
|
||||
value = { text = builtins.toJSON cfg; };
|
||||
}
|
||||
]);
|
||||
else
|
||||
[
|
||||
{
|
||||
name = toRelative (resolvePath ("~/" + dir + "/config.json"));
|
||||
value = {
|
||||
text = builtins.toJSON cfg;
|
||||
};
|
||||
}
|
||||
]
|
||||
);
|
||||
in
|
||||
lib.flatten (map mkEntries plugins);
|
||||
lib.flatten (map mkEntries plugins);
|
||||
entries = lib.flatten (lib.mapAttrsToList entryFor enabledInstances);
|
||||
in
|
||||
lib.listToAttrs entries;
|
||||
lib.listToAttrs entries;
|
||||
|
||||
pluginSkillAssertions =
|
||||
let
|
||||
skillTargets =
|
||||
lib.flatten (lib.concatLists (lib.mapAttrsToList (instName: inst:
|
||||
let
|
||||
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
|
||||
plugins = resolvedPluginsByInstance.${instName} or [];
|
||||
in
|
||||
map (p:
|
||||
map (skillPath:
|
||||
"${base}/${p.name}/${builtins.baseNameOf skillPath}"
|
||||
) p.skills
|
||||
) plugins
|
||||
) enabledInstances));
|
||||
counts = lib.foldl' (acc: path:
|
||||
acc // { "${path}" = (acc.${path} or 0) + 1; }
|
||||
) {} skillTargets;
|
||||
skillTargets = lib.flatten (
|
||||
lib.concatLists (
|
||||
lib.mapAttrsToList (
|
||||
instName: inst:
|
||||
let
|
||||
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
|
||||
plugins = resolvedPluginsByInstance.${instName} or [ ];
|
||||
in
|
||||
map (p: map (skillPath: "${base}/${p.name}/${builtins.baseNameOf skillPath}") p.skills) plugins
|
||||
) enabledInstances
|
||||
)
|
||||
);
|
||||
counts = lib.foldl' (acc: path: acc // { "${path}" = (acc.${path} or 0) + 1; }) { } skillTargets;
|
||||
duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts);
|
||||
in
|
||||
if duplicates == [] then [] else [
|
||||
if duplicates == [ ] then
|
||||
[ ]
|
||||
else
|
||||
[
|
||||
{
|
||||
assertion = false;
|
||||
message = "Duplicate skill paths detected: ${lib.concatStringsSep ", " duplicates}";
|
||||
@ -193,13 +224,14 @@ let
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
entriesForInstance = instName:
|
||||
map (entry: entry // { instance = instName; }) (pluginEnvFor instName);
|
||||
entriesForInstance =
|
||||
instName: map (entry: entry // { instance = instName; }) (pluginEnvFor instName);
|
||||
entries = lib.flatten (map entriesForInstance (lib.attrNames enabledInstances));
|
||||
in
|
||||
lib.concatStringsSep "\n" (map renderCheck entries);
|
||||
lib.concatStringsSep "\n" (map renderCheck entries);
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
inherit
|
||||
resolvedPluginsByInstance
|
||||
pluginPackagesFor
|
||||
@ -212,5 +244,6 @@ in {
|
||||
pluginSkillsFiles
|
||||
pluginConfigFiles
|
||||
pluginSkillAssertions
|
||||
pluginGuards;
|
||||
pluginGuards
|
||||
;
|
||||
}
|
||||
|
||||
@ -2,13 +2,18 @@ final: prev:
|
||||
let
|
||||
packages = import ./packages { pkgs = prev; };
|
||||
toolNames = (import ./tools/extended.nix { pkgs = prev; }).toolNames;
|
||||
withTools = { toolNamesOverride ? null, excludeToolNames ? [] }:
|
||||
withTools =
|
||||
{
|
||||
toolNamesOverride ? null,
|
||||
excludeToolNames ? [ ],
|
||||
}:
|
||||
import ./packages {
|
||||
pkgs = prev;
|
||||
inherit toolNamesOverride excludeToolNames;
|
||||
};
|
||||
in
|
||||
packages // {
|
||||
packages
|
||||
// {
|
||||
openclawPackages = packages // {
|
||||
inherit toolNames withTools;
|
||||
};
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
{ pkgs
|
||||
, sourceInfo ? import ../sources/openclaw-source.nix
|
||||
, steipetePkgs ? {}
|
||||
, toolNamesOverride ? null
|
||||
, excludeToolNames ? []
|
||||
{
|
||||
pkgs,
|
||||
sourceInfo ? import ../sources/openclaw-source.nix,
|
||||
steipetePkgs ? { },
|
||||
toolNamesOverride ? null,
|
||||
excludeToolNames ? [ ],
|
||||
}:
|
||||
let
|
||||
isDarwin = pkgs.stdenv.hostPlatform.isDarwin;
|
||||
@ -26,8 +27,10 @@ let
|
||||
openclaw-app = openclawApp;
|
||||
extendedTools = toolSets.tools;
|
||||
};
|
||||
in {
|
||||
in
|
||||
{
|
||||
openclaw-gateway = openclawGateway;
|
||||
openclaw = openclawBundle;
|
||||
openclaw-tools = openclawTools;
|
||||
} // (if isDarwin then { openclaw-app = openclawApp; } else {})
|
||||
}
|
||||
// (if isDarwin then { openclaw-app = openclawApp; } else { })
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{ lib
|
||||
, stdenvNoCC
|
||||
, fetchzip
|
||||
{
|
||||
lib,
|
||||
stdenvNoCC,
|
||||
fetchzip,
|
||||
}:
|
||||
|
||||
stdenvNoCC.mkDerivation {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
{ lib
|
||||
, buildEnv
|
||||
, openclaw-gateway
|
||||
, openclaw-app ? null
|
||||
, extendedTools ? []
|
||||
{
|
||||
lib,
|
||||
buildEnv,
|
||||
openclaw-gateway,
|
||||
openclaw-app ? null,
|
||||
extendedTools ? [ ],
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
@ -8,45 +8,55 @@
|
||||
nix-openclaw.url = "github:openclaw/nix-openclaw";
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, home-manager, nix-openclaw, ... }:
|
||||
outputs =
|
||||
{
|
||||
nixpkgs,
|
||||
home-manager,
|
||||
nix-openclaw,
|
||||
...
|
||||
}:
|
||||
let
|
||||
system = "aarch64-darwin";
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ nix-openclaw.overlays.default ];
|
||||
};
|
||||
in {
|
||||
in
|
||||
{
|
||||
homeConfigurations.hm-test = home-manager.lib.homeManagerConfiguration {
|
||||
inherit pkgs;
|
||||
modules = [
|
||||
nix-openclaw.homeManagerModules.openclaw
|
||||
({ ... }: {
|
||||
home = {
|
||||
username = "runner";
|
||||
homeDirectory = "/tmp/hm-activation-home";
|
||||
stateVersion = "23.11";
|
||||
};
|
||||
(
|
||||
{ ... }:
|
||||
{
|
||||
home = {
|
||||
username = "runner";
|
||||
homeDirectory = "/tmp/hm-activation-home";
|
||||
stateVersion = "23.11";
|
||||
};
|
||||
|
||||
programs.openclaw = {
|
||||
enable = true;
|
||||
installApp = false;
|
||||
instances.default = {
|
||||
gatewayPort = 18999;
|
||||
config = {
|
||||
logging = {
|
||||
level = "debug";
|
||||
file = "/tmp/openclaw/openclaw-gateway.log";
|
||||
};
|
||||
gateway = {
|
||||
mode = "local";
|
||||
auth = {
|
||||
token = "hm-activation-test-token";
|
||||
programs.openclaw = {
|
||||
enable = true;
|
||||
installApp = false;
|
||||
instances.default = {
|
||||
gatewayPort = 18999;
|
||||
config = {
|
||||
logging = {
|
||||
level = "debug";
|
||||
file = "/tmp/openclaw/openclaw-gateway.log";
|
||||
};
|
||||
gateway = {
|
||||
mode = "local";
|
||||
auth = {
|
||||
token = "hm-activation-test-token";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,19 +1,26 @@
|
||||
{ pkgs
|
||||
, steipetePkgs ? {}
|
||||
, toolNamesOverride ? null
|
||||
, excludeToolNames ? []
|
||||
{
|
||||
pkgs,
|
||||
steipetePkgs ? { },
|
||||
toolNamesOverride ? null,
|
||||
excludeToolNames ? [ ],
|
||||
}:
|
||||
let
|
||||
lib = pkgs.lib;
|
||||
safe = list: builtins.filter (p: p != null) list;
|
||||
pickFrom = scope: name:
|
||||
pickFrom =
|
||||
scope: name:
|
||||
if builtins.hasAttr name scope then
|
||||
let pkg = scope.${name}; in
|
||||
let
|
||||
pkg = scope.${name};
|
||||
in
|
||||
if lib.meta.availableOn pkgs.stdenv.hostPlatform pkg then pkg else null
|
||||
else
|
||||
null;
|
||||
pick = name:
|
||||
let fromSteipete = pickFrom steipetePkgs name; in
|
||||
pick =
|
||||
name:
|
||||
let
|
||||
fromSteipete = pickFrom steipetePkgs name;
|
||||
in
|
||||
if fromSteipete != null then fromSteipete else pickFrom pkgs name;
|
||||
ensure = names: safe (map pick names);
|
||||
|
||||
@ -55,7 +62,8 @@ let
|
||||
toolNamesBase = if toolNamesOverride != null then toolNamesOverride else baseNames ++ extraNames;
|
||||
toolNames = builtins.filter (name: !builtins.elem name excludeToolNames) toolNamesBase;
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
tools = ensure toolNames;
|
||||
toolNames = toolNames;
|
||||
}
|
||||
|
||||
@ -8,12 +8,22 @@
|
||||
nix-openclaw.url = "github:openclaw/nix-openclaw";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, home-manager, nix-openclaw }:
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
home-manager,
|
||||
nix-openclaw,
|
||||
}:
|
||||
let
|
||||
# REPLACE: aarch64-darwin (Apple Silicon), x86_64-darwin (Intel), or x86_64-linux
|
||||
system = "<system>";
|
||||
pkgs = import nixpkgs { inherit system; overlays = [ nix-openclaw.overlays.default ]; };
|
||||
in {
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ nix-openclaw.overlays.default ];
|
||||
};
|
||||
in
|
||||
{
|
||||
# REPLACE: <user> with your username (run `whoami`)
|
||||
homeConfigurations."<user>" = home-manager.lib.homeManagerConfiguration {
|
||||
inherit pkgs;
|
||||
@ -47,7 +57,9 @@
|
||||
# REPLACE: your Telegram user ID (get from @userinfobot)
|
||||
allowFrom = [ <allowFrom> ];
|
||||
groups = {
|
||||
"*" = { requireMention = true; };
|
||||
"*" = {
|
||||
requireMention = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user