Skip to content

Instantly share code, notes, and snippets.

@andir
Last active March 30, 2026 22:47
Show Gist options
  • Select an option

  • Save andir/48c2969b82d3eeaea6f3f0bc8cc3e8d2 to your computer and use it in GitHub Desktop.

Select an option

Save andir/48c2969b82d3eeaea6f3f0bc8cc3e8d2 to your computer and use it in GitHub Desktop.
{
pkgs ? import <nixpkgs> { },
lib ? pkgs.lib,
}:
with (import <nixpkgs/nixos/lib/qemu-flags.nix> { inherit pkgs; });
{
mkVM =
{
name,
config ? { },
memory ? 512,
cores ? 2,
statefulDisks ? false,
networking ? false,
monitorPath ? "/run/vm-${name}/monitor.socket",
hostPath ? "/var/lib/vm-${name}",
}:
rec {
inherit hostPath;
nixos = import <nixpkgs/nixos/lib/eval-config.nix> {
modules = [
(
{
pkgs,
lib,
config,
...
}:
{
fileSystems."/" = {
device = "tmpfs";
fsType = "tmpfs";
options = [ "mode=0755" ];
};
fileSystems."/nix/store" = {
device = "store";
fsType = "9p";
options = [
"trans=virtio"
"version=9p2000.L"
"cache=loose"
];
neededForBoot = true;
};
fileSystems."/var/" = {
device = "state";
fsType = "9p";
options = [
"trans=virtio"
"version=9p2000.L"
"cache=loose"
];
neededForBoot = true;
};
swapDevices = [ ];
networking.wireless.enable = lib.mkForce false;
networking.connman.enable = false;
boot.loader.grub.enable = false;
boot.kernelParams = [ "boot.shell_on_fail" ];
boot.initrd.availableKernelModules = [
"virtio_net"
"virtio_pci"
"virtio_mmio"
"virtio_blk"
"virtio_scsi"
"9p"
"9pnet_virtio"
];
networking.dhcpcd.extraConfig = "noarp";
networking.usePredictableInterfaceNames = false;
system.requiredKernelConfig =
with config.lib.kernelConfig;
[
(isEnabled "VIRTIO_BLK")
(isEnabled "VIRTIO_PCI")
(isEnabled "VIRTIO_NET")
(isEnabled "EXT4_FS")
(isYes "BLK_DEV")
(isYes "PCI")
(isYes "EXPERIMENTAL")
(isYes "NETDEVICES")
(isYes "NET_CORE")
(isYes "INET")
(isYes "NETWORK_FILESYSTEMS")
]
++ optional (!cfg.graphics) [
(isYes "SERIAL_8250_CONSOLE")
(isYes "SERIAL_8250")
];
}
)
(import <nixpkgs/nixos/modules/profiles/minimal.nix>)
# (import <nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>)
(
{ pkgs, lib, ... }:
{
networking.hostName = lib.mkDefault name;
}
)
config
];
};
toplevel = nixos.config.system.build.toplevel;
script = pkgs.writeScript "run-${name}" ''
#! ${pkgs.runtimeShell}
ls -la /dev/
test -e /dev/kvm || exit 1
${qemuBinary qemu} \
-name "${name}" \
-cpu host \
-m ${toString memory} \
-smp ${toString cores} \
-device virtio-rng-pci \
-virtfs local,path=/nix/store,security_model=none,mount_tag=store \
-virtfs local,path=${hostPath},security_model=mapped,mount_tag=state \
-kernel ${toplevel}/kernel \
-initrd ${toplevel}/initrd \
-append "${lib.concatStringsSep " " kernelParams}" \
-monitor unix:${monitorPath},server,nowait \
-device e1000,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::5555-:22 \
-nographic
'';
hostname = nixos.config.networking.hostName;
qemu = pkgs.qemu_kvm;
closure = pkgs.closureInfo { rootPaths = [ toplevel ]; };
closurePaths =
let
exportedGraph = builtins.fromJSON (
builtins.readFile (
pkgs.runCommand "closure.json" {
exportReferencesGraph.closure = [
toplevel
script
];
__structuredAttrs = true;
PATH = "${pkgs.coreutils}/bin";
builder = builtins.toFile "bulider" ''
. .attrs.sh
cp .attrs.json ''${outputs[out]}
'';
} ""
)
);
in
map (e: e.path) exportedGraph.closure;
kernelParams = nixos.config.boot.kernelParams ++ [
"init=${toplevel}/init"
"regInfo=${closure}/registration"
"console=ttyS0"
];
service = {
serviceConfig = {
DynamicUser = true;
ExecStart = script;
BindReadOnlyPaths = lib.concatStringsSep " " (closurePaths);
MountAPIVFS = true;
TemporaryFileSystem = "/:ro";
BindPaths = "/dev/kvm";
RuntimeDirectory = "vm-${name}";
NoNewPrivileges = true;
# StateDirectory = "vm-${name}";
};
};
};
}
{ pkgs, ... }:
let
inherit (pkgs.callPackage ./mk-vm.nix { }) mkVM;
user = "xxxx";
name = "backups-${user}";
vm = mkVM {
inherit name;
cores = 1;
memory = 1024;
monitorPath = "/run/vm-${name}/monitor.socket";
hostPath = "/var/lib/vm-xxxxx";
config = (
{ pkgs, ... }:
{
services.openssh = {
enable = true;
hostKeys = [
{
path = "/var/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
};
systemd.services.sshd = {
after = [ "systemd-tmpfiles-setup.service" ];
unitConfig.RequiresMountsFor = "/var/lib";
};
users.users.root.initialHashedPassword = "";
users.users.andi.isNormalUser = true;
users.users.andi.openssh.authorizedKeys.keys = [
];
users.users.${user} = {
isNormalUser = true;
};
boot.kernelParams = [ "systemd.log_level=debug" ];
systemd.tmpfiles.rules = [
"d /var/data 0700 root - - -"
"d /var/data/${user} 0700 ${user} - - -"
"d /var/ssh 0700 root root -"
];
environment.systemPackages = with pkgs; [
borgbackup
ncdu
vim
pciutils
];
users.motd = ''
Put your data into /var/data or it might be gone after the next system update.
'';
}
);
};
in
{
users.users.grahamc = {
isSystemUser = true;
};
systemd.tmpfiles.rules = [
"d '/run/backup-vms/${name}' 0700 ${user} - - -"
"d '/var/lib/vm-grahamc' 0700 ${user} - - -"
];
networking.firewall.allowedTCPPorts = [ 5555 ];
systemd.services.backup-vm = {
serviceConfig = vm.service.serviceConfig // {
StateDirectory = vm.hostPath;
BindPaths = vm.hostPath;
DynamicUser = false;
User = "grahamc";
};
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment