From 0f363a1cb4f1a29a95f94c27ca22e4bcca93224f Mon Sep 17 00:00:00 2001 From: r-vdp Date: Thu, 12 Mar 2026 17:47:30 +0100 Subject: [PATCH 1/2] nixos: replace container activation scripts docker: move /init symlink creation from activationScript to installBootLoader, which is the mechanism switch-to-configuration already uses for this purpose. lxc: remove redundant activation script. The installBootLoader hook was already handling /sbin/init. incus: replace activationScript with a oneshot systemd service that uses ConditionPathExists to create /etc/nixos/configuration.nix only on first boot. See: https://github.com/NixOS/nixpkgs/issues/475305 --- .../scripts/incus/incus-container-image.nix | 22 ++++++++++++------- .../incus/incus-virtual-machine-image.nix | 22 ++++++++++++------- nixos/modules/profiles/docker-container.nix | 7 +++--- .../modules/virtualisation/lxc-container.nix | 3 --- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/nixos/maintainers/scripts/incus/incus-container-image.nix b/nixos/maintainers/scripts/incus/incus-container-image.nix index 9faf6a5d87ad..989f583307fe 100644 --- a/nixos/maintainers/scripts/incus/incus-container-image.nix +++ b/nixos/maintainers/scripts/incus/incus-container-image.nix @@ -13,18 +13,24 @@ ]; }; - # copy the config for nixos-rebuild - system.activationScripts.config = + # Create a default configuration.nix on first boot so nixos-rebuild works + # out of the box. + systemd.services.incus-create-nixos-config = let - config = pkgs.replaceVars ./incus-container-image-inner.nix { + configFile = pkgs.replaceVars ./incus-container-image-inner.nix { stateVersion = lib.trivial.release; }; in - '' - if [ ! -e /etc/nixos/configuration.nix ]; then - install -m 0644 -D ${config} /etc/nixos/configuration.nix - fi - ''; + { + description = "Create default NixOS configuration for Incus"; + wantedBy = [ "multi-user.target" ]; + unitConfig.ConditionPathExists = "!/etc/nixos/configuration.nix"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.coreutils}/bin/install -m 0644 -D ${configFile} /etc/nixos/configuration.nix"; + }; + }; networking = { dhcpcd.enable = false; diff --git a/nixos/maintainers/scripts/incus/incus-virtual-machine-image.nix b/nixos/maintainers/scripts/incus/incus-virtual-machine-image.nix index db60160c9c77..f06b43dc2675 100644 --- a/nixos/maintainers/scripts/incus/incus-virtual-machine-image.nix +++ b/nixos/maintainers/scripts/incus/incus-virtual-machine-image.nix @@ -13,18 +13,24 @@ ]; }; - # copy the config for nixos-rebuild - system.activationScripts.config = + # Create a default configuration.nix on first boot so nixos-rebuild works + # out of the box. + systemd.services.incus-create-nixos-config = let - config = pkgs.replaceVars ./incus-virtual-machine-image-inner.nix { + configFile = pkgs.replaceVars ./incus-virtual-machine-image-inner.nix { stateVersion = lib.trivial.release; }; in - '' - if [ ! -e /etc/nixos/configuration.nix ]; then - install -m 0644 -D ${config} /etc/nixos/configuration.nix - fi - ''; + { + description = "Create default NixOS configuration for Incus"; + wantedBy = [ "multi-user.target" ]; + unitConfig.ConditionPathExists = "!/etc/nixos/configuration.nix"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${pkgs.coreutils}/bin/install -m 0644 -D ${configFile} /etc/nixos/configuration.nix"; + }; + }; # Network networking = { diff --git a/nixos/modules/profiles/docker-container.nix b/nixos/modules/profiles/docker-container.nix index 66d69361d584..06329d5d82f1 100644 --- a/nixos/modules/profiles/docker-container.nix +++ b/nixos/modules/profiles/docker-container.nix @@ -57,8 +57,9 @@ in ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system ''; - # Install new init script - system.activationScripts.installInitScript = '' - ln -fs $systemConfig/init /init + # Update /init symlink when switching configurations so the container + # boots the new system on restart. + system.build.installBootLoader = pkgs.writeShellScript "install-docker-init" '' + ${pkgs.coreutils}/bin/ln -fs "$1/init" /init ''; } diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix index a6964b8b2c9a..1eb5891b953b 100644 --- a/nixos/modules/virtualisation/lxc-container.nix +++ b/nixos/modules/virtualisation/lxc-container.nix @@ -119,8 +119,5 @@ systemd.packages = [ pkgs.distrobuilder.generator ]; - system.activationScripts.installInitScript = lib.mkForce '' - ln -fs $systemConfig/init /sbin/init - ''; }; } From 61425f107b2b26c6ed11fd7b2fd926b99b8fedef Mon Sep 17 00:00:00 2001 From: r-vdp Date: Wed, 18 Mar 2026 16:55:18 +0100 Subject: [PATCH 2/2] nixos/tests/incus: test config creation and switch-to-configuration Add subtests to verify the activation script replacements: - config create service creates /etc/nixos/configuration.nix on first boot - service does not overwrite existing configuration (ConditionPathExists) - switch-to-configuration switch calls installBootLoader, creating /sbin/init (container-only, since VMs use a real bootloader) --- nixos/tests/incus/incus-tests-module.nix | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/nixos/tests/incus/incus-tests-module.nix b/nixos/tests/incus/incus-tests-module.nix index 085dbfbc8750..8d8752fde81f 100644 --- a/nixos/tests/incus/incus-tests-module.nix +++ b/nixos/tests/incus/incus-tests-module.nix @@ -156,6 +156,16 @@ in server.succeed(f"incus exec {instance_name} -- test -e /dev/tpm0") server.succeed(f"incus exec {instance_name} -- test -e /dev/tpmrm0") + + with subtest("[${image_id}] default configuration.nix is created on first boot"): + server.succeed(f"incus exec {instance_name} -- test -f /etc/nixos/configuration.nix") + + with subtest("[${image_id}] configuration.nix create service does not overwrite existing config"): + server.succeed(f"incus exec {instance_name} -- systemctl restart incus-create-nixos-config.service") + status = server.succeed( + f"incus exec {instance_name} -- systemctl show -p ActiveState incus-create-nixos-config.service" + ).strip() + assert "inactive" in status, f"Expected inactive (ConditionPathExists should prevent start), got {status}" '' # # container specific @@ -164,6 +174,21 @@ in lib.optionalString (config.type == "container") # python '' + with subtest("[${image_id}] switch-to-configuration updates /sbin/init via installBootLoader"): + # Remove /sbin/init so we can verify installBootLoader recreates it + server.succeed(f"incus exec {instance_name} -- rm -f /sbin/init") + server.fail(f"incus exec {instance_name} -- test -e /sbin/init") + + server.succeed( + f"incus exec {instance_name} -- /run/current-system/bin/switch-to-configuration switch" + ) + + # Verify installBootLoader recreated /sbin/init pointing to the system's init + server.succeed(f"incus exec {instance_name} -- test -x /sbin/init") + target = server.succeed(f"incus exec {instance_name} -- readlink -f /sbin/init").strip() + current = server.succeed(f"incus exec {instance_name} -- readlink -f /run/current-system/init").strip() + assert target == current, f"/sbin/init -> {target}, expected {current}" + # TODO troubleshoot VM hot memory resizing which was introduced in 6.12 with subtest("[${image_id}] memory limits can be hotplug changed"): server.set_instance_config(instance_name, "limits.memory 512MB")