1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-12-01 06:31:04 +01:00

treewide: reformat nixfmt-rfc-style

Reformat repository using new nixfmt-rfc-style.
This commit is contained in:
Austin Horstman 2025-04-07 16:11:29 -05:00
parent 5df48c4255
commit cba2f9ce95
1051 changed files with 37028 additions and 26594 deletions

View file

@ -1,11 +1,17 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.podman;
podman-lib = import ./podman-lib.nix { inherit pkgs lib config; };
createQuadletSource = name: buildDef:
createQuadletSource =
name: buildDef:
let
quadlet = podman-lib.deepMerge {
Build = {
@ -13,7 +19,9 @@ let
Environment = buildDef.environment;
File = buildDef.file;
ImageTag = [ "homemanager/${name}" ] ++ buildDef.tags;
Label = buildDef.labels // { "nix.home-manager.managed" = true; };
Label = buildDef.labels // {
"nix.home-manager.managed" = true;
};
PodmanArgs = buildDef.extraPodmanArgs;
SetWorkingDirectory = buildDef.workingDirectory;
TLSVerify = buildDef.tlsVerify;
@ -28,9 +36,12 @@ let
TimeoutStartSec = 300;
RemainAfterExit = "yes";
};
Unit = { Description = buildDef.description; };
Unit = {
Description = buildDef.description;
};
} buildDef.extraConfig;
in {
in
{
attrs = quadlet;
text = ''
# Automatically generated by home-manager for podman build configuration
@ -41,133 +52,139 @@ let
'';
};
toQuadletInternal = name: buildDef:
let src = createQuadletSource name buildDef;
in {
toQuadletInternal =
name: buildDef:
let
src = createQuadletSource name buildDef;
in
{
assertions = podman-lib.buildConfigAsserts name buildDef.extraConfig;
serviceName =
"podman-${name}"; # generated service name: 'podman-<name>-build.service
serviceName = "podman-${name}"; # generated service name: 'podman-<name>-build.service
source = podman-lib.removeBlankLines src.text;
resourceType = "build";
};
in let
buildDefinitionType = types.submodule ({ name, ... }: {
options = {
in
let
buildDefinitionType = types.submodule (
{ name, ... }:
{
options = {
autoStart = mkOption {
type = types.bool;
default = true;
description =
"Whether to start the build on boot. Requires user lingering.";
};
authFile = mkOption {
type = with types; nullOr path;
default = null;
description = "Path of the authentication file.";
};
description = mkOption {
type = with types; nullOr str;
default = "Service for build ${name}";
defaultText = "Service for build \${name}";
example = "My Build";
description = "The description of the build.";
};
environment = mkOption {
type = podman-lib.primitiveAttrs;
default = { };
example = literalExpression ''
{
VAR1 = "0:100";
VAR2 = true;
VAR3 = 5;
}
'';
description = "Environment variables to set in the build.";
};
extraConfig = mkOption {
type = podman-lib.extraConfigType;
default = { };
example = literalExpression ''
{
Build = {
Arch = "aarch64";
};
Service = {
TimeoutStartSec = 15;
};
}
'';
description = "INI sections and values to populate the Build Quadlet.";
};
extraPodmanArgs = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "--retries 5" ];
description = "Extra arguments to pass to the podman build command.";
};
file = mkOption {
type = types.str;
example = literalExpression ''
`"xdg.configFile."containerfiles/my-img/Containerfile"`
or
`"https://github.com/.../my-img/Containerfile"`
'';
description =
"Path to a Containerfile which contains instructions to build the image.";
};
tags = mkOption {
type = with types; listOf str;
default = [ ];
description = ''
Name associated with the build.
First tag will always be "homemanager/<name>".
'';
};
labels = mkOption {
type = with types; attrsOf str;
default = { };
example = {
app = "myapp";
some-label = "somelabel";
autoStart = mkOption {
type = types.bool;
default = true;
description = "Whether to start the build on boot. Requires user lingering.";
};
description = "The labels to apply to the build.";
};
tlsVerify = mkOption {
type = types.bool;
default = true;
description =
"Require HTTPS and verification of certificates when contacting registries.";
};
authFile = mkOption {
type = with types; nullOr path;
default = null;
description = "Path of the authentication file.";
};
workingDirectory = mkOption {
type = with types; nullOr path;
default = null;
description = "WorkingDirectory of the systemd unit file.";
description = mkOption {
type = with types; nullOr str;
default = "Service for build ${name}";
defaultText = "Service for build \${name}";
example = "My Build";
description = "The description of the build.";
};
environment = mkOption {
type = podman-lib.primitiveAttrs;
default = { };
example = literalExpression ''
{
VAR1 = "0:100";
VAR2 = true;
VAR3 = 5;
}
'';
description = "Environment variables to set in the build.";
};
extraConfig = mkOption {
type = podman-lib.extraConfigType;
default = { };
example = literalExpression ''
{
Build = {
Arch = "aarch64";
};
Service = {
TimeoutStartSec = 15;
};
}
'';
description = "INI sections and values to populate the Build Quadlet.";
};
extraPodmanArgs = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "--retries 5" ];
description = "Extra arguments to pass to the podman build command.";
};
file = mkOption {
type = types.str;
example = literalExpression ''
`"xdg.configFile."containerfiles/my-img/Containerfile"`
or
`"https://github.com/.../my-img/Containerfile"`
'';
description = "Path to a Containerfile which contains instructions to build the image.";
};
tags = mkOption {
type = with types; listOf str;
default = [ ];
description = ''
Name associated with the build.
First tag will always be "homemanager/<name>".
'';
};
labels = mkOption {
type = with types; attrsOf str;
default = { };
example = {
app = "myapp";
some-label = "somelabel";
};
description = "The labels to apply to the build.";
};
tlsVerify = mkOption {
type = types.bool;
default = true;
description = "Require HTTPS and verification of certificates when contacting registries.";
};
workingDirectory = mkOption {
type = with types; nullOr path;
default = null;
description = "WorkingDirectory of the systemd unit file.";
};
};
};
});
in {
}
);
in
{
options.services.podman.builds = mkOption {
type = types.attrsOf buildDefinitionType;
default = { };
description = "Defines Podman build quadlet configurations.";
};
config = let buildQuadlets = mapAttrsToList toQuadletInternal cfg.builds;
in mkIf cfg.enable {
services.podman.internal.quadletDefinitions = buildQuadlets;
assertions = flatten (map (build: build.assertions) buildQuadlets);
config =
let
buildQuadlets = mapAttrsToList toQuadletInternal cfg.builds;
in
mkIf cfg.enable {
services.podman.internal.quadletDefinitions = buildQuadlets;
assertions = flatten (map (build: build.assertions) buildQuadlets);
xdg.configFile."podman/images.manifest".text =
podman-lib.generateManifestText buildQuadlets;
};
xdg.configFile."podman/images.manifest".text = podman-lib.generateManifestText buildQuadlets;
};
}

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
@ -7,56 +12,63 @@ let
podman-lib = import ./podman-lib.nix { inherit pkgs lib config; };
createQuadletSource = name: containerDef:
createQuadletSource =
name: containerDef:
let
extractQuadletReference = type: value:
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;
in
if parts == null then value else builtins.elemAt parts 0;
dependencyBySuffix = type: value:
dependencyBySuffix =
type: value:
if (hasInfix ".${type}" value) then
let name = extractQuadletReference type value;
in if (hasAttr name cfg.internal.builtQuadlets) then
[ (cfg.internal.builtQuadlets.${name}) ]
else
[ ]
let
name = extractQuadletReference type value;
in
if (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
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)
dependencyServices =
(withResolverFor "image" containerDef.image)
++ (withResolverFor "build" containerDef.image)
++ (withResolverFor "network" containerDef.network)
++ (withResolverFor "volume" containerDef.volumes);
checkQuadletReference = types: value:
checkQuadletReference =
types: value:
if builtins.isList value then
builtins.concatLists (map (checkQuadletReference types) value)
else
let type = findFirst (t: hasInfix ".${t}" value) null types;
in if (type != null) then
let
type = findFirst (t: hasInfix ".${t}" value) null types;
in
if (type != null) then
let
quadletName = extractQuadletReference type value;
quadletsOfType =
filterAttrs (n: v: v.quadletData.resourceType == type)
cfg.internal.builtQuadlets;
in if (hasAttr quadletName quadletsOfType) then
quadletsOfType = filterAttrs (n: v: v.quadletData.resourceType == type) cfg.internal.builtQuadlets;
in
if (hasAttr quadletName quadletsOfType) then
[
(replaceStrings [ quadletName ] [ "podman-${quadletName}" ]
value)
(replaceStrings [ quadletName ] [ "podman-${quadletName}" ] value)
]
else
[ value ]
else if ((hasInfix "/nix/store" value) == false
&& hasAttr value cfg.internal.builtQuadlets) then
else if ((hasInfix "/nix/store" value) == false && 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 '.${
@ -66,57 +78,63 @@ let
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 = optionals containerDef.autoStart [
"default.target"
"multi-user.target"
];
};
Service = {
Environment = {
PATH = (builtins.concatStringsSep ":" [
"/run/wrappers/bin"
"/run/current-system/sw/bin"
"${config.home.homeDirectory}/.nix-profile/bin"
"${pkgs.systemd}/bin"
]);
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;
};
Restart = "always";
TimeoutStopSec = 30;
};
Unit = {
Description = (if (builtins.isString containerDef.description) then
containerDef.description
else
"Service for container ${name}");
};
} containerDef.extraConfig);
in {
Install = {
WantedBy = optionals containerDef.autoStart [
"default.target"
"multi-user.target"
];
};
Service = {
Environment = {
PATH = (
builtins.concatStringsSep ":" [
"/run/wrappers/bin"
"/run/current-system/sw/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 = ''
@ -128,14 +146,16 @@ let
'';
};
toQuadletInternal = name: containerDef:
let src = createQuadletSource name containerDef;
in {
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'
serviceName = "podman-${src.attrs.Container.ContainerName}"; # generated service name: 'podman-<name>.service'
source = podman-lib.removeBlankLines src.text;
};
@ -146,7 +166,10 @@ let
addCapabilities = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "CAP_DAC_OVERRIDE" "CAP_IPC_OWNER" ];
example = [
"CAP_DAC_OVERRIDE"
"CAP_IPC_OWNER"
];
description = "The capabilities to add to the container.";
};
@ -159,7 +182,11 @@ let
};
autoUpdate = mkOption {
type = types.enum [ null "registry" "local" ];
type = types.enum [
null
"registry"
"local"
];
default = null;
example = "registry";
description = "The autoupdate policy for the container.";
@ -182,7 +209,10 @@ let
dropCapabilities = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "CAP_DAC_OVERRIDE" "CAP_IPC_OWNER" ];
example = [
"CAP_DAC_OVERRIDE"
"CAP_IPC_OWNER"
];
description = "The capabilities to drop from the container.";
};
@ -209,7 +239,10 @@ let
environmentFile = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "/etc/environment" "/etc/other-env" ];
example = [
"/etc/environment"
"/etc/other-env"
];
description = ''
Paths to files containing container environment variables.
'';
@ -304,14 +337,20 @@ let
networkAlias = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "mycontainer" "web" ];
example = [
"mycontainer"
"web"
];
description = "Network aliases for the container.";
};
ports = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "8080:80" "8443:443" ];
example = [
"8080:80"
"8443:443"
];
description = "A mapping of ports between host and container";
};
@ -330,14 +369,18 @@ let
volumes = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "/tmp:/tmp" "/var/run/test.secret:/etc/secret:ro" ];
example = [
"/tmp:/tmp"
"/var/run/test.secret:/etc/secret:ro"
];
description = "The volumes to mount into the container.";
};
};
};
in {
in
{
imports = [ ./options.nix ];
@ -348,11 +391,12 @@ in {
};
config =
let containerQuadlets = mapAttrsToList toQuadletInternal cfg.containers;
in mkIf cfg.enable {
let
containerQuadlets = mapAttrsToList toQuadletInternal cfg.containers;
in
mkIf cfg.enable {
services.podman.internal.quadletDefinitions = containerQuadlets;
assertions =
flatten (map (container: container.assertions) containerQuadlets);
assertions = flatten (map (container: container.assertions) containerQuadlets);
# manifest file
xdg.configFile."podman/containers.manifest".text =

View file

@ -1,9 +1,18 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.podman;
toml = pkgs.formats.toml { };
in {
meta.maintainers = with lib.hm.maintainers; [ bamhm182 n-hass ];
in
{
meta.maintainers = with lib.hm.maintainers; [
bamhm182
n-hass
];
imports = [
./builds.nix
@ -79,8 +88,7 @@ in {
};
config = lib.mkIf cfg.enable {
assertions =
[ (lib.hm.assertions.assertPlatform "podman" pkgs lib.platforms.linux) ];
assertions = [ (lib.hm.assertions.assertPlatform "podman" pkgs lib.platforms.linux) ];
home.packages = [ cfg.package ];
@ -89,18 +97,16 @@ in {
};
xdg.configFile = {
"containers/policy.json".source = if cfg.settings.policy != { } then
pkgs.writeText "policy.json" (builtins.toJSON cfg.settings.policy)
else
"${pkgs.skopeo.policy}/default-policy.json";
"containers/policy.json".source =
if cfg.settings.policy != { } then
pkgs.writeText "policy.json" (builtins.toJSON cfg.settings.policy)
else
"${pkgs.skopeo.policy}/default-policy.json";
"containers/registries.conf".source = toml.generate "registries.conf" {
registries =
lib.mapAttrs (n: v: { registries = v; }) cfg.settings.registries;
registries = lib.mapAttrs (n: v: { registries = v; }) cfg.settings.registries;
};
"containers/storage.conf".source =
toml.generate "storage.conf" cfg.settings.storage;
"containers/containers.conf".source =
toml.generate "containers.conf" cfg.settings.containers;
"containers/storage.conf".source = toml.generate "storage.conf" cfg.settings.storage;
"containers/containers.conf".source = toml.generate "containers.conf" cfg.settings.containers;
};
};
}

View file

@ -1,11 +1,17 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.podman;
podman-lib = import ./podman-lib.nix { inherit pkgs lib config; };
createQuadletSource = name: imageDef:
createQuadletSource =
name: imageDef:
let
credsString =
(if imageDef.username != null then imageDef.username else "")
@ -33,9 +39,12 @@ let
TimeoutStartSec = 300;
RemainAfterExit = "yes";
};
Unit = { Description = imageDef.description; };
Unit = {
Description = imageDef.description;
};
} imageDef.extraConfig;
in ''
in
''
# Automatically generated by home-manager for podman image configuration
# DO NOT EDIT THIS FILE DIRECTLY
#
@ -45,118 +54,118 @@ let
toQuadletInternal = name: imageDef: {
assertions = podman-lib.buildConfigAsserts name imageDef.extraConfig;
serviceName =
"podman-${name}"; # generated service name: 'podman-<name>-image.service
serviceName = "podman-${name}"; # generated service name: 'podman-<name>-image.service
source = podman-lib.removeBlankLines (createQuadletSource name imageDef);
resourceType = "image";
};
in let
imageDefinitionType = types.submodule ({ name, ... }: {
options = {
autoStart = mkOption {
type = types.bool;
default = true;
description =
"Whether to pull the image on boot. Requires user lingering.";
};
in
let
imageDefinitionType = types.submodule (
{ name, ... }:
{
options = {
autoStart = mkOption {
type = types.bool;
default = true;
description = "Whether to pull the image on boot. Requires user lingering.";
};
authFile = mkOption {
type = with types; nullOr path;
default = null;
description =
"Path of the authentication file used to connect to registry.";
};
authFile = mkOption {
type = with types; nullOr path;
default = null;
description = "Path of the authentication file used to connect to registry.";
};
certDir = mkOption {
type = with types; nullOr path;
default = null;
description =
"Path of certificates (*.{crt,cert,key}) used to connect to registry.";
};
certDir = mkOption {
type = with types; nullOr path;
default = null;
description = "Path of certificates (*.{crt,cert,key}) used to connect to registry.";
};
decryptionKeyFile = mkOption {
type = with types; nullOr path;
default = null;
description = "Path to key used for decryption of images.";
};
decryptionKeyFile = mkOption {
type = with types; nullOr path;
default = null;
description = "Path to key used for decryption of images.";
};
description = mkOption {
type = with types; nullOr str;
default = "Service for image ${name}";
defaultText = "Service for image \${name}";
example = "My Image";
description = "The description of the image.";
};
description = mkOption {
type = with types; nullOr str;
default = "Service for image ${name}";
defaultText = "Service for image \${name}";
example = "My Image";
description = "The description of the image.";
};
extraConfig = mkOption {
type = podman-lib.extraConfigType;
default = { };
example = literalExpression ''
{
Image = {
ContainersConfModule = "/etc/nvd.conf";
};
}
'';
description = "INI sections and values to populate the Image Quadlet.";
};
extraConfig = mkOption {
type = podman-lib.extraConfigType;
default = { };
example = literalExpression ''
{
Image = {
ContainersConfModule = "/etc/nvd.conf";
};
}
'';
description = "INI sections and values to populate the Image Quadlet.";
};
extraPodmanArgs = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "--os=linux" ];
description =
"Extra arguments to pass to the podman image pull command.";
};
extraPodmanArgs = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "--os=linux" ];
description = "Extra arguments to pass to the podman image pull command.";
};
image = mkOption {
type = types.str;
example = "quay.io/centos/centos:latest";
description = "Image to pull.";
};
image = mkOption {
type = types.str;
example = "quay.io/centos/centos:latest";
description = "Image to pull.";
};
password = mkOption {
type = with types; nullOr str;
default = null;
example = "P@ssw0rd";
description =
"Password used to connect to registry. (Will be visible in nix store)";
};
password = mkOption {
type = with types; nullOr str;
default = null;
example = "P@ssw0rd";
description = "Password used to connect to registry. (Will be visible in nix store)";
};
tag = mkOption {
type = with types; nullOr str;
default = null;
example = "quay.io/centos/centos:latest";
description =
"FQIN of referenced Image when source is a file or directory archive.";
};
tag = mkOption {
type = with types; nullOr str;
default = null;
example = "quay.io/centos/centos:latest";
description = "FQIN of referenced Image when source is a file or directory archive.";
};
tlsVerify = mkOption {
type = types.bool;
default = true;
description =
"Require HTTPS and verification of certificates when contacting registries.";
};
tlsVerify = mkOption {
type = types.bool;
default = true;
description = "Require HTTPS and verification of certificates when contacting registries.";
};
username = mkOption {
type = with types; nullOr str;
default = null;
example = "bob";
description = "Username used to connect to registry.";
};
username = mkOption {
type = with types; nullOr str;
default = null;
example = "bob";
description = "Username used to connect to registry.";
};
};
});
in {
};
}
);
in
{
options.services.podman.images = mkOption {
type = types.attrsOf imageDefinitionType;
default = { };
description = "Defines Podman image quadlet configurations.";
};
config = let imageQuadlets = mapAttrsToList toQuadletInternal cfg.images;
in mkIf cfg.enable {
services.podman.internal.quadletDefinitions = imageQuadlets;
assertions = flatten (map (image: image.assertions) imageQuadlets);
};
config =
let
imageQuadlets = mapAttrsToList toQuadletInternal cfg.images;
in
mkIf cfg.enable {
services.podman.internal.quadletDefinitions = imageQuadlets;
assertions = flatten (map (image: image.assertions) imageQuadlets);
};
}

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
@ -11,7 +16,8 @@ let
activationCleanupScript = activation.cleanup;
# derivation to build a single Podman quadlet, outputting its systemd unit files
buildPodmanQuadlet = quadlet:
buildPodmanQuadlet =
quadlet:
pkgs.stdenv.mkDerivation {
name = "home-${quadlet.resourceType}-${quadlet.serviceName}";
@ -19,9 +25,12 @@ let
unpackPhase = ''
mkdir -p $out/quadlets
${concatStringsSep "\n" (map (v:
"echo 'linking ${v.quadletData.serviceName}.${v.quadletData.resourceType}'; ln -s ${v.out}/quadlets/${v.quadletData.serviceName}.${v.quadletData.resourceType} $out/quadlets")
quadlet.dependencies)}
${concatStringsSep "\n" (
map (
v:
"echo 'linking ${v.quadletData.serviceName}.${v.quadletData.resourceType}'; ln -s ${v.out}/quadlets/${v.quadletData.serviceName}.${v.quadletData.resourceType} $out/quadlets"
) quadlet.dependencies
)}
'';
installPhase = ''
@ -44,52 +53,68 @@ let
builtQuadlets = map buildPodmanQuadlet cfg.internal.quadletDefinitions;
accumulateUnitFiles = prefix: path: quadlet:
accumulateUnitFiles =
prefix: path: quadlet:
let
entries = builtins.readDir path;
processEntry = name: type:
processEntry =
name: type:
let
newPath = "${path}/${name}";
newPrefix = prefix + (if prefix == "" then "" else "/") + name;
in if type == "directory" then
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));
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;
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);
generateSystemdFileLinks =
files:
listToAttrs (
map (unitFile: {
name = "${config.xdg.configHome}/systemd/user/${unitFile.key}";
value = {
source = unitFile.value.path;
};
}) files
);
in {
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);
home.activation.podmanQuadletCleanup = lib.mkIf (lib.length builtQuadlets >= 1) (
lib.hm.dag.entryAfter [ "reloadSystemd" ] activationCleanupScript
);
services.podman.internal.builtQuadlets = listToAttrs (map (pkg: {
name = (removePrefix "podman-" pkg.passthru.quadletData.serviceName) + "."
+ pkg.passthru.quadletData.resourceType;
value = pkg;
}) builtQuadlets);
services.podman.internal.builtQuadlets = listToAttrs (
map (pkg: {
name =
(removePrefix "podman-" pkg.passthru.quadletData.serviceName)
+ "."
+ pkg.passthru.quadletData.resourceType;
value = pkg;
}) builtQuadlets
);
};
}

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
@ -7,45 +12,62 @@ let
podman-lib = import ./podman-lib.nix { inherit pkgs lib config; };
createQuadletSource = name: networkDef:
createQuadletSource =
name: networkDef:
let
quadlet = (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 ]}"
]);
quadlet = (
podman-lib.deepMerge {
Install = {
WantedBy = (
if networkDef.autoStart then
[
"default.target"
"multi-user.target"
]
else
[ ]
);
};
ExecStartPre = [ "${podman-lib.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 ''
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 = [ "${podman-lib.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
#
@ -55,13 +77,13 @@ let
toQuadletInternal = name: networkDef: {
assertions = podman-lib.buildConfigAsserts name networkDef.extraConfig;
serviceName =
"podman-${name}"; # generated service name: 'podman-<name>-network.service'
serviceName = "podman-${name}"; # generated service name: 'podman-<name>-network.service'
source = podman-lib.removeBlankLines (createQuadletSource name networkDef);
resourceType = "network";
};
in let
in
let
networkDefinitionType = types.submodule {
options = {
@ -106,7 +128,10 @@ in let
extraPodmanArgs = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "--dns=192.168.55.1" "--ipam-driver" ];
example = [
"--dns=192.168.55.1"
"--ipam-driver"
];
description = ''
Extra arguments to pass to the podman network create command.
'';
@ -144,19 +169,22 @@ in let
};
};
in {
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);
config =
let
networkQuadlets = mapAttrsToList toQuadletInternal cfg.networks;
in
mkIf cfg.enable {
services.podman.internal.quadletDefinitions = networkQuadlets;
assertions = flatten (map (network: network.assertions) networkQuadlets);
xdg.configFile."podman/networks.manifest".text =
podman-lib.generateManifestText networkQuadlets;
};
xdg.configFile."podman/networks.manifest".text = podman-lib.generateManifestText networkQuadlets;
};
}

View file

@ -38,7 +38,8 @@ let
};
};
};
in {
in
{
options.services.podman = {
internal = {
quadletDefinitions = lib.mkOption {

View file

@ -1,21 +1,36 @@
{ pkgs, lib, config, ... }:
{
pkgs,
lib,
config,
...
}:
with lib;
let
normalizeKeyValue = k: v:
normalizeKeyValue =
k: v:
let
v' = if builtins.isBool v then
(if v then "true" else "false")
else if builtins.isAttrs v then
(concatStringsSep ''
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'}";
${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 ]);
primitive =
with types;
nullOr (oneOf [
bool
int
str
path
]);
toQuadletIni = generators.toINI {
listsAsDuplicateKeys = true;
@ -23,17 +38,22 @@ let
};
# meant for ini. favours b when two values are unmergeable
deepMerge = a: b:
foldl' (result: key:
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
listMatchesType =
list: val:
isList list
&& builtins.length list > 0
&& builtins.typeOf (builtins.head list) == builtins.typeOf val;
in if isAttrs aVal && isAttrs bVal then
in
if isAttrs aVal && isAttrs bVal then
result // { ${key} = deepMerge aVal bVal; }
else if isList aVal && isList bVal then
result // { ${key} = aVal ++ bVal; }
@ -50,61 +70,90 @@ let
else if builtins.typeOf aVal == builtins.typeOf bVal then
result // { ${key} = bVal; }
else
result // { ${key} = bVal; }) a (builtins.attrNames b);
in {
result // { ${key} = bVal; }
) a (builtins.attrNames b);
in
{
inherit primitiveAttrs;
inherit primitiveList;
inherit primitive;
inherit toQuadletIni;
inherit deepMerge;
buildConfigAsserts = quadletName: extraConfig:
buildConfigAsserts =
quadletName: extraConfig:
let
configRules = {
Build = { ImageTag = with types; listOf str; };
Container = { ContainerName = types.enum [ quadletName ]; };
Network = { NetworkName = types.enum [ quadletName ]; };
Volume = { VolumeName = types.enum [ quadletName ]; };
Build = {
ImageTag = with types; listOf str;
};
Container = {
ContainerName = types.enum [ quadletName ];
};
Network = {
NetworkName = types.enum [ quadletName ];
};
Volume = {
VolumeName = types.enum [ quadletName ];
};
};
# Function to build assertions for a specific section and its attributes.
buildSectionAsserts = section: attrs:
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)
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
[ ];
checkImageTag = extraConfig:
checkImageTag =
extraConfig:
let
imageTags = (extraConfig.Build or { }).ImageTag or [ ];
containsRequiredTag =
builtins.elem "homemanager/${quadletName}" imageTags;
containsRequiredTag = builtins.elem "homemanager/${quadletName}" imageTags;
imageTagsStr = concatMapStringsSep ''" "'' toString imageTags;
in [{
assertion = imageTags == [ ] || containsRequiredTag;
message = ''
In '${quadletName}' config. Build.ImageTag: '[ "${imageTagsStr}" ]' does not contain 'homemanager/${quadletName}'.'';
}];
in
[
{
assertion = imageTags == [ ] || containsRequiredTag;
message = ''In '${quadletName}' config. Build.ImageTag: '[ "${imageTagsStr}" ]' does not contain 'homemanager/${quadletName}'.'';
}
];
# Flatten assertions from all sections in `extraConfig`.
in flatten (concatLists [
in
flatten (concatLists [
(mapAttrsToList buildSectionAsserts extraConfig)
(checkImageTag extraConfig)
]);
extraConfigType = with types;
attrsOf (attrsOf (oneOf [ primitiveAttrs primitiveList primitive ]));
extraConfigType =
with types;
attrsOf (
attrsOf (oneOf [
primitiveAttrs
primitiveList
primitive
])
);
# input expects a list of quadletInternalType with all the same resourceType
generateManifestText = quadlets:
generateManifestText =
quadlets:
let
# create a list of all unique quadlet.resourceType in quadlets
quadletTypes = unique (map (quadlet: quadlet.resourceType) quadlets);
@ -113,22 +162,27 @@ in {
# ensures the service name is formatted correctly to be easily read
# by the activation script and matches `podman <resource> ls` output
formatServiceName = quadlet:
formatServiceName =
quadlet:
let
# remove the podman- prefix from the service name string
strippedName = lib.removePrefix "podman-" quadlet.serviceName;
# specific logic for writing the unit name goes here. It should be
# identical to what `podman <resource> ls` shows
in {
in
{
"build" = "localhost/homemanager/${strippedName}";
"container" = strippedName;
"network" = strippedName;
"volume" = strippedName;
}."${quadlet.resourceType}";
in if allQuadletsSameType then ''
${concatStringsSep "\n"
(map (quadlet: formatServiceName quadlet) quadlets)}
'' else
}
."${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}
@ -143,11 +197,13 @@ in {
# Tracking for a potential solution: https://github.com/NixOS/nixpkgs/issues/138423
newuidmapPaths = "/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin";
removeBlankLines = text:
removeBlankLines =
text:
let
lines = splitString "\n" text;
nonEmptyLines = filter (line: line != "") lines;
in concatStringsSep "\n" nonEmptyLines;
in
concatStringsSep "\n" nonEmptyLines;
awaitPodmanUnshare = pkgs.writeShellScript "await-podman-unshare" ''
until ${config.services.podman.package}/bin/podman unshare ${pkgs.coreutils}/bin/true; do

View file

@ -1,9 +1,16 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
let cfg = config.services.podman;
in {
let
cfg = config.services.podman;
in
{
options.services.podman = {
autoUpdate = {
enable = mkOption {
@ -36,12 +43,12 @@ in {
Service = {
Type = "oneshot";
Environment = "PATH=${
builtins.concatStringsSep ":" [
"/run/wrappers/bin"
"/run/current-system/sw/bin"
"${config.home.homeDirectory}/.nix-profile/bin"
]
}";
builtins.concatStringsSep ":" [
"/run/wrappers/bin"
"/run/current-system/sw/bin"
"${config.home.homeDirectory}/.nix-profile/bin"
]
}";
ExecStart = "${cfg.package}/bin/podman auto-update";
ExecStartPost = "${cfg.package}/bin/podman image prune -f";
TimeoutStartSec = "300s";
@ -50,7 +57,9 @@ in {
};
systemd.user.timers."podman-auto-update" = {
Unit = { Description = "Podman auto-update timer"; };
Unit = {
Description = "Podman auto-update timer";
};
Timer = {
OnCalendar = cfg.autoUpdate.onCalendar;
@ -58,7 +67,9 @@ in {
Persistent = true;
};
Install = { WantedBy = [ "timers.target" ]; };
Install = {
WantedBy = [ "timers.target" ];
};
};
})
({
@ -66,7 +77,14 @@ in {
''
[Service]
ExecSearchPath=${
makeBinPath (with pkgs; [ bashInteractive systemd coreutils ])
makeBinPath (
with pkgs;
[
bashInteractive
systemd
coreutils
]
)
}:/bin
'';
})

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
@ -7,7 +12,8 @@ let
podman-lib = import ./podman-lib.nix { inherit pkgs lib config; };
createQuadletSource = name: volumeDef:
createQuadletSource =
name: volumeDef:
let
quadlet = podman-lib.deepMerge {
Install = {
@ -18,16 +24,23 @@ let
};
Service = {
Environment = {
PATH = (builtins.concatStringsSep ":" [
"${podman-lib.newuidmapPaths}"
"${makeBinPath [ pkgs.su pkgs.coreutils ]}"
]);
PATH = (
builtins.concatStringsSep ":" [
"${podman-lib.newuidmapPaths}"
"${makeBinPath [
pkgs.su
pkgs.coreutils
]}"
]
);
};
ExecStartPre = [ "${podman-lib.awaitPodmanUnshare}" ];
TimeoutStartSec = 15;
RemainAfterExit = "yes";
};
Unit = { Description = volumeDef.description; };
Unit = {
Description = volumeDef.description;
};
Volume = {
Copy = volumeDef.copy;
Device = volumeDef.device;
@ -44,7 +57,8 @@ let
VolumeName = name;
};
} volumeDef.extraConfig;
in ''
in
''
# Automatically generated by home-manager for podman volume configuration
# DO NOT EDIT THIS FILE DIRECTLY
#
@ -54,133 +68,135 @@ let
toQuadletInternal = name: volumeDef: {
assertions = podman-lib.buildConfigAsserts name volumeDef.extraConfig;
serviceName =
"podman-${name}"; # generated service name: 'podman-<name>-volume.service'
serviceName = "podman-${name}"; # generated service name: 'podman-<name>-volume.service'
source = podman-lib.removeBlankLines (createQuadletSource name volumeDef);
resourceType = "volume";
};
in let
volumeDefinitionType = types.submodule ({ name, ... }: {
options = {
in
let
volumeDefinitionType = types.submodule (
{ name, ... }:
{
options = {
autoStart = mkOption {
type = types.bool;
default = true;
description = "Whether to create the volume on boot.";
};
copy = mkOption {
type = types.bool;
default = true;
description =
"Copy content of the image located at the mountpoint of the volume on first run.";
};
description = mkOption {
type = with types; nullOr str;
default = "Service for volume ${name}";
defaultText = "Service for volume \${name}";
example = "My Volume";
description = "The description of the volume.";
};
device = mkOption {
type = with types; nullOr str;
default = null;
example = "tmpfs";
description = "The path of a device which is mounted for the volume.";
};
driver = mkOption {
type = with types; nullOr str;
default = null;
example = "image";
description = "The volume driver to use.";
};
extraConfig = mkOption {
type = podman-lib.extraConfigType;
default = { };
example = literalExpression ''
{
Volume = {
ContainerConfModule = "/etc/nvd.conf";
};
}
'';
description = "INI sections and values to populate the Volume Quadlet.";
};
extraPodmanArgs = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "--opt copy" ];
description =
"Extra arguments to pass to the podman volume create command.";
};
group = mkOption {
type = with types; nullOr (either int str);
default = null;
description = "The group ID owning the volume inside the container.";
};
image = mkOption {
type = with types; nullOr str;
default = null;
example = "quay.io/centos/centos:latest";
description =
"Specifies the image the volume is based on when Driver is set to the image.";
};
labels = mkOption {
type = with types; attrsOf str;
default = { };
example = {
app = "myapp";
some-label = "somelabel";
autoStart = mkOption {
type = types.bool;
default = true;
description = "Whether to create the volume on boot.";
};
description = "The labels to apply to the volume.";
};
preserve = mkOption {
type = types.bool;
default = true;
description = ''
Whether the volume should be preserved if it is removed from the configuration.
Setting this to false will cause the volume to be deleted if the volume is removed from the configuration
'';
};
copy = mkOption {
type = types.bool;
default = true;
description = "Copy content of the image located at the mountpoint of the volume on first run.";
};
type = mkOption {
type = with types; nullOr str;
default = null;
example = "tmpfs";
description =
"Filesystem type of Device. (used as -t in mount commands)";
};
description = mkOption {
type = with types; nullOr str;
default = "Service for volume ${name}";
defaultText = "Service for volume \${name}";
example = "My Volume";
description = "The description of the volume.";
};
user = mkOption {
type = with types; nullOr (either int str);
default = null;
description = "The user ID owning the volume inside the container.";
device = mkOption {
type = with types; nullOr str;
default = null;
example = "tmpfs";
description = "The path of a device which is mounted for the volume.";
};
driver = mkOption {
type = with types; nullOr str;
default = null;
example = "image";
description = "The volume driver to use.";
};
extraConfig = mkOption {
type = podman-lib.extraConfigType;
default = { };
example = literalExpression ''
{
Volume = {
ContainerConfModule = "/etc/nvd.conf";
};
}
'';
description = "INI sections and values to populate the Volume Quadlet.";
};
extraPodmanArgs = mkOption {
type = with types; listOf str;
default = [ ];
example = [ "--opt copy" ];
description = "Extra arguments to pass to the podman volume create command.";
};
group = mkOption {
type = with types; nullOr (either int str);
default = null;
description = "The group ID owning the volume inside the container.";
};
image = mkOption {
type = with types; nullOr str;
default = null;
example = "quay.io/centos/centos:latest";
description = "Specifies the image the volume is based on when Driver is set to the image.";
};
labels = mkOption {
type = with types; attrsOf str;
default = { };
example = {
app = "myapp";
some-label = "somelabel";
};
description = "The labels to apply to the volume.";
};
preserve = mkOption {
type = types.bool;
default = true;
description = ''
Whether the volume should be preserved if it is removed from the configuration.
Setting this to false will cause the volume to be deleted if the volume is removed from the configuration
'';
};
type = mkOption {
type = with types; nullOr str;
default = null;
example = "tmpfs";
description = "Filesystem type of Device. (used as -t in mount commands)";
};
user = mkOption {
type = with types; nullOr (either int str);
default = null;
description = "The user ID owning the volume inside the container.";
};
};
};
});
in {
}
);
in
{
options.services.podman.volumes = mkOption {
type = types.attrsOf volumeDefinitionType;
default = { };
description = "Defines Podman volume quadlet configurations.";
};
config = let volumeQuadlets = mapAttrsToList toQuadletInternal cfg.volumes;
in mkIf cfg.enable {
services.podman.internal.quadletDefinitions = volumeQuadlets;
assertions = flatten (map (volume: volume.assertions) volumeQuadlets);
config =
let
volumeQuadlets = mapAttrsToList toQuadletInternal cfg.volumes;
in
mkIf cfg.enable {
services.podman.internal.quadletDefinitions = volumeQuadlets;
assertions = flatten (map (volume: volume.assertions) volumeQuadlets);
xdg.configFile."podman/volumes.manifest".text =
podman-lib.generateManifestText volumeQuadlets;
};
xdg.configFile."podman/volumes.manifest".text = podman-lib.generateManifestText volumeQuadlets;
};
}