Compare commits

..

No commits in common. "ddca4e1d7276b27d359bbe43c4f5fa17235cd1ef" and "1ac7506a87a311c09c63023e5921153d11d75469" have entirely different histories.

4 changed files with 50 additions and 371 deletions

View file

@ -2,20 +2,6 @@
# Monitor layout is host-specific; written by the flake per host. # Monitor layout is host-specific; written by the flake per host.
source = ~/.config/hypr/monitors.conf source = ~/.config/hypr/monitors.conf
# Virtual desktops plugin. A vdesk spans every monitor, so SUPER+1..5 switches
# all screens together instead of workspaces being pinned per monitor.
source = ~/.config/hypr/plugins.conf
plugin {
virtual-desktops {
# The startup toast would fire on every login now that mako exists.
notifyinit = 0
rememberlayout = size
# Deliberately NOT enabling cycleworkspaces: upstream states it does
# not work with more than 2 monitors, and pc has 3.
}
}
device { device {
name = apple-spi-keyboard name = apple-spi-keyboard
kb_options = caps:ctrl_modifier,grp:win_space_toggle kb_options = caps:ctrl_modifier,grp:win_space_toggle
@ -77,15 +63,9 @@ ecosystem {
env = QT_QPA_PLATFORMTHEME,qt6ct env = QT_QPA_PLATFORMTHEME,qt6ct
env = QT_STYLE_OVERRIDE,Adwaita-Dark env = QT_STYLE_OVERRIDE,Adwaita-Dark
env = XDG_SCREENSHOTS_DIR,$HOME/Pictures/Screenshots env = XDG_SCREENSHOTS_DIR,$HOME/Pictures/Screenshots
# hyprshot ignores XDG_SCREENSHOTS_DIR: it reads HYPRSHOT_DIR, else XDG_PICTURES_DIR,
# else $HOME. Without this, screenshots land loose in ~/Pictures.
env = HYPRSHOT_DIR,$HOME/Pictures/Screenshots
# -- Autostart -- # -- Autostart --
exec-once = uwsm finalize exec-once = uwsm finalize
# Boot lands on the lock screen rather than an unlocked desktop. Unlocking it
# also unlocks the keyring (see security.pam.services.hyprlock in flake.nix).
exec-once = hyprlock
exec-once = swaybg -c '#16181a' exec-once = swaybg -c '#16181a'
exec-once = GDK_BACKEND=wayland eww daemon exec-once = GDK_BACKEND=wayland eww daemon
exec-once = systemctl --user start hyprpolkitagent exec-once = systemctl --user start hyprpolkitagent
@ -93,27 +73,13 @@ exec-once = systemctl --user start hyprpolkitagent
# -- Core -- # -- Core --
bind = SUPER, Return, exec, kitty bind = SUPER, Return, exec, kitty
bind = SUPER, D, exec, fuzzel bind = SUPER, D, exec, fuzzel
bind = SUPER, R, exec, eww open battery-popup bind = SUPER, B, exec, eww open battery-popup
bindr = SUPER, R, exec, eww close battery-popup bindr = SUPER, B, exec, eww close battery-popup
bind = SUPER, Q, killactive bind = SUPER, Q, killactive
bind = SUPER, F, fullscreen, 1 bind = SUPER, F, fullscreen, 1
bind = SUPER SHIFT, Space, togglefloating bind = SUPER SHIFT, Space, togglefloating
bind = SUPER, Escape, exec, hyprlock bind = SUPER, Escape, exec, hyprlock
# Amphetamine-style stay-awake toggle: idle + sleep + lid. See flake.nix.
bind = SUPER, A, exec, caffeine
# -- Floating panels --
# Each gets its own app_id so the rule below floats only these and not every
# terminal. Quit with the TUI's own `q`; no toggle wrapper.
#
# The float/size rule is in monitors.conf, generated per host: Hyprland 0.56
# `size` takes ONLY absolute pixels -- percentages are silently ignored (they
# parse fine and then do nothing), so the value depends on the resolution.
bind = SUPER, W, exec, kitty --class panel-wifi impala
bind = SUPER, E, exec, kitty --class panel-bt bluetui
# -- Focus -- # -- Focus --
bind = SUPER, h, movefocus, l bind = SUPER, h, movefocus, l
bind = SUPER, j, movefocus, d bind = SUPER, j, movefocus, d
@ -126,18 +92,18 @@ bind = SUPER SHIFT, j, movewindow, d
bind = SUPER SHIFT, k, movewindow, u bind = SUPER SHIFT, k, movewindow, u
bind = SUPER SHIFT, l, movewindow, r bind = SUPER SHIFT, l, movewindow, r
# -- Virtual desktops (plugin dispatchers, not Hyprland workspaces) -- # -- Workspaces --
bind = SUPER, 1, vdesk, 1 bind = SUPER, 1, workspace, 1
bind = SUPER, 2, vdesk, 2 bind = SUPER, 2, workspace, 2
bind = SUPER, 3, vdesk, 3 bind = SUPER, 3, workspace, 3
bind = SUPER, 4, vdesk, 4 bind = SUPER, 4, workspace, 4
bind = SUPER, 5, vdesk, 5 bind = SUPER, 5, workspace, 5
bind = SUPER SHIFT, 1, movetodesk, 1 bind = SUPER SHIFT, 1, movetoworkspace, 1
bind = SUPER SHIFT, 2, movetodesk, 2 bind = SUPER SHIFT, 2, movetoworkspace, 2
bind = SUPER SHIFT, 3, movetodesk, 3 bind = SUPER SHIFT, 3, movetoworkspace, 3
bind = SUPER SHIFT, 4, movetodesk, 4 bind = SUPER SHIFT, 4, movetoworkspace, 4
bind = SUPER SHIFT, 5, movetodesk, 5 bind = SUPER SHIFT, 5, movetoworkspace, 5
# -- Move current workspace to another monitor -- # -- Move current workspace to another monitor --
bind = SUPER ALT, h, movecurrentworkspacetomonitor, l bind = SUPER ALT, h, movecurrentworkspacetomonitor, l
@ -169,8 +135,10 @@ bindl = , XF86AudioNext, exec, playerctl next
bindl = , XF86AudioPrev, exec, playerctl previous bindl = , XF86AudioPrev, exec, playerctl previous
# -- Screenshots -- # -- Screenshots --
# Super+Ctrl+S: focused monitor -> file + clipboard # Super+S: region -> clipboard
bind = SUPER CTRL, S, exec, hyprshot -m output bind = SUPER, S, exec, grimblast --notify copy area
# Super+Shift+S: region (screen frozen while selecting) -> file + clipboard # Super+Ctrl+S: full screen -> file + clipboard
bind = SUPER SHIFT, S, exec, hyprshot -m region -z bind = SUPER CTRL, S, exec, grimblast --notify copysave screen
# Super+Shift+S: region -> file + clipboard + open in GIMP
bind = SUPER SHIFT, S, exec, bash -c 'mkdir -p "$HOME/Pictures/Screenshots"; f="$HOME/Pictures/Screenshots/$(date +%Y-%m-%dT%H-%M-%S).png"; grimblast --notify --freeze save area "$f" && wl-copy --type image/png < "$f" && gimp "$f"'

View file

@ -78,43 +78,6 @@
"type": "github" "type": "github"
} }
}, },
"hyprland-virtual-desktops": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1790338297,
"narHash": "sha256-iJP+CyrS/fw/dqysRN9GtVB8GlJZcja4rkjBeJsXFsg=",
"owner": "levnikmyskin",
"repo": "hyprland-virtual-desktops",
"rev": "74b88ee5209c4dcea74eedc4646e185b02a8f946",
"type": "github"
},
"original": {
"owner": "levnikmyskin",
"repo": "hyprland-virtual-desktops",
"type": "github"
}
},
"linear-tui-src": {
"flake": false,
"locked": {
"lastModified": 1790503843,
"narHash": "sha256-FS0w8a53N+XN0tnxUzpu4FNG0rYVycv/ZlnB7bPgd+I=",
"owner": "k1-c",
"repo": "linear-tui",
"rev": "fa79ffed365c2deff1916022abe8652989682216",
"type": "github"
},
"original": {
"owner": "k1-c",
"ref": "v0.13.0",
"repo": "linear-tui",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1786106723, "lastModified": 1786106723,
@ -136,8 +99,6 @@
"apple-silicon": "apple-silicon", "apple-silicon": "apple-silicon",
"firefox-addons": "firefox-addons", "firefox-addons": "firefox-addons",
"home-manager": "home-manager", "home-manager": "home-manager",
"hyprland-virtual-desktops": "hyprland-virtual-desktops",
"linear-tui-src": "linear-tui-src",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"zen-browser": "zen-browser" "zen-browser": "zen-browser"
} }

