From 69f0ab4533a6b233dca1193ee558de8994df1ffb Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 12 Nov 2026 00:02:06 +0000 Subject: populate: add prune Populate never removes what it delivered for sources that aren't configured anymore. For flake and flake-input sources, that keeps their garbage collector roots, so what they delivered never gets collected. With prune, populate removes everything from the target directory that isn't a source, except .populate, after syncing the sources. It also removes the roots in .populate/flake-inputs of names that aren't flake or flake-input sources, and uploads left behind by interrupted runs. Pruning is opt-in, because the target directory may contain other things worth keeping, or be shared by deployments with different sources. writeDeploy, writeTest, and writeCommand pass prune on to populate. A feature scenario deploys sources, deploys fewer without pruning, then with pruning, and checks that the removed sources' entries and roots are gone, while the remaining sources and .populate stay. --- pkgs/populate/default.nix | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) (limited to 'pkgs/populate/default.nix') diff --git a/pkgs/populate/default.nix b/pkgs/populate/default.nix index 5472b5d..553183e 100644 --- a/pkgs/populate/default.nix +++ b/pkgs/populate/default.nix @@ -169,6 +169,44 @@ let fi ''; + # Removes entries of the target directory that aren't sources, garbage + # collector roots of flake inputs of sources that aren't flake or + # flake-input sources, and uploads left behind by interrupted runs. + prune' = target: sources: let + pattern = names: concatMapStringsSep "|" quote names; + flakeNames = attrNames (filterAttrs + (_: source: elem source.type [ "flake" "flake-input" ]) + sources); + in runShell target /* sh */ '' + set -efu + cd ${quote target.path} + ls -A | while read -r name; do + case $name in + ${pattern (attrNames sources ++ [ ".populate" ])}) ;; + *) + >&2 printf 'pruning %s\n' ${quote target.path}/"$name" + rm -fR ./"$name" + ;; + esac + done + roots=.populate/flake-inputs + if test -d "$roots"; then + ls -A "$roots" | while read -r name; do + case $name in + ${optionalString (flakeNames != []) "${pattern flakeNames}) ;;"} + *) rm -fR "$roots"/"$name" ;; + esac + done + if test -z "$(ls -A "$roots")"; then + rmdir "$roots" + fi + fi + if test -e .populate/flake-uploads; then + chmod -R u+w .populate/flake-uploads + rm -fR .populate/flake-uploads + fi + ''; + pop.derivation = target: source: runShell target /* sh */ '' nix-build -E ${quote source.text} -o ${quote target.path} >&2 ''; @@ -761,7 +799,7 @@ let in -{ backup ? false, force ? false, source, target }: +{ backup ? false, force ? false, prune ? false, source, target }: writers.writeDash "populate.${target.host}" '' set -efu @@ -776,6 +814,7 @@ writers.writeDash "populate.${target.host}" '' ${optionalString backup (do-backup { inherit target; })} ${concatStringsSep "\n" (mapAttrsToList (populate target) source)} ${flakes target source} + ${optionalString prune (prune' target source)} # Stop the ssh master, if any source started one. if ${ssh' target} -q -O check ${quote target.host} 2>/dev/null; then -- cgit v1.3.1