跳转到内容

高级用法

本文覆盖自定义 profile、覆盖方式和 Overlay。模块接入见 NixOS 集成Home Manager 集成; 快速开始与常用命令见 README

导入 nixosModules.default 并启用 programs.dsh

{
imports = [ inputs.deepseek-harness.nixosModules.default ];
programs.dsh = {
enable = true;
profiles.tui.bundles = [ pkgs.dsh.bundles.tui ];
defaultProfile = "nix-tui";
};
}

名为 tui 的 profile 会生成到 $DSH_HOME/profiles/nix-tui (默认 ~/.dsh/profiles/nix-tui)。Nix 只同步由它管理的 profile;已有但没有 Nix 标记的同名目录不会被接管或覆盖。

defaultProfile 使用生成后的名称(nix-tui)。不要直接填原始 profile key(tui)。 没有显式传入 --profile 时,dsh 使用这个名称;显式传入时仍可选择任何可用 profile。

每个声明的 profile 都暴露只读的 rawNametui)和 materializedNamenix-tui)。在模块中,defaultProfile 可使用 config.programs.dsh.profiles.tui.materializedName,避免手写前缀。

每个 profile 支持 bundles 和一层 YAML patchpatch 会作为 cordis.patch.yml 在 bundle 层之后应用。

programs.dsh.patch 以结构化数据管理 Home 级 $DSH_HOME/cordis.patch.yml。该层在所选 profile 的 patch 之后应用,并由 所有 profile 共用。

启用 services.dsh 可以把 web preset 作为 systemd 单元运行。默认只监听 loopback,并且不打开防火墙。

{
imports = [ inputs.deepseek-harness.nixosModules.default ];
services.dsh = {
enable = true;
profile = "nix-web";
listenAddress = "127.0.0.1";
port = 3080;
trustedHosts = [ "dsh.example.com" ];
};
}

服务把 DSH_HOME 放在 /var/lib/dsh/home,把 /var/lib/dsh/workspace 作为工作目录。如果覆盖 dataDirhomeDirectoryworkspace,请确保服务用户可写这些路径。

除非同时设置 usergroup,单元默认使用 systemd DynamicUser。 Dynamic 模式把状态放在 /var/lib/dsh;固定用户模式会为配置的 dataDirhomeDirectoryworkspace 创建对应目录。

如果需要更严格的 systemd 沙箱,请显式启用隔离模式:

services.dsh.isolation = {
enable = true;
rootDirectory = "/var/lib/dsh/root";
readWritePaths = [ "/var/lib/dsh/cache" ];
bindPaths = [ "/srv/dsh-assets:/opt/dsh/assets" ];
bindReadOnlyPaths = [ "/nix/store:/nix/store" "/etc/resolv.conf:/etc/resolv.conf" ];
};

隔离模式保留网络能力,并增加设备、内核、命名空间、能力以及 SUID/SGID 限制。homeDirectoryworkspace 仍然可写;如需其他可写路径,请使用 readWritePaths。设置 rootDirectory 后,程序运行时依赖必须位于根目录中; 请使用 bindReadOnlyPaths 挂载只读运行时输入。两个 bind 选项都使用 systemd 的 source:destination 格式。

services.dsh 会从 programs.dsh(或 services.dsh.profiles)声明的 profile 组合服务包,自定义 web profile 会直接用于服务:

{
programs.dsh.profiles.web.patch = ''
# 针对 web profile 的 cordis patch 操作
'';
services.dsh.enable = true;
}

services.dsh.profile(默认 nix-web)出现在 programs.dsh.profiles 中时,单元会运行由 programs.dsh.package 与该组 profile 组合出的包; 否则回退到 web preset。services.dsh.profiles 支持与 programs.dsh.profiles 相同的 bundlespatchmode 选项;显式赋值会 替换从 programs.dsh 继承的 profile。

不要把密钥写进 Nix 配置,使用运行时密钥来源。

environmentFile

environmentFile 由 systemd 直接读取,与 LoadCredential 分开加载。

services.dsh.environmentFile = "/run/secrets/dsh.env";

文件使用 systemd EnvironmentFile 语法:

终端窗口
DEEPSEEK_API_KEY=...
DSH_PERMISSION_MODE=workspace-write

