fix(install): pre-activation, writable home.nix, keyboard/locale selection
- Pre-activation failure ("desktop pre-activation failed"): the in-chroot
HM build hard-coded `--option substituters ""`, which only works on
OFFLINE installs (where the ISO store is copied in). Online installs
(now common — wifi works) had no pins and built from source. Fix: build
the generation in the live env (its store always has the pins), nix copy
the closure to the target, chroot only activates. In-chroot build kept
as fallback. Uses path: (not git+file:) to sidestep libgit2 ownership.
- Read-only home.nix: templates come out of the store mode 0444 and cp
preserves it — chmod -R u+w after staging so the user can edit it.
- Keyboard + locale selection: gum prompts (localectl layouts/variants +
curated UTF-8 locale list) and NOMARCHY_LOCALE/KB_LAYOUT/KB_VARIANT for
unattended. One choice flows everywhere — services.xserver.xkb (source
of truth) + console.useXkbConfig (tty/greeter) + boot.initrd.systemd
(so the LUKS passphrase prompt uses the layout; script initrd can't) +
the generated home.nix's nomarchy.keyboard.* for the Hyprland session.
- test-install.sh: NOMARCHY_OVMF env override for sandboxed OVMF paths.
Verified end-to-end in QEMU: offline LUKS install completes, boots to a
themed desktop (no autogenerated-config banner), Plymouth renders the LUKS
prompt on the theme-tinted background.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
# NOMARCHY_UNATTENDED=1 NOMARCHY_DISK=/dev/vda NOMARCHY_USERNAME=me \
|
||||
# NOMARCHY_PASSWORD=secret [NOMARCHY_HOSTNAME=nomarchy] \
|
||||
# [NOMARCHY_TIMEZONE=UTC] [NOMARCHY_LUKS_PASSPHRASE=...] \
|
||||
# [NOMARCHY_LOCALE=en_US.UTF-8] [NOMARCHY_KB_LAYOUT=us] [NOMARCHY_KB_VARIANT=] \
|
||||
# [NOMARCHY_SWAP_GB=N (default: RAM size; 0 = none)] \
|
||||
# [NOMARCHY_HW="auto"|"none"|"mod1 mod2"] [NOMARCHY_FINISH=none|reboot|poweroff]
|
||||
# nomarchy-install
|
||||
@@ -150,6 +151,9 @@ if [[ "$UNATTENDED" == "1" ]]; then
|
||||
PASSWORD="${NOMARCHY_PASSWORD:?}"
|
||||
HOSTNAME_="${NOMARCHY_HOSTNAME:-nomarchy}"
|
||||
TIMEZONE="${NOMARCHY_TIMEZONE:-UTC}"
|
||||
LOCALE="${NOMARCHY_LOCALE:-en_US.UTF-8}"
|
||||
KB_LAYOUT="${NOMARCHY_KB_LAYOUT:-us}"
|
||||
KB_VARIANT="${NOMARCHY_KB_VARIANT:-}"
|
||||
else
|
||||
while true; do
|
||||
USERNAME=$(gum input --placeholder "username (lowercase, e.g. ada)")
|
||||
@@ -169,6 +173,26 @@ else
|
||||
warn "Invalid hostname."
|
||||
done
|
||||
TIMEZONE=$(timedatectl list-timezones | gum filter --placeholder "timezone (type to search)" || echo UTC)
|
||||
# Curated UTF-8 locales: the live system only generates en_US, so
|
||||
# `locale -a` can't enumerate what the TARGET could use.
|
||||
LOCALE=$(printf '%s\n' \
|
||||
en_US en_GB de_DE fr_FR es_ES es_MX pt_PT pt_BR it_IT nl_NL \
|
||||
pl_PL ru_RU uk_UA cs_CZ sk_SK sv_SE nb_NO da_DK fi_FI tr_TR \
|
||||
el_GR hu_HU ro_RO bg_BG hr_HR sl_SI lt_LT lv_LV et_EE ja_JP \
|
||||
ko_KR zh_CN zh_TW ar_EG he_IL hi_IN th_TH vi_VN id_ID \
|
||||
| sed 's/$/.UTF-8/' \
|
||||
| gum filter --placeholder "language / locale (type to search)" \
|
||||
|| echo en_US.UTF-8)
|
||||
KB_LAYOUT=$(localectl list-x11-keymap-layouts 2>/dev/null \
|
||||
| gum filter --placeholder "keyboard layout (type to search)" \
|
||||
|| echo us)
|
||||
KB_VARIANT=""
|
||||
if [[ "$KB_LAYOUT" != "us" ]] || gum confirm --default=No "Pick a keyboard variant (intl, nodeadkeys, …)?"; then
|
||||
KB_VARIANT=$( { echo "(none)"; localectl list-x11-keymap-variants "$KB_LAYOUT" 2>/dev/null; } \
|
||||
| gum filter --placeholder "variant for $KB_LAYOUT (pick '(none)' for the default)" \
|
||||
|| echo "(none)")
|
||||
[[ "$KB_VARIANT" == "(none)" ]] && KB_VARIANT=""
|
||||
fi
|
||||
fi
|
||||
[[ "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]] || fail "Invalid username '$USERNAME'."
|
||||
[[ -f "/usr/share/zoneinfo/$TIMEZONE" || -e "/etc/zoneinfo/$TIMEZONE" ]] \
|
||||
@@ -177,6 +201,7 @@ fi
|
||||
HASHED_PASSWORD=$(printf '%s' "$PASSWORD" | mkpasswd -m sha-512 -s)
|
||||
unset PASSWORD
|
||||
info "User: $USERNAME @ $HOSTNAME_ ($TIMEZONE)"
|
||||
info "Locale: $LOCALE · keyboard: $KB_LAYOUT${KB_VARIANT:+ ($KB_VARIANT)}"
|
||||
|
||||
# ─── Hardware profile ───────────────────────────────────────────────────
|
||||
section "Hardware detection"
|
||||
@@ -320,9 +345,35 @@ nixos-generate-config --root /mnt
|
||||
mv /mnt/etc/nixos/hardware-configuration.nix "$FLAKE_DIR/"
|
||||
rm -rf /mnt/etc/nixos
|
||||
|
||||
cp "$SHARE/template/home.nix" "$FLAKE_DIR/"
|
||||
cp "$SHARE/template/theme-state.json" "$FLAKE_DIR/"
|
||||
|
||||
# home.nix is generated (not copied from the template) so the chosen
|
||||
# keyboard layout reaches the Hyprland session — standalone HM cannot
|
||||
# read system.nix.
|
||||
cat > "$FLAKE_DIR/home.nix" <<EOF
|
||||
# Your user environment. The Nomarchy desktop (Hyprland, Waybar,
|
||||
# Ghostty, theming engine, Stylix) comes from homeModules.nomarchy;
|
||||
# tune it via the nomarchy.* options, add your own packages and
|
||||
# programs below.
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
# Keyboard for the desktop session; console + LUKS prompt get the
|
||||
# same layout from system.nix (xkb + console.useXkbConfig).
|
||||
nomarchy.keyboard.layout = "$KB_LAYOUT";
|
||||
nomarchy.keyboard.variant = "$KB_VARIANT";
|
||||
|
||||
# Examples:
|
||||
# nomarchy.terminal = "kitty"; # swap the default terminal
|
||||
# nomarchy.waybar.enable = false; # bring your own bar
|
||||
# nomarchy.stylix.enable = false; # opt out of GTK/Qt theming
|
||||
|
||||
home.packages = with pkgs; [
|
||||
# firefox
|
||||
];
|
||||
}
|
||||
EOF
|
||||
|
||||
hw_nix=""
|
||||
for p in "${HW_PROFILES[@]:-}"; do
|
||||
[[ -n "$p" ]] && hw_nix+=" \"$p\""
|
||||
@@ -371,7 +422,17 @@ cat > "$FLAKE_DIR/system.nix" <<EOF
|
||||
|
||||
networking.hostName = "$HOSTNAME_";
|
||||
time.timeZone = "$TIMEZONE";
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
i18n.defaultLocale = "$LOCALE";
|
||||
|
||||
# One keyboard layout everywhere: xkb is the source of truth, the
|
||||
# virtual console derives from it (greeter/ttys), and the systemd
|
||||
# initrd applies it to the LUKS passphrase prompt too (the classic
|
||||
# script initrd cannot). The Hyprland session reads the same layout
|
||||
# from nomarchy.keyboard.* in home.nix.
|
||||
services.xserver.xkb.layout = "$KB_LAYOUT";
|
||||
services.xserver.xkb.variant = "$KB_VARIANT";
|
||||
console.useXkbConfig = true;
|
||||
boot.initrd.systemd.enable = true;
|
||||
|
||||
# Your login user — \`username\` flows in from flake.nix automatically.
|
||||
users.users.\${username} = {
|
||||
@@ -410,6 +471,9 @@ fi
|
||||
# outright. The first normal NixOS user is always 1000:users(100); the
|
||||
# account doesn't exist in the target yet, so numeric ids it is.
|
||||
chown -R 1000:100 "$FLAKE_DIR"
|
||||
# The templates come out of the nix store mode 0444 and cp preserves
|
||||
# that — without this the user owns home.nix but can't edit it.
|
||||
chmod -R u+w "$FLAKE_DIR"
|
||||
|
||||
# /etc/nixos on the installed system points at the user-owned flake.
|
||||
mkdir -p /mnt/etc
|
||||
@@ -464,6 +528,25 @@ success "System installed (bootloader in place)"
|
||||
# fully themed desktop, not bare Hyprland. Best-effort: a failure here
|
||||
# only costs the user one `home-manager switch` after logging in.
|
||||
section "Baking the desktop"
|
||||
|
||||
# Build the generation HERE in the live environment, not in the chroot:
|
||||
# the live store always carries the offline pin set, while the target
|
||||
# store only gets the full ISO store on OFFLINE installs — an online
|
||||
# install skips that copy, and the old in-chroot build (substituters
|
||||
# deliberately empty) then tried to compile the world from source.
|
||||
# Building against the live store is the exact path the live session's
|
||||
# own theme switching exercises, online or off.
|
||||
info "Building the desktop generation..."
|
||||
hm_out=""
|
||||
if hm_out=$(nix --extra-experimental-features "nix-command flakes" \
|
||||
build --no-link --print-out-paths \
|
||||
--option substituters "" \
|
||||
"path:$FLAKE_DIR#homeConfigurations.$USERNAME.activationPackage"); then
|
||||
nix --extra-experimental-features "nix-command flakes" \
|
||||
copy --no-check-sigs --to "local?root=/mnt" "$hm_out" \
|
||||
|| hm_out=""
|
||||
fi
|
||||
[[ -n "$hm_out" ]] || warn "live-side desktop build failed — retrying inside the chroot"
|
||||
# NOT /mnt/tmp: nixos-enter mounts a fresh tmpfs over /tmp inside the
|
||||
# chroot, which silently vaporizes any script staged there (cost us a
|
||||
# full verification round to find). /root persists into the chroot.
|
||||
@@ -471,10 +554,15 @@ cat > /mnt/root/nomarchy-hm-activate.sh <<EOF
|
||||
set -ex
|
||||
exec > /var/log/nomarchy-hm-preactivate.log 2>&1
|
||||
export PATH=/run/current-system/sw/bin:\$PATH
|
||||
out=\$(nix --extra-experimental-features "nix-command flakes" \
|
||||
build --no-link --print-out-paths \
|
||||
--option substituters "" \
|
||||
"path:/home/$USERNAME/.nomarchy#homeConfigurations.$USERNAME.activationPackage")
|
||||
# Normally pre-built in the live env and copied over; the in-chroot
|
||||
# build (default substituters — the live-side build already proved the
|
||||
# no-network case) is a last-resort fallback.
|
||||
out="$hm_out"
|
||||
if [ -z "\$out" ]; then
|
||||
out=\$(nix --extra-experimental-features "nix-command flakes" \
|
||||
build --no-link --print-out-paths \
|
||||
"path:/home/$USERNAME/.nomarchy#homeConfigurations.$USERNAME.activationPackage")
|
||||
fi
|
||||
install -d -o "$USERNAME" -g users /nix/var/nix/profiles/per-user/$USERNAME
|
||||
install -d -o "$USERNAME" -g users /nix/var/nix/gcroots/per-user/$USERNAME
|
||||
# activate's profile ops need store access; as the user that means a
|
||||
|
||||
Reference in New Issue
Block a user