Skip to content

nixos/etc-overlay: ship empty /etc/machine-id placeholder on immutable /etc - #529950

Merged
r-vdp merged 3 commits into
NixOS:masterfrom
r-vdp:machine-id-immutable-etc
Jun 26, 2026
Merged

nixos/etc-overlay: ship empty /etc/machine-id placeholder on immutable /etc#529950
r-vdp merged 3 commits into
NixOS:masterfrom
r-vdp:machine-id-immutable-etc

Conversation

@r-vdp

@r-vdp r-vdp commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

#523894 made /etc/machine-id a symlink to /var/lib/nixos/machine-id and
created the target with content uninitialized.
Unfortunately the generated id is never committed, because:

  • systemd-machine-id-commit.service has ConditionPathIsReadWrite=/etc,
    which is always false on the read-only overlay, so the unit is skipped.
  • Even with that condition cleared, machine_id_commit() checks whether
    /etc/machine-id itself is a mountpoint (no symlink following), finds the
    symlink isn't, and exits without writing.

So /var/lib/nixos/machine-id stays uninitialized forever, every boot is
ConditionFirstBoot=yes, and the machine-id is random on every boot.

I think it's more correct to have an empty regular file in the etc image so that:

  • systemd bind-mounts /run/machine-id over it for the session.
  • ConditionFirstBoot is no, which is the only logical default for a file
    that can never change. The previous behaviour fired first-boot units on every boot.
  • systemd-machine-id-commit is cleanly condition-skipped, no failed unit.
  • Users who want persistence can bind a writable backing file onto the file
    and have commit write through it, which does not work with a symlink.

The whole attrset is mkDefault, so any user definition of
environment.etc.machine-id overrides it.

For users who need a persistent ID, there are two main options:

  • bind-mount a writable backing file from initrd (and clear the commit condition)
  • pass systemd.machine_id= / systemd.machine_id=firmware on the kernel command line.

cc @anglesideangle @rebmit @nikstur

Things done

@nixpkgs-ci nixpkgs-ci Bot added 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 9.needs: reviewer This PR currently has no reviewers requested and needs attention. 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog This PR adds or changes release notes 8.has: module (update) This PR changes an existing module in `nixos/` 8.has: documentation This PR adds or changes documentation labels Jun 9, 2026
@r-vdp
r-vdp requested a review from rebmit June 9, 2026 14:17
@r-vdp
r-vdp force-pushed the machine-id-immutable-etc branch from 0b89cff to 7c94f36 Compare June 9, 2026 15:05
@r-vdp
r-vdp requested review from WilliButz and nikstur June 9, 2026 15:08
@rebmit

rebmit commented Jun 9, 2026

Copy link
Copy Markdown
Member

the generated id is never committed

You're right, I missed that in the previous PR. The symlink setup requires modifying systemd-machine-id-commit.service like this to work, which guarantees proper first-boot semantics as long as /var/lib/nixos is persisted.

  environment.etc."machine-id" = {
    source = "/var/lib/nixos/etc/machine-id";
    mode = "direct-symlink";
  };

  boot.initrd.systemd.tmpfiles.settings.machine-id."/sysroot/var/lib/nixos/etc/machine-id".f = {
    argument = "uninitialized";
  };

  systemd.services.systemd-machine-id-commit = {
    unitConfig.ConditionPathIsMountPoint = [
      ""
      "/var/lib/nixos/etc/machine-id"
    ];
    serviceConfig.ExecStart = [
      ""
      "systemd-machine-id-setup --commit --root /var/lib/nixos"
    ];
  };

I think it's more correct to have an empty regular file in the etc image

That makes sense. Though personally, I still slightly prefer the symlink approach for consistency with how we handle userborn / sysusers.

@nixpkgs-ci nixpkgs-ci Bot removed the 9.needs: reviewer This PR currently has no reviewers requested and needs attention. label Jun 9, 2026
@r-vdp

r-vdp commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Yeah, I would prefer to make minimal changes to upstream services though, and stay closer to what upstream expects, to lower the maintenance burden.

@nikstur

nikstur commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

@WilliButz might be able to give more context on how this used to work with symlinks and why now it doesn't.

@WilliButz

Copy link
Copy Markdown
Member

There is a bunch of context for this in this upstream issue: systemd/systemd#39438

