From b3aabe32d3d5617ad0482f0dfe72b84101df9a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Mon, 4 Aug 2025 21:27:39 +0200 Subject: [PATCH 1/6] feat: deploy nginx using system manager And use docker to run tests --- flake.lock | 188 ++++++++++++++++++++++++++ flake.nix | 3 + nix/systemModules/default.nix | 14 ++ nix/systemModules/tests/conftest.py | 109 +++++++++++++++ nix/systemModules/tests/default.nix | 70 ++++++++++ nix/systemModules/tests/test_nginx.py | 3 + 6 files changed, 387 insertions(+) create mode 100644 nix/systemModules/default.nix create mode 100644 nix/systemModules/tests/conftest.py create mode 100644 nix/systemModules/tests/default.nix create mode 100644 nix/systemModules/tests/test_nginx.py diff --git a/flake.lock b/flake.lock index d2f1214141..df5ade301b 100644 --- a/flake.lock +++ b/flake.lock @@ -36,6 +36,22 @@ "type": "github" } }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" @@ -54,6 +70,28 @@ "type": "github" } }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "system-manager", + "userborn", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1756770412, + "narHash": "sha256-+uWLQZccFHwqpGqr2Yt5VsW/PbeJVTn9Dk6SHWhNRPw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "4524271976b625a4a605beefd893f270620fd751", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -115,6 +153,29 @@ "type": "github" } }, + "gitignore_2": { + "inputs": { + "nixpkgs": [ + "system-manager", + "userborn", + "pre-commit-hooks-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, "nix": { "flake": false, "locked": { @@ -256,6 +317,50 @@ "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz" } }, + "nixpkgs_3": { + "locked": { + "lastModified": 1757745802, + "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks-nix": { + "inputs": { + "flake-compat": [ + "system-manager", + "userborn", + "flake-compat" + ], + "gitignore": "gitignore_2", + "nixpkgs": [ + "system-manager", + "userborn", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1757588530, + "narHash": "sha256-tJ7A8mID3ct69n9WCvZ3PzIIl3rXTdptn/lZmqSS95U=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "b084b2c2b6bc23e83bbfe583b03664eb0b18c411", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { "devshell": "devshell", @@ -268,6 +373,7 @@ "nixpkgs": "nixpkgs_2", "nixpkgs-oldstable": "nixpkgs-oldstable", "rust-overlay": "rust-overlay", + "system-manager": "system-manager", "treefmt-nix": "treefmt-nix" } }, @@ -291,6 +397,50 @@ "type": "github" } }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "system-manager", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1768709255, + "narHash": "sha256-aigyBfxI20FRtqajVMYXHtj5gHXENY2gLAXEhfJ8/WM=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "5e8fae80726b66e9fec023d21cd3b3e638597aa9", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, + "system-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "sops-nix": "sops-nix", + "userborn": "userborn" + }, + "locked": { + "lastModified": 1768847146, + "narHash": "sha256-ALJX+yXIHN9he7lRRG45E+NW5Hf0TY+0FI9uIvblDGA=", + "owner": "numtide", + "repo": "system-manager", + "rev": "98bafdee7bdbb3499559c151a944adce37c3af3d", + "type": "github" + }, + "original": { + "owner": "numtide", + "ref": "secrets", + "repo": "system-manager", + "type": "github" + } + }, "systems": { "locked": { "lastModified": 1681028828, @@ -306,6 +456,21 @@ "type": "github" } }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "treefmt-nix": { "inputs": { "nixpkgs": [ @@ -325,6 +490,29 @@ "repo": "treefmt-nix", "type": "github" } + }, + "userborn": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_3", + "pre-commit-hooks-nix": "pre-commit-hooks-nix", + "systems": "systems_2" + }, + "locked": { + "lastModified": 1762107051, + "narHash": "sha256-8bvUPwdiUnqgBnNAuPJlbNFGProAIzlDzjiaqQugPJY=", + "owner": "JulienMalka", + "repo": "userborn", + "rev": "6e8f0d00e683049ac727b626552d5eba7f3471ff", + "type": "github" + }, + "original": { + "owner": "JulienMalka", + "ref": "stateful-users", + "repo": "userborn", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 2145185d1a..54b6c15443 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,8 @@ rust-overlay.url = "github:oxalica/rust-overlay"; treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; treefmt-nix.url = "github:numtide/treefmt-nix"; + system-manager.inputs.nixpkgs.follows = "nixpkgs"; + system-manager.url = "github:numtide/system-manager/secrets"; }; outputs = @@ -50,6 +52,7 @@ nix/nixpkgs.nix nix/packages nix/overlays + nix/systemModules ]; }); } diff --git a/nix/systemModules/default.nix b/nix/systemModules/default.nix new file mode 100644 index 0000000000..5a3c69be27 --- /dev/null +++ b/nix/systemModules/default.nix @@ -0,0 +1,14 @@ +{ + flake-parts-lib, + withSystem, + self, + ... +}: +{ + imports = [ ./tests ]; + flake = { + systemModules = { +nginx = flake-parts-lib.importApply ./nginx.nix { inherit withSystem self; }; + }; + }; +} diff --git a/nix/systemModules/tests/conftest.py b/nix/systemModules/tests/conftest.py new file mode 100644 index 0000000000..581c2dea13 --- /dev/null +++ b/nix/systemModules/tests/conftest.py @@ -0,0 +1,109 @@ +import pytest +import shutil +import subprocess +import testinfra +import time +from rich.console import Console + +console = Console() + + +def pytest_addoption(parser): + parser.addoption( + "--image-name", + action="store", + help="Docker image and tag to use for testing", + ) + parser.addoption( + "--image-path", + action="store", + help="Compressed Docker image to load for testing", + ) + + parser.addoption( + "--force-docker", + action="store_true", + help="Force using Docker instead of Podman for testing", + ) + + +@pytest.fixture(scope="session") +def host(request): + force_docker = request.config.getoption("--force-docker") + image_name = request.config.getoption("--image-name") + image_path = request.config.getoption("--image-path") + if not force_docker and shutil.which("podman"): + console.log("Using Podman for testing") + with console.status("Loading image..."): + subprocess.check_output(["podman", "load", "-q", "-i", image_path]) + podman_id = ( + subprocess.check_output( + [ + "podman", + "run", + "--cap-add", + "SYS_ADMIN", + "-d", + image_name, + ] + ) + .decode() + .strip() + ) + yield testinfra.get_host("podman://" + podman_id) + with console.status("Cleaning up..."): + subprocess.check_call( + ["podman", "rm", "-f", podman_id], stdout=subprocess.DEVNULL + ) + else: + console.log("Using Docker for testing") + with console.status("Loading image..."): + subprocess.check_output(["docker", "load", "-q", "-i", image_path]) + docker_id = ( + subprocess.check_output( + [ + "docker", + "run", + "--privileged", + "--cap-add", + "SYS_ADMIN", + "--security-opt", + "seccomp=unconfined", + "--cgroup-parent=docker.slice", + "--cgroupns", + "private", + "-d", + image_name, + ] + ) + .decode() + .strip() + ) + yield testinfra.get_host("docker://" + docker_id) + with console.status("Cleaning up..."): + subprocess.check_call( + ["docker", "rm", "-f", docker_id], stdout=subprocess.DEVNULL + ) + + +def wait_for_target(host, target, timeout=60): + start_time = time.time() + while time.time() - start_time < timeout: + result = host.run(f"systemctl is-active {target}") + if result.rc == 0: + return True + time.sleep(0.2) + return False + + +@pytest.fixture(scope="session", autouse=True) +def activate_system_manager(host): + with console.status("Waiting systemd to be ready..."): + assert wait_for_target(host, "multi-user.target") + result = host.run("activate") + console.log(result.stdout) + console.log(result.stderr) + if result.failed: + raise pytest.fail( + "System manager activation failed with return code {}".format(result.rc) + ) diff --git a/nix/systemModules/tests/default.nix b/nix/systemModules/tests/default.nix new file mode 100644 index 0000000000..cb489b7148 --- /dev/null +++ b/nix/systemModules/tests/default.nix @@ -0,0 +1,70 @@ +{ self, ... }: +{ + perSystem = + { + lib, + pkgs, + self', + ... + }: + { + packages = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux) { + check-system-manager = + let + lib = pkgs.lib; + systemManagerConfig = self.inputs.system-manager.lib.makeSystemConfig { + modules = [ + ({ + services.nginx.enable = true; + nixpkgs.hostPlatform = pkgs.system; + }) + ]; + }; + + dockerImageUbuntuWithTools = + let + tools = [ systemManagerConfig ]; + in + pkgs.dockerTools.buildLayeredImage { + name = "ubuntu-cloudimg-with-tools"; + tag = "0.2"; + created = "now"; + maxLayers = 30; + fromImage = self'.packages.docker-image-ubuntu; + compressor = "zstd"; + config = { + Env = [ + "PATH=${lib.makeBinPath tools}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ]; + Cmd = [ "/lib/systemd/systemd" ]; + }; + }; + in + pkgs.writeShellApplication { + name = "system-manager-test"; + passthru = { + inherit systemManagerConfig dockerImageUbuntuWithTools; + }; + runtimeInputs = with pkgs; [ + (python3.withPackages ( + ps: with ps; [ + requests + pytest + pytest-testinfra + rich + ] + )) + ]; + text = '' + export DOCKER_IMAGE=${dockerImageUbuntuWithTools.imageName}:${dockerImageUbuntuWithTools.imageTag} + TEST_DIR=${./.} + pytest -p no:cacheprovider -s -v "$@" $TEST_DIR --image-name=$DOCKER_IMAGE --image-path=${dockerImageUbuntuWithTools} + ''; + meta = with pkgs.lib; { + description = "Test deployment with system-manager"; + platforms = platforms.linux; + }; + }; + }; + }; +} diff --git a/nix/systemModules/tests/test_nginx.py b/nix/systemModules/tests/test_nginx.py new file mode 100644 index 0000000000..047d07956c --- /dev/null +++ b/nix/systemModules/tests/test_nginx.py @@ -0,0 +1,3 @@ +def test_nginx_service(host): + assert host.service("nginx.service").is_valid + assert host.service("nginx.service").is_running From 222972abe6ffb366198f24de4bd09c8563b284d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Tue, 12 Aug 2025 20:11:40 +0200 Subject: [PATCH 2/6] Test system manager deployment triggered by Ansible --- ansible/tasks/setup-nix.yml | 11 +++++++++++ ansible/tasks/setup-system-manager.yml | 7 +++++++ ansible/tests/nix.yaml | 5 +++++ ansible/tests/test_nix.py | 10 ++++++++++ nix/packages/ansible-test.nix | 5 ++++- nix/packages/default.nix | 2 +- 6 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 ansible/tasks/setup-nix.yml create mode 100644 ansible/tasks/setup-system-manager.yml create mode 100644 ansible/tests/nix.yaml create mode 100644 ansible/tests/test_nix.py diff --git a/ansible/tasks/setup-nix.yml b/ansible/tasks/setup-nix.yml new file mode 100644 index 0000000000..9675677dd9 --- /dev/null +++ b/ansible/tasks/setup-nix.yml @@ -0,0 +1,11 @@ +--- +- name: Check if nix is installed + ansible.builtin.command: which nix + register: nix_installed + failed_when: nix_installed.rc != 0 + ignore_errors: true + +- name: Install nix + ansible.builtin.shell: curl --proto '=https' --tlsv1.2 -sSf -L https://artifacts.nixos.org/experimental-installer | sh -s -- install --no-confirm --extra-conf 'substituters = https://cache.nixos.org https://nix-postgres-artifacts.s3.amazonaws.com' --extra-conf 'trusted-public-keys = nix-postgres-artifacts:dGZlQOvKcNEjvT7QEAJbcV6b6uk7VF/hWMjhYleiaLI=% cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=' + when: nix_installed.rc != 0 + become: true diff --git a/ansible/tasks/setup-system-manager.yml b/ansible/tasks/setup-system-manager.yml new file mode 100644 index 0000000000..990af51206 --- /dev/null +++ b/ansible/tasks/setup-system-manager.yml @@ -0,0 +1,7 @@ +--- +- name: Deploy system manager + ansible.builtin.shell: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + cd /tmp + nix run --accept-flake-config /flake#system-manager -- switch --flake /flake + become: true diff --git a/ansible/tests/nix.yaml b/ansible/tests/nix.yaml new file mode 100644 index 0000000000..4effc67ddc --- /dev/null +++ b/ansible/tests/nix.yaml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + tasks: + - import_tasks: ../tasks/setup-nix.yml + - import_tasks: ../tasks/setup-system-manager.yml diff --git a/ansible/tests/test_nix.py b/ansible/tests/test_nix.py new file mode 100644 index 0000000000..606aa5a26c --- /dev/null +++ b/ansible/tests/test_nix.py @@ -0,0 +1,10 @@ +import pytest + + +@pytest.fixture(scope="module", autouse=True) +def run_ansible(run_ansible_playbook): + run_ansible_playbook("nix.yaml", verbose=True) + + +def test_nix_service(host): + assert host.service("nix-daemon.service").is_running diff --git a/nix/packages/ansible-test.nix b/nix/packages/ansible-test.nix index 6aaec68b37..e7dfb9289f 100644 --- a/nix/packages/ansible-test.nix +++ b/nix/packages/ansible-test.nix @@ -1,4 +1,7 @@ -{ self, pkgs }: +{ + self, + pkgs, +}: pkgs.writeShellApplication { name = "ansible-test"; runtimeInputs = with pkgs; [ diff --git a/nix/packages/default.nix b/nix/packages/default.nix index f4fdaa695a..1245aa677d 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -33,9 +33,9 @@ { packages = ( { + ansible-test = pkgs.callPackage ./ansible-test.nix { inherit self; }; build-ami = pkgs.callPackage ./build-ami.nix { packer = self'.packages.packer; }; build-test-ami = pkgs.callPackage ./build-test-ami.nix { packer = self'.packages.packer; }; - ansible-test = pkgs.callPackage ./ansible-test.nix { inherit self; }; cleanup-ami = pkgs.callPackage ./cleanup-ami.nix { }; dbmate-tool = pkgs.callPackage ./dbmate-tool.nix { inherit (self.supabase) defaults; }; docker-ansible-test = pkgs.callPackage ./docker-ansible-test.nix { From 1e72e019d44455a7dbc77aff1f45e3e14352862d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Tue, 12 Aug 2025 20:11:40 +0200 Subject: [PATCH 3/6] Create default system manager configuration --- flake.nix | 1 + nix/packages/default.nix | 1 + nix/systemConfigs.nix | 30 +++++++++++++++++++++++++++++ nix/systemModules/default.nix | 4 ++-- nix/systemModules/tests/default.nix | 9 +-------- 5 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 nix/systemConfigs.nix diff --git a/flake.nix b/flake.nix index 54b6c15443..1416dd84da 100644 --- a/flake.nix +++ b/flake.nix @@ -53,6 +53,7 @@ nix/packages nix/overlays nix/systemModules + nix/systemConfigs.nix ]; }); } diff --git a/nix/packages/default.nix b/nix/packages/default.nix index 1245aa677d..3501244f10 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -80,6 +80,7 @@ inherit (self'.packages) overlayfs-on-package; }; sync-exts-versions = pkgs.callPackage ./sync-exts-versions.nix { inherit (inputs') nix-editor; }; + system-manager = inputs'.system-manager.packages.default; trigger-nix-build = pkgs.callPackage ./trigger-nix-build.nix { }; update-readme = pkgs.callPackage ./update-readme.nix { }; inherit (pkgs.callPackage ./wal-g.nix { }) wal-g-2; diff --git a/nix/systemConfigs.nix b/nix/systemConfigs.nix new file mode 100644 index 0000000000..7f50ded93e --- /dev/null +++ b/nix/systemConfigs.nix @@ -0,0 +1,30 @@ +{ self, inputs, ... }: +let + mkModules = system: [ + ({ + services.nginx.enable = true; + nixpkgs.hostPlatform = system; + }) + ]; + + systems = [ + "aarch64-linux" + "x86_64-linux" + ]; + + mkSystemConfig = system: { + name = system; + value.default = inputs.system-manager.lib.makeSystemConfig { + modules = mkModules system; + extraSpecialArgs = { + inherit self; + inherit system; + }; + }; + }; +in +{ + flake = { + systemConfigs = builtins.listToAttrs (map mkSystemConfig systems); + }; +} diff --git a/nix/systemModules/default.nix b/nix/systemModules/default.nix index 5a3c69be27..4810dff46e 100644 --- a/nix/systemModules/default.nix +++ b/nix/systemModules/default.nix @@ -7,8 +7,8 @@ { imports = [ ./tests ]; flake = { - systemModules = { -nginx = flake-parts-lib.importApply ./nginx.nix { inherit withSystem self; }; + systemModules = { + nginx = flake-parts-lib.importApply ./nginx.nix { inherit withSystem self; }; }; }; } diff --git a/nix/systemModules/tests/default.nix b/nix/systemModules/tests/default.nix index cb489b7148..104171a578 100644 --- a/nix/systemModules/tests/default.nix +++ b/nix/systemModules/tests/default.nix @@ -12,14 +12,7 @@ check-system-manager = let lib = pkgs.lib; - systemManagerConfig = self.inputs.system-manager.lib.makeSystemConfig { - modules = [ - ({ - services.nginx.enable = true; - nixpkgs.hostPlatform = pkgs.system; - }) - ]; - }; + systemManagerConfig = self.systemConfigs.${pkgs.system}.default; dockerImageUbuntuWithTools = let From b88d785f38584f8cfecda56cd88cc6f9e752a788 Mon Sep 17 00:00:00 2001 From: Yvan Sraka Date: Fri, 19 Sep 2025 10:16:56 +0200 Subject: [PATCH 4/6] chores: add nix run .#ansible-test and .#check-system-module to github actions workflows --- .github/workflows/ansible-test.yml | 27 ++++++++++++++++++++++ .github/workflows/check-system-manager.yml | 27 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 .github/workflows/ansible-test.yml create mode 100644 .github/workflows/check-system-manager.yml diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml new file mode 100644 index 0000000000..2b75b5a22f --- /dev/null +++ b/.github/workflows/ansible-test.yml @@ -0,0 +1,27 @@ +name: Ansible Test + +on: + pull_request: + merge_group: + workflow_dispatch: + +permissions: + id-token: write + +jobs: + ansible-test: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: supabase/postgres/.github/actions/shared-checkout@HEAD + + - name: Install nix + uses: ./.github/actions/nix-install-ephemeral + with: + push-to-cache: 'true' + env: + DEV_AWS_ROLE: ${{ secrets.DEV_AWS_ROLE }} + NIX_SIGN_SECRET_KEY: ${{ secrets.NIX_SIGN_SECRET_KEY }} + + - name: Run Ansible Test + run: nix run .#ansible-test diff --git a/.github/workflows/check-system-manager.yml b/.github/workflows/check-system-manager.yml new file mode 100644 index 0000000000..3da3a3ce1a --- /dev/null +++ b/.github/workflows/check-system-manager.yml @@ -0,0 +1,27 @@ +name: Check System Manager + +on: + pull_request: + merge_group: + workflow_dispatch: + +permissions: + id-token: write + +jobs: + check-system-manager: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: supabase/postgres/.github/actions/shared-checkout@HEAD + + - name: Install nix + uses: ./.github/actions/nix-install-ephemeral + with: + push-to-cache: 'true' + env: + DEV_AWS_ROLE: ${{ secrets.DEV_AWS_ROLE }} + NIX_SIGN_SECRET_KEY: ${{ secrets.NIX_SIGN_SECRET_KEY }} + + - name: Run check-system-manager + run: nix run .#check-system-manager From fc79b03062493f1839a92138c90bc7296ea6926f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Fri, 6 Feb 2026 15:59:00 +0100 Subject: [PATCH 5/6] feat: replace Docker-based system-manager tests with container test framework Switch from building Docker images and running pytest+testinfra externally to using system-manager's built-in makeContainerTest API backed by systemd-nspawn. The test is now a Nix check derivation that runs inside the build sandbox. It requires auto-allocating UIDs in the ephemeral Nix installation, which is now enabled by default in the GitHub Action. --- .../actions/nix-install-ephemeral/action.yml | 2 + .github/workflows/check-system-manager.yml | 2 +- flake.lock | 80 ++++--------- flake.nix | 2 +- nix/systemModules/tests/conftest.py | 109 ------------------ nix/systemModules/tests/default.nix | 64 +++------- nix/systemModules/tests/test_nginx.py | 3 - 7 files changed, 43 insertions(+), 219 deletions(-) delete mode 100644 nix/systemModules/tests/conftest.py delete mode 100644 nix/systemModules/tests/test_nginx.py diff --git a/.github/actions/nix-install-ephemeral/action.yml b/.github/actions/nix-install-ephemeral/action.yml index 7edeb02def..f830c7757c 100644 --- a/.github/actions/nix-install-ephemeral/action.yml +++ b/.github/actions/nix-install-ephemeral/action.yml @@ -47,4 +47,6 @@ runs: substituters = https://cache.nixos.org https://nix-postgres-artifacts.s3.amazonaws.com trusted-public-keys = nix-postgres-artifacts:dGZlQOvKcNEjvT7QEAJbcV6b6uk7VF/hWMjhYleiaLI= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ${{ inputs.push-to-cache == 'true' && 'post-build-hook = /etc/nix/upload-to-cache.sh' || '' }} + extra-experimental-features = auto-allocate-uids cgroups + auto-allocate-uids = true max-jobs = 4 diff --git a/.github/workflows/check-system-manager.yml b/.github/workflows/check-system-manager.yml index 3da3a3ce1a..9c1fb934d5 100644 --- a/.github/workflows/check-system-manager.yml +++ b/.github/workflows/check-system-manager.yml @@ -24,4 +24,4 @@ jobs: NIX_SIGN_SECRET_KEY: ${{ secrets.NIX_SIGN_SECRET_KEY }} - name: Run check-system-manager - run: nix run .#check-system-manager + run: nix build .#checks.x86_64-linux.check-system-manager -L diff --git a/flake.lock b/flake.lock index df5ade301b..d258cd1ce3 100644 --- a/flake.lock +++ b/flake.lock @@ -39,11 +39,11 @@ "flake-compat_2": { "flake": false, "locked": { - "lastModified": 1747046372, - "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "owner": "edolstra", "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { @@ -79,11 +79,11 @@ ] }, "locked": { - "lastModified": 1756770412, - "narHash": "sha256-+uWLQZccFHwqpGqr2Yt5VsW/PbeJVTn9Dk6SHWhNRPw=", + "lastModified": 1768135262, + "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "4524271976b625a4a605beefd893f270620fd751", + "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", "type": "github" }, "original": { @@ -317,22 +317,6 @@ "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz" } }, - "nixpkgs_3": { - "locked": { - "lastModified": 1757745802, - "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "pre-commit-hooks-nix": { "inputs": { "flake-compat": [ @@ -348,11 +332,11 @@ ] }, "locked": { - "lastModified": 1757588530, - "narHash": "sha256-tJ7A8mID3ct69n9WCvZ3PzIIl3rXTdptn/lZmqSS95U=", + "lastModified": 1769069492, + "narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "b084b2c2b6bc23e83bbfe583b03664eb0b18c411", + "rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23", "type": "github" }, "original": { @@ -397,46 +381,23 @@ "type": "github" } }, - "sops-nix": { - "inputs": { - "nixpkgs": [ - "system-manager", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1768709255, - "narHash": "sha256-aigyBfxI20FRtqajVMYXHtj5gHXENY2gLAXEhfJ8/WM=", - "owner": "Mic92", - "repo": "sops-nix", - "rev": "5e8fae80726b66e9fec023d21cd3b3e638597aa9", - "type": "github" - }, - "original": { - "owner": "Mic92", - "repo": "sops-nix", - "type": "github" - } - }, "system-manager": { "inputs": { "nixpkgs": [ "nixpkgs" ], - "sops-nix": "sops-nix", "userborn": "userborn" }, "locked": { - "lastModified": 1768847146, - "narHash": "sha256-ALJX+yXIHN9he7lRRG45E+NW5Hf0TY+0FI9uIvblDGA=", + "lastModified": 1770630329, + "narHash": "sha256-Q0/JNws9SxY9wE/mCQ6WTA85MIgrJ+M0HDcJVUlk9Ds=", "owner": "numtide", "repo": "system-manager", - "rev": "98bafdee7bdbb3499559c151a944adce37c3af3d", + "rev": "442db31401bdbdadc59356232153448e05bce1db", "type": "github" }, "original": { "owner": "numtide", - "ref": "secrets", "repo": "system-manager", "type": "github" } @@ -495,21 +456,24 @@ "inputs": { "flake-compat": "flake-compat_2", "flake-parts": "flake-parts_2", - "nixpkgs": "nixpkgs_3", + "nixpkgs": [ + "system-manager", + "nixpkgs" + ], "pre-commit-hooks-nix": "pre-commit-hooks-nix", "systems": "systems_2" }, "locked": { - "lastModified": 1762107051, - "narHash": "sha256-8bvUPwdiUnqgBnNAuPJlbNFGProAIzlDzjiaqQugPJY=", - "owner": "JulienMalka", + "lastModified": 1770377964, + "narHash": "sha256-q2pnlX2IW0kg80GLFnwWd/GigIpkuZnyKPLhrgJql3E=", + "owner": "jfroche", "repo": "userborn", - "rev": "6e8f0d00e683049ac727b626552d5eba7f3471ff", + "rev": "55c2cd7952c207a62736a5bbd9499ea73da18d24", "type": "github" }, "original": { - "owner": "JulienMalka", - "ref": "stateful-users", + "owner": "jfroche", + "ref": "system-manager", "repo": "userborn", "type": "github" } diff --git a/flake.nix b/flake.nix index 1416dd84da..bf59d42987 100644 --- a/flake.nix +++ b/flake.nix @@ -30,7 +30,7 @@ treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; treefmt-nix.url = "github:numtide/treefmt-nix"; system-manager.inputs.nixpkgs.follows = "nixpkgs"; - system-manager.url = "github:numtide/system-manager/secrets"; + system-manager.url = "github:numtide/system-manager"; }; outputs = diff --git a/nix/systemModules/tests/conftest.py b/nix/systemModules/tests/conftest.py deleted file mode 100644 index 581c2dea13..0000000000 --- a/nix/systemModules/tests/conftest.py +++ /dev/null @@ -1,109 +0,0 @@ -import pytest -import shutil -import subprocess -import testinfra -import time -from rich.console import Console - -console = Console() - - -def pytest_addoption(parser): - parser.addoption( - "--image-name", - action="store", - help="Docker image and tag to use for testing", - ) - parser.addoption( - "--image-path", - action="store", - help="Compressed Docker image to load for testing", - ) - - parser.addoption( - "--force-docker", - action="store_true", - help="Force using Docker instead of Podman for testing", - ) - - -@pytest.fixture(scope="session") -def host(request): - force_docker = request.config.getoption("--force-docker") - image_name = request.config.getoption("--image-name") - image_path = request.config.getoption("--image-path") - if not force_docker and shutil.which("podman"): - console.log("Using Podman for testing") - with console.status("Loading image..."): - subprocess.check_output(["podman", "load", "-q", "-i", image_path]) - podman_id = ( - subprocess.check_output( - [ - "podman", - "run", - "--cap-add", - "SYS_ADMIN", - "-d", - image_name, - ] - ) - .decode() - .strip() - ) - yield testinfra.get_host("podman://" + podman_id) - with console.status("Cleaning up..."): - subprocess.check_call( - ["podman", "rm", "-f", podman_id], stdout=subprocess.DEVNULL - ) - else: - console.log("Using Docker for testing") - with console.status("Loading image..."): - subprocess.check_output(["docker", "load", "-q", "-i", image_path]) - docker_id = ( - subprocess.check_output( - [ - "docker", - "run", - "--privileged", - "--cap-add", - "SYS_ADMIN", - "--security-opt", - "seccomp=unconfined", - "--cgroup-parent=docker.slice", - "--cgroupns", - "private", - "-d", - image_name, - ] - ) - .decode() - .strip() - ) - yield testinfra.get_host("docker://" + docker_id) - with console.status("Cleaning up..."): - subprocess.check_call( - ["docker", "rm", "-f", docker_id], stdout=subprocess.DEVNULL - ) - - -def wait_for_target(host, target, timeout=60): - start_time = time.time() - while time.time() - start_time < timeout: - result = host.run(f"systemctl is-active {target}") - if result.rc == 0: - return True - time.sleep(0.2) - return False - - -@pytest.fixture(scope="session", autouse=True) -def activate_system_manager(host): - with console.status("Waiting systemd to be ready..."): - assert wait_for_target(host, "multi-user.target") - result = host.run("activate") - console.log(result.stdout) - console.log(result.stderr) - if result.failed: - raise pytest.fail( - "System manager activation failed with return code {}".format(result.rc) - ) diff --git a/nix/systemModules/tests/default.nix b/nix/systemModules/tests/default.nix index 104171a578..e959f63216 100644 --- a/nix/systemModules/tests/default.nix +++ b/nix/systemModules/tests/default.nix @@ -1,62 +1,32 @@ -{ self, ... }: +{ self, inputs, ... }: { perSystem = { lib, pkgs, - self', ... }: { - packages = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux) { + checks = lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { check-system-manager = let - lib = pkgs.lib; - systemManagerConfig = self.systemConfigs.${pkgs.system}.default; - - dockerImageUbuntuWithTools = - let - tools = [ systemManagerConfig ]; - in - pkgs.dockerTools.buildLayeredImage { - name = "ubuntu-cloudimg-with-tools"; - tag = "0.2"; - created = "now"; - maxLayers = 30; - fromImage = self'.packages.docker-image-ubuntu; - compressor = "zstd"; - config = { - Env = [ - "PATH=${lib.makeBinPath tools}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - ]; - Cmd = [ "/lib/systemd/systemd" ]; - }; - }; + toplevel = self.systemConfigs.${pkgs.system}.default; in - pkgs.writeShellApplication { - name = "system-manager-test"; - passthru = { - inherit systemManagerConfig dockerImageUbuntuWithTools; - }; - runtimeInputs = with pkgs; [ - (python3.withPackages ( - ps: with ps; [ - requests - pytest - pytest-testinfra - rich - ] - )) - ]; - text = '' - export DOCKER_IMAGE=${dockerImageUbuntuWithTools.imageName}:${dockerImageUbuntuWithTools.imageTag} - TEST_DIR=${./.} - pytest -p no:cacheprovider -s -v "$@" $TEST_DIR --image-name=$DOCKER_IMAGE --image-path=${dockerImageUbuntuWithTools} + inputs.system-manager.lib.containerTest.makeContainerTest { + hostPkgs = pkgs; + name = "check-system-manager"; + inherit toplevel; + testScript = '' + start_all() + + machine.wait_for_unit("multi-user.target") + + machine.activate() + machine.wait_for_unit("system-manager.target") + + with subtest("Verify nginx service"): + assert machine.service("nginx").is_running, "nginx should be running" ''; - meta = with pkgs.lib; { - description = "Test deployment with system-manager"; - platforms = platforms.linux; - }; }; }; }; diff --git a/nix/systemModules/tests/test_nginx.py b/nix/systemModules/tests/test_nginx.py deleted file mode 100644 index 047d07956c..0000000000 --- a/nix/systemModules/tests/test_nginx.py +++ /dev/null @@ -1,3 +0,0 @@ -def test_nginx_service(host): - assert host.service("nginx.service").is_valid - assert host.service("nginx.service").is_running From fc07fb62e3c3b7b67c1bbee5b6e53f1166da301c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Fri, 6 Feb 2026 16:33:23 +0100 Subject: [PATCH 6/6] fix: ansible-lint violations in test playbook --- ansible/tests/nix.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ansible/tests/nix.yaml b/ansible/tests/nix.yaml index 4effc67ddc..44e5fa19e0 100644 --- a/ansible/tests/nix.yaml +++ b/ansible/tests/nix.yaml @@ -1,5 +1,8 @@ --- -- hosts: localhost +- name: Test nix setup and system-manager deployment + hosts: localhost tasks: - - import_tasks: ../tasks/setup-nix.yml - - import_tasks: ../tasks/setup-system-manager.yml + - name: Setup nix + ansible.builtin.import_tasks: ../tasks/setup-nix.yml + - name: Setup system-manager + ansible.builtin.import_tasks: ../tasks/setup-system-manager.yml