mirror of
https://github.com/nix-community/home-manager
synced 2024-11-26 21:19:45 +01:00
podman: add module
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.
This commit is contained in:
parent
8ca921e5a8
commit
1743615b61
21 changed files with 1327 additions and 0 deletions
|
@ -43,6 +43,12 @@
|
||||||
github = "Avimitin";
|
github = "Avimitin";
|
||||||
githubId = 30021675;
|
githubId = 30021675;
|
||||||
};
|
};
|
||||||
|
bamhm182 = {
|
||||||
|
name = "bamhm182";
|
||||||
|
email = "bamhm182@gmail.com";
|
||||||
|
github = "bamhm182";
|
||||||
|
githubId = 920269;
|
||||||
|
};
|
||||||
blmhemu = {
|
blmhemu = {
|
||||||
name = "blmhemu";
|
name = "blmhemu";
|
||||||
email = "19410501+blmhemu@users.noreply.github.com";
|
email = "19410501+blmhemu@users.noreply.github.com";
|
||||||
|
@ -288,6 +294,16 @@
|
||||||
github = "NitroSniper";
|
github = "NitroSniper";
|
||||||
githubId = 44097331;
|
githubId = 44097331;
|
||||||
};
|
};
|
||||||
|
n-hass = {
|
||||||
|
name = "Nicholas Hassan";
|
||||||
|
email = "nick@hassan.host";
|
||||||
|
github = "n-hass";
|
||||||
|
githubId = 72363381;
|
||||||
|
keys = [{
|
||||||
|
longkeyid = "rsa4096/0xFC95AB946A781EE7";
|
||||||
|
fingerprint = "FDEE 6116 DBA7 8840 7323 4466 A371 5973 2728 A6A6";
|
||||||
|
}];
|
||||||
|
};
|
||||||
seylerius = {
|
seylerius = {
|
||||||
email = "sable@seyleri.us";
|
email = "sable@seyleri.us";
|
||||||
name = "Sable Seyler";
|
name = "Sable Seyler";
|
||||||
|
|
|
@ -1813,6 +1813,20 @@ in {
|
||||||
systems" section in the Home Manager mantual for more.
|
systems" section in the Home Manager mantual for more.
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
time = "2024-11-01T19:44:59+00:00";
|
||||||
|
condition = hostPlatform.isLinux;
|
||||||
|
message = ''
|
||||||
|
A new module is available: 'services.podman'.
|
||||||
|
|
||||||
|
Podman is a daemonless container engine that lets you manage
|
||||||
|
containers, pods, and images.
|
||||||
|
|
||||||
|
This Home Manager module allows you to define containers that will run
|
||||||
|
as systemd services.
|
||||||
|
'';
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,6 +352,7 @@ let
|
||||||
./services/plan9port.nix
|
./services/plan9port.nix
|
||||||
./services/playerctld.nix
|
./services/playerctld.nix
|
||||||
./services/plex-mpv-shim.nix
|
./services/plex-mpv-shim.nix
|
||||||
|
./services/podman-linux
|
||||||
./services/polybar.nix
|
./services/polybar.nix
|
||||||
./services/poweralertd.nix
|
./services/poweralertd.nix
|
||||||
./services/psd.nix
|
./services/psd.nix
|
||||||
|
|
99
modules/services/podman-linux/activation.nix
Normal file
99
modules/services/podman-linux/activation.nix
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
{ config, podman-lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
cleanup = ''
|
||||||
|
PATH=$PATH:${podman-lib.newuidmapPaths}
|
||||||
|
export VERBOSE=true
|
||||||
|
|
||||||
|
DRYRUN_ENABLED() {
|
||||||
|
return $([ -n "''${DRY_RUN:-}" ] && echo 0 || echo 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
VERBOSE_ENABLED() {
|
||||||
|
return $([ -n "''${VERBOSE:-}" ] && echo 0 || echo 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local resourceType=$1
|
||||||
|
local manifestFile="${config.xdg.configHome}/podman/$2"
|
||||||
|
local extraListCommands="''${3:-}"
|
||||||
|
[[ $resourceType = "container" ]] && extraListCommands+=" -a"
|
||||||
|
|
||||||
|
[ ! -f "$manifestFile" ] && VERBOSE_ENABLED && echo "Manifest does not exist: $manifestFile" && return 0
|
||||||
|
|
||||||
|
VERBOSE_ENABLED && echo "Cleaning up ''${resourceType}s not in manifest..." || true
|
||||||
|
|
||||||
|
loadManifest "$manifestFile"
|
||||||
|
|
||||||
|
formatString="{{.Name}}"
|
||||||
|
[[ $resourceType = "container" ]] && formatString="{{.Names}}"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if [ ''${#podmanResources[@]} -eq 0 ]; then
|
||||||
|
VERBOSE_ENABLED && echo "No ''${resourceType}s available to process." || true
|
||||||
|
else
|
||||||
|
for resource in "''${podmanResources[@]}"; do
|
||||||
|
if ! isResourceInManifest "$resource"; then
|
||||||
|
removeResource "$resourceType" "$resource"
|
||||||
|
else
|
||||||
|
VERBOSE_ENABLED && echo "Keeping managed $resourceType: $resource" || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
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 to fill resourceManifest from the manifest file
|
||||||
|
loadManifest() {
|
||||||
|
local manifestFile="$1"
|
||||||
|
VERBOSE_ENABLED && echo "Loading manifest from $manifestFile..." || true
|
||||||
|
IFS=$'\n' read -r -d "" -a resourceManifest <<< "$(cat "$manifestFile")" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
removeResource() {
|
||||||
|
local resourceType="$1"
|
||||||
|
local resource="$2"
|
||||||
|
echo "Removing orphaned $resourceType: $resource"
|
||||||
|
commands=()
|
||||||
|
case "$resourceType" in
|
||||||
|
"container")
|
||||||
|
commands+="${config.services.podman.package}/bin/podman $resourceType stop $resource"
|
||||||
|
commands+="${config.services.podman.package}/bin/podman $resourceType rm -f $resource"
|
||||||
|
;;
|
||||||
|
"network")
|
||||||
|
commands+="${config.services.podman.package}/bin/podman $resourceType rm $resource"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
for command in "''${commands[@]}"; do
|
||||||
|
command=$(echo $command | tr -d ';&|`')
|
||||||
|
DRYRUN_ENABLED && echo "Would run: $command" && continue || true
|
||||||
|
VERBOSE_ENABLED && echo "Running: $command" || true
|
||||||
|
if [[ "$(eval "$command")" != "$resource" ]]; then
|
||||||
|
echo -e "\tCommand failed: ''${command}"
|
||||||
|
usedByContainers=$(${config.services.podman.package}/bin/podman container ls -a --filter "$resourceType=$resource" --format "{{.Names}}")
|
||||||
|
echo -e "\t$resource in use by containers: $usedByContainers"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceManifest=()
|
||||||
|
[[ "$@" == *"--verbose"* ]] && VERBOSE="true"
|
||||||
|
[[ "$@" == *"--dry-run"* ]] && DRY_RUN="true"
|
||||||
|
|
||||||
|
for type in "container" "network"; do
|
||||||
|
cleanup "$type" "''${type}s.manifest"
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
}
|
313
modules/services/podman-linux/containers.nix
Normal file
313
modules/services/podman-linux/containers.nix
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.podman;
|
||||||
|
|
||||||
|
podman-lib = import ./podman-lib.nix { inherit lib config; };
|
||||||
|
|
||||||
|
createQuadletSource = name: containerDef:
|
||||||
|
let
|
||||||
|
mapHmNetworks = network:
|
||||||
|
if builtins.hasAttr network cfg.networks then
|
||||||
|
"podman-${network}-network.service"
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
|
||||||
|
finalConfig = let
|
||||||
|
managedNetworks = if lib.isList containerDef.network then
|
||||||
|
map mapHmNetworks containerDef.network
|
||||||
|
else if containerDef.network != null then
|
||||||
|
map mapHmNetworks [ containerDef.network ]
|
||||||
|
else
|
||||||
|
[ ];
|
||||||
|
in (podman-lib.deepMerge {
|
||||||
|
Container = {
|
||||||
|
AddCapability = containerDef.addCapabilities;
|
||||||
|
AddDevice = containerDef.devices;
|
||||||
|
AutoUpdate = containerDef.autoUpdate;
|
||||||
|
ContainerName = name;
|
||||||
|
DropCapability = containerDef.dropCapabilities;
|
||||||
|
Entrypoint = containerDef.entrypoint;
|
||||||
|
Environment = containerDef.environment;
|
||||||
|
EnvironmentFile = containerDef.environmentFile;
|
||||||
|
Exec = containerDef.exec;
|
||||||
|
Group = containerDef.group;
|
||||||
|
Image = containerDef.image;
|
||||||
|
IP = containerDef.ip4;
|
||||||
|
IP6 = containerDef.ip6;
|
||||||
|
Label =
|
||||||
|
(containerDef.labels // { "nix.home-manager.managed" = true; });
|
||||||
|
Network = containerDef.network;
|
||||||
|
NetworkAlias = containerDef.networkAlias;
|
||||||
|
PodmanArgs = containerDef.extraPodmanArgs;
|
||||||
|
PublishPort = containerDef.ports;
|
||||||
|
UserNS = containerDef.userNS;
|
||||||
|
User = containerDef.user;
|
||||||
|
Volume = containerDef.volumes;
|
||||||
|
};
|
||||||
|
Install = {
|
||||||
|
WantedBy = (if containerDef.autoStart then [
|
||||||
|
"default.target"
|
||||||
|
"multi-user.target"
|
||||||
|
] else
|
||||||
|
[ ]);
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
Environment = {
|
||||||
|
PATH = (builtins.concatStringsSep ":" [
|
||||||
|
"/run/wrappers/bin"
|
||||||
|
"/run/current-system/sw/bin"
|
||||||
|
"${config.home.homeDirectory}/.nix-profile/bin"
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
Restart = "always";
|
||||||
|
TimeoutStopSec = 30;
|
||||||
|
};
|
||||||
|
Unit = {
|
||||||
|
After = [ "network.target" ] ++ managedNetworks;
|
||||||
|
Requires = managedNetworks;
|
||||||
|
Description = (if (builtins.isString containerDef.description) then
|
||||||
|
containerDef.description
|
||||||
|
else
|
||||||
|
"Service for container ${name}");
|
||||||
|
};
|
||||||
|
} containerDef.extraConfig);
|
||||||
|
in ''
|
||||||
|
# Automatically generated by home-manager podman container configuration
|
||||||
|
# DO NOT EDIT THIS FILE DIRECTLY
|
||||||
|
#
|
||||||
|
# ${name}.container
|
||||||
|
${podman-lib.toQuadletIni finalConfig}
|
||||||
|
'';
|
||||||
|
|
||||||
|
toQuadletInternal = name: containerDef: {
|
||||||
|
assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig;
|
||||||
|
resourceType = "container";
|
||||||
|
serviceName =
|
||||||
|
"podman-${name}"; # quadlet service name: 'podman-<name>.service'
|
||||||
|
source =
|
||||||
|
podman-lib.removeBlankLines (createQuadletSource name containerDef);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Define the container user type as the user interface
|
||||||
|
containerDefinitionType = types.submodule {
|
||||||
|
options = {
|
||||||
|
|
||||||
|
addCapabilities = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "CAP_DAC_OVERRIDE" "CAP_IPC_OWNER" ];
|
||||||
|
description = "The capabilities to add to the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
autoStart = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to start the container on boot (requires user lingering).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
autoUpdate = mkOption {
|
||||||
|
type = types.enum [ null "registry" "local" ];
|
||||||
|
default = null;
|
||||||
|
example = "registry";
|
||||||
|
description = "The autoupdate policy for the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
description = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
example = "My Container";
|
||||||
|
description = "The description of the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
devices = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "/dev/<host>:/dev/<container>" ];
|
||||||
|
description = "The devices to mount into the container";
|
||||||
|
};
|
||||||
|
|
||||||
|
dropCapabilities = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "CAP_DAC_OVERRIDE" "CAP_IPC_OWNER" ];
|
||||||
|
description = "The capabilities to drop from the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
entrypoint = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
example = "/foo.sh";
|
||||||
|
description = "The container entrypoint.";
|
||||||
|
};
|
||||||
|
|
||||||
|
environment = mkOption {
|
||||||
|
type = podman-lib.primitiveAttrs;
|
||||||
|
default = { };
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
VAR1 = "0:100";
|
||||||
|
VAR2 = true;
|
||||||
|
VAR3 = 5;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = "Environment variables to set in the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
environmentFile = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "/etc/environment" "/etc/other-env" ];
|
||||||
|
description = ''
|
||||||
|
Paths to files containing container environment variables.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
exec = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
example = "sleep inf";
|
||||||
|
description = "The command to run after the container start.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraPodmanArgs = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [
|
||||||
|
"--security-opt=no-new-privileges"
|
||||||
|
"--security-opt=seccomp=unconfined"
|
||||||
|
];
|
||||||
|
description = "Extra arguments to pass to the podman run command.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = podman-lib.extraConfigType;
|
||||||
|
default = { };
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
Container = {
|
||||||
|
User = 1000;
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
TimeoutStartSec = 15;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
INI sections and values to populate the Container Quadlet.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
type = with types; nullOr (either int str);
|
||||||
|
default = null;
|
||||||
|
description = "The group ID inside the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
image = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "registry.access.redhat.com/ubi9-minimal:latest";
|
||||||
|
description = "The container image.";
|
||||||
|
};
|
||||||
|
|
||||||
|
ip4 = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = "Set an IPv4 address for the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
ip6 = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = "Set an IPv6 address for the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
labels = mkOption {
|
||||||
|
type = with types; attrsOf str;
|
||||||
|
default = { };
|
||||||
|
example = {
|
||||||
|
app = "myapp";
|
||||||
|
some-label = "somelabel";
|
||||||
|
};
|
||||||
|
description = "The labels to apply to the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
network = mkOption {
|
||||||
|
type = with types; either str (listOf str);
|
||||||
|
default = [ ];
|
||||||
|
apply = value: if isString value then [ value ] else value;
|
||||||
|
example = literalMD ''
|
||||||
|
`"host"`
|
||||||
|
or
|
||||||
|
`"bridge_network_1"`
|
||||||
|
or
|
||||||
|
`[ "bridge_network_1" "bridge_network_2" ]`
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
The network mode or network/s to connect the container to. Equivalent
|
||||||
|
to `podman run --network=<option>`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
networkAlias = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "mycontainer" "web" ];
|
||||||
|
description = "Network aliases for the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
ports = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "8080:80" "8443:443" ];
|
||||||
|
description = "A mapping of ports between host and container";
|
||||||
|
};
|
||||||
|
|
||||||
|
userNS = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = "Use a user namespace for the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = with types; nullOr (either int str);
|
||||||
|
default = null;
|
||||||
|
description = "The user ID inside the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
volumes = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "/tmp:/tmp" "/var/run/test.secret:/etc/secret:ro" ];
|
||||||
|
description = "The volumes to mount into the container.";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in {
|
||||||
|
|
||||||
|
imports = [ ./options.nix ];
|
||||||
|
|
||||||
|
options.services.podman.containers = mkOption {
|
||||||
|
type = types.attrsOf containerDefinitionType;
|
||||||
|
default = { };
|
||||||
|
description = "Defines Podman container quadlet configurations.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config =
|
||||||
|
let containerQuadlets = mapAttrsToList toQuadletInternal cfg.containers;
|
||||||
|
in mkIf cfg.enable {
|
||||||
|
services.podman.internal.quadletDefinitions = containerQuadlets;
|
||||||
|
assertions =
|
||||||
|
flatten (map (container: container.assertions) containerQuadlets);
|
||||||
|
|
||||||
|
# manifest file
|
||||||
|
home.file."${config.xdg.configHome}/podman/containers.manifest".text =
|
||||||
|
podman-lib.generateManifestText containerQuadlets;
|
||||||
|
};
|
||||||
|
}
|
17
modules/services/podman-linux/default.nix
Normal file
17
modules/services/podman-linux/default.nix
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
meta.maintainers = with lib.hm.maintainers; [ bamhm182 n-hass ];
|
||||||
|
|
||||||
|
imports =
|
||||||
|
[ ./containers.nix ./install-quadlet.nix ./networks.nix ./services.nix ];
|
||||||
|
|
||||||
|
options.services.podman = {
|
||||||
|
enable = lib.mkEnableOption "Podman, a daemonless container engine";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf config.services.podman.enable {
|
||||||
|
assertions =
|
||||||
|
[ (lib.hm.assertions.assertPlatform "podman" pkgs lib.platforms.linux) ];
|
||||||
|
};
|
||||||
|
}
|
88
modules/services/podman-linux/install-quadlet.nix
Normal file
88
modules/services/podman-linux/install-quadlet.nix
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.podman;
|
||||||
|
|
||||||
|
podman-lib = import ./podman-lib.nix { inherit lib config; };
|
||||||
|
activation = import ./activation.nix { inherit config podman-lib; };
|
||||||
|
|
||||||
|
activationCleanupScript = activation.cleanup;
|
||||||
|
|
||||||
|
# derivation to build a single Podman quadlet, outputting its systemd unit files
|
||||||
|
buildPodmanQuadlet = quadlet:
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
name = "home-${quadlet.resourceType}-${quadlet.serviceName}";
|
||||||
|
|
||||||
|
buildInputs = [ cfg.package ];
|
||||||
|
|
||||||
|
dontUnpack = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
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.resourceType}
|
||||||
|
|
||||||
|
# Generate systemd unit file/s from the quadlet file
|
||||||
|
export QUADLET_UNIT_DIRS=$out/quadlets
|
||||||
|
${cfg.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 cfg.internal.quadletDefinitions;
|
||||||
|
|
||||||
|
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 = mkIf cfg.enable {
|
||||||
|
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" ] activationCleanupScript);
|
||||||
|
};
|
||||||
|
}
|
168
modules/services/podman-linux/networks.nix
Normal file
168
modules/services/podman-linux/networks.nix
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.podman;
|
||||||
|
|
||||||
|
podman-lib = import ./podman-lib.nix { inherit lib config; };
|
||||||
|
|
||||||
|
awaitPodmanUnshare = pkgs.writeShellScript "await-podman-unshare" ''
|
||||||
|
until ${cfg.package}/bin/podman unshare ${pkgs.coreutils}/bin/true; do
|
||||||
|
sleep 1;
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
createQuadletSource = name: networkDef:
|
||||||
|
let
|
||||||
|
cfg = (podman-lib.deepMerge {
|
||||||
|
Install = {
|
||||||
|
WantedBy = (if networkDef.autoStart then [
|
||||||
|
"default.target"
|
||||||
|
"multi-user.target"
|
||||||
|
] else
|
||||||
|
[ ]);
|
||||||
|
};
|
||||||
|
Network = {
|
||||||
|
Driver = networkDef.driver;
|
||||||
|
Gateway = networkDef.gateway;
|
||||||
|
Internal = networkDef.internal;
|
||||||
|
NetworkName = name;
|
||||||
|
Label = networkDef.labels // { "nix.home-manager.managed" = true; };
|
||||||
|
PodmanArgs = networkDef.extraPodmanArgs;
|
||||||
|
Subnet = networkDef.subnet;
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
Environment = {
|
||||||
|
PATH = (builtins.concatStringsSep ":" [
|
||||||
|
"${podman-lib.newuidmapPaths}"
|
||||||
|
"${makeBinPath [ pkgs.su pkgs.coreutils ]}"
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
ExecStartPre = [ "${awaitPodmanUnshare}" ];
|
||||||
|
TimeoutStartSec = 15;
|
||||||
|
RemainAfterExit = "yes";
|
||||||
|
};
|
||||||
|
Unit = {
|
||||||
|
After = [ "network.target" ];
|
||||||
|
Description = (if (builtins.isString networkDef.description) then
|
||||||
|
networkDef.description
|
||||||
|
else
|
||||||
|
"Service for network ${name}");
|
||||||
|
};
|
||||||
|
} networkDef.extraConfig);
|
||||||
|
in ''
|
||||||
|
# Automatically generated by home-manager for podman network configuration
|
||||||
|
# DO NOT EDIT THIS FILE DIRECTLY
|
||||||
|
#
|
||||||
|
# ${name}.network
|
||||||
|
${podman-lib.toQuadletIni cfg}
|
||||||
|
'';
|
||||||
|
|
||||||
|
toQuadletInternal = name: networkDef: {
|
||||||
|
assertions = podman-lib.buildConfigAsserts name networkDef.extraConfig;
|
||||||
|
serviceName =
|
||||||
|
"podman-${name}"; # quadlet service name: 'podman-<name>-network.service'
|
||||||
|
source = podman-lib.removeBlankLines (createQuadletSource name networkDef);
|
||||||
|
resourceType = "network";
|
||||||
|
};
|
||||||
|
|
||||||
|
in let
|
||||||
|
networkDefinitionType = types.submodule {
|
||||||
|
options = {
|
||||||
|
|
||||||
|
autoStart = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to start the network on boot (requires user lingering).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
description = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
example = "My Network";
|
||||||
|
description = "The description of the network.";
|
||||||
|
};
|
||||||
|
|
||||||
|
driver = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
example = "bridge";
|
||||||
|
description = "The network driver to use.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = podman-lib.extraConfigType;
|
||||||
|
default = { };
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
Network = {
|
||||||
|
ContainerConfModule = "/etc/nvd.conf";
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
TimeoutStartSec = 30;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = "INI sections and values to populate the Network Quadlet";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraPodmanArgs = mkOption {
|
||||||
|
type = with types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "--dns=192.168.55.1" "--ipam-driver" ];
|
||||||
|
description = ''
|
||||||
|
Extra arguments to pass to the podman network create command.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
gateway = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
example = "192.168.20.1";
|
||||||
|
description = "The gateway IP to use for the network.";
|
||||||
|
};
|
||||||
|
|
||||||
|
internal = mkOption {
|
||||||
|
type = with types; nullOr bool;
|
||||||
|
default = null;
|
||||||
|
description = "Whether the network should be internal";
|
||||||
|
};
|
||||||
|
|
||||||
|
labels = mkOption {
|
||||||
|
type = with types; attrsOf str;
|
||||||
|
default = { };
|
||||||
|
example = {
|
||||||
|
app = "myapp";
|
||||||
|
some-label = "somelabel";
|
||||||
|
};
|
||||||
|
description = "The labels to apply to the network.";
|
||||||
|
};
|
||||||
|
|
||||||
|
subnet = mkOption {
|
||||||
|
type = with types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
example = "192.168.20.0/24";
|
||||||
|
description = "The subnet to use for the network.";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
options.services.podman.networks = mkOption {
|
||||||
|
type = types.attrsOf networkDefinitionType;
|
||||||
|
default = { };
|
||||||
|
description = "Defines Podman network quadlet configurations.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = let networkQuadlets = mapAttrsToList toQuadletInternal cfg.networks;
|
||||||
|
in mkIf cfg.enable {
|
||||||
|
services.podman.internal.quadletDefinitions = networkQuadlets;
|
||||||
|
assertions = flatten (map (network: network.assertions) networkQuadlets);
|
||||||
|
|
||||||
|
home.file."${config.xdg.configHome}/podman/networks.manifest".text =
|
||||||
|
podman-lib.generateManifestText networkQuadlets;
|
||||||
|
};
|
||||||
|
}
|
52
modules/services/podman-linux/options.nix
Normal file
52
modules/services/podman-linux/options.nix
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{ lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Define the systemd service type
|
||||||
|
quadletInternalType = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
assertions = lib.mkOption {
|
||||||
|
type = with lib.types; listOf unspecified;
|
||||||
|
default = [ ];
|
||||||
|
internal = true;
|
||||||
|
description = "List of Nix type assertions.";
|
||||||
|
};
|
||||||
|
|
||||||
|
resourceType = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
internal = true;
|
||||||
|
description = "The type of the podman Quadlet resource.";
|
||||||
|
};
|
||||||
|
|
||||||
|
serviceName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
internal = true;
|
||||||
|
description = "The name of the systemd service.";
|
||||||
|
};
|
||||||
|
|
||||||
|
source = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
internal = true;
|
||||||
|
description = "The quadlet source file content.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
options.services.podman = {
|
||||||
|
internal.quadletDefinitions = lib.mkOption {
|
||||||
|
type = lib.types.listOf quadletInternalType;
|
||||||
|
default = { };
|
||||||
|
internal = true;
|
||||||
|
description = "List of quadlet source file content and service names.";
|
||||||
|
};
|
||||||
|
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = pkgs.podman;
|
||||||
|
defaultText = lib.literalExpression "pkgs.podman";
|
||||||
|
description = "The podman package to use.";
|
||||||
|
};
|
||||||
|
|
||||||
|
enableTypeChecks = lib.mkEnableOption "type checks for podman quadlets";
|
||||||
|
};
|
||||||
|
}
|
136
modules/services/podman-linux/podman-lib.nix
Normal file
136
modules/services/podman-linux/podman-lib.nix
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
{ lib, config, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
normalizeKeyValue = k: v:
|
||||||
|
let
|
||||||
|
v' = (if builtins.isBool v then
|
||||||
|
(if v then "true" else "false")
|
||||||
|
else if builtins.isAttrs v then
|
||||||
|
(concatStringsSep ''
|
||||||
|
|
||||||
|
${k}='' (mapAttrsToList normalizeKeyValue v))
|
||||||
|
else
|
||||||
|
builtins.toString v);
|
||||||
|
in if builtins.isNull v then "" else "${k}=${v'}";
|
||||||
|
|
||||||
|
primitiveAttrs = with types; attrsOf (either primitive (listOf primitive));
|
||||||
|
primitiveList = with types; listOf primitive;
|
||||||
|
primitive = with types; nullOr (oneOf [ bool int str path ]);
|
||||||
|
|
||||||
|
toQuadletIni = generators.toINI {
|
||||||
|
listsAsDuplicateKeys = true;
|
||||||
|
mkKeyValue = normalizeKeyValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
# meant for ini. favours b when two values are unmergeable
|
||||||
|
deepMerge = a: b:
|
||||||
|
foldl' (result: key:
|
||||||
|
let
|
||||||
|
aVal = if builtins.hasAttr key a then a.${key} else null;
|
||||||
|
bVal = if builtins.hasAttr key b then b.${key} else null;
|
||||||
|
|
||||||
|
# check if the types inside a list match the type of a primitive
|
||||||
|
listMatchesType = (list: val:
|
||||||
|
isList list && builtins.length list > 0
|
||||||
|
&& builtins.typeOf (builtins.head list) == builtins.typeOf val);
|
||||||
|
in if isAttrs aVal && isAttrs bVal then
|
||||||
|
result // { ${key} = deepMerge aVal bVal; }
|
||||||
|
else if isList aVal && isList bVal then
|
||||||
|
result // { ${key} = aVal ++ bVal; }
|
||||||
|
else if aVal == bVal then
|
||||||
|
result // { ${key} = aVal; }
|
||||||
|
else if aVal == null then
|
||||||
|
result // { ${key} = bVal; }
|
||||||
|
else if bVal == null then
|
||||||
|
result // { ${key} = aVal; }
|
||||||
|
else if isList aVal && listMatchesType aVal bVal then
|
||||||
|
result // { ${key} = aVal ++ [ bVal ]; }
|
||||||
|
else if isList bVal && listMatchesType bVal aVal then
|
||||||
|
result // { ${key} = [ aVal ] ++ bVal; }
|
||||||
|
else if builtins.typeOf aVal == builtins.typeOf bVal then
|
||||||
|
result // { ${key} = bVal; }
|
||||||
|
else
|
||||||
|
result // { ${key} = bVal; }) a (builtins.attrNames b);
|
||||||
|
in {
|
||||||
|
inherit primitiveAttrs;
|
||||||
|
inherit primitiveList;
|
||||||
|
inherit primitive;
|
||||||
|
inherit toQuadletIni;
|
||||||
|
inherit deepMerge;
|
||||||
|
|
||||||
|
buildConfigAsserts = quadletName: extraConfig:
|
||||||
|
let
|
||||||
|
configRules = {
|
||||||
|
Container = { ContainerName = types.enum [ quadletName ]; };
|
||||||
|
Network = { NetworkName = types.enum [ quadletName ]; };
|
||||||
|
};
|
||||||
|
|
||||||
|
# Function to build assertions for a specific section and its attributes.
|
||||||
|
buildSectionAsserts = section: attrs:
|
||||||
|
if builtins.hasAttr section configRules then
|
||||||
|
flatten (mapAttrsToList (attrName: attrValue:
|
||||||
|
if builtins.hasAttr attrName configRules.${section} then [{
|
||||||
|
assertion = configRules.${section}.${attrName}.check attrValue;
|
||||||
|
message = "In '${quadletName}' config. ${section}.${attrName}: '${
|
||||||
|
toString attrValue
|
||||||
|
}' does not match expected type: ${
|
||||||
|
configRules.${section}.${attrName}.description
|
||||||
|
}";
|
||||||
|
}] else
|
||||||
|
[ ]) attrs)
|
||||||
|
else
|
||||||
|
[ ];
|
||||||
|
|
||||||
|
# Flatten assertions from all sections in `extraConfig`.
|
||||||
|
in flatten (mapAttrsToList buildSectionAsserts extraConfig);
|
||||||
|
|
||||||
|
extraConfigType = with types;
|
||||||
|
attrsOf (attrsOf (oneOf [ primitiveAttrs primitiveList primitive ]));
|
||||||
|
|
||||||
|
# input expects a list of quadletInternalType with all the same resourceType
|
||||||
|
generateManifestText = quadlets:
|
||||||
|
let
|
||||||
|
# create a list of all unique quadlet.resourceType in quadlets
|
||||||
|
quadletTypes = unique (map (quadlet: quadlet.resourceType) quadlets);
|
||||||
|
# if quadletTypes is > 1, then all quadlets are not the same type
|
||||||
|
allQuadletsSameType = length quadletTypes <= 1;
|
||||||
|
|
||||||
|
# ensures the service name is formatted correctly to be easily read
|
||||||
|
# by the activation script and matches `podman <resource> ls` output
|
||||||
|
formatServiceName = quadlet:
|
||||||
|
let
|
||||||
|
# remove the podman- prefix from the service name string
|
||||||
|
strippedName =
|
||||||
|
builtins.replaceStrings [ "podman-" ] [ "" ] quadlet.serviceName;
|
||||||
|
# specific logic for writing the unit name goes here. It should be
|
||||||
|
# identical to what `podman <resource> ls` shows
|
||||||
|
in {
|
||||||
|
"container" = strippedName;
|
||||||
|
"network" = strippedName;
|
||||||
|
}."${quadlet.resourceType}";
|
||||||
|
in if allQuadletsSameType then ''
|
||||||
|
${concatStringsSep "\n"
|
||||||
|
(map (quadlet: formatServiceName quadlet) quadlets)}
|
||||||
|
'' else
|
||||||
|
abort ''
|
||||||
|
All quadlets must be of the same type.
|
||||||
|
Quadlet types in this manifest: ${concatStringsSep ", " quadletTypes}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# podman requires setuid on newuidmad, so it cannot be provided by pkgs.shadow
|
||||||
|
# Including all possible locations in PATH for newuidmap is a workaround.
|
||||||
|
# NixOS provides a 'wrapped' variant at /run/wrappers/bin/newuidmap.
|
||||||
|
# Other distros must install the 'uidmap' package, ie for ubuntu: apt install uidmap.
|
||||||
|
# Extra paths are added to handle where distro package managers may put the uidmap binaries.
|
||||||
|
#
|
||||||
|
# Tracking for a potential solution: https://github.com/NixOS/nixpkgs/issues/138423
|
||||||
|
newuidmapPaths = "/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
||||||
|
|
||||||
|
removeBlankLines = text:
|
||||||
|
let
|
||||||
|
lines = splitString "\n" text;
|
||||||
|
nonEmptyLines = filter (line: line != "") lines;
|
||||||
|
in concatStringsSep "\n" nonEmptyLines;
|
||||||
|
}
|
65
modules/services/podman-linux/services.nix
Normal file
65
modules/services/podman-linux/services.nix
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let cfg = config.services.podman;
|
||||||
|
in {
|
||||||
|
options.services.podman = {
|
||||||
|
autoUpdate = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Automatically update the podman images.";
|
||||||
|
};
|
||||||
|
|
||||||
|
onCalendar = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "Sun *-*-* 00:00";
|
||||||
|
description = ''
|
||||||
|
The systemd `OnCalendar` expression for the update. See
|
||||||
|
{manpage}`systemd.time(7)` for a description of the format.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
(mkIf cfg.autoUpdate.enable {
|
||||||
|
systemd.user.services."podman-auto-update" = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Podman auto-update service";
|
||||||
|
Documentation = "man:podman-auto-update(1)";
|
||||||
|
Wants = [ "network-online.target" ];
|
||||||
|
After = [ "network-online.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
Type = "oneshot";
|
||||||
|
Environment = "PATH=${
|
||||||
|
builtins.concatStringsSep ":" [
|
||||||
|
"/run/wrappers/bin"
|
||||||
|
"/run/current-system/sw/bin"
|
||||||
|
"${config.home.homeDirectory}/.nix-profile/bin"
|
||||||
|
]
|
||||||
|
}";
|
||||||
|
ExecStart = "${pkgs.podman}/bin/podman auto-update";
|
||||||
|
ExecStartPost = "${pkgs.podman}/bin/podman image prune -f";
|
||||||
|
TimeoutStartSec = "300s";
|
||||||
|
TimeoutStopSec = "10s";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.user.timers."podman-auto-update" = {
|
||||||
|
Unit = { Description = "Podman auto-update timer"; };
|
||||||
|
|
||||||
|
Timer = {
|
||||||
|
OnCalendar = cfg.autoUpdate.onCalendar;
|
||||||
|
RandomizedDelaySec = 300;
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Install = { WantedBy = [ "timers.target" ]; };
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
|
@ -269,6 +269,7 @@ in import nmtSrc {
|
||||||
./modules/services/pbgopy
|
./modules/services/pbgopy
|
||||||
./modules/services/picom
|
./modules/services/picom
|
||||||
./modules/services/playerctld
|
./modules/services/playerctld
|
||||||
|
./modules/services/podman-linux
|
||||||
./modules/services/polybar
|
./modules/services/polybar
|
||||||
./modules/services/recoll
|
./modules/services/recoll
|
||||||
./modules/services/redshift-gammastep
|
./modules/services/redshift-gammastep
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator
|
||||||
|
#
|
||||||
|
# Automatically generated by home-manager podman container configuration
|
||||||
|
# DO NOT EDIT THIS FILE DIRECTLY
|
||||||
|
#
|
||||||
|
# my-container.container
|
||||||
|
[X-Container]
|
||||||
|
AddDevice=/dev/null:/dev/null
|
||||||
|
AutoUpdate=registry
|
||||||
|
ContainerName=my-container
|
||||||
|
Entrypoint=/sleep.sh
|
||||||
|
Environment=VAL_A=A
|
||||||
|
Environment=VAL_B=2
|
||||||
|
Environment=VAL_C=false
|
||||||
|
Image=docker.io/alpine:latest
|
||||||
|
Label=nix.home-manager.managed=true
|
||||||
|
Network=mynet
|
||||||
|
NetworkAlias=test-alias-1
|
||||||
|
NetworkAlias=test-alias-2
|
||||||
|
PodmanArgs=--security-opt=no-new-privileges
|
||||||
|
PublishPort=8080:80
|
||||||
|
ReadOnlyTmpfs=true
|
||||||
|
Volume=/tmp:/tmp
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PATH=/run/wrappers/bin:/run/current-system/sw/bin:/home/hm-user/.nix-profile/bin
|
||||||
|
Restart=on-failure
|
||||||
|
TimeoutStopSec=30
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
KillMode=mixed
|
||||||
|
ExecStop=/nix/store/00000000000000000000000000000000-podman/bin/podman rm -v -f -i --cidfile=%t/%N.cid
|
||||||
|
ExecStopPost=-/nix/store/00000000000000000000000000000000-podman/bin/podman rm -v -f -i --cidfile=%t/%N.cid
|
||||||
|
Delegate=yes
|
||||||
|
Type=notify
|
||||||
|
NotifyAccess=all
|
||||||
|
SyslogIdentifier=%N
|
||||||
|
ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --name=my-container --cidfile=%t/%N.cid --replace --rm --cgroups=split --network=mynet --network-alias test-alias-1 --network-alias test-alias-2 --sdnotify=conmon -d --device=/dev/null:/dev/null --entrypoint=/sleep.sh --read-only-tmpfs -v /tmp:/tmp --label io.containers.autoupdate=registry --publish 8080:80 --env VAL_A=A --env VAL_B=2 --env VAL_C=false --label nix.home-manager.managed=true --security-opt=no-new-privileges docker.io/alpine:latest
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
After=network.target
|
||||||
|
Before=fake.target
|
||||||
|
Description=home-manager test
|
||||||
|
SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container/quadlets/podman-my-container.container
|
||||||
|
RequiresMountsFor=%t/containers
|
||||||
|
RequiresMountsFor=/tmp
|
60
tests/modules/services/podman-linux/container.nix
Normal file
60
tests/modules/services/podman-linux/container.nix
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
services.podman = {
|
||||||
|
enable = true;
|
||||||
|
containers = {
|
||||||
|
"my-container" = {
|
||||||
|
description = "home-manager test";
|
||||||
|
autoStart = true;
|
||||||
|
autoUpdate = "registry";
|
||||||
|
devices = [ "/dev/null:/dev/null" ];
|
||||||
|
entrypoint = "/sleep.sh";
|
||||||
|
environment = {
|
||||||
|
"VAL_A" = "A";
|
||||||
|
"VAL_B" = 2;
|
||||||
|
"VAL_C" = false;
|
||||||
|
};
|
||||||
|
extraPodmanArgs = [ "--security-opt=no-new-privileges" ];
|
||||||
|
extraConfig = {
|
||||||
|
Container = {
|
||||||
|
ReadOnlyTmpfs = true;
|
||||||
|
NetworkAlias = "test-alias-2";
|
||||||
|
};
|
||||||
|
Service.Restart = "on-failure";
|
||||||
|
Unit.Before = "fake.target";
|
||||||
|
};
|
||||||
|
image = "docker.io/alpine:latest";
|
||||||
|
# Should not generate Requires/After for network because there is no
|
||||||
|
# services.podman.networks.mynet.
|
||||||
|
network = "mynet";
|
||||||
|
networkAlias = [ "test-alias-1" ];
|
||||||
|
ports = [ "8080:80" ];
|
||||||
|
volumes = [ "/tmp:/tmp" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
"my-container-2" = {
|
||||||
|
image = "docker.io/alpine:latest";
|
||||||
|
extraConfig = {
|
||||||
|
Container.ContainerName = "some-other-container-name";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test.asserts.assertions.expected = [
|
||||||
|
''
|
||||||
|
In 'my-container-2' config. Container.ContainerName: 'some-other-container-name' does not match expected type: value "my-container-2" (singular enum)''
|
||||||
|
];
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
configPath=home-files/.config/systemd/user
|
||||||
|
containerFile=$configPath/podman-my-container.service
|
||||||
|
|
||||||
|
assertFileExists $containerFile
|
||||||
|
|
||||||
|
containerFile=$(normalizeStorePaths $containerFile)
|
||||||
|
|
||||||
|
assertFileContent $containerFile ${./container-expected.service}
|
||||||
|
'';
|
||||||
|
}
|
6
tests/modules/services/podman-linux/default.nix
Normal file
6
tests/modules/services/podman-linux/default.nix
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
podman-container = ./container.nix;
|
||||||
|
podman-integration = ./integration.nix;
|
||||||
|
podman-manifest = ./manifest.nix;
|
||||||
|
podman-network = ./network.nix;
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator
|
||||||
|
#
|
||||||
|
# Automatically generated by home-manager podman container configuration
|
||||||
|
# DO NOT EDIT THIS FILE DIRECTLY
|
||||||
|
#
|
||||||
|
# my-container.container
|
||||||
|
[X-Container]
|
||||||
|
ContainerName=my-container
|
||||||
|
Environment=
|
||||||
|
Image=docker.io/alpine:latest
|
||||||
|
Label=nix.home-manager.managed=true
|
||||||
|
Network=my-net
|
||||||
|
Network=externalnet
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PATH=/run/wrappers/bin:/run/current-system/sw/bin:/home/hm-user/.nix-profile/bin
|
||||||
|
Restart=always
|
||||||
|
TimeoutStopSec=30
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
KillMode=mixed
|
||||||
|
ExecStop=/nix/store/00000000000000000000000000000000-podman/bin/podman rm -v -f -i --cidfile=%t/%N.cid
|
||||||
|
ExecStopPost=-/nix/store/00000000000000000000000000000000-podman/bin/podman rm -v -f -i --cidfile=%t/%N.cid
|
||||||
|
Delegate=yes
|
||||||
|
Type=notify
|
||||||
|
NotifyAccess=all
|
||||||
|
SyslogIdentifier=%N
|
||||||
|
ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --name=my-container --cidfile=%t/%N.cid --replace --rm --cgroups=split --network=my-net --network=externalnet --sdnotify=conmon -d --label nix.home-manager.managed=true docker.io/alpine:latest
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
After=network.target
|
||||||
|
After=podman-my-net-network.service
|
||||||
|
Description=Service for container my-container
|
||||||
|
Requires=podman-my-net-network.service
|
||||||
|
SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container/quadlets/podman-my-container.container
|
||||||
|
RequiresMountsFor=%t/containers
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator
|
||||||
|
#
|
||||||
|
# Automatically generated by home-manager for podman network configuration
|
||||||
|
# DO NOT EDIT THIS FILE DIRECTLY
|
||||||
|
#
|
||||||
|
# my-net.network
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
[X-Network]
|
||||||
|
Gateway=192.168.123.1
|
||||||
|
Label=nix.home-manager.managed=true
|
||||||
|
NetworkName=my-net
|
||||||
|
Subnet=192.168.123.0/24
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PATH=/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin:/nix/store/00000000000000000000000000000000-shadow/bin:/nix/store/00000000000000000000000000000000-coreutils/bin
|
||||||
|
ExecStartPre=/nix/store/00000000000000000000000000000000-await-podman-unshare
|
||||||
|
RemainAfterExit=yes
|
||||||
|
TimeoutStartSec=15
|
||||||
|
ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman network create --ignore --subnet=192.168.123.0/24 --gateway=192.168.123.1 --label nix.home-manager.managed=true my-net
|
||||||
|
Type=oneshot
|
||||||
|
SyslogIdentifier=%N
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
After=network.target
|
||||||
|
Description=Service for network my-net
|
||||||
|
RequiresMountsFor=%t/containers
|
29
tests/modules/services/podman-linux/integration.nix
Normal file
29
tests/modules/services/podman-linux/integration.nix
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
services.podman = {
|
||||||
|
enable = true;
|
||||||
|
containers."my-container" = {
|
||||||
|
image = "docker.io/alpine:latest";
|
||||||
|
network = [ "my-net" "externalnet" ];
|
||||||
|
};
|
||||||
|
networks."my-net" = {
|
||||||
|
gateway = "192.168.123.1";
|
||||||
|
subnet = "192.168.123.0/24";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
configPath=home-files/.config/systemd/user
|
||||||
|
containerFile=$configPath/podman-my-container.service
|
||||||
|
networkFile=$configPath/podman-my-net-network.service
|
||||||
|
assertFileExists $containerFile
|
||||||
|
assertFileExists $networkFile
|
||||||
|
|
||||||
|
containerFile=$(normalizeStorePaths $containerFile)
|
||||||
|
networkFile=$(normalizeStorePaths $networkFile)
|
||||||
|
|
||||||
|
assertFileContent $containerFile ${./integration-container-expected.service}
|
||||||
|
assertFileContent $networkFile ${./integration-network-expected.service}
|
||||||
|
'';
|
||||||
|
}
|
64
tests/modules/services/podman-linux/manifest.nix
Normal file
64
tests/modules/services/podman-linux/manifest.nix
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
services.podman = {
|
||||||
|
enable = true;
|
||||||
|
containers."my-container-1" = {
|
||||||
|
description = "home-manager test";
|
||||||
|
autoUpdate = "registry";
|
||||||
|
autoStart = true;
|
||||||
|
image = "docker.io/alpine:latest";
|
||||||
|
entrypoint = "sleep 1000";
|
||||||
|
environment = {
|
||||||
|
"VAL_A" = "A";
|
||||||
|
"VAL_B" = 2;
|
||||||
|
"VAL_C" = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.podman.containers."my-container-2" = {
|
||||||
|
description = "home-manager test";
|
||||||
|
autoUpdate = "registry";
|
||||||
|
autoStart = true;
|
||||||
|
image = "docker.io/alpine:latest";
|
||||||
|
entrypoint = "sleep 1000";
|
||||||
|
environment = {
|
||||||
|
"VAL_A" = "B";
|
||||||
|
"VAL_B" = 3;
|
||||||
|
"VAL_C" = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.podman.networks."mynet-1" = {
|
||||||
|
subnet = "192.168.1.0/24";
|
||||||
|
gateway = "192.168.1.1";
|
||||||
|
};
|
||||||
|
services.podman.networks."mynet-2" = {
|
||||||
|
subnet = "192.168.2.0/24";
|
||||||
|
gateway = "192.168.2.1";
|
||||||
|
};
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
configPath=home-files/.config/podman
|
||||||
|
containerManifest=$configPath/containers.manifest
|
||||||
|
networkManifest=$configPath/networks.manifest
|
||||||
|
|
||||||
|
assertFileExists $containerManifest
|
||||||
|
assertFileExists $networkManifest
|
||||||
|
|
||||||
|
assertFileContent $containerManifest ${
|
||||||
|
builtins.toFile "containers.expected" ''
|
||||||
|
my-container-1
|
||||||
|
my-container-2
|
||||||
|
''
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFileContent $networkManifest ${
|
||||||
|
builtins.toFile "networks.expected" ''
|
||||||
|
mynet-1
|
||||||
|
mynet-2
|
||||||
|
''
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
}
|
33
tests/modules/services/podman-linux/network-expected.service
Normal file
33
tests/modules/services/podman-linux/network-expected.service
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator
|
||||||
|
#
|
||||||
|
# Automatically generated by home-manager for podman network configuration
|
||||||
|
# DO NOT EDIT THIS FILE DIRECTLY
|
||||||
|
#
|
||||||
|
# my-net.network
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
[X-Network]
|
||||||
|
Gateway=192.168.1.1
|
||||||
|
Label=nix.home-manager.managed=true
|
||||||
|
NetworkName=my-net
|
||||||
|
Options=isolate=true
|
||||||
|
PodmanArgs=--ipam-driver dhcp
|
||||||
|
PodmanArgs=--dns=192.168.55.1
|
||||||
|
PodmanArgs=--log-level=debug
|
||||||
|
Subnet=192.168.1.0/24
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PATH=/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin:/nix/store/00000000000000000000000000000000-shadow/bin:/nix/store/00000000000000000000000000000000-coreutils/bin
|
||||||
|
ExecStartPre=/nix/store/00000000000000000000000000000000-await-podman-unshare
|
||||||
|
RemainAfterExit=yes
|
||||||
|
TimeoutStartSec=15
|
||||||
|
ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman network create --ignore --subnet=192.168.1.0/24 --gateway=192.168.1.1 --opt isolate=true --label nix.home-manager.managed=true --ipam-driver dhcp --dns=192.168.55.1 --log-level=debug my-net
|
||||||
|
Type=oneshot
|
||||||
|
SyslogIdentifier=%N
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
After=network.target
|
||||||
|
Description=Service for network my-net
|
||||||
|
RequiresMountsFor=%t/containers
|
44
tests/modules/services/podman-linux/network.nix
Normal file
44
tests/modules/services/podman-linux/network.nix
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
services.podman = {
|
||||||
|
enable = true;
|
||||||
|
networks = {
|
||||||
|
"my-net" = {
|
||||||
|
subnet = "192.168.1.0/24";
|
||||||
|
gateway = "192.168.1.1";
|
||||||
|
extraPodmanArgs = [ "--ipam-driver dhcp" ];
|
||||||
|
extraConfig = {
|
||||||
|
Network = {
|
||||||
|
NetworkName = "my-net";
|
||||||
|
Options = { isolate = "true"; };
|
||||||
|
PodmanArgs = [ "--dns=192.168.55.1" "--log-level=debug" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"my-net-2" = {
|
||||||
|
subnet = "192.168.2.0/24";
|
||||||
|
gateway = "192.168.2.1";
|
||||||
|
extraConfig = {
|
||||||
|
Network = { NetworkName = "some-other-network-name"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test.asserts.assertions.expected = [
|
||||||
|
''
|
||||||
|
In 'my-net-2' config. Network.NetworkName: 'some-other-network-name' does not match expected type: value "my-net-2" (singular enum)''
|
||||||
|
];
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
configPath=home-files/.config/systemd/user
|
||||||
|
networkFile=$configPath/podman-my-net-network.service
|
||||||
|
assertFileExists $networkFile
|
||||||
|
|
||||||
|
networkFile=$(normalizeStorePaths $networkFile)
|
||||||
|
|
||||||
|
assertFileContent $networkFile ${./network-expected.service}
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Reference in a new issue