home-manager/modules/services/podman-linux/install-quadlet.nix
Nicholas Hassan 14bbb67c08
podman: add new module 'podman'
Adds a new Podman module for creating user containers and networks as
systemd services. These are installed to the user's XDG_CONFIG/systemd/user directory.
2024-05-12 22:30:17 +09:30

170 lines
5.5 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
quadletActivationCleanupScript = ''
resourceManifest=()
# Define VERBOSE_ENABLED as a function
VERBOSE_ENABLED() {
if [[ -n "''${VERBOSE:-}" ]]; then
return 0
else
return 1
fi
}
# Function to fill resourceManifest from the manifest file
function loadManifest {
local manifestFile="$1"
VERBOSE_ENABLED && echo "Loading manifest from $manifestFile..."
IFS=$'\n' read -r -d "" -a resourceManifest <<< "$(cat "$manifestFile")" || true
}
function isResourceInManifest {
local resource="$1"
for manifestEntry in "''${resourceManifest[@]}"; do
if [ "$resource" = "$manifestEntry" ]; then
return 0 # Resource found in manifest
fi
done
return 1 # Resource not found in manifest
}
function removeContainer {
echo "Removing orphaned container: $1"
if [[ -n "''${DRY_RUN:-}" ]]; then
echo "Would run podman stop $1"
echo "Would run podman $resourceType rm -f $1"
else
${config.services.podman.package}/bin/podman stop "$1"
${config.services.podman.package}/bin/podman $resourceType rm -f "$1"
fi
}
function removeNetwork {
echo "Removing orphaned network: $1"
if [[ -n "''${DRY_RUN:-}" ]]; then
echo "Would run podman network rm $1"
else
if ! ${config.services.podman.package}/bin/podman network rm "$1"; then
echo "Failed to remove network $1. Is it still in use by a container?"
return 1
fi
fi
}
function cleanup {
local resourceType=$1
local manifestFile="${config.xdg.configHome}/podman/$2"
local extraListCommands="''${3:-}"
[[ $resourceType = "container" ]] && extraListCommands+=" -a"
VERBOSE_ENABLED && echo "Cleaning up ''${resourceType}s not in manifest..."
loadManifest "$manifestFile"
formatString="{{.Name}}"
[[ $resourceType = "container" ]] && formatString="{{.Names}}"
# Capture the output of the podman command to a variable
local listOutput=$(${config.services.podman.package}/bin/podman $resourceType ls $extraListCommands --filter 'label=nix.home-manager.managed=true' --format "$formatString")
IFS=$'\n' read -r -d "" -a podmanResources <<< "$listOutput" || true
# Check if the array is populated and iterate over it
if [ ''${#resourceManifest[@]} -eq 0 ]; then
VERBOSE_ENABLED && echo "No ''${resourceType}s available to process."
else
for resource in "''${podmanResources[@]}"; do
if ! isResourceInManifest "$resource"; then
[[ $resourceType = "container" ]] && removeContainer "$resource"
[[ $resourceType = "network" ]] && removeNetwork "$resource"
else
if VERBOSE_ENABLED; then
echo "Keeping managed $resourceType: $resource"
fi
fi
done
fi
}
# Cleanup containers
cleanup "container" "containers.manifest"
# Cleanup networks
cleanup "network" "networks.manifest"
'';
# derivation to build a single Podman quadlet, outputting its systemd unit files
buildPodmanQuadlet = quadlet: pkgs.stdenv.mkDerivation {
name = "home-${quadlet.unitType}-${quadlet.serviceName}";
buildInputs = [ config.services.podman.package ];
dontUnpack = true;
buildPhase = ''
mkdir $out
# Directory for the quadlet file
mkdir -p $out/quadlets
# Directory for systemd unit files
mkdir -p $out/units
# Write the quadlet file
echo -n "${quadlet.source}" > $out/quadlets/${quadlet.serviceName}.${quadlet.unitType}
# Generate systemd unit file/s from the quadlet file
export QUADLET_UNIT_DIRS=$out/quadlets
${config.services.podman.package}/lib/systemd/user-generators/podman-user-generator $out/units
'';
passthru = {
outPath = self.out;
quadletData = quadlet;
};
};
# Create a derivation for each quadlet spec
builtQuadlets = map buildPodmanQuadlet config.internal.podman-quadlet-definitions;
accumulateUnitFiles = prefix: path: quadlet: let
entries = builtins.readDir path;
processEntry = name: type:
let
newPath = "${path}/${name}";
newPrefix = prefix + (if prefix == "" then "" else "/") + name;
in
if type == "directory" then accumulateUnitFiles newPrefix newPath quadlet
else [{
key = newPrefix;
value = { path = newPath; parentQuadlet = quadlet; };
}];
in flatten (map (name: processEntry name (getAttr name entries)) (attrNames entries));
allUnitFiles = concatMap (builtQuadlet: accumulateUnitFiles "" "${builtQuadlet.outPath}/units" builtQuadlet.quadletData ) builtQuadlets;
# we're doing this because the home-manager recursive file linking implementation can't
# merge from multiple sources. so we link each file explicitly, which is fine for all unique files
generateSystemdFileLinks = files: listToAttrs (map (unitFile: {
name = "${config.xdg.configHome}/systemd/user/${unitFile.key}";
value = {
source = unitFile.value.path;
};
}) files);
in {
imports = [
./options.nix
];
config = {
home.file = generateSystemdFileLinks allUnitFiles;
# if the length of builtQuadlets is 0, then we don't need register the activation script
home.activation.podmanQuadletCleanup = lib.mkIf (lib.length builtQuadlets >= 1) (lib.hm.dag.entryAfter ["reloadSystemd"] quadletActivationCleanupScript);
};
}