style: nix fmt (nixfmt-tree) + exclude generated config options

This commit is contained in:
joshp123 2026-02-14 23:24:11 -08:00
parent f8681411dd
commit c3d3be60ac
20 changed files with 908 additions and 664 deletions

View File

@ -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 = [ ];
};
};
}

View File

@ -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;

View File

@ -1,11 +1,19 @@
{ lib, pkgs, stdenv, nodejs_22, openclawGateway }:
{
lib,
pkgs,
stdenv,
nodejs_22,
openclawGateway,
}:
let
stubModule = { lib, ... }: {
stubModule =
{ lib, ... }:
{
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [];
default = [ ];
};
home.homeDirectory = lib.mkOption {
@ -15,27 +23,27 @@ let
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.anything;
default = [];
default = [ ];
};
home.file = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
home.activation = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
launchd.agents = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
systemd.user.services = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
programs.git.enable = lib.mkOption {
@ -45,7 +53,7 @@ let
lib = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
};
};
@ -54,7 +62,9 @@ let
modules = [
stubModule
../modules/home-manager/openclaw.nix
({ lib, ... }: {
(
{ lib, ... }:
{
config = {
home.homeDirectory = "/tmp";
programs.git.enable = false;
@ -63,7 +73,7 @@ let
enable = true;
launchd.enable = false;
systemd.enable = false;
instances.default = {};
instances.default = { };
config = {
gateway = {
bind = "tailnet";
@ -80,7 +90,8 @@ let
};
};
};
})
}
)
];
specialArgs = { inherit pkgs; };
};

View File

@ -1,11 +1,17 @@
{ lib, pkgs, stdenv }:
{
lib,
pkgs,
stdenv,
}:
let
stubModule = { lib, ... }: {
stubModule =
{ lib, ... }:
{
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [];
default = [ ];
};
home.homeDirectory = lib.mkOption {
@ -15,27 +21,27 @@ let
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.anything;
default = [];
default = [ ];
};
home.file = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
home.activation = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
launchd.agents = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
systemd.user.services = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
programs.git.enable = lib.mkOption {
@ -45,7 +51,7 @@ let
lib = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
};
};
};
@ -54,7 +60,9 @@ let
modules = [
stubModule
../modules/home-manager/openclaw.nix
({ lib, ... }: {
(
{ lib, ... }:
{
config = {
home.homeDirectory = "/tmp";
programs.git.enable = false;
@ -65,7 +73,8 @@ let
systemd.enable = true;
};
};
})
}
)
];
specialArgs = { inherit pkgs; };
};

View File

@ -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 ];

View File

@ -1,4 +1,8 @@
{ lib, stdenv, openclawGateway }:
{
lib,
stdenv,
openclawGateway,
}:
stdenv.mkDerivation {
pname = "openclaw-package-contents";

View File

@ -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,7 +82,9 @@ let
};
};
mkInstanceConfig = name: inst: let
mkInstanceConfig =
name: inst:
let
gatewayPackage =
if inst.gatewayPath != null then
pkgs.callPackage ../../packages/openclaw-gateway.nix {
@ -73,11 +98,19 @@ let
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);
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; }; }; }
lib.recursiveUpdate mergedConfig0 {
agents = {
defaults = {
workspace = inst.workspaceDir;
};
};
}
else
mergedConfig0;
configJson = builtins.toJSON mergedConfig;
@ -89,10 +122,13 @@ let
export PATH="${lib.makeBinPath pluginPackages}:$PATH"
fi
${lib.concatStringsSep "\n" (map (entry:
${lib.concatStringsSep "\n" (
map (
entry:
let
isFile = lib.hasSuffix "_FILE" entry.key;
in ''
in
''
if [ -f "${entry.value}" ]; then
if ${if isFile then "true" else "false"}; then
export ${entry.key}="${entry.value}"
@ -107,7 +143,9 @@ let
else
export ${entry.key}="${entry.value}"
fi
'') pluginEnvAll)}
''
) pluginEnvAll
)}
exec "${gatewayPackage}/bin/openclaw" "$@"
'';
@ -117,9 +155,11 @@ let
nixMode = inst.appDefaults.nixMode;
};
appInstall = if !(pkgs.stdenv.hostPlatform.isDarwin && inst.app.install.enable && appPackage != null) then
appInstall =
if !(pkgs.stdenv.hostPlatform.isDarwin && inst.app.install.enable && appPackage != null) then
null
else {
else
{
name = lib.removePrefix "${homeDir}/" inst.app.install.path;
value = {
source = "${appPackage}/Applications/OpenClaw.app";
@ -129,15 +169,22 @@ let
};
package = gatewayPackage;
in {
in
{
homeFile = {
name = openclawLib.toRelative inst.configPath;
value = { text = configJson; };
value = {
text = configJson;
};
};
configFile = configFile;
configPath = inst.configPath;
dirs = [ inst.stateDir inst.workspaceDir (builtins.dirOf inst.logPath) ];
dirs = [
inst.stateDir
inst.workspaceDir
(builtins.dirOf inst.logPath)
];
launchdAgent = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.enable) {
"${inst.launchd.label}" = {
@ -196,17 +243,22 @@ let
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,12 +315,20 @@ in {
${plugins.pluginGuards}
'';
home.activation.openclawAppDefaults = lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != {}) (
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)}
/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)
}
''
);

View File

@ -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
];

