Skip to content

NixOS Integration

This guide covers the nixosModules.default NixOS module: enabling the dsh CLI, declaring bundles and profiles, and running the web profile as a system service. For bundle and preset details, see Bundles and Presets. For advanced package overrides, secret injection, and reverse proxy setup, see Advanced Usage.

{
inputs.deepseek-harness.url = "github:moraxyc/deepseek-harness.nix";
}

If this repository is declared under another input name, such as dsh, replace inputs.deepseek-harness in the examples with that name.

Import nixosModules.default in a NixOS configuration:

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

The module:

  • sets nixpkgs.overlays so the pkgs.dsh.* scope is available;
  • adds a composed dsh package to environment.systemPackages;
  • exposes the programs.dsh and services.dsh option groups.

Set programs.dsh.home to export an explicit DSH_HOME through the system environment:

programs.dsh.home = "/var/lib/dsh-cli";

When unset, dsh keeps its per-user $HOME/.dsh default.

programs.dsh.patch accepts a structured list of Cordis patch entries and manages it as $DSH_HOME/cordis.patch.yml:

programs.dsh.patch = [
{
id = "agent-default-model";
config = {
provider = "deepseek-official";
model = "deepseek-v4-flash";
};
}
];

This layer applies after each profile’s own patch and therefore affects every profile. Module-composed CLI and service packages restore the declared file before launch. The default null leaves the file unmanaged.

Profiles are declared as attributes of programs.dsh.profiles. A profile named tui is materialized as $DSH_HOME/profiles/nix-tui, where $DSH_HOME defaults to ~/.dsh.

programs.dsh.package defaults to pkgs.dsh.dsh and can be overridden when you need a different composition or package source.

Each profile supports:

  • bundles: a list of bundle packages, e.g. pkgs.dsh.bundles.tui;
  • agentPreset: an ID from programs.dsh.agentPresets; the referenced shipped preset is copied to $DSH_HOME/.agent-presets;
  • patch: a list of Cordis patch operations or raw YAML applied after all bundle layers as cordis.patch.yml;
  • mode: managed (default) or mutable;
  • read-only rawName and materializedName.

Example:

programs.dsh.profiles = {
tui = {
bundles = [
pkgs.dsh.bundles.tui
pkgs.dsh.bundles.web-ui
];
mode = "managed";
};
};

Declare an Agent Preset under programs.dsh.agentPresets and reference it from the profile. enableTools removes disabled only from the listed preset rows; it does not install a provider bundle. See Advanced Usage for a complete subagent example.

Use the materialized name for defaults instead of hardcoding the nix- prefix:

{ config, inputs, pkgs, ... }:
{
imports = [ inputs.deepseek-harness.nixosModules.default ];
programs.dsh = {
enable = true;
profiles.tui.bundles = [ pkgs.dsh.bundles.tui ];
defaultProfile = config.programs.dsh.profiles.tui.materializedName;
};
}
  • managed: every dsh invocation re-synchronizes the profile from the configuration, so local changes to managed package.json and cordis.patch.yml are restored.
  • mutable: Nix seeds the profile only when the directory does not exist; afterwards the user manages it with dsh plugin and Nix leaves it alone.

Neither mode takes over an existing unmarked directory with the same name.

services.dsh runs the web profile as a NixOS systemd unit. It binds only to loopback by default and does not open the firewall.

services.dsh = {
enable = true;
listenAddress = "127.0.0.1";
port = 3080;
trustedHosts = [ "dsh.example.com" ];
};

By default the unit serves nix-web. If a custom programs.dsh.profiles.web profile is declared, the service composes it from programs.dsh.package and those profiles; otherwise it falls back to the web preset.

Key service options:

  • profile: materialized profile served by the unit, default nix-web;
  • listenAddress, port, and trustedHosts: binding and browser-trust settings;
  • extraArguments: additional arguments appended to the dsh command;
  • user and group: fixed service account; leave both unset for DynamicUser;
  • dataDir, homeDirectory, and workspace: state and working paths;
  • environment, environmentFile, and credentials: runtime environment and secret sources;
  • openFirewall: whether to open port, default false;
  • autoStart: whether to start with multi-user.target, default true.

Enable the optional systemd isolation profile when the service needs a stronger sandbox. It keeps networking available for the web server, while adding device, kernel, namespace, capability, and SUID/SGID restrictions. Additional writable paths and bind mounts must be declared explicitly:

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" ];
};

rootDirectory is optional. When set, the directory is the service root and the executable’s runtime must be made available below it. Use bindReadOnlyPaths for the required Nix store paths, certificates, DNS files, or other read-only inputs. Both bind options use systemd’s source:destination syntax. The service’s homeDirectory and workspace remain writable in isolation mode; readWritePaths adds more paths.

The unit is named dsh-web:

Terminal window
sudo systemctl start dsh-web
sudo systemctl restart dsh-web
systemctl status dsh-web
journalctl -u dsh-web -f

The service defaults to systemd DynamicUser and stores state under /var/lib/dsh. For reverse proxies, secret files, credentials, fixed user/group mode, and the complete service option list, see Advanced Usage.

  • If attribute 'dsh' missing appears while evaluating a NixOS module, check that nixosModules.default is imported and that no other module replaces pkgs with a fixed package set that predates the dsh overlay.
  • If your flake passes pkgs through specialArgs to nixosSystem, NixOS ignores nixpkgs.overlays. Use a pkgs set that already includes the dsh overlay. When reusing an existing pkgs set, import inputs.nixpkgs.nixosModules.readOnlyPkgs and set nixpkgs.pkgs to that pkgs set.
  • If dsh starts but does not use the expected profile, check that programs.dsh.defaultProfile uses the materialized name (nix-tui), not the raw profile key (tui).