diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1fb532..7b4f076 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ concurrency: jobs: validate-system: name: Validate igor-desktop - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 120 steps: - name: Check out repository @@ -49,7 +49,7 @@ jobs: repository-quality: name: Repository quality - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 45 steps: - name: Check out repository @@ -83,7 +83,7 @@ jobs: history-secrets: name: Git history secrets - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 timeout-minutes: 20 steps: - name: Check out repository and history diff --git a/configuration.nix b/configuration.nix index a396a3b..05af8bd 100644 --- a/configuration.nix +++ b/configuration.nix @@ -20,6 +20,10 @@ nixpkgs.config.allowUnfree = true; + # RDNA4 (RX 9070 XT) needs a newer kernel and Mesa than the nixpkgs default + # kernel targets; track the latest stable release instead of the default. + boot.kernelPackages = pkgs.linuxPackages_latest; + users.users.igor = { isNormalUser = true; description = "Igor Camilo"; @@ -34,6 +38,60 @@ zramSwap.enable = true; + # AMD GPU: Mesa provides RADV (Vulkan) and RadeonSI (OpenGL) for Hyprland's + # rendering, and the redistributable firmware carries the microcode the + # amdgpu kernel driver loads for display and video decode/encode. + hardware = { + enableRedistributableFirmware = true; + graphics = { + enable = true; + enable32Bit = true; # Steam and other 32-bit apps need these too. + }; + bluetooth = { + enable = true; + powerOnBoot = true; + }; + }; + + security.rtkit.enable = true; # Realtime scheduling for PipeWire. + + services = { + # PipeWire is the current NixOS-recommended audio server, replacing both + # PulseAudio and JACK. + pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + }; + + # Pairing UI and polkit rules for the Bluetooth radio enabled above. + blueman.enable = true; + + fwupd.enable = true; + + # greetd runs a throwaway Hyprland+Quickshell "greeter" session (see + # dotfiles/hypr/greeter.lua and dotfiles/quickshell/greeter) that + # authenticates over greetd's own IPC protocol, then hands off to the + # user's normal session below. The greeter runs as its own system + # user (created automatically by this module), so its files are + # published system-wide via environment.etc below rather than + # home-manager. + greetd = { + enable = true; + settings.default_session = { + # Preserve compositor and greeter output after their runtime directory + # disappears. Read it with: journalctl -b -t greetd-greeter + command = "${pkgs.systemd}/bin/systemd-cat --identifier=greetd-greeter -- start-hyprland -- --config /etc/greetd/hyprland.lua"; + user = "greeter"; + }; + }; + + udisks2.enable = true; + + btrfs.autoScrub.enable = true; + }; + # Desktop: Hyprland, with Quickshell as the shell layer (bar, wallpaper, # lock screen). Wallpaper is drawn by Quickshell itself (a background # layer-shell surface), so no separate wallpaper daemon is installed. @@ -43,15 +101,38 @@ # Registers zsh as a valid login shell; users.users.igor.shell above # is what makes it igor's actual default. zsh.enable = true; + # Needs the 32-bit graphics support enabled above. + steam.enable = true; + }; + # qt5ct/qt6ct (platformTheme) is the standard way to theme Qt apps outside + # a running KDE Plasma session; Kvantum (style) is the engine that actually + # renders the theme qt5ct/qt6ct picks. home.nix carries the GTK3/4 side. + qt = { + enable = true; + platformTheme = "qt5ct"; + style = "kvantum"; + }; + + # programs.hyprland adds xdg-desktop-portal-hyprland and ships its own + # hyprland-portals.conf (default = hyprland, then gtk). That file lives in + # the package's own store path, so the override below at a higher-priority + # /etc path is needed to route FileChooser to Dolphin's KDE portal instead. + xdg.portal = { + extraPortals = [ pkgs.kdePackages.xdg-desktop-portal-kde ]; + config.hyprland = { + default = [ + "hyprland" + "gtk" + ]; + "org.freedesktop.impl.portal.FileChooser" = [ "kde" ]; + }; }; - qt.enable = true; - - # greetd runs a throwaway Hyprland+Quickshell "greeter" session (see - # dotfiles/hypr/greeter.conf and dotfiles/quickshell/greeter) that - # authenticates over greetd's own IPC protocol, then hands off to the - # user's normal session below. The greeter runs as its own system - # user (created automatically by this module), so its files are - # published system-wide via environment.etc rather than home-manager. + + # GUI privilege prompts (mounting a drive, some NetworkManager actions) + # need an authentication agent; home.nix installs Hyprland's own + # (hyprpolkitagent) and hyprland.lua starts it. + security.polkit.enable = true; + environment = { systemPackages = [ pkgs.git @@ -62,7 +143,7 @@ ]; etc = { "wallpaper.jpg".source = ./wallpapers/weic2216b.jpg; - "greetd/hyprland.conf".source = ./dotfiles/hypr/greeter.conf; + "greetd/hyprland.lua".source = ./dotfiles/hypr/greeter.lua; # Keep the greeter and its ../shared QML import in one Nix store tree. "greetd/quickshell".source = ./dotfiles/quickshell; }; @@ -73,16 +154,6 @@ # just for igor, since fontconfig discovery works the same either way. fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ]; - services.greetd = { - enable = true; - settings.default_session = { - # Preserve compositor and greeter output after their runtime directory - # disappears. Read it with: journalctl -b -t greetd-greeter - command = "${pkgs.systemd}/bin/systemd-cat --identifier=greetd-greeter -- start-hyprland -- --config /etc/greetd/hyprland.conf"; - user = "greeter"; - }; - }; - nix.settings.experimental-features = [ "nix-command" "flakes" diff --git a/dotfiles/hypr/greeter.conf b/dotfiles/hypr/greeter.conf deleted file mode 100644 index 3dc6901..0000000 --- a/dotfiles/hypr/greeter.conf +++ /dev/null @@ -1,16 +0,0 @@ -# Minimal compositor used only to host the login greeter. Once the -# greeter authenticates and hands off to the real session (see -# quickshell/greeter/shell.qml), this Hyprland instance has to exit so -# greetd can start the user's session on the same VT/seat. -# -# Exit only after a successful Quickshell session. If QML cannot start, -# leave this compositor alive instead of making greetd restart it in a loop. -exec-once = quickshell -p /etc/greetd/quickshell/greeter && hyprctl dispatch exit - -# The greeter session is short-lived, so its /run/user log disappears. -# Mirror its complete Hyprland log to this boot's system journal. -debug { - disable_logs = false - enable_stdout_logs = true - # colored_stdout_logs = false -} diff --git a/dotfiles/hypr/greeter.lua b/dotfiles/hypr/greeter.lua new file mode 100644 index 0000000..8452ceb --- /dev/null +++ b/dotfiles/hypr/greeter.lua @@ -0,0 +1,28 @@ +-- Minimal compositor used only to host the login greeter. Once the +-- greeter authenticates and hands off to the real session (see +-- quickshell/greeter/shell.qml), this Hyprland instance has to exit so +-- greetd can start the user's session on the same VT/seat. + +-- ASUS TUF VG34VQL1B: 3440x1440, matching the user session (see hyprland.lua). +hl.monitor({ + output = "", + mode = "3440x1440@165", + position = "auto", + scale = "auto", +}) + +-- Exit only after a successful Quickshell session. If QML cannot start, +-- leave this compositor alive instead of making greetd restart it in a loop. +hl.on("hyprland.start", function() + hl.exec_cmd("quickshell -p /etc/greetd/quickshell/greeter && hyprctl dispatch exit") +end) + +-- The greeter session is short-lived, so its /run/user log disappears. +-- Mirror its complete Hyprland log to this boot's system journal. +hl.config({ + debug = { + disable_logs = false, + enable_stdout_logs = true, + -- colored_stdout_logs = false, + }, +}) diff --git a/dotfiles/hypr/hyprland.conf b/dotfiles/hypr/hyprland.conf deleted file mode 100644 index 23c5fbd..0000000 --- a/dotfiles/hypr/hyprland.conf +++ /dev/null @@ -1,6 +0,0 @@ -$mainMod = SUPER - -exec-once = quickshell - -bind = $mainMod, L, exec, systemctl --user start quickshell-lock.service -bind = $mainMod, Return, exec, ghostty diff --git a/dotfiles/hypr/hyprland.lua b/dotfiles/hypr/hyprland.lua new file mode 100644 index 0000000..a60f3f2 --- /dev/null +++ b/dotfiles/hypr/hyprland.lua @@ -0,0 +1,92 @@ +local mainMod = "SUPER" +local terminal = "ghostty" +local fileManager = "dolphin" + +------------ +-- MONITOR -- +------------ + +-- ASUS TUF VG34VQL1B: 3440x1440, FreeSync Premium up to 165Hz over DisplayPort. +hl.monitor({ + output = "", + mode = "3440x1440@165", + position = "auto", + scale = "auto", + vrr = 1, +}) + +----------- +-- INPUT -- +----------- + +hl.config({ + input = { + kb_layout = "us", -- Change this if the keyboard isn't a US layout. + }, +}) + +--------------- +-- AUTOSTART -- +--------------- + +hl.on("hyprland.start", function() + hl.exec_cmd("quickshell") + hl.exec_cmd("hyprpolkitagent") +end) + +---------------- +-- KEYBINDING -- +---------------- + +hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd(terminal)) +hl.bind(mainMod .. " + Q", hl.dsp.exec_cmd(terminal)) +hl.bind(mainMod .. " + C", hl.dsp.window.close()) +hl.bind(mainMod .. " + M", hl.dsp.exit()) +hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(fileManager)) +hl.bind(mainMod .. " + V", hl.dsp.window.float({ action = "toggle" })) +hl.bind(mainMod .. " + R", hl.dsp.exec_cmd("systemctl --user start quickshell-launcher.service")) +hl.bind(mainMod .. " + P", hl.dsp.window.pseudo()) +hl.bind(mainMod .. " + J", hl.dsp.layout("togglesplit")) -- dwindle only +hl.bind(mainMod .. " + L", hl.dsp.exec_cmd("systemctl --user start quickshell-lock.service")) + +-- Move focus with mainMod + arrow keys +hl.bind(mainMod .. " + left", hl.dsp.focus({ direction = "left" })) +hl.bind(mainMod .. " + right", hl.dsp.focus({ direction = "right" })) +hl.bind(mainMod .. " + up", hl.dsp.focus({ direction = "up" })) +hl.bind(mainMod .. " + down", hl.dsp.focus({ direction = "down" })) + +-- Switch workspaces with mainMod + [0-9] +-- Move the focused window to a workspace with mainMod + SHIFT + [0-9] +for i = 1, 10 do + local key = i % 10 -- 10 maps to key 0 + hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i })) + hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i })) +end + +-- Scratchpad-style special workspace +hl.bind(mainMod .. " + S", hl.dsp.workspace.toggle_special("magic")) +hl.bind(mainMod .. " + SHIFT + S", hl.dsp.window.move({ workspace = "special:magic" })) + +-- Scroll through workspaces with mainMod + scroll wheel +hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" })) +hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" })) + +-- Move/resize windows with mainMod + drag +hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) +hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) + +-- Volume and mic mute +hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true }) +hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true }) +hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true, repeating = true }) +hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true, repeating = true }) + +-- Screen brightness; laptop panels only, likely a no-op on desktop monitors +hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%+"), { locked = true, repeating = true }) +hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%-"), { locked = true, repeating = true }) + +-- Media playback +hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true }) +hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) +hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) +hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true }) diff --git a/dotfiles/quickshell/bar/Notifications.qml b/dotfiles/quickshell/bar/Notifications.qml new file mode 100644 index 0000000..c29adb3 --- /dev/null +++ b/dotfiles/quickshell/bar/Notifications.qml @@ -0,0 +1,104 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets +import Quickshell.Services.Notifications + +PanelWindow { + WlrLayershell.layer: WlrLayer.Overlay + exclusionMode: ExclusionMode.Ignore + anchors { + top: true + right: true + } + margins { + top: 40 + right: 10 + } + implicitWidth: 340 + implicitHeight: column.implicitHeight + color: "transparent" + + NotificationServer { + id: server + bodySupported: true + + onNotification: notif => { + notif.tracked = true; + } + } + + ColumnLayout { + id: column + width: parent.width + spacing: 8 + + Repeater { + model: server.trackedNotifications + + delegate: Rectangle { + id: card + required property var modelData + Layout.fillWidth: true + implicitHeight: content.implicitHeight + 24 + radius: 8 + color: "#1a1b26" + + RowLayout { + id: content + anchors.fill: parent + anchors.margins: 12 + spacing: 10 + + IconImage { + source: Quickshell.iconPath(card.modelData.appIcon, true) + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + Layout.alignment: Qt.AlignTop + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 4 + + Text { + Layout.fillWidth: true + text: card.modelData.appName + color: "#c0caf5" + opacity: 0.6 + font.pixelSize: 11 + } + Text { + Layout.fillWidth: true + text: card.modelData.summary + color: "#c0caf5" + font.pixelSize: 14 + font.bold: true + wrapMode: Text.Wrap + } + Text { + Layout.fillWidth: true + visible: card.modelData.body !== "" + text: card.modelData.body + color: "#c0caf5" + font.pixelSize: 12 + wrapMode: Text.Wrap + } + } + } + + MouseArea { + anchors.fill: parent + onClicked: card.modelData.dismiss() + } + + Timer { + running: true + interval: card.modelData.expireTimeout > 0 ? card.modelData.expireTimeout * 1000 : 5000 + onTriggered: card.modelData.expire() + } + } + } + } +} diff --git a/dotfiles/quickshell/bar/shell.qml b/dotfiles/quickshell/bar/shell.qml index 05714f3..2010995 100644 --- a/dotfiles/quickshell/bar/shell.qml +++ b/dotfiles/quickshell/bar/shell.qml @@ -65,4 +65,6 @@ ShellRoot { } } } + + Notifications {} } diff --git a/dotfiles/quickshell/launcher/shell.qml b/dotfiles/quickshell/launcher/shell.qml new file mode 100644 index 0000000..141e3f7 --- /dev/null +++ b/dotfiles/quickshell/launcher/shell.qml @@ -0,0 +1,149 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Widgets + +ShellRoot { + PanelWindow { + id: launcher + + WlrLayershell.layer: WlrLayer.Overlay + exclusionMode: ExclusionMode.Ignore + focusable: true + anchors { + top: true + bottom: true + left: true + right: true + } + color: "transparent" + + property string query: "" + + function launchSelected() { + if (list.currentItem && list.currentItem.modelData) { + list.currentItem.modelData.execute(); + Qt.quit(); + } + } + + // Click outside the card to dismiss. + MouseArea { + anchors.fill: parent + onClicked: Qt.quit() + } + + Rectangle { + anchors.centerIn: parent + width: 560 + height: 360 + radius: 8 + color: "#1a1b26" + + MouseArea { + // Swallow clicks so they don't reach the dismiss MouseArea behind. + anchors.fill: parent + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 8 + + TextField { + id: input + Layout.fillWidth: true + placeholderText: "Run..." + font.pixelSize: 18 + color: "#c0caf5" + focus: true + padding: 12 + + background: Rectangle { + color: "transparent" + } + + onTextChanged: { + launcher.query = text; + list.currentIndex = filtered.values.length > 0 ? 0 : -1; + } + + Keys.onEscapePressed: Qt.quit() + Keys.onPressed: event => { + if (event.key === Qt.Key_Up) { + event.accepted = true; + if (list.currentIndex > 0) + list.currentIndex--; + } else if (event.key === Qt.Key_Down) { + event.accepted = true; + if (list.currentIndex < list.count - 1) + list.currentIndex++; + } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + event.accepted = true; + launcher.launchSelected(); + } + } + } + + ScriptModel { + id: filtered + values: { + const all = [...DesktopEntries.applications.values]; + const q = launcher.query.trim().toLowerCase(); + return q === "" ? all : all.filter(d => d.name && d.name.toLowerCase().includes(q)); + } + } + + ListView { + id: list + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + model: filtered.values + currentIndex: filtered.values.length > 0 ? 0 : -1 + highlight: Rectangle { + radius: 4 + color: "#c0caf5" + opacity: 0.2 + } + + delegate: Item { + id: entry + required property var modelData + required property int index + width: ListView.view.width + height: 36 + + MouseArea { + anchors.fill: parent + onClicked: list.currentIndex = entry.index + onDoubleClicked: launcher.launchSelected() + } + + Row { + anchors.fill: parent + anchors.margins: 8 + spacing: 10 + + IconImage { + source: Quickshell.iconPath(modelData.icon, true) + width: 20 + height: 20 + } + + Text { + text: modelData.name + color: "#c0caf5" + font.pixelSize: 14 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + } + } + } + } + } + } +} diff --git a/home.nix b/home.nix index 32dd24f..1f74440 100644 --- a/home.nix +++ b/home.nix @@ -11,19 +11,59 @@ }; }; + systemd.user.services.quickshell-launcher = { + Unit.Description = "Quickshell app launcher"; + Service = { + Type = "simple"; + ExecStart = "${pkgs.quickshell}/bin/quickshell -c launcher"; + }; + }; + home = { username = "igor"; homeDirectory = "/home/igor"; stateVersion = "26.05"; - # Hyprland session and Quickshell shells (bar + lock screen). The - # greeter has its own copy of Quickshell's config, published via + # hyprpolkitagent: GUI polkit agent, started from hyprland.lua. dolphin: + # file manager bound in hyprland.lua, with ffmpegthumbs for its video + # thumbnails. playerctl and wireplumber (wpctl): back the media-key binds + # in hyprland.lua. ffmpeg: the system H.264/AAC decoder Firefox loads at + # run time (it can't bundle those codecs itself; see the firefox profile + # below). libva-utils: run `vainfo` to confirm GPU video decode is active. + packages = [ + pkgs.hyprpolkitagent + pkgs.kdePackages.dolphin + pkgs.kdePackages.ffmpegthumbs + # Kate and KWrite ship as one package upstream; kwrite below is the + # lightweight one, kate the fuller editor, same binary set. + pkgs.kdePackages.kate + pkgs.playerctl + pkgs.wireplumber + pkgs.ffmpeg + pkgs.libva-utils + pkgs.vscode + ]; + + # nano is the plain terminal editor git commit/crontab -e/etc. expect + # from $EDITOR; it blocks the caller by default, no extra flag needed. + # NIXOS_OZONE_WL: VS Code (kept for manual use, not wired as a default) + # is Electron; without this it falls back to XWayland instead of native + # Wayland. + sessionVariables = { + EDITOR = "nano"; + VISUAL = "nano"; + NIXOS_OZONE_WL = "1"; + }; + + # Hyprland session and Quickshell shells (bar, lock screen, launcher). + # The greeter has its own copy of Quickshell's config, published via # environment.etc in configuration.nix instead of home-manager, since # it runs as a separate system user with no home directory here. file = { - ".config/hypr/hyprland.conf".source = ./dotfiles/hypr/hyprland.conf; + ".config/hypr/hyprland.lua".source = ./dotfiles/hypr/hyprland.lua; ".config/quickshell/shell.qml".source = ./dotfiles/quickshell/bar/shell.qml; ".config/quickshell/lockscreen".source = ./dotfiles/quickshell/lockscreen; + ".config/quickshell/launcher".source = ./dotfiles/quickshell/launcher; ".config/quickshell/shared".source = ./dotfiles/quickshell/shared; }; }; @@ -51,6 +91,21 @@ }; }; + # Firefox is GTK3, so it already picks up the adw-gtk3-dark theme and + # Papirus icons below for its native chrome (dialogs, scrollbars). Its + # own tab/toolbar chrome is a separate layer, themed via userChrome on + # the profile below once there's a real stylesheet to put there. + firefox = { + enable = true; + profiles.igor = { + isDefault = true; + # Mesa's radeonsi VA-API driver comes from hardware.graphics.enable + # in configuration.nix; this just tells Firefox to actually use it + # instead of decoding video on the CPU. + settings."media.ffmpeg.vaapi.enabled" = true; + }; + }; + # home-manager-managed (not just enabled in configuration.nix) so that # Starship's shell hook below gets woven into ~/.zshrc automatically. zsh.enable = true; @@ -63,7 +118,35 @@ }; }; - xdg.autostart.enable = true; + # GTK4/libadwaita apps ignore a full theme override (see the gtk.gtk4.theme + # warning in home-manager's own module), so only GTK3 gets one: adw-gtk3 + # mimics libadwaita's look, keeping GTK3 and GTK4 apps consistent without + # fighting GTK4 for it. colorScheme and iconTheme apply to both versions. + gtk = { + enable = true; + theme = { + name = "adw-gtk3-dark"; + package = pkgs.adw-gtk3; + }; + iconTheme = { + name = "Papirus-Dark"; + package = pkgs.papirus-icon-theme; + }; + colorScheme = "dark"; + }; + + xdg = { + autostart.enable = true; + + # Equivalent to `xdg-mime default kwrite.desktop text/plain`, but + # declarative in mimeapps.list, so it survives rebuilds. Named explicitly + # (not via defaultApplicationPackages) since kate.desktop and + # kwrite.desktop ship in the same package and both claim text/plain. + mimeApps = { + enable = true; + defaultApplications."text/plain" = [ "kwrite.desktop" ]; + }; + }; # Auto-lock on idle: lock at 5 minutes, blank the display 30 seconds # after that, lock again before suspend regardless of idle time. diff --git a/lefthook.yml b/lefthook.yml index 28bb060..3d3b927 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -2,3 +2,13 @@ pre-commit: commands: secrets: run: gitleaks protect --staged --redact + nixfmt: + glob: "*.nix" + run: nixfmt {staged_files} + stage_fixed: true + # Same invocation scripts/check.sh uses in CI: statix doesn't offer a + # reliable fix for every lint (e.g. repeated-key warnings are report-only), + # so this checks rather than auto-fixes, same as CI does. + statix: + glob: "*.nix" + run: statix check . diff --git a/modules/boot/splash.nix b/modules/boot/splash.nix index 36628dc..00396c0 100644 --- a/modules/boot/splash.nix +++ b/modules/boot/splash.nix @@ -17,4 +17,9 @@ _: "rd.systemd.show_status=auto" ]; }; + + # Loads the amdgpu driver during the initrd, before Plymouth starts, so the + # splash renders at its final resolution instead of flickering when the + # driver takes over later in boot. + hardware.amdgpu.initrd.enable = true; } diff --git a/modules/storage/luks-btrfs.nix b/modules/storage/luks-btrfs.nix index c3346e8..d57381d 100644 --- a/modules/storage/luks-btrfs.nix +++ b/modules/storage/luks-btrfs.nix @@ -19,7 +19,10 @@ in ESP = { priority = 1; name = "ESP"; - size = "1G"; + # Lanzaboote keeps a full signed UKI (kernel + initrd, amdgpu firmware + # included) per generation, times configurationLimit generations kept + # in modules/boot/secure-boot.nix; 1G leaves too little headroom. + size = "2G"; type = "EF00"; content = { type = "filesystem";