mirror of
https://github.com/nix-community/home-manager.git
synced 2026-01-12 01:59:37 +08:00
podman: add darwin support with machine management
- restructure module from `podman-linux` to platform-agnostic `podman` - move linux-specific implementation to `modules/services/podman/linux/` - add darwin module with declarative machine management - implement launchd-based watchdog for auto-starting machines - maintains backward compatibility with existing linux functionality
This commit is contained in:
committed by
Austin Horstman
parent
297a085108
commit
f4bcc1ae1c
425
modules/services/podman/linux/containers.nix
Normal file
425
modules/services/podman/linux/containers.nix
Normal file
@@ -0,0 +1,425 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
mkIf
|
||||
mkOption
|
||||
mkMerge
|
||||
types
|
||||
;
|
||||
assertions = import ../assertions.nix { inherit lib; };
|
||||
|
||||
cfg = config.services.podman;
|
||||
|
||||
podman-lib = import ./podman-lib.nix { inherit pkgs lib config; };
|
||||
|
||||
createQuadletSource =
|
||||
name: containerDef:
|
||||
let
|
||||
extractQuadletReference =
|
||||
type: value:
|
||||
let
|
||||
regex = "([a-zA-Z0-9_-]+\\." + type + ").*";
|
||||
parts = builtins.match regex value;
|
||||
in
|
||||
if parts == null then value else builtins.elemAt parts 0;
|
||||
|
||||
dependencyBySuffix =
|
||||
type: value:
|
||||
if (lib.hasInfix ".${type}" value) then
|
||||
let
|
||||
name = extractQuadletReference type value;
|
||||
in
|
||||
if (lib.hasAttr name cfg.internal.builtQuadlets) then
|
||||
[ (cfg.internal.builtQuadlets.${name}) ]
|
||||
else
|
||||
[ ]
|
||||
else
|
||||
[ ];
|
||||
|
||||
withResolverFor =
|
||||
type: value:
|
||||
let
|
||||
resolve = v: dependencyBySuffix type v;
|
||||
in
|
||||
if builtins.isList value then
|
||||
builtins.concatLists (map resolve value) # Flatten list of lists
|
||||
else
|
||||
resolve value;
|
||||
|
||||
dependencyServices =
|
||||
(withResolverFor "image" containerDef.image)
|
||||
++ (withResolverFor "build" containerDef.image)
|
||||
++ (withResolverFor "network" containerDef.network)
|
||||
++ (withResolverFor "volume" containerDef.volumes);
|
||||
|
||||
checkQuadletReference =
|
||||
types: value:
|
||||
if builtins.isList value then
|
||||
builtins.concatLists (map (checkQuadletReference types) value)
|
||||
else
|
||||
let
|
||||
type = lib.findFirst (t: lib.hasInfix ".${t}" value) null types;
|
||||
in
|
||||
if (type != null) then
|
||||
let
|
||||
quadletName = extractQuadletReference type value;
|
||||
quadletsOfType = lib.filterAttrs (
|
||||
n: v: v.quadletData.resourceType == type
|
||||
) cfg.internal.builtQuadlets;
|
||||
in
|
||||
if (lib.hasAttr quadletName quadletsOfType) then
|
||||
[
|
||||
(lib.replaceStrings [ quadletName ] [ "podman-${quadletName}" ] value)
|
||||
]
|
||||
else
|
||||
[ value ]
|
||||
else if
|
||||
((lib.hasInfix "/nix/store" value) == false && lib.hasAttr value cfg.internal.builtQuadlets)
|
||||
then
|
||||
lib.warn ''
|
||||
A value for Podman container '${name}' might use a reference to another quadlet: ${value}.
|
||||
Append the type '.${
|
||||
cfg.internal.builtQuadlets.${value}.quadletData.resourceType
|
||||
}' to '${lib.baseName value}' if this is intended.
|
||||
'' [ value ]
|
||||
else
|
||||
[ value ];
|
||||
|
||||
quadlet = (
|
||||
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 = checkQuadletReference [ "build" "image" ] containerDef.image;
|
||||
IP = containerDef.ip4;
|
||||
IP6 = containerDef.ip6;
|
||||
Label = (containerDef.labels // { "nix.home-manager.managed" = true; });
|
||||
Network = checkQuadletReference [ "network" ] containerDef.network;
|
||||
NetworkAlias = containerDef.networkAlias;
|
||||
PodmanArgs = containerDef.extraPodmanArgs;
|
||||
PublishPort = containerDef.ports;
|
||||
UserNS = containerDef.userNS;
|
||||
User = containerDef.user;
|
||||
Volume = checkQuadletReference [ "volume" ] containerDef.volumes;
|
||||
};
|
||||
Install = {
|
||||
WantedBy = lib.optionals containerDef.autoStart [
|
||||
"default.target"
|
||||
"multi-user.target"
|
||||
];
|
||||
};
|
||||
Service = {
|
||||
Environment = {
|
||||
PATH = (
|
||||
builtins.concatStringsSep ":" [
|
||||
"/run/wrappers/bin"
|
||||
"/run/current-system/sw/bin"
|
||||
"${pkgs.nftables}/bin"
|
||||
"${config.home.homeDirectory}/.nix-profile/bin"
|
||||
"${pkgs.systemd}/bin"
|
||||
]
|
||||
);
|
||||
};
|
||||
Restart = "always";
|
||||
TimeoutStopSec = 30;
|
||||
};
|
||||
Unit = {
|
||||
Description = (
|
||||
if (builtins.isString containerDef.description) then
|
||||
containerDef.description
|
||||
else
|
||||
"Service for container ${name}"
|
||||
);
|
||||
};
|
||||
} containerDef.extraConfig
|
||||
);
|
||||
in
|
||||
{
|
||||
dependencies = dependencyServices;
|
||||
attrs = quadlet;
|
||||
text = ''
|
||||
# Automatically generated by home-manager podman container configuration
|
||||
# DO NOT EDIT THIS FILE DIRECTLY
|
||||
#
|
||||
# ${name}.container
|
||||
${podman-lib.toQuadletIni quadlet}
|
||||
'';
|
||||
};
|
||||
|
||||
toQuadletInternal =
|
||||
name: containerDef:
|
||||
let
|
||||
src = createQuadletSource name containerDef;
|
||||
in
|
||||
{
|
||||
assertions = podman-lib.buildConfigAsserts name containerDef.extraConfig;
|
||||
dependencies = src.dependencies;
|
||||
resourceType = "container";
|
||||
serviceName = "podman-${src.attrs.Container.ContainerName}"; # generated service name: 'podman-<name>.service'
|
||||
source = podman-lib.removeBlankLines src.text;
|
||||
};
|
||||
|
||||
# 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 = lib.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 = lib.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 lib.isString value then [ value ] else value;
|
||||
example = lib.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 = lib.mapAttrsToList toQuadletInternal cfg.containers;
|
||||
in
|
||||
mkIf cfg.enable (mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
(assertions.assertPlatform "services.podman.containers" config pkgs lib.platforms.linux)
|
||||
];
|
||||
}
|
||||
(mkIf pkgs.stdenv.hostPlatform.isLinux {
|
||||
services.podman.internal.quadletDefinitions = containerQuadlets;
|
||||
assertions = lib.flatten (map (container: container.assertions) containerQuadlets);
|
||||
|
||||
# manifest file
|
||||
xdg.configFile."podman/containers.manifest".text =
|
||||
podman-lib.generateManifestText containerQuadlets;
|
||||
})
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user