This commit is contained in:
2026-03-02 16:35:14 +01:00
parent d182532b34
commit a511c65d84
17 changed files with 741 additions and 52 deletions

View File

@@ -0,0 +1,177 @@
{
pkgs,
lib,
inputs,
config,
...
}:
{
imports = [
./hardware-configuration.nix
inputs.self.nixosModules.vm-guest
inputs.self.nixosModules.seed-ssh
inputs.self.nixosModules.localisation
];
networking.hostName = "sandbox";
vm-guest = {
enable = true;
headless = true;
};
seed-ssh = {
enable = true;
user = "sandbox";
};
localisation = {
enable = true;
timeZone = "UTC";
defaultLocale = "en_US.UTF-8";
};
users.users.sandbox = {
isNormalUser = true;
extraGroups = [
"wheel"
"docker"
];
};
# 9p mounts — silently fail if shares not provided at runtime
fileSystems."/mnt/9p-projects" = {
device = "projects";
fsType = "9p";
options = [
"trans=virtio"
"version=9p2000.L"
"msize=65536"
"nofail"
"x-systemd.automount"
"x-systemd.device-timeout=2s"
];
};
# remap ownership so sandbox user can read/write regardless of host UID
fileSystems."/home/sandbox/projects" = {
device = "/mnt/9p-projects";
fsType = "fuse.bindfs";
options = [
"force-user=sandbox"
"force-group=users"
"perms=0755"
"create-for-user=sandbox"
"create-for-group=users"
"nofail"
"x-systemd.automount"
];
};
fileSystems."/mnt/host-claude" = {
device = "hostclaude";
fsType = "9p";
options = [
"trans=virtio"
"version=9p2000.L"
"msize=65536"
"nofail"
"x-systemd.device-timeout=2s"
];
};
fileSystems."/mnt/host-home" = {
device = "hosthome";
fsType = "9p";
options = [
"trans=virtio"
"version=9p2000.L"
"msize=65536"
"nofail"
"x-systemd.device-timeout=2s"
"ro"
];
};
# pre-auth claude-code from host config
systemd.services.claude-auth = {
description = "Copy claude-code credentials from host mount";
after = [
"mnt-host\\x2dclaude.mount"
"mnt-host\\x2dhome.mount"
];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart =
let
mountpoint = "${pkgs.util-linux}/bin/mountpoint";
in
pkgs.writeShellScript "claude-auth" ''
# wait for mounts to appear
for i in $(seq 1 10); do
${mountpoint} -q /mnt/host-claude && break
${mountpoint} -q /mnt/host-home && break
sleep 1
done
if ! ${mountpoint} -q /mnt/host-claude && ! ${mountpoint} -q /mnt/host-home; then
echo "no host mounts found, skipping"
exit 0
fi
mkdir -p /home/sandbox/.claude
if ${mountpoint} -q /mnt/host-claude; then
cp -a /mnt/host-claude/. /home/sandbox/.claude/
fi
if ${mountpoint} -q /mnt/host-home; then
cp /mnt/host-home/.claude.json /home/sandbox/.claude.json || true
fi
chown -R sandbox:users /home/sandbox/.claude /home/sandbox/.claude.json 2>/dev/null || true
'';
};
};
virtualisation.docker = {
enable = true;
logDriver = "json-file";
};
environment.systemPackages = with pkgs; [
bindfs
claude-code
# tools
tmux
fd
ripgrep
jq
fzf
just
];
# image builder VM needs more than the default 1G to copy closure
image.modules =
let
imageMemOverride =
{ config, modulesPath, ... }:
{
system.build.image = lib.mkForce (
import (modulesPath + "/../lib/make-disk-image.nix") {
inherit lib config pkgs;
inherit (config.virtualisation) diskSize;
inherit (config.image) baseName format;
partitionTableType = if config.image.efiSupport then "efi" else "legacy";
memSize = 16384;
}
);
};
in
{
qemu = imageMemOverride;
qemu-efi = imageMemOverride;
};
system.stateVersion = "25.11";
}

View File

@@ -0,0 +1,21 @@
{
lib,
pkgs,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
fsType = "ext4";
};
# image.modules (disk-image.nix) overrides boot loader per variant
# x86_64: qemu (grub), aarch64: qemu-efi (systemd-boot)
boot.loader.grub.device = lib.mkDefault "/dev/vda";
}