diff --git a/nixos/modules/system/etc/etc-activation.nix b/nixos/modules/system/etc/etc-activation.nix index 31842fc9c0c6..873a53fdae6c 100644 --- a/nixos/modules/system/etc/etc-activation.nix +++ b/nixos/modules/system/etc/etc-activation.nix @@ -182,16 +182,18 @@ }) (lib.mkIf (config.system.etc.overlay.enable && !config.system.etc.overlay.mutable) { - # Systemd requires /etc/machine-id exists or can be initialized on first - # boot. This file should not be part of an image or system config because - # it is unique to the machine, so it is initialized at first boot and - # persisted in the system state directory, /var/lib/nixos. - environment.etc."machine-id".source = lib.mkDefault "/var/lib/nixos/machine-id"; - boot.initrd.systemd.tmpfiles.settings.machine-id."/sysroot/var/lib/nixos/machine-id".f = - lib.mkDefault - { - argument = "uninitialized"; - }; + # An empty regular file means systemd will bind mount /run/machine-id + # on top, and ConditionFirstBoot will be false (the file will never + # change, so this makes sense). See machine-id(5) "First Boot + # Semantics". It also serves as a target to bind mount an actually + # persistent machine-id onto. A symlink doesn't work here since + # systemd-machine-id-commit checks /etc/machine-id itself for being a + # mountpoint without following symlinks, so it would never commit + # through a symlink. + environment.etc.machine-id = lib.mkDefault { + text = ""; + mode = "0444"; + }; }) ]; diff --git a/nixos/tests/activation/etc-overlay-immutable.nix b/nixos/tests/activation/etc-overlay-immutable.nix index 0f529161e58a..febac2e016f2 100644 --- a/nixos/tests/activation/etc-overlay-immutable.nix +++ b/nixos/tests/activation/etc-overlay-immutable.nix @@ -76,6 +76,19 @@ with subtest("/etc is mounted as an overlay"): machine.succeed("findmnt --kernel --type overlay /etc") + with subtest("machine-id is set up without first-boot looping"): + # The baked-in placeholder is an empty regular file; systemd overlays + # /run/machine-id on top so the session has a valid ID while the + # commit service is condition-skipped (no writable /etc to commit to). + machine.succeed("stat --format '%F' /etc/machine-id | tee /dev/stderr | grep -q 'regular'") + machine.succeed("grep -qE '^[0-9a-f]{32}$' /etc/machine-id") + machine.fail("journalctl -b | grep -F 'System cannot boot: Missing /etc/machine-id'") + machine.fail("journalctl -b | grep -F 'Detected first boot'") + machine.fail("systemctl is-failed --quiet systemd-machine-id-commit.service") + assert machine.succeed( + "systemctl show -P ConditionResult systemd-machine-id-commit.service" + ).strip() == "no" + with subtest("modes work correctly"): machine.succeed("stat --format '%F' /etc/modetest | tee /dev/stderr | grep -q 'regular file'") machine.succeed("stat --format '%F' /etc/modetest2 | tee /dev/stderr | grep -q 'regular file'")