feat: include only dsh-context in agent VM setup

Install through the upstream CLI before Web startup, preserve existing profile state and skip completed installs. Keep pnpm's SQLite store on the guest cache disk and map the caller to namespace root for 9p atomic saves, without host-root privileges.

Cover context-only installation, retries, idempotence and preservation with offline checks. Validate real VM boot, ports, Firefox and a live DSH/context launch using a disposable profile.
This commit is contained in:
OpenAI Coding Assistant
2026-09-06 15:08:10 -05:00
parent a58dd24efe
commit 15878fd3b7
7 changed files with 174 additions and 10 deletions
+11 -1
View File
@@ -47,14 +47,24 @@ fi
[[ $(< "$DSH_AGENTS_HOME/skills/test.md") == shared ]]
[[ $(stat -c %u changed) == "$(id -u)" ]]
"$launcher" ssh 'command -v rg python3 git playwright-cli; findmnt /workspace; findmnt /root/.dsh'
# pnpm's atomic saves preserve ownership; these chowns must work over 9p.
"$launcher" ssh 'chown 0:0 /root/.dsh/config-test; chown --reference=/root/.dsh/config-test /root/.dsh/credentials-test'
"$launcher" ssh 'systemctl start agent.service; test ! -e /tmp/.X11-unix/X0'
skill="$DSH_AGENTS_HOME/skills/playwright-firefox/SKILL.md"
grep -q '^name: playwright-firefox$' "$skill"
[[ ! -L $skill && -w $skill && $(stat -c %a "$skill") == 600 ]]
[[ $(stat -c %u "$skill") == "$(id -u)" ]]
printf '\nuser customization\n' >> "$skill"
manifest="$DSH_HOME/profiles/web/package.json"
[[ $(< "$DSH_HOME/plugin-calls") == 'plugin --profile web add dsh-context@latest' ]]
[[ -f $DSH_HOME/profiles/web/node_modules/dsh-context/package.json ]]
[[ ! -L $manifest && -w $manifest && $(stat -c %a "$manifest") == 600 ]]
[[ $(stat -c %u "$manifest") == "$(id -u)" ]]
cp "$manifest" "$tmp/installed.json"
"$launcher" ssh 'systemctl restart agent.service'
grep -q '^user customization$' "$skill"
[[ $(wc -l < "$DSH_HOME/plugin-calls") == 1 ]]
cmp "$manifest" "$tmp/installed.json"
if [[ -n $browser_test ]]; then
cp "$browser_test" ./playwright-test.sh
"$launcher" ssh 'bash ./playwright-test.sh'
@@ -65,4 +75,4 @@ if "$launcher" run >/dev/null 2>&1; then echo 'Duplicate launch succeeded' >&2;
wait "$pid"
trap - EXIT
rm -rf "$tmp"
echo 'PASS: microVM boot, root SSH, shared toolchain, RW cwd/config/creds/skills, writable non-clobbering skill seed, headless browser CLI, host ownership, symlink isolation, duplicate lock, shutdown'
echo 'PASS: microVM boot, root SSH, shared toolchain, RW cwd/config/creds/skills, writable non-clobbering skill seed, context-only plugin setup preserved on restart, headless browser CLI, host ownership, symlink isolation, duplicate lock, shutdown'
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Exercise the real setup script against an offline, strict DSH CLI fixture.
set -euo pipefail
setup=$1
dsh=$2
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
export HOME="$tmp/home" DSH_HOME="$tmp/dsh home"
mkdir -p "$HOME" "$DSH_HOME"
profile="$DSH_HOME/profiles/web"
manifest="$profile/package.json"
run() { bash "$setup" "$dsh"; }
calls() { wc -l < "$DSH_HOME/plugin-calls"; }
# Fresh home: upstream initializes Web, and only the requested plugin is added.
run
[[ $(< "$DSH_HOME/plugin-calls") == 'plugin --profile web add dsh-context@latest' ]]
jq -e '.dependencies | keys == ["dsh-context"]' "$manifest"
cp "$manifest" "$tmp/installed.json"
run
[[ $(calls) == 1 ]]
cmp "$manifest" "$tmp/installed.json"
# Existing home: preserve settings, credentials, patches, other profiles/plugins.
printf 'user settings\n' > "$DSH_HOME/settings.yaml"
printf 'fixture credentials, not real\n' > "$DSH_HOME/.credentials.yaml"
printf 'user patch\n' > "$profile/cordis.patch.yml"
mkdir -p "$DSH_HOME/profiles/headless"
printf '{"private":true}\n' > "$DSH_HOME/profiles/headless/package.json"
jq 'del(.dependencies["dsh-context"]) |
.dependencies["user-plugin"] = "1.2.3" |
.dsh.profile.bundles = ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "user-plugin"] |
.custom = {"keep":true}' "$manifest" > "$tmp/existing.json"
cp "$tmp/existing.json" "$manifest"
run
[[ $(calls) == 2 ]]
jq 'del(.dependencies["dsh-context"]) | .dsh.profile.bundles -= ["dsh-context"]' "$manifest" > "$tmp/preserved.json"
cmp "$tmp/existing.json" "$tmp/preserved.json"
[[ $(< "$DSH_HOME/settings.yaml") == 'user settings' ]]
[[ $(< "$DSH_HOME/.credentials.yaml") == 'fixture credentials, not real' ]]
[[ $(< "$profile/cordis.patch.yml") == 'user patch' ]]
[[ $(< "$DSH_HOME/profiles/headless/package.json") == '{"private":true}' ]]
# A selected version is not upgraded on restart, even when installation needs repair.
jq '.dependencies["dsh-context"] = "0.40.0"' "$manifest" > "$tmp/pinned.json"
cp "$tmp/pinned.json" "$manifest"
run
[[ $(calls) == 2 ]]
cmp "$manifest" "$tmp/pinned.json"
rm -rf "$profile/node_modules/dsh-context"
run
[[ $(calls) == 3 && $(tail -1 "$DSH_HOME/plugin-calls") == 'plugin --profile web add dsh-context@0.40.0' ]]
cmp "$manifest" "$tmp/pinned.json"
jq '.dsh.profile.bundles -= ["dsh-context"]' "$manifest" > "$tmp/unregistered.json"
cp "$tmp/unregistered.json" "$manifest"
run
[[ $(calls) == 4 ]]
cmp "$manifest" "$tmp/pinned.json"
# Partial failure must fail startup and be retried, not hidden behind a stamp.
rm -rf "$profile/node_modules/dsh-context"
touch "$DSH_HOME/fail-plugin-install"
if run; then echo 'Accepted a failed plugin install' >&2; exit 1; fi
[[ $(calls) == 5 && ! -e $profile/node_modules/dsh-context/package.json ]]
rm "$DSH_HOME/fail-plugin-install"
run
[[ $(calls) == 6 ]]
cmp "$manifest" "$tmp/pinned.json"
run
[[ $(calls) == 6 ]]
# Corrupt user data is an error, never permission to reset the profile.
printf 'not JSON\n' > "$manifest"
if run; then echo 'Accepted a malformed profile' >&2; exit 1; fi
[[ $(calls) == 6 && $(< "$manifest") == 'not JSON' ]]
echo 'PASS: context-only install, idempotence, profile preservation, selected version, incomplete install repair, failure/retry, malformed profile rejection'
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Required Web plugin, installed only inside the guest's live shared DSH home.
set -euo pipefail
profile="${DSH_HOME:?DSH_HOME must name the shared guest profile home}/profiles/web"
manifest="$profile/package.json"
spec=latest
if [[ -e $manifest ]]; then
# Fail on malformed JSON instead of replacing a user's profile. Keep an
# existing version/path spec when repairing an incomplete installation.
configured=$(jq -r '.dependencies["dsh-context"] // empty' "$manifest")
if [[ -n $configured ]]; then spec=$configured; fi
fi
installed() {
[[ -f $manifest && -f $profile/node_modules/dsh-context/package.json ]] &&
jq -e '.dependencies["dsh-context"] != null and
((.dsh.profile.bundles // []) | index("dsh-context") != null)' "$manifest" >/dev/null
}
if installed; then exit 0; fi
# Let upstream initialize/reconcile the profile; never generate its manifests,
# settings or credentials ourselves. No other plugin is added or updated here.
echo 'Installing dsh-context in the DSH web profile.' >&2
"$1" plugin --profile web add "dsh-context@$spec"
if ! installed; then
echo 'dsh-context installation did not produce an installed Web bundle.' >&2
exit 1
fi
+29
View File
@@ -0,0 +1,29 @@
# Offline CLI fixture only: no npm, network installs, credentials or model calls.
set -euo pipefail
profile="$DSH_HOME/profiles/web"
manifest="$profile/package.json"
if [[ ${1:-} == plugin ]]; then
[[ $# == 5 && $2 == --profile && $3 == web && $4 == add && $5 == dsh-context@* ]]
printf '%s\n' "$*" >> "$DSH_HOME/plugin-calls"
mkdir -p "$profile"
if [[ ! -e $manifest ]]; then
printf '%s\n' '{"dsh":{"profile":{"bundles":["@deepseek-ai/dsh-base","@deepseek-ai/dsh-web-app"]}}}' > "$manifest"
fi
jq --arg spec "${5#dsh-context@}" '.dependencies["dsh-context"] = $spec' "$manifest" > "$manifest.tmp"
mv "$manifest.tmp" "$manifest"
# Simulate a failed install after pnpm has already recorded the dependency.
[[ ! -e $DSH_HOME/fail-plugin-install ]] || exit 42
mkdir -p "$profile/node_modules/dsh-context"
printf '%s\n' '{"name":"dsh-context","version":"0.0.0"}' > "$profile/node_modules/dsh-context/package.json"
jq '.dsh.profile.bundles |= (. + ["dsh-context"] | unique)' "$manifest" > "$manifest.tmp"
mv "$manifest.tmp" "$manifest"
exit 0
fi
[[ ${1:-} == web ]]
# The service must finish plugin setup before starting the Web listener.
[[ -f $profile/node_modules/dsh-context/package.json ]]
jq -e '.dsh.profile.bundles | index("dsh-context") != null' "$manifest" >/dev/null
exec node -e '
require("node:http").createServer((req, res) => res.end("agent-vm-test"))
.listen(3080, "127.0.0.1", () => console.log("http://127.0.0.1:3080/?token=offline-test"));
'
+4 -2
View File
@@ -90,8 +90,10 @@ trap 'exit 143' TERM
# 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 \
# Map the caller to namespace uid/gid 0 so 9p ownership matches guest root.
# Host writes still belong to the caller; no host-root identity/capability is gained.
bwrap "${devices[@]}" --die-with-parent --new-session --unshare-user --uid 0 --gid 0 \
--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 \
+6
View File
@@ -22,6 +22,8 @@ let
];
text = ''
export npm_config_cache=/var/cache/dsh/npm
# pnpm's SQLite index needs a local filesystem, not the shared 9p mount.
export pnpm_config_store_dir=/var/cache/dsh/pnpm
# Explicitly rolling upstream, not a pretend-reproducible Nix derivation.
exec npm exec --yes --package=@deepseek-ai/dsh@latest -- ${dshNode} "$@"
'';
@@ -356,6 +358,8 @@ in
cp --update=none --no-preserve=mode \
${./skills/playwright-firefox/SKILL.md} \
/root/.agents/skills/playwright-firefox/SKILL.md
# Required plugin setup uses the mounted profile, never the host or store.
${pkgs.bash}/bin/bash ${./context.sh} ${cfg.package}/bin/dsh
'';
serviceConfig = {
User = "root";
@@ -387,6 +391,8 @@ in
) (map (port: "${net.hostAddress}:${toString port}") (lib.range net.webPort net.webPortEnd))
)
);
# First-time DSH/plugin downloads run in ExecStartPre, not at Nix build time.
TimeoutStartSec = "10min";
Restart = "on-failure";
RestartSec = 3;
UMask = "0077";
+22 -7
View File
@@ -5,6 +5,7 @@
}:
let
c = example.nixos.config;
fakeDsh = pkgs.writeShellScriptBin "dsh" (builtins.readFile ./fake-dsh.sh);
testVM = inputs.self.lib.mkAgentVM {
system = pkgs.stdenv.hostPlatform.system;
project = {
@@ -15,12 +16,7 @@ let
{
# 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" ''
exec ${pkgs.nodejs}/bin/node -e '
require("node:http").createServer((req, res) => res.end("agent-vm-test"))
.listen(3080, "127.0.0.1", () => console.log("http://127.0.0.1:3080/?token=offline-test"));
'
'';
agentVM.package = fakeDsh;
}
];
};
@@ -74,6 +70,10 @@ in
assert c.microvm.storeOnDisk;
assert c.systemd.services.agent.serviceConfig.User == "root";
assert c.systemd.services.agent.serviceConfig.WorkingDirectory == "/workspace";
assert pkgs.lib.hasInfix
(builtins.unsafeDiscardStringContext "${./context.sh} ${c.agentVM.package}/bin/dsh")
c.systemd.services.agent.preStart;
assert c.systemd.services.agent.serviceConfig.TimeoutStartSec == "10min";
assert c.services.openssh.settings.PasswordAuthentication == false;
assert c.services.openssh.settings.AllowAgentForwarding == false;
assert !c.services.xserver.enable;
@@ -99,11 +99,26 @@ in
];
}
''
shellcheck -s bash ${./launch.sh} ${./boot-test.sh} ${./playwright-test.sh} ${./port-test.sh}
shellcheck -s bash ${./launch.sh} ${./boot-test.sh} ${./playwright-test.sh} ${./port-test.sh} \
${./context.sh} ${./context-test.sh} ${./fake-dsh.sh}
bash -n ${./launch.sh}
touch "$out"
'';
context =
pkgs.runCommand "agent-dsh-context-check"
{
nativeBuildInputs = [
pkgs.bash
pkgs.coreutils
pkgs.jq
];
}
''
bash ${./context-test.sh} ${./context.sh} ${fakeDsh}/bin/dsh
touch "$out"
'';
playwright =
pkgs.runCommand "agent-playwright-firefox-check"
{