- Add incus.nix module for fully declarative Incus/LXC containers - Build NixOS LXC images using nixpkgs.lib.nixosSystem - Ephemeral containers: recreated on each start, cleaned up on stop - Use flock to serialize concurrent container operations - Deterministic MAC addresses via lib.mkMac to prevent ARP cache issues - Add veth* to NetworkManager unmanaged interfaces - Update CLAUDE.md with coding conventions and shared lib docs
158 lines
5.3 KiB
Nix
158 lines
5.3 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
# VM-specific configuration for sandboxed workspaces using microvm.nix
|
|
# This module is imported by default.nix for workspaces with type = "vm"
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.sandboxed-workspace;
|
|
hostConfig = config;
|
|
|
|
# Generate a deterministic vsock CID from workspace name.
|
|
#
|
|
# vsock (virtual sockets) enables host-VM communication without networking.
|
|
# cloud-hypervisor uses vsock for systemd-notify integration: when a VM finishes
|
|
# booting, systemd sends READY=1 to the host via vsock, allowing the host's
|
|
# microvm@ service to accurately track VM boot status instead of guessing.
|
|
#
|
|
# Each VM needs a unique CID (Context Identifier). Reserved CIDs per vsock(7):
|
|
# - VMADDR_CID_HYPERVISOR (0): reserved for hypervisor
|
|
# - VMADDR_CID_LOCAL (1): loopback address
|
|
# - VMADDR_CID_HOST (2): host address
|
|
# See: https://man7.org/linux/man-pages/man7/vsock.7.html
|
|
# https://docs.kernel.org/virt/kvm/vsock.html
|
|
#
|
|
# We auto-generate from SHA256 hash to ensure uniqueness without manual assignment.
|
|
# Range: 100 - 16777315 (offset avoids reserved CIDs and leaves 3-99 for manual use)
|
|
nameToCid = name:
|
|
let
|
|
hash = builtins.hashString "sha256" name;
|
|
hexPart = builtins.substring 0 6 hash;
|
|
in
|
|
100 + (builtins.foldl'
|
|
(acc: c: acc * 16 + (
|
|
if c == "a" then 10
|
|
else if c == "b" then 11
|
|
else if c == "c" then 12
|
|
else if c == "d" then 13
|
|
else if c == "e" then 14
|
|
else if c == "f" then 15
|
|
else lib.strings.toInt c
|
|
)) 0
|
|
(lib.stringToCharacters hexPart));
|
|
|
|
# Filter for VM-type workspaces only
|
|
vmWorkspaces = filterAttrs (n: ws: ws.type == "vm") cfg.workspaces;
|
|
|
|
# Generate VM configuration for a workspace
|
|
mkVmConfig = name: ws: {
|
|
inherit pkgs; # Use host's pkgs (includes allowUnfree)
|
|
config = import ws.config;
|
|
specialArgs = { inputs = hostConfig.inputs; };
|
|
extraModules = [
|
|
(import ./base.nix {
|
|
inherit hostConfig;
|
|
workspaceName = name;
|
|
ip = ws.ip;
|
|
networkInterface = { Type = "ether"; };
|
|
})
|
|
{
|
|
# Copy credentials from host mount to per-workspace config
|
|
systemd.services.claude-credentials = {
|
|
description = "Copy Claude credentials from host";
|
|
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 = {
|
|
# Use cloud-hypervisor for better performance
|
|
hypervisor = lib.mkDefault "cloud-hypervisor";
|
|
|
|
# Resource allocation
|
|
vcpu = 8;
|
|
mem = 4096; # 4GB RAM
|
|
|
|
# Disk for writable overlay
|
|
volumes = [{
|
|
image = "overlay.img";
|
|
mountPoint = "/nix/.rw-store";
|
|
size = 8192; # 8GB
|
|
}];
|
|
|
|
# Shared directories with host using virtiofs
|
|
shares = [
|
|
{
|
|
# Share the host's /nix/store for accessing packages
|
|
proto = "virtiofs";
|
|
tag = "ro-store";
|
|
source = "/nix/store";
|
|
mountPoint = "/nix/.ro-store";
|
|
}
|
|
{
|
|
proto = "virtiofs";
|
|
tag = "workspace";
|
|
source = "/home/googlebot/sandboxed/${name}/workspace";
|
|
mountPoint = "/home/googlebot/workspace";
|
|
}
|
|
{
|
|
proto = "virtiofs";
|
|
tag = "ssh-host-keys";
|
|
source = "/home/googlebot/sandboxed/${name}/ssh-host-keys";
|
|
mountPoint = "/etc/ssh-host-keys";
|
|
}
|
|
{
|
|
proto = "virtiofs";
|
|
tag = "claude-config";
|
|
source = "/home/googlebot/sandboxed/${name}/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
|
|
writableStoreOverlay = "/nix/.rw-store";
|
|
|
|
# TAP interface for bridged networking
|
|
# The interface name "vm-*" matches the pattern in common/network/microvm.nix
|
|
# which automatically attaches it to the microbr bridge
|
|
interfaces = [{
|
|
type = "tap";
|
|
id = "vm-${name}";
|
|
mac = lib.mkMac "vm-${name}";
|
|
}];
|
|
|
|
# Enable vsock for systemd-notify integration
|
|
vsock.cid =
|
|
if ws.cid != null
|
|
then ws.cid
|
|
else nameToCid name;
|
|
};
|
|
}
|
|
];
|
|
autostart = ws.autoStart;
|
|
};
|
|
in
|
|
{
|
|
config = mkIf (cfg.enable && vmWorkspaces != { }) {
|
|
# Convert VM workspace configs to microvm.nix format
|
|
microvm.vms = mapAttrs mkVmConfig vmWorkspaces;
|
|
};
|
|
}
|