nixos: add Cua sandbox module

This commit is contained in:
2026-05-03 00:12:41 -07:00
parent ca5b2b566f
commit ee1a6b8904
3 changed files with 256 additions and 0 deletions

View File

@@ -7,6 +7,7 @@
./cache-server.nix ./cache-server.nix
./cache.nix ./cache.nix
./code.nix ./code.nix
./cua.nix
./desktop.nix ./desktop.nix
./electron.nix ./electron.nix
./environment.nix ./environment.nix

246
nixos/cua.nix Normal file
View File

@@ -0,0 +1,246 @@
{ config, lib, ... }:
let
cfg = config.myModules.cua;
flavorDefaults = {
xfce = {
image = "trycua/cua-xfce:latest";
storageMountPath = "/home/cua/storage";
apiContainerPort = 8000;
noVncContainerPort = 6901;
};
kasm = {
image = "trycua/cua-ubuntu:latest";
storageMountPath = "/home/kasm-user/storage";
apiContainerPort = 8000;
noVncContainerPort = 6901;
};
qemu-linux = {
image = "trycua/cua-qemu-linux:latest";
storageMountPath = "/storage";
apiContainerPort = 5000;
noVncContainerPort = 8006;
};
};
selectedFlavor = flavorDefaults.${cfg.flavor};
usingQemu = cfg.flavor == "qemu-linux";
in
{
options.myModules.cua = {
enable = lib.mkEnableOption "Cua Linux computer-use sandbox";
android = {
enable = lib.mkEnableOption "Cua Android QEMU computer-use sandbox";
image = lib.mkOption {
type = lib.types.str;
default = "trycua/cua-qemu-android:latest";
description = "OCI image to run for the Cua Android QEMU sandbox.";
};
bindAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Address on which to expose the Cua Android API and VNC web ports.";
};
apiPort = lib.mkOption {
type = lib.types.port;
default = 8001;
description = "Host port for the Cua Android computer-server API.";
};
webVncPort = lib.mkOption {
type = lib.types.port;
default = 6080;
description = "Host port for the Cua Android VNC web UI.";
};
emulatorDevice = lib.mkOption {
type = lib.types.str;
default = "Samsung Galaxy S10";
description = "Android emulator device profile.";
};
autoStart = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to start the Cua Android sandbox automatically.";
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to open the Cua Android API and VNC web ports in the firewall.";
};
extraOptions = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra options passed to the Cua Android container runtime.";
};
};
flavor = lib.mkOption {
type = lib.types.enum [ "xfce" "kasm" "qemu-linux" ];
default = "xfce";
description = "Cua Linux sandbox flavor to run.";
};
image = lib.mkOption {
type = lib.types.str;
default = selectedFlavor.image;
defaultText = "upstream default image for the selected Cua flavor";
description = "OCI image to run for the Cua Linux sandbox.";
};
storageDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/cua/linux";
description = "Host directory used for persistent Cua VM storage.";
};
bindAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = "Address on which to expose the Cua API and noVNC ports.";
};
apiPort = lib.mkOption {
type = lib.types.port;
default = selectedFlavor.apiContainerPort;
defaultText = "upstream API port for the selected Cua flavor";
description = "Host port for the Cua computer-server API.";
};
noVncPort = lib.mkOption {
type = lib.types.port;
default = selectedFlavor.noVncContainerPort;
defaultText = "upstream noVNC port for the selected Cua flavor";
description = "Host port for the Cua noVNC web UI.";
};
vncPort = lib.mkOption {
type = lib.types.nullOr lib.types.port;
default = 5901;
description = "Optional host port for direct VNC access. Only used by the XFCE flavor.";
};
vncResolution = lib.mkOption {
type = lib.types.str;
default = "1024x768";
description = "VNC desktop resolution for the Cua XFCE container.";
};
ramSize = lib.mkOption {
type = lib.types.str;
default = "8G";
description = "RAM allocated to the Cua QEMU VM.";
};
cpuCores = lib.mkOption {
type = lib.types.ints.positive;
default = 4;
description = "CPU cores allocated to the Cua QEMU VM.";
};
diskSize = lib.mkOption {
type = lib.types.str;
default = "64G";
description = "Disk size allocated to the Cua QEMU VM. Only used by the QEMU Linux flavor.";
};
shmSize = lib.mkOption {
type = lib.types.str;
default = "512m";
description = "Shared-memory size for non-QEMU Cua desktop containers.";
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to open the Cua API and noVNC ports in the firewall.";
};
autoStart = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to start the Cua sandbox container automatically.";
};
extraOptions = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Extra options passed to the container runtime.";
};
};
config = lib.mkIf cfg.enable {
virtualisation.docker.enable = true;
systemd.tmpfiles.rules = [
"d ${toString cfg.storageDir} 0750 root root -"
];
virtualisation.oci-containers.containers = {
cua-sandbox = {
image = cfg.image;
autoStart = cfg.autoStart;
ports = [
"${cfg.bindAddress}:${toString cfg.noVncPort}:${toString selectedFlavor.noVncContainerPort}"
"${cfg.bindAddress}:${toString cfg.apiPort}:${toString selectedFlavor.apiContainerPort}"
]
++ lib.optionals (cfg.flavor == "xfce" && cfg.vncPort != null) [
"${cfg.bindAddress}:${toString cfg.vncPort}:5901"
];
volumes = [
"${toString cfg.storageDir}:${selectedFlavor.storageMountPath}"
];
devices = lib.optionals usingQemu [
"/dev/kvm:/dev/kvm"
];
capabilities = lib.optionalAttrs usingQemu {
NET_ADMIN = true;
};
environment =
lib.optionalAttrs usingQemu {
RAM_SIZE = cfg.ramSize;
CPU_CORES = toString cfg.cpuCores;
DISK_SIZE = cfg.diskSize;
}
// lib.optionalAttrs (cfg.flavor == "xfce") {
VNC_RESOLUTION = cfg.vncResolution;
}
// lib.optionalAttrs (cfg.flavor == "kasm") {
VNCOPTIONS = "-disableBasicAuth";
};
extraOptions =
lib.optionals (!usingQemu) [ "--shm-size=${cfg.shmSize}" ]
++ cfg.extraOptions;
};
cua-android = lib.mkIf cfg.android.enable {
image = cfg.android.image;
autoStart = cfg.android.autoStart;
ports = [
"${cfg.android.bindAddress}:${toString cfg.android.webVncPort}:6080"
"${cfg.android.bindAddress}:${toString cfg.android.apiPort}:8000"
];
devices = [
"/dev/kvm:/dev/kvm"
];
environment = {
EMULATOR_DEVICE = cfg.android.emulatorDevice;
WEB_VNC = "true";
};
extraOptions = cfg.android.extraOptions;
};
};
networking.firewall.allowedTCPPorts =
lib.optionals cfg.openFirewall (
[ cfg.apiPort cfg.noVncPort ]
++ lib.optional (cfg.flavor == "xfce" && cfg.vncPort != null) cfg.vncPort
)
++ lib.optionals cfg.android.openFirewall [ cfg.android.apiPort cfg.android.webVncPort ];
};
}

View File

@@ -19,6 +19,15 @@
myModules.gitea-runner.enable = true; myModules.gitea-runner.enable = true;
myModules.postgres.enable = true; myModules.postgres.enable = true;
myModules.tts.enable = true; myModules.tts.enable = true;
myModules.cua = {
enable = true;
android = {
enable = true;
# Android is QEMU/KVM-only. Keep this manually startable until SVM/AMD-V
# is exposed to Linux and /dev/kvm exists on this host.
autoStart = false;
};
};
myModules.railbird-k3s = { myModules.railbird-k3s = {
enable = false; enable = false;
serverAddr = "https://jimi-hendnix.local:6443"; serverAddr = "https://jimi-hendnix.local:6443";