init: public

This commit is contained in:
2025-04-13 15:09:14 +08:00
parent 5995c2050b
commit 50247d94e8
253 changed files with 12964 additions and 567 deletions

228
modules/virt/default.nix Normal file
View File

@@ -0,0 +1,228 @@
{
config,
lib,
pkgs,
username,
...
}:
let
cfg = config.my.virt;
generateConfig =
settings:
lib.generators.toINI {
mkKeyValue = lib.generators.mkKeyValueDefault {
mkValueString =
v:
if (true == v) then
"yes"
else if (false == v) then
"no"
else
(lib.generators.mkValueStringDefault { } v);
} "=";
} settings;
in
{
imports = [
(lib.my.makeHomePackageConfig {
inherit config pkgs;
packageName = "moonlight-qt";
packagePath = [ "moonlight-qt" ];
optionPath = [
"virt"
"moonlight"
];
})
];
options.my.virt = {
enable = lib.mkEnableOption "virtualization";
looking-glass = {
enable = lib.mkEnableOption "looking-glass";
package = lib.mkPackageOption pkgs "looking-glass-client" { };
kvmfr = {
enable = lib.mkEnableOption "KVM Frame Relay (kvmfr)";
package = lib.mkPackageOption config.boot.kernelPackages "kvmfr" {
pkgsText = "config.boot.kernelPackages";
};
owner = lib.mkOption {
description = "Owner of the kvmfr device file(s). You probably want to set this to the name of the user who will run looking-glass-client.";
default = "kvm";
example = "your-user-name";
type = lib.types.str;
};
group = lib.mkOption {
description = "Group of the kvmfr device file(s)";
default = "kvm";
example = "kvm";
type = lib.types.str;
};
sizeMB = lib.mkOption {
description = "Size (in megabytes) of the kvmfr device";
default = 32;
example = 128;
type = lib.types.ints.positive;
};
configureDeviceACLs = lib.mkOption {
description = ''
Configure libvirtd's qemu.conf to include the kvmfr device in the
cgroup_device_acl setting. When enabled, this module adds a block
to the 'virtualisation.libvirtd.qemu.verbatimConfig' option which
sets cgroup_device_acl to the default list of devices along with
the kvmfr0 device. You probably want this enabled unless you
explicitly set the 'verbatimConfig' option elsewhere.
If you already set a custom value for cgroup_device_acl, you should
not enable this option. Instead, add '/dev/kvmfr0' to the setting
yourself.
If you do not add '/dev/kvmfr0' to the list or enable this option,
any VMs attempting to utilize kvmfr will fail to start, and libvirt
will report a Permission Denied error.
'';
default = false;
example = true;
type = lib.types.bool;
};
};
# Options for creating the /dev/shm shared memory file
shm = {
enable = lib.mkEnableOption "Shared Memory Frame Relay";
owner = lib.mkOption {
description = "Owner of the shared memory file";
default = "kvm";
example = "your-user-name";
type = lib.types.str;
};
name = lib.mkOption {
description = "Name of the shared memory file under /dev/shm";
default = "looking-glass";
example = "looking-glass";
};
};
# Client configuration (written to /etc/looking-glass-client.ini)
settings = lib.mkOption {
description = "Looking Glass client configuration";
default = { };
type = lib.types.submodule ./types;
example = {
app.shmFile = "/dev/kvmfr0";
input.escapeKey = "KEY_RIGHTCTRL";
win = {
fullScreen = true;
noScreensaver = true;
showFPS = true;
};
};
};
};
};
config = lib.mkMerge [
(lib.mkIf cfg.looking-glass.enable (
let
cfg' = cfg.looking-glass;
in
{
warnings = lib.lists.optional (!cfg'.kvmfr.enable && !cfg'.shm.enable) ''
Neither the shared memory nor kvmfr kernel module are enabled.
You must configure one of these manually or set one of programs.looking-glass.kvmfr.enable
or programs.looking-glass.shm.enable to have them automatically configured.
'';
# Install looking glass
environment.systemPackages = [ cfg'.package ];
# Optionally install the kvmfr kernel module
boot.extraModulePackages = lib.lists.optional cfg'.kvmfr.enable cfg'.kvmfr.package;
# Create the configuration file if requested
environment.etc = {
# Write the looking glass configuration
"looking-glass-client.ini".text = generateConfig cfg'.settings;
# Set the frame size if kvmfr is enabled
"modprobe.d/kvmfr.conf" = {
enable = cfg'.kvmfr.enable;
text = ''
options kvmfr static_size_mb=${toString cfg'.kvmfr.sizeMB}
'';
};
# Load the kvmfr module at boot if enabled
"modules-load.d/kvmfr.conf" = {
enable = cfg'.kvmfr.enable;
text = ''
# KVMFR Looking Glass Module
kvmfr
'';
};
};
# Automatically apply permissions to the kvmfr device as requested
services.udev.packages = lib.lists.optional cfg'.kvmfr.enable (
pkgs.writeTextFile {
name = "99-looking-glass-kvmfr.rules";
destination = "/etc/udev/rules.d/99-looking-glass-kvmfr.rules";
text = ''
SUBSYSTEM=="kvmfr", OWNER="${cfg'.kvmfr.owner}", GROUP="${cfg'.kvmfr.group}", MODE="0660"
'';
}
);
# Create the /dev/shm file if requested
systemd.tmpfiles.rules = lib.lists.optional cfg'.shm.enable ''
f /dev/shm/${cfg'.shm.name} 0660 ${cfg'.shm.owner} qemu-libvirtd -
'';
# Allow access to the kvmfr device from the libvirt cgroups
virtualisation.libvirtd.qemu.verbatimConfig =
lib.strings.optionalString (cfg'.kvmfr.enable && cfg'.kvmfr.configureDeviceACLs)
''
cgroup_device_acl = [
"/dev/null", "/dev/full", "/dev/zero",
"/dev/random", "/dev/urandom",
"/dev/ptmx", "/dev/kvm", "/dev/kqemu",
"/dev/rtc","/dev/hpet", "/dev/vfio/vfio",
"/dev/kvmfr0"
]
'';
# Add libvirt-qemu apparmor policy allowing rw access to kvmfr device if apparmor is enabled
security.apparmor.packages =
lib.lists.optional (cfg'.kvmfr.enable && config.security.apparmor.enable)
(
pkgs.writeTextFile {
name = "looking-glass-kvmfr-apparmor-policy";
destination = "/etc/apparmor.d/local/abstractions/libvirt-qemu";
text = ''
# Looking Glass
/dev/kvmfr0 rw,
'';
}
);
}
))
(lib.mkIf cfg.enable {
virtualisation.libvirtd = {
enable = true;
qemu.verbatimConfig = ''
dynamic_ownership = 0
remember_owner = 0
'';
};
programs.virt-manager.enable = true;
users.users.${username}.extraGroups = [ "libvirtd" ];
environment.systemPackages = with pkgs; [ virglrenderer ];
})
];
}

View File

@@ -0,0 +1,34 @@
{ lib, ... }:
{
options = {
renderer = lib.mkOption {
description = "Specify the renderer to use";
default = "auto";
type = lib.types.str;
};
cursorPollInterval = lib.mkOption {
description = "How often to check for a cursor update in microseconds";
default = 1000;
type = lib.types.ints.positive;
};
framePollInterval = lib.mkOption {
description = "How often to check for a frame update in microseconds";
default = 1000;
type = lib.types.ints.positive;
};
allowDMA = lib.mkOption {
description = "Allow DMA transfers if supported";
default = true;
type = lib.types.bool;
};
shmFile = lib.mkOption {
description = "Path to the shared memory file or kvmfr device";
default = "/dev/kvmfr0";
type = lib.types.str;
};
};
}

View File

@@ -0,0 +1,32 @@
{ lib, ... }:
{
options = {
periodSize = lib.mkOption {
description = "Requested audio device period size in samples";
default = 2048;
type = lib.types.ints.positive;
};
bufferLatency = lib.mkOption {
description = "Additional buffer latency in milliseconds";
default = 13;
type = lib.types.ints.positive;
};
micDefault = lib.mkOption {
description = "Default action when an application opens the microphone";
default = "prompt";
type = lib.types.enum [
"prompt"
"allow"
"deny"
];
};
micShowIndicator = lib.mkOption {
description = "Display microphone usage indicator";
default = true;
type = lib.types.bool;
};
};
}

View File

@@ -0,0 +1,52 @@
{ lib, ... }:
{
options = {
app = lib.mkOption {
description = "Application-wide configuration";
default = { };
type = lib.types.submodule ./app.nix;
};
win = lib.mkOption {
description = "Window configuration";
default = { };
type = lib.types.submodule ./win.nix;
};
input = lib.mkOption {
description = "Input configuration";
default = { };
type = lib.types.submodule ./input.nix;
};
spice = lib.mkOption {
description = "Spice agent configuration";
default = { };
type = lib.types.submodule ./spice.nix;
};
audio = lib.mkOption {
description = "Audio configuration";
default = { };
type = lib.types.submodule ./audio.nix;
};
egl = lib.mkOption {
description = "EGL configuration";
default = { };
type = lib.types.submodule ./egl.nix;
};
opengl = lib.mkOption {
description = "OpenGL configuration";
default = { };
type = lib.types.submodule ./opengl.nix;
};
wayland = lib.mkOption {
description = "Wayland configuration";
default = { };
type = lib.types.submodule ./wayland.nix;
};
};
}

View File

@@ -0,0 +1,70 @@
{ lib, ... }:
{
options = {
vsync = lib.mkOption {
description = "Enable vsync";
default = false;
type = lib.types.bool;
};
doubleBuffer = lib.mkOption {
description = "Enable double buffering";
default = false;
type = lib.types.bool;
};
multiSample = lib.mkOption {
description = "Enable multisampling";
default = true;
type = lib.types.bool;
};
nvGainMax = lib.mkOption {
description = "The maximum night vision gain";
default = 1;
type = lib.types.int;
};
nvGain = lib.mkOption {
description = "The initial night vision gain at startup";
default = 0;
type = lib.types.int;
};
cbMode = lib.mkOption {
description = "Color Blind Mode (0 = Off, 1 = Protanope, 2 = Deuteranope, 3 = Tritanope)";
default = 0;
type = lib.types.addCheck lib.types.int (x: x >= 0 && x <= 3);
};
scale = lib.mkOption {
description = "Set the scale algorithm (0 = auto, 1 = nearest, 2 = linear)";
default = 0;
type = lib.types.addCheck lib.types.int (x: x >= 0 && x <= 2);
};
debug = lib.mkOption {
description = "Enable debug output";
default = false;
type = lib.types.bool;
};
noBufferAge = lib.mkOption {
description = "Disable partial rendering based on buffer age";
default = false;
type = lib.types.bool;
};
noSwapDamage = lib.mkOption {
description = "Disable swapping with damage";
default = false;
type = lib.types.bool;
};
scalePointer = lib.mkOption {
description = "Keep the pointer size 1:1 when downscaling";
default = true;
type = lib.types.bool;
};
};
}

View File

@@ -0,0 +1,84 @@
{ lib, ... }:
{
options = {
grabKeyboard = lib.mkOption {
description = "Grab the keybaord in capture mode";
default = true;
type = lib.types.bool;
};
grabKeyboardOnFocus = lib.mkOption {
description = "Grab the keyboard when focused";
default = false;
type = lib.types.bool;
};
releaseKeysOnFocusLoss = lib.mkOption {
description = "On focus loss, send key up events to guest for all held keys";
default = true;
type = lib.types.bool;
};
escapeKey = lib.mkOption {
description = "Specify the escape/menu key to use";
default = "KEY_SCROLLLOCK";
type = import ./keys.nix {
inherit lib;
};
};
ignoreWindowsKeys = lib.mkOption {
description = "Do not pass events for the windows keys to the guest";
default = false;
type = lib.types.bool;
};
hideCursor = lib.mkOption {
description = "Hide the local mouse cursor";
default = true;
type = lib.types.bool;
};
mouseSens = lib.mkOption {
description = "Initial mouse sensitivity when in capture mode (-9 to 9)";
default = 0;
type = lib.types.addCheck lib.types.int (x: x >= -9 && x <= 9);
};
mouseSmoothing = lib.mkOption {
description = "Apply simple mouse smoothing when rawMouse is not in use";
default = true;
type = lib.types.bool;
};
rawMouse = lib.mkOption {
description = "Use RAW mouse input when in capture mode (good for gaming)";
default = false;
type = lib.types.bool;
};
mouseRedraw = lib.mkOption {
description = "Mouse movements trigger redraws (ignore FPS minimum)";
default = true;
type = lib.types.bool;
};
autoCapture = lib.mkOption {
description = "Try to keep the mouse captured when needed";
default = false;
type = lib.types.bool;
};
captureOnly = lib.mkOption {
description = "Only enable input via SPICE if in capture mode";
default = false;
type = lib.types.bool;
};
helpMenuDelay = lib.mkOption {
description = "Show help menu after holding down the escape key for this many milliseconds";
default = 200;
type = lib.types.ints.positive;
};
};
}

129
modules/virt/types/keys.nix Normal file
View File

@@ -0,0 +1,129 @@
{ lib, ... }:
lib.types.enum [
"KEY_RESERVED"
"KEY_ESC"
"KEY_1"
"KEY_2"
"KEY_3"
"KEY_4"
"KEY_5"
"KEY_6"
"KEY_7"
"KEY_8"
"KEY_9"
"KEY_0"
"KEY_MINUS"
"KEY_EQUAL"
"KEY_BACKSPACE"
"KEY_TAB"
"KEY_Q"
"KEY_W"
"KEY_E"
"KEY_R"
"KEY_T"
"KEY_Y"
"KEY_U"
"KEY_I"
"KEY_O"
"KEY_P"
"KEY_LEFTBRACE"
"KEY_RIGHTBRACE"
"KEY_ENTER"
"KEY_LEFTCTRL"
"KEY_A"
"KEY_S"
"KEY_D"
"KEY_F"
"KEY_G"
"KEY_H"
"KEY_J"
"KEY_K"
"KEY_L"
"KEY_SEMICOLON"
"KEY_APOSTROPHE"
"KEY_GRAVE"
"KEY_LEFTSHIFT"
"KEY_BACKSLASH"
"KEY_Z"
"KEY_X"
"KEY_C"
"KEY_V"
"KEY_B"
"KEY_N"
"KEY_M"
"KEY_COMMA"
"KEY_DOT"
"KEY_SLASH"
"KEY_RIGHTSHIFT"
"KEY_KPASTERISK"
"KEY_LEFTALT"
"KEY_SPACE"
"KEY_CAPSLOCK"
"KEY_F1"
"KEY_F2"
"KEY_F3"
"KEY_F4"
"KEY_F5"
"KEY_F6"
"KEY_F7"
"KEY_F8"
"KEY_F9"
"KEY_F10"
"KEY_NUMLOCK"
"KEY_SCROLLLOCK"
"KEY_KP7"
"KEY_KP8"
"KEY_KP9"
"KEY_KPMINUS"
"KEY_KP4"
"KEY_KP5"
"KEY_KP6"
"KEY_KPPLUS"
"KEY_KP1"
"KEY_KP2"
"KEY_KP3"
"KEY_KP0"
"KEY_KPDOT"
"KEY_102ND"
"KEY_F11"
"KEY_F12"
"KEY_RO"
"KEY_HENKAN"
"KEY_KATAKANAHIRAGANA"
"KEY_MUHENKAN"
"KEY_KPENTER"
"KEY_RIGHTCTRL"
"KEY_KPSLASH"
"KEY_SYSRQ"
"KEY_RIGHTALT"
"KEY_HOME"
"KEY_UP"
"KEY_PAGEUP"
"KEY_LEFT"
"KEY_RIGHT"
"KEY_END"
"KEY_DOWN"
"KEY_PAGEDOWN"
"KEY_INSERT"
"KEY_DELETE"
"KEY_MUTE"
"KEY_VOLUMEDOWN"
"KEY_VOLUMEUP"
"KEY_KPEQUAL"
"KEY_PAUSE"
"KEY_KPCOMMA"
"KEY_HANGEUL"
"KEY_HANJA"
"KEY_YEN"
"KEY_LEFTMETA"
"KEY_RIGHTMETA"
"KEY_COMPOSE"
"KEY_NEXTSONG"
"KEY_PLAYPAUSE"
"KEY_PREVIOUSSONG"
"KEY_STOPCD"
"KEY_F13"
"KEY_F14"
"KEY_F15"
"KEY_PRINT"
]

View File

@@ -0,0 +1,28 @@
{ lib, ... }:
{
options = {
mipmap = lib.mkOption {
description = "Enable mipmapping";
default = true;
type = lib.types.bool;
};
vsync = lib.mkOption {
description = "Enable vsync";
default = false;
type = lib.types.bool;
};
preventBuffer = lib.mkOption {
description = "Prevent the driver from buffering frames";
default = true;
type = lib.types.bool;
};
amdPinnedMem = lib.mkOption {
description = "Use GL_AMD_pinned_memory if it is available";
default = true;
type = lib.types.bool;
};
};
}

View File

@@ -0,0 +1,76 @@
{ lib, ... }:
{
options = {
enable = lib.mkOption {
description = "Enable the built-in SPICE client for input and/or clipboard support";
default = true;
type = lib.types.bool;
};
host = lib.mkOption {
description = "The SPICE server host or UNIX socket";
default = "127.0.0.1";
type = lib.types.str;
};
port = lib.mkOption {
description = "The SPICE server port (0 = unix socket)";
default = 5900;
type = lib.types.port;
};
input = lib.mkOption {
description = "Use SPICE to send keyboard and mouse input events to the guest";
default = true;
type = lib.types.bool;
};
clipboard = lib.mkOption {
description = "Use SPICE to synchronize the clipboard contents with the guest";
default = true;
type = lib.types.bool;
};
clipboardToVM = lib.mkOption {
description = "Allow the clipboard to be synchronized TO the VM";
default = true;
type = lib.types.bool;
};
clipboardToLocal = lib.mkOption {
description = "Allow the clipbaord to be synchronized FROM the VM";
default = true;
type = lib.types.bool;
};
audio = lib.mkOption {
description = "Enable SPICE audio support";
default = true;
type = lib.types.bool;
};
scaleCursor = lib.mkOption {
description = "Scale cursor input position to screen size when up/down scaled";
default = true;
type = lib.types.bool;
};
captureOnStart = lib.mkOption {
description = "Capture mouse and keybaord on start";
default = false;
type = lib.types.bool;
};
alwaysShowCursor = lib.mkOption {
description = "Always show host cursor";
default = false;
type = lib.types.bool;
};
showCursorDot = lib.mkOption {
description = "Use a 'dot' cursor when the window does not have focus";
default = true;
type = lib.types.bool;
};
};
}

View File

@@ -0,0 +1,16 @@
{ lib, ... }:
{
options = {
warpSupport = lib.mkOption {
description = "Enable cursor warping";
default = true;
type = lib.types.bool;
};
fractionScale = lib.mkOption {
description = "Enable fractional scale";
default = true;
type = lib.types.bool;
};
};
}

160
modules/virt/types/win.nix Normal file
View File

@@ -0,0 +1,160 @@
{ lib, ... }:
{
options = {
title = lib.mkOption {
description = "Window Title";
default = "Looking Glass (client)";
type = lib.types.str;
};
position = lib.mkOption {
description = "Initial window position at startup";
default = "center";
type = lib.types.str;
};
size = lib.mkOption {
description = "Initial window size at startup";
default = "1024x768";
type = lib.types.str;
};
autoResize = lib.mkOption {
description = "Auto resize the window to the guest";
default = false;
type = lib.types.bool;
};
allowResize = lib.mkOption {
description = "Allow the window to be resized manually";
default = true;
type = lib.types.bool;
};
keepAspect = lib.mkOption {
description = "Maintain correct aspect ratio";
default = true;
type = lib.types.bool;
};
forceAspect = lib.mkOption {
description = "Force the window to maintain the aspect ratio";
default = true;
type = lib.types.bool;
};
dontUpscale = lib.mkOption {
description = "Never try to upscale the window";
default = false;
type = lib.types.bool;
};
intUpscale = lib.mkOption {
description = "Allow only integer upscaling";
default = false;
type = lib.types.bool;
};
shrinkOnUpscale = lib.mkOption {
description = "Limit the window dimensions when dontUpscale is enabled";
default = false;
type = lib.types.bool;
};
borderless = lib.mkOption {
description = "Borderless mode";
default = false;
type = lib.types.bool;
};
fullScreen = lib.mkOption {
description = "Launch in fullscreen borderless mode";
default = false;
type = lib.types.bool;
};
maximize = lib.mkOption {
description = "Launch window maximized";
default = false;
type = lib.types.bool;
};
minimizeOnFocusLoss = lib.mkOption {
description = "Minimize window on focus loss";
default = false;
type = lib.types.bool;
};
fpsMin = lib.mkOption {
description = "Frame rate minimum (0 = disabled - not recommended, -1 = auto-detect)";
default = -1;
type = lib.types.addCheck lib.types.int (x: x == -1 || x >= 0);
};
ignoreQuit = lib.mkOption {
description = "Ignore requests to quit (i.e. Alt+F4)";
default = false;
type = lib.types.bool;
};
noScreensaver = lib.mkOption {
description = "Prevent the screensaver from starting";
default = false;
type = lib.types.bool;
};
autoScreensaver = lib.mkOption {
description = "Prevent the screensaver from starting when the guest requests it";
default = false;
type = lib.types.bool;
};
alerts = lib.mkOption {
description = "Show on screen alert messages";
default = true;
type = lib.types.bool;
};
quickSplash = lib.mkOption {
description = "Skip fading out the splash screen when a connection is established";
default = false;
type = lib.types.bool;
};
overlayDimsDesktop = lib.mkOption {
description = "Dim the desktop when in interactive overlay mode";
default = true;
type = lib.types.bool;
};
rotate = lib.mkOption {
description = "Rotate the displayed image (0, 90, 180, 270)";
default = 0;
type = lib.types.int;
};
uiFont = lib.mkOption {
description = "The font to use when rendering on-screen UI";
default = "DejaVu Sans Mono";
type = lib.types.str;
};
uiSize = lib.mkOption {
description = "The font size to use when rendering on-screen UI";
default = 14;
type = lib.types.ints.positive;
};
jitRender = lib.mkOption {
description = "Enable just-in-time rendering";
default = false;
type = lib.types.bool;
};
showFPS = lib.mkOption {
description = "Enable the FPS and UPS display";
default = false;
type = lib.types.bool;
};
};
}