The underlying change mentioned there breaks a couple of setups that do not seem to be supported/intended by upstream (which I gathered from discussions in person after the issue stalled), i.e. any setup that tries to persist only the machine-id outside /etc via symlink or bind-mount. Breaking in the sense that symlinks are no longer followed (since 258) and a bind-mounted machine-id file doesn't work without changes to upstream units. Same for systemd-firstboo, which also no longer works in such a setup without changes to upstream units. (as mentioned in comments above)

The two explicitly supported options seem to be limited to persisting all of /etc, including a generated machine-id, or not persisting a generated machine-id at all.

Anything that does not fit with how upstream expects a system to be set up could break just like the symlink setup did. So I think the correct thing to do would be to find some way to align our and upstream's expectations on this. The linked issue above was an attempt to communicate an actual use case that is not "just" a niche NixOS thing, but could be valuable to other platforms/setups as well (as shown by others that commented there) and then for example simply going back to resolving symlinks again, but with explicit support. Unfortunately the discussion on the issue stalled, but I don't think it's entirely hopeless.

@r-vdp

r-vdp commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I guess the question remains though, what do we do right now? Because what we currently have on master is not working.

Do we ship this PR as a solution for now that's at least workable, until upstream comes with a better fix? Or does anyone have another suggestion?

@rebmit rebmit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we ship this PR as a solution for now that's at least workable, until upstream comes with a better fix?

I agree.

@nixpkgs-ci nixpkgs-ci Bot added the 12.approvals: 1 This PR was reviewed and approved by one person. label Jun 10, 2026
@anglesideangle

Copy link
Copy Markdown
Contributor

Sorry for not catching this in the original PR. It's unfortunate that systemd does not consider symlinked /etc/machine-id to be supported.

For users who need a persistent ID, there are two main options:

  • bind-mount a writable backing file from initrd (and clear the commit condition)
  • pass systemd.machine_id= / systemd.machine_id=firmware on the kernel command line.

I think it would be useful to document this in the etc-overlay section of the nixos manual

@nixpkgs-ci nixpkgs-ci Bot added 2.status: merge conflict This PR has merge conflicts with the target branch 12.approvals: 2 This PR was reviewed and approved by two persons. and removed 12.approvals: 1 This PR was reviewed and approved by one person. labels Jun 14, 2026
r-vdp added 3 commits June 26, 2026 10:50
The symlink to /var/lib/nixos/machine-id never persists the ID,
systemd-machine-id-commit.service requires a writable /etc, and
machine_id_commit() does not follow symlinks for its mountpoint check.
So the backing file stays "uninitialized", every boot is
ConditionFirstBoot=yes, and the machine-id is random per boot.

Ship an empty regular file instead, systemd then overlays /run/machine-id
for the session, ConditionFirstBoot is correctly "no", commit is
cleanly condition-skipped, and the file is a usable bind target for
users that want persistence.

Fixes NixOS#523878
The upstream unit has ConditionPathIsReadWrite=/etc, which is always
false on the read-only overlay, so the previous commit alone requires
users that want persistence to override the conditions themselves before
the generated ID can be written back.

Use ConditionFirstBoot instead, with the empty placeholder
first-boot is "no" and commit stays skipped, but when a writable
file containing "uninitialized" is bind-mounted over /etc/machine-id,
first-boot is "yes" once and commit writes the ID through the bind.
@r-vdp
r-vdp force-pushed the machine-id-immutable-etc branch from 7c94f36 to f438cd8 Compare June 26, 2026 08:52
@r-vdp
r-vdp enabled auto-merge June 26, 2026 08:55
@r-vdp
r-vdp added this pull request to the merge queue Jun 26, 2026
@nixpkgs-ci nixpkgs-ci Bot removed the 2.status: merge conflict This PR has merge conflicts with the target branch label Jun 26, 2026
Merged via the queue into NixOS:master with commit 5e6a9f4 Jun 26, 2026
26 checks passed
@r-vdp
r-vdp deleted the machine-id-immutable-etc branch June 26, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog This PR adds or changes release notes 8.has: documentation This PR adds or changes documentation 8.has: module (update) This PR changes an existing module in `nixos/` 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 12.approvals: 2 This PR was reviewed and approved by two persons.

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

6 participants