From 00f7b3e711923273932a7abeed3e34f0d0a68d39 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 14:44:18 +0100 Subject: [PATCH 01/10] Make the service name available to the service config --- doc/manual/default.nix | 2 +- src/nix/eval-service.nix | 3 ++- src/nix/modules/composition/docker-compose.nix | 2 +- src/nix/modules/service/docker-compose-service.nix | 8 ++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/doc/manual/default.nix b/doc/manual/default.nix index 59f62ab..8038f8c 100644 --- a/doc/manual/default.nix +++ b/doc/manual/default.nix @@ -61,7 +61,7 @@ let declarations = map (d: "src/nix" + (lib.strings.removePrefix (toString ${src}) (toString d))) opt.declarations; }; inherit (pkgs) lib; - composition = pkgs.callPackage ${src}/eval-service.nix {} { modules = []; host = {}; }; + composition = pkgs.callPackage ${src}/eval-service.nix {} { modules = []; host = {}; name = abort "The manual's service options section must not depend on the service name."; }; in map fixPaths (lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList composition.options)) ''; }; diff --git a/src/nix/eval-service.nix b/src/nix/eval-service.nix index f2273d1..c1cb667 100644 --- a/src/nix/eval-service.nix +++ b/src/nix/eval-service.nix @@ -1,6 +1,6 @@ { lib, pkgs, ... }: -{ modules, host }: +{ modules, host, name }: let composite = lib.evalModules { check = true; @@ -21,6 +21,7 @@ let key = ./docker-compose.nix; config._module.args.pkgs = lib.mkForce pkgs; config.host = host; + config.service.name = name; }; in diff --git a/src/nix/modules/composition/docker-compose.nix b/src/nix/modules/composition/docker-compose.nix index 8ce8393..6f0d2d8 100644 --- a/src/nix/modules/composition/docker-compose.nix +++ b/src/nix/modules/composition/docker-compose.nix @@ -11,7 +11,7 @@ */ { pkgs, lib, config, ... }: let -evalService = name: modules: (pkgs.callPackage ../../eval-service.nix {} { inherit modules; inherit (config) host; }).config.build.service; + evalService = name: modules: (pkgs.callPackage ../../eval-service.nix {} { inherit name modules; inherit (config) host; }).config.build.service; in { diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix index b708e4a..25a9f8d 100644 --- a/src/nix/modules/service/docker-compose-service.nix +++ b/src/nix/modules/service/docker-compose-service.nix @@ -33,6 +33,14 @@ in ''; }; + service.name = mkOption { + type = str; + description = '' + The name of the service - <name> in the composition-level docker-compose.services.<name> + ''; + readOnly = true; + }; + service.volumes = mkOption { type = listOf types.unspecified; default = []; From ed2d58c8bd2e11f78284ba4c761eab3753ef03f8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 14:55:55 +0100 Subject: [PATCH 02/10] Add read-only option docker-compose.evaluatedServices --- src/arion | 2 +- src/nix/modules/composition/docker-compose.nix | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/arion b/src/arion index 15eab98..27f49c0 100755 --- a/src/arion +++ b/src/arion @@ -184,7 +184,7 @@ To get started: To see deployment-wide configuration type config. and hit TAB To see the services - type config.docker-compose.services TAB or ENTER + type config.docker-compose.evaluatedServices TAB or ENTER To bring the top-level Nixpkgs attributes into scope type :a (config._module.args.pkgs) // { inherit config; } diff --git a/src/nix/modules/composition/docker-compose.nix b/src/nix/modules/composition/docker-compose.nix index 6f0d2d8..3ec39a6 100644 --- a/src/nix/modules/composition/docker-compose.nix +++ b/src/nix/modules/composition/docker-compose.nix @@ -11,7 +11,7 @@ */ { pkgs, lib, config, ... }: let - evalService = name: modules: (pkgs.callPackage ../../eval-service.nix {} { inherit name modules; inherit (config) host; }).config.build.service; + evalService = name: modules: pkgs.callPackage ../../eval-service.nix {} { inherit name modules; inherit (config) host; }; in { @@ -33,14 +33,20 @@ in type = with lib.types; attrsOf (coercedTo unspecified (a: [a]) (listOf unspecified)); description = "A attribute set of service configurations. A service specifies how to run an image. Each of these service configurations is specified using modules whose options are described in the Service Options section."; }; + docker-compose.evaluatedServices = lib.mkOption { + type = lib.types.attrsOf lib.types.attrs; + description = "Attribute set of evaluated service configurations."; + readOnly = true; + }; }; config = { build.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.build.dockerComposeYamlText; build.dockerComposeYamlText = builtins.toJSON (config.docker-compose.raw); + docker-compose.evaluatedServices = lib.mapAttrs evalService config.docker-compose.services; docker-compose.raw = { version = "3"; - services = lib.mapAttrs evalService config.docker-compose.services; + services = lib.mapAttrs (k: c: c.config.build.service) config.docker-compose.evaluatedServices; }; }; } From 9922cb6b82a486150ececef1804c7e89374124a4 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 15:03:27 +0100 Subject: [PATCH 03/10] Add image building and loading --- src/arion | 26 ++++++ src/nix/eval-composition.nix | 1 + src/nix/eval-service.nix | 1 + .../modules/composition/docker-compose.nix | 2 +- src/nix/modules/composition/images.nix | 36 ++++++++ src/nix/modules/service/host-store.nix | 3 + src/nix/modules/service/image.nix | 91 +++++++++++++++++++ src/nix/modules/service/nixos-init.nix | 2 +- 8 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/nix/modules/composition/images.nix create mode 100644 src/nix/modules/service/image.nix diff --git a/src/arion b/src/arion index 27f49c0..32ec65d 100755 --- a/src/arion +++ b/src/arion @@ -163,6 +163,32 @@ do_build() { --show-trace \ --attr 'config.build.dockerComposeYaml' \ >/dev/null ; + + echo 1>&2 "Ensuring required images are loaded..." + jq -r <"$docker_compose_yaml" \ + '.["x-arion"].images | map(" - " + .imageName + ":" + .imageTag) | join("\n")' + eval "$( + jq -r '.["docker-compose"]["x-arion"].images as $images + | .["existing-images"] as $loaded + | $images + | map( + if $loaded[.imageName + ":" + .imageTag] + then "" + else "docker load <" + .image + ";" end + ) + | join("\n") + ' <docker loaded. + ''; + internal = true; + }; + build.imageName = mkOption { + type = str; + description = "Derived from build.image"; + internal = true; + }; + build.imageTag = mkOption { + type = str; + description = "Derived from build.image"; + internal = true; + }; + image.nixBuild = mkOption { + type = bool; + description = '' + Whether to build this image with Nixpkgs' + dockerTools.buildLayeredImage + and then load it with docker load. + ''; + default = true; + }; + image.name = mkOption { + type = str; + default = config.service.name; + defaultText = lib.literalExample "config.service.name"; + description = '' + A human readable name for the docker image. + + Shows up in the docker ps output in the + IMAGE column, among other places. + ''; + }; + image.contents = mkOption { + type = listOf package; + default = []; + description = '' + Top level paths in the container. + ''; + }; + image.rawConfig = mkOption { + type = attrsOf unspecified; + default = {}; + description = '' + This is a low-level fallback for when a container option has not + been modeled in the Arion module system. + + This attribute set does not have an appropriate merge function. + Please use the specific image options instead. + + Run-time configuration of the container. A full list of the + options are available at in the Docker Image Specification + v1.2.0. + ''; + }; + image.command = mkOption { + type = listOf str; + default = []; + description = '' + ''; + }; + }; + config = { + build.image = pkgs.dockerTools.buildLayeredImage { + inherit (config.image) + name + contents + ; + config = config.image.rawConfig; + }; + build.imageName = config.build.image.imageName; + build.imageTag = + if config.build.image.imageTag != "" + then config.build.image.imageTag + else lib.head (lib.strings.splitString "-" (baseNameOf config.build.image.outPath)); + + service.image = lib.mkDefault "${config.build.imageName}:${config.build.imageTag}"; + image.rawConfig.Cmd = config.image.command; + }; +} diff --git a/src/nix/modules/service/nixos-init.nix b/src/nix/modules/service/nixos-init.nix index 2b37db8..f248403 100644 --- a/src/nix/modules/service/nixos-init.nix +++ b/src/nix/modules/service/nixos-init.nix @@ -23,7 +23,7 @@ in ../nixos/container-systemd.nix (pkgs.path + "/nixos/modules/profiles/minimal.nix") ]; - service.command = [ "${config.nixos.build.toplevel}/init" ]; + image.command = [ "${config.nixos.build.toplevel}/init" ]; service.environment.container = "docker"; service.volumes = [ "/sys/fs/cgroup:/sys/fs/cgroup:ro" From 772456e3f1354e1e7249590fbbc44737922c2e4f Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 7 Mar 2019 00:36:44 +0100 Subject: [PATCH 04/10] doc/manual: Add --show-trace --- doc/manual/default.nix | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/manual/default.nix b/doc/manual/default.nix index 8038f8c..ff2096c 100644 --- a/doc/manual/default.nix +++ b/doc/manual/default.nix @@ -20,7 +20,15 @@ let } '' export NIX_LOG_DIR=$PWD export NIX_STATE_DIR=$PWD - nix-instantiate --option sandbox false --readonly-mode --eval --expr "$optionsExpr" --xml --strict >$out + nix-instantiate \ + --option sandbox false \ + --readonly-mode \ + --eval \ + --expr "$optionsExpr" \ + --xml \ + --strict \ + --show-trace \ + >$out ''; optionsDocBook = runCommand "options-db.xml" {} '' From fb62d75d0baf0a47f3e15c3399cda5274faf0885 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 14:34:08 +0100 Subject: [PATCH 05/10] container-systemd.nix: Disable Nix daemon by default --- src/nix/modules/nixos/container-systemd.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nix/modules/nixos/container-systemd.nix b/src/nix/modules/nixos/container-systemd.nix index d97812f..b754ea9 100644 --- a/src/nix/modules/nixos/container-systemd.nix +++ b/src/nix/modules/nixos/container-systemd.nix @@ -19,6 +19,6 @@ systemd.services.systemd-logind.enable = false; systemd.services.console-getty.enable = false; - systemd.sockets.nix-daemon.enable = false; - systemd.services.nix-daemon.enable = false; + systemd.sockets.nix-daemon.enable = lib.mkDefault false; + systemd.services.nix-daemon.enable = lib.mkDefault false; } From ac012a1ad189e837fc3eadf93fe4b19cc0641e46 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 14:34:53 +0100 Subject: [PATCH 06/10] formatting --- src/nix/modules/service/docker-compose-service.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix index 25a9f8d..0fc3f5c 100644 --- a/src/nix/modules/service/docker-compose-service.nix +++ b/src/nix/modules/service/docker-compose-service.nix @@ -11,7 +11,7 @@ let inherit (types) listOf nullOr attrsOf str either int bool; dockerComposeRef = fragment: - ''See Docker Compose#${fragment}''; + ''See Docker Compose#${fragment}''; dockerComposeKitchenSink = '' Analogous to the docker run counterpart. From 2f119795972a53eb09526d2186c17a38f8ecd740 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 14:36:18 +0100 Subject: [PATCH 07/10] Add service.capabilities option --- .../service/docker-compose-service.nix | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix index 0fc3f5c..9f336f9 100644 --- a/src/nix/modules/service/docker-compose-service.nix +++ b/src/nix/modules/service/docker-compose-service.nix @@ -10,6 +10,8 @@ let inherit (lib) mkOption types; inherit (types) listOf nullOr attrsOf str either int bool; + link = url: text: + ''${text}''; dockerComposeRef = fragment: ''See Docker Compose#${fragment}''; dockerComposeKitchenSink = '' @@ -17,6 +19,10 @@ let ${dockerComposeRef "domainname-hostname-ipc-mac_address-privileged-read_only-shm_size-stdin_open-tty-user-working_dir"} ''; + + cap_add = lib.attrNames (lib.filterAttrs (name: value: value == true) config.service.capabilities); + cap_drop = lib.attrNames (lib.filterAttrs (name: value: value == false) config.service.capabilities); + in { options = { @@ -153,6 +159,24 @@ in default = null; description = dockerComposeRef "stop_signal"; }; + service.capabilities = mkOption { + type = attrsOf (nullOr bool); + default = {}; + example = { ALL = true; SYS_ADMIN = false; NET_ADMIN = false; }; + description = '' + Enable/disable linux capabilities, or pick Docker's default. + + Setting a capability to true means that it will be + "added". Setting it to false means that it will be "dropped". + ${dockerComposeRef "cap_add-cap_drop"} + + Omitted and null capabilities will therefore be set + according to Docker's ${ + link "https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities" + "default list of capabilities." + } + ''; + }; }; config.build.service = { @@ -163,6 +187,10 @@ in ; } // lib.optionalAttrs (config.service.build.context != null) { inherit (config.service) build; + } // lib.optionalAttrs (cap_add != []) { + inherit cap_add; + } // lib.optionalAttrs (cap_drop != []) { + inherit cap_drop; } // lib.optionalAttrs (config.service.command != null) { inherit (config.service) command; } // lib.optionalAttrs (config.service.depends_on != []) { From ac49df440fcc2b54dba4cf1a2fa03c0baf60adbd Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 14:36:49 +0100 Subject: [PATCH 08/10] Add service.devices option --- src/nix/modules/service/docker-compose-service.nix | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix index 9f336f9..43a4c60 100644 --- a/src/nix/modules/service/docker-compose-service.nix +++ b/src/nix/modules/service/docker-compose-service.nix @@ -95,6 +95,16 @@ in default = []; description = dockerComposeRef "depends_on"; }; + service.devices = mkOption { + type = listOf str; + default = []; + description = '' + See ${link "https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities" + "docker run --device documentation"} + + ${dockerComposeRef "devices"} + ''; + }; service.links = mkOption { type = listOf str; default = []; @@ -195,6 +205,8 @@ in inherit (config.service) command; } // lib.optionalAttrs (config.service.depends_on != []) { inherit (config.service) depends_on; + } // lib.optionalAttrs (config.service.devices != []) { + inherit (config.service) devices; } // lib.optionalAttrs (config.service.entrypoint != null) { inherit (config.service) entrypoint; } // lib.optionalAttrs (config.service.env_file != []) { From c6374e09310b66a3bfc66523f39be30ed73cd897 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 14:38:07 +0100 Subject: [PATCH 09/10] nixos-init.nix: Remove /tmp -o noexec --- src/nix/modules/service/nixos-init.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nix/modules/service/nixos-init.nix b/src/nix/modules/service/nixos-init.nix index f248403..0dab936 100644 --- a/src/nix/modules/service/nixos-init.nix +++ b/src/nix/modules/service/nixos-init.nix @@ -29,9 +29,9 @@ in "/sys/fs/cgroup:/sys/fs/cgroup:ro" ]; service.tmpfs = [ - "/tmp" - "/run" - "/run/wrappers" + "/tmp:exec,mode=777" + "/run" # noexec is fine because exes should be symlinked from elsewhere anyway + "/run/wrappers" # noexec breaks this intentionally ]; service.stop_signal = "SIGRTMIN+3"; service.tty = true; From 4ce69bbfba697f65667f4b8c2b6a555d56eeafba Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 11 Mar 2019 14:40:23 +0100 Subject: [PATCH 10/10] build.image: Add nix store db, maxLayers = 100 --- src/nix/modules/service/image.nix | 38 +++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/nix/modules/service/image.nix b/src/nix/modules/service/image.nix index e98b87f..f530165 100644 --- a/src/nix/modules/service/image.nix +++ b/src/nix/modules/service/image.nix @@ -2,6 +2,36 @@ let inherit (lib) types mkOption; inherit (types) attrsOf listOf nullOr package str unspecified bool; + + # TODO: dummy-config is a useless layer. Nix 2.3 will let us inspect + # the string context instead, so we can avoid this. + contentsList = config.image.contents ++ [ + (pkgs.writeText "dummy-config.json" (builtins.toJSON config.image.rawConfig)) + ]; + + builtImage = pkgs.dockerTools.buildLayeredImage { + inherit (config.image) + name + contents + ; + config = config.image.rawConfig; + maxLayers = 100; + + # TODO: allow use of image's Nix package instead + # TODO: option to disable db generation + extraCommands = '' + echo "Generating the nix database..." + echo "Warning: only the database of the deepest Nix layer is loaded." + echo " If you want to use nix commands in the container, it would" + echo " be better to only have one layer that contains a nix store." + export NIX_REMOTE=local?root=$PWD + ${pkgs.nix}/bin/nix-store --load-db < ${pkgs.closureInfo {rootPaths = contentsList;}}/registration + mkdir -p nix/var/nix/gcroots/docker/ + for i in ${lib.concatStringsSep " " contentsList}; do + ln -s $i nix/var/nix/gcroots/docker/$(basename $i) + done; + ''; + }; in { options = { @@ -72,13 +102,7 @@ in }; }; config = { - build.image = pkgs.dockerTools.buildLayeredImage { - inherit (config.image) - name - contents - ; - config = config.image.rawConfig; - }; + build.image = builtImage; build.imageName = config.build.image.imageName; build.imageTag = if config.build.image.imageTag != ""