286
flake.nix
View file

@ -24,46 +24,10 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
inputs.home-manager.follows = "home-manager"; inputs.home-manager.follows = "home-manager";
}; };
# linear-tui has no flake of its own, so take the source and build it below.
# Pinned to a tag; `nix flake update` moves it and records the hash in
# flake.lock, so there is no sha256 to maintain by hand.
linear-tui-src = {
url = "github:k1-c/linear-tui/v0.13.0";
flake = false;
}; };
# Virtual desktops: one desktop spans all monitors, replacing Hyprland's outputs = { self, nixpkgs, apple-silicon, home-manager, firefox-addons, zen-browser, ... }:
# workspace-pinned-to-monitor model. Its own nixpkgs pin is older than
# ours, and Hyprland plugins are ABI-locked to the compositor, so it must
# follow ours to compile against the hyprland we actually run.
hyprland-virtual-desktops = {
url = "github:levnikmyskin/hyprland-virtual-desktops";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, apple-silicon, home-manager, firefox-addons, zen-browser, linear-tui-src, hyprland-virtual-desktops, ... }:
let let
# linear-tui, built from the pinned source input above. Upstream ships no
# flake and it is not in nixpkgs, but it has a committed Cargo.lock, so
# cargoLock.lockFile covers the dependency hashes with nothing to vendor.
#
# No OpenSSL/pkg-config needed: the crate selects reqwest's rustls
# backend on purpose, and takes its trust roots from the OS store.
linearTui = pkgs: pkgs.rustPlatform.buildRustPackage {
pname = "linear-tui";
version = "0.13.0";
src = linear-tui-src;
cargoLock.lockFile = "${linear-tui-src}/Cargo.lock";
# One test (a_worktree_belongs_to_its_repository) shells out to `git`,
# which the build sandbox lacks -- it panics on NotFound unwrapping
# Command::new("git"). Supplying git lets all 512 tests run.
nativeCheckInputs = [ pkgs.git ];
meta.mainProgram = "linear-tui";
};
# ───────────────────── shared home-manager user ───────────────────── # ───────────────────── shared home-manager user ─────────────────────
home = { pkgs, osConfig, ... }: { home = { pkgs, osConfig, ... }: {
imports = [ zen-browser.homeModules.beta ]; imports = [ zen-browser.homeModules.beta ];
@ -83,76 +47,19 @@
if osConfig.networking.hostName == "mbp" if osConfig.networking.hostName == "mbp"
then '' then ''
monitor = , preferred, auto, 2.0 monitor = , preferred, auto, 2.0
# Floating TUI panels: 70% wide, full height. Absolute px because
# Hyprland 0.56 ignores percentages in `size`. 1728x1080 logical.
windowrule = match:class ^(panel-wifi|panel-bt)$, float on, size 1210 1080, center on
'' ''
else '' else ''
monitor = desc:Lenovo Group Limited P27q-20 V909LRFR, 2560x1440@60, 0x0, 1.0 monitor = desc:Lenovo Group Limited P27q-20 V909LRFR, 2560x1440@60, 0x0, 1.0
monitor = desc:Lenovo Group Limited P27q-20 V909LPNY, 2560x1440@60, 2560x0, 1.0 monitor = desc:Lenovo Group Limited P27q-20 V909LPNY, 2560x1440@60, 2560x0, 1.0
monitor = desc:Lenovo Group Limited P27q-20 V909LRDD, 2560x1440@60, 5120x0, 1.0 monitor = desc:Lenovo Group Limited P27q-20 V909LRDD, 2560x1440@60, 5120x0, 1.0
workspace = 1, monitor:desc:Lenovo Group Limited P27q-20 V909LRFR, default:true
# Same panels, sized for 2560x1440 logical. workspace = 2, monitor:desc:Lenovo Group Limited P27q-20 V909LPNY, default:true
windowrule = match:class ^(panel-wifi|panel-bt)$, float on, size 1792 1440, center on workspace = 3, monitor:desc:Lenovo Group Limited P27q-20 V909LRDD, default:true
'';
# Plugin load line, as a store path. Sourced from hyprland.conf, the
# same pattern as monitors.conf. Upstream installs the library as
# libvirtual-desktops.so (not lib<pname>.so).
"hypr/plugins.conf".text = ''
plugin = ${hyprland-virtual-desktops.packages.${pkgs.stdenv.hostPlatform.system}.virtual-desktops}/lib/libvirtual-desktops.so
''; '';
"eww".source = ./config/eww; "eww".source = ./config/eww;
"fuzzel".source = ./config/fuzzel; "fuzzel".source = ./config/fuzzel;
}; };
# Launcher hygiene. These .desktop entries ship with packages we do want,
# but they are noise in fuzzel. A NoDisplay entry in XDG_DATA_HOME
# shadows the package's own copy, which is the only way to hide one
# without patching the package.
#
# Deliberately home.file and NOT xdg.desktopEntries: that option only
# emits anything when xdg.enable = true, which we don't set, so it
# silently produces no files at all.
# nvim - neovim's "Neovim wrapper" entry; we only launch it from a terminal
# uuctl - uwsm's systemd-user-unit picker, pulled in by withUWSM
home.file = {
".local/share/applications/nvim.desktop".text = ''
[Desktop Entry]
Type=Application
Name=Neovim wrapper
NoDisplay=true
'';
".local/share/applications/uuctl.desktop".text = ''
[Desktop Entry]
Type=Application
Name=uuctl
NoDisplay=true
'';
};
# Notification daemon. Nothing owned org.freedesktop.Notifications before
# this, so notify-send failed outright: hyprshot's "Screenshot saved"
# toasts went nowhere, and kitty logged a DBus ServiceUnknown error on
# every launch. Colours match the cyberdream kitty theme above.
services.mako = {
enable = true;
settings = {
font = "JetBrainsMono Nerd Font 11";
background-color = "#16181a";
text-color = "#ffffff";
border-color = "#5ea1ff";
border-size = 2;
border-radius = 8;
default-timeout = 5000;
anchor = "top-right";
margin = "12";
padding = "12";
width = 380;
};
};
programs.kitty = { programs.kitty = {
enable = true; enable = true;
settings = { settings = {
@ -198,7 +105,7 @@
hyprcursor.enable = true; hyprcursor.enable = true;
}; };
# Dark GTK apps (file dialogs, etc). # Dark GTK apps (gimp, file dialogs).
gtk = { gtk = {
enable = true; enable = true;
theme = { name = "Adwaita-dark"; package = pkgs.gnome-themes-extra; }; theme = { name = "Adwaita-dark"; package = pkgs.gnome-themes-extra; };
@ -301,47 +208,30 @@
}; };
}; };
home.packages = with pkgs; [ ripgrep fd tridactyl-native lazygit rustc cargo clippy rustfmt rust-analyzer claude-code gcc bluetui impala postgresql fastfetch (linearTui pkgs) ]; home.packages = with pkgs; [ ripgrep fd tridactyl-native lazygit rustc cargo clippy rustfmt rust-analyzer claude-code gcc bluetui impala];
}; };
# ───────────────────── shared system (both hosts) ───────────────────── # ───────────────────── shared system (both hosts) ─────────────────────
common = { pkgs, ... }: { common = { pkgs, ... }: {
nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Trusted so client-side nix config takes effect — substituters and # Trusted so client-side nix config takes effect — the deepvis repo's
# remote builders are trusted settings, silently ignored for anyone # flake nixConfig advertises the team dev cache (attic on the builder,
# else. A cache that appears to do nothing is the usual symptom. # over the tailnet), and the daemon ignores it for untrusted users.
nix.settings.trusted-users = [ "mads" ]; nix.settings.trusted-users = [ "mads" ];
# x86_64 derivations build on the build host instead of under local
# The deepvis binary cache: the embedded flakes in models/ort-nix # qemu emulation — anything the dev cache doesn't already have ships
# (onnxruntime built from source, a CUDA training env) are hours of # there as a remote build (tailnet; the builder's `builder` user
# work to build and seconds to substitute. # exists for exactly this).
nix.settings.extra-substituters = [ "https://deepvis.cachix.org" ];
nix.settings.extra-trusted-public-keys = [
"deepvis.cachix.org-1:UE/H6ERLwqhSqubzh8+pzl9eTm0kpMExGe0ym6fENp8="
];
# Private cache, so reads need a token. nix refuses `netrc-file` on
# the command line under the daemon, and a token in the substituter
# URL would land in the process table — a root-owned 0600 netrc is
# the only form the calling process cannot read.
nix.settings.netrc-file = "/etc/nix/netrc";
# x86_64 derivations build off this machine rather than under local
# qemu emulation, and aarch64 ones go out too when the local
# machine is busy.
# #
# The host keys are pinned system-wide because the ssh'ing party is # The host key is pinned system-wide because the ssh'ing party is the
# the root daemon, which has no known_hosts and never gets a tty to # root daemon, which has no known_hosts and never gets a tty to
# answer a trust prompt. # answer a trust prompt.
programs.ssh.knownHosts."builder" = { programs.ssh.knownHosts."builder" = {
extraHostNames = [ "5.9.71.104" "builder.tailf5eaab.ts.net" ]; extraHostNames = [ "5.9.71.104" "builder.tailf5eaab.ts.net" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHnKIrxYtlovvYGgkpEZXY8wG/CuSLMGOM/84eIlyeVW"; publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHnKIrxYtlovvYGgkpEZXY8wG/CuSLMGOM/84eIlyeVW";
}; };
programs.ssh.knownHosts."eu.nixbuild.net" = {
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIQCZc54poJ8vqawd8TraNryQeJnvH1eLpIDgbiqymM";
};
nix.distributedBuilds = true; nix.distributedBuilds = true;
nix.buildMachines = [ nix.buildMachines = [{
{
hostName = "builder"; hostName = "builder";
sshUser = "builder"; sshUser = "builder";
# The daemon (root) does the ssh'ing, so it needs an explicit # The daemon (root) does the ssh'ing, so it needs an explicit
@ -351,33 +241,7 @@
system = "x86_64-linux"; system = "x86_64-linux";
maxJobs = 16; maxJobs = 16;
supportedFeatures = [ "big-parallel" "kvm" "nixos-test" ]; supportedFeatures = [ "big-parallel" "kvm" "nixos-test" ];
} }];
# nixbuild.net, for both architectures. Listed after `builder` so
# the machine we already pay for is preferred.
#
# Every feature it supports is advertised, not just the two in the
# quickstart: nix only sends a derivation to a builder claiming
# *every* feature that derivation requires, so omitting `kvm` or
# `nixos-test` would silently route those back to local builds.
{
hostName = "eu.nixbuild.net";
sshUser = "root";
sshKey = "/home/mads/.ssh/id_ed25519";
protocol = "ssh-ng";
system = "x86_64-linux";
maxJobs = 100;
supportedFeatures = [ "benchmark" "big-parallel" "ca-derivations" "kvm" "nixos-test" ];
}
{
hostName = "eu.nixbuild.net";
sshUser = "root";
sshKey = "/home/mads/.ssh/id_ed25519";
protocol = "ssh-ng";
system = "aarch64-linux";
maxJobs = 100;
supportedFeatures = [ "benchmark" "big-parallel" "ca-derivations" "kvm" "nixos-test" ];
}
];
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
networking.networkmanager.wifi.backend = "iwd"; networking.networkmanager.wifi.backend = "iwd";
@ -409,44 +273,7 @@
programs.hyprland = { enable = true; withUWSM = true; }; programs.hyprland = { enable = true; withUWSM = true; };
programs.hyprlock.enable = true; programs.hyprlock.enable = true;
# Every GUI app lives in the one wayland-wm@hyprland.service cgroup, so
# with systemd's default OOMPolicy=stop a kernel OOM-kill of ANY process
# in it stops the whole unit -- compositor included. 1Password was always
# the victim (highest oom_score_adj), but losing it killed the entire
# desktop, and uwsm sets Restart=no, so nothing came back. It read as
# "Hyprland crashed" when the compositor was never at fault.
#
# continue: log the kill, lose that one app, keep the session. Merges
# into the drop-in the uwsm module already generates for this unit.
systemd.user.services."wayland-wm@".serviceConfig.OOMPolicy = "continue";
# Autologin means no password reaches PAM at login, so the gnome-keyring
# login keyring is never created or unlocked -- which is why 1Password
# could not persist its 2FA device token and re-prompted the YubiKey
# every unlock. Putting pam_gnome_keyring in hyprlock's stack instead
# makes the lock screen we already have do that job, so there is still
# exactly one password prompt and no second login screen.
security.pam.services.hyprlock.enableGnomeKeyring = true;
services.hypridle.enable = true; services.hypridle.enable = true;
# ── Amphetamine-style "stay awake" ──
# caffeine.service holds ONE logind block-inhibitor covering all three
# ways this machine can go dark:
# idle -> hypridle stops counting (no blank, no hyprlock)
# sleep -> suspend requests are refused
# handle-lid-switch -> closing the lid does nothing
# systemd owns the process, so "is it on?" is just "is the unit
# active?" -- no pidfile, and it can't be orphaned. Deliberately no
# wantedBy: it only ever starts via SUPER+A or the `caffeine` command.
systemd.user.services.caffeine = {
description = "Caffeine - inhibit idle, sleep and lid-switch";
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.systemd}/bin/systemd-inhibit --what=idle:sleep:handle-lid-switch --who=caffeine --why=stay-awake --mode=block ${pkgs.coreutils}/bin/sleep infinity";
};
};
xdg.portal = { xdg.portal = {
enable = true; enable = true;
extraPortals = [ pkgs.xdg-desktop-portal-hyprland ]; extraPortals = [ pkgs.xdg-desktop-portal-hyprland ];
@ -462,60 +289,19 @@
}; };
}; };
virtualisation.docker.enable = true;
hardware.bluetooth.enable = true; hardware.bluetooth.enable = true;
programs.zsh.enable = true; programs.zsh.enable = true;
# No NixOS manual: it puts a "NixOS Manual" entry in the launcher and we
# read the docs on the web. man pages are unaffected.
documentation.nixos.enable = false;
fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ]; fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ];
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
git kitty fuzzel git kitty fuzzel gimp
wl-clipboard grim slurp swaybg brightnessctl playerctl hyprshot wl-clipboard grim slurp swaybg brightnessctl playerctl grimblast
hyprpolkitagent xdg-utils hyprpolkitagent xdg-utils
# Front-end for caffeine.service (above). Bound to SUPER+A.
(writeShellApplication {
name = "caffeine";
runtimeInputs = [ systemd ];
text = ''
unit=caffeine.service
is_on() { systemctl --user is-active --quiet "$unit"; }
# Guarded so `caffeine` still works over SSH / with no compositor.
toast() {
command -v hyprctl >/dev/null 2>&1 || return 0
hyprctl notify "$1" 2000 "$2" "$3" >/dev/null 2>&1 || true
}
wake_on() {
systemctl --user start "$unit"
toast 5 "rgb(5eff6c)" "Caffeine ON - idle, sleep and lid disabled"
}
wake_off() {
systemctl --user stop "$unit"
toast 1 "rgb(ff6e5e)" "Caffeine OFF - normal sleep restored"
}
case "''${1:-toggle}" in
toggle) if is_on; then wake_off; else wake_on; fi ;;
on) wake_on ;;
off) wake_off ;;
status) if is_on; then echo on; else echo off; fi ;;
*) echo "usage: caffeine [toggle|on|off|status]" >&2; exit 2 ;;
esac
'';
})
]; ];
users.users.mads = { users.users.mads = {
isNormalUser = true; isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "video" "docker" ]; extraGroups = [ "wheel" "networkmanager" "video" ];
shell = pkgs.zsh; shell = pkgs.zsh;
initialPassword = "changeme"; # CHANGE after first boot (`passwd`) initialPassword = "changeme"; # CHANGE after first boot (`passwd`)
}; };
@ -561,38 +347,6 @@
boot.extraModprobeConfig = "options hid_apple fnmode=1"; boot.extraModprobeConfig = "options hid_apple fnmode=1";
boot.blacklistedKernelModules = [ "pcspkr" ]; boot.blacklistedKernelModules = [ "pcspkr" ];
# Root is btrfs (p8). A scrub re-reads every block and verifies its
# checksum, which is the only way bit rot on this SSD surfaces before
# something tries to read the damaged file. Metadata is DUP, so a bad
# metadata block gets repaired from its copy automatically.
services.btrfs.autoScrub = {
enable = true;
interval = "monthly";
fileSystems = [ "/" ];
};
# 15G of RAM and no swap at all: a `nixos-rebuild` forks ~60 nix
# evaluators, and stacked on a browser and a few editor sessions
# that was enough to exhaust it. The kernel then OOM-killed a
# process inside wayland-wm@hyprland.service, and systemd's default
# OOMPolicy=stop tore the whole graphical session down with it.
#
# zram rather than a swapfile, because root is compress=zstd and a
# btrfs swapfile needs its own nodatacow, uncompressed subvolume.
zramSwap = {
enable = true;
memoryPercent = 50; # 7.6G of backing store, ~a third of that in real RAM
};
# Swapping to RAM is cheap, so let the kernel compress anon pages
# rather than evict page cache. At the OOM above, cache had already
# been squeezed to 40M while 13G sat in anon and nothing was
# reclaimable — the default swappiness of 60 gets that backwards here.
boot.kernel.sysctl = {
"vm.swappiness" = 180;
"vm.page-cluster" = 0; # zram has no seek penalty, so no readahead
};
# Build x86_64 derivations (packing-station iso/deploys) via qemu. # Build x86_64 derivations (packing-station iso/deploys) via qemu.
boot.binfmt.emulatedSystems = [ "x86_64-linux" ]; boot.binfmt.emulatedSystems = [ "x86_64-linux" ];

View file

@ -13,13 +13,9 @@
boot.kernelModules = [ ]; boot.kernelModules = [ ];
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
# Root lives on nvme0n1p8 (btrfs, subvol @) — the 256G reclaimed from
# the shrunk macOS container. The old ext4 p5 is kept as a rollback until
# it is added to this pool as a second device.
fileSystems."/" = fileSystems."/" =
{ device = "/dev/disk/by-uuid/c828c1a0-ce7b-4e27-b8e4-bce032bb30bf"; { device = "/dev/disk/by-uuid/2664fdc1-4309-4682-a02e-4e27eb1e14f6";
fsType = "btrfs"; fsType = "ext4";
options = [ "subvol=@" "compress=zstd" "noatime" ];
}; };
fileSystems."/boot" = fileSystems."/boot" =