# Save this as podman-pod.nix { config, lib, pkgs, ... }: with lib; let cfg = config.services.podmanPods; podOptions = { name, config, ... }: { options = { name = mkOption { type = types.str; description = "Name of the pod"; }; ports = mkOption { type = types.listOf types.str; default = [ ]; description = "List of port mappings (e.g. ['8080:80'])"; }; containers = mkOption { type = types.attrsOf (types.submodule { options = { image = mkOption { type = types.str; description = "Docker image for the container"; }; extraOptions = mkOption { type = types.listOf types.str; default = [ ]; description = "Additional options for the container"; }; }; }); default = { }; description = "Containers to run in the pod"; }; }; }; createPodScript = name: podDef: let podDefinitionString = builtins.toJSON { inherit (podDef) ports; }; in pkgs.writeScript "manage-pod-${name}.sh" '' #!/usr/bin/env nix-shell #!nix-shell -i bash -p htop curl set -e POD_NAME="${name}" POD_DEFINITION='${podDefinitionString}' create_pod() { podman pod create --name "$POD_NAME" \ ${concatStringsSep " " (map (port: "--publish ${port}") podDef.ports)} } if podman pod exists "$POD_NAME"; then CURRENT_CONFIG=$(podman pod inspect "$POD_NAME" | jq -c '.[0] | {ports: [.PortMappings[].HostPort | tostring + ":" + (.ContainerPort | tostring)]}') if [ "$CURRENT_CONFIG" != "$POD_DEFINITION" ]; then echo "Pod configuration has changed. Recreating pod..." podman pod rm -f "$POD_NAME" create_pod else echo "Pod configuration unchanged." fi else echo "Pod does not exist. Creating..." create_pod fi ''; in { options.services.podmanPods = mkOption { type = types.attrsOf (types.submodule podOptions); default = { }; description = "Podman pods to create"; }; config = mkIf (cfg != { }) { virtualisation.podman.enable = true; virtualisation.oci-containers.backend = "podman"; environment.systemPackages = [ pkgs.jq ]; systemd.services = mapAttrs' (name: podDef: nameValuePair "podman-pod-${name}" { description = "Manage Podman pod: ${name}"; serviceConfig = { Type = "oneshot"; ExecStart = "${createPodScript name podDef}"; }; path = [ pkgs.podman ]; wantedBy = [ "multi-user.target" ]; } ) cfg // mapAttrs' (podName: podDef: nameValuePair "podman-pod-${podName}" { after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; } ) cfg // mapAttrs' (podName: podDef: nameValuePair "podman-${podName}" { after = [ "podman-pod-${podName}.service" ]; requires = [ "podman-pod-${podName}.service" ]; partOf = [ "podman-pod-${podName}.service" ]; } ) (flattenAttrs (mapAttrsToList (podName: podDef: mapAttrs' (containerName: containerDef: nameValuePair "${podName}-${containerName}" containerDef ) podDef.containers ) cfg)); virtualisation.oci-containers.containers = flatten (mapAttrsToList (podName: podDef: mapAttrsToList (containerName: containerDef: nameValuePair "${podName}-${containerName}" ({ inherit (containerDef) image; extraOptions = [ "--pod=${podName}" ] ++ containerDef.extraOptions; }) ) podDef.containers ) cfg); networking.firewall.allowedTCPPorts = flatten (mapAttrsToList (name: podDef: map (portMapping: lib.toInt (lib.head (lib.splitString ":" portMapping))) podDef.ports ) cfg); # systemd.services = mapAttrs' # (podName: podDef: # nameValuePair "podman-pod-${podName}" { # after = [ "network.target" ]; # wantedBy = [ "multi-user.target" ]; # } # ) # cfg // # mapAttrs' # (podName: podDef: # nameValuePair "podman-${podName}" { # after = [ "podman-pod-${podName}.service" ]; # requires = [ "podman-pod-${podName}.service" ]; # partOf = [ "podman-pod-${podName}.service" ]; # } # ) # (flattenAttrs (mapAttrsToList # (podName: podDef: # mapAttrs' # (containerName: containerDef: # nameValuePair "${podName}-${containerName}" containerDef # ) # podDef.containers # ) # cfg)); }; }