diff --git a/config/hypr/hyprland.conf b/config/hypr/hyprland.conf index c441654..ebf1366 100644 --- a/config/hypr/hyprland.conf +++ b/config/hypr/hyprland.conf @@ -1,5 +1,3 @@ -# -- Monitors & input defaults -- -# Monitor layout is host-specific; written by the flake per host. source = ~/.config/hypr/monitors.conf device { @@ -7,14 +5,6 @@ device { kb_options = caps:ctrl_modifier,grp:win_space_toggle } -# TrackPoint (Corne nipple): hold middle button + move to scroll, joystick-style -device { - name = corne-tp - scroll_method = on_button_down - scroll_button = 274 # BTN_MIDDLE - scroll_button_lock = false # set true to toggle scroll on/off with a tap instead of holding -} - input { kb_layout = us,dk kb_options = grp:win_space_toggle @@ -27,7 +17,7 @@ input { natural_scroll = true scroll_factor = 0.9 tap-to-click = false - clickfinger_behavior = true # 2-finger click = right-click, 3-finger = middle (Mac-like) + clickfinger_behavior = true disable_while_typing = true } } @@ -48,8 +38,6 @@ dwindle { force_split = 2 } -# Apps don't get to maximize themselves: kitty (0.49) asks ~250ms after it -# maps, so every new terminal opened maximized. SUPER+F still works. windowrule = match:class .*, suppress_event maximize misc { @@ -63,25 +51,19 @@ ecosystem { no_donation_nag = true } -# -- Environment -- env = QT_QPA_PLATFORMTHEME,qt6ct env = QT_STYLE_OVERRIDE,Adwaita-Dark 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 -- 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 = GDK_BACKEND=wayland eww daemon exec-once = systemctl --user start hyprpolkitagent -# -- Core -- bind = SUPER, Return, exec, kitty +bind = SUPER, A, exec, caffeine bind = SUPER, D, exec, fuzzel bind = SUPER, R, exec, eww open battery-popup bindr = SUPER, R, exec, eww close battery-popup @@ -89,88 +71,45 @@ bind = SUPER, Q, killactive bind = SUPER, F, fullscreen, 1 bind = SUPER SHIFT, Space, togglefloating 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 -- bind = SUPER, h, movefocus, l bind = SUPER, j, movefocus, d bind = SUPER, k, movefocus, u bind = SUPER, l, movefocus, r - -# -- Move windows -- bind = SUPER SHIFT, h, movewindow, l bind = SUPER SHIFT, j, movewindow, d bind = SUPER SHIFT, k, movewindow, u bind = SUPER SHIFT, l, movewindow, r - -# -- Workspaces (AeroSpace-style) -- -# Each workspace lives on one monitor. SUPER+N shows N on its monitor and -# focuses it, leaving the other screens alone; a new workspace opens on the -# focused monitor. SUPER+ALT+h/l carries a workspace to another monitor. bind = SUPER, 1, workspace, 1 bind = SUPER, 2, workspace, 2 bind = SUPER, 3, workspace, 3 bind = SUPER, 4, workspace, 4 bind = SUPER, 5, workspace, 5 - -# Send the window without following it, like AeroSpace's move-node-to-workspace. bind = SUPER SHIFT, 1, movetoworkspacesilent, 1 bind = SUPER SHIFT, 2, movetoworkspacesilent, 2 bind = SUPER SHIFT, 3, movetoworkspacesilent, 3 bind = SUPER SHIFT, 4, movetoworkspacesilent, 4 bind = SUPER SHIFT, 5, movetoworkspacesilent, 5 - -# AeroSpace's alt-tab / alt-shift-tab: previous workspace, and send the -# current workspace to the next monitor (+1 wraps past the last one). bind = SUPER, Tab, workspace, previous bind = SUPER SHIFT, Tab, movecurrentworkspacetomonitor, +1 bind = SUPER ALT, Tab, movecurrentworkspacetomonitor, +1 - -# -- Move current workspace to another monitor -- bind = SUPER ALT, h, movecurrentworkspacetomonitor, l bind = SUPER ALT, l, movecurrentworkspacetomonitor, r - -# -- Resize (hold for repeat) -- binde = SUPER CTRL, h, resizeactive, -40 0 binde = SUPER CTRL, j, resizeactive, 0 40 binde = SUPER CTRL, k, resizeactive, 0 -40 binde = SUPER CTRL, l, resizeactive, 40 0 - -# -- Mouse -- bindm = SUPER, mouse:272, movewindow bindm = SUPER, mouse:273, resizewindow - -# -- Audio (bindel: locked + repeat; bindl: locked, no repeat) -- bindel = , XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+ bindel = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- bindl = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle bindl = , XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle - -# -- Brightness -- bindel = , XF86MonBrightnessUp, exec, brightnessctl set 5%+ bindel = , XF86MonBrightnessDown, exec, brightnessctl set 5%- - -# -- Media -- bindl = , XF86AudioPlay, exec, playerctl play-pause bindl = , XF86AudioNext, exec, playerctl next bindl = , XF86AudioPrev, exec, playerctl previous - -# -- Screenshots -- -# Super+Ctrl+S: focused monitor -> file + clipboard bind = SUPER CTRL, S, exec, hyprshot -m output -# Super+Shift+S: region (screen frozen while selecting) -> file + clipboard bind = SUPER SHIFT, S, exec, hyprshot -m region -z - diff --git a/flake.nix b/flake.nix index 4650ea1..e4b7b3e 100644 --- a/flake.nix +++ b/flake.nix @@ -24,38 +24,10 @@ inputs.nixpkgs.follows = "nixpkgs"; 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; - }; }; - outputs = { self, nixpkgs, apple-silicon, home-manager, firefox-addons, zen-browser, linear-tui-src, ... }: + outputs = { self, nixpkgs, apple-silicon, home-manager, firefox-addons, zen-browser, ... }: 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 ───────────────────── home = { pkgs, osConfig, ... }: { imports = [ zen-browser.homeModules.beta ]; home.stateVersion = "25.11"; @@ -65,9 +37,6 @@ package = pkgs.eww; }; - # Raw configs that stay files (no good native module). - # `hypr` is recursive so we can drop a host-specific monitors.conf in - # alongside the shared files (hyprland.conf `source`s it). xdg.configFile = { "hypr" = { source = ./config/hypr; recursive = true; }; "hypr/monitors.conf".text = @@ -96,17 +65,6 @@ "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] @@ -122,10 +80,6 @@ ''; }; - # 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 = { @@ -147,8 +101,6 @@ enable = true; settings = { font_size = "11.5"; - - # cyberdream theme background = "#16181a"; foreground = "#ffffff"; cursor = "#ffffff"; @@ -188,26 +140,20 @@ hyprcursor.enable = true; }; - # Dark GTK apps (file dialogs, etc). gtk = { enable = true; theme = { name = "Adwaita-dark"; package = pkgs.gnome-themes-extra; }; gtk3.extraConfig.gtk-application-prefer-dark-theme = 1; gtk4.extraConfig.gtk-application-prefer-dark-theme = 1; - # Adopt the new default: don't force a theme on GTK4/libadwaita apps - # (dark still applies via prefer-dark-theme above). Silences the - # stateVersion < 26.05 legacy-default warning. gtk4.theme = null; }; - # Neovim: init.lua verbatim; plugins + LSP pinned by Nix. programs.neovim = { enable = true; withPython3 = false; withRuby = false; withNodeJs = false; - initLua = "vim.g.nix_managed = true\n" - + builtins.readFile ./config/nvim/init.lua; + initLua = builtins.readFile ./config/nvim/init.lua; plugins = with pkgs.vimPlugins; [ nvim-web-devicons cyberdream-nvim oil-nvim nvim-lspconfig typst-preview-nvim blink-cmp Navigator-nvim @@ -222,7 +168,6 @@ ]; }; - # Zen browser + Tridactyl, extensions pinned (works on aarch64 + x86). programs.zen-browser = { enable = true; setAsDefaultBrowser = true; @@ -230,16 +175,11 @@ settings = { "browser.newtabpage.enabled" = false; "privacy.donottrackheader.enabled" = true; - "signon.rememberSignons" = false; - - # Dark mode "browser.theme.toolbar-theme" = 0; "browser.theme.content-theme" = 0; "layout.css.prefers-color-scheme.content-override" = 0; "ui.systemUsesDarkTheme" = 1; }; - # Re-instantiate pkgs with the firefox-addons overlay + allowUnfree so - # the (unfree) 1Password addon evaluates under *our* config. extensions.packages = let addonsPkgs = import pkgs.path { inherit (pkgs.stdenv.hostPlatform) system; @@ -254,7 +194,6 @@ }; }; - # zsh + starship, no framework. programs.zsh = { enable = true; autosuggestion.enable = true; @@ -263,17 +202,12 @@ }; programs.starship.enable = true; - - - # SSH auth via the 1Password agent — private keys stay in the vault, - # never on disk or in the Nix store. programs.ssh = { enable = true; enableDefaultConfig = false; settings."*".IdentityAgent = "~/.1password/agent.sock"; }; - # git identity + SSH commit signing through 1Password's op-ssh-sign. programs.git = { enable = true; settings = { @@ -281,12 +215,9 @@ user.email = "mads@deepvis.ai"; user.signingkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAPlpVGZLhfrJqh2j5q3reyGOPQQ9BgW8syQ28tYjlg2"; gpg.format = "ssh"; - # NixOS store path — NOT 1Password's suggested /opt/1Password/... which doesn't exist here. "gpg \"ssh\"".program = "${pkgs._1password-gui}/share/1password/op-ssh-sign"; commit.gpgsign = true; - # Transparently use SSH (1Password agent) for the deepvis server, - # so existing HTTPS remotes need no manual `git remote set-url`. url."git@git.deepvis.ai:".insteadOf = "https://git.deepvis.ai/"; }; }; @@ -294,34 +225,14 @@ home.packages = with pkgs; [ ripgrep fd tridactyl-native lazygit rustc cargo clippy rustfmt rust-analyzer claude-code gcc bluetui impala postgresql fastfetch (linearTui pkgs) ]; }; - # ───────────────────── shared system (both hosts) ───────────────────── common = { pkgs, ... }: { nix.settings.experimental-features = [ "nix-command" "flakes" ]; - # Trusted so client-side nix config takes effect — substituters and - # remote builders are trusted settings, silently ignored for anyone - # else. A cache that appears to do nothing is the usual symptom. nix.settings.trusted-users = [ "mads" ]; - - # The deepvis binary cache: the embedded flakes in models/ort-nix - # (onnxruntime built from source, a CUDA training env) are hours of - # work to build and seconds to substitute. 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 root daemon, which has no known_hosts and never gets a tty to - # answer a trust prompt. programs.ssh.knownHosts."builder" = { extraHostNames = [ "5.9.71.104" "builder.tailf5eaab.ts.net" ]; publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHnKIrxYtlovvYGgkpEZXY8wG/CuSLMGOM/84eIlyeVW"; @@ -334,51 +245,18 @@ { hostName = "builder"; sshUser = "builder"; - # The daemon (root) does the ssh'ing, so it needs an explicit - # on-disk key — it has no access to a user's agent. sshKey = "/home/mads/.ssh/id_ed25519"; protocol = "ssh-ng"; system = "x86_64-linux"; maxJobs = 16; 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" ]; - } ]; - # iwd owns Wi-Fi (driven from impala, which is iwd-only); networkd does - # DHCP and resolved does DNS. No NetworkManager: running it on top of - # iwd meant two managers with separate saved networks, which ended in - # "Secrets were required, but not provided". networking.wireless.iwd.enable = true; - networking.useNetworkd = true; # DHCP on any ethernet/wifi by default - systemd.network.wait-online.anyInterface = true; # one link up is enough + networking.useNetworkd = true; + systemd.network.wait-online.anyInterface = true; - # iwd only reads /var/lib/iwd. C = copy once, so a password added to - # the file later (or via impala) is never overwritten. systemd.tmpfiles.rules = [ "C /var/lib/iwd/eduroam.8021x 0600 root root - ${pkgs.writeText "eduroam.8021x" '' [Security] @@ -389,26 +267,17 @@ ''}" ]; - # Secret Service (org.freedesktop.secrets) provider, D-Bus-activated on - # demand. Without it 1Password can't persist the 2FA device token - # ("unable to save your two-factor token") and re-prompts every unlock. services.gnome.gnome-keyring.enable = true; - programs._1password.enable = true; programs._1password-gui = { enable = true; polkitPolicyOwners = [ "mads" ]; }; - # Zen (a Firefox fork) runs as "zen-beta"; 1Password only trusts an - # allowlist of browser process names, so add it or the extension can't - # unlock via the desktop app. Manifest already lives in ~/.mozilla. environment.etc."1password/custom_allowed_browsers".text = '' zen-beta ''; services.tailscale.enable = true; - # Tailscale SSH: keyless, portless SSH gated by tailnet ACLs. - # Needs a matching "ssh" rule in the admin console to actually permit access. services.tailscale.extraUpFlags = [ "--ssh" ]; time.timeZone = "Europe/Copenhagen"; # set yours @@ -417,35 +286,11 @@ programs.hyprland = { enable = true; withUWSM = 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; - # ── 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 = { @@ -459,8 +304,6 @@ extraPortals = [ pkgs.xdg-desktop-portal-hyprland ]; }; - - # greetd autologins mads straight into Hyprland (via uwsm). services.greetd = { enable = true; settings = { @@ -473,8 +316,6 @@ hardware.bluetooth.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 ]; @@ -484,7 +325,6 @@ wl-clipboard grim slurp swaybg brightnessctl playerctl hyprshot hyprpolkitagent xdg-utils - # Front-end for caffeine.service (above). Bound to SUPER+A. (writeShellApplication { name = "caffeine"; runtimeInputs = [ systemd ]; @@ -524,14 +364,12 @@ isNormalUser = true; extraGroups = [ "wheel" "video" "docker" ]; shell = pkgs.zsh; - initialPassword = "changeme"; # CHANGE after first boot (`passwd`) + initialPassword = "changeme"; }; system.stateVersion = "25.11"; - home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; - # Back up (don't clobber) any pre-existing files HM wants to manage. home-manager.backupFileExtension = "hm-bak"; home-manager.users.mads = home; }; @@ -550,69 +388,45 @@ in { nixosConfigurations = { - # ── Apple Silicon MacBook (Asahi) ── mbp = mkHost { system = "aarch64-linux"; modules = [ apple-silicon.nixosModules.apple-silicon-support - ./hosts/mbp/hardware-configuration.nix # generated on the Mac + ./hosts/mbp/hardware-configuration.nix ({ ... }: { networking.hostName = "mbp"; - - # Wi-Fi/BT firmware, vendored (a pure flake can't read /boot/asahi). hardware.asahi.peripheralFirmwareDirectory = ./firmware; hardware.asahi.enable = true; boot.loader.systemd-boot.enable = true; - boot.loader.systemd-boot.configurationLimit = 3; # 500M ESP is small + boot.loader.systemd-boot.configurationLimit = 3; boot.extraModprobeConfig = "options hid_apple fnmode=1"; 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 + memoryPercent = 50; }; - # 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 + "vm.page-cluster" = 0; }; - # Build x86_64 derivations (packing-station iso/deploys) via qemu. boot.binfmt.emulatedSystems = [ "x86_64-linux" ]; - - # Resolve .local names, so the packing stations can be reached and - # deployed to by hostname instead of by address. + services.avahi = { enable = true; nssmdns4 = true; }; - services.resolved.settings.Resolve.MulticastDNS = false; # avahi owns mDNS + services.resolved.settings.Resolve.MulticastDNS = false; - # Asahi kernel + Mesa cache, and the Raspberry Pi vendor - # kernel/firmware cache (else both compile from source). nix.settings = { extra-substituters = [ "https://nixos-apple-silicon.cachix.org" @@ -627,47 +441,40 @@ ]; }; - # ── Arbitrary x86_64 PC ── pc = mkHost { system = "x86_64-linux"; modules = [ - ./hosts/pc/hardware-configuration.nix # generated on the PC + ./hosts/pc/hardware-configuration.nix ({ config, ... }: { networking.hostName = "pc"; - # GigE camera link: static, never a route out. The segment also - # carries AAU's IPv6 RAs, and taking them made v6 traffic default - # out this port into a dead end. networking.interfaces.enp7s0 = { useDHCP = false; ipv4.addresses = [ { address = "192.168.12.1"; prefixLength = 24; } ]; }; systemd.network.networks."40-enp7s0" = { networkConfig.IPv6AcceptRA = false; - networkConfig.ConfigureWithoutCarrier = true; # camera may be off + networkConfig.ConfigureWithoutCarrier = true; linkConfig.RequiredForOnline = "no"; }; boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; # normal x86 UEFI + boot.loader.efi.canTouchEfiVariables = true; - # Desktop: never suspend or hibernate, even if something asks. systemd.sleep.settings.Sleep = { AllowSuspend = "no"; AllowHibernation = "no"; AllowHybridSleep = "no"; AllowSuspendThenHibernate = "no"; }; - # Ignore the keys that would normally trigger sleep. services.logind.suspendKey = "ignore"; services.logind.hibernateKey = "ignore"; services.logind.lidSwitch = "ignore"; - # NVIDIA (RTX 20-series or newer → open kernel module). services.xserver.videoDrivers = [ "nvidia" ]; - hardware.graphics.enable = true; # OpenGL/Vulkan userspace (was hardware.opengl) + hardware.graphics.enable = true; hardware.nvidia = { - modesetting.enable = true; # required for Wayland/Hyprland - open = true; # open kernel module (Turing+) + modesetting.enable = true; + open = true; nvidiaSettings = true; package = config.boot.kernelPackages.nvidiaPackages.stable; }; @@ -676,7 +483,6 @@ }; }; - # `nix develop` on either arch. devShells = nixpkgs.lib.genAttrs [ "aarch64-linux" "x86_64-linux" ] (system: { default = nixpkgs.legacyPackages.${system}.mkShell { packages = [ nixpkgs.legacyPackages.${system}.nodejs ];