diff options
Diffstat (limited to 'nixos/tests')
88 files changed, 1579 insertions, 446 deletions
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index d62a4dd49fbf8..530447b997863 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -21,7 +21,7 @@ let if isAttrs val then if hasAttr "test" val then callTest val - else mapAttrs (n: s: discoverTests s) val + else mapAttrs (n: s: if n == "passthru" then s else discoverTests s) val else if isFunction val then # Tests based on make-test-python.nix will return the second lambda @@ -89,6 +89,7 @@ in { extra-python-packages = handleTest ./nixos-test-driver/extra-python-packages.nix {}; lib-extend = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./nixos-test-driver/lib-extend.nix {}; node-name = runTest ./nixos-test-driver/node-name.nix; + busybox = runTest ./nixos-test-driver/busybox.nix; }; # NixOS vm tests and non-vm unit tests @@ -111,6 +112,7 @@ in { anuko-time-tracker = handleTest ./anuko-time-tracker.nix {}; apcupsd = handleTest ./apcupsd.nix {}; apfs = runTest ./apfs.nix; + appliance-repart-image = runTest ./appliance-repart-image.nix; apparmor = handleTest ./apparmor.nix {}; atd = handleTest ./atd.nix {}; atop = handleTest ./atop.nix {}; @@ -138,6 +140,7 @@ in { borgbackup = handleTest ./borgbackup.nix {}; botamusique = handleTest ./botamusique.nix {}; bpf = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bpf.nix {}; + bpftune = handleTest ./bpftune.nix {}; breitbandmessung = handleTest ./breitbandmessung.nix {}; brscan5 = handleTest ./brscan5.nix {}; btrbk = handleTest ./btrbk.nix {}; @@ -164,6 +167,7 @@ in { cgit = handleTest ./cgit.nix {}; charliecloud = handleTest ./charliecloud.nix {}; chromium = (handleTestOn ["aarch64-linux" "x86_64-linux"] ./chromium.nix {}).stable or {}; + chrony = handleTestOn ["aarch64-linux" "x86_64-linux"] ./chrony.nix {}; chrony-ptp = handleTestOn ["aarch64-linux" "x86_64-linux"] ./chrony-ptp.nix {}; cinnamon = handleTest ./cinnamon.nix {}; cjdns = handleTest ./cjdns.nix {}; @@ -202,6 +206,7 @@ in { couchdb = handleTest ./couchdb.nix {}; cri-o = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cri-o.nix {}; cups-pdf = handleTest ./cups-pdf.nix {}; + curl-impersonate = handleTest ./curl-impersonate.nix {}; custom-ca = handleTest ./custom-ca.nix {}; croc = handleTest ./croc.nix {}; darling = handleTest ./darling.nix {}; @@ -213,7 +218,7 @@ in { disable-installer-tools = handleTest ./disable-installer-tools.nix {}; discourse = handleTest ./discourse.nix {}; dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {}; - dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {}; + dnscrypt-wrapper = runTestOn ["x86_64-linux"] ./dnscrypt-wrapper; dnsdist = handleTest ./dnsdist.nix {}; doas = handleTest ./doas.nix {}; docker = handleTestOn ["aarch64-linux" "x86_64-linux"] ./docker.nix {}; @@ -248,6 +253,7 @@ in { envoy = handleTest ./envoy.nix {}; ergo = handleTest ./ergo.nix {}; ergochat = handleTest ./ergochat.nix {}; + eris-server = handleTest ./eris-server.nix {}; esphome = handleTest ./esphome.nix {}; etc = pkgs.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; }; activation = pkgs.callPackage ../modules/system/activation/test.nix { }; @@ -256,6 +262,7 @@ in { etebase-server = handleTest ./etebase-server.nix {}; etesync-dav = handleTest ./etesync-dav.nix {}; evcc = handleTest ./evcc.nix {}; + fail2ban = handleTest ./fail2ban.nix { }; fakeroute = handleTest ./fakeroute.nix {}; fancontrol = handleTest ./fancontrol.nix {}; fcitx5 = handleTest ./fcitx5 {}; @@ -266,6 +273,7 @@ in { firefox-devedition = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-devedition; }; firefox-esr = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job firefox-esr-102 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-102; }; + firefox-esr-115 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-115; }; firejail = handleTest ./firejail.nix {}; firewall = handleTest ./firewall.nix { nftables = false; }; firewall-nftables = handleTest ./firewall.nix { nftables = true; }; @@ -335,6 +343,7 @@ in { hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; }; hedgedoc = handleTest ./hedgedoc.nix {}; herbstluftwm = handleTest ./herbstluftwm.nix {}; + homepage-dashboard = handleTest ./homepage-dashboard.nix {}; installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {}); invidious = handleTest ./invidious.nix {}; oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {}; @@ -434,6 +443,7 @@ in { lxd = handleTest ./lxd.nix {}; lxd-nftables = handleTest ./lxd-nftables.nix {}; lxd-image-server = handleTest ./lxd-image-server.nix {}; + lxd-ui = handleTest ./lxd-ui.nix {}; #logstash = handleTest ./logstash.nix {}; lorri = handleTest ./lorri/default.nix {}; maddy = discoverTests (import ./maddy { inherit handleTest; }); @@ -527,11 +537,12 @@ in { nginx-http3 = handleTest ./nginx-http3.nix {}; nginx-modsecurity = handleTest ./nginx-modsecurity.nix {}; nginx-njs = handleTest ./nginx-njs.nix {}; + nginx-proxyprotocol = handleTest ./nginx-proxyprotocol {}; nginx-pubhtml = handleTest ./nginx-pubhtml.nix {}; nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {}; nginx-sso = handleTest ./nginx-sso.nix {}; + nginx-status-page = handleTest ./nginx-status-page.nix {}; nginx-variants = handleTest ./nginx-variants.nix {}; - nginx-proxyprotocol = handleTest ./nginx-proxyprotocol {}; nifi = handleTestOn ["x86_64-linux"] ./web-apps/nifi.nix {}; nitter = handleTest ./nitter.nix {}; nix-ld = handleTest ./nix-ld.nix {}; @@ -572,6 +583,7 @@ in { openvscode-server = handleTest ./openvscode-server.nix {}; orangefs = handleTest ./orangefs.nix {}; os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {}; + osquery = handleTestOn ["x86_64-linux"] ./osquery.nix {}; osrm-backend = handleTest ./osrm-backend.nix {}; overlayfs = handleTest ./overlayfs.nix {}; pacemaker = handleTest ./pacemaker.nix {}; @@ -593,6 +605,7 @@ in { peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {}; peroxide = handleTest ./peroxide.nix {}; pgadmin4 = handleTest ./pgadmin4.nix {}; + pgbouncer = handleTest ./pgbouncer.nix {}; pgjwt = handleTest ./pgjwt.nix {}; pgmanage = handleTest ./pgmanage.nix {}; phosh = handleTest ./phosh.nix {}; @@ -722,6 +735,7 @@ in { switchTest = handleTest ./switch-test.nix {}; sympa = handleTest ./sympa.nix {}; syncthing = handleTest ./syncthing.nix {}; + syncthing-no-settings = handleTest ./syncthing-no-settings.nix {}; syncthing-init = handleTest ./syncthing-init.nix {}; syncthing-relay = handleTest ./syncthing-relay.nix {}; systemd = handleTest ./systemd.nix {}; @@ -762,6 +776,7 @@ in { systemd-portabled = handleTest ./systemd-portabled.nix {}; systemd-repart = handleTest ./systemd-repart.nix {}; systemd-shutdown = handleTest ./systemd-shutdown.nix {}; + systemd-sysupdate = runTest ./systemd-sysupdate.nix; systemd-timesyncd = handleTest ./systemd-timesyncd.nix {}; systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {}; systemd-misc = handleTest ./systemd-misc.nix {}; @@ -799,6 +814,8 @@ in { tuptime = handleTest ./tuptime.nix {}; turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {}; tuxguitar = handleTest ./tuxguitar.nix {}; + twingate = runTest ./twingate.nix; + typesense = handleTest ./typesense.nix {}; ucarp = handleTest ./ucarp.nix {}; udisks2 = handleTest ./udisks2.nix {}; ulogd = handleTest ./ulogd.nix {}; diff --git a/nixos/tests/appliance-repart-image.nix b/nixos/tests/appliance-repart-image.nix new file mode 100644 index 0000000000000..3f256db846214 --- /dev/null +++ b/nixos/tests/appliance-repart-image.nix @@ -0,0 +1,116 @@ +# Tests building and running a GUID Partition Table (GPT) appliance image. +# "Appliance" here means that the image does not contain the normal NixOS +# infrastructure of a system profile and cannot be re-built via +# `nixos-rebuild`. + +{ lib, ... }: + +let + rootPartitionLabel = "root"; + + bootLoaderConfigPath = "/loader/entries/nixos.conf"; + kernelPath = "/EFI/nixos/kernel.efi"; + initrdPath = "/EFI/nixos/initrd.efi"; +in +{ + name = "appliance-gpt-image"; + + meta.maintainers = with lib.maintainers; [ nikstur ]; + + nodes.machine = { config, lib, pkgs, ... }: { + + imports = [ ../modules/image/repart.nix ]; + + virtualisation.directBoot.enable = false; + virtualisation.mountHostNixStore = false; + virtualisation.useEFIBoot = true; + + # Disable boot loaders because we install one "manually". + # TODO(raitobezarius): revisit this when #244907 lands + boot.loader.grub.enable = false; + + virtualisation.fileSystems = lib.mkForce { + "/" = { + device = "/dev/disk/by-partlabel/${rootPartitionLabel}"; + fsType = "ext4"; + }; + }; + + image.repart = { + name = "appliance-gpt-image"; + partitions = { + "esp" = { + contents = + let + efiArch = config.nixpkgs.hostPlatform.efiArch; + in + { + "/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source = + "${pkgs.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi"; + + # TODO: create an abstraction for Boot Loader Specification (BLS) entries. + "${bootLoaderConfigPath}".source = pkgs.writeText "nixos.conf" '' + title NixOS + linux ${kernelPath} + initrd ${initrdPath} + options init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} + ''; + + "${kernelPath}".source = + "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}"; + + "${initrdPath}".source = + "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; + }; + repartConfig = { + Type = "esp"; + Format = "vfat"; + # Minimize = "guess" seems to not work very vell for vfat + # partitons. It's better to set a sensible default instead. The + # aarch64 kernel seems to generally be a little bigger than the + # x86_64 kernel. To stay on the safe side, leave some more slack + # for every platform other than x86_64. + SizeMinBytes = if config.nixpkgs.hostPlatform.isx86_64 then "64M" else "96M"; + }; + }; + "root" = { + storePaths = [ config.system.build.toplevel ]; + repartConfig = { + Type = "root"; + Format = config.fileSystems."/".fsType; + Label = rootPartitionLabel; + Minimize = "guess"; + }; + }; + }; + }; + }; + + testScript = { nodes, ... }: '' + import os + import subprocess + import tempfile + + tmp_disk_image = tempfile.NamedTemporaryFile() + + subprocess.run([ + "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", + "create", + "-f", + "qcow2", + "-b", + "${nodes.machine.system.build.image}/image.raw", + "-F", + "raw", + tmp_disk_image.name, + ]) + + # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. + os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name + + bootctl_status = machine.succeed("bootctl status") + assert "${bootLoaderConfigPath}" in bootctl_status + assert "${kernelPath}" in bootctl_status + assert "${initrdPath}" in bootctl_status + ''; +} diff --git a/nixos/tests/atuin.nix b/nixos/tests/atuin.nix index 9f23a3cf6713f..3164c83c683dc 100644 --- a/nixos/tests/atuin.nix +++ b/nixos/tests/atuin.nix @@ -14,6 +14,8 @@ in server = { ... }: { + services.postgresql.enable = true; + services.atuin = { enable = true; port = testPort; diff --git a/nixos/tests/binary-cache.nix b/nixos/tests/binary-cache.nix index 0809e59e5a115..bc1c6fb9a2671 100644 --- a/nixos/tests/binary-cache.nix +++ b/nixos/tests/binary-cache.nix @@ -1,16 +1,14 @@ -import ./make-test-python.nix ({ lib, ... }: - -with lib; +import ./make-test-python.nix ({ lib, pkgs, ... }: { name = "binary-cache"; - meta.maintainers = with maintainers; [ thomasjm ]; + meta.maintainers = with lib.maintainers; [ thomasjm ]; nodes.machine = { pkgs, ... }: { imports = [ ../modules/installer/cd-dvd/channel.nix ]; - environment.systemPackages = with pkgs; [python3]; - system.extraDependencies = with pkgs; [hello.inputDerivation]; + environment.systemPackages = [ pkgs.python3 ]; + system.extraDependencies = [ pkgs.hello.inputDerivation ]; nix.extraOptions = '' experimental-features = nix-command ''; diff --git a/nixos/tests/bpftune.nix b/nixos/tests/bpftune.nix new file mode 100644 index 0000000000000..c17bbcd110920 --- /dev/null +++ b/nixos/tests/bpftune.nix @@ -0,0 +1,20 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + + name = "bpftune"; + + meta = { + maintainers = with lib.maintainers; [ nickcao ]; + }; + + nodes = { + machine = { pkgs, ... }: { + services.bpftune.enable = true; + }; + }; + + testScript = '' + machine.wait_for_unit("bpftune.service") + machine.wait_for_console_text("bpftune works") + ''; + +}) diff --git a/nixos/tests/budgie.nix b/nixos/tests/budgie.nix index b1b0e618c884c..34ee1e303de9f 100644 --- a/nixos/tests/budgie.nix +++ b/nixos/tests/budgie.nix @@ -32,7 +32,15 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { in '' with subtest("Wait for login"): - machine.wait_for_x() + # wait_for_x() checks graphical-session.target, which is expected to be + # inactive on Budgie before #228946 (i.e. systemd managed gnome-session) is + # done on upstream. + # https://github.com/BuddiesOfBudgie/budgie-desktop/blob/v10.7.2/src/session/budgie-desktop.in#L16 + # + # Previously this was unconditionally touched by xsessionWrapper but was + # changed in #233981 (we have Budgie:GNOME in XDG_CURRENT_DESKTOP). + # machine.wait_for_x() + machine.wait_until_succeeds('journalctl -t gnome-session-binary --grep "Entering running state"') machine.wait_for_file("${user.home}/.Xauthority") machine.succeed("xauth merge ${user.home}/.Xauthority") diff --git a/nixos/tests/buildkite-agents.nix b/nixos/tests/buildkite-agents.nix index 2c5593323e873..a5abfdb5e2e5d 100644 --- a/nixos/tests/buildkite-agents.nix +++ b/nixos/tests/buildkite-agents.nix @@ -1,10 +1,8 @@ -import ./make-test-python.nix ({ pkgs, ... }: +import ./make-test-python.nix ({ lib, pkgs, ... }: { name = "buildkite-agent"; - meta = with pkgs.lib.maintainers; { - maintainers = [ flokli ]; - }; + meta.maintainers = with lib.maintainers; [ flokli ]; nodes.machine = { pkgs, ... }: { services.buildkite-agents = { diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix index c4abd33d0395d..238091ec606f5 100644 --- a/nixos/tests/caddy.nix +++ b/nixos/tests/caddy.nix @@ -20,22 +20,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { } } ''; - - specialisation.etag.configuration = { - services.caddy.extraConfig = lib.mkForce '' - http://localhost { - encode gzip - - file_server - root * ${ - pkgs.runCommand "testdir2" {} '' - mkdir "$out" - echo changed > "$out/example.html" - '' - } - } - ''; - }; + services.caddy.enableReload = true; specialisation.config-reload.configuration = { services.caddy.extraConfig = '' @@ -54,9 +39,8 @@ import ./make-test-python.nix ({ pkgs, ... }: { testScript = { nodes, ... }: let - etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag"; - justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload"; - multipleConfigs = "${nodes.webserver.config.system.build.toplevel}/specialisation/multiple-configs"; + justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/config-reload"; + multipleConfigs = "${nodes.webserver.system.build.toplevel}/specialisation/multiple-configs"; in '' url = "http://localhost/example.html" @@ -64,38 +48,13 @@ import ./make-test-python.nix ({ pkgs, ... }: { webserver.wait_for_open_port(80) - def check_etag(url): - etag = webserver.succeed( - "curl --fail -v '{}' 2>&1 | sed -n -e \"s/^< [Ee][Tt][Aa][Gg]: *//p\"".format( - url - ) - ) - etag = etag.replace("\r\n", " ") - http_code = webserver.succeed( - "curl --fail --silent --show-error -o /dev/null -w \"%{{http_code}}\" --head -H 'If-None-Match: {}' {}".format( - etag, url - ) - ) - assert int(http_code) == 304, "HTTP code is {}, expected 304".format(http_code) - return etag - - - with subtest("check ETag if serving Nix store paths"): - old_etag = check_etag(url) - webserver.succeed( - "${etagSystem}/bin/switch-to-configuration test >&2" - ) - webserver.sleep(1) - new_etag = check_etag(url) - assert old_etag != new_etag, "Old ETag {} is the same as {}".format( - old_etag, new_etag - ) - with subtest("config is reloaded on nixos-rebuild switch"): webserver.succeed( "${justReloadSystem}/bin/switch-to-configuration test >&2" ) webserver.wait_for_open_port(8080) + webserver.fail("journalctl -u caddy | grep -q -i stopped") + webserver.succeed("journalctl -u caddy | grep -q -i reloaded") with subtest("multiple configs are correctly merged"): webserver.succeed( diff --git a/nixos/tests/cage.nix b/nixos/tests/cage.nix index db1b7854673df..3b49185124f3e 100644 --- a/nixos/tests/cage.nix +++ b/nixos/tests/cage.nix @@ -11,7 +11,7 @@ import ./make-test-python.nix ({ pkgs, ...} : { imports = [ ./common/user-account.nix ]; - fonts.fonts = with pkgs; [ dejavu_fonts ]; + fonts.packages = with pkgs; [ dejavu_fonts ]; services.cage = { enable = true; diff --git a/nixos/tests/chrony.nix b/nixos/tests/chrony.nix new file mode 100644 index 0000000000000..578b1e32d50c9 --- /dev/null +++ b/nixos/tests/chrony.nix @@ -0,0 +1,31 @@ +import ./make-test-python.nix ({ lib, ... }: +{ + name = "chrony"; + + meta = { + maintainers = with lib.maintainers; [ fpletz ]; + }; + + nodes = { + default = { + services.chrony.enable = true; + }; + graphene-hardened = { + services.chrony.enable = true; + services.chrony.enableMemoryLocking = true; + environment.memoryAllocator.provider = "graphene-hardened"; + # dhcpcd privsep is incompatible with graphene-hardened + networking.useNetworkd = true; + }; + }; + + testScript = {nodes, ...} : let + graphene-hardened = nodes.graphene-hardened.system.build.toplevel; + in '' + default.start() + default.wait_for_unit('multi-user.target') + default.succeed('systemctl is-active chronyd.service') + default.succeed('${graphene-hardened}/bin/switch-to-configuration test') + default.succeed('systemctl is-active chronyd.service') + ''; +}) diff --git a/nixos/tests/common/gpg-keyring.nix b/nixos/tests/common/gpg-keyring.nix new file mode 100644 index 0000000000000..fb8d07b1183e0 --- /dev/null +++ b/nixos/tests/common/gpg-keyring.nix @@ -0,0 +1,21 @@ +{ pkgs, ... }: + +pkgs.runCommand "gpg-keyring" { nativeBuildInputs = [ pkgs.gnupg ]; } '' + mkdir -p $out + export GNUPGHOME=$out + cat > foo <<EOF + %echo Generating a basic OpenPGP key + %no-protection + Key-Type: EdDSA + Key-Curve: ed25519 + Name-Real: Bob Foobar + Name-Email: bob@foo.bar + Expire-Date: 0 + # Do a commit here, so that we can later print "done" + %commit + %echo done + EOF + gpg --batch --generate-key foo + rm $out/S.gpg-agent $out/S.gpg-agent.* + gpg --export bob@foo.bar -a > $out/pubkey.gpg +'' diff --git a/nixos/tests/common/resolver.nix b/nixos/tests/common/resolver.nix index 3ddf730668c4f..609058a7374a5 100644 --- a/nixos/tests/common/resolver.nix +++ b/nixos/tests/common/resolver.nix @@ -63,7 +63,7 @@ matched = builtins.match "[ \t]+(${reHost})(.*)" str; continue = lib.singleton (lib.head matched) ++ matchAliases (lib.last matched); - in if matched == null then [] else continue; + in lib.optional (matched != null) continue; matchLine = str: let result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str; diff --git a/nixos/tests/cups-pdf.nix b/nixos/tests/cups-pdf.nix index 957b0296a755b..5602193b0408a 100644 --- a/nixos/tests/cups-pdf.nix +++ b/nixos/tests/cups-pdf.nix @@ -4,7 +4,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: { nodes.machine = { pkgs, ... }: { imports = [ ./common/user-account.nix ]; environment.systemPackages = [ pkgs.poppler_utils ]; - fonts.fonts = [ pkgs.dejavu_fonts ]; # yields more OCR-able pdf + fonts.packages = [ pkgs.dejavu_fonts ]; # yields more OCR-able pdf services.printing.cups-pdf.enable = true; services.printing.cups-pdf.instances = { opt = {}; diff --git a/nixos/tests/curl-impersonate.nix b/nixos/tests/curl-impersonate.nix new file mode 100644 index 0000000000000..7954e9e5584c4 --- /dev/null +++ b/nixos/tests/curl-impersonate.nix @@ -0,0 +1,157 @@ +/* + Test suite for curl-impersonate + + Abstract: + Uses the test suite from the curl-impersonate source repo which: + + 1. Performs requests with libcurl and captures the TLS client-hello + packets with tcpdump to compare against known-good signatures + 2. Spins up an nghttpd2 server to test client HTTP/2 headers against + known-good headers + + See https://github.com/lwthiker/curl-impersonate/tree/main/tests/signatures + for details. + + Notes: + - We need to have our own web server running because the tests expect to be able + to hit domains like wikipedia.org and the sandbox has no internet + - We need to be able to do (verifying) TLS handshakes without internet access. + We do that by creating a trusted CA and issuing a cert that includes + all of the test domains as subject-alternative names and then spoofs the + hostnames in /etc/hosts. +*/ + +import ./make-test-python.nix ({ pkgs, lib, ... }: let + # Update with domains in TestImpersonate.TEST_URLS if needed from: + # https://github.com/lwthiker/curl-impersonate/blob/main/tests/test_impersonate.py + domains = [ + "www.wikimedia.org" + "www.wikipedia.org" + "www.mozilla.org" + "www.apache.org" + "www.kernel.org" + "git-scm.com" + ]; + + tls-certs = let + # Configure CA with X.509 v3 extensions that would be trusted by curl + ca-cert-conf = pkgs.writeText "curl-impersonate-ca.cnf" '' + basicConstraints = critical, CA:TRUE + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid:always, issuer:always + keyUsage = critical, cRLSign, digitalSignature, keyCertSign + ''; + + # Configure leaf certificate with X.509 v3 extensions that would be trusted + # by curl and set subject-alternative names for test domains + tls-cert-conf = pkgs.writeText "curl-impersonate-tls.cnf" '' + basicConstraints = critical, CA:FALSE + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid:always, issuer:always + keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement + extendedKeyUsage = critical, serverAuth + subjectAltName = @alt_names + + [alt_names] + ${lib.concatStringsSep "\n" (lib.imap0 (idx: domain: "DNS.${toString idx} = ${domain}") domains)} + ''; + in pkgs.runCommand "curl-impersonate-test-certs" { + nativeBuildInputs = [ pkgs.openssl ]; + } '' + # create CA certificate and key + openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca-csr.pem -nodes -subj '/CN=curl-impersonate-ca.nixos.test' + openssl x509 -req -sha512 -in ca-csr.pem -key ca-key.pem -out ca.pem -extfile ${ca-cert-conf} -days 36500 + openssl x509 -in ca.pem -text + + # create server certificate and key + openssl req -newkey rsa:4096 -keyout key.pem -out csr.pem -nodes -subj '/CN=curl-impersonate.nixos.test' + openssl x509 -req -sha512 -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile ${tls-cert-conf} -days 36500 + openssl x509 -in cert.pem -text + + # output CA cert and server cert and key + mkdir -p $out + cp key.pem cert.pem ca.pem $out + ''; + + # Test script + curl-impersonate-test = let + # Build miniature libcurl client used by test driver + minicurl = pkgs.runCommandCC "minicurl" { + buildInputs = [ pkgs.curl ]; + } '' + mkdir -p $out/bin + $CC -Wall -Werror -o $out/bin/minicurl ${pkgs.curl-impersonate.src}/tests/minicurl.c `curl-config --libs` + ''; + in pkgs.writeShellScript "curl-impersonate-test" '' + set -euxo pipefail + + # Test driver requirements + export PATH="${with pkgs; lib.makeBinPath [ + bash + coreutils + python3Packages.pytest + nghttp2 + tcpdump + ]}" + export PYTHONPATH="${with pkgs.python3Packages; makePythonPath [ + pyyaml + pytest-asyncio + dpkt + ]}" + + # Prepare test root prefix + mkdir -p usr/{bin,lib} + cp -rs ${pkgs.curl-impersonate}/* ${minicurl}/* usr/ + + cp -r ${pkgs.curl-impersonate.src}/tests ./ + + # Run tests + cd tests + pytest . --install-dir ../usr --capture-interface eth1 + ''; +in { + name = "curl-impersonate"; + + meta = with lib.maintainers; { + maintainers = [ lilyinstarlight ]; + }; + + nodes = { + web = { nodes, pkgs, lib, config, ... }: { + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + services = { + nginx = { + enable = true; + virtualHosts."curl-impersonate.nixos.test" = { + default = true; + addSSL = true; + sslCertificate = "${tls-certs}/cert.pem"; + sslCertificateKey = "${tls-certs}/key.pem"; + }; + }; + }; + }; + + curl = { nodes, pkgs, lib, config, ... }: { + networking.extraHosts = lib.concatStringsSep "\n" (map (domain: "${nodes.web.networking.primaryIPAddress} ${domain}") domains); + + security.pki.certificateFiles = [ "${tls-certs}/ca.pem" ]; + }; + }; + + testScript = { nodes, ... }: '' + start_all() + + with subtest("Wait for network"): + web.wait_for_unit("network-online.target") + curl.wait_for_unit("network-online.target") + + with subtest("Wait for web server"): + web.wait_for_unit("nginx.service") + web.wait_for_open_port(443) + + with subtest("Run curl-impersonate tests"): + curl.succeed("${curl-impersonate-test}") + ''; +}) diff --git a/nixos/tests/deepin.nix b/nixos/tests/deepin.nix index acec27ca1c42a..7b2e2430f31c7 100644 --- a/nixos/tests/deepin.nix +++ b/nixos/tests/deepin.nix @@ -1,9 +1,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "deepin"; - meta = with lib; { - maintainers = teams.deepin.members; - }; + meta.maintainers = lib.teams.deepin.members; nodes.machine = { ... }: { imports = [ diff --git a/nixos/tests/dnscrypt-wrapper/default.nix b/nixos/tests/dnscrypt-wrapper/default.nix index 1bdd064e1130c..1c05376e097b3 100644 --- a/nixos/tests/dnscrypt-wrapper/default.nix +++ b/nixos/tests/dnscrypt-wrapper/default.nix @@ -1,4 +1,6 @@ -import ../make-test-python.nix ({ pkgs, ... }: { +{ lib, pkgs, ... }: + +{ name = "dnscrypt-wrapper"; meta = with pkgs.lib.maintainers; { maintainers = [ rnhmjoj ]; @@ -50,23 +52,23 @@ import ../make-test-python.nix ({ pkgs, ... }: { server.wait_for_unit("dnscrypt-wrapper") server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.key") server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.crt") + almost_expiration = server.succeed("date --date '4days 23 hours 56min'").strip() with subtest("The client can connect to the server"): server.wait_for_unit("tinydns") client.wait_for_unit("dnscrypt-proxy2") - assert "1.2.3.4" in client.succeed( + assert "1.2.3.4" in client.wait_until_succeeds( "host it.works" ), "The IP address of 'it.works' does not match 1.2.3.4" with subtest("The server rotates the ephemeral keys"): # advance time by a little less than 5 days - server.succeed("date -s \"$(date --date '4 days 6 hours')\"") - client.succeed("date -s \"$(date --date '4 days 6 hours')\"") + server.succeed(f"date -s '{almost_expiration}'") + client.succeed(f"date -s '{almost_expiration}'") server.wait_for_file("/var/lib/dnscrypt-wrapper/oldkeys") with subtest("The client can still connect to the server"): server.wait_for_unit("dnscrypt-wrapper") client.succeed("host it.works") ''; -}) - +} diff --git a/nixos/tests/eris-server.nix b/nixos/tests/eris-server.nix new file mode 100644 index 0000000000000..a50db3afebf5f --- /dev/null +++ b/nixos/tests/eris-server.nix @@ -0,0 +1,23 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "eris-server"; + meta.maintainers = with lib.maintainers; [ ehmry ]; + + nodes.server = { + environment.systemPackages = [ pkgs.eris-go pkgs.nim.pkgs.eris ]; + services.eris-server = { + enable = true; + decode = true; + listenHttp = "[::1]:80"; + backends = [ "badger+file:///var/cache/eris.badger?get&put" ]; + mountpoint = "/eris"; + }; + }; + + testScript = '' + start_all() + server.wait_for_unit("eris-server.service") + server.wait_for_open_port(5683) + server.wait_for_open_port(80) + server.succeed("eriscmd get http://[::1] $(echo 'Hail ERIS!' | eriscmd put coap+tcp://[::1]:5683)") + ''; +}) diff --git a/nixos/tests/fail2ban.nix b/nixos/tests/fail2ban.nix new file mode 100644 index 0000000000000..c3708575b702b --- /dev/null +++ b/nixos/tests/fail2ban.nix @@ -0,0 +1,18 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "fail2ban"; + + nodes.machine = _: { + services.fail2ban = { + enable = true; + bantime-increment.enable = true; + }; + + services.openssh.enable = true; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + + machine.wait_for_unit("fail2ban") + ''; +}) diff --git a/nixos/tests/fontconfig-default-fonts.nix b/nixos/tests/fontconfig-default-fonts.nix index 664afc9bf44c4..d8c6ea0f721b5 100644 --- a/nixos/tests/fontconfig-default-fonts.nix +++ b/nixos/tests/fontconfig-default-fonts.nix @@ -7,8 +7,8 @@ import ./make-test-python.nix ({ lib, ... }: ]; nodes.machine = { config, pkgs, ... }: { - fonts.enableDefaultFonts = true; # Background fonts - fonts.fonts = with pkgs; [ + fonts.enableDefaultPackages = true; # Background fonts + fonts.packages = with pkgs; [ noto-fonts-emoji cantarell-fonts twitter-color-emoji diff --git a/nixos/tests/freshrss-http-auth.nix b/nixos/tests/freshrss-http-auth.nix new file mode 100644 index 0000000000000..d0ec3da316896 --- /dev/null +++ b/nixos/tests/freshrss-http-auth.nix @@ -0,0 +1,20 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "freshrss"; + meta.maintainers = with lib.maintainers; [ mattchrist ]; + + nodes.machine = { pkgs, ... }: { + services.freshrss = { + enable = true; + baseUrl = "http://localhost"; + dataDir = "/srv/freshrss"; + authType = "http_auth"; + }; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + machine.wait_for_open_port(80) + response = machine.succeed("curl -vvv -s -H 'Host: freshrss' -H 'Remote-User: testuser' http://127.0.0.1:80/i/") + assert 'Account: testuser' in response, "http_auth method didn't work." + ''; +}) diff --git a/nixos/tests/gitea.nix b/nixos/tests/gitea.nix index 2285bb5a42529..b747659de8298 100644 --- a/nixos/tests/gitea.nix +++ b/nixos/tests/gitea.nix @@ -37,9 +37,25 @@ let package = giteaPackage; settings.service.DISABLE_REGISTRATION = true; settings."repository.signing".SIGNING_KEY = signingPrivateKeyId; + settings.actions.ENABLED = true; }; environment.systemPackages = [ giteaPackage pkgs.gnupg pkgs.jq ]; services.openssh.enable = true; + + specialisation.runner = { + inheritParentConfig = true; + + configuration.services.gitea-actions-runner.instances."test" = { + enable = true; + name = "ci"; + url = "http://localhost:3000"; + labels = [ + # don't require docker/podman + "native:host" + ]; + tokenFile = "/var/lib/gitea/runner_token"; + }; + }; }; client1 = { config, pkgs, ... }: { environment.systemPackages = [ pkgs.git ]; @@ -49,8 +65,9 @@ let }; }; - testScript = let + testScript = { nodes, ... }: let inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; + serverSystem = nodes.server.system.build.toplevel; in '' GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no" REPO = "gitea@server:test/repo" @@ -121,14 +138,18 @@ let client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git clone {REPO}") client2.succeed('test "$(cat repo/testfile | xargs echo -n)" = "hello world"') - server.succeed( + server.wait_until_succeeds( 'test "$(curl http://localhost:3000/api/v1/repos/test/repo/commits ' + '-H "Accept: application/json" | jq length)" = "1"' ) - client1.shutdown() - client2.shutdown() - server.shutdown() + with subtest("Testing runner registration"): + server.succeed( + "su -l gitea -c 'GITEA_WORK_DIR=/var/lib/gitea gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/gitea/runner_token" + ) + server.succeed("${serverSystem}/specialisation/runner/bin/switch-to-configuration test") + server.wait_for_unit("gitea-runner-test.service") + server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'") ''; }); in diff --git a/nixos/tests/gnome-flashback.nix b/nixos/tests/gnome-flashback.nix index 70569db797ec9..876d36477c1a9 100644 --- a/nixos/tests/gnome-flashback.nix +++ b/nixos/tests/gnome-flashback.nix @@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : { nodes.machine = { nodes, ... }: let - user = nodes.machine.config.users.users.alice; + user = nodes.machine.users.users.alice; in { imports = [ ./common/user-account.nix ]; @@ -27,12 +27,20 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : { }; testScript = { nodes, ... }: let - user = nodes.machine.config.users.users.alice; + user = nodes.machine.users.users.alice; uid = toString user.uid; xauthority = "/run/user/${uid}/gdm/Xauthority"; in '' with subtest("Login to GNOME Flashback with GDM"): - machine.wait_for_x() + # wait_for_x() checks graphical-session.target, which is expected to be + # inactive on gnome-flashback before #228946 (i.e. systemd managed + # gnome-session) is done. + # https://github.com/NixOS/nixpkgs/pull/208060 + # + # Previously this was unconditionally touched by xsessionWrapper but was + # changed in #233981 (we have GNOME-Flashback:GNOME in XDG_CURRENT_DESKTOP). + # machine.wait_for_x() + machine.wait_until_succeeds('journalctl -t gnome-session-binary --grep "Entering running state"') # Wait for alice to be logged in" machine.wait_for_unit("default.target", "${user.name}") machine.wait_for_file("${xauthority}") diff --git a/nixos/tests/hibernate.nix b/nixos/tests/hibernate.nix index cd8c436ee2b37..296aa9ba68b92 100644 --- a/nixos/tests/hibernate.nix +++ b/nixos/tests/hibernate.nix @@ -8,128 +8,48 @@ with import ../lib/testing-python.nix { inherit system pkgs; }; -let - # System configuration of the installed system, which is used for the actual - # hibernate testing. - installedConfig = with pkgs.lib; { - imports = [ - ../modules/testing/test-instrumentation.nix - ../modules/profiles/qemu-guest.nix - ../modules/profiles/minimal.nix - ]; - - hardware.enableAllFirmware = mkForce false; - documentation.nixos.enable = false; - boot.loader.grub.device = "/dev/vda"; - - systemd.services.backdoor.conflicts = [ "sleep.target" ]; - - powerManagement.resumeCommands = "systemctl --no-block restart backdoor.service"; - - fileSystems."/" = { - device = "/dev/vda2"; - fsType = "ext3"; - }; - swapDevices = mkOverride 0 [ { device = "/dev/vda1"; } ]; - boot.resumeDevice = mkIf systemdStage1 "/dev/vda1"; - boot.initrd.systemd = mkIf systemdStage1 { - enable = true; - emergencyAccess = true; - }; - }; - installedSystem = (import ../lib/eval-config.nix { - inherit system; - modules = [ installedConfig ]; - }).config.system.build.toplevel; -in makeTest { +makeTest { name = "hibernate"; nodes = { - # System configuration used for installing the installedConfig from above. machine = { config, lib, pkgs, ... }: { imports = [ - ../modules/profiles/installation-device.nix - ../modules/profiles/base.nix ./common/auto-format-root-device.nix ]; - nix.settings = { - substituters = lib.mkForce []; - hashed-mirrors = null; - connect-timeout = 1; - }; + systemd.services.backdoor.conflicts = [ "sleep.target" ]; + powerManagement.resumeCommands = "systemctl --no-block restart backdoor.service"; - virtualisation.diskSize = 8 * 1024; - virtualisation.emptyDiskImages = [ - # Small root disk for installer - 512 - ]; - virtualisation.rootDevice = "/dev/vdb"; + virtualisation.emptyDiskImages = [ (2 * config.virtualisation.memorySize) ]; + virtualisation.useNixStoreImage = true; + + swapDevices = lib.mkOverride 0 [ { device = "/dev/vdc"; options = [ "x-systemd.makefs" ]; } ]; + boot.resumeDevice = "/dev/vdc"; + boot.initrd.systemd.enable = systemdStage1; }; }; - # 9P doesn't support reconnection to virtio transport after a hibernation. - # Therefore, machine just hangs on any Nix store access. - # To avoid this, we install NixOS onto a temporary disk with everything we need - # included into the store. - - testScript = - '' - def create_named_machine(name): - machine = create_machine( - { - "qemuFlags": "-cpu max ${ - if system == "x86_64-linux" then "-m 1024" - else "-m 768 -enable-kvm -machine virt,gic-version=host"}", - "hdaInterface": "virtio", - "hda": "vm-state-machine/machine.qcow2", - "name": name, - } - ) - driver.machines.append(machine) - return machine - - - # Install NixOS - machine.start() - machine.succeed( - # Partition /dev/vda - "flock /dev/vda parted --script /dev/vda -- mklabel msdos" - + " mkpart primary linux-swap 1M 1024M" - + " mkpart primary ext2 1024M -1s", - "udevadm settle", - "mkfs.ext3 -L nixos /dev/vda2", - "mount LABEL=nixos /mnt", - "mkswap /dev/vda1 -L swap", - # Install onto /mnt - "nix-store --load-db < ${pkgs.closureInfo {rootPaths = [installedSystem];}}/registration", - "nixos-install --root /mnt --system ${installedSystem} --no-root-passwd --no-channel-copy >&2", - ) - machine.shutdown() - - # Start up - hibernate = create_named_machine("hibernate") - - # Drop in file that checks if we un-hibernated properly (and not booted fresh) - hibernate.succeed( - "mkdir /run/test", - "mount -t ramfs -o size=1m ramfs /run/test", - "echo not persisted to disk > /run/test/suspended", - ) - - # Hibernate machine - hibernate.execute("systemctl hibernate >&2 &", check_return=False) - hibernate.wait_for_shutdown() - - # Restore machine from hibernation, validate our ramfs file is there. - resume = create_named_machine("resume") - resume.start() - resume.succeed("grep 'not persisted to disk' /run/test/suspended") - - # Ensure we don't restore from hibernation when booting again - resume.crash() - resume.wait_for_unit("default.target") - resume.fail("grep 'not persisted to disk' /run/test/suspended") - ''; + testScript = '' + # Drop in file that checks if we un-hibernated properly (and not booted fresh) + machine.wait_for_unit("default.target") + machine.succeed( + "mkdir /run/test", + "mount -t ramfs -o size=1m ramfs /run/test", + "echo not persisted to disk > /run/test/suspended", + ) + + # Hibernate machine + machine.execute("systemctl hibernate >&2 &", check_return=False) + machine.wait_for_shutdown() + + # Restore machine from hibernation, validate our ramfs file is there. + machine.start() + machine.succeed("grep 'not persisted to disk' /run/test/suspended") + + # Ensure we don't restore from hibernation when booting again + machine.crash() + machine.wait_for_unit("default.target") + machine.fail("grep 'not persisted to disk' /run/test/suspended") + ''; } diff --git a/nixos/tests/homepage-dashboard.nix b/nixos/tests/homepage-dashboard.nix new file mode 100644 index 0000000000000..56e077f5ff6de --- /dev/null +++ b/nixos/tests/homepage-dashboard.nix @@ -0,0 +1,14 @@ +import ./make-test-python.nix ({ lib, ... }: { + name = "homepage-dashboard"; + meta.maintainers = with lib.maintainers; [ jnsgruk ]; + + nodes.machine = { pkgs, ... }: { + services.homepage-dashboard.enable = true; + }; + + testScript = '' + machine.wait_for_unit("homepage-dashboard.service") + machine.wait_for_open_port(8082) + machine.succeed("curl --fail http://localhost:8082/") + ''; +}) diff --git a/nixos/tests/initrd-network-ssh/default.nix b/nixos/tests/initrd-network-ssh/default.nix index 017de6882081d..17b6c21ff1e99 100644 --- a/nixos/tests/initrd-network-ssh/default.nix +++ b/nixos/tests/initrd-network-ssh/default.nix @@ -1,12 +1,10 @@ -import ../make-test-python.nix ({ lib, ... }: +import ../make-test-python.nix ({ lib, pkgs, ... }: { name = "initrd-network-ssh"; - meta = with lib.maintainers; { - maintainers = [ willibutz emily ]; - }; + meta.maintainers = with lib.maintainers; [ willibutz emily ]; - nodes = with lib; { + nodes = { server = { config, ... }: { @@ -17,7 +15,7 @@ import ../make-test-python.nix ({ lib, ... }: enable = true; ssh = { enable = true; - authorizedKeys = [ (readFile ./id_ed25519.pub) ]; + authorizedKeys = [ (lib.readFile ./id_ed25519.pub) ]; port = 22; hostKeys = [ ./ssh_host_ed25519_key ]; }; @@ -37,12 +35,12 @@ import ../make-test-python.nix ({ lib, ... }: { environment.etc = { knownHosts = { - text = concatStrings [ + text = lib.concatStrings [ "server," - "${toString (head (splitString " " ( - toString (elemAt (splitString "\n" config.networking.extraHosts) 2) + "${toString (lib.head (lib.splitString " " ( + toString (lib.elemAt (lib.splitString "\n" config.networking.extraHosts) 2) )))} " - "${readFile ./ssh_host_ed25519_key.pub}" + "${lib.readFile ./ssh_host_ed25519_key.pub}" ]; }; sshKey = { diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix index 78a6325a245ea..e87edb2007e93 100644 --- a/nixos/tests/installed-tests/default.nix +++ b/nixos/tests/installed-tests/default.nix @@ -107,5 +107,6 @@ in malcontent = callInstalledTest ./malcontent.nix {}; ostree = callInstalledTest ./ostree.nix {}; pipewire = callInstalledTest ./pipewire.nix {}; + upower = callInstalledTest ./upower.nix {}; xdg-desktop-portal = callInstalledTest ./xdg-desktop-portal.nix {}; } diff --git a/nixos/tests/installed-tests/upower.nix b/nixos/tests/installed-tests/upower.nix new file mode 100644 index 0000000000000..a8e777a555279 --- /dev/null +++ b/nixos/tests/installed-tests/upower.nix @@ -0,0 +1,9 @@ +{ pkgs, makeInstalledTest, ... }: + +makeInstalledTest { + tested = pkgs.upower; + + testConfig = { + services.upower.enable = true; + }; +} diff --git a/nixos/tests/installer-systemd-stage-1.nix b/nixos/tests/installer-systemd-stage-1.nix index 05fb2b2ae89c7..85155a6c682b3 100644 --- a/nixos/tests/installer-systemd-stage-1.nix +++ b/nixos/tests/installer-systemd-stage-1.nix @@ -28,7 +28,7 @@ simpleUefiGrubSpecialisation simpleUefiSystemdBoot stratisRoot - # swraid + swraid zfsroot ; diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index 4f4b91518845e..56ba85b76e6f5 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -11,16 +11,20 @@ let # The configuration to install. makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi - , extraConfig, forceGrubReinstallCount ? 0 + , extraConfig, forceGrubReinstallCount ? 0, flake ? false }: pkgs.writeText "configuration.nix" '' { config, lib, pkgs, modulesPath, ... }: { imports = [ ./hardware-configuration.nix - <nixpkgs/nixos/modules/testing/test-instrumentation.nix> + ${if flake + then "" # Still included, but via installer/flake.nix + else "<nixpkgs/nixos/modules/testing/test-instrumentation.nix>"} ]; + networking.hostName = "thatworked"; + documentation.enable = false; # To ensure that we can rebuild the grub configuration on the nixos-rebuild @@ -67,7 +71,7 @@ let # partitions and filesystems. testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi , grubIdentifier, preBootCommands, postBootCommands, extraConfig - , testSpecialisationConfig + , testSpecialisationConfig, testFlakeSwitch }: let iface = "virtio"; isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi); @@ -86,9 +90,14 @@ let qemu_flags = {"qemuFlags": assemble_qemu_flags()} + import os + + image_dir = machine.state_dir + disk_image = os.path.join(image_dir, "machine.qcow2") + hd_flags = { "hdaInterface": "${iface}", - "hda": "vm-state-machine/machine.qcow2", + "hda": disk_image, } ${optionalString isEfi '' hd_flags.update( @@ -232,6 +241,11 @@ let machine = create_machine_named("boot-after-rebuild-switch") ${preBootCommands} machine.wait_for_unit("network.target") + + # Sanity check, is it the configuration.nix we generated? + hostname = machine.succeed("hostname").strip() + assert hostname == "thatworked" + ${postBootCommands} machine.shutdown() @@ -272,6 +286,84 @@ let ${postBootCommands} machine.shutdown() + '' + + optionalString testFlakeSwitch '' + ${preBootCommands} + machine.start() + + with subtest("Configure system with flake"): + # TODO: evaluate as user? + machine.succeed(""" + mkdir /root/my-config + mv /etc/nixos/hardware-configuration.nix /root/my-config/ + mv /etc/nixos/secret /root/my-config/ + rm /etc/nixos/configuration.nix + """) + machine.copy_from_host_via_shell( + "${makeConfig { + inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig; + forceGrubReinstallCount = 1; + flake = true; + }}", + "/root/my-config/configuration.nix", + ) + machine.copy_from_host_via_shell( + "${./installer/flake.nix}", + "/root/my-config/flake.nix", + ) + machine.succeed(""" + # for some reason the image does not have `pkgs.path`, so + # we use readlink to find a Nixpkgs source. + pkgs=$(readlink -f /nix/var/nix/profiles/per-user/root/channels)/nixos + if ! [[ -e $pkgs/pkgs/top-level/default.nix ]]; then + echo 1>&2 "$pkgs does not seem to be a nixpkgs source. Please fix the test so that pkgs points to a nixpkgs source."; + exit 1; + fi + sed -e s^@nixpkgs@^$pkgs^ -i /root/my-config/flake.nix + """) + + with subtest("Switch to flake based config"): + machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz") + + ${postBootCommands} + machine.shutdown() + + ${preBootCommands} + machine.start() + + machine.wait_for_unit("multi-user.target") + + with subtest("nix-channel command is not available anymore"): + machine.succeed("! which nix-channel") + + # Note that the channel profile is still present on disk, but configured + # not to be used. + with subtest("builtins.nixPath is now empty"): + machine.succeed(""" + [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]] + """) + + with subtest("<nixpkgs> does not resolve"): + machine.succeed(""" + ! nix-instantiate '<nixpkgs>' --eval --expr + """) + + with subtest("Evaluate flake config in fresh env without nix-channel"): + machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz") + + with subtest("Evaluate flake config in fresh env without channel profiles"): + machine.succeed(""" + ( + exec 1>&2 + rm -v /root/.nix-channels + rm -vrf ~/.nix-defexpr + rm -vrf /nix/var/nix/profiles/per-user/root/channels* + ) + """) + machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz") + + ${postBootCommands} + machine.shutdown() ''; @@ -282,6 +374,7 @@ let , grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false , enableOCR ? false, meta ? {} , testSpecialisationConfig ? false + , testFlakeSwitch ? false }: makeTest { inherit enableOCR; @@ -334,6 +427,7 @@ let # The test cannot access the network, so any packages we # need must be included in the VM. system.extraDependencies = with pkgs; [ + bintools brotli brotli.dev brotli.lib @@ -387,7 +481,7 @@ let testScript = testScriptFun { inherit bootLoader createPartitions preBootCommands postBootCommands grubDevice grubIdentifier grubUseEfi extraConfig - testSpecialisationConfig; + testSpecialisationConfig testFlakeSwitch; }; }; @@ -439,6 +533,10 @@ let ''; }; + simple-test-config-flake = simple-test-config // { + testFlakeSwitch = true; + }; + simple-uefi-grub-config = { createPartitions = '' machine.succeed( @@ -493,6 +591,8 @@ in { # one big filesystem partition. simple = makeInstallerTest "simple" simple-test-config; + switchToFlake = makeInstallerTest "switch-to-flake" simple-test-config-flake; + # Test cloned configurations with the simple grub configuration simpleSpecialised = makeInstallerTest "simpleSpecialised" (simple-test-config // specialisation-test-extraconfig); diff --git a/nixos/tests/installer/flake.nix b/nixos/tests/installer/flake.nix new file mode 100644 index 0000000000000..4bbef44e34fc9 --- /dev/null +++ b/nixos/tests/installer/flake.nix @@ -0,0 +1,20 @@ +# This file gets copied into the installation + +{ + # To keep things simple, we'll use an absolute path dependency here. + inputs.nixpkgs.url = "@nixpkgs@"; + + outputs = { nixpkgs, ... }: { + + nixosConfigurations.xyz = nixpkgs.lib.nixosSystem { + modules = [ + ./configuration.nix + ( nixpkgs + "/nixos/modules/testing/test-instrumentation.nix" ) + { + # We don't need nix-channel anymore + nix.channel.enable = false; + } + ]; + }; + }; +} diff --git a/nixos/tests/jenkins.nix b/nixos/tests/jenkins.nix index a1ede6dc917bf..a8f6210006547 100644 --- a/nixos/tests/jenkins.nix +++ b/nixos/tests/jenkins.nix @@ -73,8 +73,8 @@ import ./make-test-python.nix ({ pkgs, ...} : { testScript = { nodes, ... }: let - configWithoutJobs = "${nodes.master.config.system.build.toplevel}/specialisation/noJenkinsJobs"; - jenkinsPort = nodes.master.config.services.jenkins.port; + configWithoutJobs = "${nodes.master.system.build.toplevel}/specialisation/noJenkinsJobs"; + jenkinsPort = nodes.master.services.jenkins.port; jenkinsUrl = "http://localhost:${toString jenkinsPort}"; in '' start_all() diff --git a/nixos/tests/k3s/single-node.nix b/nixos/tests/k3s/single-node.nix index d61595d889e2a..e059603b9c9d7 100644 --- a/nixos/tests/k3s/single-node.nix +++ b/nixos/tests/k3s/single-node.nix @@ -62,20 +62,20 @@ import ../make-test-python.nix ({ pkgs, lib, k3s, ... }: start_all() machine.wait_for_unit("k3s") - machine.succeed("k3s kubectl cluster-info") - machine.fail("sudo -u noprivs k3s kubectl cluster-info") + machine.succeed("kubectl cluster-info") + machine.fail("sudo -u noprivs kubectl cluster-info") '' # Fix-Me: Tests fail for 'aarch64-linux' as: "CONFIG_CGROUP_FREEZER: missing (fail)" + lib.optionalString (!pkgs.stdenv.isAarch64) ''machine.succeed("k3s check-config")'' + '' machine.succeed( - "${pauseImage} | k3s ctr image import -" + "${pauseImage} | ctr image import -" ) # Also wait for our service account to show up; it takes a sec - machine.wait_until_succeeds("k3s kubectl get serviceaccount default") - 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.wait_until_succeeds("kubectl get serviceaccount default") + machine.succeed("kubectl apply -f ${testPodYaml}") + machine.succeed("kubectl wait --for 'condition=Ready' pod/test") + machine.succeed("kubectl delete -f ${testPodYaml}") # regression test for #176445 machine.fail("journalctl -o cat -u k3s.service | grep 'ipset utility not found'") diff --git a/nixos/tests/kanidm.nix b/nixos/tests/kanidm.nix index 673a65174dfee..3f5bca397740e 100644 --- a/nixos/tests/kanidm.nix +++ b/nixos/tests/kanidm.nix @@ -67,9 +67,10 @@ import ./make-test-python.nix ({ pkgs, ... }: '' start_all() server.wait_for_unit("kanidm.service") + client.wait_for_unit("network-online.target") with subtest("Test HTTP interface"): - server.wait_until_succeeds("curl -sf https://${serverDomain} | grep Kanidm") + server.wait_until_succeeds("curl -Lsf https://${serverDomain} | grep Kanidm") with subtest("Test LDAP interface"): server.succeed("ldapsearch -H ldaps://${serverDomain}:636 -b '${ldapBaseDN}' -x '(name=test)'") @@ -80,15 +81,11 @@ import ./make-test-python.nix ({ pkgs, ... }: client.succeed("kanidm logout") with subtest("Recover idm_admin account"): - # Must stop the server for account recovery or else kanidmd fails with - # "unable to lock kanidm exclusive lock at /var/lib/kanidm/kanidm.db.klock". - server.succeed("systemctl stop kanidm") idm_admin_password = server.succeed("su - kanidm -c 'kanidmd recover-account -c ${serverConfigFile} idm_admin 2>&1 | rg -o \'[A-Za-z0-9]{48}\' '").strip().removeprefix("'").removesuffix("'") - server.succeed("systemctl start kanidm") with subtest("Test unixd connection"): client.wait_for_unit("kanidm-unixd.service") - # TODO: client.wait_for_file("/run/kanidm-unixd/sock") + client.wait_for_file("/run/kanidm-unixd/sock") client.wait_until_succeeds("kanidm-unix status | grep working!") with subtest("Test user creation"): diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix index 82d9118c6fb10..e69dd550289c1 100644 --- a/nixos/tests/kernel-generic.nix +++ b/nixos/tests/kernel-generic.nix @@ -9,7 +9,7 @@ let testsForLinuxPackages = linuxPackages: (import ./make-test-python.nix ({ pkgs, ... }: { name = "kernel-${linuxPackages.kernel.version}"; meta = with pkgs.lib.maintainers; { - maintainers = [ nequissimus atemu ]; + maintainers = [ nequissimus atemu ma27 ]; }; nodes.machine = { ... }: @@ -31,13 +31,20 @@ let linux_5_10_hardened linux_5_15_hardened linux_6_1_hardened - linux_6_3_hardened + linux_6_4_hardened + linux_rt_5_4 + linux_rt_5_10 + linux_rt_5_15 + linux_rt_6_1 + linux_libre linux_testing; }; in mapAttrs (_: lP: testsForLinuxPackages lP) kernels // { - inherit testsForLinuxPackages; + passthru = { + inherit testsForLinuxPackages; - testsForKernel = kernel: testsForLinuxPackages (pkgs.linuxPackagesFor kernel); + testsForKernel = kernel: testsForLinuxPackages (pkgs.linuxPackagesFor kernel); + }; } diff --git a/nixos/tests/keyd.nix b/nixos/tests/keyd.nix index d492cc194895c..1ee08b4101f72 100644 --- a/nixos/tests/keyd.nix +++ b/nixos/tests/keyd.nix @@ -32,7 +32,7 @@ let nodes.machine = { services.keyd = { enable = true; - inherit settings; + keyboards.default = { inherit settings; }; }; }; diff --git a/nixos/tests/keymap.nix b/nixos/tests/keymap.nix index 0bde21093b0a2..cc45824667edb 100644 --- a/nixos/tests/keymap.nix +++ b/nixos/tests/keymap.nix @@ -29,10 +29,10 @@ let mkKeyboardTest = layout: { extraConfig ? {}, tests }: with pkgs.lib; makeTest { name = "keymap-${layout}"; - machine.console.keyMap = mkOverride 900 layout; - machine.services.xserver.desktopManager.xterm.enable = false; - machine.services.xserver.layout = mkOverride 900 layout; - machine.imports = [ ./common/x11.nix extraConfig ]; + nodes.machine.console.keyMap = mkOverride 900 layout; + nodes.machine.services.xserver.desktopManager.xterm.enable = false; + nodes.machine.services.xserver.layout = mkOverride 900 layout; + nodes.machine.imports = [ ./common/x11.nix extraConfig ]; testScript = '' import json @@ -201,4 +201,33 @@ in pkgs.lib.mapAttrs mkKeyboardTest { extraConfig.console.keyMap = "de"; extraConfig.services.xserver.layout = "de"; }; + + custom = { + tests = { + us.qwerty = [ "a" "b" "g" "d" "z" "shift-2" "shift-3" ]; + us.expect = [ "a" "b" "g" "d" "z" "@" "#" ]; + greek.qwerty = map (x: "alt_r-${x}") + [ "a" "b" "g" "d" "z" ]; + greek.expect = [ "α" "β" "γ" "δ" "ζ" ]; + }; + + extraConfig.console.useXkbConfig = true; + extraConfig.services.xserver.layout = "us-greek"; + extraConfig.services.xserver.extraLayouts.us-greek = + { description = "US layout with alt-gr greek"; + languages = [ "eng" ]; + symbolsFile = pkgs.writeText "us-greek" '' + xkb_symbols "us-greek" + { + include "us(basic)" + include "level3(ralt_switch)" + key <LatA> { [ a, A, Greek_alpha ] }; + key <LatB> { [ b, B, Greek_beta ] }; + key <LatG> { [ g, G, Greek_gamma ] }; + key <LatD> { [ d, D, Greek_delta ] }; + key <LatZ> { [ z, Z, Greek_zeta ] }; + }; + ''; + }; + }; } diff --git a/nixos/tests/lemmy.nix b/nixos/tests/lemmy.nix index b1a39827b0723..de2c4938fe231 100644 --- a/nixos/tests/lemmy.nix +++ b/nixos/tests/lemmy.nix @@ -22,16 +22,16 @@ in # Without setup, the /feeds/* and /nodeinfo/* API endpoints won't return 200 setup = { admin_username = "mightyiam"; - admin_password = "ThisIsWhatIUseEverywhereTryIt"; site_name = "Lemmy FTW"; admin_email = "mightyiam@example.com"; }; - # https://github.com/LemmyNet/lemmy/blob/50efb1d519c63a7007a07f11cc8a11487703c70d/crates/utils/src/settings/mod.rs#L52 - database.uri = "postgres:///lemmy?host=/run/postgresql&user=lemmy"; }; + adminPasswordFile = /etc/lemmy-admin-password.txt; caddy.enable = true; }; + environment.etc."lemmy-admin-password.txt".text = "ThisIsWhatIUseEverywhereTryIt"; + networking.firewall.allowedTCPPorts = [ 80 ]; # pict-rs seems to need more than 1025114112 bytes @@ -42,8 +42,14 @@ in testScript = '' server = ${lemmyNodeName} - with subtest("the backend starts and responds"): + with subtest("the merged config is secure"): server.wait_for_unit("lemmy.service") + config_permissions = server.succeed("stat --format %A /run/lemmy/config.hjson").rstrip() + assert config_permissions == "-rw-------", f"merged config permissions {config_permissions} are insecure" + directory_permissions = server.succeed("stat --format %A /run/lemmy").rstrip() + assert directory_permissions[5] == directory_permissions[8] == "-", "merged config can be replaced" + + with subtest("the backend starts and responds"): server.wait_for_open_port(${toString backendPort}) server.succeed("curl --fail localhost:${toString backendPort}/api/v3/site") diff --git a/nixos/tests/libvirtd.nix b/nixos/tests/libvirtd.nix index b6e6040759976..41d06cc9643fe 100644 --- a/nixos/tests/libvirtd.nix +++ b/nixos/tests/libvirtd.nix @@ -11,6 +11,9 @@ import ./make-test-python.nix ({ pkgs, ... }: { memorySize = 2048; libvirtd.enable = true; + libvirtd.hooks.qemu.is_working = "${pkgs.writeShellScript "testHook.sh" '' + touch /tmp/qemu_hook_is_working + ''}"; }; boot.supportedFilesystems = [ "zfs" ]; networking.hostId = "deadbeef"; # needed for zfs @@ -57,5 +60,9 @@ import ./make-test-python.nix ({ pkgs, ... }: { virthost.shutdown() virthost.wait_for_unit("multi-user.target") virthost.wait_until_succeeds("ping -c 1 nixos") + + with subtest("test if hooks are linked and run"): + virthost.succeed("ls /var/lib/libvirt/hooks/qemu.d/is_working") + virthost.succeed("ls /tmp/qemu_hook_is_working") ''; }) diff --git a/nixos/tests/lxd-ui.nix b/nixos/tests/lxd-ui.nix new file mode 100644 index 0000000000000..19eaa226c0bfe --- /dev/null +++ b/nixos/tests/lxd-ui.nix @@ -0,0 +1,35 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "lxd-ui"; + + meta = with pkgs.lib.maintainers; { + maintainers = [ jnsgruk ]; + }; + + nodes.machine = { lib, ... }: { + virtualisation = { + lxd.enable = true; + lxd.ui.enable = true; + }; + + environment.systemPackages = [ pkgs.curl ]; + }; + + testScript = '' + machine.wait_for_unit("sockets.target") + machine.wait_for_unit("lxd.service") + machine.wait_for_file("/var/lib/lxd/unix.socket") + + # Wait for lxd to settle + machine.succeed("lxd waitready") + + # Configure LXC listen address + machine.succeed("lxc config set core.https_address :8443") + machine.succeed("systemctl restart lxd") + + # Check that the LXD_UI environment variable is populated in the systemd unit + machine.succeed("cat /etc/systemd/system/lxd.service | grep 'LXD_UI'") + + # Ensure the endpoint returns an HTML page with 'LXD UI' in the title + machine.succeed("curl -kLs https://localhost:8443/ui | grep '<title>LXD UI</title>'") + ''; +}) diff --git a/nixos/tests/maestral.nix b/nixos/tests/maestral.nix index ba2e0b2f3baab..67a265926187d 100644 --- a/nixos/tests/maestral.nix +++ b/nixos/tests/maestral.nix @@ -52,7 +52,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { testScript = { nodes, ... }: let - user = nodes.cli.config.users.users.alice; + user = nodes.cli.users.users.alice; in '' start_all() @@ -65,7 +65,8 @@ import ./make-test-python.nix ({ pkgs, ... }: { with subtest("GUI"): gui.wait_for_x() - gui.succeed("xauth merge ${user.home}/.Xauthority") + gui.wait_for_file("/tmp/xauth_*") + gui.succeed("xauth merge /tmp/xauth_*") gui.wait_for_window("^Desktop ") gui.wait_for_unit("maestral.service", "${user.name}") ''; diff --git a/nixos/tests/miniflux.nix b/nixos/tests/miniflux.nix index be3e7abb6abd4..a3af53db0e7a1 100644 --- a/nixos/tests/miniflux.nix +++ b/nixos/tests/miniflux.nix @@ -25,6 +25,7 @@ in default = { ... }: { + security.apparmor.enable = true; services.miniflux = { enable = true; inherit adminCredentialsFile; @@ -34,6 +35,7 @@ in withoutSudo = { ... }: { + security.apparmor.enable = true; services.miniflux = { enable = true; inherit adminCredentialsFile; @@ -44,6 +46,7 @@ in customized = { ... }: { + security.apparmor.enable = true; services.miniflux = { enable = true; config = { @@ -63,6 +66,7 @@ in default.succeed( "curl 'http://localhost:${toString defaultPort}/v1/me' -u '${defaultUsername}:${defaultPassword}' -H Content-Type:application/json | grep '\"is_admin\":true'" ) + default.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""') withoutSudo.wait_for_unit("miniflux.service") withoutSudo.wait_for_open_port(${toString defaultPort}) @@ -70,6 +74,7 @@ in withoutSudo.succeed( "curl 'http://localhost:${toString defaultPort}/v1/me' -u '${defaultUsername}:${defaultPassword}' -H Content-Type:application/json | grep '\"is_admin\":true'" ) + withoutSudo.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""') customized.wait_for_unit("miniflux.service") customized.wait_for_open_port(${toString port}) @@ -77,5 +82,6 @@ in customized.succeed( "curl 'http://localhost:${toString port}/v1/me' -u '${username}:${password}' -H Content-Type:application/json | grep '\"is_admin\":true'" ) + customized.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""') ''; }) diff --git a/nixos/tests/miriway.nix b/nixos/tests/miriway.nix index 9000e92781978..f12c4d5ecc41e 100644 --- a/nixos/tests/miriway.nix +++ b/nixos/tests/miriway.nix @@ -83,7 +83,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { }; }; - fonts.fonts = [ pkgs.inconsolata ]; + fonts.packages = [ pkgs.inconsolata ]; }; enableOCR = true; diff --git a/nixos/tests/mumble.nix b/nixos/tests/mumble.nix index 2b5cc20163bcb..8eee454721a13 100644 --- a/nixos/tests/mumble.nix +++ b/nixos/tests/mumble.nix @@ -20,6 +20,7 @@ in nodes = { server = { config, ... }: { + security.apparmor.enable = true; services.murmur.enable = true; services.murmur.registerName = "NixOS tests"; services.murmur.password = "$MURMURD_PASSWORD"; @@ -81,5 +82,8 @@ in server.sleep(5) # wait to get screenshot client1.screenshot("screen1") client2.screenshot("screen2") + + # check if apparmor denied anything + server.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""') ''; }) diff --git a/nixos/tests/n8n.nix b/nixos/tests/n8n.nix index 771296bf37a5a..0a12192d5c712 100644 --- a/nixos/tests/n8n.nix +++ b/nixos/tests/n8n.nix @@ -1,6 +1,7 @@ import ./make-test-python.nix ({ lib, ... }: let port = 5678; + webhookUrl = "http://example.com"; in { name = "n8n"; @@ -11,6 +12,7 @@ in { services.n8n = { enable = true; + webhookUrl = webhookUrl; }; }; @@ -18,5 +20,6 @@ in machine.wait_for_unit("n8n.service") machine.wait_for_console_text("Editor is now accessible via") machine.succeed("curl --fail -vvv http://localhost:${toString port}/") + machine.succeed("grep -qF ${webhookUrl} /etc/systemd/system/n8n.service") ''; }) diff --git a/nixos/tests/netdata.nix b/nixos/tests/netdata.nix index aea67c29d0d4b..c5f7294f79abc 100644 --- a/nixos/tests/netdata.nix +++ b/nixos/tests/netdata.nix @@ -10,7 +10,7 @@ import ./make-test-python.nix ({ pkgs, ...} : { netdata = { pkgs, ... }: { - environment.systemPackages = with pkgs; [ curl jq ]; + environment.systemPackages = with pkgs; [ curl jq netdata ]; services.netdata.enable = true; }; }; @@ -34,5 +34,8 @@ import ./make-test-python.nix ({ pkgs, ...} : { filter = '[.data[range(10)][.labels | indices("root")[0]]] | add | . > 0' cmd = f"curl -s {url} | jq -e '{filter}'" netdata.wait_until_succeeds(cmd) + + # check if the control socket is available + netdata.succeed("sudo netdatacli ping") ''; }) diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix index 05fed0f4b4732..46fc715d0891d 100644 --- a/nixos/tests/networking.nix +++ b/nixos/tests/networking.nix @@ -29,23 +29,49 @@ let ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ]; }))); }; - services.dhcpd4 = { - enable = true; - interfaces = map (n: "eth${toString n}") vlanIfs; - extraConfig = flip concatMapStrings vlanIfs (n: '' - subnet 192.168.${toString n}.0 netmask 255.255.255.0 { - option routers 192.168.${toString n}.1; - range 192.168.${toString n}.3 192.168.${toString n}.254; - } - '') - ; - machines = flip map vlanIfs (vlan: - { - hostName = "client${toString vlan}"; - ethernetAddress = qemu-common.qemuNicMac vlan 1; - ipAddress = "192.168.${toString vlan}.2"; - } - ); + services.kea = { + dhcp4 = { + enable = true; + settings = { + interfaces-config = { + interfaces = map (n: "eth${toString n}") vlanIfs; + dhcp-socket-type = "raw"; + service-sockets-require-all = true; + service-sockets-max-retries = 5; + service-sockets-retry-wait-time = 2500; + }; + subnet4 = map (n: { + id = n; + subnet = "192.168.${toString n}.0/24"; + pools = [{ pool = "192.168.${toString n}.3 - 192.168.${toString n}.254"; }]; + option-data = [{ name = "routers"; data = "192.168.${toString n}.1"; }]; + + reservations = [{ + hw-address = qemu-common.qemuNicMac n 1; + hostname = "client${toString n}"; + ip-address = "192.168.${toString n}.2"; + }]; + }) vlanIfs; + }; + }; + dhcp6 = { + enable = true; + settings = { + interfaces-config = { + interfaces = map (n: "eth${toString n}") vlanIfs; + service-sockets-require-all = true; + service-sockets-max-retries = 5; + service-sockets-retry-wait-time = 2500; + }; + + subnet6 = map (n: { + id = n; + subnet = "fd00:1234:5678:${toString n}::/64"; + interface = "eth${toString n}"; + pools = [{ pool = "fd00:1234:5678:${toString n}::2-fd00:1234:5678:${toString n}::2"; }]; + }) vlanIfs; + }; + }; }; services.radvd = { enable = true; @@ -61,17 +87,6 @@ let }; ''); }; - services.dhcpd6 = { - enable = true; - interfaces = map (n: "eth${toString n}") vlanIfs; - extraConfig = '' - authoritative; - '' + flip concatMapStrings vlanIfs (n: '' - subnet6 fd00:1234:5678:${toString n}::/64 { - range6 fd00:1234:5678:${toString n}::2 fd00:1234:5678:${toString n}::2; - } - ''); - }; }; testCases = { @@ -117,8 +132,9 @@ let client.wait_for_unit("network.target") router.wait_for_unit("network-online.target") - with subtest("Make sure dhcpcd is not started"): - client.fail("systemctl status dhcpcd.service") + with subtest("Make sure DHCP server is not started"): + client.fail("systemctl status kea-dhcp4-server.service") + client.fail("systemctl status kea-dhcp6-server.service") with subtest("Test vlan 1"): client.wait_until_succeeds("ping -c 1 192.168.1.1") @@ -1035,7 +1051,7 @@ let testScript = '' machine.succeed("udevadm settle") print(machine.succeed("ip link show dev enCustom")) - machine.wait_until_succeeds("ip link show dev enCustom | grep -q '52:54:00:12:0b:01") + machine.wait_until_succeeds("ip link show dev enCustom | grep -q 52:54:00:12:0b:01") ''; }; }; diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix index e17f701c54b7d..b7af6d6d73647 100644 --- a/nixos/tests/nextcloud/basic.nix +++ b/nixos/tests/nextcloud/basic.nix @@ -14,12 +14,12 @@ in { client = { ... }: { services.davfs2.enable = true; system.activationScripts.davfs2-secrets = '' - echo "http://nextcloud/remote.php/webdav/ ${adminuser} ${adminpass}" > /tmp/davfs2-secrets + echo "http://nextcloud/remote.php/dav/files/${adminuser} ${adminuser} ${adminpass}" > /tmp/davfs2-secrets chmod 600 /tmp/davfs2-secrets ''; virtualisation.fileSystems = { "/mnt/dav" = { - device = "http://nextcloud/remote.php/webdav/"; + device = "http://nextcloud/remote.php/dav/files/${adminuser}"; fsType = "davfs"; options = let davfs2Conf = (pkgs.writeText "davfs2.conf" "secrets /tmp/davfs2-secrets"); @@ -70,7 +70,7 @@ in { withRcloneEnv = pkgs.writeScript "with-rclone-env" '' #!${pkgs.runtimeShell} export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/" + export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" @@ -90,8 +90,8 @@ in { test -e graph grep "$what" graph >$out || true ''; - nextcloudUsesImagick = findInClosure "imagick" nodes.nextcloud.config.system.build.vm; - nextcloudWithoutDoesntUseIt = findInClosure "imagick" nodes.nextcloudWithoutMagick.config.system.build.vm; + nextcloudUsesImagick = findInClosure "imagick" nodes.nextcloud.system.build.vm; + nextcloudWithoutDoesntUseIt = findInClosure "imagick" nodes.nextcloudWithoutMagick.system.build.vm; in '' assert open("${nextcloudUsesImagick}").read() != "" assert open("${nextcloudWithoutDoesntUseIt}").read() == "" diff --git a/nixos/tests/nextcloud/openssl-sse.nix b/nixos/tests/nextcloud/openssl-sse.nix index 659a4311cdddd..d6ea39c6155a5 100644 --- a/nixos/tests/nextcloud/openssl-sse.nix +++ b/nixos/tests/nextcloud/openssl-sse.nix @@ -33,7 +33,7 @@ in { withRcloneEnv = host: pkgs.writeScript "with-rclone-env" '' #!${pkgs.runtimeShell} export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://${host}/remote.php/webdav/" + export RCLONE_CONFIG_NEXTCLOUD_URL="http://${host}/remote.php/dav/files/${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" @@ -49,8 +49,8 @@ in { #!${pkgs.runtimeShell} echo 'bye' | ${withRcloneEnv3} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file2 ''; - openssl1-node = nodes.nextcloudwithopenssl1.config.system.build.toplevel; - openssl3-node = nodes.nextcloudwithopenssl3.config.system.build.toplevel; + openssl1-node = nodes.nextcloudwithopenssl1.system.build.toplevel; + openssl3-node = nodes.nextcloudwithopenssl3.system.build.toplevel; in '' nextcloudwithopenssl1.start() nextcloudwithopenssl1.wait_for_unit("multi-user.target") diff --git a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix index 915fa58ab1458..e638f2e5b861f 100644 --- a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix +++ b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix @@ -1,6 +1,6 @@ args@{ nextcloudVersion ? 27, ... }: (import ../make-test-python.nix ({ pkgs, ...}: let - username = "custom_admin_username"; + adminuser = "custom_admin_username"; # This will be used both for redis and postgresql pass = "hunter2"; # Don't do this at home, use a file outside of the nix store instead @@ -34,9 +34,9 @@ in { config = { dbtype = "pgsql"; dbname = "nextcloud"; - dbuser = username; + dbuser = adminuser; dbpassFile = passFile; - adminuser = username; + adminuser = adminuser; adminpassFile = passFile; }; secretFile = "/etc/nextcloud-secrets.json"; @@ -66,9 +66,9 @@ in { systemd.services.postgresql.postStart = pkgs.lib.mkAfter '' password=$(cat ${passFile}) ${config.services.postgresql.package}/bin/psql <<EOF - CREATE ROLE ${username} WITH LOGIN PASSWORD '$password' CREATEDB; + CREATE ROLE ${adminuser} WITH LOGIN PASSWORD '$password' CREATEDB; CREATE DATABASE nextcloud; - GRANT ALL PRIVILEGES ON DATABASE nextcloud TO ${username}; + GRANT ALL PRIVILEGES ON DATABASE nextcloud TO ${adminuser}; EOF ''; @@ -89,9 +89,9 @@ in { withRcloneEnv = pkgs.writeScript "with-rclone-env" '' #!${pkgs.runtimeShell} export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/" + export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" - export RCLONE_CONFIG_NEXTCLOUD_USER="${username}" + export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${pass})" "''${@}" ''; diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix index e57aabfaf86b5..035a7fdcb0c80 100644 --- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix +++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix @@ -49,7 +49,7 @@ in { withRcloneEnv = pkgs.writeScript "with-rclone-env" '' #!${pkgs.runtimeShell} export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/" + export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix index a91208e6cdc4e..586bf50fd939c 100644 --- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix +++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix @@ -60,7 +60,7 @@ in { withRcloneEnv = pkgs.writeScript "with-rclone-env" '' #!${pkgs.runtimeShell} export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/" + export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" diff --git a/nixos/tests/nginx-proxyprotocol/default.nix b/nixos/tests/nginx-proxyprotocol/default.nix index 9ef5d01cd65b0..2ff7debfcbe25 100644 --- a/nixos/tests/nginx-proxyprotocol/default.nix +++ b/nixos/tests/nginx-proxyprotocol/default.nix @@ -4,6 +4,10 @@ in import ../make-test-python.nix ({ pkgs, ... }: { name = "nginx-proxyprotocol"; + meta = { + maintainers = with pkgs.lib.maintainers; [ raitobezarius ]; + }; + nodes = { webserver = { pkgs, lib, ... }: { environment.systemPackages = [ pkgs.netcat ]; diff --git a/nixos/tests/nginx-status-page.nix b/nixos/tests/nginx-status-page.nix new file mode 100644 index 0000000000000..ff2c0940379c7 --- /dev/null +++ b/nixos/tests/nginx-status-page.nix @@ -0,0 +1,72 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "nginx-status-page"; + meta = with pkgs.lib.maintainers; { + maintainers = [ h7x4 ]; + }; + + nodes = { + webserver = { ... }: { + virtualisation.vlans = [ 1 ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.1/24"; + }; + + services.nginx = { + enable = true; + statusPage = true; + virtualHosts."localhost".locations."/index.html".return = "200 'hello world\n'"; + }; + + environment.systemPackages = with pkgs; [ curl ]; + }; + + client = { ... }: { + virtualisation.vlans = [ 1 ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.2/24"; + }; + + environment.systemPackages = with pkgs; [ curl ]; + }; + }; + + testScript = { nodes, ... }: '' + start_all() + + webserver.wait_for_unit("nginx") + webserver.wait_for_open_port(80) + + def expect_http_code(node, code, url): + http_code = node.succeed(f"curl -w '%{{http_code}}' '{url}'") + assert http_code.split("\n")[-1].strip() == code, \ + f"expected {code} but got following response:\n{http_code}" + + with subtest("localhost can access status page"): + expect_http_code(webserver, "200", "http://localhost/nginx_status") + + with subtest("localhost can access other page"): + expect_http_code(webserver, "200", "http://localhost/index.html") + + with subtest("client can not access status page"): + expect_http_code(client, "403", "http://10.0.0.1/nginx_status") + + with subtest("client can access other page"): + expect_http_code(client, "200", "http://10.0.0.1/index.html") + ''; +}) diff --git a/nixos/tests/nixos-test-driver/busybox.nix b/nixos/tests/nixos-test-driver/busybox.nix new file mode 100644 index 0000000000000..426f4494436e2 --- /dev/null +++ b/nixos/tests/nixos-test-driver/busybox.nix @@ -0,0 +1,16 @@ +{ + name = "Test that basic tests work when busybox is installed"; + + nodes = { + machine = ({ pkgs, ... }: { + environment.systemPackages = [ + pkgs.busybox + ]; + }); + }; + + testScript = '' + start_all() + machine.wait_for_unit("multi-user.target") + ''; +} diff --git a/nixos/tests/non-default-filesystems.nix b/nixos/tests/non-default-filesystems.nix index 6233e8d265d0f..08a17107dd2fd 100644 --- a/nixos/tests/non-default-filesystems.nix +++ b/nixos/tests/non-default-filesystems.nix @@ -94,6 +94,8 @@ with pkgs.lib; makeTest { name = "non-default-filesystems-erofs"; + meta.maintainers = with maintainers; [ nikstur ]; + nodes.machine = _: { virtualisation.qemu.drives = [{ name = "non-default-filesystem"; @@ -128,4 +130,43 @@ with pkgs.lib; assert "erofs" in file_contents ''; }; + + squashfs = + let + fsImage = "/tmp/non-default-filesystem.img"; + in + makeTest { + name = "non-default-filesystems-squashfs"; + + meta.maintainers = with maintainers; [ nikstur ]; + + nodes.machine = { + virtualisation.qemu.drives = [{ + name = "non-default-filesystem"; + file = fsImage; + deviceExtraOpts.serial = "non-default"; + }]; + + virtualisation.fileSystems."/non-default" = { + device = "/dev/disk/by-id/virtio-non-default"; + fsType = "squashfs"; + neededForBoot = true; + }; + }; + + testScript = '' + import subprocess + + with open("filesystem", "w") as f: + f.write("squashfs") + + subprocess.run([ + "${pkgs.squashfsTools}/bin/mksquashfs", + "filesystem", + "${fsImage}", + ]) + + assert "squashfs" in machine.succeed("cat /non-default/filesystem") + ''; + }; } diff --git a/nixos/tests/noto-fonts-cjk-qt-default-weight.nix b/nixos/tests/noto-fonts-cjk-qt-default-weight.nix index 678013cf3ab90..c2e0cb3adaebd 100644 --- a/nixos/tests/noto-fonts-cjk-qt-default-weight.nix +++ b/nixos/tests/noto-fonts-cjk-qt-default-weight.nix @@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { nodes.machine = { imports = [ ./common/x11.nix ]; fonts = { - enableDefaultFonts = false; + enableDefaultPackages = false; fonts = [ pkgs.noto-fonts-cjk-sans ]; }; }; diff --git a/nixos/tests/noto-fonts.nix b/nixos/tests/noto-fonts.nix index 0515f16d101cd..edbb0db4cb7aa 100644 --- a/nixos/tests/noto-fonts.nix +++ b/nixos/tests/noto-fonts.nix @@ -4,9 +4,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { nodes.machine = { imports = [ ./common/x11.nix ]; - environment.systemPackages = [ pkgs.gnome.gedit ]; + environment.systemPackages = [ pkgs.gedit ]; fonts = { - enableDefaultFonts = false; + enableDefaultPackages = false; fonts = with pkgs;[ noto-fonts noto-fonts-cjk-sans diff --git a/nixos/tests/osquery.nix b/nixos/tests/osquery.nix new file mode 100644 index 0000000000000..9aa9820e50c52 --- /dev/null +++ b/nixos/tests/osquery.nix @@ -0,0 +1,52 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: + +let + config_refresh = "10"; + nullvalue = "NULL"; + utc = false; +in +{ + name = "osquery"; + meta.maintainers = with lib.maintainers; [ znewman01 lewo ]; + + nodes.machine = { config, pkgs, ... }: { + services.osquery = { + enable = true; + + settings.options = { inherit nullvalue utc; }; + flags = { + inherit config_refresh; + nullvalue = "IGNORED"; + }; + }; + }; + + testScript = { nodes, ... }: + let + cfg = nodes.machine.services.osquery; + in + '' + machine.start() + machine.wait_for_unit("osqueryd.service") + + # Stop the osqueryd service so that we can use osqueryi to check information stored in the database. + machine.wait_until_succeeds("systemctl stop osqueryd.service") + + # osqueryd was able to query information about the host. + machine.succeed("echo 'SELECT address FROM etc_hosts LIMIT 1;' | osqueryi | tee /dev/console | grep -q '127.0.0.1'") + + # osquery binaries respect configuration from the Nix config option. + machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"utc\";' | osqueryi | tee /dev/console | grep -q ${boolToString utc}") + + # osquery binaries respect configuration from the Nix flags option. + machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"config_refresh\";' | osqueryi | tee /dev/console | grep -q ${config_refresh}") + + # Demonstrate that osquery binaries prefer configuration plugin options over CLI flags. + # https://osquery.readthedocs.io/en/latest/deployment/configuration/#options. + machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"nullvalue\";' | osqueryi | tee /dev/console | grep -q ${nullvalue}") + + # Module creates directories for default database_path and pidfile flag values. + machine.succeed("test -d $(dirname ${cfg.flags.database_path})") + machine.succeed("test -d $(dirname ${cfg.flags.pidfile})") + ''; +}) diff --git a/nixos/tests/paperless.nix b/nixos/tests/paperless.nix index 7f36de4c29b71..ce6a4d8128dfd 100644 --- a/nixos/tests/paperless.nix +++ b/nixos/tests/paperless.nix @@ -30,20 +30,27 @@ import ./make-test-python.nix ({ lib, ... }: { with subtest("Task-queue gets ready"): machine.wait_for_unit("paperless-task-queue.service") - with subtest("Add a document via the web interface"): + with subtest("Add a png document via the web interface"): machine.succeed( "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black " "-annotate +5+20 'hello web 16-10-2005' /tmp/webdoc.png" ) machine.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.png -fs localhost:28981/api/documents/post_document/") + with subtest("Add a txt document via the web interface"): + machine.succeed( + "echo 'hello web 16-10-2005' > /tmp/webdoc.txt" + ) + machine.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.txt -fs localhost:28981/api/documents/post_document/") + with subtest("Documents are consumed"): machine.wait_until_succeeds( - "(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 2))" + "(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 3))" ) docs = json.loads(machine.succeed("curl -u admin:admin -fs localhost:28981/api/documents/"))['results'] assert "2005-10-16" in docs[0]['created'] assert "2005-10-16" in docs[1]['created'] + assert "2005-10-16" in docs[2]['created'] # Detects gunicorn issues, see PR #190888 with subtest("Document metadata can be accessed"): @@ -52,5 +59,8 @@ import ./make-test-python.nix ({ lib, ... }: { metadata = json.loads(machine.succeed("curl -u admin:admin -fs localhost:28981/api/documents/2/metadata/")) assert "original_checksum" in metadata + + metadata = json.loads(machine.succeed("curl -u admin:admin -fs localhost:28981/api/documents/3/metadata/")) + assert "original_checksum" in metadata ''; }) diff --git a/nixos/tests/pgbouncer.nix b/nixos/tests/pgbouncer.nix new file mode 100644 index 0000000000000..1e72327d42005 --- /dev/null +++ b/nixos/tests/pgbouncer.nix @@ -0,0 +1,61 @@ +import ./make-test-python.nix ({ pkgs, ... } : +let + testAuthFile = pkgs.writeTextFile { + name = "authFile"; + text = '' + "testuser" "testpass" + ''; + }; +in +{ + name = "pgbouncer"; + meta = with pkgs.lib.maintainers; { + maintainers = [ _1000101 ]; + }; + nodes = { + one = { config, pkgs, ... }: { + + systemd.services.postgresql = { + postStart = '' + ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER ROLE testuser WITH LOGIN PASSWORD 'testpass'"; + ''; + }; + + services = { + postgresql = { + enable = true; + ensureDatabases = [ "testdb" ]; + ensureUsers = [ + { + name = "testuser"; + ensurePermissions = { + "DATABASE testdb" = "ALL PRIVILEGES"; + }; + }]; + authentication = '' + local testdb testuser scram-sha-256 + ''; + }; + + pgbouncer = { + enable = true; + listenAddress = "localhost"; + databases = { testdb = "host=/run/postgresql/ port=5432 auth_user=testuser dbname=testdb"; }; + authType = "scram-sha-256"; + authFile = testAuthFile; + }; + }; + }; + }; + + testScript = '' + start_all() + one.wait_for_unit("default.target") + one.require_unit_state("pgbouncer.service", "active") + + # Test if we can make a query through PgBouncer + one.wait_until_succeeds( + "psql 'postgres://testuser:testpass@localhost:6432/testdb' -c 'SELECT 1;'" + ) + ''; +}) diff --git a/nixos/tests/plasma-bigscreen.nix b/nixos/tests/plasma-bigscreen.nix index 1c61cafcbff33..2fe90fa9b539c 100644 --- a/nixos/tests/plasma-bigscreen.nix +++ b/nixos/tests/plasma-bigscreen.nix @@ -22,14 +22,11 @@ import ./make-test-python.nix ({ pkgs, ...} : users.users.alice.extraGroups = ["uinput"]; }; - testScript = { nodes, ... }: let - user = nodes.machine.users.users.alice; - xdo = "${pkgs.xdotool}/bin/xdotool"; - in '' + testScript = { nodes, ... }: '' with subtest("Wait for login"): start_all() - machine.wait_for_file("${user.home}/.Xauthority") - machine.succeed("xauth merge ${user.home}/.Xauthority") + machine.wait_for_file("/tmp/xauth_*") + machine.succeed("xauth merge /tmp/xauth_*") with subtest("Check plasmashell started"): machine.wait_until_succeeds("pgrep plasmashell") diff --git a/nixos/tests/plasma5-systemd-start.nix b/nixos/tests/plasma5-systemd-start.nix index f584c1ec137aa..31a313af308b4 100644 --- a/nixos/tests/plasma5-systemd-start.nix +++ b/nixos/tests/plasma5-systemd-start.nix @@ -23,13 +23,11 @@ import ./make-test-python.nix ({ pkgs, ...} : }; }; - testScript = { nodes, ... }: let - user = nodes.machine.config.users.users.alice; - in '' + testScript = { nodes, ... }: '' with subtest("Wait for login"): start_all() - machine.wait_for_file("${user.home}/.Xauthority") - machine.succeed("xauth merge ${user.home}/.Xauthority") + machine.wait_for_file("/tmp/xauth_*") + machine.succeed("xauth merge /tmp/xauth_*") with subtest("Check plasmashell started"): machine.wait_until_succeeds("pgrep plasmashell") diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix index b3836cf641d4b..fb8a5b73832ea 100644 --- a/nixos/tests/plasma5.nix +++ b/nixos/tests/plasma5.nix @@ -13,10 +13,8 @@ import ./make-test-python.nix ({ pkgs, ...} : services.xserver.enable = true; services.xserver.displayManager.sddm.enable = true; services.xserver.displayManager.defaultSession = "plasma"; - services.xserver.desktopManager.plasma5 = { - enable = true; - excludePackages = [ pkgs.plasma5Packages.elisa ]; - }; + services.xserver.desktopManager.plasma5.enable = true; + environment.plasma5.excludePackages = [ pkgs.plasma5Packages.elisa ]; services.xserver.displayManager.autoLogin = { enable = true; user = "alice"; @@ -25,13 +23,13 @@ import ./make-test-python.nix ({ pkgs, ...} : }; testScript = { nodes, ... }: let - user = nodes.machine.config.users.users.alice; + user = nodes.machine.users.users.alice; xdo = "${pkgs.xdotool}/bin/xdotool"; in '' with subtest("Wait for login"): start_all() - machine.wait_for_file("${user.home}/.Xauthority") - machine.succeed("xauth merge ${user.home}/.Xauthority") + machine.wait_for_file("/tmp/xauth_*") + machine.succeed("xauth merge /tmp/xauth_*") with subtest("Check plasmashell started"): machine.wait_until_succeeds("pgrep plasmashell") @@ -46,6 +44,8 @@ import ./make-test-python.nix ({ pkgs, ...} : with subtest("Ensure Elisa is not installed"): machine.fail("which elisa") + machine.succeed("su - ${user.name} -c 'xauth merge /tmp/xauth_*'") + with subtest("Run Dolphin"): machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 dolphin >&2 &'") machine.wait_for_window(" Dolphin") diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix index 23740dd98e3d1..64e2811beb060 100644 --- a/nixos/tests/prometheus-exporters.nix +++ b/nixos/tests/prometheus-exporters.nix @@ -307,6 +307,23 @@ let ''; }; + idrac = { + exporterConfig = { + enable = true; + port = 9348; + configuration = { + hosts = { + default = { username = "username"; password = "password"; }; + }; + }; + }; + exporterTest = '' + wait_for_unit("prometheus-idrac-exporter.service") + wait_for_open_port(9348) + wait_until_succeeds("curl localhost:9348") + ''; + }; + influxdb = { exporterConfig = { enable = true; diff --git a/nixos/tests/retroarch.nix b/nixos/tests/retroarch.nix index f4bf232ea725e..0e5f60aa8be28 100644 --- a/nixos/tests/retroarch.nix +++ b/nixos/tests/retroarch.nix @@ -30,8 +30,8 @@ import ./make-test-python.nix ({ pkgs, ... }: in '' with subtest("Wait for login"): start_all() - machine.wait_for_file("${user.home}/.Xauthority") - machine.succeed("xauth merge ${user.home}/.Xauthority") + machine.wait_for_file("/tmp/xauth_*") + machine.succeed("xauth merge /tmp/xauth_*") with subtest("Check RetroArch started"): machine.wait_until_succeeds("pgrep retroarch") diff --git a/nixos/tests/samba-wsdd.nix b/nixos/tests/samba-wsdd.nix index 0e3185b0c6849..666a626d1b4a5 100644 --- a/nixos/tests/samba-wsdd.nix +++ b/nixos/tests/samba-wsdd.nix @@ -8,25 +8,23 @@ import ./make-test-python.nix ({ pkgs, ... }: client_wsdd = { pkgs, ... }: { services.samba-wsdd = { enable = true; + openFirewall = true; interface = "eth1"; workgroup = "WORKGROUP"; hostname = "CLIENT-WSDD"; discovery = true; extraOptions = [ "--no-host" ]; }; - networking.firewall.allowedTCPPorts = [ 5357 ]; - networking.firewall.allowedUDPPorts = [ 3702 ]; }; server_wsdd = { ... }: { services.samba-wsdd = { enable = true; + openFirewall = true; interface = "eth1"; workgroup = "WORKGROUP"; hostname = "SERVER-WSDD"; }; - networking.firewall.allowedTCPPorts = [ 5357 ]; - networking.firewall.allowedUDPPorts = [ 3702 ]; }; }; diff --git a/nixos/tests/sddm.nix b/nixos/tests/sddm.nix index c76a9683e66d0..b6c05deac05e4 100644 --- a/nixos/tests/sddm.nix +++ b/nixos/tests/sddm.nix @@ -23,14 +23,14 @@ let enableOCR = true; testScript = { nodes, ... }: let - user = nodes.machine.config.users.users.alice; + user = nodes.machine.users.users.alice; in '' start_all() machine.wait_for_text("(?i)select your user") machine.screenshot("sddm") machine.send_chars("${user.password}\n") - machine.wait_for_file("${user.home}/.Xauthority") - machine.succeed("xauth merge ${user.home}/.Xauthority") + machine.wait_for_file("/tmp/xauth_*") + machine.succeed("xauth merge /tmp/xauth_*") machine.wait_for_window("^IceWM ") ''; }; @@ -55,12 +55,10 @@ let services.xserver.windowManager.icewm.enable = true; }; - testScript = { nodes, ... }: let - user = nodes.machine.config.users.users.alice; - in '' + testScript = { nodes, ... }: '' start_all() - machine.wait_for_file("${user.home}/.Xauthority") - machine.succeed("xauth merge ${user.home}/.Xauthority") + machine.wait_for_file("/tmp/xauth_*") + machine.succeed("xauth merge /tmp/xauth_*") machine.wait_for_window("^IceWM ") ''; }; diff --git a/nixos/tests/sftpgo.nix b/nixos/tests/sftpgo.nix index ca55b9c962a07..8cd5675c1d4d4 100644 --- a/nixos/tests/sftpgo.nix +++ b/nixos/tests/sftpgo.nix @@ -12,8 +12,6 @@ # would be a nice to have for the future. { pkgs, lib, ... }: -with lib; - let inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; @@ -54,7 +52,7 @@ let # inside the dataprovider they will be automatically created. # You have to create the folder on the filesystem yourself virtual_folders = - optional (isMemberOf config sharedFolderName user) { + lib.optional (lib.isMemberOf config sharedFolderName user) { name = sharedFolderName; mapped_path = "${config.services.sftpgo.dataDir}/${sharedFolderName}"; virtual_path = "/${sharedFolderName}"; @@ -62,10 +60,10 @@ let # Defines the ACL on the virtual filesystem permissions = - recursiveUpdate { + lib.recursiveUpdate { "/" = [ "list" ]; # read-only top level directory "/private" = [ "*" ]; # private subdirectory, not shared with others - } (optionalAttrs (isMemberOf config "shared" user) { + } (lib.optionalAttrs (lib.isMemberOf config "shared" user) { "/shared" = [ "*" ]; }); @@ -91,7 +89,7 @@ let # of users and folders to import to SFTPGo. loadDataJson = config: pkgs.writeText "users-and-folders.json" (builtins.toJSON { users = - mapAttrsToList (name: user: generateUserAttrSet config user) (normalUsers config); + lib.mapAttrsToList (name: user: lib.generateUserAttrSet config user) (normalUsers config); folders = [ { diff --git a/nixos/tests/sing-box.nix b/nixos/tests/sing-box.nix index 43be89317642c..582d594be3fdb 100644 --- a/nixos/tests/sing-box.nix +++ b/nixos/tests/sing-box.nix @@ -8,7 +8,10 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: { nodes.machine = { pkgs, ... }: { environment.systemPackages = [ pkgs.curl ]; - services.nginx.enable = true; + services.nginx = { + enable = true; + statusPage = true; + }; services.sing-box = { enable = true; settings = { diff --git a/nixos/tests/sway.nix b/nixos/tests/sway.nix index d95334c10e494..695d4a7708104 100644 --- a/nixos/tests/sway.nix +++ b/nixos/tests/sway.nix @@ -51,7 +51,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { ''; }; - fonts.fonts = [ pkgs.inconsolata ]; + fonts.packages = [ pkgs.inconsolata ]; # Automatically configure and start Sway when logging in on tty1: programs.bash.loginShellInit = '' diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix index f891a2cb2f4c2..f44dede7fef45 100644 --- a/nixos/tests/switch-test.nix +++ b/nixos/tests/switch-test.nix @@ -70,6 +70,19 @@ in { }; }; + simpleServiceSeparateActivationScript.configuration = { + system.activatable = false; + systemd.services.test = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.coreutils}/bin/true"; + ExecReload = "${pkgs.coreutils}/bin/true"; + }; + }; + }; + simpleServiceDifferentDescription.configuration = { imports = [ simpleService.configuration ]; systemd.services.test.description = "Test unit"; @@ -482,9 +495,9 @@ in { }; testScript = { nodes, ... }: let - originalSystem = nodes.machine.config.system.build.toplevel; - otherSystem = nodes.other.config.system.build.toplevel; - machine = nodes.machine.config.system.build.toplevel; + originalSystem = nodes.machine.system.build.toplevel; + otherSystem = nodes.other.system.build.toplevel; + machine = nodes.machine.system.build.toplevel; # Ensures failures pass through using pipefail, otherwise failing to # switch-to-configuration is hidden by the success of `tee`. @@ -497,11 +510,15 @@ in { in /* python */ '' def switch_to_specialisation(system, name, action="test", fail=False): if name == "": - stc = f"{system}/bin/switch-to-configuration" + switcher = f"{system}/bin/switch-to-configuration" else: - stc = f"{system}/specialisation/{name}/bin/switch-to-configuration" - out = machine.fail(f"{stc} {action} 2>&1") if fail \ - else machine.succeed(f"{stc} {action} 2>&1") + switcher = f"{system}/specialisation/{name}/bin/switch-to-configuration" + return run_switch(switcher, action, fail) + + # like above but stc = switcher + def run_switch(switcher, action="test", fail=False): + out = machine.fail(f"{switcher} {action} 2>&1") if fail \ + else machine.succeed(f"{switcher} {action} 2>&1") assert_lacks(out, "switch-to-configuration line") # Perl warnings return out @@ -639,6 +656,22 @@ in { assert_lacks(out, "the following new units were started:") assert_contains(out, "would start the following units: test.service\n") + out = switch_to_specialisation("${machine}", "", action="test") + + # Ensure the service can be started when the activation script isn't in toplevel + # This is a lot like "Start a simple service", except activation-only deps could be gc-ed + out = run_switch("${nodes.machine.specialisation.simpleServiceSeparateActivationScript.configuration.system.build.separateActivationScript}/bin/switch-to-configuration"); + assert_lacks(out, "installing dummy bootloader") # test does not install a bootloader + assert_lacks(out, "stopping the following units:") + assert_lacks(out, "NOT restarting the following changed units:") + assert_contains(out, "reloading the following units: dbus.service\n") # huh + assert_lacks(out, "\nrestarting the following units:") + assert_lacks(out, "\nstarting the following units:") + assert_contains(out, "the following new units were started: test.service\n") + machine.succeed("! test -e /run/current-system/activate") + machine.succeed("! test -e /run/current-system/dry-activate") + machine.succeed("! test -e /run/current-system/bin/switch-to-configuration") + # Ensure \ works in unit names out = switch_to_specialisation("${machine}", "unitWithBackslash") assert_contains(out, "stopping the following units: test.service\n") diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing-init.nix index 5102c01278320..195c157ffb6e8 100644 --- a/nixos/tests/syncthing-init.nix +++ b/nixos/tests/syncthing-init.nix @@ -10,14 +10,14 @@ in { nodes.machine = { services.syncthing = { enable = true; - devices.${testName} = { + settings.devices.testDevice = { id = testId; }; - folders.testFolder = { + settings.folders.testFolder = { path = "/tmp/test"; - devices = [ testName ]; + devices = [ "testDevice" ]; }; - extraOptions.gui.user = "guiUser"; + settings.gui.user = "guiUser"; }; }; diff --git a/nixos/tests/syncthing-no-settings.nix b/nixos/tests/syncthing-no-settings.nix new file mode 100644 index 0000000000000..fee122b5e35c0 --- /dev/null +++ b/nixos/tests/syncthing-no-settings.nix @@ -0,0 +1,18 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "syncthing"; + meta.maintainers = with pkgs.lib.maintainers; [ chkno ]; + + nodes = { + a = { + environment.systemPackages = with pkgs; [ curl libxml2 syncthing ]; + services.syncthing = { + enable = true; + }; + }; + }; + # Test that indeed a syncthing-init.service systemd service is not created. + # + testScript = /* python */ '' + a.succeed("systemctl list-unit-files | awk '$1 == \"syncthing-init.service\" {exit 1;}'") + ''; +}) diff --git a/nixos/tests/systemd-initrd-networkd-ssh.nix b/nixos/tests/systemd-initrd-networkd-ssh.nix index 30bd1950de242..6aaa6c828f7bd 100644 --- a/nixos/tests/systemd-initrd-networkd-ssh.nix +++ b/nixos/tests/systemd-initrd-networkd-ssh.nix @@ -2,15 +2,16 @@ import ./make-test-python.nix ({ lib, ... }: { name = "systemd-initrd-network-ssh"; meta.maintainers = [ lib.maintainers.elvishjerricco ]; - nodes = with lib; { + nodes = { server = { config, pkgs, ... }: { - environment.systemPackages = [pkgs.cryptsetup]; + environment.systemPackages = [ pkgs.cryptsetup ]; boot.loader.systemd-boot.enable = true; boot.loader.timeout = 0; virtualisation = { emptyDiskImages = [ 4096 ]; useBootLoader = true; - # Booting off the encrypted disk requires an available init script from the Nix store + # Booting off the encrypted disk requires an available init script from + # the Nix store mountHostNixStore = true; useEFIBoot = true; }; @@ -26,7 +27,7 @@ import ./make-test-python.nix ({ lib, ... }: { enable = true; ssh = { enable = true; - authorizedKeys = [ (readFile ./initrd-network-ssh/id_ed25519.pub) ]; + authorizedKeys = [ (lib.readFile ./initrd-network-ssh/id_ed25519.pub) ]; port = 22; # Terrible hack so it works with useBootLoader hostKeys = [ { outPath = "${./initrd-network-ssh/ssh_host_ed25519_key}"; } ]; @@ -38,13 +39,13 @@ import ./make-test-python.nix ({ lib, ... }: { client = { config, ... }: { environment.etc = { knownHosts = { - text = concatStrings [ + text = lib.concatStrings [ "server," "${ - toString (head (splitString " " (toString - (elemAt (splitString "\n" config.networking.extraHosts) 2)))) + toString (lib.head (lib.splitString " " (toString + (lib.elemAt (lib.splitString "\n" config.networking.extraHosts) 2)))) } " - "${readFile ./initrd-network-ssh/ssh_host_ed25519_key.pub}" + "${lib.readFile ./initrd-network-ssh/ssh_host_ed25519_key.pub}" ]; }; sshKey = { diff --git a/nixos/tests/systemd-initrd-swraid.nix b/nixos/tests/systemd-initrd-swraid.nix index 0d5a1c6354d05..d87170c925742 100644 --- a/nixos/tests/systemd-initrd-swraid.nix +++ b/nixos/tests/systemd-initrd-swraid.nix @@ -14,17 +14,17 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: { boot.loader.efi.canTouchEfiVariables = true; environment.systemPackages = with pkgs; [ mdadm e2fsprogs ]; # for mdadm and mkfs.ext4 + boot.swraid = { + enable = true; + mdadmConf = '' + ARRAY /dev/md0 devices=/dev/vdb,/dev/vdc + ''; + }; boot.initrd = { systemd = { enable = true; emergencyAccess = true; }; - services.swraid = { - enable = true; - mdadmConf = '' - ARRAY /dev/md0 devices=/dev/vdb,/dev/vdc - ''; - }; kernelModules = [ "raid0" ]; }; diff --git a/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix b/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix index a8254a158016b..f6d5411aa5cad 100644 --- a/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix +++ b/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix @@ -3,7 +3,7 @@ import ./make-test-python.nix ({ lib, ... }: { name = "systemd-networkd-dhcpserver-static-leases"; meta = with lib.maintainers; { - maintainers = [ veehaitch tomfitzhenry ]; + maintainers = [ veehaitch ]; }; nodes = { router = { diff --git a/nixos/tests/systemd-networkd-dhcpserver.nix b/nixos/tests/systemd-networkd-dhcpserver.nix index a016f059456e1..cf0ccb7442118 100644 --- a/nixos/tests/systemd-networkd-dhcpserver.nix +++ b/nixos/tests/systemd-networkd-dhcpserver.nix @@ -10,7 +10,7 @@ import ./make-test-python.nix ({pkgs, ...}: { name = "systemd-networkd-dhcpserver"; meta = with pkgs.lib.maintainers; { - maintainers = [ tomfitzhenry ]; + maintainers = [ ]; }; nodes = { router = { config, pkgs, ... }: { diff --git a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix index e6bed6b9218ff..54f371e6c070f 100644 --- a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix +++ b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix @@ -67,14 +67,14 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { interfaces-config.interfaces = [ "eth1" ]; subnet6 = [ { interface = "eth1"; - subnet = "2001:DB8:F::/36"; + subnet = "2001:DB8::/32"; pd-pools = [ { - prefix = "2001:DB8:F::"; + prefix = "2001:DB8:1000::"; prefix-len = 36; delegated-len = 48; } ]; pools = [ { - pool = "2001:DB8:0000:0000:FFFF::-2001:DB8:0000:0000:FFFF::FFFF"; + pool = "2001:DB8:0000:0000::-2001:DB8:0FFF:FFFF::FFFF"; } ]; } ]; diff --git a/nixos/tests/systemd-nspawn.nix b/nixos/tests/systemd-nspawn.nix index bc77ee2a4d158..1a4251ef069e8 100644 --- a/nixos/tests/systemd-nspawn.nix +++ b/nixos/tests/systemd-nspawn.nix @@ -1,26 +1,6 @@ import ./make-test-python.nix ({pkgs, lib, ...}: let - gpgKeyring = (pkgs.runCommand "gpg-keyring" { buildInputs = [ pkgs.gnupg ]; } '' - mkdir -p $out - export GNUPGHOME=$out - cat > foo <<EOF - %echo Generating a basic OpenPGP key - %no-protection - Key-Type: DSA - Key-Length: 1024 - Subkey-Type: ELG-E - Subkey-Length: 1024 - Name-Real: Bob Foobar - Name-Email: bob@foo.bar - Expire-Date: 0 - # Do a commit here, so that we can later print "done" - %commit - %echo done - EOF - gpg --batch --generate-key foo - rm $out/S.gpg-agent $out/S.gpg-agent.* - gpg --export bob@foo.bar -a > $out/pubkey.gpg - ''); + gpgKeyring = import ./common/gpg-keyring.nix { inherit pkgs; }; nspawnImages = (pkgs.runCommand "localhost" { buildInputs = [ pkgs.coreutils pkgs.gnupg ]; } '' mkdir -p $out diff --git a/nixos/tests/systemd-shutdown.nix b/nixos/tests/systemd-shutdown.nix index dad8167f198fc..ca6754046f57a 100644 --- a/nixos/tests/systemd-shutdown.nix +++ b/nixos/tests/systemd-shutdown.nix @@ -22,6 +22,6 @@ in { machine.wait_for_console_text("Unmounting '/oldroot'") machine.wait_for_console_text("${msg}") # Don't try to sync filesystems - machine.booted = False + machine.wait_for_shutdown() ''; }) diff --git a/nixos/tests/systemd-sysupdate.nix b/nixos/tests/systemd-sysupdate.nix new file mode 100644 index 0000000000000..37811605dbb2b --- /dev/null +++ b/nixos/tests/systemd-sysupdate.nix @@ -0,0 +1,66 @@ +# Tests downloading a signed update aritfact from a server to a target machine. +# This test does not rely on the `systemd.timer` units provided by the +# `systemd-sysupdate` module but triggers the `systemd-sysupdate` service +# manually to make the test more robust. + +{ lib, pkgs, ... }: + +let + gpgKeyring = import ./common/gpg-keyring.nix { inherit pkgs; }; +in +{ + name = "systemd-sysupdate"; + + meta.maintainers = with lib.maintainers; [ nikstur ]; + + nodes = { + server = { pkgs, ... }: { + networking.firewall.enable = false; + services.nginx = { + enable = true; + virtualHosts."server" = { + root = pkgs.runCommand "sysupdate-artifacts" { buildInputs = [ pkgs.gnupg ]; } '' + mkdir -p $out + cd $out + + echo "nixos" > nixos_1.efi + sha256sum nixos_1.efi > SHA256SUMS + + export GNUPGHOME="$(mktemp -d)" + cp -R ${gpgKeyring}/* $GNUPGHOME + + gpg --batch --sign --detach-sign --output SHA256SUMS.gpg SHA256SUMS + ''; + }; + }; + }; + + target = { + systemd.sysupdate = { + enable = true; + transfers = { + "uki" = { + Source = { + Type = "url-file"; + Path = "http://server/"; + MatchPattern = "nixos_@v.efi"; + }; + Target = { + Path = "/boot/EFI/Linux"; + MatchPattern = "nixos_@v.efi"; + }; + }; + }; + }; + + environment.etc."systemd/import-pubring.gpg".source = "${gpgKeyring}/pubkey.gpg"; + }; + }; + + testScript = '' + server.wait_for_unit("nginx.service") + + target.succeed("systemctl start systemd-sysupdate") + assert "nixos" in target.wait_until_succeeds("cat /boot/EFI/Linux/nixos_1.efi", timeout=5) + ''; +} diff --git a/nixos/tests/terminal-emulators.nix b/nixos/tests/terminal-emulators.nix index 4269d05056d8c..6d76cc8e57412 100644 --- a/nixos/tests/terminal-emulators.nix +++ b/nixos/tests/terminal-emulators.nix @@ -35,6 +35,8 @@ let tests = { darktile.pkg = p: p.darktile; + deepin-terminal.pkg = p: p.deepin.deepin-terminal; + eterm.pkg = p: p.eterm; eterm.executable = "Eterm"; eterm.pinkValue = "#D40055"; @@ -72,6 +74,9 @@ let tests = { qterminal.pkg = p: p.lxqt.qterminal; qterminal.kill = true; + rio.pkg = p: p.rio; + rio.cmd = "rio -e $command"; + roxterm.pkg = p: p.roxterm; roxterm.cmd = "roxterm -e $command"; @@ -118,7 +123,7 @@ in mapAttrs (name: { pkg, executable ? name, cmd ? "SHELL=$command ${executable} maintainers = [ jjjollyjim ]; }; - machine = { pkgsInner, ... }: + nodes.machine = { pkgsInner, ... }: { imports = [ ./common/x11.nix ./common/user-account.nix ]; diff --git a/nixos/tests/twingate.nix b/nixos/tests/twingate.nix new file mode 100644 index 0000000000000..f8bede09d9f26 --- /dev/null +++ b/nixos/tests/twingate.nix @@ -0,0 +1,14 @@ +{ + name = "twingate"; + + nodes.machine.services.twingate.enable = true; + + testScript = { nodes, ... }: '' + machine.wait_for_unit("twingate.service") + machine.succeed("twingate --version | grep '${nodes.machine.services.twingate.package.version}' >&2") + machine.succeed("twingate config log-level 'debug'") + machine.systemctl("restart twingate.service") + machine.succeed("grep 'debug' /etc/twingate/log_level.conf >&2") + machine.succeed("twingate config log-level | grep 'debug' >&2") + ''; +} diff --git a/nixos/tests/typesense.nix b/nixos/tests/typesense.nix new file mode 100644 index 0000000000000..4f07a2e194be8 --- /dev/null +++ b/nixos/tests/typesense.nix @@ -0,0 +1,23 @@ +import ./make-test-python.nix ({ pkgs, ... }: let + testPort = 8108; +in { + name = "typesense"; + meta.maintainers = with pkgs.lib.maintainers; [ oddlama ]; + + nodes.machine = { ... }: { + services.typesense = { + enable = true; + apiKeyFile = pkgs.writeText "typesense-api-key" "dummy"; + settings.server = { + api-port = testPort; + api-address = "0.0.0.0"; + }; + }; + }; + + testScript = '' + machine.wait_for_unit("typesense.service") + machine.wait_for_open_port(${toString testPort}) + assert machine.succeed("curl --fail http://localhost:${toString testPort}/health") == '{"ok":true}' + ''; +}) diff --git a/nixos/tests/vscodium.nix b/nixos/tests/vscodium.nix index 3eda8b6cfb206..d817ce927ff8d 100644 --- a/nixos/tests/vscodium.nix +++ b/nixos/tests/vscodium.nix @@ -8,7 +8,7 @@ let environment.variables.NIXOS_OZONE_WL = "1"; environment.variables.DISPLAY = "do not use"; - fonts.fonts = with pkgs; [ dejavu_fonts ]; + fonts.packages = with pkgs; [ dejavu_fonts ]; }; xorg = { pkgs, ... }: { imports = [ ./common/user-account.nix ./common/x11.nix ]; diff --git a/nixos/tests/web-apps/peering-manager.nix b/nixos/tests/web-apps/peering-manager.nix index 56b7eebfadffd..3f0acd560d132 100644 --- a/nixos/tests/web-apps/peering-manager.nix +++ b/nixos/tests/web-apps/peering-manager.nix @@ -34,7 +34,7 @@ import ../make-test-python.nix ({ lib, pkgs, ... }: { "sudo -u postgres psql -c 'ALTER USER \"peering-manager\" WITH SUPERUSER;'" ) machine.succeed( - "cd ${nodes.machine.config.system.build.peeringManagerPkg}/opt/peering-manager ; peering-manager-manage test --no-input" + "cd ${nodes.machine.system.build.peeringManagerPkg}/opt/peering-manager ; peering-manager-manage test --no-input" ) ''; }) diff --git a/nixos/tests/wpa_supplicant.nix b/nixos/tests/wpa_supplicant.nix index a05a79e8367d9..8c701ca7d5f71 100644 --- a/nixos/tests/wpa_supplicant.nix +++ b/nixos/tests/wpa_supplicant.nix @@ -2,63 +2,160 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: { name = "wpa_supplicant"; meta = with lib.maintainers; { - maintainers = [ rnhmjoj ]; + maintainers = [ oddlama rnhmjoj ]; }; - nodes.machine = { ... }: { - imports = [ ../modules/profiles/minimal.nix ]; + nodes = let + machineWithHostapd = extraConfigModule: { ... }: { + imports = [ + ../modules/profiles/minimal.nix + extraConfigModule + ]; + + # add a virtual wlan interface + boot.kernelModules = [ "mac80211_hwsim" ]; + + # wireless access point + services.hostapd = { + enable = true; + radios.wlan0 = { + band = "2g"; + countryCode = "US"; + networks = { + wlan0 = { + ssid = "nixos-test-sae"; + authentication = { + mode = "wpa3-sae"; + saePasswords = [ { password = "reproducibility"; } ]; + }; + bssid = "02:00:00:00:00:00"; + }; + wlan0-1 = { + ssid = "nixos-test-mixed"; + authentication = { + mode = "wpa3-sae-transition"; + saeAddToMacAllow = true; + saePasswordsFile = pkgs.writeText "password" "reproducibility"; + wpaPasswordFile = pkgs.writeText "password" "reproducibility"; + }; + bssid = "02:00:00:00:00:01"; + }; + wlan0-2 = { + ssid = "nixos-test-wpa2"; + authentication = { + mode = "wpa2-sha256"; + wpaPassword = "reproducibility"; + }; + bssid = "02:00:00:00:00:02"; + }; + }; + }; + }; - # add a virtual wlan interface - boot.kernelModules = [ "mac80211_hwsim" ]; + # wireless client + networking.wireless = { + # the override is needed because the wifi is + # disabled with mkVMOverride in qemu-vm.nix. + enable = lib.mkOverride 0 true; + userControlled.enable = true; + interfaces = [ "wlan1" ]; + fallbackToWPA2 = lib.mkDefault true; + + # networks will be added on-demand below for the specific + # network that should be tested + + # secrets + environmentFile = pkgs.writeText "wpa-secrets" '' + PSK_NIXOS_TEST="reproducibility" + ''; + }; + }; + in { + basic = { ... }: { + imports = [ ../modules/profiles/minimal.nix ]; + + # add a virtual wlan interface + boot.kernelModules = [ "mac80211_hwsim" ]; + + # wireless client + networking.wireless = { + # the override is needed because the wifi is + # disabled with mkVMOverride in qemu-vm.nix. + enable = lib.mkOverride 0 true; + userControlled.enable = true; + interfaces = [ "wlan1" ]; + fallbackToWPA2 = true; + + networks = { + # test WPA2 fallback + mixed-wpa = { + psk = "password"; + authProtocols = [ "WPA-PSK" "SAE" ]; + }; + sae-only = { + psk = "password"; + authProtocols = [ "SAE" ]; + }; + + # secrets substitution test cases + test1.psk = "@PSK_VALID@"; # should be replaced + test2.psk = "@PSK_SPECIAL@"; # should be replaced + test3.psk = "@PSK_MISSING@"; # should not be replaced + test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced + }; - # wireless access point - services.hostapd = { - enable = true; - wpa = true; - interface = "wlan0"; - ssid = "nixos-test"; - wpaPassphrase = "reproducibility"; + # secrets + environmentFile = pkgs.writeText "wpa-secrets" '' + PSK_VALID="S0m3BadP4ssw0rd"; + # taken from https://github.com/minimaxir/big-list-of-naughty-strings + PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~"; + ''; + }; }; - # wireless client - networking.wireless = { - # the override is needed because the wifi is - # disabled with mkVMOverride in qemu-vm.nix. - enable = lib.mkOverride 0 true; - userControlled.enable = true; - interfaces = [ "wlan1" ]; - fallbackToWPA2 = true; - - networks = { - # test WPA2 fallback - mixed-wpa = { - psk = "password"; - authProtocols = [ "WPA-PSK" "SAE" ]; - }; - sae-only = { - psk = "password"; + # Test connecting to the SAE-only hotspot using SAE + machineSae = machineWithHostapd { + networking.wireless = { + fallbackToWPA2 = false; + networks.nixos-test-sae = { + psk = "@PSK_NIXOS_TEST@"; authProtocols = [ "SAE" ]; }; + }; + }; - # test network - nixos-test.psk = "@PSK_NIXOS_TEST@"; - - # secrets substitution test cases - test1.psk = "@PSK_VALID@"; # should be replaced - test2.psk = "@PSK_SPECIAL@"; # should be replaced - test3.psk = "@PSK_MISSING@"; # should not be replaced - test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced + # Test connecting to the SAE and WPA2 mixed hotspot using SAE + machineMixedUsingSae = machineWithHostapd { + networking.wireless = { + fallbackToWPA2 = false; + networks.nixos-test-mixed = { + psk = "@PSK_NIXOS_TEST@"; + authProtocols = [ "SAE" ]; + }; }; + }; - # secrets - environmentFile = pkgs.writeText "wpa-secrets" '' - PSK_NIXOS_TEST="reproducibility" - PSK_VALID="S0m3BadP4ssw0rd"; - # taken from https://github.com/minimaxir/big-list-of-naughty-strings - PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~"; - ''; + # Test connecting to the SAE and WPA2 mixed hotspot using WPA2 + machineMixedUsingWpa2 = machineWithHostapd { + networking.wireless = { + fallbackToWPA2 = true; + networks.nixos-test-mixed = { + psk = "@PSK_NIXOS_TEST@"; + authProtocols = [ "WPA-PSK-SHA256" ]; + }; + }; }; + # Test connecting to the WPA2 legacy hotspot using WPA2 + machineWpa2 = machineWithHostapd { + networking.wireless = { + fallbackToWPA2 = true; + networks.nixos-test-wpa2 = { + psk = "@PSK_NIXOS_TEST@"; + authProtocols = [ "WPA-PSK-SHA256" ]; + }; + }; + }; }; testScript = @@ -66,30 +163,47 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: config_file = "/run/wpa_supplicant/wpa_supplicant.conf" with subtest("Configuration file is inaccessible to other users"): - machine.wait_for_file(config_file) - machine.fail(f"sudo -u nobody ls {config_file}") + basic.wait_for_file(config_file) + basic.fail(f"sudo -u nobody ls {config_file}") with subtest("Secrets variables have been substituted"): - machine.fail(f"grep -q @PSK_VALID@ {config_file}") - machine.fail(f"grep -q @PSK_SPECIAL@ {config_file}") - machine.succeed(f"grep -q @PSK_MISSING@ {config_file}") - machine.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}") + basic.fail(f"grep -q @PSK_VALID@ {config_file}") + basic.fail(f"grep -q @PSK_SPECIAL@ {config_file}") + basic.succeed(f"grep -q @PSK_MISSING@ {config_file}") + basic.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}") with subtest("WPA2 fallbacks have been generated"): - assert int(machine.succeed(f"grep -c sae-only {config_file}")) == 1 - assert int(machine.succeed(f"grep -c mixed-wpa {config_file}")) == 2 + assert int(basic.succeed(f"grep -c sae-only {config_file}")) == 1 + assert int(basic.succeed(f"grep -c mixed-wpa {config_file}")) == 2 # save file for manual inspection - machine.copy_from_vm(config_file) + basic.copy_from_vm(config_file) with subtest("Daemon is running and accepting connections"): - machine.wait_for_unit("wpa_supplicant-wlan1.service") - status = machine.succeed("wpa_cli -i wlan1 status") + basic.wait_for_unit("wpa_supplicant-wlan1.service") + status = basic.succeed("wpa_cli -i wlan1 status") assert "Failed to connect" not in status, \ "Failed to connect to the daemon" - with subtest("Daemon can connect to the access point"): - machine.wait_until_succeeds( + machineSae.wait_for_unit("hostapd.service") + machineSae.copy_from_vm("/run/hostapd/wlan0.hostapd.conf") + with subtest("Daemon can connect to the SAE access point using SAE"): + machineSae.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" + ) + + with subtest("Daemon can connect to the SAE and WPA2 mixed access point using SAE"): + machineMixedUsingSae.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" + ) + + with subtest("Daemon can connect to the SAE and WPA2 mixed access point using WPA2"): + machineMixedUsingWpa2.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" + ) + + with subtest("Daemon can connect to the WPA2 access point using WPA2"): + machineWpa2.wait_until_succeeds( "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" ) ''; diff --git a/nixos/tests/wrappers.nix b/nixos/tests/wrappers.nix index 08c1ad0b6b999..391e9b42b45bd 100644 --- a/nixos/tests/wrappers.nix +++ b/nixos/tests/wrappers.nix @@ -55,6 +55,10 @@ in out = machine.succeed(cmd_as_regular(cmd)).strip() assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out) + def test_as_regular_in_userns_mapped_as_root(cmd, expected): + out = machine.succeed(f"su -l regular -c '${pkgs.util-linux}/bin/unshare -rm {cmd}'").strip() + assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out) + test_as_regular('${busybox pkgs}/bin/busybox id -u', '${toString userUid}') test_as_regular('${busybox pkgs}/bin/busybox id -ru', '${toString userUid}') test_as_regular('${busybox pkgs}/bin/busybox id -g', '${toString usersGid}') @@ -70,10 +74,27 @@ in test_as_regular('/run/wrappers/bin/sgid_root_busybox id -g', '0') test_as_regular('/run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}') + test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -u', '0') + test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -ru', '0') + test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -g', '0') + test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -rg', '0') + + test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -u', '0') + test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -ru', '0') + test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0') + test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0') + # We are only testing the permitted set, because it's easiest to look at with capsh. machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN')) machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN')) machine.succeed(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_CHOWN')) machine.fail(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_SYS_ADMIN')) + + # test a few "attacks" against which the wrapper protects itself + machine.succeed("cp /run/wrappers/bin/suid_root_busybox{,.real} /tmp/") + machine.fail(cmd_as_regular("/tmp/suid_root_busybox id -u")) + + machine.succeed("chmod u+s,a+w /run/wrappers/bin/suid_root_busybox") + machine.fail(cmd_as_regular("/run/wrappers/bin/suid_root_busybox id -u")) ''; }) |