365 lines
12 KiB
Nix
365 lines
12 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}: let
|
|
containerOptions = {...}: {
|
|
options = {
|
|
pullPolicy = mkOption {
|
|
type = with types; str;
|
|
description = "Podman container pulling policy";
|
|
default = "newer";
|
|
};
|
|
image = mkOption {
|
|
type = with types; str;
|
|
description = "OCI image to run.";
|
|
example = "library/hello-world";
|
|
};
|
|
|
|
imageFile = mkOption {
|
|
type = with types; nullOr package;
|
|
default = null;
|
|
description = ''
|
|
Path to an image file to load before running the image. This can
|
|
be used to bypass pulling the image from the registry.
|
|
|
|
The `image` attribute must match the name and
|
|
tag of the image contained in this file, as they will be used to
|
|
run the container with that image. If they do not match, the
|
|
image will be pulled from the registry as usual.
|
|
'';
|
|
example = literalExpression "pkgs.dockerTools.buildImage {...};";
|
|
};
|
|
|
|
login = {
|
|
username = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "Username for login.";
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "Path to file containing password.";
|
|
example = "/etc/nixos/dockerhub-password.txt";
|
|
};
|
|
|
|
registry = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "Registry where to login to.";
|
|
example = "https://docker.pkg.github.com";
|
|
};
|
|
};
|
|
|
|
cmd = mkOption {
|
|
type = with types; listOf str;
|
|
default = [];
|
|
description = "Commandline arguments to pass to the image's entrypoint.";
|
|
example = literalExpression ''
|
|
["--port=9000"]
|
|
'';
|
|
};
|
|
|
|
labels = mkOption {
|
|
type = with types; attrsOf str;
|
|
default = {};
|
|
description = "Labels to attach to the container at runtime.";
|
|
example = literalExpression ''
|
|
{
|
|
"traefik.https.routers.example.rule" = "Host(`example.container`)";
|
|
}
|
|
'';
|
|
};
|
|
|
|
entrypoint = mkOption {
|
|
type = with types; nullOr str;
|
|
description = "Override the default entrypoint of the image.";
|
|
default = null;
|
|
example = "/bin/my-app";
|
|
};
|
|
|
|
environment = mkOption {
|
|
type = with types; attrsOf str;
|
|
default = {};
|
|
description = "Environment variables to set for this container.";
|
|
example = literalExpression ''
|
|
{
|
|
DATABASE_HOST = "db.example.com";
|
|
DATABASE_PORT = "3306";
|
|
}
|
|
'';
|
|
};
|
|
|
|
environmentFiles = mkOption {
|
|
type = with types; listOf path;
|
|
default = [];
|
|
description = "Environment files for this container.";
|
|
example = literalExpression ''
|
|
[
|
|
/path/to/.env
|
|
/path/to/.env.secret
|
|
]
|
|
'';
|
|
};
|
|
|
|
log-driver = mkOption {
|
|
type = types.str;
|
|
default = "journald";
|
|
description = ''
|
|
Logging driver for the container. The default of
|
|
`"journald"` means that the container's logs will be
|
|
handled as part of the systemd unit.
|
|
|
|
For more details and a full list of logging drivers, refer to podman documentation.
|
|
|
|
For Docker:
|
|
[Docker engine documentation](https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver)
|
|
|
|
For Podman:
|
|
Refer to the docker-run(1) man page.
|
|
'';
|
|
};
|
|
|
|
ports = mkOption {
|
|
type = with types; listOf str;
|
|
default = [];
|
|
description = ''
|
|
Network ports to publish from the container to the outer host.
|
|
|
|
Valid formats:
|
|
- `<ip>:<hostPort>:<containerPort>`
|
|
- `<ip>::<containerPort>`
|
|
- `<hostPort>:<containerPort>`
|
|
- `<containerPort>`
|
|
|
|
Both `hostPort` and `containerPort` can be specified as a range of
|
|
ports. When specifying ranges for both, the number of container
|
|
ports in the range must match the number of host ports in the
|
|
range. Example: `1234-1236:1234-1236/tcp`
|
|
|
|
When specifying a range for `hostPort` only, the `containerPort`
|
|
must *not* be a range. In this case, the container port is published
|
|
somewhere within the specified `hostPort` range.
|
|
Example: `1234-1236:1234/tcp`
|
|
|
|
Refer to the
|
|
[Docker engine documentation](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) for full details.
|
|
'';
|
|
example = literalExpression ''
|
|
[
|
|
"8080:9000"
|
|
]
|
|
'';
|
|
};
|
|
|
|
user = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = ''
|
|
Override the username or UID (and optionally groupname or GID) used
|
|
in the container.
|
|
'';
|
|
example = "nobody:nogroup";
|
|
};
|
|
|
|
volumes = mkOption {
|
|
type = with types; listOf str;
|
|
default = [];
|
|
description = ''
|
|
List of volumes to attach to this container.
|
|
|
|
Note that this is a list of `"src:dst"` strings to
|
|
allow for `src` to refer to `/nix/store` paths, which
|
|
would be difficult with an attribute set. There are
|
|
also a variety of mount options available as a third
|
|
field; please refer to the
|
|
[docker engine documentation](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) for details.
|
|
'';
|
|
example = literalExpression ''
|
|
[
|
|
"volume_name:/path/inside/container"
|
|
"/path/on/host:/path/inside/container"
|
|
]
|
|
'';
|
|
};
|
|
|
|
workdir = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "Override the default working directory for the container.";
|
|
example = "/var/lib/hello_world";
|
|
};
|
|
|
|
dependsOn = mkOption {
|
|
type = with types; listOf str;
|
|
default = [];
|
|
description = ''
|
|
Define which other containers this one depends on. They will be added to both After and Requires for the unit.
|
|
|
|
Use the same name as the attribute under `virtualisation.oci-containers.containers`.
|
|
'';
|
|
example = literalExpression ''
|
|
containers = {
|
|
node1 = {};
|
|
node2 = {
|
|
dependsOn = [ "node1" ];
|
|
}
|
|
}
|
|
'';
|
|
};
|
|
|
|
hostname = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = "The hostname of the container.";
|
|
example = "hello-world";
|
|
};
|
|
|
|
preRunExtraOptions = mkOption {
|
|
type = with types; listOf str;
|
|
default = [];
|
|
description = "Extra options for podman that go before the `run` argument.";
|
|
example = ["--runtime" "runsc"];
|
|
};
|
|
|
|
extraOptions = mkOption {
|
|
type = with types; listOf str;
|
|
default = [];
|
|
description = "Extra options for podman run`.";
|
|
example = literalExpression ''
|
|
["--network=host"]
|
|
'';
|
|
};
|
|
|
|
autoStart = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
When enabled, the container is automatically started on boot.
|
|
If this option is set to false, the container has to be started on-demand via its service.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
mkService = name: container: let
|
|
podman = lib.getExe pkgs.podman;
|
|
rm = lib.getExe' pkgs.coreutils "rm";
|
|
printf = lib.getExe' pkgs.coreutils "printf";
|
|
getId = ''"$(${lib.getExe' pkgs.coreutils "id"} -u)"'';
|
|
dependsOn = map (x: "podman-${x}.service") container.dependsOn;
|
|
escapedName = lib.escapeShellArg name;
|
|
preStartScript = pkgs.writeShellApplication {
|
|
name = "pre-start";
|
|
runtimeInputs = [];
|
|
text = ''
|
|
${printf} "Running pre-start script.."
|
|
${podman} rm -f ${name} || true
|
|
${lib.optionalString (container.imageFile != null) ''
|
|
${podman} load -i ${container.imageFile}
|
|
''}
|
|
${rm} -f /run/user/${getId}/podman-${escapedName}.ctr-id
|
|
${printf} " success.\nStarting Podman...\n"
|
|
'';
|
|
};
|
|
script = concatStringsSep " \\\n " (
|
|
[
|
|
"exec ${podman} "
|
|
]
|
|
++ map escapeShellArg container.preRunExtraOptions
|
|
++ [
|
|
"run"
|
|
"--log-level=debug"
|
|
"--rm"
|
|
"--name=${escapedName}"
|
|
"--log-driver=${container.log-driver}"
|
|
]
|
|
++ optional (container.entrypoint != null)
|
|
"--entrypoint=${escapeShellArg container.entrypoint}"
|
|
++ optional (container.hostname != null)
|
|
"--hostname=${escapeShellArg container.hostname}"
|
|
++ optional (container.pullPolicy != null)
|
|
"--pull=${container.pullPolicy}"
|
|
++ [
|
|
"--cidfile=/run/user/${getId}/podman-${escapedName}.ctr-id"
|
|
# "--sdnotify=ignore"
|
|
"--cgroups=no-conmon"
|
|
"--sdnotify=conmon"
|
|
"-d"
|
|
"--replace"
|
|
]
|
|
++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
|
|
++ map (f: "--env-file ${escapeShellArg f}") container.environmentFiles
|
|
++ map (p: "-p ${escapeShellArg p}") container.ports
|
|
++ optional (container.user != null) "-u ${escapeShellArg container.user}"
|
|
++ map (v: "-v ${escapeShellArg v}") container.volumes
|
|
++ (mapAttrsToList (k: v: "-l ${escapeShellArg k}=${escapeShellArg v}") container.labels)
|
|
++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
|
|
++ map escapeShellArg container.extraOptions
|
|
++ [container.image]
|
|
++ map escapeShellArg container.cmd
|
|
);
|
|
|
|
inherit (lib) concatStringsSep escapeShellArg optional mapAttrsToList;
|
|
in {
|
|
Unit = {
|
|
WantedBy = [] ++ lib.optional (container.autoStart) "default.target"; # graphical-session instead maybe?
|
|
After = dependsOn;
|
|
Requires = dependsOn;
|
|
# StopWhenUnneeded = true;
|
|
};
|
|
# TODO make network target..
|
|
# wants = lib.optional (container.imageFile == null) "network-online.target";
|
|
# after = lib.optionals (container.imageFile == null) [ "network-online.target" ]
|
|
# ++ dependsOn;
|
|
# environment = proxy_env;
|
|
|
|
Service = {
|
|
### There is no generalized way of supporting `reload` for docker
|
|
### containers. Some containers may respond well to SIGHUP sent to their
|
|
### init process, but it is not guaranteed; some apps have other reload
|
|
### mechanisms, some don't have a reload signal at all, and some docker
|
|
### images just have broken signal handling. The best compromise in this
|
|
### case is probably to leave ExecReload undefined, so `systemctl reload`
|
|
### will at least result in an error instead of potentially undefined
|
|
### behaviour.
|
|
###
|
|
### Advanced users can still override this part of the unit to implement
|
|
### a custom reload handler, since the result of all this is a normal
|
|
### systemd service from the perspective of the NixOS module system.
|
|
###
|
|
# ExecReload = ...;
|
|
###
|
|
ExecStartPre = ["${preStartScript}/bin/pre-start"];
|
|
ExecStart = [
|
|
"${pkgs.writeShellScript "start" script}"
|
|
];
|
|
ExecStop = [
|
|
"${podman} stop --ignore --cidfile=/run/user/${getId}/podman-${escapedName}.ctr-id"
|
|
"${podman} rm -f --ignore --cidfile=/run/user/${getId}/podman-${escapedName}.ctr-id"
|
|
];
|
|
# TimeoutStartSec = 0;
|
|
# TimeoutStopSec = 120;
|
|
# Restart = "always";
|
|
Environment = ["PODMAN_SYSTEMD_UNIT=podman-${name}.service"];
|
|
Type = "notify";
|
|
NotifyAccess = "all";
|
|
# Type = "exec";
|
|
};
|
|
};
|
|
|
|
cfg = config.containers;
|
|
inherit (lib) mapAttrs' nameValuePair mkOption types mkIf literalExpression;
|
|
in {
|
|
options.containers = mkOption {
|
|
default = {};
|
|
type = with types; attrsOf (submodule containerOptions);
|
|
};
|
|
|
|
config = mkIf (cfg != {}) {
|
|
systemd.user.services = mapAttrs' (k: v: nameValuePair "podman-${k}" (mkService k v)) cfg;
|
|
};
|
|
}
|