使用 sops-nix 时,把 environmentFile 指向渲染后的 secret:

imports = [
inputs.sops-nix.nixosModules.sops
inputs.deepseek-harness.nixosModules.default
];
sops.secrets."dsh-env" = { };
services.dsh.environmentFile = "/run/secrets/dsh-env";

使用 systemd credentials 时,用 credentials.<name> 指定 LoadCredential 来源。如果 credential 本身是环境文件,命名为 env,再把它 作为 EnvironmentFile 加载:

services.dsh = {
credentials.env = "/run/secrets/dsh.env";
environmentFile = "/run/credentials/dsh-web/env";
};

credential 会暴露在 /run/credentials/dsh-web/<name>

保持 listenAddress 为 loopback,把公网入口交给反向代理。Nginx:

services.nginx = {
enable = true;
virtualHosts."dsh.example.com" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3080";
proxyWebsockets = true;
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
'';
};
};
};
services.dsh.trustedHosts = [ "dsh.example.com" ];

Caddy:

services.caddy = {
enable = true;
virtualHosts."dsh.example.com".extraConfig = ''
reverse_proxy 127.0.0.1:3080
'';
};
services.dsh.trustedHosts = [ "dsh.example.com" ];

服务单元名为 dsh-web,默认随 multi-user.target 启动。设置 services.dsh.autoStart = false 后需要手动启动:

终端窗口
sudo systemctl start dsh-web
sudo systemctl stop dsh-web
sudo systemctl restart dsh-web
systemctl status dsh-web
journalctl -u dsh-web -f

homeModules.default 还提供 services.dsh,为每个用户生成 systemd user 单元,让非 NixOS 用户获得等价的服务体验:

{
imports = [ inputs.deepseek-harness.homeModules.default ];
services.dsh = {
enable = true;
port = 3080;
};
}

用户管理器中的单元名为 dsh-web.service,默认随 default.target 启动, 设置 services.dsh.autoStart = false 后需要手动启动。它默认使用 ~/.dsh 作为 DSH_HOME,与 CLI 共享已生成的 profile:

终端窗口
systemctl --user start dsh-web
systemctl --user status dsh-web
journalctl --user -u dsh-web -f

选项与 NixOS 服务一致,仅不含 usergroupopenFirewallprograms.dsh.profiles 的复用规则同样适用。

在 NixOS 之外,用 withProfiles 从包中生成 profile:

pkgs.dsh.dsh.withProfiles {
tui.bundles = [ pkgs.dsh.bundles.tui ];
}

每个 profile 的 bundles 既接受列表,也接受一个接收 bundle scope 的函数, 方便直接使用短名称:

pkgs.dsh.dsh.withProfiles {
tui.bundles = b: with b; [
tui
];
}

这会生成 nix-tui profile,并清除默认 profile。如果需要让它成为默认,再对 结果执行 override

(pkgs.dsh.dsh.withProfiles {
tui.bundles = [ pkgs.dsh.bundles.tui ];
}).override {
defaultProfile = "nix-tui";
}

withAgentPresets 可以给包添加 preset 定义。多次调用会按 ID 合并;同一个 ID 以后一次定义为准。它可以放在 withProfiles 前面或后面:

(
(pkgs.dsh.dsh.withAgentPresets {
web-subagents = {
source = "standard";
enableTools = [ "tool-subagent-codex" ];
};
}).withAgentPresets {
code-subagents = {
source = "ptc";
enableTools = [ "tool-subagent-claude-code" ];
};
}
).withProfiles {
web = {
bundles = [ pkgs.dsh.bundles.subagent-codex ];
agentPreset = "web-subagents";
};
}

Codex 和 Claude Code 以可选 Profile Bundle 提供。只需把允许该 profile 使用的 提供程序加入组合:

programs.dsh.profiles.web.bundles = with pkgs.dsh.bundles; [
web-app
subagent-codex
subagent-claude-code
];

安装提供程序 Bundle 只会把 Host provider 放进运行时。它不会启动 Codex 或 Claude Code,也不会自动开放对应工具。内置的 standardptc Agent Preset 默认禁用 tool-subagent-codextool-subagent-claude-code。可以同时声明 preset 和 profile:

