omnixient/modules/home-manager-bootstrap.nix
2026-06-28 09:59:04 +02:00

69 lines
2.8 KiB
Nix

# modules/home-manager-bootstrap.nix
#
# Pre-create the directory tree that the home-manager activation
# script expects to exist on a fresh first boot. Without these
# rules, `home-manager-user.service` crashes in (at least) three
# different places — each one only visible after fixing the
# previous — making first-boot of any clean install fail with a
# misleading error.
#
# This module is imported by both:
# - lib/mksystem.nix (for normal hosts), and
# - iso.nix (for the live ISO),
# so the bootstrap is consistent across both code paths. It is
# intentionally a separate module rather than inlined in mksystem
# so the ISO — which bypasses mksystem — does not silently miss
# the fix.
#
# Reads `config.omni.user`, which is the canonical "main user"
# option set by modules/core.nix. Does nothing if that option is
# unset (e.g. during a flake check that does not set up users).
#
# The bugs this fixes:
#
# 1. The "find a profile dir" check in
# home-manager-generation/activate (around line 90 of the
# generated script) needs $HOME/.local/state/nix/profiles
# OR /nix/var/nix/profiles/per-user/$USER to exist. The
# nix-daemon only creates the per-user dir lazily on the
# user's first nix command, so on a fresh boot neither path
# exists. Symptom: "Could not find suitable profile
# directory" → service fails.
#
# 2. After (1) is fixed, `nix-store --realise ... --add-root
# $HOME/.local/state/home-manager/gcroots/new-home` fails
# because the activate script declares `hmGcrootsDir` but
# never `mkdir -p`s it. nix-store will not create parent
# dirs for --add-root. Symptom: silent exit, status 1, no
# error in journal.
#
# 3. After (2) is fixed, `linkGeneration` fails with
# "Permission denied" creating `~/.local/share/dev-env/...`.
# modules/lib.nix declares `d /home/$USER/.local/share/
# omni ...` and systemd-tmpfiles auto-creates the missing
# intermediate `.local/share` parent as root:root, which
# then blocks the user-running activation. Pinning the
# `.local/share` parent here keeps it user-owned regardless
# of declaration order.
#
# tmpfiles `d` rules do not recursively create parents, so each
# level of the tree is spelled out explicitly.
{ config, lib, ... }:
let
user = config.omni.user or null;
in
{
config = lib.mkIf (user != null) {
systemd.tmpfiles.rules = [
"d /nix/var/nix/profiles/per-user/${user} 0755 ${user} users -"
"d /nix/var/nix/gcroots/per-user/${user} 0755 ${user} users -"
"d /home/${user}/.local 0755 ${user} users -"
"d /home/${user}/.local/share 0755 ${user} users -"
"d /home/${user}/.local/state 0755 ${user} users -"
"d /home/${user}/.local/state/home-manager 0755 ${user} users -"
"d /home/${user}/.local/state/home-manager/gcroots 0755 ${user} users -"
];
};
}