Files
claude-vm/default.nix
T
2026-05-29 16:33:00 +02:00

179 lines
4.8 KiB
Nix

{
sources ? import ./npins,
system ? builtins.currentSystem,
pkgs ? import sources.nixpkgs {
inherit system;
config.allowUnfree = true;
},
}:
let
claudeCode = pkgs.callPackage "${sources.claude-code-nix}/package.nix" { };
oraios-pywebview = pkgs.callPackage ./pkgs/oraios-pywebview/package.nix { };
serena = pkgs.callPackage ./pkgs/serena/package.nix { inherit oraios-pywebview; };
fable = pkgs.callPackage ./pkgs/fable/package.nix { };
nixosCfg = import "${sources.nixpkgs}/nixos/lib/eval-config.nix" {
inherit system;
specialArgs = {
inherit sources;
};
modules = [
{ nixpkgs.pkgs = pkgs; }
(
{ modulesPath, ... }:
{
imports = [ "${modulesPath}/virtualisation/qemu-vm.nix" ];
virtualisation = {
graphics = false;
memorySize = 4096;
cores = 4;
# TODO: Port-forward the serena dashboard
# forwardPorts = [
# {
# from = "guest";
# guest = {
# address = "10.0.2.15";
# port = 24282;
# };
# host = {
# address = "127.0.0.1";
# port = 24282;
# };
# }
# ];
sharedDirectories = {
workspace = {
source = ''"$WORKSPACE_DIR"'';
target = "/workspace";
securityModel = "none";
};
config = {
source = ''"$CLAUDE_VM_CONFIG_DIR"'';
target = "/mnt/claude-vm-config";
securityModel = "none";
};
claude = {
source = ''"$CLAUDE_HOME_DIR"'';
target = "/home/claude";
securityModel = "none";
};
nuget = {
source = ''"$NUGET_DIR"'';
target = "/home/claude/.nuget";
securityModel = "none";
};
};
};
boot = {
kernelParams = [ "console=ttyS0" ];
loader.grub.enable = false;
initrd.kernelModules = [
"9p"
"9pnet_virtio"
];
};
users.users.claude = {
isNormalUser = true;
home = "/home/claude";
extraGroups = [ "wheel" ];
};
services.getty.autologinUser = "claude";
services.udisks2.enable = false;
programs.command-not-found.enable = false;
# packages
environment.systemPackages = with pkgs; [
claudeCode
serena
fable
git
curl
vim
dotnetCorePackages.sdk_10_0
dapr-cli
bun
just
yaml-language-server
typescript-language-server
fsautocomplete
nixd
python3
# sqlfluff
];
# login shell launches claude
programs.bash.interactiveShellInit = ''
# Only run for the auto-login claude user
[ "$(whoami)" = "claude" ] || return
args=()
if [ -f /mnt/claude-vm-config/claude-args ]; then
while IFS= read -r line; do
[ -n "$line" ] && args+=("$line")
done < /mnt/claude-vm-config/claude-args
fi
# if [ -f /mnt/claude-vm-config/gitconfig ]; then
# cp /mnt/claude-vm-config/gitconfig ~/.gitconfig
# fi
cd /workspace 2>/dev/null || true
# Register serena as an MCP server if not already configured
if ! claude mcp get serena &>/dev/null; then
claude mcp add serena -- ${serena}/bin/serena start-mcp-server --context=claude-code --project-from-cwd
fi
exec claude "''${args[@]}"
'';
# misc
networking.hostName = "claude-vm";
system.stateVersion = "25.05";
}
)
];
};
vmBuild = nixosCfg.config.system.build.vm;
in
{
claude-vm = pkgs.writeShellScriptBin "claude-vm" ''
CONFIG_DIR=$(mktemp -d)
trap "rm -rf '$CONFIG_DIR'" EXIT
# Write all CLI args to a file, one per line
if [ $# -gt 0 ]; then
printf '%s\n' "$@" > "$CONFIG_DIR/claude-args"
else
touch "$CONFIG_DIR/claude-args"
fi
if [ -e ~/.config/git/config ]; then
cp ~/.config/git/config $CONFIG_DIR/gitconfig
fi
[ ! -d ~/.claude-vm ] && mkdir -p ~/.claude-vm
if [ -e ~/.nuget ]; then
export NUGET_DIR=~/.nuget
else
mkdir -p $CONFIG_DIR/nuget
export NUGET_DIR=$CONFIG_DIR/nuget
fi
export CLAUDE_VM_CONFIG_DIR="$CONFIG_DIR"
export CLAUDE_HOME_DIR=~/.claude-vm
export WORKSPACE_DIR="$(pwd)"
${vmBuild}/bin/run-claude-vm-vm
'';
}