Rework Claude Code config in sandboxed workspaces

Remove credential passing to sandboxes (didn't work well enough).
Move onboarding config init from host-side setup into base.nix so
each workspace initializes its own Claude config on first boot.
Wrap claude command in VM and Incus workspaces to always skip
permission prompts.
This commit is contained in:
2026-02-09 19:56:11 -08:00
parent cbf2aedcad
commit 8293a7dc2a
5 changed files with 40 additions and 76 deletions

View File

@@ -11,6 +11,17 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let
claudeConfigFile = pkgs.writeText "claude-config.json" (builtins.toJSON {
hasCompletedOnboarding = true;
theme = "dark";
projects = {
"/home/googlebot/workspace" = {
hasTrustDialogAccepted = true;
};
};
});
in
{ {
imports = [ imports = [
../shell.nix ../shell.nix
@@ -112,6 +123,22 @@
# Enable fish shell # Enable fish shell
programs.fish.enable = true; programs.fish.enable = true;
# Initialize Claude Code config on first boot (skips onboarding, trusts workspace)
systemd.services.claude-config-init = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = "googlebot";
Group = "users";
};
script = ''
if [ ! -f /home/googlebot/claude-config/.claude.json ]; then
cp ${claudeConfigFile} /home/googlebot/claude-config/.claude.json
fi
'';
};
# Home Manager configuration # Home Manager configuration
home-manager.useGlobalPkgs = true; home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true; home-manager.useUserPackages = true;

View File

@@ -47,16 +47,10 @@ in
hostPath = "/home/googlebot/sandboxed/${name}/ssh-host-keys"; hostPath = "/home/googlebot/sandboxed/${name}/ssh-host-keys";
isReadOnly = false; isReadOnly = false;
}; };
# Per-workspace claude config for isolated session data
"/home/googlebot/claude-config" = { "/home/googlebot/claude-config" = {
hostPath = "/home/googlebot/sandboxed/${name}/claude-config"; hostPath = "/home/googlebot/sandboxed/${name}/claude-config";
isReadOnly = false; isReadOnly = false;
}; };
# Share credentials from host (read-only)
"/home/googlebot/claude-config/.credentials.json" = {
hostPath = "/home/googlebot/.claude/.credentials.json";
isReadOnly = true;
};
}; };
config = { config, lib, pkgs, ... }: { config = { config, lib, pkgs, ... }: {

View File

@@ -122,45 +122,13 @@ in
cfg.workspaces); cfg.workspaces);
# Automatically generate SSH host keys and directories for all workspaces # Automatically generate SSH host keys and directories for all workspaces
systemd.services = lib.mkMerge [ systemd.services = lib.mapAttrs'
# Create credentials-only directory for VMs (symlinks to actual credentials)
{
claude-credentials-dir = {
description = "Setup Claude credentials directory for VM workspaces";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = "googlebot";
Group = "users";
};
script = ''
mkdir -p /home/googlebot/.claude-credentials
# Copy credentials file (not symlink - virtiofs can't follow host symlinks)
if [ -f /home/googlebot/.claude/.credentials.json ]; then
cp /home/googlebot/.claude/.credentials.json /home/googlebot/.claude-credentials/.credentials.json
chmod 600 /home/googlebot/.claude-credentials/.credentials.json
fi
'';
};
}
# Per-workspace setup services
(lib.mapAttrs'
(name: ws: (name: ws:
let let
serviceName = serviceName =
if ws.type == "vm" then "microvm@${name}" if ws.type == "vm" then "microvm@${name}"
else if ws.type == "incus" then "incus-workspace-${name}" else if ws.type == "incus" then "incus-workspace-${name}"
else "container@${name}"; else "container@${name}";
claudeConfig = builtins.toJSON {
hasCompletedOnboarding = true;
theme = "dark";
projects = {
"/home/googlebot/workspace" = {
hasTrustDialogAccepted = true;
};
};
};
in in
lib.nameValuePair "workspace-${name}-setup" { lib.nameValuePair "workspace-${name}-setup" {
description = "Setup directories and SSH keys for workspace ${name}"; description = "Setup directories and SSH keys for workspace ${name}";
@@ -188,23 +156,9 @@ in
chown googlebot:users /home/googlebot/sandboxed/${name}/ssh-host-keys/ssh_host_ed25519_key* chown googlebot:users /home/googlebot/sandboxed/${name}/ssh-host-keys/ssh_host_ed25519_key*
echo "Generated SSH host key for workspace ${name}" echo "Generated SSH host key for workspace ${name}"
fi fi
# Create claude-code config to skip onboarding and trust ~/workspace
if [ ! -f /home/googlebot/sandboxed/${name}/claude-config/.claude.json ]; then
echo '${claudeConfig}' > /home/googlebot/sandboxed/${name}/claude-config/.claude.json
chown googlebot:users /home/googlebot/sandboxed/${name}/claude-config/.claude.json
fi
'' + lib.optionalString (ws.type == "incus") ''
# Copy credentials for incus (can't use bind mount for files inside another mount)
if [ -f /home/googlebot/.claude/.credentials.json ]; then
cp /home/googlebot/.claude/.credentials.json /home/googlebot/sandboxed/${name}/claude-config/.credentials.json
chown googlebot:users /home/googlebot/sandboxed/${name}/claude-config/.credentials.json
chmod 600 /home/googlebot/sandboxed/${name}/claude-config/.credentials.json
fi
''; '';
} }
) )
cfg.workspaces) cfg.workspaces;
];
}; };
} }

View File

@@ -31,6 +31,12 @@ let
boot.isContainer = true; boot.isContainer = true;
networking.useHostResolvConf = false; networking.useHostResolvConf = false;
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
environment.systemPackages = [
(lib.hiPrio (pkgs.writeShellScriptBin "claude" ''
exec ${pkgs.claude-code}/bin/claude --dangerously-skip-permissions "$@"
''))
];
}) })
]; ];
}; };

View File

@@ -58,20 +58,11 @@ let
networkInterface = { Type = "ether"; }; networkInterface = { Type = "ether"; };
}) })
{ {
# Copy credentials from host mount to per-workspace config environment.systemPackages = [
systemd.services.claude-credentials = { (lib.hiPrio (pkgs.writeShellScriptBin "claude" ''
description = "Copy Claude credentials from host"; exec ${pkgs.claude-code}/bin/claude --dangerously-skip-permissions "$@"
wantedBy = [ "multi-user.target" ]; ''))
before = [ "multi-user.target" ]; ];
serviceConfig.Type = "oneshot";
script = ''
if [ -f /home/googlebot/.claude-credentials/.credentials.json ]; then
install -m 600 -o googlebot -g users \
/home/googlebot/.claude-credentials/.credentials.json \
/home/googlebot/claude-config/.credentials.json
fi
'';
};
# MicroVM specific configuration # MicroVM specific configuration
microvm = { microvm = {
@@ -116,14 +107,6 @@ let
source = "/home/googlebot/sandboxed/${name}/claude-config"; source = "/home/googlebot/sandboxed/${name}/claude-config";
mountPoint = "/home/googlebot/claude-config"; mountPoint = "/home/googlebot/claude-config";
} }
{
# Credentials-only directory (read-only)
# This directory should contain only .credentials.json
proto = "virtiofs";
tag = "claude-credentials";
source = "/home/googlebot/.claude-credentials";
mountPoint = "/home/googlebot/.claude-credentials";
}
]; ];
# Writeable overlay for /nix/store # Writeable overlay for /nix/store