
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.
170 lines
5.5 KiB
Nix
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);
|
|
};
|
|
}
|