feat: add project-composable DeepSeek Harness microVMs

Share the live project cwd, DSH home and skills read-write while running guest root behind rootless QEMU and Bubblewrap. Reuse project toolchains, expose configurable SSH-forwarded web access, and launch the latest official DSH.

Include the project template, operating guide, offline boot and mount tests, and shell checks.
This commit is contained in:
OpenAI Coding Assistant
2026-09-06 12:45:36 -05:00
parent 221e0b33cc
commit 35ca88d517
14 changed files with 1219 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Offline test of the real microvm.nix runner, mounts, SSH and host-side sandbox.
set -euo pipefail
launcher=$1
tmp=$(mktemp -d)
export HOME=$tmp/home DSH_HOME=$tmp/dsh DSH_AGENTS_HOME=$tmp/agents XDG_STATE_HOME=$tmp/state
mkdir -p "$HOME" "$tmp/project" "$DSH_HOME/skills" "$DSH_AGENTS_HOME/skills"
chmod 700 "$DSH_HOME"
printf 'not shared\n' > "$HOME/host-only-secret"
ln -s "$HOME/host-only-secret" "$tmp/project/escape"
cd "$tmp/project"
"$launcher" run > "$tmp/launcher.log" 2>&1 &
pid=$!
cleanup() {
status=$?
if (( status )); then
grep -h . "$tmp/launcher.log" "$XDG_STATE_HOME"/agent-vm/*/console.log | tail -80 || true
fi
"$launcher" stop >/dev/null 2>&1 || true
kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
# Never delete real project/config data; everything here is a test fixture.
rm -rf "$tmp"
return "$status"
}
trap cleanup EXIT
ready=false
for ((i=0; i<120; i++)); do
if "$launcher" ssh true 2>/dev/null; then ready=true; break; fi
if ! kill -0 "$pid" 2>/dev/null; then break; fi
sleep 2
done
if ! $ready; then
grep -h . "$tmp/launcher.log" "$XDG_STATE_HOME"/agent-vm/*/console.log || true
exit 1
fi
[[ $("$launcher" ssh 'id -u') == 0 ]]
[[ $("$launcher" ssh pwd) == "$tmp/project" ]]
[[ $("$launcher" ssh nproc) == 4 ]]
[[ $("$launcher" ssh 'printenv AGENT_PROJECT_TEST') == shared ]]
[[ $("$launcher" ssh hello) == 'Hello, world!' ]]
"$launcher" ssh 'test ! -e /workspace/escape; test ! -e /run/host; test -d /nix/.rw-store'
"$launcher" ssh 'printf edited > /workspace/changed; printf config > /root/.dsh/config-test; printf creds > /root/.dsh/credentials-test; printf skill > /root/.dsh/skills/test.md; printf shared > /root/.agents/skills/test.md'
[[ $(< changed) == edited && $(< "$DSH_HOME/config-test") == config ]]
[[ $(< "$DSH_HOME/credentials-test") == creds && $(< "$DSH_HOME/skills/test.md") == skill ]]
[[ $(< "$DSH_AGENTS_HOME/skills/test.md") == shared ]]
[[ $(stat -c %u changed) == "$(id -u)" ]]
"$launcher" ssh 'command -v rg python3 git; findmnt /workspace; findmnt /root/.dsh'
# A second start must fail without disrupting the existing VM.
if "$launcher" run >/dev/null 2>&1; then echo 'Duplicate launch succeeded' >&2; exit 1; fi
"$launcher" stop
wait "$pid"
trap - EXIT
rm -rf "$tmp"
echo 'PASS: microVM boot, root SSH, shared toolchain, RW cwd/config/creds/skills, host ownership, symlink isolation, duplicate lock, shutdown'
+65
View File
@@ -0,0 +1,65 @@
{
"nodes": {
"microvm": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"spectrum": "spectrum"
},
"locked": {
"lastModified": 1788636433,
"narHash": "sha256-iCLUJO5V2ZlEAlYxlkCZ5YLkyQrpkyXOMUTkPXjsdPk=",
"owner": "microvm-nix",
"repo": "microvm.nix",
"rev": "804cbac7a462aa0fa8bb60c3d2fc4ead0a62060f",
"type": "github"
},
"original": {
"owner": "microvm-nix",
"repo": "microvm.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1788614874,
"narHash": "sha256-7QYjT2vHLuX9Z1pdxHXDKCbh1CR3D/2rywB9Tx0MPRg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c043004d1c6985732bcc1cbc5a9c9aecbbb4e0f0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"microvm": "microvm",
"nixpkgs": "nixpkgs"
}
},
"spectrum": {
"flake": false,
"locked": {
"lastModified": 1785761586,
"narHash": "sha256-MWOMVqJJERwjQGgRIv8d6rlEBqbLM3VZWxHkNKIIZNw=",
"ref": "refs/heads/main",
"rev": "a7762d6f54b40560dd5255ce902e6e6a5d980fe9",
"revCount": 1416,
"type": "git",
"url": "https://spectrum-os.org/git/spectrum"
},
"original": {
"type": "git",
"url": "https://spectrum-os.org/git/spectrum"
}
}
},
"root": "root",
"version": 7
}
+39
View File
@@ -0,0 +1,39 @@
{
description = "Project-composable, rootless microVMs for a DeepSeek coding agent";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
microvm = {
url = "github:microvm-nix/microvm.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
inputs@{
self,
nixpkgs,
microvm,
...
}:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
example = self.lib.mkAgentVM {
inherit system;
project = {
packages = [ pkgs.hello ];
env.AGENT_PROJECT_TEST = "shared";
};
};
in
{
lib.mkAgentVM = import ./lib.nix { inherit nixpkgs microvm; };
nixosModules.agent = ./module.nix;
nixosConfigurations.agent = example.nixos;
packages.${system}.default = example.package;
apps.${system}.default = example.app;
formatter.${system} = pkgs.nixfmt;
checks.${system} = import ./tests.nix { inherit inputs pkgs example; };
};
}
+121
View File
@@ -0,0 +1,121 @@
# Included by writeShellApplication: bash and PATH are supplied by Nix.
set -euo pipefail
if [[ ${1:-} == --help ]]; then
echo 'Usage: nix run .#agent -- [run | ssh [command ...] | url | stop]'
echo 'Workspace = cwd. RW config/credentials/skills = DSH_HOME (default ~/.dsh)'
echo 'Also shares DSH_AGENTS_HOME/skills (default ~/.agents/skills). RAM/CPU/network: flake.'
exit 0
fi
[[ $EUID != 0 ]] || { echo 'Run as your normal host user, not sudo/root.' >&2; exit 1; }
umask 077
project=$(pwd -P)
dsh=$(realpath -m "${DSH_HOME:-$HOME/.dsh}")
skills=$(realpath -m "${DSH_AGENTS_HOME:-$HOME/.agents}/skills")
state=$(realpath -m "${XDG_STATE_HOME:-$HOME/.local/state}/agent-vm/$(printf %s "$project" | sha256sum | cut -c1-16)")
for path in "$project" "$dsh" "$skills"; do
case "$path" in /|/home|/etc|/nix|/nix/*|/proc|/proc/*|/sys|/sys/*|/dev|/dev/*|/run|/run/*|"$HOME"|*$'\n'*) echo "Refusing broad/system share: $path" >&2; exit 1;; esac
[[ $state != "$path" && $state != "$path/"* ]] || { echo 'State must be outside shared directories.' >&2; exit 1; }
done
# Prevent a broad workspace/config mount from accidentally including other mounts.
disjoint() {
[[ $1 != "$2" && $1 != "$2/"* && $2 != "$1/"* ]] || { echo 'Writable shares must not overlap.' >&2; exit 1; }
}
disjoint "$project" "$dsh"; disjoint "$project" "$skills"; disjoint "$dsh" "$skills"
ssh_cmd=(ssh -F /dev/null -i "$state/client-key" -p "$AGENT_SSH_PORT"
-o IdentitiesOnly=yes -o IdentityAgent=none -o ForwardAgent=no -o BatchMode=yes
-o StrictHostKeyChecking=yes -o HostKeyAlias=agent-vm -o ConnectTimeout=3
-o "UserKnownHostsFile=$state/known_hosts" -o GlobalKnownHostsFile=/dev/null)
remote="root@$AGENT_SSH_HOST"
url() {
local found address=$AGENT_WEB_BIND
[[ $address != 0.0.0.0 ]] || address=127.0.0.1
found=$("${ssh_cmd[@]}" "$remote" 'journalctl -u agent -b -o cat --no-pager' |
grep -oE 'http://127\.0\.0\.1:3080/\?token=[a-zA-Z0-9_%.-]+' | tail -1) || return 1
[[ -n $found ]] || return 1
printf '%s\n' "${found/http:\/\/127.0.0.1:3080/http:\/\/$address:$AGENT_WEB_PORT}"
}
# Expand cwd inside the guest, not on the host.
# shellcheck disable=SC2016
case ${1:-run} in
ssh) shift; if (( $# )); then exec "${ssh_cmd[@]}" "$remote" 'cd -- "$(cat /run/agent-vm/workdir)" || exit; '"$*"; else exec "${ssh_cmd[@]}" -t "$remote" 'cd -- "$(cat /run/agent-vm/workdir)" || exit; exec bash -l'; fi;;
url) url || { echo "DSH not ready; inspect: nix run .#agent -- ssh 'journalctl -u agent -b'" >&2; exit 1; }; exit;;
stop) cd "$state"; exec "$AGENT_RUNNER/microvm-shutdown";;
run) [[ $# -le 1 ]] || { echo 'Unexpected run arguments; use --help.' >&2; exit 1; };;
*) echo 'Unknown command; use --help.' >&2; exit 1;;
esac
[[ -r /dev/kvm && -w /dev/kvm ]] || { echo 'Need read/write access to /dev/kvm.' >&2; exit 1; }
mkdir -p "$state" "$dsh" "$skills"
for dir in "$state" "$dsh"; do
[[ $(stat -c %u "$dir") == "$(id -u)" && $(stat -c %a "$dir") == 700 ]] || {
echo "Make this directory private and user-owned first: $dir (chmod 700)" >&2; exit 1;
}
done
exec 9>"$state/run.lock"
flock -n 9 || { echo 'This project VM is already running.' >&2; exit 1; }
for key in client-key ssh-host-key; do
[[ -f $state/$key ]] || ssh-keygen -q -t ed25519 -N '' -C agent-vm -f "$state/$key"
done
printf '%s\n' "$project" > "$state/workdir"
cp "$state/client-key.pub" "$state/ssh-authorized-key"
printf 'agent-vm %s\n' "$(cut -d' ' -f1,2 "$state/ssh-host-key.pub")" > "$state/known_hosts"
printf 'RW workspace: %s -> /workspace\nRW DSH home: %s\nRW shared skills: %s\nConsole log: %s/console.log\n' "$project" "$dsh" "$skills" "$state"
if [[ $AGENT_WEB_BIND != 127.0.0.1 ]]; then
echo 'WARNING: off-host Web access is plaintext HTTP. Use a VPN/TLS; never expose directly to the Internet.' >&2
fi
vm_pid=''
tunnel_pid=''
cleanup() {
trap - EXIT INT TERM
if [[ -n $vm_pid ]] && kill -0 "$vm_pid" 2>/dev/null; then
(cd "$state"; timeout 30 "$AGENT_RUNNER/microvm-shutdown") >/dev/null 2>&1 || true
kill "$vm_pid" 2>/dev/null || true
wait "$vm_pid" 2>/dev/null || true
fi
if [[ -n $tunnel_pid ]]; then kill "$tunnel_pid" 2>/dev/null || true; wait "$tunnel_pid" 2>/dev/null || true; fi
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
# Host-side defense in depth around QEMU. No host home/session sockets or other
# processes; only the three explicit shares and private VM control state are RW.
# Network is intentionally inherited for API access (not an egress firewall).
devices=()
[[ $AGENT_NETWORK != tap ]] || devices=(--dev-bind /dev/net/tun /dev/net/tun)
bwrap "${devices[@]}" --die-with-parent --new-session --unshare-user --unshare-pid --unshare-ipc \
--unshare-uts --unshare-cgroup-try --cap-drop ALL --clearenv \
--setenv HOME /tmp --setenv PATH /no-host-path --setenv LANG C.UTF-8 \
--ro-bind /nix/store /nix/store --proc /proc --dev /dev --dev-bind /dev/kvm /dev/kvm \
--tmpfs /tmp --bind "$state" /state --bind "$project" /workspace \
--bind "$dsh" /dsh-home --bind "$skills" /skills \
--ro-bind-try /etc/resolv.conf /etc/resolv.conf --ro-bind-try /etc/hosts /etc/hosts \
--chdir /state "$AGENT_RUNNER/microvm-run" >"$state/console.log" 2>&1 &
vm_pid=$!
ready=false
for ((i=0; i<90; i++)); do
kill -0 "$vm_pid" 2>/dev/null || { echo "VM exited; see $state/console.log" >&2; exit 1; }
if "${ssh_cmd[@]}" "$remote" true 2>/dev/null; then ready=true; break; fi
sleep 1
done
$ready || { echo "SSH boot timeout; see $state/console.log" >&2; exit 1; }
# DSH deliberately refuses --host 0.0.0.0. Keep its own authenticated browser
# endpoint on guest loopback and publish an SSH forward on the chosen host IP.
"${ssh_cmd[@]}" -N -g -o ExitOnForwardFailure=yes -o ServerAliveInterval=10 \
-o ServerAliveCountMax=3 -L "$AGENT_WEB_BIND:$AGENT_WEB_PORT:127.0.0.1:3080" \
"$remote" >>"$state/console.log" 2>&1 &
tunnel_pid=$!
echo "Booted. DSH resolves npm @latest on startup; first launch may take a few minutes."
echo 'Use another terminal: nix run .#agent -- url (or: ssh / stop)'
printed=false
while kill -0 "$vm_pid" 2>/dev/null; do
# Normal guest poweroff can close SSH slightly before QEMU exits.
if ! kill -0 "$tunnel_pid" 2>/dev/null; then
timeout 30 tail --pid="$vm_pid" -f /dev/null || true
if kill -0 "$vm_pid" 2>/dev/null; then echo "Web tunnel exited; see $state/console.log" >&2; exit 1; fi
break
fi
if ! $printed; then
if login_url=$(url 2>/dev/null); then printf 'Private login URL: %s\n' "$login_url"; printed=true; fi
fi
sleep 2
done
wait "$vm_pid"
+54
View File
@@ -0,0 +1,54 @@
{ nixpkgs, microvm }:
{
system,
project,
modules ? [ ],
}:
let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) lib;
nixos = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
microvm.nixosModules.microvm
./module.nix
{
agentVM.packages = project.packages or [ ];
environment.variables = project.env or { };
}
]
++ modules;
};
net = nixos.config.agentVM.network;
package = pkgs.writeShellApplication {
name = "agent-vm";
runtimeInputs = with pkgs; [
coreutils
util-linux
openssh
bubblewrap
gnugrep
gnused
];
runtimeEnv = {
AGENT_RUNNER = "${nixos.config.microvm.declaredRunner}/bin";
AGENT_NETWORK = net.mode;
AGENT_WEB_BIND = net.hostAddress;
AGENT_WEB_PORT = toString net.webPort;
AGENT_SSH_HOST = if net.mode == "user" then "127.0.0.1" else net.guestAddress;
AGENT_SSH_PORT = toString (if net.mode == "user" then net.sshPort else 22);
};
text = builtins.readFile ./launch.sh;
};
in
assert lib.assertMsg (
system == "x86_64-linux"
) "agent-vm currently supports x86_64-linux hosts/guests";
{
inherit nixos package;
app = {
type = "app";
program = lib.getExe package;
meta.description = "Run DSH with this project's toolchain and live workspace";
};
}
+376
View File
@@ -0,0 +1,376 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib) mkOption types;
cfg = config.agentVM;
net = cfg.network;
ipv4 = types.strMatching "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+";
dshNode = pkgs.writeShellScript "dsh-node" ''
# Cordis HMR requires this Node flag; npm's published dsh shebang omits it.
exec node --expose-internals "$(command -v dsh)" "$@"
'';
dshLatest = pkgs.writeShellApplication {
name = "dsh";
runtimeInputs = [
pkgs.nodejs
pkgs.pnpm
];
text = ''
export npm_config_cache=/var/cache/dsh/npm
# Explicitly rolling upstream, not a pretend-reproducible Nix derivation.
exec npm exec --yes --package=@deepseek-ai/dsh@latest -- ${dshNode} "$@"
'';
};
guestLaunch = pkgs.writeShellScript "dsh-project" ''
cd -- "$(cat /run/agent-vm/workdir)"
exec "$@"
'';
share = source: mountPoint: tag: {
inherit source mountPoint tag;
proto = "9p";
securityModel = "none"; # QEMU writes as its unprivileged host uid, not guest root.
readOnly = false;
};
in
{
options.agentVM = {
packages = mkOption {
type = types.listOf types.package;
default = [ ];
description = "The same project package list used by the development shell.";
};
package = mkOption {
type = types.package;
default = dshLatest;
description = "Official DSH launcher; resolves the npm latest tag inside the guest at launch.";
};
network = {
mode = mkOption {
type = types.enum [
"user"
"tap"
];
default = "user";
description = "Rootless QEMU NAT, or an administrator-prepared TAP interface.";
};
hostAddress = mkOption {
type = ipv4;
default = "127.0.0.1";
description = "Host IPv4 bind address for the SSH-forwarded Web UI.";
};
sshPort = mkOption {
type = types.port;
default = 2222;
description = "Host SSH port in user mode; SSH is always bound to host loopback.";
};
webPort = mkOption {
type = types.port;
default = 3080;
description = "Host Web UI port; the guest DSH listener stays on 127.0.0.1:3080.";
};
trustedHosts = mkOption {
type = types.listOf (types.strMatching "[a-zA-Z0-9.:-]+");
default = [ ];
description = "Additional exact browser authorities for DSH's Host/Origin protection. Required for wildcard publication.";
};
tapName = mkOption {
type = types.strMatching "[a-zA-Z0-9_-]{1,15}";
default = "agent0";
description = "Pre-created host TAP interface, not a physical NIC.";
};
mac = mkOption {
type = types.strMatching "[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}";
default = "02:00:00:00:00:01";
description = "Unique locally administered guest MAC; change for each TAP guest.";
};
guestAddress = mkOption {
type = types.nullOr ipv4;
default = null;
description = "Static guest IPv4 address in TAP mode.";
};
prefixLength = mkOption {
type = types.ints.between 1 32;
default = 24;
description = "Guest IPv4 prefix length in TAP mode.";
};
gateway = mkOption {
type = types.nullOr ipv4;
default = null;
description = "Guest default router in TAP mode; routing/NAT is configured separately.";
};
dns = mkOption {
type = types.listOf types.str;
default = [ ];
description = "DNS servers in TAP mode. User mode uses QEMU DHCP/DNS.";
};
};
};
config = {
assertions = [
{
assertion =
net.mode != "tap" || (net.guestAddress != null && net.gateway != null && net.dns != [ ]);
message = "agentVM TAP mode requires network.guestAddress, gateway and dns.";
}
{
assertion = net.webPort >= 1024 && net.sshPort >= 1024 && net.webPort != net.sshPort;
message = "Rootless Web/SSH listeners need distinct unprivileged ports (>=1024).";
}
{
assertion = net.hostAddress != "0.0.0.0" || net.trustedHosts != [ ];
message = "When publishing on 0.0.0.0, list the actual browser IP:port/hostname:port in network.trustedHosts.";
}
];
networking.hostName = lib.mkDefault "project-agent";
system.stateVersion = "26.05";
microvm = {
hypervisor = "qemu";
mem = lib.mkDefault 4096;
vcpu = lib.mkDefault 4;
socket = "control.sock";
storeOnDisk = true;
# Ephemeral guest-only Nix writes; never share the host store or daemon.
writableStoreOverlay = "/nix/.rw-store";
volumes = [
{
image = "cache.img";
mountPoint = "/var/cache/dsh";
size = 4096;
}
];
# Paths are in the launcher's restricted mount namespace, not Nix paths.
# No credential/project contents enter the Nix store.
shares = [
(share "/workspace" "/workspace" "project")
(share "/dsh-home" "/root/.dsh" "dsh-home")
(share "/skills" "/root/.agents/skills" "agent-skills")
];
interfaces = [
{
type = net.mode;
id = if net.mode == "user" then "agentnet" else net.tapName;
inherit (net) mac;
}
];
forwardPorts = lib.optionals (net.mode == "user") [
{
from = "host";
host.address = "127.0.0.1";
host.port = net.sshPort;
guest.port = 22;
}
];
# Firmware credentials carry only dedicated VM SSH keys, not DSH secrets.
# Relative runtime filenames avoid embedding user paths in derivations.
qemu.extraArgs =
lib.concatMap
(name: [
"-fw_cfg"
"name=opt/io.systemd.credentials/${name},file=${name}"
])
[
"ssh-authorized-key"
"ssh-host-key"
"workdir"
];
};
fileSystems."/workspace".options = [
"nodev"
"nosuid"
"cache=none"
];
fileSystems."/root/.dsh".options = [
"nodev"
"nosuid"
"cache=none"
];
fileSystems."/root/.agents/skills".options = [
"nodev"
"nosuid"
"cache=none"
];
networking.useDHCP = false;
systemd.network.enable = true;
systemd.network.networks."20-agent" = {
matchConfig.MACAddress = net.mac;
networkConfig =
if net.mode == "user" then
{ DHCP = "ipv4"; }
else
{
Address = [ "${net.guestAddress}/${toString net.prefixLength}" ];
Gateway = net.gateway;
DNS = net.dns;
};
};
networking.firewall.allowedTCPPorts = [ 22 ]; # Web stays on guest loopback.
nix.settings.experimental-features = [
"nix-command"
"flakes"
];
nix.settings.auto-optimise-store = false;
nix.channel.enable = false;
users.users.root.hashedPassword = "!";
services.openssh = {
enable = true;
hostKeys = [
{
path = "/run/agent-vm/ssh-host-key";
type = "ed25519";
}
];
authorizedKeysFiles = lib.mkForce [ "/run/agent-vm/ssh-authorized-key" ];
settings = {
PermitRootLogin = "prohibit-password";
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
AllowAgentForwarding = false;
X11Forwarding = false;
AllowTcpForwarding = "local";
};
};
systemd.services.agent-vm-credentials = {
before = [
"sshd.service"
"sshd-keygen.service"
"agent.service"
];
requiredBy = [
"sshd.service"
"sshd-keygen.service"
"agent.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "agent-vm";
RuntimeDirectoryMode = "0700";
ImportCredential = [
"ssh-authorized-key"
"ssh-host-key"
"workdir"
];
};
script = ''
for name in ssh-authorized-key ssh-host-key workdir; do
install -m 600 "$CREDENTIALS_DIRECTORY/$name" "/run/agent-vm/$name"
done
'';
};
environment.variables = {
DSH_HOME = "/root/.dsh";
DSH_AGENTS_HOME = "/root/.agents";
DSH_TELEMETRY_DISABLED = "1";
};
programs.git.config.safe.directory = "/workspace"; # 9p files retain host ownership.
programs.nix-ld.enable = true; # Upstream npm native executables, guest only.
environment.systemPackages = [
cfg.package
]
++ cfg.packages
++ (with pkgs; [
bashInteractive
coreutils
findutils
gnugrep
gnused
gawk
diffutils
git
git-lfs
openssh
ripgrep
fd
jq
yq-go
tree
file
less
python3
nodejs
pnpm
curl
wget
cacert
unzip
zip
gnutar
gzip
xz
zstd
procps
util-linux
which
patch
gnumake
pkg-config
shellcheck
bubblewrap
]);
systemd.services.agent = {
description = "Official DeepSeek Harness (root inside the guest)";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
unitConfig.RequiresMountsFor = [
"/workspace"
"/root/.dsh"
"/root/.agents/skills"
"/var/cache/dsh"
];
path = [ "/run/current-system/sw" ];
environment = config.environment.variables // {
HOME = "/root";
};
# Preserve the real cwd path in DSH's workspace/session identity. Mapping
# every project to /workspace alone would conflate their shared sessions.
preStart = ''
workdir=$(cat /run/agent-vm/workdir)
mkdir -p -- "$workdir"
mountpoint -q -- "$workdir" || mount --bind /workspace "$workdir"
git config --global --replace-all safe.directory "$workdir"
'';
serviceConfig = {
User = "root";
WorkingDirectory = "/workspace";
ExecStart = lib.escapeShellArgs (
[
"${guestLaunch}"
"${cfg.package}/bin/dsh"
"web"
"--no-open"
"--host"
"127.0.0.1"
"--port"
"3080"
]
++
lib.concatMap
(host: [
"--trusted-host"
host
])
(
net.trustedHosts
++ lib.optional (
!builtins.elem net.hostAddress [
"127.0.0.1"
"0.0.0.0"
]
) "${net.hostAddress}:${toString net.webPort}"
)
);
Restart = "on-failure";
RestartSec = 3;
UMask = "0077";
};
};
};
}
+92
View File
@@ -0,0 +1,92 @@
{
inputs,
pkgs,
example,
}:
let
c = example.nixos.config;
testVM = inputs.self.lib.mkAgentVM {
system = pkgs.stdenv.hostPlatform.system;
project = {
packages = [ pkgs.hello ];
env.AGENT_PROJECT_TEST = "shared";
};
modules = [
{
# Offline infrastructure test. A real DSH startup is tested separately;
# @latest needs the network and is intentionally outside Nix reproducibility.
agentVM.package = pkgs.writeShellScriptBin "dsh" ''
echo 'http://127.0.0.1:3080/?token=offline-test'
exec ${pkgs.coreutils}/bin/sleep infinity
'';
}
];
};
tap = inputs.self.lib.mkAgentVM {
system = pkgs.stdenv.hostPlatform.system;
project.packages = [ ];
modules = [
{
microvm.mem = 8192;
microvm.vcpu = 6;
agentVM.network = {
mode = "tap";
tapName = "agent-test";
guestAddress = "192.168.77.2";
gateway = "192.168.77.1";
dns = [ "192.168.77.1" ];
};
}
];
};
in
{
config =
assert c.microvm.mem == 4096;
assert c.microvm.vcpu == 4;
assert builtins.length c.microvm.shares == 3;
assert builtins.all (s: !s.readOnly && s.securityModel == "none") c.microvm.shares;
assert c.microvm.storeOnDisk;
assert c.systemd.services.agent.serviceConfig.User == "root";
assert c.systemd.services.agent.serviceConfig.WorkingDirectory == "/workspace";
assert c.services.openssh.settings.PasswordAuthentication == false;
assert c.services.openssh.settings.AllowAgentForwarding == false;
assert builtins.length c.microvm.forwardPorts == 1;
assert (builtins.head c.microvm.forwardPorts).host.address == "127.0.0.1";
assert builtins.elem pkgs.hello c.environment.systemPackages;
assert c.environment.variables.AGENT_PROJECT_TEST == "shared";
assert tap.nixos.config.microvm.mem == 8192;
assert tap.nixos.config.microvm.vcpu == 6;
assert tap.nixos.config.microvm.forwardPorts == [ ];
assert (builtins.head tap.nixos.config.microvm.interfaces).id == "agent-test";
pkgs.runCommand "agent-vm-config-check" { } ''touch "$out"'';
shell =
pkgs.runCommand "agent-vm-shell-check"
{
nativeBuildInputs = [
pkgs.shellcheck
pkgs.bash
];
}
''
shellcheck -s bash ${./launch.sh} ${./boot-test.sh}
bash -n ${./launch.sh}
touch "$out"
'';
boot =
pkgs.runCommand "agent-vm-boot-check"
{
requiredSystemFeatures = [ "kvm" ];
nativeBuildInputs = [
pkgs.bash
pkgs.coreutils
pkgs.gnugrep
];
}
''
bash ${./boot-test.sh} ${testVM.package}/bin/agent-vm
touch "$out"
'';
}