View File

@ -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 = [
"---"
@ -29,10 +37,13 @@ let
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,10 +52,12 @@ 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;
@ -52,7 +65,9 @@ let
if mode == "inline" then
{
name = "${base}/${skill.name}/SKILL.md";
value = { text = renderSkill skill; };
value = {
text = renderSkill skill;
};
}
else if mode == "copy" then
{
@ -73,12 +88,16 @@ let
recursive = true;
};
};
pluginEntriesFor = p:
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));
in
@ -105,7 +124,7 @@ let
in
documentsRequiredFiles ++ extra
else
[];
[ ];
documentsAssertions = lib.optionals documentsEnabled [
{
@ -126,8 +145,7 @@ let
}
];
documentsGuard =
lib.optionalString documentsEnabled (
documentsGuard = lib.optionalString documentsEnabled (
let
guardLine = file: ''
if [ -e "${file}" ] && [ ! -L "${file}" ]; then
@ -145,36 +163,34 @@ let
toolsReport =
if documentsEnabled then
let
toolNames = toolSets.toolNames or [];
renderPkgName = pkg:
if pkg ? pname then pkg.pname else lib.getName pkg;
renderPlugin = plugin:
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);
pkgSuffix = if pkgNames == [ ] then "" else " " + (lib.concatStringsSep ", " pkgNames);
in
"- " + plugin.name + pkgSuffix + " (" + plugin.source + ")";
pluginLinesFor = instName: inst:
pluginLinesFor =
instName: inst:
let
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [];
lines = if pluginsForInstance == [] then [ "- (none)" ] else map renderPlugin pluginsForInstance;
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [ ];
lines = if pluginsForInstance == [ ] then [ "- (none)" ] else map renderPlugin pluginsForInstance;
in
[
""
"### Instance: ${instName}"
] ++ lines;
reportLines =
[
]
++ lines;
reportLines = [
"<!-- BEGIN NIX-REPORT -->"
""
"## Nix-managed tools"
""
"### Built-in toolchain"
]
++ (if toolNames == [] then [ "- (none)" ] else map (name: "- " + name) toolNames)
++ (if toolNames == [ ] then [ "- (none)" ] else map (name: "- " + name) toolNames)
++ [
""
"## Nix-managed plugin report"
@ -196,7 +212,7 @@ let
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}");
@ -220,14 +237,16 @@ let
in
lib.mkMerge (map mkDocFiles instanceWorkspaceDirs)
else
{};
{ };
in {
in
{
inherit
documentsEnabled
documentsAssertions
documentsGuard
documentsFiles
skillAssertions
skillFiles;
skillFiles
;
}

View File

@ -1,4 +1,8 @@
{ config, lib, pkgs }:
{
config,
lib,
pkgs,
}:
let
cfg = config.programs.openclaw;
@ -9,21 +13,25 @@ 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
bundledPluginSources =
let
stepieteRev = "983210e3b6e9285780e87f48ce9354b51a270e95";
stepieteNarHash = "sha256-fY8t41kMSHu2ovf89mIdvC7vkceroCwKxw/MKVn4rsE=";
stepiete = tool:
stepiete =
tool:
"github:openclaw/nix-steipete-tools?dir=tools/${tool}&rev=${stepieteRev}&narHash=${stepieteNarHash}";
in {
in
{
summarize = stepiete "summarize";
peekaboo = stepiete "peekaboo";
oracle = stepiete "oracle";
@ -37,31 +45,30 @@ let
imsg = stepiete "imsg";
};
bundledPlugins = lib.filter (p: p != null) (lib.mapAttrsToList (name: source:
bundledPlugins = lib.filter (p: p != null) (
lib.mapAttrsToList (
name: source:
let
pluginCfg = cfg.bundledPlugins.${name};
in
if (pluginCfg.enable or false) then {
if (pluginCfg.enable or false) then
{
inherit source;
config = pluginCfg.config or {};
} else null
) bundledPluginSources);
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
;
}

View File

@ -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,7 +66,8 @@
};
plugins = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
type = lib.types.listOf (
lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.str;
@ -70,18 +75,19 @@
};
config = lib.mkOption {
type = lib.types.attrs;
default = {};
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.";
};

View File

@ -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,12 +114,13 @@ 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 {
type = lib.types.listOf (
lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.str;
@ -117,17 +128,24 @@ in {
};
config = lib.mkOption {
type = lib.types.attrs;
default = {};
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 }: {
bundledPlugins =
let
mkPlugin =
{
name,
defaultEnable ? false,
}:
{
enable = lib.mkOption {
type = lib.types.bool;
default = defaultEnable;
@ -135,11 +153,12 @@ in {
};
config = lib.mkOption {
type = lib.types.attrs;
default = {};
default = { };
description = "Bundled plugin configuration passed through to ${name} (env/settings).";
};
};
in {
in
{
summarize = mkPlugin { name = "summarize"; };
peekaboo = mkPlugin { name = "peekaboo"; };
oracle = mkPlugin { name = "oracle"; };
@ -147,7 +166,10 @@ in {
sag = mkPlugin { name = "sag"; };
camsnap = mkPlugin { name = "camsnap"; };
gogcli = mkPlugin { name = "gogcli"; };
goplaces = mkPlugin { name = "goplaces"; defaultEnable = true; };
goplaces = mkPlugin {
name = "goplaces";
defaultEnable = true;
};
bird = mkPlugin { name = "bird"; };
sonoscli = mkPlugin { name = "sonoscli"; };
imsg = mkPlugin { name = "imsg"; };
@ -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).";
};
};

View File

@ -1,104 +1,124 @@
{ lib, pkgs, openclawLib, enabledInstances }:
{
lib,
pkgs,
openclawLib,
enabledInstances,
}:
let
resolvePath = openclawLib.resolvePath;
toRelative = openclawLib.toRelative;
resolvePlugin = plugin: let
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}";
if flake ? openclawPlugin then
flake.openclawPlugin
else
throw "openclawPlugin missing in ${plugin.source}";
openclawPlugin =
if builtins.isFunction openclawPluginRaw
then openclawPluginRaw system
else openclawPluginRaw;
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 {
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 [];
skills = resolvedPlugin.skills or [ ];
packages = resolvedPlugin.packages or [ ];
needs = {
stateDirs = needs.stateDirs or [];
requiredEnv = needs.requiredEnv or [];
stateDirs = needs.stateDirs or [ ];
requiredEnv = needs.requiredEnv or [ ];
};
config = plugin.config or {};
config = plugin.config or { };
};
resolvedPluginsByInstance =
lib.mapAttrs (instName: inst:
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;
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;
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."
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 []));
pluginPackagesFor =
instName: lib.flatten (map (p: p.packages) (resolvedPluginsByInstance.${instName} or [ ]));
pluginPackagesAll =
lib.flatten (map pluginPackagesFor (lib.attrNames enabledInstances));
pluginPackagesAll = lib.flatten (map pluginPackagesFor (lib.attrNames enabledInstances));
pluginStateDirsFor = instName:
pluginStateDirsFor =
instName:
let
dirs = lib.flatten (map (p: p.needs.stateDirs) (resolvedPluginsByInstance.${instName} or []));
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));
pluginStateDirsAll = lib.flatten (map pluginStateDirsFor (lib.attrNames enabledInstances));
pluginEnvFor = instName:
pluginEnvFor =
instName:
let
entries = resolvedPluginsByInstance.${instName} or [];
toPairs = p:
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);
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);
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: {
@ -107,19 +127,25 @@ let
};
in
(map mkAssertion plugins) ++ (map mkConfigAssertion plugins)
) enabledInstances);
) 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);
in
@ -127,28 +153,32 @@ let
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
[]
if cfg == { } then
[ ]
else
(if dir == null then
(
if dir == null then
throw "plugin ${p.name} provides settings but no stateDirs are defined"
else [
else
[
{
name = toRelative (resolvePath ("~/" + dir + "/config.json"));
value = { text = builtins.toJSON cfg; };
value = {
text = builtins.toJSON cfg;
};
}
]);
]
);
in
lib.flatten (map mkEntries plugins);
entries = lib.flatten (lib.mapAttrsToList entryFor enabledInstances);
@ -157,24 +187,25 @@ let
pluginSkillAssertions =
let
skillTargets =
lib.flatten (lib.concatLists (lib.mapAttrsToList (instName: inst:
skillTargets = lib.flatten (
lib.concatLists (
lib.mapAttrsToList (
instName: inst:
let
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
plugins = resolvedPluginsByInstance.${instName} or [];
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;
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);
in {
in
{
inherit
resolvedPluginsByInstance
pluginPackagesFor
@ -212,5 +244,6 @@ in {
pluginSkillsFiles
pluginConfigFiles
pluginSkillAssertions
pluginGuards;
pluginGuards
;
}

View File

@ -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;
};

View File

@ -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 { })

View File

@ -1,6 +1,7 @@
{ lib
, stdenvNoCC
, fetchzip
{
lib,
stdenvNoCC,
fetchzip,
}:
stdenvNoCC.mkDerivation {

View File

@ -1,8 +1,9 @@
{ lib
, buildEnv
, openclaw-gateway
, openclaw-app ? null
, extendedTools ? []
{
lib,
buildEnv,
openclaw-gateway,
openclaw-app ? null,
extendedTools ? [ ],
}:
let

View File

@ -8,19 +8,28 @@
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";
@ -46,7 +55,8 @@
};
};
};
})
}
)
];
};
};

View File

@ -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;
}

View File

@ -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;
};
};
};
};