programs.dsh = {
agentPresets.web-subagents = {
source = "standard";
enableTools = [ "tool-subagent-codex" ];
};
profiles.web = {
bundles = with pkgs.dsh.bundles; [
web-app
subagent-codex
];
agentPreset = "web-subagents";
};
};

构建时会把指定的 preset 复制到 $DSH_HOME/.agent-presets/web-subagents,只从 enableTools 列出的行删除 disabledmanaged profile 会在启动前同步副本;mutable profile 只生成一次。 安装 Bundle 不会开放工具;工具是否可用由 Agent Preset 单独决定。

这些 Bundle 使用 DeepSeek Harness 上游 workspace 固定的版本,不搜索 PATH。如需改用某个 Nixpkgs revision 或 overlay 单独打包的二进制,可以覆盖 Bundle input:

programs.dsh.profiles.web.bundles = with pkgs.dsh.bundles; [
(subagent-codex.override {
codexPackage = pkgs.codex;
})
(subagent-claude-code.override {
claudeCodePackage = pkgs.claude-code;
})
];

codexPackage 必须提供 bin/codexbin/codex-code-mode-hostclaudeCodePackage 必须把 bin/claude 声明为 main program。属性名和可用性取决于 所选 Nixpkgs revision 或 overlay。覆盖后,可执行文件版本由覆盖包决定,不再跟随 上游 workspace lock。

Claude Agent SDK 使用 MIT 许可证,其内置 Claude Code payload 为非自由软件。 选择 subagent-claude-code 时需显式允许该包:

{ lib, ... }:
{
nixpkgs.config.allowUnfreePredicate =
package: lib.getName package == "dsh-subagent-claude-code";
}

withBundles 追加 bundle。它既接受 bundle 包列表,也接受一个接收 bundle scope 的函数,方便直接使用短名称:

pkgs.dsh.dsh.withBundles [
pkgs.dsh.bundles.tui
pkgs.dsh.bundles.web-app
]
pkgs.dsh.dsh.withBundles (b: with b; [
tui
web-app
])

选中的 bundle 会追加到当前组合,也会追加到包里的每个 Nix 管理 profile, 因此生成的 profile 会随这次组合同步。所有 bundle 按列表顺序应用,后面的 bundle 会覆盖前面 bundle 的 Cordis 配置;基础层固定在最前面。

preset 通过 override 设置其他包输入。withAgentPresets 按 ID 合并 preset 定义,withBundles 用于添加 Bundle,withProfiles 会替换包里的 profiles,并清除 preset 的默认 profile。

把 overlay 加入 Nixpkgs 后,使用 pkgs.dsh scope:

{
nixpkgs.overlays = [ inputs.deepseek-harness.overlays.default ];
environment.systemPackages = [ pkgs.dsh.dsh ];
}

该 scope 包含 pkgs.dsh.bundles.*pkgs.dsh.presets.*pkgs.dsh.helpers.* 下的公开 helper,以及 pkgs.dsh.dsh-desktop 等包输出。 flake 的 packages 展开的是同一 scope,因此 nix build .#bundles.tuinix run .#presets.web 仍然可用。

对于本 flake 未收录的独立 bundle,使用 pkgs.dsh.helpers.buildBundle

{ pkgs, ... }:
let
customBundle = pkgs.dsh.helpers.buildBundle (finalAttrs: {
pname = "example-bundle";
version = "0.1.0";
src = pkgs.fetchFromGitHub {
owner = "example";
repo = "example-bundle";
tag = "v${finalAttrs.version}";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
npmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
npmBuildScript = "build";
linkKernelNodeModules = pkgs.dsh.dsh-kernel;
meta.description = "Example DSH bundle";
});
in
{
programs.dsh.profiles.tui.bundles = [ customBundle ];
}

该 helper 使用 buildNpmPackage 的标准 npmInstallHook:由 package.json.name 决定 package 目录,由 npm pack 文件清单决定运行时内容。 安装后的 manifest 必须声明 dsh.bundle.patch,对应文件也必须包含在 package 内容中。Builder 会校验结果,并生成组合阶段使用的 bundle manifest。只有非标准 package 布局才需要覆盖 installPhase。外部 pnpm monorepo 可使用 pkgs.dsh.helpers.buildBundle.fromPnpmWorkspace