From 3210ce7784cb462659e7b0d29db8c40bd7b7e99f Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Sat, 23 Oct 2021 18:05:32 -0700 Subject: nixos/tests/k3s: cleanly shutdown on test success When this test was written, I don't think the explicit '.shutdown' was required, but it is now. Do so. --- nixos/tests/k3s.nix | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/k3s.nix b/nixos/tests/k3s.nix index 494a3b68b59..3d1584fa2e2 100644 --- a/nixos/tests/k3s.nix +++ b/nixos/tests/k3s.nix @@ -8,9 +8,9 @@ let contents = with pkgs; [ tini coreutils busybox ]; config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; }; + # Don't use the default service account because there's a race where it may + # not be created yet; make our own instead. testPodYaml = pkgs.writeText "test.yml" '' - # Don't use the default service account because there's a race where it may - # not be created yet; make our own instead. apiVersion: v1 kind: ServiceAccount metadata: @@ -38,7 +38,7 @@ in nodes = { k3s = { pkgs, ... }: { - environment.systemPackages = [ pkgs.k3s pkgs.gzip ]; + environment.systemPackages = with pkgs; [ k3s gzip ]; # k3s uses enough resources the default vm fails. virtualisation.memorySize = pkgs.lib.mkDefault 1536; @@ -74,5 +74,8 @@ in k3s.succeed("k3s kubectl apply -f ${testPodYaml}") k3s.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") + k3s.succeed("k3s kubectl delete -f ${testPodYaml}") + + k3s.shutdown() ''; }) -- cgit 1.4.1 From f20af9dbfbe54dfb1ba557fcc7edb316559a9130 Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Sat, 23 Oct 2021 18:21:47 -0700 Subject: nixos/tests/k3s: make more idiomatic For single-node tests, using machine is more idiomatic from what I gather, so do that. I do want multi-node tests, but those should be in different files. --- nixos/tests/k3s.nix | 53 +++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/k3s.nix b/nixos/tests/k3s.nix index 3d1584fa2e2..ff329af3c5e 100644 --- a/nixos/tests/k3s.nix +++ b/nixos/tests/k3s.nix @@ -35,47 +35,44 @@ in maintainers = [ euank ]; }; - nodes = { - k3s = - { pkgs, ... }: { - environment.systemPackages = with pkgs; [ k3s gzip ]; + machine = { pkgs, ... }: { + environment.systemPackages = with pkgs; [ k3s gzip ]; - # k3s uses enough resources the default vm fails. - virtualisation.memorySize = pkgs.lib.mkDefault 1536; - virtualisation.diskSize = pkgs.lib.mkDefault 4096; + # k3s uses enough resources the default vm fails. + virtualisation.memorySize = pkgs.lib.mkDefault 1536; + virtualisation.diskSize = pkgs.lib.mkDefault 4096; - services.k3s.enable = true; - services.k3s.role = "server"; - services.k3s.package = pkgs.k3s; - # Slightly reduce resource usage - services.k3s.extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; + services.k3s.enable = true; + services.k3s.role = "server"; + services.k3s.package = pkgs.k3s; + # Slightly reduce resource usage + services.k3s.extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; - users.users = { - noprivs = { - isNormalUser = true; - description = "Can't access k3s by default"; - password = "*"; - }; - }; + users.users = { + noprivs = { + isNormalUser = true; + description = "Can't access k3s by default"; + password = "*"; }; + }; }; testScript = '' start_all() - k3s.wait_for_unit("k3s") - k3s.succeed("k3s kubectl cluster-info") - k3s.fail("sudo -u noprivs k3s kubectl cluster-info") - # k3s.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes + machine.wait_for_unit("k3s") + machine.succeed("k3s kubectl cluster-info") + machine.fail("sudo -u noprivs k3s kubectl cluster-info") + # machine.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes - k3s.succeed( + machine.succeed( "zcat ${pauseImage} | k3s ctr image import -" ) - k3s.succeed("k3s kubectl apply -f ${testPodYaml}") - k3s.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") - k3s.succeed("k3s kubectl delete -f ${testPodYaml}") + machine.succeed("k3s kubectl apply -f ${testPodYaml}") + machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") + machine.succeed("k3s kubectl delete -f ${testPodYaml}") - k3s.shutdown() + machine.shutdown() ''; }) -- cgit 1.4.1 From 8bf9500e6576c7f6b150834ab6e55409d056979c Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Sat, 23 Oct 2021 18:29:05 -0700 Subject: nixos/tests/k3s-docker: add 'k3s.docker=true' test This verifies the docker driver works too, which matters because it interacts with systemd, cgroups, etc differently. --- nixos/tests/k3s-docker.nix | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 nixos/tests/k3s-docker.nix (limited to 'nixos/tests') diff --git a/nixos/tests/k3s-docker.nix b/nixos/tests/k3s-docker.nix new file mode 100644 index 00000000000..d6575bd6fff --- /dev/null +++ b/nixos/tests/k3s-docker.nix @@ -0,0 +1,80 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +let + # A suitable k3s pause image, also used for the test pod + pauseImage = pkgs.dockerTools.buildImage { + name = "test.local/pause"; + tag = "local"; + contents = with pkgs; [ tini coreutils busybox ]; + config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; + }; + # Don't use the default service account because there's a race where it may + # not be created yet; make our own instead. + testPodYaml = pkgs.writeText "test.yml" '' + apiVersion: v1 + kind: ServiceAccount + metadata: + name: test + --- + apiVersion: v1 + kind: Pod + metadata: + name: test + spec: + serviceAccountName: test + containers: + - name: test + image: test.local/pause:local + imagePullPolicy: Never + command: ["sh", "-c", "sleep inf"] + ''; +in +{ + name = "k3s"; + meta = with pkgs.lib.maintainers; { + maintainers = [ euank ]; + }; + + machine = { pkgs, ... }: { + environment.systemPackages = with pkgs; [ k3s gzip ]; + + # k3s uses enough resources the default vm fails. + virtualisation.memorySize = pkgs.lib.mkDefault 1536; + virtualisation.diskSize = pkgs.lib.mkDefault 4096; + + services.k3s = { + enable = true; + role = "server"; + docker = true; + # Slightly reduce resource usage + extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; + }; + + users.users = { + noprivs = { + isNormalUser = true; + description = "Can't access k3s by default"; + password = "*"; + }; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("k3s") + machine.succeed("k3s kubectl cluster-info") + machine.fail("sudo -u noprivs k3s kubectl cluster-info") + # machine.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes + + machine.succeed( + "zcat ${pauseImage} | docker load" + ) + + machine.succeed("k3s kubectl apply -f ${testPodYaml}") + machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") + machine.succeed("k3s kubectl delete -f ${testPodYaml}") + + machine.shutdown() + ''; +}) -- cgit 1.4.1 From 83ed4b46fde24845c3157dcf0923210d6423998c Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Sat, 23 Oct 2021 18:34:10 -0700 Subject: nixos/tests/k3s: nixpkgs-fmt --- nixos/tests/k3s-docker.nix | 134 ++++++++++++++++++++++----------------------- nixos/tests/k3s.nix | 130 +++++++++++++++++++++---------------------- 2 files changed, 132 insertions(+), 132 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/k3s-docker.nix b/nixos/tests/k3s-docker.nix index d6575bd6fff..62544e3ee13 100644 --- a/nixos/tests/k3s-docker.nix +++ b/nixos/tests/k3s-docker.nix @@ -1,80 +1,80 @@ import ./make-test-python.nix ({ pkgs, ... }: -let - # A suitable k3s pause image, also used for the test pod - pauseImage = pkgs.dockerTools.buildImage { - name = "test.local/pause"; - tag = "local"; - contents = with pkgs; [ tini coreutils busybox ]; - config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; - }; - # Don't use the default service account because there's a race where it may - # not be created yet; make our own instead. - testPodYaml = pkgs.writeText "test.yml" '' - apiVersion: v1 - kind: ServiceAccount - metadata: - name: test - --- - apiVersion: v1 - kind: Pod - metadata: - name: test - spec: - serviceAccountName: test - containers: - - name: test - image: test.local/pause:local - imagePullPolicy: Never - command: ["sh", "-c", "sleep inf"] - ''; -in -{ - name = "k3s"; - meta = with pkgs.lib.maintainers; { - maintainers = [ euank ]; - }; + let + # A suitable k3s pause image, also used for the test pod + pauseImage = pkgs.dockerTools.buildImage { + name = "test.local/pause"; + tag = "local"; + contents = with pkgs; [ tini coreutils busybox ]; + config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; + }; + # Don't use the default service account because there's a race where it may + # not be created yet; make our own instead. + testPodYaml = pkgs.writeText "test.yml" '' + apiVersion: v1 + kind: ServiceAccount + metadata: + name: test + --- + apiVersion: v1 + kind: Pod + metadata: + name: test + spec: + serviceAccountName: test + containers: + - name: test + image: test.local/pause:local + imagePullPolicy: Never + command: ["sh", "-c", "sleep inf"] + ''; + in + { + name = "k3s"; + meta = with pkgs.lib.maintainers; { + maintainers = [ euank ]; + }; - machine = { pkgs, ... }: { - environment.systemPackages = with pkgs; [ k3s gzip ]; + machine = { pkgs, ... }: { + environment.systemPackages = with pkgs; [ k3s gzip ]; - # k3s uses enough resources the default vm fails. - virtualisation.memorySize = pkgs.lib.mkDefault 1536; - virtualisation.diskSize = pkgs.lib.mkDefault 4096; + # k3s uses enough resources the default vm fails. + virtualisation.memorySize = pkgs.lib.mkDefault 1536; + virtualisation.diskSize = pkgs.lib.mkDefault 4096; - services.k3s = { - enable = true; - role = "server"; - docker = true; - # Slightly reduce resource usage - extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; - }; + services.k3s = { + enable = true; + role = "server"; + docker = true; + # Slightly reduce resource usage + extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; + }; - users.users = { - noprivs = { - isNormalUser = true; - description = "Can't access k3s by default"; - password = "*"; + users.users = { + noprivs = { + isNormalUser = true; + description = "Can't access k3s by default"; + password = "*"; + }; }; }; - }; - testScript = '' - start_all() + testScript = '' + start_all() - machine.wait_for_unit("k3s") - machine.succeed("k3s kubectl cluster-info") - machine.fail("sudo -u noprivs k3s kubectl cluster-info") - # machine.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes + machine.wait_for_unit("k3s") + machine.succeed("k3s kubectl cluster-info") + machine.fail("sudo -u noprivs k3s kubectl cluster-info") + # machine.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes - machine.succeed( - "zcat ${pauseImage} | docker load" - ) + machine.succeed( + "zcat ${pauseImage} | docker load" + ) - machine.succeed("k3s kubectl apply -f ${testPodYaml}") - machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") - machine.succeed("k3s kubectl delete -f ${testPodYaml}") + machine.succeed("k3s kubectl apply -f ${testPodYaml}") + machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") + machine.succeed("k3s kubectl delete -f ${testPodYaml}") - machine.shutdown() - ''; -}) + machine.shutdown() + ''; + }) diff --git a/nixos/tests/k3s.nix b/nixos/tests/k3s.nix index ff329af3c5e..dfebca50677 100644 --- a/nixos/tests/k3s.nix +++ b/nixos/tests/k3s.nix @@ -1,78 +1,78 @@ import ./make-test-python.nix ({ pkgs, ... }: -let - # A suitable k3s pause image, also used for the test pod - pauseImage = pkgs.dockerTools.buildImage { - name = "test.local/pause"; - tag = "local"; - contents = with pkgs; [ tini coreutils busybox ]; - config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; - }; - # Don't use the default service account because there's a race where it may - # not be created yet; make our own instead. - testPodYaml = pkgs.writeText "test.yml" '' - apiVersion: v1 - kind: ServiceAccount - metadata: - name: test - --- - apiVersion: v1 - kind: Pod - metadata: - name: test - spec: - serviceAccountName: test - containers: - - name: test - image: test.local/pause:local - imagePullPolicy: Never - command: ["sh", "-c", "sleep inf"] - ''; -in -{ - name = "k3s"; - meta = with pkgs.lib.maintainers; { - maintainers = [ euank ]; - }; + let + # A suitable k3s pause image, also used for the test pod + pauseImage = pkgs.dockerTools.buildImage { + name = "test.local/pause"; + tag = "local"; + contents = with pkgs; [ tini coreutils busybox ]; + config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; + }; + # Don't use the default service account because there's a race where it may + # not be created yet; make our own instead. + testPodYaml = pkgs.writeText "test.yml" '' + apiVersion: v1 + kind: ServiceAccount + metadata: + name: test + --- + apiVersion: v1 + kind: Pod + metadata: + name: test + spec: + serviceAccountName: test + containers: + - name: test + image: test.local/pause:local + imagePullPolicy: Never + command: ["sh", "-c", "sleep inf"] + ''; + in + { + name = "k3s"; + meta = with pkgs.lib.maintainers; { + maintainers = [ euank ]; + }; - machine = { pkgs, ... }: { - environment.systemPackages = with pkgs; [ k3s gzip ]; + machine = { pkgs, ... }: { + environment.systemPackages = with pkgs; [ k3s gzip ]; - # k3s uses enough resources the default vm fails. - virtualisation.memorySize = pkgs.lib.mkDefault 1536; - virtualisation.diskSize = pkgs.lib.mkDefault 4096; + # k3s uses enough resources the default vm fails. + virtualisation.memorySize = pkgs.lib.mkDefault 1536; + virtualisation.diskSize = pkgs.lib.mkDefault 4096; - services.k3s.enable = true; - services.k3s.role = "server"; - services.k3s.package = pkgs.k3s; - # Slightly reduce resource usage - services.k3s.extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; + services.k3s.enable = true; + services.k3s.role = "server"; + services.k3s.package = pkgs.k3s; + # Slightly reduce resource usage + services.k3s.extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; - users.users = { - noprivs = { - isNormalUser = true; - description = "Can't access k3s by default"; - password = "*"; + users.users = { + noprivs = { + isNormalUser = true; + description = "Can't access k3s by default"; + password = "*"; + }; }; }; - }; - testScript = '' - start_all() + testScript = '' + start_all() - machine.wait_for_unit("k3s") - machine.succeed("k3s kubectl cluster-info") - machine.fail("sudo -u noprivs k3s kubectl cluster-info") - # machine.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes + machine.wait_for_unit("k3s") + machine.succeed("k3s kubectl cluster-info") + machine.fail("sudo -u noprivs k3s kubectl cluster-info") + # machine.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes - machine.succeed( - "zcat ${pauseImage} | k3s ctr image import -" - ) + machine.succeed( + "zcat ${pauseImage} | k3s ctr image import -" + ) - machine.succeed("k3s kubectl apply -f ${testPodYaml}") - machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") - machine.succeed("k3s kubectl delete -f ${testPodYaml}") + machine.succeed("k3s kubectl apply -f ${testPodYaml}") + machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") + machine.succeed("k3s kubectl delete -f ${testPodYaml}") - machine.shutdown() - ''; -}) + machine.shutdown() + ''; + }) -- cgit 1.4.1 From 3c61779d5c15de7e9a34e3f0eb8e59f4dbadc1cc Mon Sep 17 00:00:00 2001 From: Euan Kemp Date: Mon, 25 Oct 2021 17:43:27 -0700 Subject: nixos/tests/k3s: address pr feedback --- nixos/tests/k3s-docker.nix | 80 -------------------------------- nixos/tests/k3s-single-node-docker.nix | 84 ++++++++++++++++++++++++++++++++++ nixos/tests/k3s-single-node.nix | 82 +++++++++++++++++++++++++++++++++ nixos/tests/k3s.nix | 78 ------------------------------- 4 files changed, 166 insertions(+), 158 deletions(-) delete mode 100644 nixos/tests/k3s-docker.nix create mode 100644 nixos/tests/k3s-single-node-docker.nix create mode 100644 nixos/tests/k3s-single-node.nix delete mode 100644 nixos/tests/k3s.nix (limited to 'nixos/tests') diff --git a/nixos/tests/k3s-docker.nix b/nixos/tests/k3s-docker.nix deleted file mode 100644 index 62544e3ee13..00000000000 --- a/nixos/tests/k3s-docker.nix +++ /dev/null @@ -1,80 +0,0 @@ -import ./make-test-python.nix ({ pkgs, ... }: - - let - # A suitable k3s pause image, also used for the test pod - pauseImage = pkgs.dockerTools.buildImage { - name = "test.local/pause"; - tag = "local"; - contents = with pkgs; [ tini coreutils busybox ]; - config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; - }; - # Don't use the default service account because there's a race where it may - # not be created yet; make our own instead. - testPodYaml = pkgs.writeText "test.yml" '' - apiVersion: v1 - kind: ServiceAccount - metadata: - name: test - --- - apiVersion: v1 - kind: Pod - metadata: - name: test - spec: - serviceAccountName: test - containers: - - name: test - image: test.local/pause:local - imagePullPolicy: Never - command: ["sh", "-c", "sleep inf"] - ''; - in - { - name = "k3s"; - meta = with pkgs.lib.maintainers; { - maintainers = [ euank ]; - }; - - machine = { pkgs, ... }: { - environment.systemPackages = with pkgs; [ k3s gzip ]; - - # k3s uses enough resources the default vm fails. - virtualisation.memorySize = pkgs.lib.mkDefault 1536; - virtualisation.diskSize = pkgs.lib.mkDefault 4096; - - services.k3s = { - enable = true; - role = "server"; - docker = true; - # Slightly reduce resource usage - extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; - }; - - users.users = { - noprivs = { - isNormalUser = true; - description = "Can't access k3s by default"; - password = "*"; - }; - }; - }; - - testScript = '' - start_all() - - machine.wait_for_unit("k3s") - machine.succeed("k3s kubectl cluster-info") - machine.fail("sudo -u noprivs k3s kubectl cluster-info") - # machine.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes - - machine.succeed( - "zcat ${pauseImage} | docker load" - ) - - machine.succeed("k3s kubectl apply -f ${testPodYaml}") - machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") - machine.succeed("k3s kubectl delete -f ${testPodYaml}") - - machine.shutdown() - ''; - }) diff --git a/nixos/tests/k3s-single-node-docker.nix b/nixos/tests/k3s-single-node-docker.nix new file mode 100644 index 00000000000..7f3d15788b0 --- /dev/null +++ b/nixos/tests/k3s-single-node-docker.nix @@ -0,0 +1,84 @@ +import ./make-test-python.nix ({ pkgs, ... }: + + let + imageEnv = pkgs.buildEnv { + name = "k3s-pause-image-env"; + paths = with pkgs; [ tini (hiPrio coreutils) busybox ]; + }; + pauseImage = pkgs.dockerTools.streamLayeredImage { + name = "test.local/pause"; + tag = "local"; + contents = imageEnv; + config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; + }; + # Don't use the default service account because there's a race where it may + # not be created yet; make our own instead. + testPodYaml = pkgs.writeText "test.yml" '' + apiVersion: v1 + kind: ServiceAccount + metadata: + name: test + --- + apiVersion: v1 + kind: Pod + metadata: + name: test + spec: + serviceAccountName: test + containers: + - name: test + image: test.local/pause:local + imagePullPolicy: Never + command: ["sh", "-c", "sleep inf"] + ''; + in + { + name = "k3s"; + meta = with pkgs.lib.maintainers; { + maintainers = [ euank ]; + }; + + machine = { pkgs, ... }: { + environment.systemPackages = with pkgs; [ k3s gzip ]; + + # k3s uses enough resources the default vm fails. + virtualisation.memorySize = 1536; + virtualisation.diskSize = 4096; + + services.k3s = { + enable = true; + role = "server"; + docker = true; + # Slightly reduce resource usage + extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; + }; + + users.users = { + noprivs = { + isNormalUser = true; + description = "Can't access k3s by default"; + password = "*"; + }; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("k3s") + machine.succeed("k3s kubectl cluster-info") + machine.fail("sudo -u noprivs k3s kubectl cluster-info") + # FIXME: this fails with the current nixos kernel config; once it passes, we should uncomment it + # machine.succeed("k3s check-config") + + machine.succeed( + "${pauseImage} | docker load" + ) + + machine.succeed("k3s kubectl apply -f ${testPodYaml}") + machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") + machine.succeed("k3s kubectl delete -f ${testPodYaml}") + + machine.shutdown() + ''; + }) diff --git a/nixos/tests/k3s-single-node.nix b/nixos/tests/k3s-single-node.nix new file mode 100644 index 00000000000..d98f20d468c --- /dev/null +++ b/nixos/tests/k3s-single-node.nix @@ -0,0 +1,82 @@ +import ./make-test-python.nix ({ pkgs, ... }: + + let + imageEnv = pkgs.buildEnv { + name = "k3s-pause-image-env"; + paths = with pkgs; [ tini (hiPrio coreutils) busybox ]; + }; + pauseImage = pkgs.dockerTools.streamLayeredImage { + name = "test.local/pause"; + tag = "local"; + contents = imageEnv; + config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; + }; + # Don't use the default service account because there's a race where it may + # not be created yet; make our own instead. + testPodYaml = pkgs.writeText "test.yml" '' + apiVersion: v1 + kind: ServiceAccount + metadata: + name: test + --- + apiVersion: v1 + kind: Pod + metadata: + name: test + spec: + serviceAccountName: test + containers: + - name: test + image: test.local/pause:local + imagePullPolicy: Never + command: ["sh", "-c", "sleep inf"] + ''; + in + { + name = "k3s"; + meta = with pkgs.lib.maintainers; { + maintainers = [ euank ]; + }; + + machine = { pkgs, ... }: { + environment.systemPackages = with pkgs; [ k3s gzip ]; + + # k3s uses enough resources the default vm fails. + virtualisation.memorySize = 1536; + virtualisation.diskSize = 4096; + + services.k3s.enable = true; + services.k3s.role = "server"; + services.k3s.package = pkgs.k3s; + # Slightly reduce resource usage + services.k3s.extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; + + users.users = { + noprivs = { + isNormalUser = true; + description = "Can't access k3s by default"; + password = "*"; + }; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("k3s") + machine.succeed("k3s kubectl cluster-info") + machine.fail("sudo -u noprivs k3s kubectl cluster-info") + # FIXME: this fails with the current nixos kernel config; once it passes, we should uncomment it + # machine.succeed("k3s check-config") + + machine.succeed( + "${pauseImage} | k3s ctr image import -" + ) + + machine.succeed("k3s kubectl apply -f ${testPodYaml}") + machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") + machine.succeed("k3s kubectl delete -f ${testPodYaml}") + + machine.shutdown() + ''; + }) diff --git a/nixos/tests/k3s.nix b/nixos/tests/k3s.nix deleted file mode 100644 index dfebca50677..00000000000 --- a/nixos/tests/k3s.nix +++ /dev/null @@ -1,78 +0,0 @@ -import ./make-test-python.nix ({ pkgs, ... }: - - let - # A suitable k3s pause image, also used for the test pod - pauseImage = pkgs.dockerTools.buildImage { - name = "test.local/pause"; - tag = "local"; - contents = with pkgs; [ tini coreutils busybox ]; - config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ]; - }; - # Don't use the default service account because there's a race where it may - # not be created yet; make our own instead. - testPodYaml = pkgs.writeText "test.yml" '' - apiVersion: v1 - kind: ServiceAccount - metadata: - name: test - --- - apiVersion: v1 - kind: Pod - metadata: - name: test - spec: - serviceAccountName: test - containers: - - name: test - image: test.local/pause:local - imagePullPolicy: Never - command: ["sh", "-c", "sleep inf"] - ''; - in - { - name = "k3s"; - meta = with pkgs.lib.maintainers; { - maintainers = [ euank ]; - }; - - machine = { pkgs, ... }: { - environment.systemPackages = with pkgs; [ k3s gzip ]; - - # k3s uses enough resources the default vm fails. - virtualisation.memorySize = pkgs.lib.mkDefault 1536; - virtualisation.diskSize = pkgs.lib.mkDefault 4096; - - services.k3s.enable = true; - services.k3s.role = "server"; - services.k3s.package = pkgs.k3s; - # Slightly reduce resource usage - services.k3s.extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local"; - - users.users = { - noprivs = { - isNormalUser = true; - description = "Can't access k3s by default"; - password = "*"; - }; - }; - }; - - testScript = '' - start_all() - - machine.wait_for_unit("k3s") - machine.succeed("k3s kubectl cluster-info") - machine.fail("sudo -u noprivs k3s kubectl cluster-info") - # machine.succeed("k3s check-config") # fails with the current nixos kernel config, uncomment once this passes - - machine.succeed( - "zcat ${pauseImage} | k3s ctr image import -" - ) - - machine.succeed("k3s kubectl apply -f ${testPodYaml}") - machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") - machine.succeed("k3s kubectl delete -f ${testPodYaml}") - - machine.shutdown() - ''; - }) -- cgit 1.4.1 From a43c2c1e70affcd96d983081e75b7afcb996ca92 Mon Sep 17 00:00:00 2001 From: ajs124 Date: Tue, 30 Nov 2021 01:29:43 +0100 Subject: Revert "nixos/tests/installer: lvm: test lvm2-pvscan@ units" This reverts commit 53a34361afe7b58db95c77cd99f268fd933e4053. --- nixos/tests/installer.nix | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index bc41b6efc2e..cf00bcafe4f 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -561,26 +561,16 @@ in { + " mkpart primary 2048M -1s" # PV2 + " set 2 lvm on", "udevadm settle", - "sleep 1", "pvcreate /dev/vda1 /dev/vda2", - "sleep 1", "vgcreate MyVolGroup /dev/vda1 /dev/vda2", - "sleep 1", "lvcreate --size 1G --name swap MyVolGroup", - "sleep 1", "lvcreate --size 3G --name nixos MyVolGroup", - "sleep 1", "mkswap -f /dev/MyVolGroup/swap -L swap", "swapon -L swap", "mkfs.xfs -L nixos /dev/MyVolGroup/nixos", "mount LABEL=nixos /mnt", ) ''; - postBootCommands = '' - assert "loaded active" in machine.succeed( - "systemctl list-units 'lvm2-pvscan@*' -ql --no-legend | tee /dev/stderr" - ) - ''; }; # Boot off an encrypted root partition with the default LUKS header format -- cgit 1.4.1 From 2f0cfde482e780dd7abcc8b4e1f51d160f74f7aa Mon Sep 17 00:00:00 2001 From: Michael Hoang Date: Sun, 19 Dec 2021 15:53:47 +1100 Subject: tests/systemd-boot: Add tests for `extraFiles` --- nixos/tests/systemd-boot.nix | 141 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) (limited to 'nixos/tests') diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix index c3899b58d6b..51cfd82e6c4 100644 --- a/nixos/tests/systemd-boot.nix +++ b/nixos/tests/systemd-boot.nix @@ -110,4 +110,145 @@ in assert "updating systemd-boot from (000.0-1-notnixos) to " in output ''; }; + + memtest86 = makeTest { + name = "systemd-boot-memtest86"; + meta.maintainers = with pkgs.lib.maintainers; [ Enzime ]; + + machine = { pkgs, lib, ... }: { + imports = [ common ]; + boot.loader.systemd-boot.memtest86.enable = true; + nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ + "memtest86-efi" + ]; + }; + + testScript = '' + machine.succeed("test -e /boot/loader/entries/memtest86.conf") + machine.succeed("test -e /boot/efi/memtest86/BOOTX64.efi") + ''; + }; + + netbootxyz = makeTest { + name = "systemd-boot-netbootxyz"; + meta.maintainers = with pkgs.lib.maintainers; [ Enzime ]; + + machine = { pkgs, lib, ... }: { + imports = [ common ]; + boot.loader.systemd-boot.netbootxyz.enable = true; + }; + + testScript = '' + machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf") + machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") + ''; + }; + + entryFilename = makeTest { + name = "systemd-boot-entry-filename"; + meta.maintainers = with pkgs.lib.maintainers; [ Enzime ]; + + machine = { pkgs, lib, ... }: { + imports = [ common ]; + boot.loader.systemd-boot.memtest86.enable = true; + boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf"; + nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ + "memtest86-efi" + ]; + }; + + testScript = '' + machine.fail("test -e /boot/loader/entries/memtest86.conf") + machine.succeed("test -e /boot/loader/entries/apple.conf") + machine.succeed("test -e /boot/efi/memtest86/BOOTX64.efi") + ''; + }; + + extraEntries = makeTest { + name = "systemd-boot-extra-entries"; + meta.maintainers = with pkgs.lib.maintainers; [ Enzime ]; + + machine = { pkgs, lib, ... }: { + imports = [ common ]; + boot.loader.systemd-boot.extraEntries = { + "banana.conf" = '' + title banana + ''; + }; + }; + + testScript = '' + machine.succeed("test -e /boot/loader/entries/banana.conf") + machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf") + ''; + }; + + extraFiles = makeTest { + name = "systemd-boot-extra-files"; + meta.maintainers = with pkgs.lib.maintainers; [ Enzime ]; + + machine = { pkgs, lib, ... }: { + imports = [ common ]; + boot.loader.systemd-boot.extraFiles = { + "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; + }; + }; + + testScript = '' + machine.succeed("test -e /boot/efi/fruits/tomato.efi") + machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + ''; + }; + + switch-test = makeTest { + name = "systemd-boot-switch-test"; + meta.maintainers = with pkgs.lib.maintainers; [ Enzime ]; + + nodes = { + inherit common; + + machine = { pkgs, ... }: { + imports = [ common ]; + boot.loader.systemd-boot.extraFiles = { + "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi; + }; + }; + + with_netbootxyz = { pkgs, ... }: { + imports = [ common ]; + boot.loader.systemd-boot.netbootxyz.enable = true; + }; + }; + + testScript = { nodes, ... }: let + originalSystem = nodes.machine.config.system.build.toplevel; + baseSystem = nodes.common.config.system.build.toplevel; + finalSystem = nodes.with_netbootxyz.config.system.build.toplevel; + in '' + machine.succeed("test -e /boot/efi/fruits/tomato.efi") + machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + + with subtest("remove files when no longer needed"): + machine.succeed("${baseSystem}/bin/switch-to-configuration boot") + machine.fail("test -e /boot/efi/fruits/tomato.efi") + machine.fail("test -d /boot/efi/fruits") + machine.succeed("test -d /boot/efi/nixos/.extra-files") + machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits") + + with subtest("files are added back when needed again"): + machine.succeed("${originalSystem}/bin/switch-to-configuration boot") + machine.succeed("test -e /boot/efi/fruits/tomato.efi") + machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + + with subtest("simultaneously removing and adding files works"): + machine.succeed("${finalSystem}/bin/switch-to-configuration boot") + machine.fail("test -e /boot/efi/fruits/tomato.efi") + machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi") + machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf") + machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi") + machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/o_netbootxyz.conf") + machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi") + ''; + }; } -- cgit 1.4.1 From ea0276523a26f661d7f783f7d036d113cc6e1d5e Mon Sep 17 00:00:00 2001 From: Cleeyv Date: Thu, 30 Dec 2021 23:33:10 -0500 Subject: nixos/tests/jibri: updated test with a new log message --- nixos/tests/jibri.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nixos/tests') diff --git a/nixos/tests/jibri.nix b/nixos/tests/jibri.nix index 3dd28e6aac1..af20e639d30 100644 --- a/nixos/tests/jibri.nix +++ b/nixos/tests/jibri.nix @@ -63,7 +63,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { """sleep 15 && curl -H "Content-Type: application/json" -X POST http://localhost:2222/jibri/api/v1.0/stopService -d '{"sessionId": "RecordTest","callParams":{"callUrlInfo":{"baseUrl": "https://machine","callName": "TestCall"}},"callLoginParams":{"domain": "recorder.machine", "username": "recorder", "password": "'"$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"'" },"sinkType": "file"}'""" ) machine.wait_until_succeeds( - "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Recording finalize script finished with exit value 0'", timeout=36 + "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Finalize script finished with exit value 0'", timeout=36 ) ''; }) -- cgit 1.4.1 From 4e1556ed4d43da1f930b3fcf0fc20d827a34f3d2 Mon Sep 17 00:00:00 2001 From: Patrick Hilhorst Date: Sat, 1 Jan 2022 22:35:20 +0100 Subject: nixos/test-driver: add polling_condition --- .../development/writing-nixos-tests.section.md | 51 ++ .../development/writing-nixos-tests.section.xml | 846 +++++++++++---------- nixos/lib/test-driver/test_driver/driver.py | 40 +- nixos/lib/test-driver/test_driver/machine.py | 6 + .../test-driver/test_driver/polling_condition.py | 90 +++ nixos/tests/vscodium.nix | 50 +- 6 files changed, 673 insertions(+), 410 deletions(-) create mode 100644 nixos/lib/test-driver/test_driver/polling_condition.py (limited to 'nixos/tests') diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md index d9749d37da7..7de57d0d2a3 100644 --- a/nixos/doc/manual/development/writing-nixos-tests.section.md +++ b/nixos/doc/manual/development/writing-nixos-tests.section.md @@ -88,6 +88,8 @@ starting them in parallel: start_all() ``` +## Machine objects {#ssec-machine-objects} + The following methods are available on machine objects: `start` @@ -313,3 +315,52 @@ repository): # fmt: on ''; ``` + +## Failing tests early {#ssec-failing-tests-early} + +To fail tests early when certain invariables are no longer met (instead of waiting for the build to time out), the decorator `polling_condition` is provided. For example, if we are testing a program `foo` that should not quit after being started, we might write the following: + +```py +@polling_condition +def foo_running(): + machine.succeed("pgrep -x foo") + + +machine.succeed("foo --start") +machine.wait_until_succeeds("pgrep -x foo") + +with foo_running: + ... # Put `foo` through its paces +``` + + +`polling_condition` takes the following (optional) arguments: + +`seconds_interval` + +: + specifies how often the condition should be polled: + + ```py + @polling_condition(seconds_interval=10) + def foo_running(): + machine.succeed("pgrep -x foo") + ``` + +`description` + +: + is used in the log when the condition is checked. If this is not provided, the description is pulled from the docstring of the function. These two are therefore equivalent: + + ```py + @polling_condition + def foo_running(): + "check that foo is running" + machine.succeed("pgrep -x foo") + ``` + + ```py + @polling_condition(description="check that foo is running") + def foo_running(): + machine.succeed("pgrep -x foo") + ``` diff --git a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml index 0d523681b63..45c9c40c609 100644 --- a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml +++ b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml @@ -117,407 +117,413 @@ if not "Linux" in machine.succeed("uname"): start_all() - - The following methods are available on machine objects: - - - - - start - - - - Start the virtual machine. This method is asynchronous — it - does not wait for the machine to finish booting. - - - - - - shutdown - - - - Shut down the machine, waiting for the VM to exit. - - - - - - crash - - - - Simulate a sudden power failure, by telling the VM to exit - immediately. - - - - - - block - - - - Simulate unplugging the Ethernet cable that connects the - machine to the other machines. - - - - - - unblock - - - - Undo the effect of block. - - - - - - screenshot - - - - Take a picture of the display of the virtual machine, in PNG - format. The screenshot is linked from the HTML log. - - - - - - get_screen_text_variants - - - - Return a list of different interpretations of what is - currently visible on the machine's screen using optical - character recognition. The number and order of the - interpretations is not specified and is subject to change, but - if no exception is raised at least one will be returned. - - +
+ Machine objects + + The following methods are available on machine objects: + + + + + start + + - This requires passing enableOCR to the - test attribute set. + Start the virtual machine. This method is asynchronous — it + does not wait for the machine to finish booting. - - - - - - get_screen_text - - - - Return a textual representation of what is currently visible - on the machine's screen using optical character recognition. - - + + + + + shutdown + + - This requires passing enableOCR to the - test attribute set. + Shut down the machine, waiting for the VM to exit. - - - - - - send_monitor_command - - - - Send a command to the QEMU monitor. This is rarely used, but - allows doing stuff such as attaching virtual USB disks to a - running machine. - - - - - - send_key - - - - Simulate pressing keys on the virtual keyboard, e.g., - send_key("ctrl-alt-delete"). - - - - - - send_chars - - - - Simulate typing a sequence of characters on the virtual - keyboard, e.g., - send_chars("foobar\n") will type - the string foobar followed by the Enter - key. - - - - - - execute - - - - Execute a shell command, returning a list - (status, stdout). If the command detaches, - it must close stdout, as execute will wait - for this to consume all output reliably. This can be achieved - by redirecting stdout to stderr >&2, - to /dev/console, - /dev/null or a file. Examples of detaching - commands are sleep 365d &, where the - shell forks a new process that can write to stdout and - xclip -i, where the - xclip command itself forks without closing - stdout. Takes an optional parameter - check_return that defaults to - True. Setting this parameter to - False will not check for the return code - and return -1 instead. This can be used for commands that shut - down the VM and would therefore break the pipe that would be - used for retrieving the return code. - - - - - - succeed - - - - Execute a shell command, raising an exception if the exit - status is not zero, otherwise returning the standard output. - Commands are run with set -euo pipefail - set: - - - - - If several commands are separated by ; - and one fails, the command as a whole will fail. - - - + + + + + crash + + + + Simulate a sudden power failure, by telling the VM to exit + immediately. + + + + + + block + + + + Simulate unplugging the Ethernet cable that connects the + machine to the other machines. + + + + + + unblock + + + + Undo the effect of block. + + + + + + screenshot + + + + Take a picture of the display of the virtual machine, in PNG + format. The screenshot is linked from the HTML log. + + + + + + get_screen_text_variants + + + + Return a list of different interpretations of what is + currently visible on the machine's screen using optical + character recognition. The number and order of the + interpretations is not specified and is subject to change, + but if no exception is raised at least one will be returned. + + - For pipelines, the last non-zero exit status will be - returned (if there is one, zero will be returned - otherwise). + This requires passing enableOCR to the + test attribute set. - - + + + + + + get_screen_text + + + + Return a textual representation of what is currently visible + on the machine's screen using optical character recognition. + + - Dereferencing unset variables fail the command. + This requires passing enableOCR to the + test attribute set. - - + + + + + + send_monitor_command + + + + Send a command to the QEMU monitor. This is rarely used, but + allows doing stuff such as attaching virtual USB disks to a + running machine. + + + + + + send_key + + + + Simulate pressing keys on the virtual keyboard, e.g., + send_key("ctrl-alt-delete"). + + + + + + send_chars + + + + Simulate typing a sequence of characters on the virtual + keyboard, e.g., + send_chars("foobar\n") will + type the string foobar followed by the + Enter key. + + + + + + execute + + + + Execute a shell command, returning a list + (status, stdout). If the command + detaches, it must close stdout, as + execute will wait for this to consume all + output reliably. This can be achieved by redirecting stdout + to stderr >&2, to + /dev/console, + /dev/null or a file. Examples of + detaching commands are sleep 365d &, + where the shell forks a new process that can write to stdout + and xclip -i, where the + xclip command itself forks without + closing stdout. Takes an optional parameter + check_return that defaults to + True. Setting this parameter to + False will not check for the return code + and return -1 instead. This can be used for commands that + shut down the VM and would therefore break the pipe that + would be used for retrieving the return code. + + + + + + succeed + + + + Execute a shell command, raising an exception if the exit + status is not zero, otherwise returning the standard output. + Commands are run with set -euo pipefail + set: + + + + + If several commands are separated by + ; and one fails, the command as a + whole will fail. + + + + + For pipelines, the last non-zero exit status will be + returned (if there is one, zero will be returned + otherwise). + + + + + Dereferencing unset variables fail the command. + + + + + It will wait for stdout to be closed. See + execute for the implications. + + + + + + + + fail + + + + Like succeed, but raising an exception if + the command returns a zero status. + + + + + + wait_until_succeeds + + + + Repeat a shell command with 1-second intervals until it + succeeds. + + + + + + wait_until_fails + + + + Repeat a shell command with 1-second intervals until it + fails. + + + + + + wait_for_unit + + + + Wait until the specified systemd unit has reached the + active state. + + + + + + wait_for_file + + + + Wait until the specified file exists. + + + + + + wait_for_open_port + + + + Wait until a process is listening on the given TCP port (on + localhost, at least). + + + + + + wait_for_closed_port + + + + Wait until nobody is listening on the given TCP port. + + + + + + wait_for_x + + + + Wait until the X11 server is accepting connections. + + + + + + wait_for_text + + + + Wait until the supplied regular expressions matches the + textual contents of the screen by using optical character + recognition (see get_screen_text and + get_screen_text_variants). + + - It will wait for stdout to be closed. See - execute for the implications. + This requires passing enableOCR to the + test attribute set. - - - - - - - fail - - - - Like succeed, but raising an exception if - the command returns a zero status. - - - - - - wait_until_succeeds - - - - Repeat a shell command with 1-second intervals until it - succeeds. - - - - - - wait_until_fails - - - - Repeat a shell command with 1-second intervals until it fails. - - - - - - wait_for_unit - - - - Wait until the specified systemd unit has reached the - active state. - - - - - - wait_for_file - - - - Wait until the specified file exists. - - - - - - wait_for_open_port - - - - Wait until a process is listening on the given TCP port (on - localhost, at least). - - - - - - wait_for_closed_port - - - - Wait until nobody is listening on the given TCP port. - - - - - - wait_for_x - - - - Wait until the X11 server is accepting connections. - - - - - - wait_for_text - - - - Wait until the supplied regular expressions matches the - textual contents of the screen by using optical character - recognition (see get_screen_text and - get_screen_text_variants). - - + + + + + + wait_for_console_text + + - This requires passing enableOCR to the - test attribute set. + Wait until the supplied regular expressions match a line of + the serial console output. This method is useful when OCR is + not possibile or accurate enough. - - - - - - wait_for_console_text - - - - Wait until the supplied regular expressions match a line of - the serial console output. This method is useful when OCR is - not possibile or accurate enough. - - - - - - wait_for_window - - - - Wait until an X11 window has appeared whose name matches the - given regular expression, e.g., - wait_for_window("Terminal"). - - - - - - copy_from_host - - - - Copies a file from host to machine, e.g., - copy_from_host("myfile", "/etc/my/important/file"). - - - The first argument is the file on the host. The file needs to - be accessible while building the nix derivation. The second - argument is the location of the file on the machine. - - - - - - systemctl - - - - Runs systemctl commands with optional - support for systemctl --user - - + + + + + wait_for_window + + + + Wait until an X11 window has appeared whose name matches the + given regular expression, e.g., + wait_for_window("Terminal"). + + + + + + copy_from_host + + + + Copies a file from host to machine, e.g., + copy_from_host("myfile", "/etc/my/important/file"). + + + The first argument is the file on the host. The file needs + to be accessible while building the nix derivation. The + second argument is the location of the file on the machine. + + + + + + systemctl + + + + Runs systemctl commands with optional + support for systemctl --user + + machine.systemctl("list-jobs --no-pager") # runs `systemctl list-jobs --no-pager` machine.systemctl("list-jobs --no-pager", "any-user") # spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager` - - - - - shell_interact - - - - Allows you to directly interact with the guest shell. This - should only be used during test development, not in production - tests. Killing the interactive session with - Ctrl-d or Ctrl-c also - ends the guest session. - - - - - - To test user units declared by - systemd.user.services the optional - user argument can be used: - - + + + + + shell_interact + + + + Allows you to directly interact with the guest shell. This + should only be used during test development, not in + production tests. Killing the interactive session with + Ctrl-d or Ctrl-c also + ends the guest session. + + + + + + To test user units declared by + systemd.user.services the optional + user argument can be used: + + machine.start() machine.wait_for_x() machine.wait_for_unit("xautolock.service", "x-session-user") - - This applies to systemctl, - get_unit_info, wait_for_unit, - start_job and stop_job. - - - For faster dev cycles it's also possible to disable the code-linters - (this shouldn't be commited though): - - + + This applies to systemctl, + get_unit_info, + wait_for_unit, start_job and + stop_job. + + + For faster dev cycles it's also possible to disable the + code-linters (this shouldn't be commited though): + + import ./make-test-python.nix { skipLint = true; machine = @@ -531,13 +537,13 @@ import ./make-test-python.nix { ''; } - - This will produce a Nix warning at evaluation time. To fully disable - the linter, wrap the test script in comment directives to disable - the Black linter directly (again, don't commit this within the - Nixpkgs repository): - - + + This will produce a Nix warning at evaluation time. To fully + disable the linter, wrap the test script in comment directives to + disable the Black linter directly (again, don't commit this within + the Nixpkgs repository): + + testScript = '' # fmt: off @@ -545,4 +551,66 @@ import ./make-test-python.nix { # fmt: on ''; +
+
+ Failing tests early + + To fail tests early when certain invariables are no longer met + (instead of waiting for the build to time out), the decorator + polling_condition is provided. For example, if + we are testing a program foo that should not + quit after being started, we might write the following: + + +@polling_condition +def foo_running(): + machine.succeed("pgrep -x foo") + + +machine.succeed("foo --start") +machine.wait_until_succeeds("pgrep -x foo") + +with foo_running: + ... # Put `foo` through its paces + + + polling_condition takes the following + (optional) arguments: + + + seconds_interval + + + : specifies how often the condition should be polled: + + +```py +@polling_condition(seconds_interval=10) +def foo_running(): + machine.succeed("pgrep -x foo") +``` + + + description + + + : is used in the log when the condition is checked. If this is not + provided, the description is pulled from the docstring of the + function. These two are therefore equivalent: + + +```py +@polling_condition +def foo_running(): + "check that foo is running" + machine.succeed("pgrep -x foo") +``` + +```py +@polling_condition(description="check that foo is running") +def foo_running(): + machine.succeed("pgrep -x foo") +``` + +
diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py index f3af98537ad..e22f9ee7a75 100644 --- a/nixos/lib/test-driver/test_driver/driver.py +++ b/nixos/lib/test-driver/test_driver/driver.py @@ -1,12 +1,13 @@ from contextlib import contextmanager from pathlib import Path -from typing import Any, Dict, Iterator, List +from typing import Any, Dict, Iterator, List, Union, Optional, Callable, ContextManager import os import tempfile from test_driver.logger import rootlog from test_driver.machine import Machine, NixStartScript, retry from test_driver.vlan import VLan +from test_driver.polling_condition import PollingCondition class Driver: @@ -16,6 +17,7 @@ class Driver: tests: str vlans: List[VLan] machines: List[Machine] + polling_conditions: List[Callable] def __init__( self, @@ -36,12 +38,15 @@ class Driver: for s in scripts: yield NixStartScript(s) + self.polling_conditions = [] + self.machines = [ Machine( start_command=cmd, keep_vm_state=keep_vm_state, name=cmd.machine_name, tmp_dir=tmp_dir, + fail_early=self.fail_early, ) for cmd in cmd(start_scripts) ] @@ -84,6 +89,7 @@ class Driver: retry=retry, serial_stdout_off=self.serial_stdout_off, serial_stdout_on=self.serial_stdout_on, + polling_condition=self.polling_condition, Machine=Machine, # for typing ) machine_symbols = {m.name: m for m in self.machines} @@ -159,3 +165,35 @@ class Driver: def serial_stdout_off(self) -> None: rootlog._print_serial_logs = False + + def fail_early(self) -> bool: + return any(not f() for f in self.polling_conditions) + + def polling_condition( + self, + fun_: Optional[Callable] = None, + *, + seconds_interval: float = 2.0, + description: Optional[str] = None, + ) -> Union[Callable[[Callable], ContextManager], ContextManager]: + driver = self + + class Poll: + def __init__(self, fun: Callable): + self.condition = PollingCondition( + fun, + seconds_interval, + description, + ).check + + def __enter__(self) -> None: + driver.polling_conditions.append(self.condition) + + def __exit__(self, a, b, c) -> None: # type: ignore + res = driver.polling_conditions.pop() + assert res is self.condition + + if fun_ is None: + return Poll + else: + return Poll(fun_) diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py index b3dbe5126fc..dbf9fd24486 100644 --- a/nixos/lib/test-driver/test_driver/machine.py +++ b/nixos/lib/test-driver/test_driver/machine.py @@ -17,6 +17,7 @@ import threading import time from test_driver.logger import rootlog +from test_driver.polling_condition import PollingCondition, coopmulti CHAR_TO_KEY = { "A": "shift-a", @@ -318,6 +319,7 @@ class Machine: # Store last serial console lines for use # of wait_for_console_text last_lines: Queue = Queue() + fail_early: Callable def __repr__(self) -> str: return f"" @@ -329,12 +331,14 @@ class Machine: name: str = "machine", keep_vm_state: bool = False, allow_reboot: bool = False, + fail_early: Callable = lambda: False, ) -> None: self.tmp_dir = tmp_dir self.keep_vm_state = keep_vm_state self.allow_reboot = allow_reboot self.name = name self.start_command = start_command + self.fail_early = fail_early # set up directories self.shared_dir = self.tmp_dir / "shared-xchg" @@ -405,6 +409,7 @@ class Machine: break return answer + @coopmulti def send_monitor_command(self, command: str) -> str: with self.nested("sending monitor command: {}".format(command)): message = ("{}\n".format(command)).encode() @@ -506,6 +511,7 @@ class Machine: break return "".join(output_buffer) + @coopmulti def execute( self, command: str, check_return: bool = True, timeout: Optional[int] = 900 ) -> Tuple[int, str]: diff --git a/nixos/lib/test-driver/test_driver/polling_condition.py b/nixos/lib/test-driver/test_driver/polling_condition.py new file mode 100644 index 00000000000..f38dea71376 --- /dev/null +++ b/nixos/lib/test-driver/test_driver/polling_condition.py @@ -0,0 +1,90 @@ +from typing import Callable, Optional, Any, List, Dict +from functools import wraps + +import time + +from .logger import rootlog + + +class PollingConditionFailed(Exception): + pass + + +def coopmulti(fun: Callable, *, machine: Any = None) -> Callable: + assert not (fun is None and machine is None) + + def inner(fun_: Callable) -> Any: + @wraps(fun_) + def wrapper(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: + this_machine = args[0] if machine is None else machine + + if this_machine.fail_early(): # type: ignore + raise PollingConditionFailed("Action interrupted early...") + + return fun_(*args, **kwargs) + + return wrapper + + if fun is None: + return inner + else: + return inner(fun) + + +class PollingCondition: + condition: Callable[[], bool] + seconds_interval: float + description: Optional[str] + + last_called: float + entered: bool + + def __init__( + self, + condition: Callable[[], Optional[bool]], + seconds_interval: float = 2.0, + description: Optional[str] = None, + ): + self.condition = condition # type: ignore + self.seconds_interval = seconds_interval + + if description is None: + self.description = condition.__doc__ + else: + self.description = str(description) + + self.last_called = float("-inf") + self.entered = False + + def check(self) -> bool: + if self.entered or not self.overdue: + return True + + with self, rootlog.nested(self.nested_message): + rootlog.info(f"Time since last: {time.monotonic() - self.last_called:.2f}s") + try: + res = self.condition() # type: ignore + except Exception: + res = False + res = res is None or res + rootlog.info(f"Polling condition {'succeeded' if res else 'failed'}") + return res + + @property + def nested_message(self) -> str: + nested_message = ["Checking polling condition"] + if self.description is not None: + nested_message.append(repr(self.description)) + + return " ".join(nested_message) + + @property + def overdue(self) -> bool: + return self.last_called + self.seconds_interval < time.monotonic() + + def __enter__(self) -> None: + self.entered = True + + def __exit__(self, exc_type, exc_value, traceback) -> None: # type: ignore + self.entered = False + self.last_called = time.monotonic() diff --git a/nixos/tests/vscodium.nix b/nixos/tests/vscodium.nix index 43a0d61c856..66baea73ec6 100644 --- a/nixos/tests/vscodium.nix +++ b/nixos/tests/vscodium.nix @@ -34,36 +34,46 @@ let }; enableOCR = true; testScript = '' + @polling_condition + def codium_running(): + machine.succeed('pgrep -x codium') + + start_all() machine.wait_for_unit('graphical.target') machine.wait_until_succeeds('pgrep -x codium') - # Wait until vscodium is visible. "File" is in the menu bar. - machine.wait_for_text('File') - machine.screenshot('start_screen') + with codium_running: + # Wait until vscodium is visible. "File" is in the menu bar. + machine.wait_for_text('Get Started') + machine.screenshot('start_screen') + + test_string = 'testfile' - test_string = 'testfile' + # Create a new file + machine.send_key('ctrl-n') + machine.wait_for_text('Untitled') + machine.screenshot('empty_editor') - # Create a new file - machine.send_key('ctrl-n') - machine.wait_for_text('Untitled') - machine.screenshot('empty_editor') + # Type a string + machine.send_chars(test_string) + machine.wait_for_text(test_string) + machine.screenshot('editor') - # Type a string - machine.send_chars(test_string) - machine.wait_for_text(test_string) - machine.screenshot('editor') + # Save the file + machine.send_key('ctrl-s') + machine.wait_for_text('Save') + machine.screenshot('save_window') + machine.send_key('ret') - # Save the file - machine.send_key('ctrl-s') - machine.wait_for_text('Save') - machine.screenshot('save_window') - machine.send_key('ret') + # (the default filename is the first line of the file) + machine.wait_for_file(f'/home/alice/{test_string}') - # (the default filename is the first line of the file) - machine.wait_for_file(f'/home/alice/{test_string}') + machine.send_key('ctrl-q') + machine.wait_until_fails('pgrep -x codium') ''; }); -in builtins.mapAttrs (k: v: mkTest k v { }) tests +in +builtins.mapAttrs (k: v: mkTest k v { }) tests -- cgit 1.4.1 From e9ebd5e90ed786599c9a8ddbf8b89ed23a688db6 Mon Sep 17 00:00:00 2001 From: Naïm Favier Date: Thu, 16 Dec 2021 16:30:19 +0100 Subject: nixosTests.gnome: add autologin delay to catch GDM failures Catches failures like https://github.com/NixOS/nixpkgs/issues/149539 that don't happen with AutomaticLoginEnable. We still have a 0-delay autologin test in gnome-xorg, in case there's ever an issue that only arises with AutomaticLoginEnable. --- nixos/tests/gnome.nix | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nixos/tests') diff --git a/nixos/tests/gnome.nix b/nixos/tests/gnome.nix index 06f387ecad6..4471126902a 100644 --- a/nixos/tests/gnome.nix +++ b/nixos/tests/gnome.nix @@ -18,6 +18,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : { enable = true; user = "alice"; }; + # Catch GDM failures that don't happen with AutomaticLoginEnable, e.g. https://github.com/NixOS/nixpkgs/issues/149539 + gdm.autoLogin.delay = 1; }; services.xserver.desktopManager.gnome.enable = true; -- cgit 1.4.1 From e9efbc0bbb222cc90d14ba919f77bb198dd2d55f Mon Sep 17 00:00:00 2001 From: Michael Weiss Date: Tue, 28 Dec 2021 21:32:26 +0100 Subject: nixos/tests/tinywl: init This adds a very minimalistic (in terms of functionality and dependencies) test for wlroots, Wayland, and related packages. The Sway test covers more functionality and packages (e.g. XWayland) but this test has tree advantages: - Less dependencies: Much fewer rebuilds are required when testing core changes that need to go through staging. - Testing wlroots updates: The Sway package isn't immediately updated after a new wlroots version is released and a lot of other packages depend on wlroots as well. - Determining whether a bug only affects Sway or wlroots/TinyWL as well. --- nixos/tests/all-tests.nix | 1 + nixos/tests/tinywl.nix | 56 ++++++++++++++++++++++ .../window-managers/tinywl/default.nix | 2 +- pkgs/development/libraries/wlroots/0.15.nix | 4 ++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 nixos/tests/tinywl.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 4f62980e8e9..dc28b3d6478 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -476,6 +476,7 @@ in timezone = handleTest ./timezone.nix {}; tinc = handleTest ./tinc {}; tinydns = handleTest ./tinydns.nix {}; + tinywl = handleTest ./tinywl.nix {}; tor = handleTest ./tor.nix {}; # traefik test relies on docker-containers traefik = handleTestOn ["x86_64-linux"] ./traefik.nix {}; diff --git a/nixos/tests/tinywl.nix b/nixos/tests/tinywl.nix new file mode 100644 index 00000000000..b286cab7794 --- /dev/null +++ b/nixos/tests/tinywl.nix @@ -0,0 +1,56 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: + + { + name = "tinywl"; + meta = { + maintainers = with lib.maintainers; [ primeos ]; + }; + + machine = { config, ... }: { + # Automatically login on tty1 as a normal user: + imports = [ ./common/user-account.nix ]; + services.getty.autologinUser = "alice"; + + environment = { + systemPackages = with pkgs; [ tinywl foot wayland-utils ]; + }; + + # Automatically start TinyWL when logging in on tty1: + programs.bash.loginShellInit = '' + if [ "$(tty)" = "/dev/tty1" ]; then + set -e + test ! -e /tmp/tinywl.log # Only start tinywl once + readonly TEST_CMD="wayland-info |& tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok; read" + readonly FOOT_CMD="foot sh -c '$TEST_CMD'" + tinywl -s "$FOOT_CMD" |& tee /tmp/tinywl.log + touch /tmp/tinywl-exit-ok + fi + ''; + + # Switch to a different GPU driver (default: -vga std), otherwise TinyWL segfaults: + virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ]; + }; + + testScript = { nodes, ... }: '' + start_all() + machine.wait_for_unit("multi-user.target") + + # Wait for complete startup: + machine.wait_until_succeeds("pgrep tinywl") + machine.wait_for_file("/run/user/1000/wayland-0") + machine.wait_until_succeeds("pgrep foot") + machine.wait_for_file("/tmp/test-wayland-exit-ok") + + # Make a screenshot and save the result: + machine.screenshot("tinywl_foot") + print(machine.succeed("cat /tmp/test-wayland.out")) + machine.copy_from_vm("/tmp/test-wayland.out") + + # Terminate cleanly: + machine.send_key("alt-esc") + machine.wait_until_fails("pgrep foot") + machine.wait_until_fails("pgrep tinywl") + machine.wait_for_file("/tmp/tinywl-exit-ok") + machine.copy_from_vm("/tmp/tinywl.log") + ''; + }) diff --git a/pkgs/applications/window-managers/tinywl/default.nix b/pkgs/applications/window-managers/tinywl/default.nix index 965ec67b6e9..b89d9dafd90 100644 --- a/pkgs/applications/window-managers/tinywl/default.nix +++ b/pkgs/applications/window-managers/tinywl/default.nix @@ -21,7 +21,7 @@ stdenv.mkDerivation { meta = with lib; { homepage = "https://github.com/swaywm/wlroots/tree/master/tinywl"; description = ''A "minimum viable product" Wayland compositor based on wlroots''; - maintainers = with maintainers; [ qyliss ]; + maintainers = with maintainers; [ qyliss ] ++ wlroots.meta.maintainers; license = licenses.cc0; inherit (wlroots.meta) platforms; }; diff --git a/pkgs/development/libraries/wlroots/0.15.nix b/pkgs/development/libraries/wlroots/0.15.nix index 93ecf0bd3ab..e7cf7bc9b4c 100644 --- a/pkgs/development/libraries/wlroots/0.15.nix +++ b/pkgs/development/libraries/wlroots/0.15.nix @@ -2,6 +2,7 @@ , libGL, wayland, wayland-protocols, libinput, libxkbcommon, pixman , xcbutilwm, libX11, libcap, xcbutilimage, xcbutilerrors, mesa , libpng, ffmpeg, xcbutilrenderutil, seatd, vulkan-loader, glslang +, nixosTests , enableXWayland ? true, xwayland ? null }: @@ -49,6 +50,9 @@ stdenv.mkDerivation rec { done ''; + # Test via TinyWL (the "minimum viable product" Wayland compositor based on wlroots): + passthru.tests.tinywl = nixosTests.tinywl; + meta = with lib; { description = "A modular Wayland compositor library"; longDescription = '' -- cgit 1.4.1 From 97864e984d89248e74b13be175a1cfe9e16d984e Mon Sep 17 00:00:00 2001 From: Thomas Dy Date: Sat, 18 Dec 2021 22:18:10 +0900 Subject: nixos/kubernetes: actually set containerd to use systemd cgroups The correct configuration is listed in the kubernetes documentation https://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd-systemd The correct option can also be seen in `containerd config default` --- nixos/modules/services/cluster/kubernetes/default.nix | 5 +---- nixos/modules/services/cluster/kubernetes/kubelet.nix | 2 -- nixos/tests/kubernetes/base.nix | 7 ------- 3 files changed, 1 insertion(+), 13 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix index ae10657202d..35ec99d83c8 100644 --- a/nixos/modules/services/cluster/kubernetes/default.nix +++ b/nixos/modules/services/cluster/kubernetes/default.nix @@ -26,10 +26,7 @@ let containerd.runtimes.runc = { runtime_type = "io.containerd.runc.v2"; - }; - - containerd.runtimes."io.containerd.runc.v2".options = { - SystemdCgroup = true; + options.SystemdCgroup = true; }; }; }; diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix index 253355c20cb..af3a5062feb 100644 --- a/nixos/modules/services/cluster/kubernetes/kubelet.nix +++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix @@ -264,8 +264,6 @@ in "net.bridge.bridge-nf-call-ip6tables" = 1; }; - systemd.enableUnifiedCgroupHierarchy = false; # true breaks node memory metrics - systemd.services.kubelet = { description = "Kubernetes Kubelet Service"; wantedBy = [ "kubernetes.target" ]; diff --git a/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix index e1736f6fe17..f0c72084be5 100644 --- a/nixos/tests/kubernetes/base.nix +++ b/nixos/tests/kubernetes/base.nix @@ -60,13 +60,6 @@ let advertiseAddress = master.ip; }; masterAddress = "${masterName}.${config.networking.domain}"; - # workaround for: - # https://github.com/kubernetes/kubernetes/issues/102676 - # (workaround from) https://github.com/kubernetes/kubernetes/issues/95488 - kubelet.extraOpts = ''\ - --cgroups-per-qos=false \ - --enforce-node-allocatable="" \ - ''; }; } (optionalAttrs (any (role: role == "master") machine.roles) { -- cgit 1.4.1 From b52607f43b11319edb716d65bbecbfdbf2f5b92b Mon Sep 17 00:00:00 2001 From: Winter Date: Sat, 8 Jan 2022 15:05:34 -0500 Subject: nixos/acme: ensure web servers using certs can access them --- nixos/modules/module-list.nix | 2 +- nixos/modules/security/acme.nix | 921 --------------------- nixos/modules/security/acme.xml | 413 --------- nixos/modules/security/acme/default.nix | 921 +++++++++++++++++++++ nixos/modules/security/acme/doc.xml | 413 +++++++++ .../security/acme/mk-cert-ownership-assertion.nix | 4 + .../services/web-servers/apache-httpd/default.nix | 8 +- .../modules/services/web-servers/caddy/default.nix | 13 +- .../modules/services/web-servers/nginx/default.nix | 8 +- nixos/tests/acme.nix | 12 +- 10 files changed, 1369 insertions(+), 1346 deletions(-) delete mode 100644 nixos/modules/security/acme.nix delete mode 100644 nixos/modules/security/acme.xml create mode 100644 nixos/modules/security/acme/default.nix create mode 100644 nixos/modules/security/acme/doc.xml create mode 100644 nixos/modules/security/acme/mk-cert-ownership-assertion.nix (limited to 'nixos/tests') diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 73a61c4ca0e..1290c7b99d5 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -226,7 +226,7 @@ ./programs/zsh/zsh-autosuggestions.nix ./programs/zsh/zsh-syntax-highlighting.nix ./rename.nix - ./security/acme.nix + ./security/acme ./security/apparmor.nix ./security/audit.nix ./security/auditd.nix diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix deleted file mode 100644 index e244989d640..00000000000 --- a/nixos/modules/security/acme.nix +++ /dev/null @@ -1,921 +0,0 @@ -{ config, lib, pkgs, options, ... }: -with lib; -let - cfg = config.security.acme; - opt = options.security.acme; - user = if cfg.useRoot then "root" else "acme"; - - # Used to calculate timer accuracy for coalescing - numCerts = length (builtins.attrNames cfg.certs); - _24hSecs = 60 * 60 * 24; - - # Used to make unique paths for each cert/account config set - mkHash = with builtins; val: substring 0 20 (hashString "sha256" val); - mkAccountHash = acmeServer: data: mkHash "${toString acmeServer} ${data.keyType} ${data.email}"; - accountDirRoot = "/var/lib/acme/.lego/accounts/"; - - # There are many services required to make cert renewals work. - # They all follow a common structure: - # - They inherit this commonServiceConfig - # - They all run as the acme user - # - They all use BindPath and StateDirectory where possible - # to set up a sort of build environment in /tmp - # The Group can vary depending on what the user has specified in - # security.acme.certs..group on some of the services. - commonServiceConfig = { - Type = "oneshot"; - User = user; - Group = mkDefault "acme"; - UMask = 0022; - StateDirectoryMode = 750; - ProtectSystem = "strict"; - ReadWritePaths = [ - "/var/lib/acme" - ]; - PrivateTmp = true; - - WorkingDirectory = "/tmp"; - - CapabilityBoundingSet = [ "" ]; - DevicePolicy = "closed"; - LockPersonality = true; - MemoryDenyWriteExecute = true; - NoNewPrivileges = true; - PrivateDevices = true; - ProtectClock = true; - ProtectHome = true; - ProtectHostname = true; - ProtectControlGroups = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - ProcSubset = "pid"; - RemoveIPC = true; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - # 1. allow a reasonable set of syscalls - "@system-service" - # 2. and deny unreasonable ones - "~@privileged @resources" - # 3. then allow the required subset within denied groups - "@chown" - ]; - }; - - # In order to avoid race conditions creating the CA for selfsigned certs, - # we have a separate service which will create the necessary files. - selfsignCAService = { - description = "Generate self-signed certificate authority"; - - path = with pkgs; [ minica ]; - - unitConfig = { - ConditionPathExists = "!/var/lib/acme/.minica/key.pem"; - StartLimitIntervalSec = 0; - }; - - serviceConfig = commonServiceConfig // { - StateDirectory = "acme/.minica"; - BindPaths = "/var/lib/acme/.minica:/tmp/ca"; - UMask = 0077; - }; - - # Working directory will be /tmp - script = '' - minica \ - --ca-key ca/key.pem \ - --ca-cert ca/cert.pem \ - --domains selfsigned.local - ''; - }; - - # Ensures that directories which are shared across all certs - # exist and have the correct user and group, since group - # is configurable on a per-cert basis. - userMigrationService = let - script = with builtins; '' - chown -R ${user} .lego/accounts - '' + (concatStringsSep "\n" (mapAttrsToList (cert: data: '' - for fixpath in ${escapeShellArg cert} .lego/${escapeShellArg cert}; do - if [ -d "$fixpath" ]; then - chmod -R u=rwX,g=rX,o= "$fixpath" - chown -R ${user}:${data.group} "$fixpath" - fi - done - '') certConfigs)); - in { - description = "Fix owner and group of all ACME certificates"; - - serviceConfig = commonServiceConfig // { - # We don't want this to run every time a renewal happens - RemainAfterExit = true; - - # These StateDirectory entries negate the need for tmpfiles - StateDirectory = [ "acme" "acme/.lego" "acme/.lego/accounts" ]; - StateDirectoryMode = 755; - WorkingDirectory = "/var/lib/acme"; - - # Run the start script as root - ExecStart = "+" + (pkgs.writeShellScript "acme-fixperms" script); - }; - }; - - certToConfig = cert: data: let - acmeServer = data.server; - useDns = data.dnsProvider != null; - destPath = "/var/lib/acme/${cert}"; - selfsignedDeps = optionals (cfg.preliminarySelfsigned) [ "acme-selfsigned-${cert}.service" ]; - - # Minica and lego have a "feature" which replaces * with _. We need - # to make this substitution to reference the output files from both programs. - # End users never see this since we rename the certs. - keyName = builtins.replaceStrings ["*"] ["_"] data.domain; - - # FIXME when mkChangedOptionModule supports submodules, change to that. - # This is a workaround - extraDomains = data.extraDomainNames ++ ( - optionals - (data.extraDomains != "_mkMergedOptionModule") - (builtins.attrNames data.extraDomains) - ); - - # Create hashes for cert data directories based on configuration - # Flags are separated to avoid collisions - hashData = with builtins; '' - ${concatStringsSep " " data.extraLegoFlags} - - ${concatStringsSep " " data.extraLegoRunFlags} - - ${concatStringsSep " " data.extraLegoRenewFlags} - - ${toString acmeServer} ${toString data.dnsProvider} - ${toString data.ocspMustStaple} ${data.keyType} - ''; - certDir = mkHash hashData; - # TODO remove domainHash usage entirely. Waiting on go-acme/lego#1532 - domainHash = mkHash "${concatStringsSep " " extraDomains} ${data.domain}"; - accountHash = (mkAccountHash acmeServer data); - accountDir = accountDirRoot + accountHash; - - protocolOpts = if useDns then ( - [ "--dns" data.dnsProvider ] - ++ optionals (!data.dnsPropagationCheck) [ "--dns.disable-cp" ] - ++ optionals (data.dnsResolver != null) [ "--dns.resolvers" data.dnsResolver ] - ) else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ] - else [ "--http" "--http.webroot" data.webroot ]; - - commonOpts = [ - "--accept-tos" # Checking the option is covered by the assertions - "--path" "." - "-d" data.domain - "--email" data.email - "--key-type" data.keyType - ] ++ protocolOpts - ++ optionals (acmeServer != null) [ "--server" acmeServer ] - ++ concatMap (name: [ "-d" name ]) extraDomains - ++ data.extraLegoFlags; - - # Although --must-staple is common to both modes, it is not declared as a - # mode-agnostic argument in lego and thus must come after the mode. - runOpts = escapeShellArgs ( - commonOpts - ++ [ "run" ] - ++ optionals data.ocspMustStaple [ "--must-staple" ] - ++ data.extraLegoRunFlags - ); - renewOpts = escapeShellArgs ( - commonOpts - ++ [ "renew" ] - ++ optionals data.ocspMustStaple [ "--must-staple" ] - ++ data.extraLegoRenewFlags - ); - - # We need to collect all the ACME webroots to grant them write - # access in the systemd service. - webroots = - lib.remove null - (lib.unique - (builtins.map - (certAttrs: certAttrs.webroot) - (lib.attrValues config.security.acme.certs))); - in { - inherit accountHash cert selfsignedDeps; - - group = data.group; - - renewTimer = { - description = "Renew ACME Certificate for ${cert}"; - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = data.renewInterval; - Unit = "acme-${cert}.service"; - Persistent = "yes"; - - # Allow systemd to pick a convenient time within the day - # to run the check. - # This allows the coalescing of multiple timer jobs. - # We divide by the number of certificates so that if you - # have many certificates, the renewals are distributed over - # the course of the day to avoid rate limits. - AccuracySec = "${toString (_24hSecs / numCerts)}s"; - - # Skew randomly within the day, per https://letsencrypt.org/docs/integration-guide/. - RandomizedDelaySec = "24h"; - }; - }; - - selfsignService = { - description = "Generate self-signed certificate for ${cert}"; - after = [ "acme-selfsigned-ca.service" "acme-fixperms.service" ]; - requires = [ "acme-selfsigned-ca.service" "acme-fixperms.service" ]; - - path = with pkgs; [ minica ]; - - unitConfig = { - ConditionPathExists = "!/var/lib/acme/${cert}/key.pem"; - StartLimitIntervalSec = 0; - }; - - serviceConfig = commonServiceConfig // { - Group = data.group; - UMask = 0027; - - StateDirectory = "acme/${cert}"; - - BindPaths = [ - "/var/lib/acme/.minica:/tmp/ca" - "/var/lib/acme/${cert}:/tmp/${keyName}" - ]; - }; - - # Working directory will be /tmp - # minica will output to a folder sharing the name of the first domain - # in the list, which will be ${data.domain} - script = '' - minica \ - --ca-key ca/key.pem \ - --ca-cert ca/cert.pem \ - --domains ${escapeShellArg (builtins.concatStringsSep "," ([ data.domain ] ++ extraDomains))} - - # Create files to match directory layout for real certificates - cd '${keyName}' - cp ../ca/cert.pem chain.pem - cat cert.pem chain.pem > fullchain.pem - cat key.pem fullchain.pem > full.pem - - # Group might change between runs, re-apply it - chown '${user}:${data.group}' * - - # Default permissions make the files unreadable by group + anon - # Need to be readable by group - chmod 640 * - ''; - }; - - renewService = { - description = "Renew ACME certificate for ${cert}"; - after = [ "network.target" "network-online.target" "acme-fixperms.service" "nss-lookup.target" ] ++ selfsignedDeps; - wants = [ "network-online.target" "acme-fixperms.service" ] ++ selfsignedDeps; - - # https://github.com/NixOS/nixpkgs/pull/81371#issuecomment-605526099 - wantedBy = optionals (!config.boot.isContainer) [ "multi-user.target" ]; - - path = with pkgs; [ lego coreutils diffutils openssl ]; - - serviceConfig = commonServiceConfig // { - Group = data.group; - - # Keep in mind that these directories will be deleted if the user runs - # systemctl clean --what=state - # acme/.lego/${cert} is listed for this reason. - StateDirectory = [ - "acme/${cert}" - "acme/.lego/${cert}" - "acme/.lego/${cert}/${certDir}" - "acme/.lego/accounts/${accountHash}" - ]; - - ReadWritePaths = commonServiceConfig.ReadWritePaths ++ webroots; - - # Needs to be space separated, but can't use a multiline string because that'll include newlines - BindPaths = [ - "${accountDir}:/tmp/accounts" - "/var/lib/acme/${cert}:/tmp/out" - "/var/lib/acme/.lego/${cert}/${certDir}:/tmp/certificates" - ]; - - # Only try loading the credentialsFile if the dns challenge is enabled - EnvironmentFile = mkIf useDns data.credentialsFile; - - # Run as root (Prefixed with +) - ExecStartPost = "+" + (pkgs.writeShellScript "acme-postrun" '' - cd /var/lib/acme/${escapeShellArg cert} - if [ -e renewed ]; then - rm renewed - ${data.postRun} - ${optionalString (data.reloadServices != []) - "systemctl --no-block try-reload-or-restart ${escapeShellArgs data.reloadServices}" - } - fi - ''); - } // optionalAttrs (data.listenHTTP != null && toInt (elemAt (splitString ":" data.listenHTTP) 1) < 1024) { - CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; - }; - - # Working directory will be /tmp - script = '' - ${optionalString data.enableDebugLogs "set -x"} - set -euo pipefail - - # This reimplements the expiration date check, but without querying - # the acme server first. By doing this offline, we avoid errors - # when the network or DNS are unavailable, which can happen during - # nixos-rebuild switch. - is_expiration_skippable() { - pem=$1 - - # This function relies on set -e to exit early if any of the - # conditions or programs fail. - - [[ -e $pem ]] - - expiration_line="$( - set -euxo pipefail - openssl x509 -noout -enddate <$pem \ - | grep notAfter \ - | sed -e 's/^notAfter=//' - )" - [[ -n "$expiration_line" ]] - - expiration_date="$(date -d "$expiration_line" +%s)" - now="$(date +%s)" - expiration_s=$[expiration_date - now] - expiration_days=$[expiration_s / (3600 * 24)] # rounds down - - [[ $expiration_days -gt ${toString data.validMinDays} ]] - } - - ${optionalString (data.webroot != null) '' - # Ensure the webroot exists. Fixing group is required in case configuration was changed between runs. - # Lego will fail if the webroot does not exist at all. - ( - mkdir -p '${data.webroot}/.well-known/acme-challenge' \ - && chgrp '${data.group}' ${data.webroot}/.well-known/acme-challenge - ) || ( - echo 'Please ensure ${data.webroot}/.well-known/acme-challenge exists and is writable by acme:${data.group}' \ - && exit 1 - ) - ''} - - echo '${domainHash}' > domainhash.txt - - # Check if we can renew. - # We can only renew if the list of domains has not changed. - if cmp -s domainhash.txt certificates/domainhash.txt && [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' -a -n "$(ls -1 accounts)" ]; then - - # Even if a cert is not expired, it may be revoked by the CA. - # Try to renew, and silently fail if the cert is not expired. - # Avoids #85794 and resolves #129838 - if ! lego ${renewOpts} --days ${toString data.validMinDays}; then - if is_expiration_skippable out/full.pem; then - echo 1>&2 "nixos-acme: Ignoring failed renewal because expiration isn't within the coming ${toString data.validMinDays} days" - else - # High number to avoid Systemd reserved codes. - exit 11 - fi - fi - - # Otherwise do a full run - elif ! lego ${runOpts}; then - # Produce a nice error for those doing their first nixos-rebuild with these certs - echo Failed to fetch certificates. \ - This may mean your DNS records are set up incorrectly. \ - ${optionalString (cfg.preliminarySelfsigned) "Selfsigned certs are in place and dependant services will still start."} - # Exit 10 so that users can potentially amend SuccessExitStatus to ignore this error. - # High number to avoid Systemd reserved codes. - exit 10 - fi - - mv domainhash.txt certificates/ - - # Group might change between runs, re-apply it - chown '${user}:${data.group}' certificates/* - - # Copy all certs to the "real" certs directory - if ! cmp -s 'certificates/${keyName}.crt' out/fullchain.pem; then - touch out/renewed - echo Installing new certificate - cp -vp 'certificates/${keyName}.crt' out/fullchain.pem - cp -vp 'certificates/${keyName}.key' out/key.pem - cp -vp 'certificates/${keyName}.issuer.crt' out/chain.pem - ln -sf fullchain.pem out/cert.pem - cat out/key.pem out/fullchain.pem > out/full.pem - fi - - # By default group will have no access to the cert files. - # This chmod will fix that. - chmod 640 out/* - ''; - }; - }; - - certConfigs = mapAttrs certToConfig cfg.certs; - - # These options can be specified within - # security.acme.defaults or security.acme.certs. - inheritableModule = isDefaults: { config, ... }: let - defaultAndText = name: default: { - # When ! isDefaults then this is the option declaration for the - # security.acme.certs. path, which has the extra inheritDefaults - # option, which if disabled means that we can't inherit it - default = if isDefaults || ! config.inheritDefaults then default else cfg.defaults.${name}; - # The docs however don't need to depend on inheritDefaults, they should - # stay constant. Though notably it wouldn't matter much, because to get - # the option information, a submodule with name `` is evaluated - # without any definitions. - defaultText = if isDefaults then default else literalExpression "config.security.acme.defaults.${name}"; - }; - in { - options = { - validMinDays = mkOption { - type = types.int; - inherit (defaultAndText "validMinDays" 30) default defaultText; - description = "Minimum remaining validity before renewal in days."; - }; - - renewInterval = mkOption { - type = types.str; - inherit (defaultAndText "renewInterval" "daily") default defaultText; - description = '' - Systemd calendar expression when to check for renewal. See - systemd.time - 7. - ''; - }; - - enableDebugLogs = mkEnableOption "debug logging for this certificate" // { - inherit (defaultAndText "enableDebugLogs" true) default defaultText; - }; - - webroot = mkOption { - type = types.nullOr types.str; - inherit (defaultAndText "webroot" null) default defaultText; - example = "/var/lib/acme/acme-challenge"; - description = '' - Where the webroot of the HTTP vhost is located. - .well-known/acme-challenge/ directory - will be created below the webroot if it doesn't exist. - http://example.org/.well-known/acme-challenge/ must also - be available (notice unencrypted HTTP). - ''; - }; - - server = mkOption { - type = types.nullOr types.str; - inherit (defaultAndText "server" null) default defaultText; - description = '' - ACME Directory Resource URI. Defaults to Let's Encrypt's - production endpoint, - , if unset. - ''; - }; - - email = mkOption { - type = types.str; - inherit (defaultAndText "email" null) default defaultText; - description = '' - Email address for account creation and correspondence from the CA. - It is recommended to use the same email for all certs to avoid account - creation limits. - ''; - }; - - group = mkOption { - type = types.str; - inherit (defaultAndText "group" "acme") default defaultText; - description = "Group running the ACME client."; - }; - - reloadServices = mkOption { - type = types.listOf types.str; - inherit (defaultAndText "reloadServices" []) default defaultText; - description = '' - The list of systemd services to call systemctl try-reload-or-restart - on. - ''; - }; - - postRun = mkOption { - type = types.lines; - inherit (defaultAndText "postRun" "") default defaultText; - example = "cp full.pem backup.pem"; - description = '' - Commands to run after new certificates go live. Note that - these commands run as the root user. - - Executed in the same directory with the new certificate. - ''; - }; - - keyType = mkOption { - type = types.str; - inherit (defaultAndText "keyType" "ec256") default defaultText; - description = '' - Key type to use for private keys. - For an up to date list of supported values check the --key-type option - at . - ''; - }; - - dnsProvider = mkOption { - type = types.nullOr types.str; - inherit (defaultAndText "dnsProvider" null) default defaultText; - example = "route53"; - description = '' - DNS Challenge provider. For a list of supported providers, see the "code" - field of the DNS providers listed at . - ''; - }; - - dnsResolver = mkOption { - type = types.nullOr types.str; - inherit (defaultAndText "dnsResolver" null) default defaultText; - example = "1.1.1.1:53"; - description = '' - Set the resolver to use for performing recursive DNS queries. Supported: - host:port. The default is to use the system resolvers, or Google's DNS - resolvers if the system's cannot be determined. - ''; - }; - - credentialsFile = mkOption { - type = types.path; - inherit (defaultAndText "credentialsFile" null) default defaultText; - description = '' - Path to an EnvironmentFile for the cert's service containing any required and - optional environment variables for your selected dnsProvider. - To find out what values you need to set, consult the documentation at - for the corresponding dnsProvider. - ''; - example = "/var/src/secrets/example.org-route53-api-token"; - }; - - dnsPropagationCheck = mkOption { - type = types.bool; - inherit (defaultAndText "dnsPropagationCheck" true) default defaultText; - description = '' - Toggles lego DNS propagation check, which is used alongside DNS-01 - challenge to ensure the DNS entries required are available. - ''; - }; - - ocspMustStaple = mkOption { - type = types.bool; - inherit (defaultAndText "ocspMustStaple" false) default defaultText; - description = '' - Turns on the OCSP Must-Staple TLS extension. - Make sure you know what you're doing! See: - - - - - ''; - }; - - extraLegoFlags = mkOption { - type = types.listOf types.str; - inherit (defaultAndText "extraLegoFlags" []) default defaultText; - description = '' - Additional global flags to pass to all lego commands. - ''; - }; - - extraLegoRenewFlags = mkOption { - type = types.listOf types.str; - inherit (defaultAndText "extraLegoRenewFlags" []) default defaultText; - description = '' - Additional flags to pass to lego renew. - ''; - }; - - extraLegoRunFlags = mkOption { - type = types.listOf types.str; - inherit (defaultAndText "extraLegoRunFlags" []) default defaultText; - description = '' - Additional flags to pass to lego run. - ''; - }; - }; - }; - - certOpts = { name, config, ... }: { - options = { - # user option has been removed - user = mkOption { - visible = false; - default = "_mkRemovedOptionModule"; - }; - - # allowKeysForGroup option has been removed - allowKeysForGroup = mkOption { - visible = false; - default = "_mkRemovedOptionModule"; - }; - - # extraDomains was replaced with extraDomainNames - extraDomains = mkOption { - visible = false; - default = "_mkMergedOptionModule"; - }; - - directory = mkOption { - type = types.str; - readOnly = true; - default = "/var/lib/acme/${name}"; - description = "Directory where certificate and other state is stored."; - }; - - domain = mkOption { - type = types.str; - default = name; - description = "Domain to fetch certificate for (defaults to the entry name)."; - }; - - extraDomainNames = mkOption { - type = types.listOf types.str; - default = []; - example = literalExpression '' - [ - "example.org" - "mydomain.org" - ] - ''; - description = '' - A list of extra domain names, which are included in the one certificate to be issued. - ''; - }; - - # This setting must be different for each configured certificate, otherwise - # two or more renewals may fail to bind to the address. Hence, it is not in - # the inheritableOpts. - listenHTTP = mkOption { - type = types.nullOr types.str; - default = null; - example = ":1360"; - description = '' - Interface and port to listen on to solve HTTP challenges - in the form [INTERFACE]:PORT. - If you use a port other than 80, you must proxy port 80 to this port. - ''; - }; - - inheritDefaults = mkOption { - default = true; - example = true; - description = "Whether to inherit values set in `security.acme.defaults` or not."; - type = lib.types.bool; - }; - }; - }; - -in { - - options = { - security.acme = { - preliminarySelfsigned = mkOption { - type = types.bool; - default = true; - description = '' - Whether a preliminary self-signed certificate should be generated before - doing ACME requests. This can be useful when certificates are required in - a webserver, but ACME needs the webserver to make its requests. - - With preliminary self-signed certificate the webserver can be started and - can later reload the correct ACME certificates. - ''; - }; - - acceptTerms = mkOption { - type = types.bool; - default = false; - description = '' - Accept the CA's terms of service. The default provider is Let's Encrypt, - you can find their ToS at . - ''; - }; - - useRoot = mkOption { - type = types.bool; - default = false; - description = '' - Whether to use the root user when generating certs. This is not recommended - for security + compatiblity reasons. If a service requires root owned certificates - consider following the guide on "Using ACME with services demanding root - owned certificates" in the NixOS manual, and only using this as a fallback - or for testing. - ''; - }; - - defaults = mkOption { - type = types.submodule (inheritableModule true); - description = '' - Default values inheritable by all configured certs. You can - use this to define options shared by all your certs. These defaults - can also be ignored on a per-cert basis using the - `security.acme.certs.''${cert}.inheritDefaults' option. - ''; - }; - - certs = mkOption { - default = { }; - type = with types; attrsOf (submodule [ (inheritableModule false) certOpts ]); - description = '' - Attribute set of certificates to get signed and renewed. Creates - acme-''${cert}.{service,timer} systemd units for - each certificate defined here. Other services can add dependencies - to those units if they rely on the certificates being present, - or trigger restarts of the service if certificates get renewed. - ''; - example = literalExpression '' - { - "example.com" = { - webroot = "/var/lib/acme/acme-challenge/"; - email = "foo@example.com"; - extraDomainNames = [ "www.example.com" "foo.example.com" ]; - }; - "bar.example.com" = { - webroot = "/var/lib/acme/acme-challenge/"; - email = "bar@example.com"; - }; - } - ''; - }; - }; - }; - - imports = [ - (mkRemovedOptionModule [ "security" "acme" "production" ] '' - Use security.acme.server to define your staging ACME server URL instead. - - To use the let's encrypt staging server, use security.acme.server = - "https://acme-staging-v02.api.letsencrypt.org/directory". - '') - (mkRemovedOptionModule [ "security" "acme" "directory" ] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.") - (mkRemovedOptionModule [ "security" "acme" "preDelay" ] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") - (mkRemovedOptionModule [ "security" "acme" "activationDelay" ] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") - (mkChangedOptionModule [ "security" "acme" "validMin" ] [ "security" "acme" "defaults" "validMinDays" ] (config: config.security.acme.validMin / (24 * 3600))) - (mkChangedOptionModule [ "security" "acme" "validMinDays" ] [ "security" "acme" "defaults" "validMinDays" ] (config: config.security.acme.validMinDays)) - (mkChangedOptionModule [ "security" "acme" "renewInterval" ] [ "security" "acme" "defaults" "renewInterval" ] (config: config.security.acme.renewInterval)) - (mkChangedOptionModule [ "security" "acme" "email" ] [ "security" "acme" "defaults" "email" ] (config: config.security.acme.email)) - (mkChangedOptionModule [ "security" "acme" "server" ] [ "security" "acme" "defaults" "server" ] (config: config.security.acme.server)) - (mkChangedOptionModule [ "security" "acme" "enableDebugLogs" ] [ "security" "acme" "defaults" "enableDebugLogs" ] (config: config.security.acme.enableDebugLogs)) - ]; - - config = mkMerge [ - (mkIf (cfg.certs != { }) { - - # FIXME Most of these custom warnings and filters for security.acme.certs.* are required - # because using mkRemovedOptionModule/mkChangedOptionModule with attrsets isn't possible. - warnings = filter (w: w != "") (mapAttrsToList (cert: data: if data.extraDomains != "_mkMergedOptionModule" then '' - The option definition `security.acme.certs.${cert}.extraDomains` has changed - to `security.acme.certs.${cert}.extraDomainNames` and is now a list of strings. - Setting a custom webroot for extra domains is not possible, instead use separate certs. - '' else "") cfg.certs); - - assertions = let - certs = attrValues cfg.certs; - in [ - { - assertion = cfg.email != null || all (certOpts: certOpts.email != null) certs; - message = '' - You must define `security.acme.certs..email` or - `security.acme.email` to register with the CA. Note that using - many different addresses for certs may trigger account rate limits. - ''; - } - { - assertion = cfg.acceptTerms; - message = '' - You must accept the CA's terms of service before using - the ACME module by setting `security.acme.acceptTerms` - to `true`. For Let's Encrypt's ToS see https://letsencrypt.org/repository/ - ''; - } - ] ++ (builtins.concatLists (mapAttrsToList (cert: data: [ - { - assertion = data.user == "_mkRemovedOptionModule"; - message = '' - The option definition `security.acme.certs.${cert}.user' no longer has any effect; Please remove it. - Certificate user is now hard coded to the "acme" user. If you would - like another user to have access, consider adding them to the - "acme" group or changing security.acme.certs.${cert}.group. - ''; - } - { - assertion = data.allowKeysForGroup == "_mkRemovedOptionModule"; - message = '' - The option definition `security.acme.certs.${cert}.allowKeysForGroup' no longer has any effect; Please remove it. - All certs are readable by the configured group. If this is undesired, - consider changing security.acme.certs.${cert}.group to an unused group. - ''; - } - # * in the cert value breaks building of systemd services, and makes - # referencing them as a user quite weird too. Best practice is to use - # the domain option. - { - assertion = ! hasInfix "*" cert; - message = '' - The cert option path `security.acme.certs.${cert}.dnsProvider` - cannot contain a * character. - Instead, set `security.acme.certs.${cert}.domain = "${cert}";` - and remove the wildcard from the path. - ''; - } - { - assertion = data.dnsProvider == null || data.webroot == null; - message = '' - Options `security.acme.certs.${cert}.dnsProvider` and - `security.acme.certs.${cert}.webroot` are mutually exclusive. - ''; - } - { - assertion = data.webroot == null || data.listenHTTP == null; - message = '' - Options `security.acme.certs.${cert}.webroot` and - `security.acme.certs.${cert}.listenHTTP` are mutually exclusive. - ''; - } - { - assertion = data.listenHTTP == null || data.dnsProvider == null; - message = '' - Options `security.acme.certs.${cert}.listenHTTP` and - `security.acme.certs.${cert}.dnsProvider` are mutually exclusive. - ''; - } - { - assertion = data.dnsProvider != null || data.webroot != null || data.listenHTTP != null; - message = '' - One of `security.acme.certs.${cert}.dnsProvider`, - `security.acme.certs.${cert}.webroot`, or - `security.acme.certs.${cert}.listenHTTP` must be provided. - ''; - } - ]) cfg.certs)); - - users.users.acme = { - home = "/var/lib/acme"; - group = "acme"; - isSystemUser = true; - }; - - users.groups.acme = {}; - - systemd.services = { - "acme-fixperms" = userMigrationService; - } // (mapAttrs' (cert: conf: nameValuePair "acme-${cert}" conf.renewService) certConfigs) - // (optionalAttrs (cfg.preliminarySelfsigned) ({ - "acme-selfsigned-ca" = selfsignCAService; - } // (mapAttrs' (cert: conf: nameValuePair "acme-selfsigned-${cert}" conf.selfsignService) certConfigs))); - - systemd.timers = mapAttrs' (cert: conf: nameValuePair "acme-${cert}" conf.renewTimer) certConfigs; - - systemd.targets = let - # Create some targets which can be depended on to be "active" after cert renewals - finishedTargets = mapAttrs' (cert: conf: nameValuePair "acme-finished-${cert}" { - wantedBy = [ "default.target" ]; - requires = [ "acme-${cert}.service" ]; - after = [ "acme-${cert}.service" ]; - }) certConfigs; - - # Create targets to limit the number of simultaneous account creations - # How it works: - # - Pick a "leader" cert service, which will be in charge of creating the account, - # and run first (requires + after) - # - Make all other cert services sharing the same account wait for the leader to - # finish before starting (requiredBy + before). - # Using a target here is fine - account creation is a one time event. Even if - # systemd clean --what=state is used to delete the account, so long as the user - # then runs one of the cert services, there won't be any issues. - accountTargets = mapAttrs' (hash: confs: let - leader = "acme-${(builtins.head confs).cert}.service"; - dependantServices = map (conf: "acme-${conf.cert}.service") (builtins.tail confs); - in nameValuePair "acme-account-${hash}" { - requiredBy = dependantServices; - before = dependantServices; - requires = [ leader ]; - after = [ leader ]; - }) (groupBy (conf: conf.accountHash) (attrValues certConfigs)); - in finishedTargets // accountTargets; - }) - ]; - - meta = { - maintainers = lib.teams.acme.members; - doc = ./acme.xml; - }; -} diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml deleted file mode 100644 index f623cc509be..00000000000 --- a/nixos/modules/security/acme.xml +++ /dev/null @@ -1,413 +0,0 @@ - - SSL/TLS Certificates with ACME - - NixOS supports automatic domain validation & certificate retrieval and - renewal using the ACME protocol. Any provider can be used, but by default - NixOS uses Let's Encrypt. The alternative ACME client - lego is used under - the hood. - - - Automatic cert validation and configuration for Apache and Nginx virtual - hosts is included in NixOS, however if you would like to generate a wildcard - cert or you are not using a web server you will have to configure DNS - based validation. - -
- Prerequisites - - - To use the ACME module, you must accept the provider's terms of service - by setting - to true. The Let's Encrypt ToS can be found - here. - - - - You must also set an email address to be used when creating accounts with - Let's Encrypt. You can set this for all certs with - - and/or on a per-cert basis with - . - This address is only used for registration and renewal reminders, - and cannot be used to administer the certificates in any way. - - - - Alternatively, you can use a different ACME server by changing the - option - to a provider of your choosing, or just change the server for one cert with - . - - - - You will need an HTTP server or DNS server for verification. For HTTP, - the server must have a webroot defined that can serve - .well-known/acme-challenge. This directory must be - writeable by the user that will run the ACME client. For DNS, you must - set up credentials with your provider/server for use with lego. - -
-
- Using ACME certificates in Nginx - - - NixOS supports fetching ACME certificates for you by setting - enableACME - = true; in a virtualHost config. We first create self-signed - placeholder certificates in place of the real ACME certs. The placeholder - certs are overwritten when the ACME certs arrive. For - foo.example.com the config would look like this: - - - - = true; - = "admin+acme@example.com"; -services.nginx = { - enable = true; - virtualHosts = { - "foo.example.com" = { - forceSSL = true; - enableACME = true; - # All serverAliases will be added as extra domain names on the certificate. - serverAliases = [ "bar.example.com" ]; - locations."/" = { - root = "/var/www"; - }; - }; - - # We can also add a different vhost and reuse the same certificate - # but we have to append extraDomainNames manually. - security.acme.certs."foo.example.com".extraDomainNames = [ "baz.example.com" ]; - "baz.example.com" = { - forceSSL = true; - useACMEHost = "foo.example.com"; - locations."/" = { - root = "/var/www"; - }; - }; - }; -} - -
-
- Using ACME certificates in Apache/httpd - - - Using ACME certificates with Apache virtual hosts is identical - to using them with Nginx. The attribute names are all the same, just replace - "nginx" with "httpd" where appropriate. - -
-
- Manual configuration of HTTP-01 validation - - - First off you will need to set up a virtual host to serve the challenges. - This example uses a vhost called certs.example.com, with - the intent that you will generate certs for all your vhosts and redirect - everyone to HTTPS. - - - - = true; - = "admin+acme@example.com"; - -# /var/lib/acme/.challenges must be writable by the ACME user -# and readable by the Nginx user. The easiest way to achieve -# this is to add the Nginx user to the ACME group. -users.users.nginx.extraGroups = [ "acme" ]; - -services.nginx = { - enable = true; - virtualHosts = { - "acmechallenge.example.com" = { - # Catchall vhost, will redirect users to HTTPS for all vhosts - serverAliases = [ "*.example.com" ]; - locations."/.well-known/acme-challenge" = { - root = "/var/lib/acme/.challenges"; - }; - locations."/" = { - return = "301 https://$host$request_uri"; - }; - }; - }; -} -# Alternative config for Apache -users.users.wwwrun.extraGroups = [ "acme" ]; -services.httpd = { - enable = true; - virtualHosts = { - "acmechallenge.example.com" = { - # Catchall vhost, will redirect users to HTTPS for all vhosts - serverAliases = [ "*.example.com" ]; - # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user. - # By default, this is the case. - documentRoot = "/var/lib/acme/.challenges"; - extraConfig = '' - RewriteEngine On - RewriteCond %{HTTPS} off - RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge [NC] - RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301] - ''; - }; - }; -} - - - - Now you need to configure ACME to generate a certificate. - - - -."foo.example.com" = { - webroot = "/var/lib/acme/.challenges"; - email = "foo@example.com"; - # Ensure that the web server you use can read the generated certs - # Take a look at the group option for the web server you choose. - group = "nginx"; - # Since we have a wildcard vhost to handle port 80, - # we can generate certs for anything! - # Just make sure your DNS resolves them. - extraDomainNames = [ "mail.example.com" ]; -}; - - - - The private key key.pem and certificate - fullchain.pem will be put into - /var/lib/acme/foo.example.com. - - - - Refer to for all available configuration - options for the security.acme - module. - -
-
- Configuring ACME for DNS validation - - - This is useful if you want to generate a wildcard certificate, since - ACME servers will only hand out wildcard certs over DNS validation. - There are a number of supported DNS providers and servers you can utilise, - see the lego docs - for provider/server specific configuration values. For the sake of these - docs, we will provide a fully self-hosted example using bind. - - - -services.bind = { - enable = true; - extraConfig = '' - include "/var/lib/secrets/dnskeys.conf"; - ''; - zones = [ - rec { - name = "example.com"; - file = "/var/db/bind/${name}"; - master = true; - extraConfig = "allow-update { key rfc2136key.example.com.; };"; - } - ]; -} - -# Now we can configure ACME - = true; - = "admin+acme@example.com"; -."example.com" = { - domain = "*.example.com"; - dnsProvider = "rfc2136"; - credentialsFile = "/var/lib/secrets/certs.secret"; - # We don't need to wait for propagation since this is a local DNS server - dnsPropagationCheck = false; -}; - - - - The dnskeys.conf and certs.secret - must be kept secure and thus you should not keep their contents in your - Nix config. Instead, generate them one time with a systemd service: - - - -systemd.services.dns-rfc2136-conf = { - requiredBy = ["acme-example.com.service", "bind.service"]; - before = ["acme-example.com.service", "bind.service"]; - unitConfig = { - ConditionPathExists = "!/var/lib/secrets/dnskeys.conf"; - }; - serviceConfig = { - Type = "oneshot"; - UMask = 0077; - }; - path = [ pkgs.bind ]; - script = '' - mkdir -p /var/lib/secrets - tsig-keygen rfc2136key.example.com > /var/lib/secrets/dnskeys.conf - chown named:root /var/lib/secrets/dnskeys.conf - chmod 400 /var/lib/secrets/dnskeys.conf - - # Copy the secret value from the dnskeys.conf, and put it in - # RFC2136_TSIG_SECRET below - - cat > /var/lib/secrets/certs.secret << EOF - RFC2136_NAMESERVER='127.0.0.1:53' - RFC2136_TSIG_ALGORITHM='hmac-sha256.' - RFC2136_TSIG_KEY='rfc2136key.example.com' - RFC2136_TSIG_SECRET='your secret key' - EOF - chmod 400 /var/lib/secrets/certs.secret - ''; -}; - - - - Now you're all set to generate certs! You should monitor the first invocation - by running systemctl start acme-example.com.service & - journalctl -fu acme-example.com.service and watching its log output. - -
- -
- Using DNS validation with web server virtual hosts - - - It is possible to use DNS-01 validation with all certificates, - including those automatically configured via the Nginx/Apache - enableACME - option. This configuration pattern is fully - supported and part of the module's test suite for Nginx + Apache. - - - - You must follow the guide above on configuring DNS-01 validation - first, however instead of setting the options for one certificate - (e.g. ) - you will set them as defaults - (e.g. ). - - - -# Configure ACME appropriately - = true; - = "admin+acme@example.com"; - = { - dnsProvider = "rfc2136"; - credentialsFile = "/var/lib/secrets/certs.secret"; - # We don't need to wait for propagation since this is a local DNS server - dnsPropagationCheck = false; -}; - -# For each virtual host you would like to use DNS-01 validation with, -# set acmeRoot = null -services.nginx = { - enable = true; - virtualHosts = { - "foo.example.com" = { - enableACME = true; - acmeRoot = null; - }; - }; -} - - - - And that's it! Next time your configuration is rebuilt, or when - you add a new virtualHost, it will be DNS-01 validated. - -
- -
- Using ACME with services demanding root owned certificates - - - Some services refuse to start if the configured certificate files - are not owned by root. PostgreSQL and OpenSMTPD are examples of these. - There is no way to change the user the ACME module uses (it will always be - acme), however you can use systemd's - LoadCredential feature to resolve this elegantly. - Below is an example configuration for OpenSMTPD, but this pattern - can be applied to any service. - - - -# Configure ACME however you like (DNS or HTTP validation), adding -# the following configuration for the relevant certificate. -# Note: You cannot use `systemctl reload` here as that would mean -# the LoadCredential configuration below would be skipped and -# the service would continue to use old certificates. -security.acme.certs."mail.example.com".postRun = '' - systemctl restart opensmtpd -''; - -# Now you must augment OpenSMTPD's systemd service to load -# the certificate files. -systemd.services.opensmtpd.requires = ["acme-finished-mail.example.com.target"]; -systemd.services.opensmtpd.serviceConfig.LoadCredential = let - certDir = config.security.acme.certs."mail.example.com".directory; -in [ - "cert.pem:${certDir}/cert.pem" - "key.pem:${certDir}/key.pem" -]; - -# Finally, configure OpenSMTPD to use these certs. -services.opensmtpd = let - credsDir = "/run/credentials/opensmtpd.service"; -in { - enable = true; - setSendmail = false; - serverConfiguration = '' - pki mail.example.com cert "${credsDir}/cert.pem" - pki mail.example.com key "${credsDir}/key.pem" - listen on localhost tls pki mail.example.com - action act1 relay host smtp://127.0.0.1:10027 - match for local action act1 - ''; -}; - -
- -
- Regenerating certificates - - - Should you need to regenerate a particular certificate in a hurry, such - as when a vulnerability is found in Let's Encrypt, there is now a convenient - mechanism for doing so. Running - systemctl clean --what=state acme-example.com.service - will remove all certificate files and the account data for the given domain, - allowing you to then systemctl start acme-example.com.service - to generate fresh ones. - -
-
- Fixing JWS Verification error - - - It is possible that your account credentials file may become corrupt and need - to be regenerated. In this scenario lego will produce the error JWS verification error. - The solution is to simply delete the associated accounts file and - re-run the affected service(s). - - - -# Find the accounts folder for the certificate -systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*' -export accountdir="$(!!)" -# Move this folder to some place else -mv /var/lib/acme/.lego/$accountdir{,.bak} -# Recreate the folder using systemd-tmpfiles -systemd-tmpfiles --create -# Get a new account and reissue certificates -# Note: Do this for all certs that share the same account email address -systemctl start acme-example.com.service - - -
-
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix new file mode 100644 index 00000000000..d827c448055 --- /dev/null +++ b/nixos/modules/security/acme/default.nix @@ -0,0 +1,921 @@ +{ config, lib, pkgs, options, ... }: +with lib; +let + cfg = config.security.acme; + opt = options.security.acme; + user = if cfg.useRoot then "root" else "acme"; + + # Used to calculate timer accuracy for coalescing + numCerts = length (builtins.attrNames cfg.certs); + _24hSecs = 60 * 60 * 24; + + # Used to make unique paths for each cert/account config set + mkHash = with builtins; val: substring 0 20 (hashString "sha256" val); + mkAccountHash = acmeServer: data: mkHash "${toString acmeServer} ${data.keyType} ${data.email}"; + accountDirRoot = "/var/lib/acme/.lego/accounts/"; + + # There are many services required to make cert renewals work. + # They all follow a common structure: + # - They inherit this commonServiceConfig + # - They all run as the acme user + # - They all use BindPath and StateDirectory where possible + # to set up a sort of build environment in /tmp + # The Group can vary depending on what the user has specified in + # security.acme.certs..group on some of the services. + commonServiceConfig = { + Type = "oneshot"; + User = user; + Group = mkDefault "acme"; + UMask = 0022; + StateDirectoryMode = 750; + ProtectSystem = "strict"; + ReadWritePaths = [ + "/var/lib/acme" + ]; + PrivateTmp = true; + + WorkingDirectory = "/tmp"; + + CapabilityBoundingSet = [ "" ]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + ProtectClock = true; + ProtectHome = true; + ProtectHostname = true; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + # 1. allow a reasonable set of syscalls + "@system-service" + # 2. and deny unreasonable ones + "~@privileged @resources" + # 3. then allow the required subset within denied groups + "@chown" + ]; + }; + + # In order to avoid race conditions creating the CA for selfsigned certs, + # we have a separate service which will create the necessary files. + selfsignCAService = { + description = "Generate self-signed certificate authority"; + + path = with pkgs; [ minica ]; + + unitConfig = { + ConditionPathExists = "!/var/lib/acme/.minica/key.pem"; + StartLimitIntervalSec = 0; + }; + + serviceConfig = commonServiceConfig // { + StateDirectory = "acme/.minica"; + BindPaths = "/var/lib/acme/.minica:/tmp/ca"; + UMask = 0077; + }; + + # Working directory will be /tmp + script = '' + minica \ + --ca-key ca/key.pem \ + --ca-cert ca/cert.pem \ + --domains selfsigned.local + ''; + }; + + # Ensures that directories which are shared across all certs + # exist and have the correct user and group, since group + # is configurable on a per-cert basis. + userMigrationService = let + script = with builtins; '' + chown -R ${user} .lego/accounts + '' + (concatStringsSep "\n" (mapAttrsToList (cert: data: '' + for fixpath in ${escapeShellArg cert} .lego/${escapeShellArg cert}; do + if [ -d "$fixpath" ]; then + chmod -R u=rwX,g=rX,o= "$fixpath" + chown -R ${user}:${data.group} "$fixpath" + fi + done + '') certConfigs)); + in { + description = "Fix owner and group of all ACME certificates"; + + serviceConfig = commonServiceConfig // { + # We don't want this to run every time a renewal happens + RemainAfterExit = true; + + # These StateDirectory entries negate the need for tmpfiles + StateDirectory = [ "acme" "acme/.lego" "acme/.lego/accounts" ]; + StateDirectoryMode = 755; + WorkingDirectory = "/var/lib/acme"; + + # Run the start script as root + ExecStart = "+" + (pkgs.writeShellScript "acme-fixperms" script); + }; + }; + + certToConfig = cert: data: let + acmeServer = data.server; + useDns = data.dnsProvider != null; + destPath = "/var/lib/acme/${cert}"; + selfsignedDeps = optionals (cfg.preliminarySelfsigned) [ "acme-selfsigned-${cert}.service" ]; + + # Minica and lego have a "feature" which replaces * with _. We need + # to make this substitution to reference the output files from both programs. + # End users never see this since we rename the certs. + keyName = builtins.replaceStrings ["*"] ["_"] data.domain; + + # FIXME when mkChangedOptionModule supports submodules, change to that. + # This is a workaround + extraDomains = data.extraDomainNames ++ ( + optionals + (data.extraDomains != "_mkMergedOptionModule") + (builtins.attrNames data.extraDomains) + ); + + # Create hashes for cert data directories based on configuration + # Flags are separated to avoid collisions + hashData = with builtins; '' + ${concatStringsSep " " data.extraLegoFlags} - + ${concatStringsSep " " data.extraLegoRunFlags} - + ${concatStringsSep " " data.extraLegoRenewFlags} - + ${toString acmeServer} ${toString data.dnsProvider} + ${toString data.ocspMustStaple} ${data.keyType} + ''; + certDir = mkHash hashData; + # TODO remove domainHash usage entirely. Waiting on go-acme/lego#1532 + domainHash = mkHash "${concatStringsSep " " extraDomains} ${data.domain}"; + accountHash = (mkAccountHash acmeServer data); + accountDir = accountDirRoot + accountHash; + + protocolOpts = if useDns then ( + [ "--dns" data.dnsProvider ] + ++ optionals (!data.dnsPropagationCheck) [ "--dns.disable-cp" ] + ++ optionals (data.dnsResolver != null) [ "--dns.resolvers" data.dnsResolver ] + ) else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ] + else [ "--http" "--http.webroot" data.webroot ]; + + commonOpts = [ + "--accept-tos" # Checking the option is covered by the assertions + "--path" "." + "-d" data.domain + "--email" data.email + "--key-type" data.keyType + ] ++ protocolOpts + ++ optionals (acmeServer != null) [ "--server" acmeServer ] + ++ concatMap (name: [ "-d" name ]) extraDomains + ++ data.extraLegoFlags; + + # Although --must-staple is common to both modes, it is not declared as a + # mode-agnostic argument in lego and thus must come after the mode. + runOpts = escapeShellArgs ( + commonOpts + ++ [ "run" ] + ++ optionals data.ocspMustStaple [ "--must-staple" ] + ++ data.extraLegoRunFlags + ); + renewOpts = escapeShellArgs ( + commonOpts + ++ [ "renew" ] + ++ optionals data.ocspMustStaple [ "--must-staple" ] + ++ data.extraLegoRenewFlags + ); + + # We need to collect all the ACME webroots to grant them write + # access in the systemd service. + webroots = + lib.remove null + (lib.unique + (builtins.map + (certAttrs: certAttrs.webroot) + (lib.attrValues config.security.acme.certs))); + in { + inherit accountHash cert selfsignedDeps; + + group = data.group; + + renewTimer = { + description = "Renew ACME Certificate for ${cert}"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = data.renewInterval; + Unit = "acme-${cert}.service"; + Persistent = "yes"; + + # Allow systemd to pick a convenient time within the day + # to run the check. + # This allows the coalescing of multiple timer jobs. + # We divide by the number of certificates so that if you + # have many certificates, the renewals are distributed over + # the course of the day to avoid rate limits. + AccuracySec = "${toString (_24hSecs / numCerts)}s"; + + # Skew randomly within the day, per https://letsencrypt.org/docs/integration-guide/. + RandomizedDelaySec = "24h"; + }; + }; + + selfsignService = { + description = "Generate self-signed certificate for ${cert}"; + after = [ "acme-selfsigned-ca.service" "acme-fixperms.service" ]; + requires = [ "acme-selfsigned-ca.service" "acme-fixperms.service" ]; + + path = with pkgs; [ minica ]; + + unitConfig = { + ConditionPathExists = "!/var/lib/acme/${cert}/key.pem"; + StartLimitIntervalSec = 0; + }; + + serviceConfig = commonServiceConfig // { + Group = data.group; + UMask = 0027; + + StateDirectory = "acme/${cert}"; + + BindPaths = [ + "/var/lib/acme/.minica:/tmp/ca" + "/var/lib/acme/${cert}:/tmp/${keyName}" + ]; + }; + + # Working directory will be /tmp + # minica will output to a folder sharing the name of the first domain + # in the list, which will be ${data.domain} + script = '' + minica \ + --ca-key ca/key.pem \ + --ca-cert ca/cert.pem \ + --domains ${escapeShellArg (builtins.concatStringsSep "," ([ data.domain ] ++ extraDomains))} + + # Create files to match directory layout for real certificates + cd '${keyName}' + cp ../ca/cert.pem chain.pem + cat cert.pem chain.pem > fullchain.pem + cat key.pem fullchain.pem > full.pem + + # Group might change between runs, re-apply it + chown '${user}:${data.group}' * + + # Default permissions make the files unreadable by group + anon + # Need to be readable by group + chmod 640 * + ''; + }; + + renewService = { + description = "Renew ACME certificate for ${cert}"; + after = [ "network.target" "network-online.target" "acme-fixperms.service" "nss-lookup.target" ] ++ selfsignedDeps; + wants = [ "network-online.target" "acme-fixperms.service" ] ++ selfsignedDeps; + + # https://github.com/NixOS/nixpkgs/pull/81371#issuecomment-605526099 + wantedBy = optionals (!config.boot.isContainer) [ "multi-user.target" ]; + + path = with pkgs; [ lego coreutils diffutils openssl ]; + + serviceConfig = commonServiceConfig // { + Group = data.group; + + # Keep in mind that these directories will be deleted if the user runs + # systemctl clean --what=state + # acme/.lego/${cert} is listed for this reason. + StateDirectory = [ + "acme/${cert}" + "acme/.lego/${cert}" + "acme/.lego/${cert}/${certDir}" + "acme/.lego/accounts/${accountHash}" + ]; + + ReadWritePaths = commonServiceConfig.ReadWritePaths ++ webroots; + + # Needs to be space separated, but can't use a multiline string because that'll include newlines + BindPaths = [ + "${accountDir}:/tmp/accounts" + "/var/lib/acme/${cert}:/tmp/out" + "/var/lib/acme/.lego/${cert}/${certDir}:/tmp/certificates" + ]; + + # Only try loading the credentialsFile if the dns challenge is enabled + EnvironmentFile = mkIf useDns data.credentialsFile; + + # Run as root (Prefixed with +) + ExecStartPost = "+" + (pkgs.writeShellScript "acme-postrun" '' + cd /var/lib/acme/${escapeShellArg cert} + if [ -e renewed ]; then + rm renewed + ${data.postRun} + ${optionalString (data.reloadServices != []) + "systemctl --no-block try-reload-or-restart ${escapeShellArgs data.reloadServices}" + } + fi + ''); + } // optionalAttrs (data.listenHTTP != null && toInt (elemAt (splitString ":" data.listenHTTP) 1) < 1024) { + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + }; + + # Working directory will be /tmp + script = '' + ${optionalString data.enableDebugLogs "set -x"} + set -euo pipefail + + # This reimplements the expiration date check, but without querying + # the acme server first. By doing this offline, we avoid errors + # when the network or DNS are unavailable, which can happen during + # nixos-rebuild switch. + is_expiration_skippable() { + pem=$1 + + # This function relies on set -e to exit early if any of the + # conditions or programs fail. + + [[ -e $pem ]] + + expiration_line="$( + set -euxo pipefail + openssl x509 -noout -enddate <$pem \ + | grep notAfter \ + | sed -e 's/^notAfter=//' + )" + [[ -n "$expiration_line" ]] + + expiration_date="$(date -d "$expiration_line" +%s)" + now="$(date +%s)" + expiration_s=$[expiration_date - now] + expiration_days=$[expiration_s / (3600 * 24)] # rounds down + + [[ $expiration_days -gt ${toString data.validMinDays} ]] + } + + ${optionalString (data.webroot != null) '' + # Ensure the webroot exists. Fixing group is required in case configuration was changed between runs. + # Lego will fail if the webroot does not exist at all. + ( + mkdir -p '${data.webroot}/.well-known/acme-challenge' \ + && chgrp '${data.group}' ${data.webroot}/.well-known/acme-challenge + ) || ( + echo 'Please ensure ${data.webroot}/.well-known/acme-challenge exists and is writable by acme:${data.group}' \ + && exit 1 + ) + ''} + + echo '${domainHash}' > domainhash.txt + + # Check if we can renew. + # We can only renew if the list of domains has not changed. + if cmp -s domainhash.txt certificates/domainhash.txt && [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' -a -n "$(ls -1 accounts)" ]; then + + # Even if a cert is not expired, it may be revoked by the CA. + # Try to renew, and silently fail if the cert is not expired. + # Avoids #85794 and resolves #129838 + if ! lego ${renewOpts} --days ${toString data.validMinDays}; then + if is_expiration_skippable out/full.pem; then + echo 1>&2 "nixos-acme: Ignoring failed renewal because expiration isn't within the coming ${toString data.validMinDays} days" + else + # High number to avoid Systemd reserved codes. + exit 11 + fi + fi + + # Otherwise do a full run + elif ! lego ${runOpts}; then + # Produce a nice error for those doing their first nixos-rebuild with these certs + echo Failed to fetch certificates. \ + This may mean your DNS records are set up incorrectly. \ + ${optionalString (cfg.preliminarySelfsigned) "Selfsigned certs are in place and dependant services will still start."} + # Exit 10 so that users can potentially amend SuccessExitStatus to ignore this error. + # High number to avoid Systemd reserved codes. + exit 10 + fi + + mv domainhash.txt certificates/ + + # Group might change between runs, re-apply it + chown '${user}:${data.group}' certificates/* + + # Copy all certs to the "real" certs directory + if ! cmp -s 'certificates/${keyName}.crt' out/fullchain.pem; then + touch out/renewed + echo Installing new certificate + cp -vp 'certificates/${keyName}.crt' out/fullchain.pem + cp -vp 'certificates/${keyName}.key' out/key.pem + cp -vp 'certificates/${keyName}.issuer.crt' out/chain.pem + ln -sf fullchain.pem out/cert.pem + cat out/key.pem out/fullchain.pem > out/full.pem + fi + + # By default group will have no access to the cert files. + # This chmod will fix that. + chmod 640 out/* + ''; + }; + }; + + certConfigs = mapAttrs certToConfig cfg.certs; + + # These options can be specified within + # security.acme.defaults or security.acme.certs. + inheritableModule = isDefaults: { config, ... }: let + defaultAndText = name: default: { + # When ! isDefaults then this is the option declaration for the + # security.acme.certs. path, which has the extra inheritDefaults + # option, which if disabled means that we can't inherit it + default = if isDefaults || ! config.inheritDefaults then default else cfg.defaults.${name}; + # The docs however don't need to depend on inheritDefaults, they should + # stay constant. Though notably it wouldn't matter much, because to get + # the option information, a submodule with name `` is evaluated + # without any definitions. + defaultText = if isDefaults then default else literalExpression "config.security.acme.defaults.${name}"; + }; + in { + options = { + validMinDays = mkOption { + type = types.int; + inherit (defaultAndText "validMinDays" 30) default defaultText; + description = "Minimum remaining validity before renewal in days."; + }; + + renewInterval = mkOption { + type = types.str; + inherit (defaultAndText "renewInterval" "daily") default defaultText; + description = '' + Systemd calendar expression when to check for renewal. See + systemd.time + 7. + ''; + }; + + enableDebugLogs = mkEnableOption "debug logging for this certificate" // { + inherit (defaultAndText "enableDebugLogs" true) default defaultText; + }; + + webroot = mkOption { + type = types.nullOr types.str; + inherit (defaultAndText "webroot" null) default defaultText; + example = "/var/lib/acme/acme-challenge"; + description = '' + Where the webroot of the HTTP vhost is located. + .well-known/acme-challenge/ directory + will be created below the webroot if it doesn't exist. + http://example.org/.well-known/acme-challenge/ must also + be available (notice unencrypted HTTP). + ''; + }; + + server = mkOption { + type = types.nullOr types.str; + inherit (defaultAndText "server" null) default defaultText; + description = '' + ACME Directory Resource URI. Defaults to Let's Encrypt's + production endpoint, + , if unset. + ''; + }; + + email = mkOption { + type = types.str; + inherit (defaultAndText "email" null) default defaultText; + description = '' + Email address for account creation and correspondence from the CA. + It is recommended to use the same email for all certs to avoid account + creation limits. + ''; + }; + + group = mkOption { + type = types.str; + inherit (defaultAndText "group" "acme") default defaultText; + description = "Group running the ACME client."; + }; + + reloadServices = mkOption { + type = types.listOf types.str; + inherit (defaultAndText "reloadServices" []) default defaultText; + description = '' + The list of systemd services to call systemctl try-reload-or-restart + on. + ''; + }; + + postRun = mkOption { + type = types.lines; + inherit (defaultAndText "postRun" "") default defaultText; + example = "cp full.pem backup.pem"; + description = '' + Commands to run after new certificates go live. Note that + these commands run as the root user. + + Executed in the same directory with the new certificate. + ''; + }; + + keyType = mkOption { + type = types.str; + inherit (defaultAndText "keyType" "ec256") default defaultText; + description = '' + Key type to use for private keys. + For an up to date list of supported values check the --key-type option + at . + ''; + }; + + dnsProvider = mkOption { + type = types.nullOr types.str; + inherit (defaultAndText "dnsProvider" null) default defaultText; + example = "route53"; + description = '' + DNS Challenge provider. For a list of supported providers, see the "code" + field of the DNS providers listed at . + ''; + }; + + dnsResolver = mkOption { + type = types.nullOr types.str; + inherit (defaultAndText "dnsResolver" null) default defaultText; + example = "1.1.1.1:53"; + description = '' + Set the resolver to use for performing recursive DNS queries. Supported: + host:port. The default is to use the system resolvers, or Google's DNS + resolvers if the system's cannot be determined. + ''; + }; + + credentialsFile = mkOption { + type = types.path; + inherit (defaultAndText "credentialsFile" null) default defaultText; + description = '' + Path to an EnvironmentFile for the cert's service containing any required and + optional environment variables for your selected dnsProvider. + To find out what values you need to set, consult the documentation at + for the corresponding dnsProvider. + ''; + example = "/var/src/secrets/example.org-route53-api-token"; + }; + + dnsPropagationCheck = mkOption { + type = types.bool; + inherit (defaultAndText "dnsPropagationCheck" true) default defaultText; + description = '' + Toggles lego DNS propagation check, which is used alongside DNS-01 + challenge to ensure the DNS entries required are available. + ''; + }; + + ocspMustStaple = mkOption { + type = types.bool; + inherit (defaultAndText "ocspMustStaple" false) default defaultText; + description = '' + Turns on the OCSP Must-Staple TLS extension. + Make sure you know what you're doing! See: + + + + + ''; + }; + + extraLegoFlags = mkOption { + type = types.listOf types.str; + inherit (defaultAndText "extraLegoFlags" []) default defaultText; + description = '' + Additional global flags to pass to all lego commands. + ''; + }; + + extraLegoRenewFlags = mkOption { + type = types.listOf types.str; + inherit (defaultAndText "extraLegoRenewFlags" []) default defaultText; + description = '' + Additional flags to pass to lego renew. + ''; + }; + + extraLegoRunFlags = mkOption { + type = types.listOf types.str; + inherit (defaultAndText "extraLegoRunFlags" []) default defaultText; + description = '' + Additional flags to pass to lego run. + ''; + }; + }; + }; + + certOpts = { name, config, ... }: { + options = { + # user option has been removed + user = mkOption { + visible = false; + default = "_mkRemovedOptionModule"; + }; + + # allowKeysForGroup option has been removed + allowKeysForGroup = mkOption { + visible = false; + default = "_mkRemovedOptionModule"; + }; + + # extraDomains was replaced with extraDomainNames + extraDomains = mkOption { + visible = false; + default = "_mkMergedOptionModule"; + }; + + directory = mkOption { + type = types.str; + readOnly = true; + default = "/var/lib/acme/${name}"; + description = "Directory where certificate and other state is stored."; + }; + + domain = mkOption { + type = types.str; + default = name; + description = "Domain to fetch certificate for (defaults to the entry name)."; + }; + + extraDomainNames = mkOption { + type = types.listOf types.str; + default = []; + example = literalExpression '' + [ + "example.org" + "mydomain.org" + ] + ''; + description = '' + A list of extra domain names, which are included in the one certificate to be issued. + ''; + }; + + # This setting must be different for each configured certificate, otherwise + # two or more renewals may fail to bind to the address. Hence, it is not in + # the inheritableOpts. + listenHTTP = mkOption { + type = types.nullOr types.str; + default = null; + example = ":1360"; + description = '' + Interface and port to listen on to solve HTTP challenges + in the form [INTERFACE]:PORT. + If you use a port other than 80, you must proxy port 80 to this port. + ''; + }; + + inheritDefaults = mkOption { + default = true; + example = true; + description = "Whether to inherit values set in `security.acme.defaults` or not."; + type = lib.types.bool; + }; + }; + }; + +in { + + options = { + security.acme = { + preliminarySelfsigned = mkOption { + type = types.bool; + default = true; + description = '' + Whether a preliminary self-signed certificate should be generated before + doing ACME requests. This can be useful when certificates are required in + a webserver, but ACME needs the webserver to make its requests. + + With preliminary self-signed certificate the webserver can be started and + can later reload the correct ACME certificates. + ''; + }; + + acceptTerms = mkOption { + type = types.bool; + default = false; + description = '' + Accept the CA's terms of service. The default provider is Let's Encrypt, + you can find their ToS at . + ''; + }; + + useRoot = mkOption { + type = types.bool; + default = false; + description = '' + Whether to use the root user when generating certs. This is not recommended + for security + compatiblity reasons. If a service requires root owned certificates + consider following the guide on "Using ACME with services demanding root + owned certificates" in the NixOS manual, and only using this as a fallback + or for testing. + ''; + }; + + defaults = mkOption { + type = types.submodule (inheritableModule true); + description = '' + Default values inheritable by all configured certs. You can + use this to define options shared by all your certs. These defaults + can also be ignored on a per-cert basis using the + `security.acme.certs.''${cert}.inheritDefaults' option. + ''; + }; + + certs = mkOption { + default = { }; + type = with types; attrsOf (submodule [ (inheritableModule false) certOpts ]); + description = '' + Attribute set of certificates to get signed and renewed. Creates + acme-''${cert}.{service,timer} systemd units for + each certificate defined here. Other services can add dependencies + to those units if they rely on the certificates being present, + or trigger restarts of the service if certificates get renewed. + ''; + example = literalExpression '' + { + "example.com" = { + webroot = "/var/lib/acme/acme-challenge/"; + email = "foo@example.com"; + extraDomainNames = [ "www.example.com" "foo.example.com" ]; + }; + "bar.example.com" = { + webroot = "/var/lib/acme/acme-challenge/"; + email = "bar@example.com"; + }; + } + ''; + }; + }; + }; + + imports = [ + (mkRemovedOptionModule [ "security" "acme" "production" ] '' + Use security.acme.server to define your staging ACME server URL instead. + + To use the let's encrypt staging server, use security.acme.server = + "https://acme-staging-v02.api.letsencrypt.org/directory". + '') + (mkRemovedOptionModule [ "security" "acme" "directory" ] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.") + (mkRemovedOptionModule [ "security" "acme" "preDelay" ] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") + (mkRemovedOptionModule [ "security" "acme" "activationDelay" ] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") + (mkChangedOptionModule [ "security" "acme" "validMin" ] [ "security" "acme" "defaults" "validMinDays" ] (config: config.security.acme.validMin / (24 * 3600))) + (mkChangedOptionModule [ "security" "acme" "validMinDays" ] [ "security" "acme" "defaults" "validMinDays" ] (config: config.security.acme.validMinDays)) + (mkChangedOptionModule [ "security" "acme" "renewInterval" ] [ "security" "acme" "defaults" "renewInterval" ] (config: config.security.acme.renewInterval)) + (mkChangedOptionModule [ "security" "acme" "email" ] [ "security" "acme" "defaults" "email" ] (config: config.security.acme.email)) + (mkChangedOptionModule [ "security" "acme" "server" ] [ "security" "acme" "defaults" "server" ] (config: config.security.acme.server)) + (mkChangedOptionModule [ "security" "acme" "enableDebugLogs" ] [ "security" "acme" "defaults" "enableDebugLogs" ] (config: config.security.acme.enableDebugLogs)) + ]; + + config = mkMerge [ + (mkIf (cfg.certs != { }) { + + # FIXME Most of these custom warnings and filters for security.acme.certs.* are required + # because using mkRemovedOptionModule/mkChangedOptionModule with attrsets isn't possible. + warnings = filter (w: w != "") (mapAttrsToList (cert: data: if data.extraDomains != "_mkMergedOptionModule" then '' + The option definition `security.acme.certs.${cert}.extraDomains` has changed + to `security.acme.certs.${cert}.extraDomainNames` and is now a list of strings. + Setting a custom webroot for extra domains is not possible, instead use separate certs. + '' else "") cfg.certs); + + assertions = let + certs = attrValues cfg.certs; + in [ + { + assertion = cfg.email != null || all (certOpts: certOpts.email != null) certs; + message = '' + You must define `security.acme.certs..email` or + `security.acme.email` to register with the CA. Note that using + many different addresses for certs may trigger account rate limits. + ''; + } + { + assertion = cfg.acceptTerms; + message = '' + You must accept the CA's terms of service before using + the ACME module by setting `security.acme.acceptTerms` + to `true`. For Let's Encrypt's ToS see https://letsencrypt.org/repository/ + ''; + } + ] ++ (builtins.concatLists (mapAttrsToList (cert: data: [ + { + assertion = data.user == "_mkRemovedOptionModule"; + message = '' + The option definition `security.acme.certs.${cert}.user' no longer has any effect; Please remove it. + Certificate user is now hard coded to the "acme" user. If you would + like another user to have access, consider adding them to the + "acme" group or changing security.acme.certs.${cert}.group. + ''; + } + { + assertion = data.allowKeysForGroup == "_mkRemovedOptionModule"; + message = '' + The option definition `security.acme.certs.${cert}.allowKeysForGroup' no longer has any effect; Please remove it. + All certs are readable by the configured group. If this is undesired, + consider changing security.acme.certs.${cert}.group to an unused group. + ''; + } + # * in the cert value breaks building of systemd services, and makes + # referencing them as a user quite weird too. Best practice is to use + # the domain option. + { + assertion = ! hasInfix "*" cert; + message = '' + The cert option path `security.acme.certs.${cert}.dnsProvider` + cannot contain a * character. + Instead, set `security.acme.certs.${cert}.domain = "${cert}";` + and remove the wildcard from the path. + ''; + } + { + assertion = data.dnsProvider == null || data.webroot == null; + message = '' + Options `security.acme.certs.${cert}.dnsProvider` and + `security.acme.certs.${cert}.webroot` are mutually exclusive. + ''; + } + { + assertion = data.webroot == null || data.listenHTTP == null; + message = '' + Options `security.acme.certs.${cert}.webroot` and + `security.acme.certs.${cert}.listenHTTP` are mutually exclusive. + ''; + } + { + assertion = data.listenHTTP == null || data.dnsProvider == null; + message = '' + Options `security.acme.certs.${cert}.listenHTTP` and + `security.acme.certs.${cert}.dnsProvider` are mutually exclusive. + ''; + } + { + assertion = data.dnsProvider != null || data.webroot != null || data.listenHTTP != null; + message = '' + One of `security.acme.certs.${cert}.dnsProvider`, + `security.acme.certs.${cert}.webroot`, or + `security.acme.certs.${cert}.listenHTTP` must be provided. + ''; + } + ]) cfg.certs)); + + users.users.acme = { + home = "/var/lib/acme"; + group = "acme"; + isSystemUser = true; + }; + + users.groups.acme = {}; + + systemd.services = { + "acme-fixperms" = userMigrationService; + } // (mapAttrs' (cert: conf: nameValuePair "acme-${cert}" conf.renewService) certConfigs) + // (optionalAttrs (cfg.preliminarySelfsigned) ({ + "acme-selfsigned-ca" = selfsignCAService; + } // (mapAttrs' (cert: conf: nameValuePair "acme-selfsigned-${cert}" conf.selfsignService) certConfigs))); + + systemd.timers = mapAttrs' (cert: conf: nameValuePair "acme-${cert}" conf.renewTimer) certConfigs; + + systemd.targets = let + # Create some targets which can be depended on to be "active" after cert renewals + finishedTargets = mapAttrs' (cert: conf: nameValuePair "acme-finished-${cert}" { + wantedBy = [ "default.target" ]; + requires = [ "acme-${cert}.service" ]; + after = [ "acme-${cert}.service" ]; + }) certConfigs; + + # Create targets to limit the number of simultaneous account creations + # How it works: + # - Pick a "leader" cert service, which will be in charge of creating the account, + # and run first (requires + after) + # - Make all other cert services sharing the same account wait for the leader to + # finish before starting (requiredBy + before). + # Using a target here is fine - account creation is a one time event. Even if + # systemd clean --what=state is used to delete the account, so long as the user + # then runs one of the cert services, there won't be any issues. + accountTargets = mapAttrs' (hash: confs: let + leader = "acme-${(builtins.head confs).cert}.service"; + dependantServices = map (conf: "acme-${conf.cert}.service") (builtins.tail confs); + in nameValuePair "acme-account-${hash}" { + requiredBy = dependantServices; + before = dependantServices; + requires = [ leader ]; + after = [ leader ]; + }) (groupBy (conf: conf.accountHash) (attrValues certConfigs)); + in finishedTargets // accountTargets; + }) + ]; + + meta = { + maintainers = lib.teams.acme.members; + doc = ./doc.xml; + }; +} diff --git a/nixos/modules/security/acme/doc.xml b/nixos/modules/security/acme/doc.xml new file mode 100644 index 00000000000..f623cc509be --- /dev/null +++ b/nixos/modules/security/acme/doc.xml @@ -0,0 +1,413 @@ + + SSL/TLS Certificates with ACME + + NixOS supports automatic domain validation & certificate retrieval and + renewal using the ACME protocol. Any provider can be used, but by default + NixOS uses Let's Encrypt. The alternative ACME client + lego is used under + the hood. + + + Automatic cert validation and configuration for Apache and Nginx virtual + hosts is included in NixOS, however if you would like to generate a wildcard + cert or you are not using a web server you will have to configure DNS + based validation. + +
+ Prerequisites + + + To use the ACME module, you must accept the provider's terms of service + by setting + to true. The Let's Encrypt ToS can be found + here. + + + + You must also set an email address to be used when creating accounts with + Let's Encrypt. You can set this for all certs with + + and/or on a per-cert basis with + . + This address is only used for registration and renewal reminders, + and cannot be used to administer the certificates in any way. + + + + Alternatively, you can use a different ACME server by changing the + option + to a provider of your choosing, or just change the server for one cert with + . + + + + You will need an HTTP server or DNS server for verification. For HTTP, + the server must have a webroot defined that can serve + .well-known/acme-challenge. This directory must be + writeable by the user that will run the ACME client. For DNS, you must + set up credentials with your provider/server for use with lego. + +
+
+ Using ACME certificates in Nginx + + + NixOS supports fetching ACME certificates for you by setting + enableACME + = true; in a virtualHost config. We first create self-signed + placeholder certificates in place of the real ACME certs. The placeholder + certs are overwritten when the ACME certs arrive. For + foo.example.com the config would look like this: + + + + = true; + = "admin+acme@example.com"; +services.nginx = { + enable = true; + virtualHosts = { + "foo.example.com" = { + forceSSL = true; + enableACME = true; + # All serverAliases will be added as extra domain names on the certificate. + serverAliases = [ "bar.example.com" ]; + locations."/" = { + root = "/var/www"; + }; + }; + + # We can also add a different vhost and reuse the same certificate + # but we have to append extraDomainNames manually. + security.acme.certs."foo.example.com".extraDomainNames = [ "baz.example.com" ]; + "baz.example.com" = { + forceSSL = true; + useACMEHost = "foo.example.com"; + locations."/" = { + root = "/var/www"; + }; + }; + }; +} + +
+
+ Using ACME certificates in Apache/httpd + + + Using ACME certificates with Apache virtual hosts is identical + to using them with Nginx. The attribute names are all the same, just replace + "nginx" with "httpd" where appropriate. + +
+
+ Manual configuration of HTTP-01 validation + + + First off you will need to set up a virtual host to serve the challenges. + This example uses a vhost called certs.example.com, with + the intent that you will generate certs for all your vhosts and redirect + everyone to HTTPS. + + + + = true; + = "admin+acme@example.com"; + +# /var/lib/acme/.challenges must be writable by the ACME user +# and readable by the Nginx user. The easiest way to achieve +# this is to add the Nginx user to the ACME group. +users.users.nginx.extraGroups = [ "acme" ]; + +services.nginx = { + enable = true; + virtualHosts = { + "acmechallenge.example.com" = { + # Catchall vhost, will redirect users to HTTPS for all vhosts + serverAliases = [ "*.example.com" ]; + locations."/.well-known/acme-challenge" = { + root = "/var/lib/acme/.challenges"; + }; + locations."/" = { + return = "301 https://$host$request_uri"; + }; + }; + }; +} +# Alternative config for Apache +users.users.wwwrun.extraGroups = [ "acme" ]; +services.httpd = { + enable = true; + virtualHosts = { + "acmechallenge.example.com" = { + # Catchall vhost, will redirect users to HTTPS for all vhosts + serverAliases = [ "*.example.com" ]; + # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user. + # By default, this is the case. + documentRoot = "/var/lib/acme/.challenges"; + extraConfig = '' + RewriteEngine On + RewriteCond %{HTTPS} off + RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge [NC] + RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301] + ''; + }; + }; +} + + + + Now you need to configure ACME to generate a certificate. + + + +."foo.example.com" = { + webroot = "/var/lib/acme/.challenges"; + email = "foo@example.com"; + # Ensure that the web server you use can read the generated certs + # Take a look at the group option for the web server you choose. + group = "nginx"; + # Since we have a wildcard vhost to handle port 80, + # we can generate certs for anything! + # Just make sure your DNS resolves them. + extraDomainNames = [ "mail.example.com" ]; +}; + + + + The private key key.pem and certificate + fullchain.pem will be put into + /var/lib/acme/foo.example.com. + + + + Refer to for all available configuration + options for the security.acme + module. + +
+
+ Configuring ACME for DNS validation + + + This is useful if you want to generate a wildcard certificate, since + ACME servers will only hand out wildcard certs over DNS validation. + There are a number of supported DNS providers and servers you can utilise, + see the lego docs + for provider/server specific configuration values. For the sake of these + docs, we will provide a fully self-hosted example using bind. + + + +services.bind = { + enable = true; + extraConfig = '' + include "/var/lib/secrets/dnskeys.conf"; + ''; + zones = [ + rec { + name = "example.com"; + file = "/var/db/bind/${name}"; + master = true; + extraConfig = "allow-update { key rfc2136key.example.com.; };"; + } + ]; +} + +# Now we can configure ACME + = true; + = "admin+acme@example.com"; +."example.com" = { + domain = "*.example.com"; + dnsProvider = "rfc2136"; + credentialsFile = "/var/lib/secrets/certs.secret"; + # We don't need to wait for propagation since this is a local DNS server + dnsPropagationCheck = false; +}; + + + + The dnskeys.conf and certs.secret + must be kept secure and thus you should not keep their contents in your + Nix config. Instead, generate them one time with a systemd service: + + + +systemd.services.dns-rfc2136-conf = { + requiredBy = ["acme-example.com.service", "bind.service"]; + before = ["acme-example.com.service", "bind.service"]; + unitConfig = { + ConditionPathExists = "!/var/lib/secrets/dnskeys.conf"; + }; + serviceConfig = { + Type = "oneshot"; + UMask = 0077; + }; + path = [ pkgs.bind ]; + script = '' + mkdir -p /var/lib/secrets + tsig-keygen rfc2136key.example.com > /var/lib/secrets/dnskeys.conf + chown named:root /var/lib/secrets/dnskeys.conf + chmod 400 /var/lib/secrets/dnskeys.conf + + # Copy the secret value from the dnskeys.conf, and put it in + # RFC2136_TSIG_SECRET below + + cat > /var/lib/secrets/certs.secret << EOF + RFC2136_NAMESERVER='127.0.0.1:53' + RFC2136_TSIG_ALGORITHM='hmac-sha256.' + RFC2136_TSIG_KEY='rfc2136key.example.com' + RFC2136_TSIG_SECRET='your secret key' + EOF + chmod 400 /var/lib/secrets/certs.secret + ''; +}; + + + + Now you're all set to generate certs! You should monitor the first invocation + by running systemctl start acme-example.com.service & + journalctl -fu acme-example.com.service and watching its log output. + +
+ +
+ Using DNS validation with web server virtual hosts + + + It is possible to use DNS-01 validation with all certificates, + including those automatically configured via the Nginx/Apache + enableACME + option. This configuration pattern is fully + supported and part of the module's test suite for Nginx + Apache. + + + + You must follow the guide above on configuring DNS-01 validation + first, however instead of setting the options for one certificate + (e.g. ) + you will set them as defaults + (e.g. ). + + + +# Configure ACME appropriately + = true; + = "admin+acme@example.com"; + = { + dnsProvider = "rfc2136"; + credentialsFile = "/var/lib/secrets/certs.secret"; + # We don't need to wait for propagation since this is a local DNS server + dnsPropagationCheck = false; +}; + +# For each virtual host you would like to use DNS-01 validation with, +# set acmeRoot = null +services.nginx = { + enable = true; + virtualHosts = { + "foo.example.com" = { + enableACME = true; + acmeRoot = null; + }; + }; +} + + + + And that's it! Next time your configuration is rebuilt, or when + you add a new virtualHost, it will be DNS-01 validated. + +
+ +
+ Using ACME with services demanding root owned certificates + + + Some services refuse to start if the configured certificate files + are not owned by root. PostgreSQL and OpenSMTPD are examples of these. + There is no way to change the user the ACME module uses (it will always be + acme), however you can use systemd's + LoadCredential feature to resolve this elegantly. + Below is an example configuration for OpenSMTPD, but this pattern + can be applied to any service. + + + +# Configure ACME however you like (DNS or HTTP validation), adding +# the following configuration for the relevant certificate. +# Note: You cannot use `systemctl reload` here as that would mean +# the LoadCredential configuration below would be skipped and +# the service would continue to use old certificates. +security.acme.certs."mail.example.com".postRun = '' + systemctl restart opensmtpd +''; + +# Now you must augment OpenSMTPD's systemd service to load +# the certificate files. +systemd.services.opensmtpd.requires = ["acme-finished-mail.example.com.target"]; +systemd.services.opensmtpd.serviceConfig.LoadCredential = let + certDir = config.security.acme.certs."mail.example.com".directory; +in [ + "cert.pem:${certDir}/cert.pem" + "key.pem:${certDir}/key.pem" +]; + +# Finally, configure OpenSMTPD to use these certs. +services.opensmtpd = let + credsDir = "/run/credentials/opensmtpd.service"; +in { + enable = true; + setSendmail = false; + serverConfiguration = '' + pki mail.example.com cert "${credsDir}/cert.pem" + pki mail.example.com key "${credsDir}/key.pem" + listen on localhost tls pki mail.example.com + action act1 relay host smtp://127.0.0.1:10027 + match for local action act1 + ''; +}; + +
+ +
+ Regenerating certificates + + + Should you need to regenerate a particular certificate in a hurry, such + as when a vulnerability is found in Let's Encrypt, there is now a convenient + mechanism for doing so. Running + systemctl clean --what=state acme-example.com.service + will remove all certificate files and the account data for the given domain, + allowing you to then systemctl start acme-example.com.service + to generate fresh ones. + +
+
+ Fixing JWS Verification error + + + It is possible that your account credentials file may become corrupt and need + to be regenerated. In this scenario lego will produce the error JWS verification error. + The solution is to simply delete the associated accounts file and + re-run the affected service(s). + + + +# Find the accounts folder for the certificate +systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*' +export accountdir="$(!!)" +# Move this folder to some place else +mv /var/lib/acme/.lego/$accountdir{,.bak} +# Recreate the folder using systemd-tmpfiles +systemd-tmpfiles --create +# Get a new account and reissue certificates +# Note: Do this for all certs that share the same account email address +systemctl start acme-example.com.service + + +
+
diff --git a/nixos/modules/security/acme/mk-cert-ownership-assertion.nix b/nixos/modules/security/acme/mk-cert-ownership-assertion.nix new file mode 100644 index 00000000000..b80d89aeb9f --- /dev/null +++ b/nixos/modules/security/acme/mk-cert-ownership-assertion.nix @@ -0,0 +1,4 @@ +{ cert, group, groups, user }: { + assertion = cert.group == group || builtins.any (u: u == user) groups.${cert.group}.members; + message = "Group for certificate ${cert.domain} must be ${group}, or user ${user} must be a member of group ${cert.group}"; +} diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index 1a49b4ca15c..d817ff6019a 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -370,6 +370,8 @@ let cat ${php.phpIni} > $out echo "$options" >> $out ''; + + mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix; in @@ -657,7 +659,11 @@ in `services.httpd.virtualHosts..useACMEHost` are mutually exclusive. ''; } - ]; + ] ++ map (name: mkCertOwnershipAssertion { + inherit (cfg) group user; + cert = config.security.acme.certs.${name}; + groups = config.users.groups; + }) dependentCertNames; warnings = mapAttrsToList (name: hostOpts: '' diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix index a4ada662cfb..2b8c6f2e308 100644 --- a/nixos/modules/services/web-servers/caddy/default.nix +++ b/nixos/modules/services/web-servers/caddy/default.nix @@ -38,6 +38,10 @@ let ''; in if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile; + + acmeHosts = unique (catAttrs "useACMEHost" acmeVHosts); + + mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix; in { imports = [ @@ -266,7 +270,11 @@ in { assertion = cfg.adapter != "caddyfile" -> cfg.configFile != configFile; message = "Any value other than 'caddyfile' is only valid when providing your own `services.caddy.configFile`"; } - ]; + ] ++ map (name: mkCertOwnershipAssertion { + inherit (cfg) group user; + cert = config.security.acme.certs.${name}; + groups = config.users.groups; + }) acmeHosts; services.caddy.extraConfig = concatMapStringsSep "\n" mkVHostConf virtualHosts; services.caddy.globalConfig = '' @@ -323,8 +331,7 @@ in security.acme.certs = let - eachACMEHost = unique (catAttrs "useACMEHost" acmeVHosts); - reloads = map (useACMEHost: nameValuePair useACMEHost { reloadServices = [ "caddy.service" ]; }) eachACMEHost; + reloads = map (useACMEHost: nameValuePair useACMEHost { reloadServices = [ "caddy.service" ]; }) acmeHosts; in listToAttrs reloads; diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index dc174c8b41d..41bce3669c5 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -374,6 +374,8 @@ let ${user}:{PLAIN}${password} '') authDef) ); + + mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix; in { @@ -842,7 +844,11 @@ in services.nginx.virtualHosts..useACMEHost are mutually exclusive. ''; } - ]; + ] ++ map (name: mkCertOwnershipAssertion { + inherit (cfg) group user; + cert = config.security.acme.certs.${name}; + groups = config.users.groups; + }) dependentCertNames; systemd.services.nginx = { description = "Nginx Web Server"; diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix index 0dd7743c52b..2dd06a50f40 100644 --- a/nixos/tests/acme.nix +++ b/nixos/tests/acme.nix @@ -54,15 +54,15 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let baseConfig = { nodes, config, specialConfig ? {} }: lib.mkMerge [ { security.acme = { - defaults = (dnsConfig nodes) // { - inherit group; - }; + defaults = (dnsConfig nodes); # One manual wildcard cert certs."example.test" = { domain = "*.example.test"; }; }; + users.users."${config.services."${server}".user}".extraGroups = ["acme"]; + services."${server}" = { enable = true; virtualHosts = { @@ -252,15 +252,15 @@ in { } // (let baseCaddyConfig = { nodes, config, ... }: { security.acme = { - defaults = (dnsConfig nodes) // { - group = config.services.caddy.group; - }; + defaults = (dnsConfig nodes); # One manual wildcard cert certs."example.test" = { domain = "*.example.test"; }; }; + users.users."${config.services.caddy.user}".extraGroups = ["acme"]; + services.caddy = { enable = true; virtualHosts."a.exmaple.test" = { -- cgit 1.4.1 From 33911b092d06e02f55998a7f74d4f28f21517974 Mon Sep 17 00:00:00 2001 From: Frank Doepper Date: Tue, 8 Jun 2021 21:37:03 +0200 Subject: nixos/tests/frr: init Co-Authored-By: Martin Weinelt --- nixos/tests/all-tests.nix | 1 + nixos/tests/frr.nix | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 nixos/tests/frr.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 4f62980e8e9..ec88beb5489 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -143,6 +143,7 @@ in fluidd = handleTest ./fluidd.nix {}; fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {}; freeswitch = handleTest ./freeswitch.nix {}; + frr = handleTest ./frr.nix {}; fsck = handleTest ./fsck.nix {}; ft2-clone = handleTest ./ft2-clone.nix {}; gerrit = handleTest ./gerrit.nix {}; diff --git a/nixos/tests/frr.nix b/nixos/tests/frr.nix new file mode 100644 index 00000000000..598d7a7d286 --- /dev/null +++ b/nixos/tests/frr.nix @@ -0,0 +1,104 @@ +# This test runs FRR and checks if OSPF routing works. +# +# Network topology: +# [ client ]--net1--[ router1 ]--net2--[ router2 ]--net3--[ server ] +# +# All interfaces are in OSPF Area 0. + +import ./make-test-python.nix ({ pkgs, ... }: + let + + ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ipv4.addresses).address; + + ospfConf1 = '' + router ospf + network 192.168.0.0/16 area 0 + ''; + + ospfConf2 = '' + interface eth2 + ip ospf hello-interval 1 + ip ospf dead-interval 5 + ! + router ospf + network 192.168.0.0/16 area 0 + ''; + + in + { + name = "frr"; + + meta = with pkgs.lib.maintainers; { + maintainers = [ hexa ]; + }; + + nodes = { + + client = + { nodes, ... }: + { + virtualisation.vlans = [ 1 ]; + networking.defaultGateway = ifAddr nodes.router1 "eth1"; + }; + + router1 = + { ... }: + { + virtualisation.vlans = [ 1 2 ]; + boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; + networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospfigp -j ACCEPT"; + services.frr.ospf = { + enable = true; + config = ospfConf1; + }; + + specialisation.ospf.configuration = { + services.frr.ospf.config = ospfConf2; + }; + }; + + router2 = + { ... }: + { + virtualisation.vlans = [ 3 2 ]; + boot.kernel.sysctl."net.ipv4.ip_forward" = "1"; + networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospfigp -j ACCEPT"; + services.frr.ospf = { + enable = true; + config = ospfConf2; + }; + }; + + server = + { nodes, ... }: + { + virtualisation.vlans = [ 3 ]; + networking.defaultGateway = ifAddr nodes.router2 "eth1"; + }; + }; + + testScript = + { nodes, ... }: + '' + start_all() + + # Wait for the networking to start on all machines + for machine in client, router1, router2, server: + machine.wait_for_unit("network.target") + + with subtest("Wait for Zebra and OSPFD"): + for gw in router1, router2: + gw.wait_for_unit("zebra") + gw.wait_for_unit("ospfd") + + router1.succeed("${nodes.router1.config.system.build.toplevel}/specialisation/ospf/bin/switch-to-configuration test >&2") + + with subtest("Wait for OSPF to form adjacencies"): + for gw in router1, router2: + gw.wait_until_succeeds("vtysh -c 'show ip ospf neighbor' | grep Full") + gw.wait_until_succeeds("vtysh -c 'show ip route' | grep '^O>'") + + with subtest("Test ICMP"): + client.wait_until_succeeds("ping -c 3 server >&2") + ''; + }) -- cgit 1.4.1 From be3967e351b6e1b010e95ec16217ed2db33da0c5 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 3 Dec 2021 12:04:36 +0000 Subject: nixos/nixpkgs.nix: Make independent (cherry picked from commit 62e7f0eda1c5acf0beb13a00a23f577912a6b8eb) --- nixos/modules/misc/nixpkgs.nix | 5 +++++ nixos/modules/misc/nixpkgs/test.nix | 8 ++++++++ nixos/tests/all-tests.nix | 4 ++++ 3 files changed, 17 insertions(+) create mode 100644 nixos/modules/misc/nixpkgs/test.nix (limited to 'nixos/tests') diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix index 2e0c8e4cf2c..69967c8a760 100644 --- a/nixos/modules/misc/nixpkgs.nix +++ b/nixos/modules/misc/nixpkgs.nix @@ -64,6 +64,11 @@ let in { + imports = [ + ./assertions.nix + ./meta.nix + ]; + options.nixpkgs = { pkgs = mkOption { diff --git a/nixos/modules/misc/nixpkgs/test.nix b/nixos/modules/misc/nixpkgs/test.nix new file mode 100644 index 00000000000..ec5fab9fb4a --- /dev/null +++ b/nixos/modules/misc/nixpkgs/test.nix @@ -0,0 +1,8 @@ +{ evalMinimalConfig, pkgs, lib, stdenv }: +lib.recurseIntoAttrs { + invokeNixpkgsSimple = + (evalMinimalConfig ({ config, modulesPath, ... }: { + imports = [ (modulesPath + "/misc/nixpkgs.nix") ]; + nixpkgs.system = stdenv.hostPlatform.system; + }))._module.args.pkgs.hello; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 4f62980e8e9..d7971f6c2eb 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -19,6 +19,9 @@ let handleTestOn = systems: path: args: if elem system systems then handleTest path args else {}; + + nixosLib = import ../lib {}; + evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; }; in { _3proxy = handleTest ./3proxy.nix {}; @@ -327,6 +330,7 @@ in nix-serve-ssh = handleTest ./nix-serve-ssh.nix {}; nixops = handleTest ./nixops/default.nix {}; nixos-generate-config = handleTest ./nixos-generate-config.nix {}; + nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; }; node-red = handleTest ./node-red.nix {}; nomad = handleTest ./nomad.nix {}; novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {}; -- cgit 1.4.1 From d3f956aba324a18bfafde59138929b320a9b4a2b Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 7 Jan 2022 01:09:46 +0100 Subject: nixos/lib: Add featureFlags, use it for minimal modules --- nixos/lib/default.nix | 24 ++++++++++++++++++++++-- nixos/lib/eval-config-minimal.nix | 13 +++++-------- nixos/tests/all-tests.nix | 6 +++++- 3 files changed, 32 insertions(+), 11 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/lib/default.nix b/nixos/lib/default.nix index 41ae2d863e0..2b3056e0145 100644 --- a/nixos/lib/default.nix +++ b/nixos/lib/default.nix @@ -1,5 +1,25 @@ -{ lib ? import ../../lib, ... }: let + # The warning is in a top-level let binding so it is only printed once. + minimalModulesWarning = warn "lib.nixos.evalModules is experimental and subject to change. See nixos/lib/default.nix" null; + inherit (nonExtendedLib) warn; + nonExtendedLib = import ../../lib; +in +{ # Optional. Allows an extended `lib` to be used instead of the regular Nixpkgs lib. + lib ? nonExtendedLib, + + # Feature flags allow you to opt in to unfinished code. These may change some + # behavior or disable warnings. + featureFlags ? {}, + + # This file itself is rather new, so we accept unknown parameters to be forward + # compatible. This is generally not recommended, because typos go undetected. + ... +}: +let + seqIf = cond: if cond then builtins.seq else a: b: b; + # If cond, force `a` before returning any attr + seqAttrsIf = cond: a: lib.mapAttrs (_: v: seqIf cond a v); + eval-config-minimal = import ./eval-config-minimal.nix { inherit lib; }; in /* @@ -7,7 +27,7 @@ in using a binding like `nixosLib = import (nixpkgs + "/nixos/lib") { }`. */ { - inherit (eval-config-minimal) + inherit (seqAttrsIf (!featureFlags?minimalModules) minimalModulesWarning eval-config-minimal) evalModules ; } diff --git a/nixos/lib/eval-config-minimal.nix b/nixos/lib/eval-config-minimal.nix index a0fcf238977..ed26e623b2f 100644 --- a/nixos/lib/eval-config-minimal.nix +++ b/nixos/lib/eval-config-minimal.nix @@ -1,10 +1,7 @@ -let - # The warning is in a top-level let binding so it is only printed once. - experimentalWarning = warn "lib.nixos.evalModules is experimental and subject to change. See nixos/lib/default.nix" null; - inherit (nonExtendedLib) warn; - nonExtendedLib = import ../../lib; -in -{ lib ? nonExtendedLib, bypassEvalModulesWarning ? false, ... }: + +# DO NOT IMPORT. Use nixpkgsFlake.lib.nixos, or import (nixpkgs + "/nixos/lib") +{ lib }: # read -^ + let /* @@ -43,5 +40,5 @@ let in { - evalModules = builtins.seq (if bypassEvalModulesWarning then null else experimentalWarning) evalModules; + inherit evalModules; } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index d7971f6c2eb..0dee1a0e8b0 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -20,7 +20,11 @@ let if elem system systems then handleTest path args else {}; - nixosLib = import ../lib {}; + nixosLib = import ../lib { + # Experimental features need testing too, but there's no point in warning + # about it, so we enable the feature flag. + featureFlags.minimalModules = {}; + }; evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; }; in { -- cgit 1.4.1 From 77b442226d3aaf45a13a8ebbf2567eb759f4db63 Mon Sep 17 00:00:00 2001 From: Yurii Matsiuk Date: Mon, 10 Jan 2022 13:46:47 +0100 Subject: nixos/tests/teleport: init --- nixos/tests/all-tests.nix | 1 + nixos/tests/teleport.nix | 99 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 nixos/tests/teleport.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 4f62980e8e9..5ebe07ad3cb 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -471,6 +471,7 @@ in systemd-unit-path = handleTest ./systemd-unit-path.nix {}; taskserver = handleTest ./taskserver.nix {}; telegraf = handleTest ./telegraf.nix {}; + teleport = handleTest ./teleport.nix {}; tiddlywiki = handleTest ./tiddlywiki.nix {}; tigervnc = handleTest ./tigervnc.nix {}; timezone = handleTest ./timezone.nix {}; diff --git a/nixos/tests/teleport.nix b/nixos/tests/teleport.nix new file mode 100644 index 00000000000..15b16e44409 --- /dev/null +++ b/nixos/tests/teleport.nix @@ -0,0 +1,99 @@ +{ system ? builtins.currentSystem +, config ? { } +, pkgs ? import ../.. { inherit system config; } +}: + +with import ../lib/testing-python.nix { inherit system pkgs; }; + +let + minimal = { config, ... }: { + services.teleport.enable = true; + }; + + client = { config, ... }: { + services.teleport = { + enable = true; + settings = { + teleport = { + nodename = "client"; + advertise_ip = "192.168.1.20"; + auth_token = "8d1957b2-2ded-40e6-8297-d48156a898a9"; + auth_servers = [ "192.168.1.10:3025" ]; + log.severity = "DEBUG"; + }; + ssh_service = { + enabled = true; + labels = { + role = "client"; + }; + }; + proxy_service.enabled = false; + auth_service.enabled = false; + }; + }; + networking.interfaces.eth1.ipv4.addresses = [{ + address = "192.168.1.20"; + prefixLength = 24; + }]; + }; + + server = { config, ... }: { + services.teleport = { + enable = true; + settings = { + teleport = { + nodename = "server"; + advertise_ip = "192.168.1.10"; + }; + ssh_service.enabled = true; + proxy_service.enabled = true; + auth_service = { + enabled = true; + tokens = [ "node:8d1957b2-2ded-40e6-8297-d48156a898a9" ]; + }; + }; + diag.enable = true; + insecure.enable = true; + }; + networking = { + firewall.allowedTCPPorts = [ 3025 ]; + interfaces.eth1.ipv4.addresses = [{ + address = "192.168.1.10"; + prefixLength = 24; + }]; + }; + }; +in +{ + minimal = makeTest { + # minimal setup should always work + name = "teleport-minimal-setup"; + meta.maintainers = with pkgs.lib.maintainers; [ ymatsiuk ]; + nodes = { inherit minimal; }; + + testScript = '' + minimal.wait_for_open_port("3025") + minimal.wait_for_open_port("3080") + minimal.wait_for_open_port("3022") + ''; + }; + + basic = makeTest { + # basic server and client test + name = "teleport-server-client"; + meta.maintainers = with pkgs.lib.maintainers; [ ymatsiuk ]; + nodes = { inherit server client; }; + + testScript = '' + with subtest("teleport ready"): + server.wait_for_open_port("3025") + client.wait_for_open_port("3022") + + with subtest("check applied configuration"): + server.wait_until_succeeds("tctl get nodes --format=json | ${pkgs.jq}/bin/jq -e '.[] | select(.spec.hostname==\"client\") | .metadata.labels.role==\"client\"'") + server.wait_for_open_port("3000") + client.succeed("journalctl -u teleport.service --grep='DEBU'") + server.succeed("journalctl -u teleport.service --grep='Starting teleport in insecure mode.'") + ''; + }; +} -- cgit 1.4.1 From f366ae6429096615914dd24a3ae59d7215b34180 Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Fri, 24 Dec 2021 11:24:29 +0000 Subject: nixos/starship: add a test --- nixos/tests/all-tests.nix | 1 + nixos/tests/starship.nix | 31 +++++++++++++++++++++++++++++++ pkgs/tools/misc/starship/default.nix | 5 +++++ 3 files changed, 37 insertions(+) create mode 100644 nixos/tests/starship.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 5ebe07ad3cb..1282d62272e 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -445,6 +445,7 @@ in sslh = handleTest ./sslh.nix {}; sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {}; sssd-ldap = handleTestOn ["x86_64-linux"] ./sssd-ldap.nix {}; + starship = handleTest ./starship.nix {}; step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {}; strongswan-swanctl = handleTest ./strongswan-swanctl.nix {}; sudo = handleTest ./sudo.nix {}; diff --git a/nixos/tests/starship.nix b/nixos/tests/starship.nix new file mode 100644 index 00000000000..f21da1e6e25 --- /dev/null +++ b/nixos/tests/starship.nix @@ -0,0 +1,31 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "starship"; + meta.maintainers = pkgs.starship.meta.maintainers; + + machine = { + programs = { + fish.enable = true; + zsh.enable = true; + + starship = { + enable = true; + settings.format = ""; + }; + }; + + services.getty.autologinUser = "root"; + }; + + testScript = '' + start_all() + machine.wait_for_unit("default.target") + + for shell in ["bash", "fish", "zsh"]: + machine.send_chars(f"script -c {shell} /tmp/{shell}.txt\n") + machine.wait_until_tty_matches(1, f"Script started.*{shell}.txt") + machine.send_chars("exit\n") + machine.wait_until_tty_matches(1, "Script done") + machine.sleep(1) + machine.succeed(f"grep -q '' /tmp/{shell}.txt") + ''; +}) diff --git a/pkgs/tools/misc/starship/default.nix b/pkgs/tools/misc/starship/default.nix index 25a6aaecd02..165ed45d7b7 100644 --- a/pkgs/tools/misc/starship/default.nix +++ b/pkgs/tools/misc/starship/default.nix @@ -6,6 +6,7 @@ , openssl , installShellFiles , libiconv +, nixosTests , Security }: @@ -40,6 +41,10 @@ rustPlatform.buildRustPackage rec { HOME=$TMPDIR ''; + passthru.tests = { + inherit (nixosTests) starship; + }; + meta = with lib; { description = "A minimal, blazing fast, and extremely customizable prompt for any shell"; homepage = "https://starship.rs"; -- cgit 1.4.1 From 9917a5cf11ffc2e5dec598f7fe4eda1869c39095 Mon Sep 17 00:00:00 2001 From: zowoq <59103226+zowoq@users.noreply.github.com> Date: Thu, 13 Jan 2022 09:14:26 +1000 Subject: nixos/tests/systemd-networkd-vrf: move disabled check inline --- .editorconfig | 3 --- nixos/tests/systemd-networkd-vrf.nix | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'nixos/tests') diff --git a/.editorconfig b/.editorconfig index 7d0157a6c42..b780def5d11 100644 --- a/.editorconfig +++ b/.editorconfig @@ -61,9 +61,6 @@ trim_trailing_whitespace = unset [nixos/modules/services/networking/ircd-hybrid/*.{conf,in}] trim_trailing_whitespace = unset -[nixos/tests/systemd-networkd-vrf.nix] -trim_trailing_whitespace = unset - [pkgs/build-support/dotnetenv/Wrapper/**] end_of_line = unset indent_style = unset diff --git a/nixos/tests/systemd-networkd-vrf.nix b/nixos/tests/systemd-networkd-vrf.nix index 9f09d801f77..8a1580fc2ad 100644 --- a/nixos/tests/systemd-networkd-vrf.nix +++ b/nixos/tests/systemd-networkd-vrf.nix @@ -161,6 +161,7 @@ in { # NOTE: please keep in mind that the trailing whitespaces in the following strings # are intentional as the output is compared against the raw `iproute2`-output. + # editorconfig-checker-disable client_ipv4_table = """ 192.168.1.2 dev vrf1 proto static metric 100 192.168.2.3 dev vrf2 proto static metric 100 @@ -177,6 +178,7 @@ in { local 192.168.2.1 dev eth2 proto kernel scope host src 192.168.2.1 broadcast 192.168.2.255 dev eth2 proto kernel scope link src 192.168.2.1 """.strip() + # editorconfig-checker-enable # Check that networkd properly configures the main routing table # and the routing tables for the VRF. -- cgit 1.4.1 From 7dc24c092332a6313309926028656915d676e10e Mon Sep 17 00:00:00 2001 From: Daniel Thwaites Date: Wed, 12 Jan 2022 19:43:52 +0000 Subject: nixos/starship: use expect for testing Accidentally reverted this while fixing merge conflicts on #149423. --- nixos/tests/starship.nix | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/starship.nix b/nixos/tests/starship.nix index f21da1e6e25..33e9a72f700 100644 --- a/nixos/tests/starship.nix +++ b/nixos/tests/starship.nix @@ -13,19 +13,30 @@ import ./make-test-python.nix ({ pkgs, ... }: { }; }; - services.getty.autologinUser = "root"; + environment.systemPackages = map + (shell: pkgs.writeScriptBin "expect-${shell}" '' + #!${pkgs.expect}/bin/expect -f + + spawn env TERM=xterm ${shell} -i + + expect "" { + send "exit\n" + } timeout { + send_user "\n${shell} failed to display Starship\n" + exit 1 + } + + expect eof + '') + [ "bash" "fish" "zsh" ]; }; testScript = '' start_all() machine.wait_for_unit("default.target") - for shell in ["bash", "fish", "zsh"]: - machine.send_chars(f"script -c {shell} /tmp/{shell}.txt\n") - machine.wait_until_tty_matches(1, f"Script started.*{shell}.txt") - machine.send_chars("exit\n") - machine.wait_until_tty_matches(1, "Script done") - machine.sleep(1) - machine.succeed(f"grep -q '' /tmp/{shell}.txt") + machine.succeed("expect-bash") + machine.succeed("expect-fish") + machine.succeed("expect-zsh") ''; }) -- cgit 1.4.1 From 4369bebd9a32658ded22b580886587cdc577a29d Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Fri, 14 Jan 2022 22:25:01 +0100 Subject: nixos/tests: remove broken prosody-mysql test The test has been broken for some time and the test errors are non-obvious. None of the current maintainers know how to fix it so it is better to get rid of it then to keep a continously failing test. --- nixos/tests/all-tests.nix | 1 - nixos/tests/xmpp/prosody-mysql.nix | 92 -------------------------------------- 2 files changed, 93 deletions(-) delete mode 100644 nixos/tests/xmpp/prosody-mysql.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 95ce2cc5ccf..18363edf8bf 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -397,7 +397,6 @@ in prometheus = handleTest ./prometheus.nix {}; prometheus-exporters = handleTest ./prometheus-exporters.nix {}; prosody = handleTest ./xmpp/prosody.nix {}; - prosodyMysql = handleTest ./xmpp/prosody-mysql.nix {}; proxy = handleTest ./proxy.nix {}; prowlarr = handleTest ./prowlarr.nix {}; pt2-clone = handleTest ./pt2-clone.nix {}; diff --git a/nixos/tests/xmpp/prosody-mysql.nix b/nixos/tests/xmpp/prosody-mysql.nix deleted file mode 100644 index 9a00bcabf38..00000000000 --- a/nixos/tests/xmpp/prosody-mysql.nix +++ /dev/null @@ -1,92 +0,0 @@ -import ../make-test-python.nix { - name = "prosody-mysql"; - - nodes = { - client = { nodes, pkgs, ... }: { - environment.systemPackages = [ - (pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = nodes.server.config.networking.primaryIPAddress; }) - ]; - networking.extraHosts = '' - ${nodes.server.config.networking.primaryIPAddress} example.com - ${nodes.server.config.networking.primaryIPAddress} conference.example.com - ${nodes.server.config.networking.primaryIPAddress} uploads.example.com - ''; - }; - server = { config, pkgs, ... }: { - nixpkgs.overlays = [ - (self: super: { - prosody = super.prosody.override { - withDBI = true; - withExtraLibs = [ pkgs.luaPackages.luadbi-mysql ]; - }; - }) - ]; - networking.extraHosts = '' - ${config.networking.primaryIPAddress} example.com - ${config.networking.primaryIPAddress} conference.example.com - ${config.networking.primaryIPAddress} uploads.example.com - ''; - networking.firewall.enable = false; - services.prosody = { - enable = true; - # TODO: use a self-signed certificate - c2sRequireEncryption = false; - extraConfig = '' - storage = "sql" - sql = { - driver = "MySQL"; - database = "prosody"; - host = "mysql"; - port = 3306; - username = "prosody"; - password = "password123"; - }; - ''; - virtualHosts.test = { - domain = "example.com"; - enabled = true; - }; - muc = [ - { - domain = "conference.example.com"; - } - ]; - uploadHttp = { - domain = "uploads.example.com"; - }; - }; - }; - mysql = { config, pkgs, ... }: { - networking.firewall.enable = false; - services.mysql = { - enable = true; - initialScript = pkgs.writeText "mysql_init.sql" '' - CREATE DATABASE prosody; - CREATE USER 'prosody'@'server' IDENTIFIED BY 'password123'; - GRANT ALL PRIVILEGES ON prosody.* TO 'prosody'@'server'; - FLUSH PRIVILEGES; - ''; - package = pkgs.mariadb; - }; - }; - }; - - testScript = { nodes, ... }: '' - mysql.wait_for_unit("mysql.service") - server.wait_for_unit("prosody.service") - server.succeed('prosodyctl status | grep "Prosody is running"') - - # set password to 'nothunter2' (it's asked twice) - server.succeed("yes nothunter2 | prosodyctl adduser cthon98@example.com") - # set password to 'y' - server.succeed("yes | prosodyctl adduser azurediamond@example.com") - # correct password to 'hunter2' - server.succeed("yes hunter2 | prosodyctl passwd azurediamond@example.com") - - client.succeed("send-message") - - server.succeed("prosodyctl deluser cthon98@example.com") - server.succeed("prosodyctl deluser azurediamond@example.com") - ''; -} - -- cgit 1.4.1 From 6b55249a5d0490fb4c00a8f0db677869ac643311 Mon Sep 17 00:00:00 2001 From: lassulus Date: Wed, 12 Jan 2022 21:47:27 +0100 Subject: nixos/tests/ergochat: init --- nixos/tests/all-tests.nix | 1 + nixos/tests/ergochat.nix | 97 +++++++++++++++++++++++++++++++++++ pkgs/servers/irc/ergochat/default.nix | 4 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 nixos/tests/ergochat.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 1282d62272e..21e52a3da37 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -125,6 +125,7 @@ in enlightenment = handleTest ./enlightenment.nix {}; env = handleTest ./env.nix {}; ergo = handleTest ./ergo.nix {}; + ergochat = handleTest ./ergochat.nix {}; etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {}; etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {}; etebase-server = handleTest ./etebase-server.nix {}; diff --git a/nixos/tests/ergochat.nix b/nixos/tests/ergochat.nix new file mode 100644 index 00000000000..2e9dc55e648 --- /dev/null +++ b/nixos/tests/ergochat.nix @@ -0,0 +1,97 @@ +let + clients = [ + "ircclient1" + "ircclient2" + ]; + server = "ergochat"; + ircPort = 6667; + channel = "nixos-cat"; + iiDir = "/tmp/irc"; +in + +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "ergochat"; + nodes = { + "${server}" = { + networking.firewall.allowedTCPPorts = [ ircPort ]; + services.ergochat = { + enable = true; + settings.server.motd = pkgs.writeText "ergo.motd" '' + The default MOTD doesn't contain the word "nixos" in it. + This one does. + ''; + }; + }; + } // lib.listToAttrs (builtins.map (client: lib.nameValuePair client { + imports = [ + ./common/user-account.nix + ]; + + systemd.services.ii = { + requires = [ "network.target" ]; + wantedBy = [ "default.target" ]; + + serviceConfig = { + Type = "simple"; + ExecPreStartPre = "mkdir -p ${iiDir}"; + ExecStart = '' + ${lib.getBin pkgs.ii}/bin/ii -n ${client} -s ${server} -i ${iiDir} + ''; + User = "alice"; + }; + }; + }) clients); + + testScript = + let + msg = client: "Hello, my name is ${client}"; + clientScript = client: [ + '' + ${client}.wait_for_unit("network.target") + ${client}.systemctl("start ii") + ${client}.wait_for_unit("ii") + ${client}.wait_for_file("${iiDir}/${server}/out") + '' + # look for the custom text in the MOTD. + '' + ${client}.wait_until_succeeds("grep 'nixos' ${iiDir}/${server}/out") + '' + # wait until first PING from server arrives before joining, + # so we don't try it too early + '' + ${client}.wait_until_succeeds("grep 'PING' ${iiDir}/${server}/out") + '' + # join ${channel} + '' + ${client}.succeed("echo '/j #${channel}' > ${iiDir}/${server}/in") + ${client}.wait_for_file("${iiDir}/${server}/#${channel}/in") + '' + # send a greeting + '' + ${client}.succeed( + "echo '${msg client}' > ${iiDir}/${server}/#${channel}/in" + ) + '' + # check that all greetings arrived on all clients + ] ++ builtins.map (other: '' + ${client}.succeed( + "grep '${msg other}$' ${iiDir}/${server}/#${channel}/out" + ) + '') clients; + + # foldl', but requires a non-empty list instead of a start value + reduce = f: list: + builtins.foldl' f (builtins.head list) (builtins.tail list); + in '' + start_all() + ${server}.systemctl("status ergochat") + ${server}.wait_for_open_port(${toString ircPort}) + + # run clientScript for all clients so that every list + # entry is executed by every client before advancing + # to the next one. + '' + lib.concatStrings + (reduce + (lib.zipListsWith (cs: c: cs + c)) + (builtins.map clientScript clients)); +}) diff --git a/pkgs/servers/irc/ergochat/default.nix b/pkgs/servers/irc/ergochat/default.nix index 03042df9a31..1aa5f5158aa 100644 --- a/pkgs/servers/irc/ergochat/default.nix +++ b/pkgs/servers/irc/ergochat/default.nix @@ -1,4 +1,4 @@ -{ buildGo117Module, fetchFromGitHub, lib }: +{ buildGo117Module, fetchFromGitHub, lib, nixosTests }: buildGo117Module rec { pname = "ergo"; @@ -13,6 +13,8 @@ buildGo117Module rec { vendorSha256 = null; + passthru.tests.ergochat = nixosTests.ergochat; + meta = { changelog = "https://github.com/ergochat/ergo/blob/v${version}/CHANGELOG.md"; description = "A modern IRC server (daemon/ircd) written in Go"; -- cgit 1.4.1 From cf12e0e7ed02b91b202e68d15f045b6982f22d71 Mon Sep 17 00:00:00 2001 From: Winter Date: Mon, 10 Jan 2022 21:29:04 -0500 Subject: nixos/thelounge: add test --- nixos/tests/all-tests.nix | 1 + nixos/tests/thelounge.nix | 29 +++++++++++++++++++++++++++++ pkgs/development/node-packages/default.nix | 3 ++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 nixos/tests/thelounge.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 5ebe07ad3cb..014542486f4 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -472,6 +472,7 @@ in taskserver = handleTest ./taskserver.nix {}; telegraf = handleTest ./telegraf.nix {}; teleport = handleTest ./teleport.nix {}; + thelounge = handleTest ./thelounge.nix {}; tiddlywiki = handleTest ./tiddlywiki.nix {}; tigervnc = handleTest ./tigervnc.nix {}; timezone = handleTest ./timezone.nix {}; diff --git a/nixos/tests/thelounge.nix b/nixos/tests/thelounge.nix new file mode 100644 index 00000000000..e9b85685bf2 --- /dev/null +++ b/nixos/tests/thelounge.nix @@ -0,0 +1,29 @@ +import ./make-test-python.nix { + nodes = { + private = { config, pkgs, ... }: { + services.thelounge = { + enable = true; + plugins = [ pkgs.theLoungePlugins.themes.solarized ]; + }; + }; + + public = { config, pkgs, ... }: { + services.thelounge = { + enable = true; + public = true; + }; + }; + }; + + testScript = '' + start_all() + + for machine in machines: + machine.wait_for_unit("thelounge.service") + machine.wait_for_open_port(9000) + + private.wait_until_succeeds("journalctl -u thelounge.service | grep thelounge-theme-solarized") + private.wait_until_succeeds("journalctl -u thelounge.service | grep 'in private mode'") + public.wait_until_succeeds("journalctl -u thelounge.service | grep 'in public mode'") + ''; +} diff --git a/pkgs/development/node-packages/default.nix b/pkgs/development/node-packages/default.nix index 2e7c6fc38cc..82ad390ff60 100644 --- a/pkgs/development/node-packages/default.nix +++ b/pkgs/development/node-packages/default.nix @@ -1,4 +1,4 @@ -{ pkgs, nodejs, stdenv, applyPatches, fetchFromGitHub, fetchpatch, fetchurl }: +{ pkgs, nodejs, stdenv, applyPatches, fetchFromGitHub, fetchpatch, fetchurl, nixosTests }: let inherit (pkgs) lib; @@ -442,6 +442,7 @@ let echo /var/lib/thelounge > $out/lib/node_modules/thelounge/.thelounge_home patch -d $out/lib/node_modules/thelounge -p1 < ${./thelounge-packages-path.patch} ''; + passthru.tests = { inherit (nixosTests) thelounge; }; }; yaml-language-server = super.yaml-language-server.override { -- cgit 1.4.1 From 0fe01530034762c50c5b379e3852a8cb53901b0d Mon Sep 17 00:00:00 2001 From: Justin Bedo Date: Thu, 23 Dec 2021 11:43:58 +1100 Subject: nixos/rstudio-server: init --- nixos/modules/misc/ids.nix | 2 + nixos/modules/module-list.nix | 1 + .../development/rstudio-server/default.nix | 107 +++++++++++++++++++++ nixos/tests/rstudio-server.nix | 30 ++++++ 4 files changed, 140 insertions(+) create mode 100644 nixos/modules/services/development/rstudio-server/default.nix create mode 100644 nixos/tests/rstudio-server.nix (limited to 'nixos/tests') diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index cad3ad01857..9d620084308 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -353,6 +353,7 @@ in distcc = 321; webdav = 322; pipewire = 323; + rstudio-server = 324; # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399! @@ -660,6 +661,7 @@ in distcc = 321; webdav = 322; pipewire = 323; + rstudio-server = 324; # When adding a gid, make sure it doesn't match an existing # uid. Users and groups with the same name should have equal diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 697ed4fad72..c931f6b32f5 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -394,6 +394,7 @@ ./services/development/hoogle.nix ./services/development/jupyter/default.nix ./services/development/jupyterhub/default.nix + ./services/development/rstudio-server/default.nix ./services/development/lorri.nix ./services/display-managers/greetd.nix ./services/editors/emacs.nix diff --git a/nixos/modules/services/development/rstudio-server/default.nix b/nixos/modules/services/development/rstudio-server/default.nix new file mode 100644 index 00000000000..cd903c7e55b --- /dev/null +++ b/nixos/modules/services/development/rstudio-server/default.nix @@ -0,0 +1,107 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.rstudio-server; + + rserver-conf = builtins.toFile "rserver.conf" '' + server-working-dir=${cfg.serverWorkingDir} + www-address=${cfg.listenAddr} + ${cfg.rserverExtraConfig} + ''; + + rsession-conf = builtins.toFile "rsession.conf" '' + ${cfg.rsessionExtraConfig} + ''; + +in +{ + meta.maintainers = with maintainers; [ jbedo cfhammill ]; + + options.services.rstudio-server = { + enable = mkEnableOption "RStudio server"; + + serverWorkingDir = mkOption { + type = types.str; + default = "/var/lib/rstudio-server"; + description = '' + Default working directory for server (server-working-dir in rserver.conf). + ''; + }; + + listenAddr = mkOption { + type = types.str; + default = "127.0.0.1"; + description = '' + Address to listen on (www-address in rserver.conf). + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.rstudio-server; + defaultText = literalExpression "pkgs.rstudio-server"; + example = literalExpression "pkgs.rstudioServerWrapper.override { packages = [ pkgs.rPackages.ggplot2 ]; }"; + description = '' + Rstudio server package to use. Can be set to rstudioServerWrapper to provide packages. + ''; + }; + + rserverExtraConfig = mkOption { + type = types.str; + default = ""; + description = '' + Extra contents for rserver.conf. + ''; + }; + + rsessionExtraConfig = mkOption { + type = types.str; + default = ""; + description = '' + Extra contents for resssion.conf. + ''; + }; + + }; + + config = mkIf cfg.enable + { + systemd.services.rstudio-server = { + description = "Rstudio server"; + + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ rserver-conf rsession-conf ]; + + serviceConfig = { + Restart = "on-failure"; + Type = "forking"; + ExecStart = "${cfg.package}/bin/rserver"; + StateDirectory = "rstudio-server"; + RuntimeDirectory = "rstudio-server"; + }; + }; + + environment.etc = { + "rstudio/rserver.conf".source = rserver-conf; + "rstudio/rsession.conf".source = rsession-conf; + "pam.d/rstudio".source = "/etc/pam.d/login"; + }; + environment.systemPackages = [ cfg.package ]; + + users = { + users.rstudio-server = { + uid = config.ids.uids.rstudio-server; + description = "rstudio-server"; + group = "rstudio-server"; + }; + groups.rstudio-server = { + gid = config.ids.gids.rstudio-server; + }; + }; + + }; +} diff --git a/nixos/tests/rstudio-server.nix b/nixos/tests/rstudio-server.nix new file mode 100644 index 00000000000..c7ac7670fbd --- /dev/null +++ b/nixos/tests/rstudio-server.nix @@ -0,0 +1,30 @@ +import ./make-test-python.nix ({ pkgs, ... }: + { + name = "rstudio-server-test"; + meta.maintainers = with pkgs.lib.maintainers; [ jbedo cfhammill ]; + + nodes.machine = { config, lib, pkgs, ... }: { + services.rstudio-server.enable = true; + }; + + nodes.customPackageMachine = { config, lib, pkgs, ... }: { + services.rstudio-server = { + enable = true; + package = pkgs.rstudioServerWrapper.override { packages = [ pkgs.rPackages.ggplot2 ]; }; + }; + }; + + users.testuser = { + uid = 1000; + group = "testgroup"; + }; + groups.testgroup.gid = 1000; + + testScript = '' + machine.wait_for_unit("rstudio-server.service") + machine.succeed("curl -f -vvv -s http://127.0.0.1:8787") + + customPackageMachine.wait_for_unit("rstudio-server.service") + customPackageMachine.succeed("curl -f -vvv -s http://127.0.0.1:8787") + ''; + }) -- cgit 1.4.1 From f9bc03e3c7726af2d397eb697ffe878aae503860 Mon Sep 17 00:00:00 2001 From: CRTified Date: Mon, 17 Jan 2022 01:39:27 +0100 Subject: nixos/adguardhome: add test --- nixos/tests/adguardhome.nix | 57 ++++++++++++++++++++++++++++++++++++ nixos/tests/all-tests.nix | 1 + pkgs/servers/adguardhome/default.nix | 3 +- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 nixos/tests/adguardhome.nix (limited to 'nixos/tests') diff --git a/nixos/tests/adguardhome.nix b/nixos/tests/adguardhome.nix new file mode 100644 index 00000000000..ddbe8ff9c11 --- /dev/null +++ b/nixos/tests/adguardhome.nix @@ -0,0 +1,57 @@ +import ./make-test-python.nix { + name = "adguardhome"; + + nodes = { + minimalConf = { ... }: { + services.adguardhome = { enable = true; }; + }; + + declarativeConf = { ... }: { + services.adguardhome = { + enable = true; + + mutableSettings = false; + settings = { + dns = { + bind_host = "0.0.0.0"; + bootstrap_dns = "127.0.0.1"; + }; + }; + }; + }; + + mixedConf = { ... }: { + services.adguardhome = { + enable = true; + + mutableSettings = true; + settings = { + dns = { + bind_host = "0.0.0.0"; + bootstrap_dns = "127.0.0.1"; + }; + }; + }; + }; + }; + + testScript = '' + with subtest("Minimal config test"): + minimalConf.wait_for_unit("adguardhome.service") + minimalConf.wait_for_open_port(3000) + + with subtest("Declarative config test, DNS will be reachable"): + declarativeConf.wait_for_unit("adguardhome.service") + declarativeConf.wait_for_open_port(53) + declarativeConf.wait_for_open_port(3000) + + with subtest("Mixed config test, check whether merging works"): + mixedConf.wait_for_unit("adguardhome.service") + mixedConf.wait_for_open_port(53) + mixedConf.wait_for_open_port(3000) + # Test whether merging works properly, even if nothing is changed + mixedConf.systemctl("restart adguardhome.service") + mixedConf.wait_for_unit("adguardhome.service") + mixedConf.wait_for_open_port(3000) + ''; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 5ebe07ad3cb..a403a2fce49 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -23,6 +23,7 @@ in { _3proxy = handleTest ./3proxy.nix {}; acme = handleTest ./acme.nix {}; + adguardhome = handleTest ./adguardhome.nix {}; aesmd = handleTest ./aesmd.nix {}; agda = handleTest ./agda.nix {}; airsonic = handleTest ./airsonic.nix {}; diff --git a/pkgs/servers/adguardhome/default.nix b/pkgs/servers/adguardhome/default.nix index 9940decc0bc..dcf397ed4b2 100644 --- a/pkgs/servers/adguardhome/default.nix +++ b/pkgs/servers/adguardhome/default.nix @@ -1,4 +1,4 @@ -{ lib, stdenv, fetchurl, fetchzip }: +{ lib, stdenv, fetchurl, fetchzip, nixosTests }: stdenv.mkDerivation rec { pname = "adguardhome"; @@ -12,6 +12,7 @@ stdenv.mkDerivation rec { passthru = { updateScript = ./update.sh; + tests.adguardhome = nixosTests.adguardhome; }; meta = with lib; { -- cgit 1.4.1 From f6dca95c5dc8ab63322e439ae33a148877838ba9 Mon Sep 17 00:00:00 2001 From: Yarny0 <41838844+Yarny0@users.noreply.github.com> Date: Sat, 18 Dec 2021 09:21:57 +0100 Subject: tsm-client: add test derivation and a module test The tsm-client needs a tsm-server to do anything useful. Without a server, automated tests can just check diagnostic outputs for plausibility. The commit at hand adds two tests: 1. The command line interface `dsmc` is called, then it is verified that the program does * report the correct client version, * find its configuration file, * report a connection error. 2. To check the GUI (and the tsm-client nixos module), we add a vm test which uses the module to install `tsm-client-withGui`. To verify that the GUI's basic functionality is present, we skip over all connection failure related error messages and open the "Connection Information" dialog from the main application window. This dialog presents the node name and the client version; both are verified by the test. Note: Our `tsm-client` build recipe consists of two packages: The "unwrapped" package and the final package. This commit puts the unwrapped one into the final package's `passthru` so that tests can access the original version string that is needed to check the client version reported by the application. --- nixos/tests/all-tests.nix | 1 + nixos/tests/tsm-client-gui.nix | 57 ++++++++++++++++++++++++++++++ pkgs/tools/backup/tsm-client/default.nix | 11 ++++-- pkgs/tools/backup/tsm-client/test-cli.nix | 58 +++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 nixos/tests/tsm-client-gui.nix create mode 100644 pkgs/tools/backup/tsm-client/test-cli.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 95ce2cc5ccf..ef98567a006 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -487,6 +487,7 @@ in trezord = handleTest ./trezord.nix {}; trickster = handleTest ./trickster.nix {}; trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {}; + tsm-client-gui = handleTest ./tsm-client-gui.nix {}; txredisapi = handleTest ./txredisapi.nix {}; tuptime = handleTest ./tuptime.nix {}; turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {}; diff --git a/nixos/tests/tsm-client-gui.nix b/nixos/tests/tsm-client-gui.nix new file mode 100644 index 00000000000..e4bcd344a89 --- /dev/null +++ b/nixos/tests/tsm-client-gui.nix @@ -0,0 +1,57 @@ +# The tsm-client GUI first tries to connect to a server. +# We can't simulate a server, so we just check if +# it reports the correct connection failure error. +# After that the test persuades the GUI +# to show its main application window +# and verifies some configuration information. + +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "tsm-client"; + + enableOCR = true; + + machine = { pkgs, ... }: { + imports = [ ./common/x11.nix ]; + programs.tsmClient = { + enable = true; + package = pkgs.tsm-client-withGui; + defaultServername = "testserver"; + servers.testserver = { + # 192.0.0.8 is a "dummy address" according to RFC 7600 + server = "192.0.0.8"; + node = "SOME-NODE"; + passwdDir = "/tmp"; + }; + }; + }; + + testScript = '' + machine.succeed("which dsmj") # fail early if this is missing + machine.wait_for_x() + machine.execute("DSM_LOG=/tmp dsmj -optfile=/dev/null >&2 &") + + # does it report the "TCP/IP connection failure" error code? + machine.wait_for_window("IBM Spectrum Protect") + machine.wait_for_text("ANS2610S") + machine.send_key("esc") + + # it asks to continue to restore a local backupset now; + # "yes" (return) leads to the main application window + machine.wait_for_text("backupset") + machine.send_key("ret") + + # main window: navigate to "Connection Information" + machine.wait_for_text("Welcome") + machine.send_key("alt-f") # "File" menu + machine.send_key("c") # "Connection Information" + + # "Connection Information" dialog box + machine.wait_for_window("Connection Information") + machine.wait_for_text("SOME-NODE") + machine.wait_for_text("${pkgs.tsm-client.passthru.unwrapped.version}") + + machine.shutdown() + ''; + + meta.maintainers = [ lib.maintainers.yarny ]; +}) diff --git a/pkgs/tools/backup/tsm-client/default.nix b/pkgs/tools/backup/tsm-client/default.nix index ad9b8315aed..6303d76a1da 100644 --- a/pkgs/tools/backup/tsm-client/default.nix +++ b/pkgs/tools/backup/tsm-client/default.nix @@ -1,4 +1,6 @@ { lib +, callPackage +, nixosTests , stdenv , fetchurl , autoPatchelfHook @@ -81,6 +83,11 @@ let ''; }; + passthru.tests = { + test-cli = callPackage ./test-cli.nix {}; + test-gui = nixosTests.tsm-client-gui; + }; + mkSrcUrl = version: let major = lib.versions.major version; @@ -96,7 +103,7 @@ let url = mkSrcUrl version; sha256 = "0fy9c224g6rkrgd6ls01vs30bk9j9mlhf2x6akd11r7h8bib19zn"; }; - inherit meta; + inherit meta passthru; nativeBuildInputs = [ autoPatchelfHook @@ -152,7 +159,7 @@ buildEnv { meta = meta // lib.attrsets.optionalAttrs enableGui { mainProgram = "dsmj"; }; - passthru = { inherit unwrapped; }; + passthru = passthru // { inherit unwrapped; }; paths = [ unwrapped ]; nativeBuildInputs = [ makeWrapper ]; pathsToLink = [ diff --git a/pkgs/tools/backup/tsm-client/test-cli.nix b/pkgs/tools/backup/tsm-client/test-cli.nix new file mode 100644 index 00000000000..0858083c9f9 --- /dev/null +++ b/pkgs/tools/backup/tsm-client/test-cli.nix @@ -0,0 +1,58 @@ +{ lib +, writeText +, runCommand +, tsm-client +}: + +# Let the client try to connect to a server. +# We can't simulate a server, so there's no more to test. + +let + + # 192.0.0.8 is a "dummy address" according to RFC 7600 + dsmSysCli = writeText "cli.dsm.sys" '' + defaultserver testserver + server testserver + commmethod v6tcpip + tcpserveraddress 192.0.0.8 + nodename ARBITRARYNODENAME + ''; + + tsm-client_ = tsm-client.override { inherit dsmSysCli; }; + + env.nativeBuildInputs = [ tsm-client_ ]; + + versionString = + let + inherit (tsm-client_.passthru.unwrapped) version; + major = lib.versions.major version; + minor = lib.versions.minor version; + patch = lib.versions.patch version; + fixup = lib.lists.elemAt (lib.versions.splitVersion version) 3; + in + "Client Version ${major}, Release ${minor}, Level ${patch}.${fixup}"; + +in + +runCommand "${tsm-client.name}-test-cli" env '' + set -o nounset + set -o pipefail + + export DSM_LOG=$(mktemp -d ./dsm_log.XXXXXXXXXXX) + + { dsmc -optfile=/dev/null || true; } | tee dsmc-stdout + + # does it report the correct version? + grep --fixed-strings '${versionString}' dsmc-stdout + + # does it use the provided dsm.sys config file? + # if it does, it states the node's name + grep ARBITRARYNODENAME dsmc-stdout + + # does it try (and fail) to connect to the server? + # if it does, it reports the "TCP/IP connection failure" error code + grep ANS1017E dsmc-stdout + grep ANS1017E $DSM_LOG/dsmerror.log + + touch $out +'' -- cgit 1.4.1 From 2cf157c781ef0b711ee70157f3fb11d9a95877fb Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Wed, 15 Dec 2021 15:16:58 +0000 Subject: nixos/switch-to-configuration: Rework activation script restarts This removes `/run/nixos/activation-reload-list` (which we will need in the future when reworking the reload logic) and makes `/run/nixos/activation-restart-list` honor `restartIfChanged` and `reloadIfChanged`. This way activation scripts don't have to bother with choosing between reloading and restarting. --- .../from_md/release-notes/rl-2205.section.xml | 10 +++ nixos/doc/manual/release-notes/rl-2205.section.md | 2 + .../system/activation/switch-to-configuration.pl | 72 ++++++++++++++++------ nixos/tests/switch-test.nix | 70 +++++++++++++++++++++ 4 files changed, 136 insertions(+), 18 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index f8fbe6ccd2f..698073c6dbc 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -377,6 +377,16 @@ include serif fonts.
+ + + The interface that allows activation scripts to restart units + has been reworked. Restarting and reloading is now done by a + single file + /run/nixos/activation-restart-list that + honors restartIfChanged and + reloadIfChanged of the units. + +
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index ef0069ab7f3..d290d720398 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -119,6 +119,8 @@ In addition to numerous new and upgraded packages, this release has the followin `pkgs.noto-fonts-cjk` is currently an alias of `pkgs.noto-fonts-cjk-sans` and doesn't include serif fonts. +- The interface that allows activation scripts to restart units has been reworked. Restarting and reloading is now done by a single file `/run/nixos/activation-restart-list` that honors `restartIfChanged` and `reloadIfChanged` of the units. + ## Other Notable Changes {#sec-release-22.05-notable-changes} - The option [services.redis.servers](#opt-services.redis.servers) was added diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 3fbab8b94c9..93fff889d6b 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -18,11 +18,13 @@ my $startListFile = "/run/nixos/start-list"; my $restartListFile = "/run/nixos/restart-list"; my $reloadListFile = "/run/nixos/reload-list"; -# Parse restart/reload requests by the activation script +# Parse restart/reload requests by the activation script. +# Activation scripts may write newline-separated units to this +# file and switch-to-configuration will handle them. While +# `stopIfChanged = true` is ignored, switch-to-configuration will +# handle `restartIfChanged = false` and `reloadIfChanged = true`. my $restartByActivationFile = "/run/nixos/activation-restart-list"; -my $reloadByActivationFile = "/run/nixos/activation-reload-list"; my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list"; -my $dryReloadByActivationFile = "/run/nixos/dry-activation-reload-list"; make_path("/run/nixos", { mode => oct(755) }); @@ -382,7 +384,6 @@ sub filterUnits { } my @unitsToStopFiltered = filterUnits(\%unitsToStop); -my @unitsToStartFiltered = filterUnits(\%unitsToStart); # Show dry-run actions. @@ -395,21 +396,39 @@ if ($action eq "dry-activate") { print STDERR "would activate the configuration...\n"; system("$out/dry-activate", "$out"); - $unitsToRestart{$_} = 1 foreach - split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // ""); + # Handle the activation script requesting the restart or reload of a unit. + foreach (split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // "")) { + my $unit = $_; + my $baseUnit = $unit; + my $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + + # Detect template instances. + if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) { + $baseUnit = "$1\@.$2"; + $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + } + + my $baseName = $baseUnit; + $baseName =~ s/\.[a-z]*$//; - $unitsToReload{$_} = 1 foreach - split('\n', read_file($dryReloadByActivationFile, err_mode => 'quiet') // ""); + # Start units if they were not active previously + if (not defined $activePrev->{$unit}) { + $unitsToStart{$unit} = 1; + next; + } + + handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); + } + unlink($dryRestartByActivationFile); print STDERR "would restart systemd\n" if $restartSystemd; print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n" if scalar(keys %unitsToReload) > 0; print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n" if scalar(keys %unitsToRestart) > 0; + my @unitsToStartFiltered = filterUnits(\%unitsToStart); print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n" if scalar @unitsToStartFiltered; - unlink($dryRestartByActivationFile); - unlink($dryReloadByActivationFile); exit 0; } @@ -433,13 +452,31 @@ print STDERR "activating the configuration...\n"; system("$out/activate", "$out") == 0 or $res = 2; # Handle the activation script requesting the restart or reload of a unit. -# We can only restart and reload (not stop/start) because the units to be -# stopped are already stopped before the activation script is run. -$unitsToRestart{$_} = 1 foreach - split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // ""); +foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // "")) { + my $unit = $_; + my $baseUnit = $unit; + my $newUnitFile = "$out/etc/systemd/system/$baseUnit"; -$unitsToReload{$_} = 1 foreach - split('\n', read_file($reloadByActivationFile, err_mode => 'quiet') // ""); + # Detect template instances. + if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) { + $baseUnit = "$1\@.$2"; + $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + } + + my $baseName = $baseUnit; + $baseName =~ s/\.[a-z]*$//; + + # Start units if they were not active previously + if (not defined $activePrev->{$unit}) { + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $unit); + next; + } + + handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); +} +# We can remove the file now because it has been propagated to the other restart/reload files +unlink($restartByActivationFile); # Restart systemd if necessary. Note that this is done using the # current version of systemd, just in case the new one has trouble @@ -480,7 +517,6 @@ if (scalar(keys %unitsToReload) > 0) { print STDERR "reloading the following units: ", join(", ", sort(keys %unitsToReload)), "\n"; system("@systemd@/bin/systemctl", "reload", "--", sort(keys %unitsToReload)) == 0 or $res = 4; unlink($reloadListFile); - unlink($reloadByActivationFile); } # Restart changed services (those that have to be restarted rather @@ -489,7 +525,6 @@ if (scalar(keys %unitsToRestart) > 0) { print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"; system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4; unlink($restartListFile); - unlink($restartByActivationFile); } # Start all active targets, as well as changed units we stopped above. @@ -498,6 +533,7 @@ if (scalar(keys %unitsToRestart) > 0) { # that are symlinks to other units. We shouldn't start both at the # same time because we'll get a "Failed to add path to set" error from # systemd. +my @unitsToStartFiltered = filterUnits(\%unitsToStart); print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n" if scalar @unitsToStartFiltered; system("@systemd@/bin/systemctl", "start", "--", sort(keys %unitsToStart)) == 0 or $res = 4; diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index daad9134885..1c32bf6beb9 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -45,6 +45,50 @@ import ./make-test-python.nix ({ pkgs, ...} : { systemd.services.test.restartIfChanged = false; }; + restart-and-reload-by-activation-script.configuration = { + systemd.services = rec { + simple-service = { + # No wantedBy so we can check if the activation script restart triggers them + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.coreutils}/bin/true"; + ExecReload = "${pkgs.coreutils}/bin/true"; + }; + }; + + simple-restart-service = simple-service // { + stopIfChanged = false; + }; + + simple-reload-service = simple-service // { + reloadIfChanged = true; + }; + + no-restart-service = simple-service // { + restartIfChanged = false; + }; + }; + + system.activationScripts.restart-and-reload-test = { + supportsDryActivation = true; + deps = []; + text = '' + if [ "$NIXOS_ACTION" = dry-activate ]; then + f=/run/nixos/dry-activation-restart-list + else + f=/run/nixos/activation-restart-list + fi + cat <> "$f" + simple-service.service + simple-restart-service.service + simple-reload-service.service + no-restart-service.service + EOF + ''; + }; + }; + mount.configuration = { systemd.mounts = [ { @@ -261,6 +305,32 @@ import ./make-test-python.nix ({ pkgs, ...} : { assert_lacks(out, "as well:") assert_contains(out, "would start the following units: test.service\n") + with subtest("restart and reload by activation script"): + out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script") + assert_contains(out, "stopping the following units: test.service\n") + assert_lacks(out, "NOT restarting the following changed units:") + assert_lacks(out, "reloading the following units:") + assert_lacks(out, "restarting the following units:") + assert_contains(out, "\nstarting the following units: no-restart-service.service, simple-reload-service.service, simple-restart-service.service, simple-service.service\n") + assert_lacks(out, "as well:") + # Switch to the same system where the example services get restarted + # by the activation script + out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script") + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: simple-reload-service.service\n") + assert_contains(out, "restarting the following units: simple-restart-service.service, simple-service.service\n") + assert_lacks(out, "\nstarting the following units:") + assert_lacks(out, "as well:") + # The same, but in dry mode + out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script", action="dry-activate") + assert_lacks(out, "would stop the following units:") + assert_lacks(out, "would NOT stop the following changed units:") + assert_contains(out, "would reload the following units: simple-reload-service.service\n") + assert_contains(out, "would restart the following units: simple-restart-service.service, simple-service.service\n") + assert_lacks(out, "\nwould start the following units:") + assert_lacks(out, "as well:") + with subtest("mounts"): switch_to_specialisation("${machine}", "mount") out = machine.succeed("mount | grep 'on /testmount'") -- cgit 1.4.1 From 11b2191b74c0a8b6bf30fc1136033321a2ea1b96 Mon Sep 17 00:00:00 2001 From: Daniel Frank Date: Sat, 15 Jan 2022 13:51:28 +0100 Subject: openssh: Update tests to use new option name --- nixos/tests/borgbackup.nix | 2 +- nixos/tests/btrbk.nix | 2 +- nixos/tests/google-oslogin/server.nix | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/borgbackup.nix b/nixos/tests/borgbackup.nix index cbb28689209..d3cd6c66bfe 100644 --- a/nixos/tests/borgbackup.nix +++ b/nixos/tests/borgbackup.nix @@ -106,7 +106,7 @@ in { services.openssh = { enable = true; passwordAuthentication = false; - challengeResponseAuthentication = false; + kbdInteractiveAuthentication = false; }; services.borgbackup.repos.repo1 = { diff --git a/nixos/tests/btrbk.nix b/nixos/tests/btrbk.nix index 2689bb66c63..9f34f7dfbe3 100644 --- a/nixos/tests/btrbk.nix +++ b/nixos/tests/btrbk.nix @@ -53,7 +53,7 @@ import ./make-test-python.nix ({ pkgs, ... }: services.openssh = { enable = true; passwordAuthentication = false; - challengeResponseAuthentication = false; + kbdInteractiveAuthentication = false; }; services.btrbk = { extraPackages = [ pkgs.lz4 ]; diff --git a/nixos/tests/google-oslogin/server.nix b/nixos/tests/google-oslogin/server.nix index fdb7141da31..a0a3144ae69 100644 --- a/nixos/tests/google-oslogin/server.nix +++ b/nixos/tests/google-oslogin/server.nix @@ -17,7 +17,7 @@ in { }; services.openssh.enable = true; - services.openssh.challengeResponseAuthentication = false; + services.openssh.kbdInteractiveAuthentication = false; services.openssh.passwordAuthentication = false; security.googleOsLogin.enable = true; -- cgit 1.4.1 From 909536115dab69e141d8ae03de41564d30824ada Mon Sep 17 00:00:00 2001 From: Lara Date: Tue, 18 Jan 2022 19:08:32 +0000 Subject: nixos/nextcloud: Fix nixos test on master Resolves #155509 --- nixos/tests/nextcloud/with-mysql-and-memcached.nix | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix index 80cb63df5db..891001e30b2 100644 --- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix +++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix @@ -40,15 +40,16 @@ in { services.mysql = { enable = true; - bind = "127.0.0.1"; + settings.mysqld = { + bind-address = "127.0.0.1"; + + # FIXME(@Ma27) Nextcloud isn't compatible with mariadb 10.6, + # this is a workaround. + # See https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/22 + innodb_read_only_compressed = 0; + }; package = pkgs.mariadb; - # FIXME(@Ma27) Nextcloud isn't compatible with mariadb 10.6, - # this is a workaround. - # See https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/22 - extraOptions = '' - innodb_read_only_compressed=0 - ''; initialScript = pkgs.writeText "mysql-init" '' CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'hunter2'; CREATE DATABASE IF NOT EXISTS nextcloud; -- cgit 1.4.1 From 612ad7776a4d84cc2b1a966afb7cc93dc5d11f92 Mon Sep 17 00:00:00 2001 From: Johannes Schleifenbaum Date: Tue, 18 Jan 2022 08:59:57 +0100 Subject: nixos/dnsdist: add test --- nixos/tests/all-tests.nix | 1 + nixos/tests/dnsdist.nix | 48 ++++++++++++++++++++++++++++++++++++ pkgs/servers/dns/dnsdist/default.nix | 6 ++++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 nixos/tests/dnsdist.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 2bc34d6fd78..5fc390bcc2a 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -104,6 +104,7 @@ in discourse = handleTest ./discourse.nix {}; dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {}; dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {}; + dnsdist = handleTest ./dnsdist.nix {}; doas = handleTest ./doas.nix {}; docker = handleTestOn ["x86_64-linux"] ./docker.nix {}; docker-rootless = handleTestOn ["x86_64-linux"] ./docker-rootless.nix {}; diff --git a/nixos/tests/dnsdist.nix b/nixos/tests/dnsdist.nix new file mode 100644 index 00000000000..cfc41c13864 --- /dev/null +++ b/nixos/tests/dnsdist.nix @@ -0,0 +1,48 @@ +import ./make-test-python.nix ( + { pkgs, ... }: { + name = "dnsdist"; + meta = with pkgs.lib; { + maintainers = with maintainers; [ jojosch ]; + }; + + machine = { pkgs, lib, ... }: { + services.bind = { + enable = true; + extraOptions = "empty-zones-enable no;"; + zones = lib.singleton { + name = "."; + master = true; + file = pkgs.writeText "root.zone" '' + $TTL 3600 + . IN SOA ns.example.org. admin.example.org. ( 1 3h 1h 1w 1d ) + . IN NS ns.example.org. + + ns.example.org. IN A 192.168.0.1 + ns.example.org. IN AAAA abcd::1 + + 1.0.168.192.in-addr.arpa IN PTR ns.example.org. + ''; + }; + }; + services.dnsdist = { + enable = true; + listenPort = 5353; + extraConfig = '' + newServer({address="127.0.0.1:53", name="local-bind"}) + ''; + }; + + environment.systemPackages = with pkgs; [ dig ]; + }; + + testScript = '' + machine.wait_for_unit("bind.service") + machine.wait_for_open_port(53) + machine.succeed("dig @127.0.0.1 +short -x 192.168.0.1 | grep -qF ns.example.org") + + machine.wait_for_unit("dnsdist.service") + machine.wait_for_open_port(5353) + machine.succeed("dig @127.0.0.1 -p 5353 +short -x 192.168.0.1 | grep -qF ns.example.org") + ''; + } +) diff --git a/pkgs/servers/dns/dnsdist/default.nix b/pkgs/servers/dns/dnsdist/default.nix index a104ec4d9b1..f78a9d7b92c 100644 --- a/pkgs/servers/dns/dnsdist/default.nix +++ b/pkgs/servers/dns/dnsdist/default.nix @@ -1,7 +1,7 @@ { lib, stdenv, fetchurl, pkg-config, systemd , boost, libsodium, libedit, re2 , net-snmp, lua, protobuf, openssl, zlib, h2o -, nghttp2 +, nghttp2, nixosTests }: stdenv.mkDerivation rec { @@ -39,6 +39,10 @@ stdenv.mkDerivation rec { enableParallelBuilding = true; + passthru.tests = { + inherit (nixosTests) dnsdist; + }; + meta = with lib; { description = "DNS Loadbalancer"; homepage = "https://dnsdist.org"; -- cgit 1.4.1 From 54a62ae266343b584b92d1f2b636a913899c86a8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 19 Jan 2022 11:22:23 +0100 Subject: nixos/tests/lorri: Remove redundant stdout redirect Introduced accidentally in https://github.com/NixOS/nixpkgs/pull/144679 --- nixos/tests/lorri/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nixos/tests') diff --git a/nixos/tests/lorri/default.nix b/nixos/tests/lorri/default.nix index 147ae999fdb..c33c7503993 100644 --- a/nixos/tests/lorri/default.nix +++ b/nixos/tests/lorri/default.nix @@ -14,7 +14,7 @@ import ../make-test-python.nix { ) # Start the daemon and wait until it is ready - machine.execute("lorri daemon > lorri.stdout 2> lorri.stderr >&2 &") + machine.execute("lorri daemon > lorri.stdout 2> lorri.stderr &") machine.wait_until_succeeds("grep --fixed-strings 'ready' lorri.stdout") # Ping the daemon -- cgit 1.4.1 From 741a585052c99be0bf2633c6195be57a464b962e Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Wed, 19 Jan 2022 11:43:01 +0100 Subject: nixos/tests/libreswan: fixup 739c51ae4ef --- nixos/tests/libreswan.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/libreswan.nix b/nixos/tests/libreswan.nix index 56ab908aed9..ff3d2344a67 100644 --- a/nixos/tests/libreswan.nix +++ b/nixos/tests/libreswan.nix @@ -89,7 +89,7 @@ in """ Sends a message as Alice to Bob """ - bob.execute("nc -lu ::0 1234 >/tmp/msg >&2 &") + bob.execute("nc -lu ::0 1234 >/tmp/msg &") alice.sleep(1) alice.succeed(f"echo '{msg}' | nc -uw 0 bob 1234") bob.succeed(f"grep '{msg}' /tmp/msg") @@ -100,7 +100,7 @@ in Starts eavesdropping on Alice and Bob """ match = "src host alice and dst host bob" - eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log >&2 &") + eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log &") start_all() -- cgit 1.4.1 From 44af29e6f5d8ca7d27ff951a52615ae238c27ccb Mon Sep 17 00:00:00 2001 From: ivanbrennan Date: Thu, 20 Jan 2022 01:48:05 -0500 Subject: nixosTests.xmonad: test configured recompilation Add test coverage for the enableConfiguredRecompile option, checking that we can compile and exec a new xmonad from a user's local config, as well as restart the originally configured xmonad. As I needed a reliable way to wait for recompilation to finish before proceeding with subsequent test steps, I adjusted the startup behavior to write a file ("oldXMonad" or "newXMonad") to /etc upon startup, and replaced some "sleep" calls with "wait_for_file". --- nixos/tests/xmonad.nix | 100 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 15 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix index 078cd211810..a14d4b819eb 100644 --- a/nixos/tests/xmonad.nix +++ b/nixos/tests/xmonad.nix @@ -1,4 +1,55 @@ -import ./make-test-python.nix ({ pkgs, ...} : { +import ./make-test-python.nix ({ pkgs, ...}: + +let + mkConfig = name: keys: '' + import XMonad + import XMonad.Operations (restart) + import XMonad.Util.EZConfig + import XMonad.Util.SessionStart + import Control.Monad (when) + import Text.Printf (printf) + import System.Posix.Process (executeFile) + import System.Info (arch,os) + import System.Environment (getArgs) + import System.FilePath (()) + + main = launch $ def { startupHook = startup } `additionalKeysP` myKeys + + startup = isSessionStart >>= \sessInit -> + spawn "touch /tmp/${name}" + >> if sessInit then setSessionStarted else spawn "xterm" + + myKeys = [${builtins.concatStringsSep ", " keys}] + + compiledConfig = printf "xmonad-%s-%s" arch os + + compileRestart resume = + whenX (recompile True) $ + when resume writeStateToFile + *> catchIO + ( do + dir <- getXMonadDataDir + args <- getArgs + executeFile (dir compiledConfig) False args Nothing + ) + ''; + + oldKeys = + [ ''("M-C-x", spawn "xterm")'' + ''("M-q", restart "xmonad" True)'' + ''("M-C-q", compileRestart True)'' + ''("M-C-t", spawn "touch /tmp/somefile")'' # create somefile + ]; + + newKeys = + [ ''("M-C-x", spawn "xterm")'' + ''("M-q", restart "xmonad" True)'' + ''("M-C-q", compileRestart True)'' + ''("M-C-r", spawn "rm /tmp/somefile")'' # delete somefile + ]; + + newConfig = pkgs.writeText "xmonad.hs" (mkConfig "newXMonad" newKeys); +in { name = "xmonad"; meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; @@ -10,21 +61,10 @@ import ./make-test-python.nix ({ pkgs, ...} : { services.xserver.displayManager.defaultSession = "none+xmonad"; services.xserver.windowManager.xmonad = { enable = true; + enableConfiguredRecompile = true; enableContribAndExtras = true; extraPackages = with pkgs.haskellPackages; haskellPackages: [ xmobar ]; - config = '' - import XMonad - import XMonad.Operations (restart) - import XMonad.Util.EZConfig - import XMonad.Util.SessionStart - - main = launch $ def { startupHook = startup } `additionalKeysP` myKeys - - startup = isSessionStart >>= \sessInit -> - if sessInit then setSessionStarted else spawn "xterm" - - myKeys = [ ("M-C-x", spawn "xterm"), ("M-q", restart "xmonad" True) ] - ''; + config = mkConfig "oldXMonad" oldKeys; }; }; @@ -38,10 +78,40 @@ import ./make-test-python.nix ({ pkgs, ...} : { machine.wait_for_window("${user.name}.*machine") machine.sleep(1) machine.screenshot("terminal1") + machine.succeed("rm /tmp/oldXMonad") machine.send_key("alt-q") - machine.sleep(3) + machine.wait_for_file("/tmp/oldXMonad") machine.wait_for_window("${user.name}.*machine") machine.sleep(1) machine.screenshot("terminal2") + + # /tmp/somefile should not exist yet + machine.fail("stat /tmp/somefile") + + # original config has a keybinding that creates somefile + machine.send_key("alt-ctrl-t") + machine.sleep(1) + machine.succeed("stat /tmp/somefile") + + # set up the new config + machine.succeed("mkdir -p ${user.home}/.xmonad") + machine.copy_from_host("${newConfig}", "${user.home}/.xmonad/xmonad.hs") + + # recompile xmonad using the new config + machine.send_key("alt-ctrl-q") + machine.wait_for_file("/tmp/newXMonad") + + # new config has a keybinding that deletes somefile + machine.send_key("alt-ctrl-r") + machine.sleep(1) + machine.fail("stat /tmp/somefile") + + # restart with the old config, and confirm the old keybinding is back + machine.succeed("rm /tmp/oldXMonad") + machine.send_key("alt-q") + machine.wait_for_file("/tmp/oldXMonad") + machine.send_key("alt-ctrl-t") + machine.sleep(1) + machine.succeed("stat /tmp/somefile") ''; }) -- cgit 1.4.1 From e869dc0ce06f8835d1d407aa8ad2f201f80c1ab8 Mon Sep 17 00:00:00 2001 From: Naïm Favier Date: Thu, 20 Jan 2022 13:04:47 +0100 Subject: Revert "nixosTests.gnome: add autologin delay to catch GDM failures" --- nixos/tests/gnome.nix | 2 -- 1 file changed, 2 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/gnome.nix b/nixos/tests/gnome.nix index 4471126902a..06f387ecad6 100644 --- a/nixos/tests/gnome.nix +++ b/nixos/tests/gnome.nix @@ -18,8 +18,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : { enable = true; user = "alice"; }; - # Catch GDM failures that don't happen with AutomaticLoginEnable, e.g. https://github.com/NixOS/nixpkgs/issues/149539 - gdm.autoLogin.delay = 1; }; services.xserver.desktopManager.gnome.enable = true; -- cgit 1.4.1 From 80475b46f52a1e28a70b1866c2a8edb071208ac8 Mon Sep 17 00:00:00 2001 From: Jonas Heinrich Date: Thu, 20 Jan 2022 14:45:35 +0100 Subject: nixos/invoiceplane: init module and package at 1.5.11 (#146909) --- .../from_md/release-notes/rl-2205.section.xml | 8 + nixos/doc/manual/release-notes/rl-2205.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/web-apps/invoiceplane.nix | 305 +++++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/invoiceplane.nix | 82 ++++++ pkgs/servers/web-apps/invoiceplane/default.nix | 32 +++ pkgs/top-level/all-packages.nix | 2 + 8 files changed, 433 insertions(+) create mode 100644 nixos/modules/services/web-apps/invoiceplane.nix create mode 100644 nixos/tests/invoiceplane.nix create mode 100644 pkgs/servers/web-apps/invoiceplane/default.nix (limited to 'nixos/tests') diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index ce45b0d7977..2875ea683f8 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -108,6 +108,14 @@ services.powerdns-admin. + + + InvoicePlane, + web application for managing and creating invoices. Available + at + services.invoiceplane. + + maddy, a diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index 25b3ada2c56..a59513adfc9 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -35,6 +35,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [PowerDNS-Admin](https://github.com/ngoduykhanh/PowerDNS-Admin), a web interface for the PowerDNS server. Available at [services.powerdns-admin](options.html#opt-services.powerdns-admin.enable). +- [InvoicePlane](https://invoiceplane.com), web application for managing and creating invoices. Available at [services.invoiceplane](options.html#opt-services.invoiceplane.enable). + - [maddy](https://maddy.email), a composable all-in-one mail server. Available as [services.maddy](options.html#opt-services.maddy.enable). - [mtr-exporter](https://github.com/mgumz/mtr-exporter), a Prometheus exporter for mtr metrics. Available as [services.mtr-exporter](options.html#opt-services.mtr-exporter.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index fdf93f2e17c..4b2cb803e20 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1022,6 +1022,7 @@ ./services/web-apps/keycloak.nix ./services/web-apps/lemmy.nix ./services/web-apps/invidious.nix + ./services/web-apps/invoiceplane.nix ./services/web-apps/limesurvey.nix ./services/web-apps/mastodon.nix ./services/web-apps/mattermost.nix diff --git a/nixos/modules/services/web-apps/invoiceplane.nix b/nixos/modules/services/web-apps/invoiceplane.nix new file mode 100644 index 00000000000..095eec36dec --- /dev/null +++ b/nixos/modules/services/web-apps/invoiceplane.nix @@ -0,0 +1,305 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.invoiceplane; + eachSite = cfg.sites; + user = "invoiceplane"; + webserver = config.services.${cfg.webserver}; + + invoiceplane-config = hostName: cfg: pkgs.writeText "ipconfig.php" '' + IP_URL=http://${hostName} + ENABLE_DEBUG=false + DISABLE_SETUP=false + REMOVE_INDEXPHP=false + DB_HOSTNAME=${cfg.database.host} + DB_USERNAME=${cfg.database.user} + # NOTE: file_get_contents adds newline at the end of returned string + DB_PASSWORD=${if cfg.database.passwordFile == null then "" else "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")"} + DB_DATABASE=${cfg.database.name} + DB_PORT=${toString cfg.database.port} + SESS_EXPIRATION=864000 + ENABLE_INVOICE_DELETION=false + DISABLE_READ_ONLY=false + ENCRYPTION_KEY= + ENCRYPTION_CIPHER=AES-256 + SETUP_COMPLETED=false + ''; + + extraConfig = hostName: cfg: pkgs.writeText "extraConfig.php" '' + ${toString cfg.extraConfig} + ''; + + pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec { + pname = "invoiceplane-${hostName}"; + version = src.version; + src = pkgs.invoiceplane; + + patchPhase = '' + # Patch index.php file to load additional config file + substituteInPlace index.php \ + --replace "require('vendor/autoload.php');" "require('vendor/autoload.php'); \$dotenv = new \Dotenv\Dotenv(__DIR__, 'extraConfig.php'); \$dotenv->load();"; + ''; + + installPhase = '' + mkdir -p $out + cp -r * $out/ + + # symlink uploads and log directories + rm -r $out/uploads $out/application/logs $out/vendor/mpdf/mpdf/tmp + ln -sf ${cfg.stateDir}/uploads $out/ + ln -sf ${cfg.stateDir}/logs $out/application/ + ln -sf ${cfg.stateDir}/tmp $out/vendor/mpdf/mpdf/ + + # symlink the InvoicePlane config + ln -s ${cfg.stateDir}/ipconfig.php $out/ipconfig.php + + # symlink the extraConfig file + ln -s ${extraConfig hostName cfg} $out/extraConfig.php + + # symlink additional templates + ${concatMapStringsSep "\n" (template: "cp -r ${template}/. $out/application/views/invoice_templates/pdf/") cfg.invoiceTemplates} + ''; + }; + + siteOpts = { lib, name, ... }: + { + options = { + + enable = mkEnableOption "InvoicePlane web application"; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/invoiceplane/${name}"; + description = '' + This directory is used for uploads of attachements and cache. + The directory passed here is automatically created and permissions + adjusted as required. + ''; + }; + + database = { + host = mkOption { + type = types.str; + default = "localhost"; + description = "Database host address."; + }; + + port = mkOption { + type = types.port; + default = 3306; + description = "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "invoiceplane"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "invoiceplane"; + description = "Database user."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/invoiceplane-dbpassword"; + description = '' + A file containing the password corresponding to + . + ''; + }; + + createLocally = mkOption { + type = types.bool; + default = true; + description = "Create the database and database user locally."; + }; + }; + + invoiceTemplates = mkOption { + type = types.listOf types.path; + default = []; + description = '' + List of path(s) to respective template(s) which are copied from the 'invoice_templates/pdf' directory. + These templates need to be packaged before use, see example. + ''; + example = literalExpression '' + let + # Let's package an example template + template-vtdirektmarketing = pkgs.stdenv.mkDerivation { + name = "vtdirektmarketing"; + # Download the template from a public repository + src = pkgs.fetchgit { + url = "https://git.project-insanity.org/onny/invoiceplane-vtdirektmarketing.git"; + sha256 = "1hh0q7wzsh8v8x03i82p6qrgbxr4v5fb05xylyrpp975l8axyg2z"; + }; + sourceRoot = "."; + # Installing simply means copying template php file to the output directory + installPhase = "" + mkdir -p $out + cp invoiceplane-vtdirektmarketing/vtdirektmarketing.php $out/ + ""; + }; + # And then pass this package to the template list like this: + in [ template-vtdirektmarketing ] + ''; + }; + + poolConfig = mkOption { + type = with types; attrsOf (oneOf [ str int bool ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for the InvoicePlane PHP pool. See the documentation on php-fpm.conf + for details on configuration directives. + ''; + }; + + extraConfig = mkOption { + type = types.nullOr types.lines; + default = null; + example = '' + SETUP_COMPLETED=true + DISABLE_SETUP=true + IP_URL=https://invoice.example.com + ''; + description = '' + InvoicePlane configuration. Refer to + + for details on supported values. + ''; + }; + + }; + + }; +in +{ + # interface + options = { + services.invoiceplane = mkOption { + type = types.submodule { + + options.sites = mkOption { + type = types.attrsOf (types.submodule siteOpts); + default = {}; + description = "Specification of one or more WordPress sites to serve"; + }; + + options.webserver = mkOption { + type = types.enum [ "caddy" ]; + default = "caddy"; + description = '' + Which webserver to use for virtual host management. Currently only + caddy is supported. + ''; + }; + }; + default = {}; + description = "InvoicePlane configuration."; + }; + + }; + + # implementation + config = mkIf (eachSite != {}) (mkMerge [{ + + assertions = flatten (mapAttrsToList (hostName: cfg: + [{ assertion = cfg.database.createLocally -> cfg.database.user == user; + message = ''services.invoiceplane.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned''; + } + { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = ''services.invoiceplane.sites."${hostName}".database.passwordFile cannot be specified if services.invoiceplane.sites."${hostName}".database.createLocally is set to true.''; + }] + ) eachSite); + + services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite; + ensureUsers = mapAttrsToList (hostName: cfg: + { name = cfg.database.user; + ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + } + ) eachSite; + }; + + services.phpfpm = { + phpPackage = pkgs.php74; + pools = mapAttrs' (hostName: cfg: ( + nameValuePair "invoiceplane-${hostName}" { + inherit user; + group = webserver.group; + settings = { + "listen.owner" = webserver.user; + "listen.group" = webserver.group; + } // cfg.poolConfig; + } + )) eachSite; + }; + + } + + { + systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [ + "d ${cfg.stateDir} 0750 ${user} ${webserver.group} - -" + "f ${cfg.stateDir}/ipconfig.php 0750 ${user} ${webserver.group} - -" + "d ${cfg.stateDir}/logs 0750 ${user} ${webserver.group} - -" + "d ${cfg.stateDir}/uploads 0750 ${user} ${webserver.group} - -" + "d ${cfg.stateDir}/uploads/archive 0750 ${user} ${webserver.group} - -" + "d ${cfg.stateDir}/uploads/customer_files 0750 ${user} ${webserver.group} - -" + "d ${cfg.stateDir}/uploads/temp 0750 ${user} ${webserver.group} - -" + "d ${cfg.stateDir}/uploads/temp/mpdf 0750 ${user} ${webserver.group} - -" + "d ${cfg.stateDir}/tmp 0750 ${user} ${webserver.group} - -" + ]) eachSite); + + systemd.services.invoiceplane-config = { + serviceConfig.Type = "oneshot"; + script = concatStrings (mapAttrsToList (hostName: cfg: + '' + mkdir -p ${cfg.stateDir}/logs \ + ${cfg.stateDir}/uploads + if ! grep -q IP_URL "${cfg.stateDir}/ipconfig.php"; then + cp "${invoiceplane-config hostName cfg}" "${cfg.stateDir}/ipconfig.php" + fi + '') eachSite); + wantedBy = [ "multi-user.target" ]; + }; + + users.users.${user} = { + group = webserver.group; + isSystemUser = true; + }; + } + + (mkIf (cfg.webserver == "caddy") { + services.caddy = { + enable = true; + virtualHosts = mapAttrs' (hostName: cfg: ( + nameValuePair "http://${hostName}" { + extraConfig = '' + root * ${pkg hostName cfg} + file_server + + php_fastcgi unix/${config.services.phpfpm.pools."invoiceplane-${hostName}".socket} + ''; + } + )) eachSite; + }; + }) + + + ]); +} + diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 6e66e9cbe96..940ae11ddd1 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -215,6 +215,7 @@ in initrd-secrets = handleTest ./initrd-secrets.nix {}; inspircd = handleTest ./inspircd.nix {}; installer = handleTest ./installer.nix {}; + invoiceplane = handleTest ./invoiceplane.nix {}; iodine = handleTest ./iodine.nix {}; ipfs = handleTest ./ipfs.nix {}; ipv6 = handleTest ./ipv6.nix {}; diff --git a/nixos/tests/invoiceplane.nix b/nixos/tests/invoiceplane.nix new file mode 100644 index 00000000000..4e63f8ac21c --- /dev/null +++ b/nixos/tests/invoiceplane.nix @@ -0,0 +1,82 @@ +import ./make-test-python.nix ({ pkgs, ... }: + +{ + name = "invoiceplane"; + meta = with pkgs.lib.maintainers; { + maintainers = [ + onny + ]; + }; + + nodes = { + invoiceplane_caddy = { ... }: { + services.invoiceplane.webserver = "caddy"; + services.invoiceplane.sites = { + "site1.local" = { + #database.name = "invoiceplane1"; + database.createLocally = true; + enable = true; + }; + "site2.local" = { + #database.name = "invoiceplane2"; + database.createLocally = true; + enable = true; + }; + }; + + networking.firewall.allowedTCPPorts = [ 80 ]; + networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ]; + }; + }; + + testScript = '' + start_all() + + invoiceplane_caddy.wait_for_unit("caddy") + invoiceplane_caddy.wait_for_open_port(80) + invoiceplane_caddy.wait_for_open_port(3306) + + site_names = ["site1.local", "site2.local"] + + for site_name in site_names: + machine.wait_for_unit(f"phpfpm-invoiceplane-{site_name}") + + with subtest("Website returns welcome screen"): + assert "Please install InvoicePlane" in machine.succeed(f"curl -L {site_name}") + + with subtest("Finish InvoicePlane setup"): + machine.succeed( + f"curl -sSfL --cookie-jar cjar {site_name}/index.php/setup/language" + ) + csrf_token = machine.succeed( + "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'" + ) + machine.succeed( + f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&ip_lang=english&btn_continue=Continue' {site_name}/index.php/setup/language" + ) + csrf_token = machine.succeed( + "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'" + ) + machine.succeed( + f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/index.php/setup/prerequisites" + ) + csrf_token = machine.succeed( + "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'" + ) + machine.succeed( + f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/index.php/setup/configure_database" + ) + csrf_token = machine.succeed( + "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'" + ) + machine.succeed( + f"curl -sSfl --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/index.php/setup/install_tables" + ) + csrf_token = machine.succeed( + "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'" + ) + machine.succeed( + f"curl -sSfl --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/index.php/setup/upgrade_tables" + ) + ''; +}) diff --git a/pkgs/servers/web-apps/invoiceplane/default.nix b/pkgs/servers/web-apps/invoiceplane/default.nix new file mode 100644 index 00000000000..6c9ffd44b9d --- /dev/null +++ b/pkgs/servers/web-apps/invoiceplane/default.nix @@ -0,0 +1,32 @@ +{ lib, stdenv, fetchurl, writeText, unzip, nixosTests }: + +stdenv.mkDerivation rec { + pname = "invoiceplane"; + version = "1.5.11"; + + src = fetchurl { + url = "https://github.com/InvoicePlane/InvoicePlane/releases/download/v${version}/v${version}.zip"; + sha256 = "137g0xps4kb3j7f5gz84ql18iggbya6d9dnrfp05g2qcbbp8kqad"; + }; + + nativeBuildInputs = [ unzip ]; + + sourceRoot = "."; + + installPhase = '' + mkdir -p $out/ + cp -r . $out/ + ''; + + passthru.tests = { + inherit (nixosTests) invoiceplane; + }; + + meta = with lib; { + description = "Self-hosted open source application for managing your invoices, clients and payments"; + license = licenses.mit; + homepage = "https://www.invoiceplane.com"; + platforms = platforms.all; + maintainers = with maintainers; [ onny ]; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index de442328506..0244986ffb5 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -3240,6 +3240,8 @@ with pkgs; interlock = callPackage ../servers/interlock {}; + invoiceplane = callPackage ../servers/web-apps/invoiceplane { }; + iotools = callPackage ../tools/misc/iotools { }; jellyfin = callPackage ../servers/jellyfin { }; -- cgit 1.4.1 From 51ff3add1a464f69c560908b897f285bc83cd3dc Mon Sep 17 00:00:00 2001 From: ivanbrennan Date: Thu, 20 Jan 2022 09:58:05 -0500 Subject: nixosTests.xmonad: avoid sleep() Replace sleep() calls where possible, using wait_for_* methods. This should provide more robustness in cases where tests are running on a congested system. --- nixos/tests/xmonad.nix | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix index a14d4b819eb..eb2e0563d7c 100644 --- a/nixos/tests/xmonad.nix +++ b/nixos/tests/xmonad.nix @@ -90,8 +90,7 @@ in { # original config has a keybinding that creates somefile machine.send_key("alt-ctrl-t") - machine.sleep(1) - machine.succeed("stat /tmp/somefile") + machine.wait_for_file("/tmp/somefile") # set up the new config machine.succeed("mkdir -p ${user.home}/.xmonad") @@ -103,15 +102,13 @@ in { # new config has a keybinding that deletes somefile machine.send_key("alt-ctrl-r") - machine.sleep(1) - machine.fail("stat /tmp/somefile") + machine.wait_until_fails("stat /tmp/somefile", timeout=30) # restart with the old config, and confirm the old keybinding is back machine.succeed("rm /tmp/oldXMonad") machine.send_key("alt-q") machine.wait_for_file("/tmp/oldXMonad") machine.send_key("alt-ctrl-t") - machine.sleep(1) - machine.succeed("stat /tmp/somefile") + machine.wait_for_file("/tmp/somefile") ''; }) -- cgit 1.4.1 From 8a1d379992a58edc9453d10649601c6cc7fdad6c Mon Sep 17 00:00:00 2001 From: ivanbrennan Date: Thu, 20 Jan 2022 11:17:03 -0500 Subject: nixosTests.xmonad: add ivanbrennan to maintainers --- nixos/tests/xmonad.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nixos/tests') diff --git a/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix index eb2e0563d7c..a2fb38e53bd 100644 --- a/nixos/tests/xmonad.nix +++ b/nixos/tests/xmonad.nix @@ -52,7 +52,7 @@ let in { name = "xmonad"; meta = with pkgs.lib.maintainers; { - maintainers = [ nequissimus ]; + maintainers = [ nequissimus ivanbrennan ]; }; machine = { pkgs, ... }: { -- cgit 1.4.1 From ca58bd0a50f38de43b401df716806c0f83479a8e Mon Sep 17 00:00:00 2001 From: Luflosi Date: Thu, 4 Nov 2021 00:19:54 +0100 Subject: nixos/networkd: Add routes from interfaces to [Route] section of .network file Closes https://github.com/NixOS/nixpkgs/pull/93635. --- .../from_md/release-notes/rl-2205.section.xml | 12 +++++ nixos/doc/manual/release-notes/rl-2205.section.md | 2 + nixos/modules/tasks/network-interfaces-systemd.nix | 61 ++++++++++++++++++++++ nixos/modules/tasks/network-interfaces.nix | 13 +++++ nixos/tests/networking.nix | 10 +++- 5 files changed, 97 insertions(+), 1 deletion(-) (limited to 'nixos/tests') diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 2875ea683f8..03ef4e69ca4 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -361,6 +361,18 @@ unmaintained + + + The options + networking.interfaces.<name>.ipv4.routes + and + networking.interfaces.<name>.ipv6.routes + are no longer ignored when using networkd instead of the + default scripted network backend by setting + networking.useNetworkd to + true. + + MultiMC has been replaced with the fork PolyMC due to upstream diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index a59513adfc9..7dc8fc1488d 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -116,6 +116,8 @@ In addition to numerous new and upgraded packages, this release has the followin - `pkgs.docbookrx` was removed since it's unmaintained +- The options `networking.interfaces..ipv4.routes` and `networking.interfaces..ipv6.routes` are no longer ignored when using networkd instead of the default scripted network backend by setting `networking.useNetworkd` to `true`. + - MultiMC has been replaced with the fork PolyMC due to upstream developers being hostile to 3rd party package maintainers. PolyMC removes all MultiMC branding and is aimed at providing proper 3rd party packages like the one contained in Nixpkgs. This change affects the data folder where game instances and other save and configuration files are stored. Users with existing installations should rename `~/.local/share/multimc` to `~/.local/share/polymc`. The main config file's path has also moved from `~/.local/share/multimc/multimc.cfg` to `~/.local/share/polymc/polymc.cfg`. - `pkgs.noto-fonts-cjk` is now deprecated in favor of `pkgs.noto-fonts-cjk-sans` diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix index 58239ca5452..645ff161cdd 100644 --- a/nixos/modules/tasks/network-interfaces-systemd.nix +++ b/nixos/modules/tasks/network-interfaces-systemd.nix @@ -12,6 +12,10 @@ let i.ipv4.addresses ++ optionals cfg.enableIPv6 i.ipv6.addresses; + interfaceRoutes = i: + i.ipv4.routes + ++ optionals cfg.enableIPv6 i.ipv6.routes; + dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no"; slaves = @@ -94,6 +98,63 @@ in (if i.useDHCP != null then i.useDHCP else false)); address = forEach (interfaceIps i) (ip: "${ip.address}/${toString ip.prefixLength}"); + routes = forEach (interfaceRoutes i) + (route: { + # Most of these route options have not been tested. + # Please fix or report any mistakes you may find. + routeConfig = + optionalAttrs (route.prefixLength > 0) { + Destination = "${route.address}/${toString route.prefixLength}"; + } // + optionalAttrs (route.options ? fastopen_no_cookie) { + FastOpenNoCookie = route.options.fastopen_no_cookie; + } // + optionalAttrs (route.via != null) { + Gateway = route.via; + } // + optionalAttrs (route.options ? onlink) { + GatewayOnLink = true; + } // + optionalAttrs (route.options ? initrwnd) { + InitialAdvertisedReceiveWindow = route.options.initrwnd; + } // + optionalAttrs (route.options ? initcwnd) { + InitialCongestionWindow = route.options.initcwnd; + } // + optionalAttrs (route.options ? pref) { + IPv6Preference = route.options.pref; + } // + optionalAttrs (route.options ? mtu) { + MTUBytes = route.options.mtu; + } // + optionalAttrs (route.options ? metric) { + Metric = route.options.metric; + } // + optionalAttrs (route.options ? src) { + PreferredSource = route.options.src; + } // + optionalAttrs (route.options ? protocol) { + Protocol = route.options.protocol; + } // + optionalAttrs (route.options ? quickack) { + QuickAck = route.options.quickack; + } // + optionalAttrs (route.options ? scope) { + Scope = route.options.scope; + } // + optionalAttrs (route.options ? from) { + Source = route.options.from; + } // + optionalAttrs (route.options ? table) { + Table = route.options.table; + } // + optionalAttrs (route.options ? advmss) { + TCPAdvertisedMaximumSegmentSize = route.options.advmss; + } // + optionalAttrs (route.options ? ttl-propagate) { + TTLPropagate = route.options.ttl-propagate == "enabled"; + }; + }); networkConfig.IPv6PrivacyExtensions = "kernel"; linkConfig = optionalAttrs (i.macAddress != null) { MACAddress = i.macAddress; diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 854badb23f6..1dac405ac30 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -103,6 +103,11 @@ let description = '' Other route options. See the symbol OPTIONS in the ip-route(8) manual page for the details. + You may also specify metric, + src, protocol, + scope, from + and table, which are technically + not route options, in the sense used in the manual. ''; }; @@ -208,6 +213,14 @@ let type = with types; listOf (submodule (routeOpts 4)); description = '' List of extra IPv4 static routes that will be assigned to the interface. + If the route type is the default unicast, then the scope + is set differently depending on the value of : + the script-based backend sets it to link, while networkd sets + it to global. + If you want consistency between the two implementations, + set the scope of the route manually with + networking.interfaces.eth0.ipv4.routes = [{ options.scope = "global"; }] + for example. ''; }; diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix index f46a115a07d..2510937b5dc 100644 --- a/nixos/tests/networking.nix +++ b/nixos/tests/networking.nix @@ -740,6 +740,7 @@ let routes = { name = "routes"; machine = { + networking.useNetworkd = networkd; networking.useDHCP = false; networking.interfaces.eth0 = { ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ]; @@ -749,7 +750,13 @@ let { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; } ]; ipv4.routes = [ - { address = "10.0.0.0"; prefixLength = 16; options = { mtu = "1500"; }; } + { address = "10.0.0.0"; prefixLength = 16; options = { + mtu = "1500"; + # Explicitly set scope because iproute and systemd-networkd + # disagree on what the scope should be + # if the type is the default "unicast" + scope = "link"; + }; } { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; } ]; }; @@ -798,6 +805,7 @@ let ipv6Table, targetIPv6Table ) + '' + optionalString (!networkd) '' with subtest("test clean-up of the tables"): machine.succeed("systemctl stop network-addresses-eth0") ipv4Residue = machine.succeed("ip -4 route list dev eth0 | head -n-3").strip() -- cgit 1.4.1 From 38998112c1dc0fe64b5c1828337dd9fed3dcc006 Mon Sep 17 00:00:00 2001 From: ajs124 Date: Wed, 8 Dec 2021 09:48:19 +0000 Subject: nixos/tests/mysql: test multiple mariadb versions --- nixos/tests/mysql/mysql.nix | 340 +++++++++++++++++--------------------------- 1 file changed, 134 insertions(+), 206 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/mysql/mysql.nix b/nixos/tests/mysql/mysql.nix index 2ac2b34a18e..a5e42f85a7f 100644 --- a/nixos/tests/mysql/mysql.nix +++ b/nixos/tests/mysql/mysql.nix @@ -1,221 +1,149 @@ -import ./../make-test-python.nix ({ pkgs, ...}: - +{ + system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../../.. { inherit system config; }, + lib ? pkgs.lib +}: let - # Setup common users - users = { ... }: - { - users.groups.testusers = { }; + inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages mysqlPackages; - users.users.testuser = { - isSystemUser = true; - group = "testusers"; + makeTest = import ./../make-test-python.nix; + # Setup common users + makeMySQLTest = { + package, + name ? mkTestName package, + useSocketAuth ? true, + hasMroonga ? true, + hasRocksDB ? true + }: makeTest { + inherit name; + meta = with lib.maintainers; { + maintainers = [ ajs124 das_j ]; }; - users.users.testuser2 = { - isSystemUser = true; - group = "testusers"; - }; - }; + nodes = { + ${name} = + { pkgs, ... }: { -in + users = { + groups.testusers = { }; -{ - name = "mysql"; - meta = with pkgs.lib.maintainers; { - maintainers = [ eelco shlevy ]; - }; - - nodes = { - mysql57 = - { pkgs, ... }: + users.testuser = { + isSystemUser = true; + group = "testusers"; + }; - { - imports = [ users ]; - - services.mysql.enable = true; - services.mysql.initialDatabases = [ - { name = "testdb3"; schema = ./testdb.sql; } - ]; - # note that using pkgs.writeText here is generally not a good idea, - # as it will store the password in world-readable /nix/store ;) - services.mysql.initialScript = pkgs.writeText "mysql-init.sql" '' - CREATE USER 'testuser3'@'localhost' IDENTIFIED BY 'secure'; - GRANT ALL PRIVILEGES ON testdb3.* TO 'testuser3'@'localhost'; - ''; - services.mysql.ensureDatabases = [ "testdb" "testdb2" ]; - services.mysql.ensureUsers = [{ - name = "testuser"; - ensurePermissions = { - "testdb.*" = "ALL PRIVILEGES"; - }; - } { - name = "testuser2"; - ensurePermissions = { - "testdb2.*" = "ALL PRIVILEGES"; + users.testuser2 = { + isSystemUser = true; + group = "testusers"; + }; }; - }]; - services.mysql.package = pkgs.mysql57; - }; - mysql80 = - { pkgs, ... }: - - { - imports = [ users ]; - - services.mysql.enable = true; - services.mysql.initialDatabases = [ - { name = "testdb3"; schema = ./testdb.sql; } - ]; - # note that using pkgs.writeText here is generally not a good idea, - # as it will store the password in world-readable /nix/store ;) - services.mysql.initialScript = pkgs.writeText "mysql-init.sql" '' - CREATE USER 'testuser3'@'localhost' IDENTIFIED BY 'secure'; - GRANT ALL PRIVILEGES ON testdb3.* TO 'testuser3'@'localhost'; - ''; - services.mysql.ensureDatabases = [ "testdb" "testdb2" ]; - services.mysql.ensureUsers = [{ - name = "testuser"; - ensurePermissions = { - "testdb.*" = "ALL PRIVILEGES"; - }; - } { - name = "testuser2"; - ensurePermissions = { - "testdb2.*" = "ALL PRIVILEGES"; + services.mysql = { + enable = true; + initialDatabases = [ + { name = "testdb3"; schema = ./testdb.sql; } + ]; + # note that using pkgs.writeText here is generally not a good idea, + # as it will store the password in world-readable /nix/store ;) + initialScript = pkgs.writeText "mysql-init.sql" (if (!useSocketAuth) then '' + CREATE USER 'testuser3'@'localhost' IDENTIFIED BY 'secure'; + GRANT ALL PRIVILEGES ON testdb3.* TO 'testuser3'@'localhost'; + '' else '' + ALTER USER root@localhost IDENTIFIED WITH unix_socket; + DELETE FROM mysql.user WHERE password = ''' AND plugin = '''; + DELETE FROM mysql.user WHERE user = '''; + FLUSH PRIVILEGES; + ''); + + ensureDatabases = [ "testdb" "testdb2" ]; + ensureUsers = [{ + name = "testuser"; + ensurePermissions = { + "testdb.*" = "ALL PRIVILEGES"; + }; + } { + name = "testuser2"; + ensurePermissions = { + "testdb2.*" = "ALL PRIVILEGES"; + }; + }]; + package = package; + settings = { + mysqld = { + plugin-load-add = lib.optional hasMroonga "ha_mroonga.so" + ++ lib.optional hasRocksDB "ha_rocksdb.so"; + }; + }; }; - }]; - services.mysql.package = pkgs.mysql80; - }; - - mariadb = - { pkgs, ... }: - - { - imports = [ users ]; + }; - services.mysql.enable = true; - services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" '' - ALTER USER root@localhost IDENTIFIED WITH unix_socket; - DELETE FROM mysql.user WHERE password = ''' AND plugin = '''; - DELETE FROM mysql.user WHERE user = '''; - FLUSH PRIVILEGES; - ''; - services.mysql.ensureDatabases = [ "testdb" "testdb2" ]; - services.mysql.ensureUsers = [{ - name = "testuser"; - ensurePermissions = { - "testdb.*" = "ALL PRIVILEGES"; - }; - } { - name = "testuser2"; - ensurePermissions = { - "testdb2.*" = "ALL PRIVILEGES"; - }; - }]; - services.mysql.settings = { - mysqld = { - plugin-load-add = [ "ha_mroonga.so" "ha_rocksdb.so" ]; - }; + mariadb = { }; - services.mysql.package = pkgs.mariadb; - }; + }; + testScript = '' + start_all() + + machine = ${name} + machine.wait_for_unit("mysql") + machine.succeed( + "echo 'use testdb; create table tests (test_id INT, PRIMARY KEY (test_id));' | sudo -u testuser mysql -u testuser" + ) + machine.succeed( + "echo 'use testdb; insert into tests values (42);' | sudo -u testuser mysql -u testuser" + ) + # Ensure testuser2 is not able to insert into testdb as mysql testuser2 + machine.fail( + "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser2" + ) + # Ensure testuser2 is not able to authenticate as mysql testuser + machine.fail( + "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser" + ) + machine.succeed( + "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 42" + ) + + ${lib.optionalString hasMroonga '' + # Check if Mroonga plugin works + machine.succeed( + "echo 'use testdb; create table mroongadb (test_id INT, PRIMARY KEY (test_id)) ENGINE = Mroonga;' | sudo -u testuser mysql -u testuser" + ) + machine.succeed( + "echo 'use testdb; insert into mroongadb values (25);' | sudo -u testuser mysql -u testuser" + ) + machine.succeed( + "echo 'use testdb; select test_id from mroongadb;' | sudo -u testuser mysql -u testuser -N | grep 25" + ) + machine.succeed( + "echo 'use testdb; drop table mroongadb;' | sudo -u testuser mysql -u testuser" + ) + ''} + + ${lib.optionalString hasRocksDB '' + # Check if RocksDB plugin works + machine.succeed( + "echo 'use testdb; create table rocksdb (test_id INT, PRIMARY KEY (test_id)) ENGINE = RocksDB;' | sudo -u testuser mysql -u testuser" + ) + machine.succeed( + "echo 'use testdb; insert into rocksdb values (28);' | sudo -u testuser mysql -u testuser" + ) + machine.succeed( + "echo 'use testdb; select test_id from rocksdb;' | sudo -u testuser mysql -u testuser -N | grep 28" + ) + machine.succeed( + "echo 'use testdb; drop table rocksdb;' | sudo -u testuser mysql -u testuser" + ) + ''} + ''; }; - - testScript = '' - start_all() - - mysql57.wait_for_unit("mysql") - mysql57.succeed( - "echo 'use testdb; create table tests (test_id INT, PRIMARY KEY (test_id));' | sudo -u testuser mysql -u testuser" - ) - mysql57.succeed( - "echo 'use testdb; insert into tests values (41);' | sudo -u testuser mysql -u testuser" - ) - # Ensure testuser2 is not able to insert into testdb as mysql testuser2 - mysql57.fail( - "echo 'use testdb; insert into tests values (22);' | sudo -u testuser2 mysql -u testuser2" - ) - # Ensure testuser2 is not able to authenticate as mysql testuser - mysql57.fail( - "echo 'use testdb; insert into tests values (22);' | sudo -u testuser2 mysql -u testuser" - ) - mysql57.succeed( - "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 41" - ) - mysql57.succeed( - "echo 'use testdb3; select * from tests;' | mysql -u testuser3 --password=secure -N | grep 4" - ) - - mysql80.wait_for_unit("mysql") - mysql80.succeed( - "echo 'use testdb; create table tests (test_id INT, PRIMARY KEY (test_id));' | sudo -u testuser mysql -u testuser" - ) - mysql80.succeed( - "echo 'use testdb; insert into tests values (41);' | sudo -u testuser mysql -u testuser" - ) - # Ensure testuser2 is not able to insert into testdb as mysql testuser2 - mysql80.fail( - "echo 'use testdb; insert into tests values (22);' | sudo -u testuser2 mysql -u testuser2" - ) - # Ensure testuser2 is not able to authenticate as mysql testuser - mysql80.fail( - "echo 'use testdb; insert into tests values (22);' | sudo -u testuser2 mysql -u testuser" - ) - mysql80.succeed( - "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 41" - ) - mysql80.succeed( - "echo 'use testdb3; select * from tests;' | mysql -u testuser3 --password=secure -N | grep 4" - ) - - mariadb.wait_for_unit("mysql") - mariadb.succeed( - "echo 'use testdb; create table tests (test_id INT, PRIMARY KEY (test_id));' | sudo -u testuser mysql -u testuser" - ) - mariadb.succeed( - "echo 'use testdb; insert into tests values (42);' | sudo -u testuser mysql -u testuser" - ) - # Ensure testuser2 is not able to insert into testdb as mysql testuser2 - mariadb.fail( - "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser2" - ) - # Ensure testuser2 is not able to authenticate as mysql testuser - mariadb.fail( - "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser" - ) - mariadb.succeed( - "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 42" - ) - - # Check if Mroonga plugin works - mariadb.succeed( - "echo 'use testdb; create table mroongadb (test_id INT, PRIMARY KEY (test_id)) ENGINE = Mroonga;' | sudo -u testuser mysql -u testuser" - ) - mariadb.succeed( - "echo 'use testdb; insert into mroongadb values (25);' | sudo -u testuser mysql -u testuser" - ) - mariadb.succeed( - "echo 'use testdb; select test_id from mroongadb;' | sudo -u testuser mysql -u testuser -N | grep 25" - ) - mariadb.succeed( - "echo 'use testdb; drop table mroongadb;' | sudo -u testuser mysql -u testuser" - ) - - # Check if RocksDB plugin works - mariadb.succeed( - "echo 'use testdb; create table rocksdb (test_id INT, PRIMARY KEY (test_id)) ENGINE = RocksDB;' | sudo -u testuser mysql -u testuser" - ) - mariadb.succeed( - "echo 'use testdb; insert into rocksdb values (28);' | sudo -u testuser mysql -u testuser" - ) - mariadb.succeed( - "echo 'use testdb; select test_id from rocksdb;' | sudo -u testuser mysql -u testuser -N | grep 28" - ) - mariadb.succeed( - "echo 'use testdb; drop table rocksdb;' | sudo -u testuser mysql -u testuser" - ) - ''; -}) +in + lib.mapAttrs (_: package: makeMySQLTest { + inherit package; + hasRocksDB = false; hasMroonga = false; + }) mysqlPackages + // (lib.mapAttrs (_: package: makeMySQLTest { + inherit package; + }) mariadbPackages) -- cgit 1.4.1 From 65dfe147b762c59eebe823acc44a1b7c8da00b1d Mon Sep 17 00:00:00 2001 From: ajs124 Date: Wed, 8 Dec 2021 09:47:07 +0000 Subject: nixos/tests/mariadb-galera: test multiple mariadb versions merge both tests and completely refactor --- nixos/tests/all-tests.nix | 3 +- nixos/tests/mysql/common.nix | 10 + nixos/tests/mysql/mariadb-galera-mariabackup.nix | 233 --------------------- nixos/tests/mysql/mariadb-galera-rsync.nix | 226 -------------------- nixos/tests/mysql/mariadb-galera.nix | 250 +++++++++++++++++++++++ pkgs/servers/sql/mariadb/default.nix | 3 +- 6 files changed, 262 insertions(+), 463 deletions(-) create mode 100644 nixos/tests/mysql/common.nix delete mode 100644 nixos/tests/mysql/mariadb-galera-mariabackup.nix delete mode 100644 nixos/tests/mysql/mariadb-galera-rsync.nix create mode 100644 nixos/tests/mysql/mariadb-galera.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 940ae11ddd1..93950277c27 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -268,8 +268,7 @@ in mailcatcher = handleTest ./mailcatcher.nix {}; mailhog = handleTest ./mailhog.nix {}; man = handleTest ./man.nix {}; - mariadb-galera-mariabackup = handleTest ./mysql/mariadb-galera-mariabackup.nix {}; - mariadb-galera-rsync = handleTest ./mysql/mariadb-galera-rsync.nix {}; + mariadb-galera = handleTest ./mysql/mariadb-galera.nix {}; matomo = handleTest ./matomo.nix {}; matrix-appservice-irc = handleTest ./matrix-appservice-irc.nix {}; matrix-conduit = handleTest ./matrix-conduit.nix {}; diff --git a/nixos/tests/mysql/common.nix b/nixos/tests/mysql/common.nix new file mode 100644 index 00000000000..968253d109c --- /dev/null +++ b/nixos/tests/mysql/common.nix @@ -0,0 +1,10 @@ +{ lib, pkgs }: { + mariadbPackages = lib.filterAttrs (n: _: lib.hasPrefix "mariadb" n) (pkgs.callPackage ../../../pkgs/servers/sql/mariadb { + inherit (pkgs.darwin) cctools; + inherit (pkgs.darwin.apple_sdk.frameworks) CoreServices; + }); + mysqlPackage = { + inherit (pkgs) mysql57 mysql80; + }; + mkTestName = pkg: "mariadb_${builtins.replaceStrings ["."] [""] (lib.versions.majorMinor pkg.version)}"; +} diff --git a/nixos/tests/mysql/mariadb-galera-mariabackup.nix b/nixos/tests/mysql/mariadb-galera-mariabackup.nix deleted file mode 100644 index 10682c361d1..00000000000 --- a/nixos/tests/mysql/mariadb-galera-mariabackup.nix +++ /dev/null @@ -1,233 +0,0 @@ -import ./../make-test-python.nix ({ pkgs, ...} : - -let - mysqlenv-common = pkgs.buildEnv { name = "mysql-path-env-common"; pathsToLink = [ "/bin" ]; paths = with pkgs; [ bash gawk gnutar inetutils which ]; }; - mysqlenv-mariabackup = pkgs.buildEnv { name = "mysql-path-env-mariabackup"; pathsToLink = [ "/bin" ]; paths = with pkgs; [ gzip iproute2 netcat procps pv socat ]; }; - - # Common user configuration - users = { ... }: - { - users.users.testuser = { - isSystemUser = true; - group = "testusers"; - }; - users.groups.testusers = { }; - }; - -in { - name = "mariadb-galera-mariabackup"; - meta = with pkgs.lib.maintainers; { - maintainers = [ izorkin ]; - }; - - # The test creates a Galera cluster with 3 nodes and is checking if mariabackup-based SST works. The cluster is tested by creating a DB and an empty table on one node, - # and checking the table's presence on the other node. - - nodes = { - galera_01 = - { pkgs, ... }: - { - imports = [ users ]; - networking = { - interfaces.eth1 = { - ipv4.addresses = [ - { address = "192.168.1.1"; prefixLength = 24; } - ]; - }; - extraHosts = '' - 192.168.1.1 galera_01 - 192.168.1.2 galera_02 - 192.168.1.3 galera_03 - ''; - firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; - firewall.allowedUDPPorts = [ 4567 ]; - }; - systemd.services.mysql = with pkgs; { - path = [ mysqlenv-common mysqlenv-mariabackup ]; - }; - services.mysql = { - enable = true; - package = pkgs.mariadb; - ensureDatabases = [ "testdb" ]; - ensureUsers = [{ - name = "testuser"; - ensurePermissions = { - "testdb.*" = "ALL PRIVILEGES"; - }; - }]; - initialScript = pkgs.writeText "mariadb-init.sql" '' - GRANT ALL PRIVILEGES ON *.* TO 'check_repl'@'localhost' IDENTIFIED BY 'check_pass' WITH GRANT OPTION; - FLUSH PRIVILEGES; - ''; - settings = { - mysqld = { - bind_address = "0.0.0.0"; - }; - galera = { - wsrep_on = "ON"; - wsrep_debug = "NONE"; - wsrep_retry_autocommit = "3"; - wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so"; - wsrep_cluster_address = "gcomm://"; - wsrep_cluster_name = "galera"; - wsrep_node_address = "192.168.1.1"; - wsrep_node_name = "galera_01"; - wsrep_sst_method = "mariabackup"; - wsrep_sst_auth = "check_repl:check_pass"; - binlog_format = "ROW"; - enforce_storage_engine = "InnoDB"; - innodb_autoinc_lock_mode = "2"; - }; - }; - }; - }; - - galera_02 = - { pkgs, ... }: - { - imports = [ users ]; - networking = { - interfaces.eth1 = { - ipv4.addresses = [ - { address = "192.168.1.2"; prefixLength = 24; } - ]; - }; - extraHosts = '' - 192.168.1.1 galera_01 - 192.168.1.2 galera_02 - 192.168.1.3 galera_03 - ''; - firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; - firewall.allowedUDPPorts = [ 4567 ]; - }; - systemd.services.mysql = with pkgs; { - path = [ mysqlenv-common mysqlenv-mariabackup ]; - }; - services.mysql = { - enable = true; - package = pkgs.mariadb; - settings = { - mysqld = { - bind_address = "0.0.0.0"; - }; - galera = { - wsrep_on = "ON"; - wsrep_debug = "NONE"; - wsrep_retry_autocommit = "3"; - wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so"; - wsrep_cluster_address = "gcomm://galera_01,galera_02,galera_03"; - wsrep_cluster_name = "galera"; - wsrep_node_address = "192.168.1.2"; - wsrep_node_name = "galera_02"; - wsrep_sst_method = "mariabackup"; - wsrep_sst_auth = "check_repl:check_pass"; - binlog_format = "ROW"; - enforce_storage_engine = "InnoDB"; - innodb_autoinc_lock_mode = "2"; - }; - }; - }; - }; - - galera_03 = - { pkgs, ... }: - { - imports = [ users ]; - networking = { - interfaces.eth1 = { - ipv4.addresses = [ - { address = "192.168.1.3"; prefixLength = 24; } - ]; - }; - extraHosts = '' - 192.168.1.1 galera_01 - 192.168.1.2 galera_02 - 192.168.1.3 galera_03 - ''; - firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; - firewall.allowedUDPPorts = [ 4567 ]; - }; - systemd.services.mysql = with pkgs; { - path = [ mysqlenv-common mysqlenv-mariabackup ]; - }; - services.mysql = { - enable = true; - package = pkgs.mariadb; - settings = { - mysqld = { - bind_address = "0.0.0.0"; - }; - galera = { - wsrep_on = "ON"; - wsrep_debug = "NONE"; - wsrep_retry_autocommit = "3"; - wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so"; - wsrep_cluster_address = "gcomm://galera_01,galera_02,galera_03"; - wsrep_cluster_name = "galera"; - wsrep_node_address = "192.168.1.3"; - wsrep_node_name = "galera_03"; - wsrep_sst_method = "mariabackup"; - wsrep_sst_auth = "check_repl:check_pass"; - binlog_format = "ROW"; - enforce_storage_engine = "InnoDB"; - innodb_autoinc_lock_mode = "2"; - }; - }; - }; - }; - }; - - testScript = '' - galera_01.start() - galera_01.wait_for_unit("mysql") - galera_01.wait_for_open_port(3306) - galera_01.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; create table db1 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" - ) - galera_01.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db1 values (37);'" - ) - galera_02.start() - galera_02.wait_for_unit("mysql") - galera_02.wait_for_open_port(3306) - galera_03.start() - galera_03.wait_for_unit("mysql") - galera_03.wait_for_open_port(3306) - galera_02.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 37" - ) - galera_02.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" - ) - galera_02.succeed("systemctl stop mysql") - galera_01.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db2 values (38);'" - ) - galera_03.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" - ) - galera_01.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db3 values (39);'" - ) - galera_02.succeed("systemctl start mysql") - galera_02.wait_for_open_port(3306) - galera_02.succeed( - "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_cluster_size.*3'" - ) - galera_03.succeed( - "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'" - ) - galera_01.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db3;' -N | grep 39" - ) - galera_02.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db2;' -N | grep 38" - ) - galera_03.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 37" - ) - galera_01.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db3;'") - galera_02.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db2;'") - galera_03.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db1;'") - ''; -}) diff --git a/nixos/tests/mysql/mariadb-galera-rsync.nix b/nixos/tests/mysql/mariadb-galera-rsync.nix deleted file mode 100644 index 701e01e8871..00000000000 --- a/nixos/tests/mysql/mariadb-galera-rsync.nix +++ /dev/null @@ -1,226 +0,0 @@ -import ./../make-test-python.nix ({ pkgs, ...} : - -let - mysqlenv-common = pkgs.buildEnv { name = "mysql-path-env-common"; pathsToLink = [ "/bin" ]; paths = with pkgs; [ bash gawk gnutar inetutils which ]; }; - mysqlenv-rsync = pkgs.buildEnv { name = "mysql-path-env-rsync"; pathsToLink = [ "/bin" ]; paths = with pkgs; [ lsof procps rsync stunnel ]; }; - - # Common user configuration - users = { ... }: - { - users.users.testuser = { - isSystemUser = true; - group = "testusers"; - }; - users.groups.testusers = { }; - }; - -in { - name = "mariadb-galera-rsync"; - meta = with pkgs.lib.maintainers; { - maintainers = [ izorkin ]; - }; - - # The test creates a Galera cluster with 3 nodes and is checking if rsync-based SST works. The cluster is tested by creating a DB and an empty table on one node, - # and checking the table's presence on the other node. - - nodes = { - galera_04 = - { pkgs, ... }: - { - imports = [ users ]; - networking = { - interfaces.eth1 = { - ipv4.addresses = [ - { address = "192.168.2.1"; prefixLength = 24; } - ]; - }; - extraHosts = '' - 192.168.2.1 galera_04 - 192.168.2.2 galera_05 - 192.168.2.3 galera_06 - ''; - firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; - firewall.allowedUDPPorts = [ 4567 ]; - }; - systemd.services.mysql = with pkgs; { - path = [ mysqlenv-common mysqlenv-rsync ]; - }; - services.mysql = { - enable = true; - package = pkgs.mariadb; - ensureDatabases = [ "testdb" ]; - ensureUsers = [{ - name = "testuser"; - ensurePermissions = { - "testdb.*" = "ALL PRIVILEGES"; - }; - }]; - settings = { - mysqld = { - bind_address = "0.0.0.0"; - }; - galera = { - wsrep_on = "ON"; - wsrep_debug = "NONE"; - wsrep_retry_autocommit = "3"; - wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so"; - wsrep_cluster_address = "gcomm://"; - wsrep_cluster_name = "galera-rsync"; - wsrep_node_address = "192.168.2.1"; - wsrep_node_name = "galera_04"; - wsrep_sst_method = "rsync"; - binlog_format = "ROW"; - enforce_storage_engine = "InnoDB"; - innodb_autoinc_lock_mode = "2"; - }; - }; - }; - }; - - galera_05 = - { pkgs, ... }: - { - imports = [ users ]; - networking = { - interfaces.eth1 = { - ipv4.addresses = [ - { address = "192.168.2.2"; prefixLength = 24; } - ]; - }; - extraHosts = '' - 192.168.2.1 galera_04 - 192.168.2.2 galera_05 - 192.168.2.3 galera_06 - ''; - firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; - firewall.allowedUDPPorts = [ 4567 ]; - }; - systemd.services.mysql = with pkgs; { - path = [ mysqlenv-common mysqlenv-rsync ]; - }; - services.mysql = { - enable = true; - package = pkgs.mariadb; - settings = { - mysqld = { - bind_address = "0.0.0.0"; - }; - galera = { - wsrep_on = "ON"; - wsrep_debug = "NONE"; - wsrep_retry_autocommit = "3"; - wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so"; - wsrep_cluster_address = "gcomm://galera_04,galera_05,galera_06"; - wsrep_cluster_name = "galera-rsync"; - wsrep_node_address = "192.168.2.2"; - wsrep_node_name = "galera_05"; - wsrep_sst_method = "rsync"; - binlog_format = "ROW"; - enforce_storage_engine = "InnoDB"; - innodb_autoinc_lock_mode = "2"; - }; - }; - }; - }; - - galera_06 = - { pkgs, ... }: - { - imports = [ users ]; - networking = { - interfaces.eth1 = { - ipv4.addresses = [ - { address = "192.168.2.3"; prefixLength = 24; } - ]; - }; - extraHosts = '' - 192.168.2.1 galera_04 - 192.168.2.2 galera_05 - 192.168.2.3 galera_06 - ''; - firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; - firewall.allowedUDPPorts = [ 4567 ]; - }; - systemd.services.mysql = with pkgs; { - path = [ mysqlenv-common mysqlenv-rsync ]; - }; - services.mysql = { - enable = true; - package = pkgs.mariadb; - settings = { - mysqld = { - bind_address = "0.0.0.0"; - }; - galera = { - wsrep_on = "ON"; - wsrep_debug = "NONE"; - wsrep_retry_autocommit = "3"; - wsrep_provider = "${pkgs.mariadb-galera}/lib/galera/libgalera_smm.so"; - wsrep_cluster_address = "gcomm://galera_04,galera_05,galera_06"; - wsrep_cluster_name = "galera-rsync"; - wsrep_node_address = "192.168.2.3"; - wsrep_node_name = "galera_06"; - wsrep_sst_method = "rsync"; - binlog_format = "ROW"; - enforce_storage_engine = "InnoDB"; - innodb_autoinc_lock_mode = "2"; - }; - }; - }; - }; - }; - - testScript = '' - galera_04.start() - galera_04.wait_for_unit("mysql") - galera_04.wait_for_open_port(3306) - galera_04.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; create table db1 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" - ) - galera_04.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db1 values (41);'" - ) - galera_05.start() - galera_05.wait_for_unit("mysql") - galera_05.wait_for_open_port(3306) - galera_06.start() - galera_06.wait_for_unit("mysql") - galera_06.wait_for_open_port(3306) - galera_05.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 41" - ) - galera_05.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" - ) - galera_05.succeed("systemctl stop mysql") - galera_04.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db2 values (42);'" - ) - galera_06.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" - ) - galera_04.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db3 values (43);'" - ) - galera_05.succeed("systemctl start mysql") - galera_05.wait_for_open_port(3306) - galera_05.succeed( - "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_cluster_size.*3'" - ) - galera_06.succeed( - "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'" - ) - galera_04.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db3;' -N | grep 43" - ) - galera_05.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db2;' -N | grep 42" - ) - galera_06.succeed( - "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 41" - ) - galera_04.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db3;'") - galera_05.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db2;'") - galera_06.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db1;'") - ''; -}) diff --git a/nixos/tests/mysql/mariadb-galera.nix b/nixos/tests/mysql/mariadb-galera.nix new file mode 100644 index 00000000000..c9962f49c02 --- /dev/null +++ b/nixos/tests/mysql/mariadb-galera.nix @@ -0,0 +1,250 @@ +{ + system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../../.. { inherit system config; }, + lib ? pkgs.lib +}: + +let + inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages; + + makeTest = import ./../make-test-python.nix; + + # Common user configuration + makeGaleraTest = { + mariadbPackage, + name ? mkTestName mariadbPackage, + galeraPackage ? pkgs.mariadb-galera + }: makeTest { + name = "${name}-galera-mariabackup"; + meta = with pkgs.lib.maintainers; { + maintainers = [ izorkin ajs124 das_j ]; + }; + + # The test creates a Galera cluster with 3 nodes and is checking if mariabackup-based SST works. The cluster is tested by creating a DB and an empty table on one node, + # and checking the table's presence on the other node. + nodes = let + mkGaleraNode = { + id, + method + }: let + address = "192.168.1.${toString id}"; + isFirstClusterNode = id == 1 || id == 4; + in { + users = { + users.testuser = { + isSystemUser = true; + group = "testusers"; + }; + groups.testusers = { }; + }; + + networking = { + interfaces.eth1 = { + ipv4.addresses = [ + { inherit address; prefixLength = 24; } + ]; + }; + extraHosts = lib.concatMapStringsSep "\n" (i: "192.168.1.${toString i} galera_0${toString i}") (lib.range 1 6); + firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ]; + firewall.allowedUDPPorts = [ 4567 ]; + }; + systemd.services.mysql = with pkgs; { + path = with pkgs; [ + bash + gawk + gnutar + gzip + inetutils + iproute2 + netcat + procps + pv + rsync + socat + stunnel + which + ]; + }; + services.mysql = { + enable = true; + package = mariadbPackage; + ensureDatabases = lib.mkIf isFirstClusterNode [ "testdb" ]; + ensureUsers = lib.mkIf isFirstClusterNode [{ + name = "testuser"; + ensurePermissions = { + "testdb.*" = "ALL PRIVILEGES"; + }; + }]; + initialScript = lib.mkIf isFirstClusterNode (pkgs.writeText "mariadb-init.sql" '' + GRANT ALL PRIVILEGES ON *.* TO 'check_repl'@'localhost' IDENTIFIED BY 'check_pass' WITH GRANT OPTION; + FLUSH PRIVILEGES; + ''); + settings = { + mysqld = { + bind_address = "0.0.0.0"; + }; + galera = { + wsrep_on = "ON"; + wsrep_debug = "NONE"; + wsrep_retry_autocommit = "3"; + wsrep_provider = "${galeraPackage}/lib/galera/libgalera_smm.so"; + wsrep_cluster_address = "gcomm://" + + lib.optionalString (id == 2 || id == 3) "galera_01,galera_02,galera_03" + + lib.optionalString (id == 5 || id == 6) "galera_04,galera_05,galera_06"; + wsrep_cluster_name = "galera"; + wsrep_node_address = address; + wsrep_node_name = "galera_0${toString id}"; + wsrep_sst_method = method; + wsrep_sst_auth = "check_repl:check_pass"; + binlog_format = "ROW"; + enforce_storage_engine = "InnoDB"; + innodb_autoinc_lock_mode = "2"; + }; + }; + }; + }; + in { + galera_01 = mkGaleraNode { + id = 1; + method = "mariabackup"; + }; + + galera_02 = mkGaleraNode { + id = 2; + method = "mariabackup"; + }; + + galera_03 = mkGaleraNode { + id = 3; + method = "mariabackup"; + }; + + galera_04 = mkGaleraNode { + id = 4; + method = "rsync"; + }; + + galera_05 = mkGaleraNode { + id = 5; + method = "rsync"; + }; + + galera_06 = mkGaleraNode { + id = 6; + method = "rsync"; + }; + + }; + + testScript = '' + galera_01.start() + galera_01.wait_for_unit("mysql") + galera_01.wait_for_open_port(3306) + galera_01.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; create table db1 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" + ) + galera_01.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db1 values (37);'" + ) + galera_02.start() + galera_02.wait_for_unit("mysql") + galera_02.wait_for_open_port(3306) + galera_03.start() + galera_03.wait_for_unit("mysql") + galera_03.wait_for_open_port(3306) + galera_02.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 37" + ) + galera_02.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" + ) + galera_02.succeed("systemctl stop mysql") + galera_01.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db2 values (38);'" + ) + galera_03.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" + ) + galera_01.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db3 values (39);'" + ) + galera_02.succeed("systemctl start mysql") + galera_02.wait_for_open_port(3306) + galera_02.succeed( + "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_cluster_size.*3'" + ) + galera_03.succeed( + "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'" + ) + galera_01.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db3;' -N | grep 39" + ) + galera_02.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db2;' -N | grep 38" + ) + galera_03.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 37" + ) + galera_01.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db3;'") + galera_02.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db2;'") + galera_03.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db1;'") + galera_01.crash() + galera_02.crash() + galera_03.crash() + + galera_04.start() + galera_04.wait_for_unit("mysql") + galera_04.wait_for_open_port(3306) + galera_04.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; create table db1 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" + ) + galera_04.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db1 values (41);'" + ) + galera_05.start() + galera_05.wait_for_unit("mysql") + galera_05.wait_for_open_port(3306) + galera_06.start() + galera_06.wait_for_unit("mysql") + galera_06.wait_for_open_port(3306) + galera_05.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 41" + ) + galera_05.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" + ) + galera_05.succeed("systemctl stop mysql") + galera_04.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db2 values (42);'" + ) + galera_06.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'" + ) + galera_04.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db3 values (43);'" + ) + galera_05.succeed("systemctl start mysql") + galera_05.wait_for_open_port(3306) + galera_05.succeed( + "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_cluster_size.*3'" + ) + galera_06.succeed( + "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'" + ) + galera_04.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db3;' -N | grep 43" + ) + galera_05.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db2;' -N | grep 42" + ) + galera_06.succeed( + "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 41" + ) + galera_04.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db3;'") + galera_05.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db2;'") + galera_06.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db1;'") + ''; + }; +in + lib.mapAttrs (_: mariadbPackage: makeGaleraTest { inherit mariadbPackage; }) mariadbPackages diff --git a/pkgs/servers/sql/mariadb/default.nix b/pkgs/servers/sql/mariadb/default.nix index 101e4609c50..3f1f3448dad 100644 --- a/pkgs/servers/sql/mariadb/default.nix +++ b/pkgs/servers/sql/mariadb/default.nix @@ -112,8 +112,7 @@ commonOptions = packageSettings: rec { # attributes common to both builds passthru.tests = let testVersion = "mariadb_${builtins.replaceStrings ["."] [""] (lib.versions.majorMinor (packageSettings.version))}"; in { - mariadb-galera-mariabackup = nixosTests.mariadb-galera-mariabackup.${testVersion}; - mariadb-galera-rsync = nixosTests.mariadb-galera-rsync.${testVersion}; + mariadb-galera-rsync = nixosTests.mariadb-galera.${testVersion}; mysql = nixosTests.mysql.${testVersion}; mysql-autobackup = nixosTests.mysql-autobackup.${testVersion}; mysql-backup = nixosTests.mysql-backup.${testVersion}; -- cgit 1.4.1 From a2ec554e83acf57441060f3765efbcd25afec6c6 Mon Sep 17 00:00:00 2001 From: ajs124 Date: Wed, 8 Dec 2021 15:47:42 +0000 Subject: nixos/tests/mysql-replication: test multiple mariadb versions --- nixos/tests/mysql/mysql-replication.nix | 158 +++++++++++++++++--------------- 1 file changed, 84 insertions(+), 74 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/mysql/mysql-replication.nix b/nixos/tests/mysql/mysql-replication.nix index a52372ca47c..f6014019bd5 100644 --- a/nixos/tests/mysql/mysql-replication.nix +++ b/nixos/tests/mysql/mysql-replication.nix @@ -1,91 +1,101 @@ -import ./../make-test-python.nix ({ pkgs, ...} : +{ + system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../../.. { inherit system config; }, + lib ? pkgs.lib +}: let + inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages; + replicateUser = "replicate"; replicatePassword = "secret"; -in -{ - name = "mysql-replication"; - meta = with pkgs.lib.maintainers; { - maintainers = [ eelco shlevy ]; - }; + makeTest = import ./../make-test-python.nix; - nodes = { - master = - { pkgs, ... }: + makeReplicationTest = { + package, + name ? mkTestName package, + }: makeTest { + name = "${name}-replication"; + meta = with pkgs.lib.maintainers; { + maintainers = [ ajs124 das_j ]; + }; - { - services.mysql.enable = true; - services.mysql.package = pkgs.mariadb; - services.mysql.replication.role = "master"; - services.mysql.replication.slaveHost = "%"; - services.mysql.replication.masterUser = replicateUser; - services.mysql.replication.masterPassword = replicatePassword; - services.mysql.initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ]; + nodes = { + primary = { + services.mysql = { + inherit package; + enable = true; + replication.role = "master"; + replication.slaveHost = "%"; + replication.masterUser = replicateUser; + replication.masterPassword = replicatePassword; + initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ]; + }; networking.firewall.allowedTCPPorts = [ 3306 ]; }; - slave1 = - { pkgs, nodes, ... }: - - { - services.mysql.enable = true; - services.mysql.package = pkgs.mariadb; - services.mysql.replication.role = "slave"; - services.mysql.replication.serverId = 2; - services.mysql.replication.masterHost = nodes.master.config.networking.hostName; - services.mysql.replication.masterUser = replicateUser; - services.mysql.replication.masterPassword = replicatePassword; + secondary1 = { nodes, ... }: { + services.mysql = { + inherit package; + enable = true; + replication.role = "slave"; + replication.serverId = 2; + replication.masterHost = nodes.primary.config.networking.hostName; + replication.masterUser = replicateUser; + replication.masterPassword = replicatePassword; + }; }; - slave2 = - { pkgs, nodes, ... }: - - { - services.mysql.enable = true; - services.mysql.package = pkgs.mariadb; - services.mysql.replication.role = "slave"; - services.mysql.replication.serverId = 3; - services.mysql.replication.masterHost = nodes.master.config.networking.hostName; - services.mysql.replication.masterUser = replicateUser; - services.mysql.replication.masterPassword = replicatePassword; + secondary2 = { nodes, ... }: { + services.mysql = { + inherit package; + enable = true; + replication.role = "slave"; + replication.serverId = 3; + replication.masterHost = nodes.primary.config.networking.hostName; + replication.masterUser = replicateUser; + replication.masterPassword = replicatePassword; + }; }; - }; + }; - testScript = '' - master.start() - master.wait_for_unit("mysql") - master.wait_for_open_port(3306) - # Wait for testdb to be fully populated (5 rows). - master.wait_until_succeeds( - "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" - ) + testScript = '' + primary.start() + primary.wait_for_unit("mysql") + primary.wait_for_open_port(3306) + # Wait for testdb to be fully populated (5 rows). + primary.wait_until_succeeds( + "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" + ) - slave1.start() - slave2.start() - slave1.wait_for_unit("mysql") - slave1.wait_for_open_port(3306) - slave2.wait_for_unit("mysql") - slave2.wait_for_open_port(3306) + secondary1.start() + secondary2.start() + secondary1.wait_for_unit("mysql") + secondary1.wait_for_open_port(3306) + secondary2.wait_for_unit("mysql") + secondary2.wait_for_open_port(3306) - # wait for replications to finish - slave1.wait_until_succeeds( - "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" - ) - slave2.wait_until_succeeds( - "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" - ) + # wait for replications to finish + secondary1.wait_until_succeeds( + "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" + ) + secondary2.wait_until_succeeds( + "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" + ) - slave2.succeed("systemctl stop mysql") - master.succeed( - "echo 'insert into testdb.tests values (123, 456);' | sudo -u mysql mysql -u mysql -N" - ) - slave2.succeed("systemctl start mysql") - slave2.wait_for_unit("mysql") - slave2.wait_for_open_port(3306) - slave2.wait_until_succeeds( - "echo 'select * from testdb.tests where Id = 123;' | sudo -u mysql mysql -u mysql -N | grep 456" - ) - ''; -}) + secondary2.succeed("systemctl stop mysql") + primary.succeed( + "echo 'insert into testdb.tests values (123, 456);' | sudo -u mysql mysql -u mysql -N" + ) + secondary2.succeed("systemctl start mysql") + secondary2.wait_for_unit("mysql") + secondary2.wait_for_open_port(3306) + secondary2.wait_until_succeeds( + "echo 'select * from testdb.tests where Id = 123;' | sudo -u mysql mysql -u mysql -N | grep 456" + ) + ''; + }; +in + lib.mapAttrs (_: package: makeReplicationTest { inherit package; }) mariadbPackages -- cgit 1.4.1 From 37ba30c49479b561b081d33cd9305140ec192bf4 Mon Sep 17 00:00:00 2001 From: ajs124 Date: Wed, 8 Dec 2021 16:33:23 +0000 Subject: nixos/tests/mysql-autobackup: test multiple mariadb versions --- nixos/tests/mysql/mysql-autobackup.nix | 81 ++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 33 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/mysql/mysql-autobackup.nix b/nixos/tests/mysql/mysql-autobackup.nix index b0ec7daaf05..101122f7bde 100644 --- a/nixos/tests/mysql/mysql-autobackup.nix +++ b/nixos/tests/mysql/mysql-autobackup.nix @@ -1,38 +1,53 @@ -import ./../make-test-python.nix ({ pkgs, lib, ... }: - { - name = "automysqlbackup"; - meta.maintainers = [ lib.maintainers.aanderse ]; - - machine = - { pkgs, ... }: - { - services.mysql.enable = true; - services.mysql.package = pkgs.mariadb; - services.mysql.initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ]; + system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../../.. { inherit system config; }, + lib ? pkgs.lib +}: + +let + inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages; + + makeTest = import ./../make-test-python.nix; + + makeAutobackupTest = { + package, + name ? mkTestName package, + }: makeTest { + name = "${name}-automysqlbackup"; + meta.maintainers = [ lib.maintainers.aanderse ]; + + machine = { + services.mysql = { + inherit package; + enable = true; + initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ]; + }; services.automysqlbackup.enable = true; }; - testScript = '' - start_all() - - # Need to have mysql started so that it can be populated with data. - machine.wait_for_unit("mysql.service") - - with subtest("Wait for testdb to be fully populated (5 rows)."): - machine.wait_until_succeeds( - "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" - ) - - with subtest("Do a backup and wait for it to start"): - machine.start_job("automysqlbackup.service") - machine.wait_for_job("automysqlbackup.service") - - with subtest("wait for backup file and check that data appears in backup"): - machine.wait_for_file("/var/backup/mysql/daily/testdb") - machine.succeed( - "${pkgs.gzip}/bin/zcat /var/backup/mysql/daily/testdb/daily_testdb_*.sql.gz | grep hello" - ) - ''; -}) + testScript = '' + start_all() + + # Need to have mysql started so that it can be populated with data. + machine.wait_for_unit("mysql.service") + + with subtest("Wait for testdb to be fully populated (5 rows)."): + machine.wait_until_succeeds( + "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" + ) + + with subtest("Do a backup and wait for it to start"): + machine.start_job("automysqlbackup.service") + machine.wait_for_job("automysqlbackup.service") + + with subtest("wait for backup file and check that data appears in backup"): + machine.wait_for_file("/var/backup/mysql/daily/testdb") + machine.succeed( + "${pkgs.gzip}/bin/zcat /var/backup/mysql/daily/testdb/daily_testdb_*.sql.gz | grep hello" + ) + ''; + }; +in + lib.mapAttrs (_: package: makeAutobackupTest { inherit package; }) mariadbPackages -- cgit 1.4.1 From ef5d714f8ce548716a95ed8cc66cb02851d63189 Mon Sep 17 00:00:00 2001 From: ajs124 Date: Wed, 8 Dec 2021 16:54:59 +0000 Subject: nixos/tests/mysql-backup: test multiple mariadb versions --- nixos/tests/mysql/mysql-backup.nix | 104 +++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 44 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/mysql/mysql-backup.nix b/nixos/tests/mysql/mysql-backup.nix index 269fddc66e1..9335b233327 100644 --- a/nixos/tests/mysql/mysql-backup.nix +++ b/nixos/tests/mysql/mysql-backup.nix @@ -1,56 +1,72 @@ -# Test whether mysqlBackup option works -import ./../make-test-python.nix ({ pkgs, ... } : { - name = "mysql-backup"; - meta = with pkgs.lib.maintainers; { - maintainers = [ rvl ]; - }; +{ + system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../../.. { inherit system config; }, + lib ? pkgs.lib +}: - nodes = { - master = { pkgs, ... }: { - services.mysql = { - enable = true; - initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ]; - package = pkgs.mariadb; - }; +let + inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages; + + makeTest = import ./../make-test-python.nix; + + makeBackupTest = { + package, + name ? mkTestName package + }: makeTest { + name = "${name}-backup"; + meta = with pkgs.lib.maintainers; { + maintainers = [ rvl ]; + }; + + nodes = { + master = { pkgs, ... }: { + services.mysql = { + inherit package; + enable = true; + initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ]; + }; - services.mysqlBackup = { - enable = true; - databases = [ "doesnotexist" "testdb" ]; + services.mysqlBackup = { + enable = true; + databases = [ "doesnotexist" "testdb" ]; + }; }; }; - }; - testScript = '' - start_all() + testScript = '' + start_all() - # Delete backup file that may be left over from a previous test run. - # This is not needed on Hydra but useful for repeated local test runs. - master.execute("rm -f /var/backup/mysql/testdb.gz") + # Delete backup file that may be left over from a previous test run. + # This is not needed on Hydra but useful for repeated local test runs. + master.execute("rm -f /var/backup/mysql/testdb.gz") - # Need to have mysql started so that it can be populated with data. - master.wait_for_unit("mysql.service") + # Need to have mysql started so that it can be populated with data. + master.wait_for_unit("mysql.service") - # Wait for testdb to be fully populated (5 rows). - master.wait_until_succeeds( - "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" - ) + # Wait for testdb to be fully populated (5 rows). + master.wait_until_succeeds( + "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5" + ) - # Do a backup and wait for it to start - master.start_job("mysql-backup.service") - master.wait_for_unit("mysql-backup.service") + # Do a backup and wait for it to start + master.start_job("mysql-backup.service") + master.wait_for_unit("mysql-backup.service") - # wait for backup to fail, because of database 'doesnotexist' - master.wait_until_fails("systemctl is-active -q mysql-backup.service") + # wait for backup to fail, because of database 'doesnotexist' + master.wait_until_fails("systemctl is-active -q mysql-backup.service") - # wait for backup file and check that data appears in backup - master.wait_for_file("/var/backup/mysql/testdb.gz") - master.succeed( - "${pkgs.gzip}/bin/zcat /var/backup/mysql/testdb.gz | grep hello" - ) + # wait for backup file and check that data appears in backup + master.wait_for_file("/var/backup/mysql/testdb.gz") + master.succeed( + "${pkgs.gzip}/bin/zcat /var/backup/mysql/testdb.gz | grep hello" + ) - # Check that a failed backup is logged - master.succeed( - "journalctl -u mysql-backup.service | grep 'fail.*doesnotexist' > /dev/null" - ) - ''; -}) + # Check that a failed backup is logged + master.succeed( + "journalctl -u mysql-backup.service | grep 'fail.*doesnotexist' > /dev/null" + ) + ''; + }; +in + lib.mapAttrs (_: package: makeBackupTest { inherit package; }) mariadbPackages -- cgit 1.4.1 From 4cf0848fc85a05bf46b142d3981f542e2e924034 Mon Sep 17 00:00:00 2001 From: Jonathan Ringer Date: Mon, 3 Jan 2022 20:59:18 -0800 Subject: nixosTests.boot-stage1: fix kernel build with 5.15 --- nixos/tests/boot-stage1.nix | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nixos/tests') diff --git a/nixos/tests/boot-stage1.nix b/nixos/tests/boot-stage1.nix index ce86fc5f494..756decd2039 100644 --- a/nixos/tests/boot-stage1.nix +++ b/nixos/tests/boot-stage1.nix @@ -33,6 +33,8 @@ import ./make-test-python.nix ({ pkgs, ... }: { #include #endif + MODULE_LICENSE("GPL"); + struct task_struct *canaryTask; static int kcanary(void *nothing) -- cgit 1.4.1 From c95e816c655a1032e25ddcd81d1b4b02f7b69757 Mon Sep 17 00:00:00 2001 From: Jonas Heinrich Date: Sun, 23 Jan 2022 15:17:01 +0100 Subject: nixos/wordpress: Drop old deprecated interface (#152674) --- .../from_md/release-notes/rl-2205.section.xml | 8 +++ nixos/doc/manual/release-notes/rl-2205.section.md | 4 ++ nixos/modules/services/web-apps/wordpress.nix | 57 ++++++++-------------- nixos/tests/wordpress.nix | 9 ++-- 4 files changed, 36 insertions(+), 42 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index fc4073c4936..f1c6644b67e 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -279,6 +279,14 @@ virtualisation.docker.daemon.settings. + + + The backward compatibility in + services.wordpress to configure sites with + the old interface has been removed. Please use + services.wordpress.sites instead. + + The backward compatibility in diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index dee532e77d4..525955f8c9f 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -92,6 +92,10 @@ In addition to numerous new and upgraded packages, this release has the followin - If you previously used `/etc/docker/daemon.json`, you need to incorporate the changes into the new option `virtualisation.docker.daemon.settings`. +- The backward compatibility in `services.wordpress` to configure sites with + the old interface has been removed. Please use `services.wordpress.sites` + instead. + - The backward compatibility in `services.dokuwiki` to configure sites with the old interface has been removed. Please use `services.dokuwiki.sites` instead. diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix index 3753d01bf8d..59471a739cb 100644 --- a/nixos/modules/services/web-apps/wordpress.nix +++ b/nixos/modules/services/web-apps/wordpress.nix @@ -1,20 +1,14 @@ { config, pkgs, lib, ... }: -let - inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types; - inherit (lib) any attrValues concatMapStringsSep flatten literalExpression; - inherit (lib) filterAttrs mapAttrs mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString; +with lib; - cfg = migrateOldAttrs config.services.wordpress; +let + cfg = config.services.wordpress; eachSite = cfg.sites; user = "wordpress"; webserver = config.services.${cfg.webserver}; stateDir = hostName: "/var/lib/wordpress/${hostName}"; - # Migrate config.services.wordpress. to config.services.wordpress.sites. - oldSites = filterAttrs (o: _: o != "sites" && o != "webserver"); - migrateOldAttrs = cfg: cfg // { sites = cfg.sites // oldSites cfg; }; - pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec { pname = "wordpress-${hostName}"; version = src.version; @@ -266,36 +260,29 @@ in { # interface options = { - services.wordpress = mkOption { - type = types.submodule { - # Used to support old interface - freeformType = types.attrsOf (types.submodule siteOpts); - - # New interface - options.sites = mkOption { - type = types.attrsOf (types.submodule siteOpts); - default = {}; - description = "Specification of one or more WordPress sites to serve"; - }; + services.wordpress = { - options.webserver = mkOption { - type = types.enum [ "httpd" "nginx" "caddy" ]; - default = "httpd"; - description = '' - Whether to use apache2 or nginx for virtual host management. + sites = mkOption { + type = types.attrsOf (types.submodule siteOpts); + default = {}; + description = "Specification of one or more WordPress sites to serve"; + }; - Further nginx configuration can be done by adapting services.nginx.virtualHosts.<name>. - See for further information. + webserver = mkOption { + type = types.enum [ "httpd" "nginx" "caddy" ]; + default = "httpd"; + description = '' + Whether to use apache2 or nginx for virtual host management. - Further apache2 configuration can be done by adapting services.httpd.virtualHosts.<name>. - See for further information. - ''; - }; + Further nginx configuration can be done by adapting services.nginx.virtualHosts.<name>. + See for further information. + + Further apache2 configuration can be done by adapting services.httpd.virtualHosts.<name>. + See for further information. + ''; }; - default = {}; - description = "Wordpress configuration"; - }; + }; }; # implementation @@ -312,8 +299,6 @@ in }) eachSite); - warnings = mapAttrsToList (hostName: _: ''services.wordpress."${hostName}" is deprecated use services.wordpress.sites."${hostName}"'') (oldSites cfg); - services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) { enable = true; package = mkDefault pkgs.mariadb; diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix index f7f39668c86..416a20aa7fe 100644 --- a/nixos/tests/wordpress.nix +++ b/nixos/tests/wordpress.nix @@ -15,15 +15,12 @@ import ./make-test-python.nix ({ pkgs, ... }: services.httpd.adminAddr = "webmaster@site.local"; services.httpd.logPerVirtualHost = true; - services.wordpress = { - # Test support for old interface + services.wordpress.sites = { "site1.local" = { database.tablePrefix = "site1_"; }; - sites = { - "site2.local" = { - database.tablePrefix = "site2_"; - }; + "site2.local" = { + database.tablePrefix = "site2_"; }; }; -- cgit 1.4.1 From 8b86f9816dd46a8b29088ddd91b0a590829f282a Mon Sep 17 00:00:00 2001 From: Patrick Hilhorst Date: Sun, 23 Jan 2022 16:42:27 +0100 Subject: handbrake: convert nixos test to runCommand --- nixos/tests/all-tests.nix | 1 - nixos/tests/handbrake.nix | 33 --------------------------- pkgs/applications/video/handbrake/default.nix | 28 +++++++++++++++++++---- 3 files changed, 24 insertions(+), 38 deletions(-) delete mode 100644 nixos/tests/handbrake.nix (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index b2f223e7ccd..b22dbc32959 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -174,7 +174,6 @@ in hadoop.all = handleTestOn [ "x86_64-linux" ] ./hadoop/hadoop.nix {}; hadoop.hdfs = handleTestOn [ "x86_64-linux" ] ./hadoop/hdfs.nix {}; hadoop.yarn = handleTestOn [ "x86_64-linux" ] ./hadoop/yarn.nix {}; - handbrake = handleTestOn ["x86_64-linux"] ./handbrake.nix {}; haproxy = handleTest ./haproxy.nix {}; hardened = handleTest ./hardened.nix {}; hedgedoc = handleTest ./hedgedoc.nix {}; diff --git a/nixos/tests/handbrake.nix b/nixos/tests/handbrake.nix deleted file mode 100644 index d2d41b372be..00000000000 --- a/nixos/tests/handbrake.nix +++ /dev/null @@ -1,33 +0,0 @@ -import ./make-test-python.nix ({ pkgs, ... }: - -let - # Download Big Buck Bunny example, licensed under CC Attribution 3.0. - testMkv = pkgs.fetchurl { - url = "https://github.com/Matroska-Org/matroska-test-files/blob/cf0792be144ac470c4b8052cfe19bb691993e3a2/test_files/test1.mkv?raw=true"; - sha256 = "1hfxbbgxwfkzv85pvpvx55a72qsd0hxjbm9hkl5r3590zw4s75h9"; - name = "test1.mkv"; - }; - -in -{ - name = "handbrake"; - - meta = { - maintainers = with pkgs.lib.maintainers; [ ]; - }; - - machine = { pkgs, ... }: { - environment.systemPackages = with pkgs; [ handbrake ]; - }; - - testScript = '' - # Test MP4 and MKV transcoding. Since this is a short clip, transcoding typically - # only takes a few seconds. - start_all() - - machine.succeed("HandBrakeCLI -i ${testMkv} -o test.mp4 -e x264 -q 20 -B 160") - machine.succeed("test -e test.mp4") - machine.succeed("HandBrakeCLI -i ${testMkv} -o test.mkv -e x264 -q 20 -B 160") - machine.succeed("test -e test.mkv") - ''; -}) diff --git a/pkgs/applications/video/handbrake/default.nix b/pkgs/applications/video/handbrake/default.nix index e7ac4881b56..841604399ed 100644 --- a/pkgs/applications/video/handbrake/default.nix +++ b/pkgs/applications/video/handbrake/default.nix @@ -10,7 +10,10 @@ { stdenv , lib , fetchFromGitHub -, nixosTests + # For tests +, testVersion +, runCommand +, fetchurl # Main build tools , pkg-config , autoconf @@ -103,7 +106,7 @@ let inherit (lib) optional optionals optionalString versions; in -stdenv.mkDerivation rec { +let self = stdenv.mkDerivation rec { pname = "handbrake"; inherit version src; @@ -211,7 +214,23 @@ stdenv.mkDerivation rec { makeFlags = [ "--directory=build" ]; passthru.tests = { - basic-conversion = nixosTests.handbrake; + basic-conversion = + let + # Big Buck Bunny example, licensed under CC Attribution 3.0. + testMkv = fetchurl { + url = "https://github.com/Matroska-Org/matroska-test-files/blob/cf0792be144ac470c4b8052cfe19bb691993e3a2/test_files/test1.mkv?raw=true"; + sha256 = "1hfxbbgxwfkzv85pvpvx55a72qsd0hxjbm9hkl5r3590zw4s75h9"; + }; + in + runCommand "${pname}-${version}-basic-conversion" { nativeBuildInputs = [ self ]; } '' + mkdir -p $out + cd $out + HandBrakeCLI -i ${testMkv} -o test.mp4 -e x264 -q 20 -B 160 + test -e test.mp4 + HandBrakeCLI -i ${testMkv} -o test.mkv -e x264 -q 20 -B 160 + test -e test.mkv + ''; + version = testVersion { package = self; command = "HandBrakeCLI --version"; }; }; meta = with lib; { @@ -230,4 +249,5 @@ stdenv.mkDerivation rec { platforms = with platforms; unix; broken = stdenv.isDarwin && lib.versionOlder stdenv.hostPlatform.darwinMinVersion "10.13"; }; -} +}; +in self -- cgit 1.4.1 From 2e719d1cdab9e81750c19425a1f5c7678b5e73ad Mon Sep 17 00:00:00 2001 From: Michael Weiss Date: Thu, 23 Dec 2021 20:19:41 +0100 Subject: sway: 1.6.1 -> 1.7 Release notes: https://github.com/swaywm/sway/releases/tag/1.7 Notable (backward incompatible) changes: - The default terminal changed from Alacritty to foot Known issues: - `swaynag` will crash when Sway 1.6.1 is still running while the Nix package (and thus `swaynag`) is already updated to version 1.7. - The experimental Ozone/Wayland support of Electron apps will be broken for a while. Electron version 17 should work but the Chromium fixes haven't yet been backported to Electron version 16. NixOS module: programs.sway.extraPackages: The "alacritty" package was replaced with "foot". VM test: We switched from the OpenGL ES 2.0 renderer to Pixman. The terminal was also changed to foot but Alacritty is still used for the XWayland test (since foot doesn't support X11). Co-authored-by: Patrick Hilhorst --- nixos/modules/programs/sway.nix | 4 +- nixos/tests/sway.nix | 47 +++++++++++++++------- pkgs/applications/window-managers/sway/default.nix | 8 ++-- pkgs/top-level/all-packages.nix | 4 +- 4 files changed, 39 insertions(+), 24 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix index c64e01a20cb..bb9904d1956 100644 --- a/nixos/modules/programs/sway.nix +++ b/nixos/modules/programs/sway.nix @@ -90,10 +90,10 @@ in { extraPackages = mkOption { type = with types; listOf package; default = with pkgs; [ - swaylock swayidle alacritty dmenu + swaylock swayidle foot dmenu ]; defaultText = literalExpression '' - with pkgs; [ swaylock swayidle alacritty dmenu ]; + with pkgs; [ swaylock swayidle foot dmenu ]; ''; example = literalExpression '' with pkgs; [ diff --git a/nixos/tests/sway.nix b/nixos/tests/sway.nix index 3476ebab3e2..43b8c847304 100644 --- a/nixos/tests/sway.nix +++ b/nixos/tests/sway.nix @@ -1,4 +1,4 @@ -import ./make-test-python.nix ({ pkgs, lib, ...} : +import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "sway"; @@ -13,19 +13,38 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : environment = { # For glinfo and wayland-info: - systemPackages = with pkgs; [ mesa-demos wayland-utils ]; + systemPackages = with pkgs; [ mesa-demos wayland-utils alacritty ]; # Use a fixed SWAYSOCK path (for swaymsg): variables = { "SWAYSOCK" = "/tmp/sway-ipc.sock"; - "WLR_RENDERER_ALLOW_SOFTWARE" = "1"; + # TODO: Investigate if we can get hardware acceleration to work (via + # virtio-gpu and Virgil). We currently have to use the Pixman software + # renderer since the GLES2 renderer doesn't work inside the VM (even + # with WLR_RENDERER_ALLOW_SOFTWARE): + # "WLR_RENDERER_ALLOW_SOFTWARE" = "1"; + "WLR_RENDERER" = "pixman"; }; # For convenience: shellAliases = { - test-x11 = "glinfo | head -n 3 | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok"; + test-x11 = "glinfo | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok"; test-wayland = "wayland-info | tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok"; }; + + # To help with OCR: + etc."xdg/foot/foot.ini".text = lib.generators.toINI { } { + main = { + font = "inconsolata:size=14"; + }; + colors = rec { + foreground = "000000"; + background = "ffffff"; + regular2 = foreground; + }; + }; }; + fonts.fonts = [ pkgs.inconsolata ]; + # Automatically configure and start Sway when logging in on tty1: programs.bash.loginShellInit = '' if [ "$(tty)" = "/dev/tty1" ]; then @@ -61,7 +80,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : machine.wait_for_file("/run/user/1000/wayland-1") machine.wait_for_file("/tmp/sway-ipc.sock") - # Test XWayland: + # Test XWayland (foot does not support X): machine.succeed( "su - alice -c 'swaymsg exec WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY=invalid alacritty'" ) @@ -69,21 +88,22 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : machine.send_chars("test-x11\n") machine.wait_for_file("/tmp/test-x11-exit-ok") print(machine.succeed("cat /tmp/test-x11.out")) + machine.copy_from_vm("/tmp/test-x11.out") machine.screenshot("alacritty_glinfo") machine.succeed("pkill alacritty") - # Start a terminal (Alacritty) on workspace 3: + # Start a terminal (foot) on workspace 3: machine.send_key("alt-3") - machine.succeed( - "su - alice -c 'swaymsg exec WINIT_UNIX_BACKEND=wayland DISPLAY=invalid alacritty'" - ) + machine.sleep(3) + machine.send_key("alt-ret") machine.wait_for_text("alice@machine") machine.send_chars("test-wayland\n") machine.wait_for_file("/tmp/test-wayland-exit-ok") print(machine.succeed("cat /tmp/test-wayland.out")) - machine.screenshot("alacritty_wayland_info") + machine.copy_from_vm("/tmp/test-wayland.out") + machine.screenshot("foot_wayland_info") machine.send_key("alt-shift-q") - machine.wait_until_fails("pgrep alacritty") + machine.wait_until_fails("pgrep foot") # Test gpg-agent starting pinentry-gnome3 via D-Bus (tests if # $WAYLAND_DISPLAY is correctly imported into the D-Bus user env): @@ -104,9 +124,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : # Exit Sway and verify process exit status 0: machine.succeed("su - alice -c 'swaymsg exit || true'") machine.wait_until_fails("pgrep -x sway") - - # TODO: Sway currently segfaults after "swaymsg exit" but only in this VM test: - # machine # [ 104.090032] sway[921]: segfault at 3f800008 ip 00007f7dbdc25f10 sp 00007ffe282182f8 error 4 in libwayland-server.so.0.1.0[7f7dbdc1f000+8000] - # machine.wait_for_file("/tmp/sway-exit-ok") + machine.wait_for_file("/tmp/sway-exit-ok") ''; }) diff --git a/pkgs/applications/window-managers/sway/default.nix b/pkgs/applications/window-managers/sway/default.nix index ec08568f058..72ca71d5896 100644 --- a/pkgs/applications/window-managers/sway/default.nix +++ b/pkgs/applications/window-managers/sway/default.nix @@ -1,5 +1,5 @@ { lib, stdenv, fetchFromGitHub, substituteAll, swaybg -, meson, ninja, pkg-config, wayland-scanner, scdoc +, meson_0_60, ninja, pkg-config, wayland-scanner, scdoc , wayland, libxkbcommon, pcre, json_c, dbus, libevdev , pango, cairo, libinput, libcap, pam, gdk-pixbuf, librsvg , wlroots, wayland-protocols, libdrm @@ -12,13 +12,13 @@ stdenv.mkDerivation rec { pname = "sway-unwrapped"; - version = "1.6.1"; + version = "1.7"; src = fetchFromGitHub { owner = "swaywm"; repo = "sway"; rev = version; - sha256 = "0j4sdbsrlvky1agacc0pcz9bwmaxjmrapjnzscbd2i0cria2fc5j"; + sha256 = "0ss3l258blyf2d0lwd7pi7ga1fxfj8pxhag058k7cmjhs3y30y5l"; }; patches = [ @@ -43,7 +43,7 @@ stdenv.mkDerivation rec { ]; nativeBuildInputs = [ - meson ninja pkg-config wayland-scanner scdoc + meson_0_60 ninja pkg-config wayland-scanner scdoc ]; buildInputs = [ diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 0627a2fb200..a6e0b133cd0 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -26143,9 +26143,7 @@ with pkgs; inherit (xorg) xcbutilrenderutil; }; - sway-unwrapped = callPackage ../applications/window-managers/sway { - wlroots = wlroots_0_14; - }; + sway-unwrapped = callPackage ../applications/window-managers/sway { }; sway = callPackage ../applications/window-managers/sway/wrapper.nix { }; swaybg = callPackage ../applications/window-managers/sway/bg.nix { }; swayidle = callPackage ../applications/window-managers/sway/idle.nix { }; -- cgit 1.4.1