vms: disks.os.persistMode (copy|backing) + a GC-root for the backing chain
persistMode=backing seeds the persistent OS disk as a thin qcow2 overlay (`qemu-img create -b <storeImage>`) instead of a full cp, so many VMs share one base image and each holds only its deltas. Because the overlay reads through a store path that nix does not otherwise pin (the disk lives outside the store), and because even a cp'd Windows disk backs onto the store chain, a per-VM oneshot `vm.vmix-gcroot@<name>` reads the overlay's ACTUAL backing_file at boot (not the config's current image, which drifts after a rebuild) and symlinks it under /nix/var/nix/gcroots. It runs before the VM service and outside its ProtectSystem sandbox. Fires whenever the OS disk is persistent, covering copy too. Default stays copy, so existing VMs are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117qMyjpuXsjpVAcpJbFD8g
This commit is contained in:
parent
60013006d6
commit
c40f4460e3
2 changed files with 47 additions and 2 deletions
|
|
@ -125,13 +125,34 @@ let
|
|||
if [ ! -f "$PERSIST_PATH" ]; then
|
||||
echo "Seeding persistent disk from store image..."
|
||||
mkdir -p "$(dirname "$PERSIST_PATH")"
|
||||
cp --no-preserve=mode "${toString storeImage}" "$PERSIST_PATH"
|
||||
${if vmCfg.disks.os.persistMode == "backing"
|
||||
then ''qemu-img create -f qcow2 -F qcow2 -b "${toString storeImage}" "$PERSIST_PATH"''
|
||||
else ''cp --no-preserve=mode "${toString storeImage}" "$PERSIST_PATH"''}
|
||||
chmod 600 "$PERSIST_PATH"
|
||||
fi
|
||||
'';
|
||||
|
||||
persistExecStartPre = lib.optional (hasOsDisk && vmCfg.disks.os.persist) seedPersistentDiskScript;
|
||||
|
||||
# A GC root pinning the OS overlay's ACTUAL backing store path, so
|
||||
# nix-collect-garbage cannot delete the store image the disk reads through.
|
||||
gcrootLink = "/nix/var/nix/gcroots/vmix-${vmCfg.name}-osbacking";
|
||||
gcrootScript = pkgs.writeShellScript "${vmCfg.name}-gcroot-vmix" ''
|
||||
# Read the live overlay's backing (not the config's current image, which
|
||||
# drifts to a new store path after a rebuild while the overlay keeps
|
||||
# backing the old one). Pinning the top of the chain transitively keeps
|
||||
# the whole chain -- qcow2 backing_file paths are registered nix refs.
|
||||
BACK=""
|
||||
if [ -f "${vmCfg.disks.os.persistPath}" ]; then
|
||||
BACK=$(qemu-img info "${vmCfg.disks.os.persistPath}" 2>/dev/null | awk '/^backing file:/ {print $3; exit}')
|
||||
fi
|
||||
[ -z "$BACK" ] && BACK="${toString storeImage}"
|
||||
if [ -n "$BACK" ]; then
|
||||
mkdir -p /nix/var/nix/gcroots
|
||||
ln -sfn "$BACK" "${gcrootLink}"
|
||||
fi
|
||||
'';
|
||||
|
||||
# QEMU expects single-letter boot codes (e.g. c,d,n), while vmix uses readable names.
|
||||
bootOrderQemu =
|
||||
let
|
||||
|
|
@ -254,7 +275,8 @@ let
|
|||
"vm.vmix@${vmCfg.name}" = rec {
|
||||
bindsTo = [ "net.vmix@${spaceName}.target" ] ++ lib.optional (allMacvtaps != []) "macvtaps.vm.vmix@${vmCfg.name}.service";
|
||||
unitConfig.JoinsNamespaceOf = "ns.net.vmix@${spaceName}.service";
|
||||
after = bindsTo;
|
||||
after = bindsTo ++ lib.optional (hasOsDisk && vmCfg.disks.os.persist) "vm.vmix-gcroot@${vmCfg.name}.service";
|
||||
wants = lib.optional (hasOsDisk && vmCfg.disks.os.persist) "vm.vmix-gcroot@${vmCfg.name}.service";
|
||||
path = with pkgs; [ iproute2 qemu gawk coreutils ];
|
||||
serviceConfig = {
|
||||
ExecStartPre = persistExecStartPre ++ [ createTapsforLansScript ];
|
||||
|
|
@ -284,6 +306,18 @@ let
|
|||
ExecStop = deleteMacvTapsScript;
|
||||
};
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (cfg.enable && hasOsDisk && vmCfg.disks.os.persist) {
|
||||
"vm.vmix-gcroot@${vmCfg.name}" = {
|
||||
before = [ "vm.vmix@${vmCfg.name}.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [ qemu coreutils ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = gcrootScript;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
vmServices = concatMapAttrs (spaceName: namespaceCfg: (concatMapAttrs (mkServices4aVMInNamespace spaceName) namespaceCfg.vms)) vmixCfg.namespaces;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue