Summary
CreateUser in internal/post/user.go uses root = sysroot for composefs-native deployments, but sysroot/etc/ is a ghost btrfs directory — writes to it are silently discarded after first boot.
Root cause
// user.go:30
if isComposeFsNative(sysroot) {
root = sysroot // WRONG: sysroot/etc is not writable after boot
} else {
deployDir, _ := DeploymentDirFn(sysroot)
root = deployDir // correct for ostree
}
On composefs-native systems:
- The writable
/etc is bind-mounted from state/deploy/<HASH>/etc/
sysroot/etc/ is an orphaned btrfs directory that nothing mounts
useradd --root sysroot writes to sysroot/etc/passwd etc., which is silently discarded after first boot
- The user appears created during install but is gone on first login
Compare with WriteHostname and AppendFstabEntry which both correctly use ComposeFsDeployEtcDirFn(target) for composefs paths.
Current exposure
Zero — all production images with needs_user_creation=True use grub2/ostree path. Only systemd-boot images (Dakota) use composefs-native, and Dakota has needs_user_creation=False, so CreateUser is never called for composefs images.
But this will break silently if a future image uses both composefs-native and user creation.
Fix
if isComposeFsNative(sysroot) {
var err error
etcDir, err := ComposeFsDeployEtcDirFn(sysroot)
if err != nil {
return fmt.Errorf("finding composefs deploy etc: %w", err)
}
root = filepath.Dir(etcDir) // parent of etc dir = deployment root
} else {
deployDir, err := DeploymentDirFn(sysroot)
...
}
Also: the staterootHome path ostree/deploy/default/var/home should use ComposeFsVarDir(sysroot) for composefs-native.
Assisted-by: Claude Sonnet 4.5 via pi
Summary
CreateUserininternal/post/user.gousesroot = sysrootfor composefs-native deployments, butsysroot/etc/is a ghost btrfs directory — writes to it are silently discarded after first boot.Root cause
On composefs-native systems:
/etcis bind-mounted fromstate/deploy/<HASH>/etc/sysroot/etc/is an orphaned btrfs directory that nothing mountsuseradd --root sysrootwrites tosysroot/etc/passwdetc., which is silently discarded after first bootCompare with
WriteHostnameandAppendFstabEntrywhich both correctly useComposeFsDeployEtcDirFn(target)for composefs paths.Current exposure
Zero — all production images with
needs_user_creation=Trueuse grub2/ostree path. Only systemd-boot images (Dakota) use composefs-native, and Dakota hasneeds_user_creation=False, soCreateUseris never called for composefs images.But this will break silently if a future image uses both composefs-native and user creation.
Fix
Also: the staterootHome path
ostree/deploy/default/var/homeshould useComposeFsVarDir(sysroot)for composefs-native.Assisted-by: Claude Sonnet 4.5 via pi