{ 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 ''; }