From d1713bdc99593a401dffb91539a9480896f9eee6 Mon Sep 17 00:00:00 2001 From: randogoth Date: Mon, 2 Feb 2026 14:04:56 +0200 Subject: [PATCH] Add HM bootstrap script and tidy installer workflow --- agents.md | 7 ++- docs/install-help.txt | 6 +-- hosts/installer.nix | 23 --------- hosts/iso.nix | 4 -- hosts/nanuqsaurus.nix | 14 ++++++ modules/system/home-manager-skel.nix | 48 ++++++++++++++++++ modules/system/packages.nix | 10 ++++ modules/users/admin.nix | 22 +++++++- scripts/hm-bootstrap.sh | 75 ++++++++++++++++++++++++++++ scripts/install-btrfs.sh | 18 ++++--- 10 files changed, 184 insertions(+), 43 deletions(-) delete mode 100644 hosts/installer.nix create mode 100644 hosts/nanuqsaurus.nix create mode 100644 modules/system/home-manager-skel.nix create mode 100644 modules/system/packages.nix create mode 100644 scripts/hm-bootstrap.sh diff --git a/agents.md b/agents.md index 9587cb5..495bed5 100644 --- a/agents.md +++ b/agents.md @@ -161,10 +161,9 @@ Example feature module skeleton: ## Home Manager Policy -* **Home Manager must be integrated as a NixOS module** -* No standalone Home Manager flakes -* No per-user Home Manager flakes -* User configuration lives under `modules/users/` +* Home Manager is **provided as a CLI tool system-wide** (`home-manager` in `environment.systemPackages`). +* Users manage their own HM configs (per-user, standalone). No system-wide HM module imports. +* `modules/users/*` must not declare `home-manager.users.*`; keep user accounts declarative via NixOS only. --- diff --git a/docs/install-help.txt b/docs/install-help.txt index ddda7c8..be06afd 100644 --- a/docs/install-help.txt +++ b/docs/install-help.txt @@ -6,9 +6,9 @@ Run the non-interactive installer: sudo /etc/nixos/scripts/install-btrfs.sh -Optionally set DISK and HOSTNAME (defaults: DISK=/dev/vda, HOSTNAME=nanuqsaurus) +Optionally set DISK and TARGET_HOST (defaults: DISK=/dev/vda, TARGET_HOST=nanuqsaurus) - sudo DISK=/dev/vda HOSTNAME=nanuqsaurus /etc/nixos/scripts/install-btrfs.sh + sudo DISK=/dev/vda TARGET_HOST=nanuqsaurus /etc/nixos/scripts/install-btrfs.sh The script wipes the whole disk, creates Btrfs subvolumes (root, home, nix), copies the flake, generates hardware config, and installs. It writes host files under `/etc/nixos/hosts/local-.nix` and `local--hardware.nix` (kept local to the machine). @@ -16,7 +16,7 @@ The script wipes the whole disk, creates Btrfs subvolumes (root, home, nix), cop If you need a more custom setup please run the partitioning manually, mount the target to /mnt and then install the system: - nixos-install --root /mnt --flake "/mnt/etc/nixos#${HOSTNAME}" --no-root-passwd + nixos-install --root /mnt --flake "/mnt/etc/nixos#local-${TARGET_HOST}" --no-root-passwd After install finishes, reboot into the new system. diff --git a/hosts/installer.nix b/hosts/installer.nix deleted file mode 100644 index 4597e90..0000000 --- a/hosts/installer.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ config, pkgs, inputs, ... }: - -{ - imports = [ - inputs.lix-module.nixosModules.lixFromNixpkgs - ../modules/system/base.nix - ../modules/desktop/gnome-minimal.nix - ../modules/users/admin.nix - ]; - - networking.hostName = "nanuqsaurus"; - - environment.etc."INSTALL.txt".source = ../docs/install-help.txt; - environment.etc."motd".text = builtins.readFile ../docs/install-help.txt; - environment.etc."issue".text = '' -${builtins.readFile ../docs/install-help.txt} - -Login: \l - ''; - - # Place the flake (including scripts) under /etc/nixos in the live system. - environment.etc."nixos".source = ../.; -} diff --git a/hosts/iso.nix b/hosts/iso.nix index 2ba89bd..c78f513 100644 --- a/hosts/iso.nix +++ b/hosts/iso.nix @@ -4,10 +4,6 @@ { imports = [ (inputs.nixpkgs + "/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix") - inputs.lix-module.nixosModules.lixFromNixpkgs - ../modules/system/base.nix - ../modules/desktop/gnome-minimal.nix - ../modules/users/admin.nix ]; networking.hostName = "iso-installer"; diff --git a/hosts/nanuqsaurus.nix b/hosts/nanuqsaurus.nix new file mode 100644 index 0000000..ac9f22e --- /dev/null +++ b/hosts/nanuqsaurus.nix @@ -0,0 +1,14 @@ +{ config, pkgs, inputs, ... }: + +{ + imports = [ + inputs.lix-module.nixosModules.lixFromNixpkgs + ../modules/system/base.nix + ../modules/system/packages.nix + ../modules/system/home-manager-skel.nix + ../modules/desktop/gnome-minimal.nix + ../modules/users/admin.nix + ]; + + networking.hostName = "nanuqsaurus"; +} diff --git a/modules/system/home-manager-skel.nix b/modules/system/home-manager-skel.nix new file mode 100644 index 0000000..9ae3fa9 --- /dev/null +++ b/modules/system/home-manager-skel.nix @@ -0,0 +1,48 @@ +{ ... }: + +let + flakeText = '' +{ + description = "Home Manager configuration of admin"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { nixpkgs, home-manager, ... }: { + homeConfigurations."admin" = home-manager.lib.homeManagerConfiguration { + pkgs = nixpkgs.legacyPackages.x86_64-linux; + modules = [ ./home.nix ]; + }; + }; +} +''; + + homeText = '' +{ config, pkgs, ... }: + +{ + home.username = "admin"; + home.homeDirectory = "/home/admin"; + home.stateVersion = "25.05"; + + home.packages = [ pkgs.ptyxis ]; + + home.file = { }; + + home.sessionVariables = { }; + + programs.home-manager.enable = true; +} +''; +in +{ + environment.etc = { + "skel/.config/home-manager/flake.nix".text = flakeText; + "skel/.config/home-manager/home.nix".text = homeText; + }; +} diff --git a/modules/system/packages.nix b/modules/system/packages.nix new file mode 100644 index 0000000..0416fca --- /dev/null +++ b/modules/system/packages.nix @@ -0,0 +1,10 @@ +{ lib, pkgs, ... }: + +{ + # Baseline system tools available everywhere; users run Home Manager standalone. + config.environment.systemPackages = lib.mkAfter [ + pkgs.home-manager + pkgs.ghostty + pkgs.libnotify + ]; +} diff --git a/modules/users/admin.nix b/modules/users/admin.nix index 947e26f..b43fc80 100644 --- a/modules/users/admin.nix +++ b/modules/users/admin.nix @@ -1,4 +1,4 @@ -{ lib, ... }: +{ lib, pkgs, ... }: { users.users.admin = { @@ -8,4 +8,24 @@ }; security.sudo.wheelNeedsPassword = lib.mkDefault true; + + # Bootstrap Home Manager for admin on first login (per-user, standalone) + systemd.user.services.hm-bootstrap = { + description = "One-time Home Manager bootstrap for admin"; + unitConfig = { + ConditionPathExists = "!%h/.config/home-manager/.hm_bootstrap_done"; + After = [ "graphical-session.target" ]; + Wants = [ "graphical-session.target" ]; + PartOf = [ "graphical-session.target" ]; + }; + serviceConfig = { + Type = "simple"; + TimeoutStartSec = "30min"; + Environment = [ "PATH=/run/current-system/sw/bin:/etc/profiles/per-user/%u/bin" ]; + StandardOutput = "journal"; + StandardError = "journal"; + ExecStart = pkgs.writeShellScript "hm-bootstrap.sh" (builtins.readFile ../../scripts/hm-bootstrap.sh); + }; + wantedBy = [ "graphical-session.target" ]; + }; } diff --git a/scripts/hm-bootstrap.sh b/scripts/hm-bootstrap.sh new file mode 100644 index 0000000..52fe97e --- /dev/null +++ b/scripts/hm-bootstrap.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Ensure basic PATH for user units +export PATH="/run/current-system/sw/bin:${PATH:-}" + +# Try to hook into the session bus if available +if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] && [ -S "/run/user/$(id -u)/bus" ]; then + export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus" +fi + +# Only continue in an active, unlocked user session +if command -v loginctl >/dev/null 2>&1 && [ -n "${XDG_SESSION_ID:-}" ]; then + session_class=$(loginctl show-session "${XDG_SESSION_ID}" -p Class --value 2>/dev/null || echo "") + session_state=$(loginctl show-session "${XDG_SESSION_ID}" -p State --value 2>/dev/null || echo "") + session_locked=$(loginctl show-session "${XDG_SESSION_ID}" -p LockedHint --value 2>/dev/null || echo "yes") + if [ "${session_class}" != "user" ] || [ "${session_state}" != "active" ] || [ "${session_locked}" != "no" ]; then + exit 0 + fi +fi + +wait_for_notifications() { + # Wait briefly for GNOME Shell notifications to come up and the session to be unlocked. + if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] || ! command -v gdbus >/dev/null 2>&1; then + return 1 + fi + + # Do not notify in greeter sessions. + if [ "${XDG_SESSION_CLASS:-}" != "user" ]; then + return 1 + fi + + # If loginctl is available, wait until this session is unlocked and class=user (not greeter). + if command -v loginctl >/dev/null 2>&1 && [ -n "${XDG_SESSION_ID:-}" ]; then + for _ in 1 2 3 4 5 6 7; do + session_class=$(loginctl show-session "${XDG_SESSION_ID}" -p Class --value 2>/dev/null || echo "") + locked=$(loginctl show-session "${XDG_SESSION_ID}" -p LockedHint --value 2>/dev/null || echo "yes") + if [ "${session_class}" = "user" ] && [ "${locked}" = "no" ]; then + break + fi + sleep 2 + done + fi + + for _ in 1 2 3 4 5 6 7; do + if gdbus introspect --session \ + --dest org.freedesktop.Notifications \ + --object-path /org/freedesktop/Notifications >/dev/null 2>&1; then + return 0 + fi + sleep 2 + done + return 1 +} + +notify() { + local title="$1" + shift || true + if command -v notify-send >/dev/null 2>&1 && wait_for_notifications; then + notify-send --app-name="Bootstrap" --urgency=critical --expire-time=0 "$title" "$@" >/dev/null 2>&1 || true + fi +} + +notify "System setup running" "Please wait until setup completes." +trap 'notify "System setup finished" "You may continue using the system."' EXIT + +mkdir -p "$HOME/.config/home-manager" +for f in flake.nix home.nix; do + if [ -f "/etc/skel/.config/home-manager/$f" ] && [ ! -e "$HOME/.config/home-manager/$f" ]; then + cp "/etc/skel/.config/home-manager/$f" "$HOME/.config/home-manager/$f" + fi +done + +home-manager switch --flake "$HOME/.config/home-manager#admin" +touch "$HOME/.config/home-manager/.hm_bootstrap_done" diff --git a/scripts/install-btrfs.sh b/scripts/install-btrfs.sh index ee5ab45..5f94c65 100755 --- a/scripts/install-btrfs.sh +++ b/scripts/install-btrfs.sh @@ -2,10 +2,10 @@ set -euo pipefail # Non-interactive Btrfs installer using the flake in this repo. -# Defaults: DISK=/dev/vda HOSTNAME=nanuqsaurus +# Defaults: DISK=/dev/vda TARGET_HOST=nanuqsaurus DISK=${DISK:-/dev/vda} -HOSTNAME=${HOSTNAME:-nanuqsaurus} +TARGET_HOST=${TARGET_HOST:-nanuqsaurus} SCRIPT_DIR=$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd) REPO_ROOT=$(cd -- "${SCRIPT_DIR}/.." && pwd) @@ -44,9 +44,9 @@ fi echo "[6/7] Generating hardware config" mkdir -p /mnt/etc/nixos/hosts -nixos-generate-config --root /mnt --show-hardware-config > "/mnt/etc/nixos/hosts/local-${HOSTNAME}-hardware.nix" +nixos-generate-config --root /mnt --show-hardware-config > "/mnt/etc/nixos/hosts/local-${TARGET_HOST}-hardware.nix" -host_file="/mnt/etc/nixos/hosts/local-${HOSTNAME}.nix" +host_file="/mnt/etc/nixos/hosts/local-${TARGET_HOST}.nix" if [ ! -e "${host_file}" ]; then cat > "${host_file}" <