diff --git a/docs/superpowers/specs/2026-07-27-mibook-boot-desktop-choice-design.md b/docs/superpowers/specs/2026-07-27-mibook-boot-desktop-choice-design.md index d12d7fb..9258f94 100644 --- a/docs/superpowers/specs/2026-07-27-mibook-boot-desktop-choice-design.md +++ b/docs/superpowers/specs/2026-07-27-mibook-boot-desktop-choice-design.md @@ -2,60 +2,104 @@ ## Goal -Let mibook boot to a text console by default, with KDE fully installed and -launchable on demand. The user decides after boot whether they need the -graphical desktop, without rebuilding the system. +Let mibook offer a choice, each time it boots, between the normal KDE desktop +and a terminal-only ("server") mode with no graphical session — decided at +boot, without rebuilding the system. + +## Background: why the first attempt failed + +The first implementation booted mibook to a text console by default +(`systemd.defaultUnit = "multi-user.target"`, display manager not started) and +provided a `desktop` command to start KDE on demand. On real hardware this +locked the machine out: + +- **No SSH.** mibook is WiFi-only and its WiFi credentials are stored per-user + in KWallet ("agent-owned"). NetworkManager only receives the password once a + desktop session is running, so a headless boot never joins the network and + the machine has no IP — nothing to SSH into. +- **No usable console.** The boot appeared to "hang" with no login prompt: + `NetworkManager-wait-online` stalled waiting for a network that never came + up, and the `getty` login prompt on tty1 was buried under later service + messages. + +Conclusion: a headless WiFi laptop cannot be reached remotely, and the plain +console was hard to use. The design must (a) keep the desktop as the reliable +default, (b) make the terminal path a deliberate, self-sufficient choice, and +(c) not depend on the network being up. ## Behavior -- mibook boots to a text console (`multi-user.target`). SDDM and Plasma do - **not** start automatically. -- KDE (SDDM + Plasma 6) remains fully installed. -- Running `desktop` in the terminal starts the SDDM display manager, which - presents the graphical login screen; logging in there enters a Plasma 6 - session. -- No reverse command. To return to a text console, stop the display manager - (`sudo systemctl stop display-manager`) or reboot. -- Other machines that enable the `kde-desktop` profile are unaffected; their - desktop still starts on boot. +- The **default GRUB entry** boots straight into KDE — unchanged from the + known-working baseline. +- A **separate GRUB entry, `mibook (terminal)`** (a NixOS *specialisation*), + boots to a text console with **autologin** for `finn`. From there the user + can work in the shell or run `desktop` to bring KDE up (via SDDM). +- The choice is made in the GRUB menu at boot — matching the original request + to "decide each time I boot." +- Booting never stalls on the network. + +### Known limitation (documented, not fixed in config) + +In terminal mode WiFi will not connect on its own, because the password is +stored per-user in KWallet. To reach mibook over SSH from terminal mode, the +user must first save the WiFi as a **system** connection in KDE: +network settings → the WiFi network → "All users may connect to this network". +Until then, terminal mode is local-console-only. This is a one-time manual +step outside the scope of the Nix config. ## Implementation -### `modules/environments/kde-desktop/default.nix` +### `machines/mibook/configuration.nix` -Add an opt-out option to the existing profile: +- Add a NixOS specialisation `specialisation.terminal.configuration`: + - `system.nixos.tags = [ "terminal" ];` — labels the generated boot entry. + - `systemd.defaultUnit = lib.mkForce "multi-user.target";` — boots to the + text console. `graphical.target` is what pulls in the display manager (via + its embedded `Wants=display-manager.service`), so defaulting to + `multi-user.target` leaves SDDM installed but not started at boot. + - `services.getty.autologinUser = "finn";` — guarantees a usable shell on + the console instead of a login prompt that can scroll off screen. + - A `desktop` command via + `pkgs.writeShellScriptBin "desktop" "exec sudo systemctl start display-manager.service"` + in `environment.systemPackages`, to start KDE on demand. +- Add `systemd.services.NetworkManager-wait-online.enable = false;` (applies to + both the default and terminal boots) so boot never stalls waiting for the + network. -- New option `my.profiles.kde-desktop.startOnBoot`, a boolean defaulting to - `true`. The default preserves the current behavior for every consumer. -- The existing `config` block (SDDM, Plasma 6, user packages) stays enabled - whenever the profile is enabled, regardless of `startOnBoot`. -- When `startOnBoot = false`, additionally apply: - - `systemd.services.display-manager.wantedBy = lib.mkForce [ ];` so SDDM is - defined but not pulled in by any boot target. - - `systemd.defaultUnit = "multi-user.target";` so the boot target is the - text console. - - A `desktop` command provided via - `pkgs.writeShellScriptBin "desktop" "exec sudo systemctl start display-manager.service"`, - added to `environment.systemPackages`. +### Reverted from the first attempt -### `machines/mibook/environments.nix` +- `modules/environments/kde-desktop/default.nix` — remove the `startOnBoot` + option and its `mkMerge`/`mkIf` machinery; back to the original profile that + simply enables SDDM + Plasma 6. +- `machines/mibook/environments.nix` — remove `kde-desktop.startOnBoot = false;` + (back to just `kde-desktop.enable = true;`). -Set `kde-desktop.startOnBoot = false;` alongside the existing -`kde-desktop.enable = true;`. +## Why a specialisation + +A specialisation generates a second boot-menu entry automatically from a +modified copy of the configuration. It is the idiomatic NixOS mechanism for a +boot-time choice and avoids fragile hand-written GRUB `extraEntries` that would +need to track kernel/initrd paths across generations. The default entry remains +byte-for-byte the working desktop configuration. ## Testing / verification - `nix build '.#nixosConfigurations.mibook.config.system.build.toplevel'` - builds without error. -- After `nixos-rebuild switch` on mibook: system boots to a TTY, no SDDM; - running `desktop` brings up the SDDM login and a working Plasma session. -- Confirm jupiter (or any non-mibook consumer) is unchanged: its evaluated - `systemd.defaultUnit` / display-manager wantedBy are not altered. + builds both `nixos-system-mibook` and `nixos-system-mibook-terminal`. +- Verified on the built closures: + - Parent `default.target` → `graphical.target`; no console autologin + (identical to the pre-change baseline). + - Specialisation `default.target` → `multi-user.target`; tty1 getty wrapper + contains `--autologin finn`; `desktop` present in the system profile. + - `NetworkManager-wait-online` disabled in both. +- Post-`switch` manual check on mibook: default GRUB entry boots to KDE; the + `terminal` entry boots to an autologged-in console; running `desktop` there + starts SDDM and a working Plasma session. ## Trade-offs -- `desktop` relies on `sudo`; the user has sudo access, so no extra - configuration is required. -- Chose "boot to terminal, launch on demand" over a GRUB boot-menu entry - because it is more robust across NixOS generations and needs no fragile - static kernel entries. +- Autologin on the terminal console means physical access grants a shell + without a password. Acceptable for a personal laptop the user controls; the + desktop (default) boot is unaffected. +- `desktop` relies on `sudo`; the user has sudo access, so no extra config is + required. diff --git a/machines/mibook/configuration.nix b/machines/mibook/configuration.nix index 3ac1514..eb971b8 100644 --- a/machines/mibook/configuration.nix +++ b/machines/mibook/configuration.nix @@ -20,7 +20,6 @@ useOSProber = true; }; - # Configure keymap in X11 services.xserver.xkb = { layout = "de"; @@ -34,7 +33,6 @@ services.printing.enable = true; nixpkgs.config.allowUnfree = true; - hardware.nvidia.prime = { sync.enable = false; @@ -44,6 +42,37 @@ services.openssh.enable = true; + # Don't let boot stall waiting for a network that may never come up + # (WiFi credentials live in KWallet and need a desktop session), which + # otherwise hangs the terminal boot before the login prompt appears. + systemd.services.NetworkManager-wait-online.enable = false; + + # Boot-time choice: the default GRUB entry boots straight into KDE. + # A separate "terminal" entry (a NixOS specialisation) boots to a text + # console with autologin, where you can work or run `desktop` to bring + # KDE up. Pick the entry you want in the GRUB menu at boot. + # + # NOTE: in terminal mode WiFi will not connect on its own (the password + # is stored per-user in KWallet). To reach the machine over SSH from + # terminal mode, first save the WiFi as a system connection in KDE: + # network settings -> your WiFi -> "All users may connect to this network". + specialisation.terminal.configuration = { + system.nixos.tags = [ "terminal" ]; + + # Boot to a text console. graphical.target is what pulls in the display + # manager (via its embedded Wants=display-manager.service), so defaulting + # to multi-user.target leaves SDDM installed but not started at boot. + systemd.defaultUnit = lib.mkForce "multi-user.target"; + + # Guarantee a usable shell on the console (no login prompt to hunt for). + services.getty.autologinUser = "finn"; + + # Bring the desktop up on demand from the terminal. + environment.systemPackages = [ + (pkgs.writeShellScriptBin "desktop" "exec sudo systemctl start display-manager.service") + ]; + }; + # KDE (PowerDevil) power settings: do nothing on lid close while on AC power. # Shipped as a system-wide default; KConfig cascades so a user's own # ~/.config/powerdevilrc will override this if present. diff --git a/machines/mibook/environments.nix b/machines/mibook/environments.nix index 51108b8..8581e93 100644 --- a/machines/mibook/environments.nix +++ b/machines/mibook/environments.nix @@ -6,7 +6,6 @@ in { my.profiles = { kde-desktop.enable = true; - kde-desktop.startOnBoot = false; zsh.enable = true; apps = { desktop_apps = true; diff --git a/modules/environments/kde-desktop/default.nix b/modules/environments/kde-desktop/default.nix index 1598ad7..ee7b9db 100644 --- a/modules/environments/kde-desktop/default.nix +++ b/modules/environments/kde-desktop/default.nix @@ -11,37 +11,17 @@ in { options.my.profiles.kde-desktop = with lib; { enable = mkEnableOption "KDE Desktop Environment"; - startOnBoot = mkOption { - type = types.bool; - default = true; - description = '' - Whether the display manager (SDDM) starts automatically at boot. - When false, the system boots to a text console and KDE can be - launched on demand with the `desktop` command. - ''; - }; }; - config = lib.mkIf cfg.enable (lib.mkMerge [ - { - services = { - displayManager.sddm.enable = true; - displayManager.sddm.wayland.enable = true; - desktopManager.plasma6.enable = true; - }; - users.users.finn.packages = with pkgs; [ - # Programms can be added here... - numix-icon-theme - ]; - } - (lib.mkIf (!cfg.startOnBoot) { - # Boot to a text console; SDDM stays installed but is not pulled in - # by any boot target. Launch KDE on demand with `desktop`. - systemd.services.display-manager.wantedBy = lib.mkForce [ ]; - systemd.defaultUnit = lib.mkForce "multi-user.target"; - environment.systemPackages = [ - (pkgs.writeShellScriptBin "desktop" "exec sudo systemctl start display-manager.service") - ]; - }) - ]); -} \ No newline at end of file + config = lib.mkIf cfg.enable { + services = { + displayManager.sddm.enable = true; + displayManager.sddm.wayland.enable = true; + desktopManager.plasma6.enable = true; + }; + users.users.finn.packages = with pkgs; [ + # Programms can be added here... + numix-icon-theme + ]; + }; +}