terra: add Ryzen 9 5900X desktop (Hyprland, tailnet, dev tools)

Replaces CachyOS on the OS SSD (Kingston SA400, disko-managed). Dev-data
disks (sdc ext4 /mnt/hdd_01, LVM vg_ssd /mnt/ssd_01) stay out of disko and
are mounted as plain filesystems so they're never wiped. Desktop split into
services/desktop/desktop-hyprland.nix (session: compositor, greeter, audio,
portals) and desktop-apps.nix (things darman actually launches, including
claude-code — allowlisted alongside the other unfree desktop apps).
This commit is contained in:
2026-07-23 23:14:36 +02:00
parent c86e8a19c4
commit 67b12cb96b
10 changed files with 347 additions and 0 deletions
+4
View File
@@ -12,6 +12,7 @@ keys:
# (ssh-to-age). The host decrypts at runtime using /etc/ssh/ssh_host_ed25519_key.
- &jupiter age1zak7glavmg4026p2389fyqe769vqm4jrryknuqckgqq4merz5f7q44rkkt
- &neptun age1hp72xyx2cnd05937e4eww95g5kdtn0wsf9j2nypw330pa69gfdxqn0lpkp
- &terra age1rfcmu6zh40v4260l9hnf8ajs9vly0s06rx3ey76eu78dp9t7getqyhmkut
# mercury (rpi) uses a dedicated age key (SD image, no ssh-host-key delivery);
# the private key is dropped on its boot partition after flashing.
- &mercury age1cpty7zrgnn6l97upq00w5wa8zcvnkxkdt2jvhlj97jh83exure4slha43t
@@ -24,6 +25,9 @@ creation_rules:
- path_regex: secrets/neptun\.yaml$
key_groups:
- age: [ *admin, *neptun ]
- path_regex: secrets/terra\.yaml$
key_groups:
- age: [ *admin, *terra ]
- path_regex: secrets/mercury\.yaml$
key_groups:
- age: [ *admin, *mercury ]
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-ms-sln">
<comment>Visual Studio Solution</comment>
<sub-class-of type="text/plain"/>
<glob pattern="*.sln" weight="80"/>
<magic priority="80">
<match type="string" value="Microsoft Visual Studio Solution File" offset="0"/>
</magic>
</mime-type>
<mime-type type="application/x-ms-slnx">
<comment>Visual Studio XML Solution</comment>
<sub-class-of type="application/xml"/>
<glob pattern="*.slnx" weight="80"/>
</mime-type>
</mime-info>
+47
View File
@@ -0,0 +1,47 @@
{ config, pkgs, lib, ... }:
# terra — Ryzen 9 5900X / Radeon RX 6800 XT desktop (MSI MS-7A32). Replaces
# CachyOS on the OS SSD (Kingston SA400, sdb). Dev-data disks (sdc ext4
# /mnt/hdd_01, LVM vg_ssd /mnt/ssd_01) are kept out of disko and mounted here
# as plain filesystems so they're never wiped. The leftover ntfs disks
# (sda, sdf, nvme0n1) are ignored entirely — not referenced anywhere.
{
imports = [
./hardware-configuration.nix
./disk-config.nix # disko: OS-disk (sdb) partitions + filesystems
./secrets.nix # sops-nix: darman password, tailscale key
../../common.nix # shared base: user / ssh / nix / firewall
../../services/vpn/tailscale.nix
../../services/desktop/desktop-hyprland.nix
../../services/desktop/desktop-apps.nix
];
networking.hostName = "terra";
# ---- home-manager (user-level config for darman) ----
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.backupFileExtension = "hm-bak";
home-manager.users.darman = import ./home.nix;
# ---- Boot (UEFI) ----
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
hardware.cpu.amd.updateMicrocode = true;
# ---- Dev-data disks — NOT in disko, mounted read-write, never wiped ----
# UUIDs captured from the running CachyOS box; verify after install
# (`lsblk -o NAME,UUID,MOUNTPOINT`) in case disko/kernel enumerates differently.
fileSystems."/mnt/hdd_01" = {
device = "/dev/disk/by-uuid/b8445126-ec6d-4f88-818a-d9e13031d9a4";
fsType = "ext4";
options = [ "nofail" ];
};
fileSystems."/mnt/ssd_01" = {
device = "/dev/disk/by-uuid/6ca18a9f-27bc-4e58-aea8-de43a0d0ed5d";
fsType = "ext4";
options = [ "nofail" ];
};
system.stateVersion = "26.05";
}
+42
View File
@@ -0,0 +1,42 @@
{ ... }:
# Declarative OS-disk layout (disko). UEFI: GPT with an ESP + ext4 root.
# disko both PARTITIONS/FORMATS this disk and generates the NixOS
# `fileSystems.*` entries, so hardware-configuration.nix must NOT define
# fileSystems for "/" or "/boot".
#
# ⚠️ This disk is WIPED on install. This is the Kingston SA400 SSD that
# currently holds CachyOS (btrfs root+subvols on sdb2, ESP on sdb1).
# The dev-data disks (sdc ext4 /mnt/hdd_01, LVM vg_ssd /mnt/ssd_01) and the
# leftover ntfs disks (sda, sdf, nvme0n1) are NOT listed here — they are
# mounted as plain fileSystems in configuration.nix (or, for the ntfs
# disks, ignored entirely) so they are never touched.
{
disko.devices.disk.os = {
type = "disk";
device = "/dev/disk/by-id/ata-KINGSTON_SA400S37480G_50026B738072F6C6";
content = {
type = "gpt";
partitions = {
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
}
+22
View File
@@ -0,0 +1,22 @@
# PLACEHOLDER — replaced on install.
#
# disko (disk-config.nix) owns "/" and "/boot", so this file only carries
# kernel modules + platform. nixos-anywhere regenerates it via:
# nixos-generate-config --no-filesystems
# Standard AMD desktop (SATA SSD, NVMe present but unused) — the generator
# should get ahci/nvme right on its own; these are a sane fallback so stage-1
# still mounts root if it doesn't.
{ config, lib, pkgs, modulesPath, ... }:
{
boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "usb_storage" "usbhid" "sd_mod" "nvme" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
# NO fileSystems here — disko defines "/" and "/boot".
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
+101
View File
@@ -0,0 +1,101 @@
{ pkgs, ... }:
# home-manager profile for darman on terra. System-level Hyprland enable
# (session entry, portals) lives in ../../services/desktop/desktop-hyprland.nix; this
# manages the user's own hyprland.conf + session packages.
{
home.stateVersion = "26.05";
wayland.windowManager.hyprland = {
enable = true;
# Starter config — replace with your real dotfiles.
settings = {
monitor = [ ",preferred,auto,1" ];
"$mod" = "SUPER";
bind = [
"$mod, Return, exec, alacritty"
"$mod, Q, killactive"
"$mod, D, exec, wofi --show drun"
];
};
};
programs.git.enable = true;
programs.home-manager.enable = true;
# ---- Dracula theming (GTK + Qt) ----
gtk = {
enable = true;
theme = {
name = "Dracula";
package = pkgs.dracula-theme;
};
};
# dracula-qt5-theme ships only a qt5ct color scheme (no style plugin), so
# Qt has to go through qt(5|6)ct rather than a direct style/platformTheme
# name. "qtct" pulls in both qt5ct and qt6ct; qt6ct reads its own config
# but understands the same scheme file format, so both point at it.
qt = {
enable = true;
platformTheme.name = "qtct";
};
xdg.configFile."qt5ct/qt5ct.conf".text = ''
[Appearance]
color_scheme_path=${pkgs.dracula-qt5-theme}/share/qt5ct/colors/Dracula.conf
custom_palette=true
style=Fusion
'';
xdg.configFile."qt6ct/qt6ct.conf".text = ''
[Appearance]
color_scheme_path=${pkgs.dracula-qt5-theme}/share/qt5ct/colors/Dracula.conf
custom_palette=true
style=Fusion
'';
# Custom mime-info defs (sln/slnx). xdg.mime's update-mime-database only
# indexes share/mime/packages inside the hm profile itself, so this has to
# be a package in home.packages, not a plain xdg.dataFile.
xdg.mime.enable = true;
home.packages = [
(pkgs.writeTextDir "share/mime/packages/application-x-ms-sln.xml"
(builtins.readFile ../../dotfiles/mime/application-x-ms-sln.xml))
pkgs.claude-code
];
programs.alacritty = {
enable = true;
settings = {
env.SHELL = "/bin/zsh";
terminal.shell = {
program = "/bin/zsh";
args = [ "-l" ];
};
window = {
padding = { x = 10; y = 10; };
opacity = 0.8;
};
font.normal = {
family = "DepartureMono Nerd Font";
style = "Regular";
};
colors.primary = {
background = "#222831";
foreground = "#ffd369";
};
hints.enabled = [
{
hyperlinks = true;
regex = "(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file:|git://|ssh:|ftp://)[^\\u0000-\\u001F\\u007F-\\u009F<>\"\\s{-}\\^`]+";
command = "xdg-open";
mouse.enabled = true;
}
];
keyboard.bindings = [
# ESC + CR: nix strings have no \u escape, so fromJSON (which
# supports \u001B) is used to get the literal control chars here.
{ key = "Return"; mods = "Shift"; chars = builtins.fromJSON ''"\u001B\r"''; }
];
};
};
}
+15
View File
@@ -0,0 +1,15 @@
{ config, ... }:
# sops-nix wiring for terra (desktop). Encrypted values in ../../secrets/terra.yaml,
# decrypted with terra's own SSH host key (recipient in ../../.sops.yaml).
# The host key is pre-generated on the laptop and shipped at install
# (nixos-anywhere --extra-files -> /etc/ssh/ssh_host_ed25519_key).
{
sops.defaultSopsFile = ../../secrets/terra.yaml;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
sops.secrets.tailscale_authkey = { };
sops.secrets.darman_password.neededForUsers = true;
users.users.darman.hashedPasswordFile = config.sops.secrets.darman_password.path;
}
+26
View File
@@ -0,0 +1,26 @@
tailscale_authkey: ENC[AES256_GCM,data:5ZaIjgl4d380JGUC+GjhGUeoAkkrS8ky,iv:oRh/7v/od+Mxj+i3z2ouZ0H2NrUYujEiFR+fEODlaNU=,tag:xUbVxCoYqa1eGx2zvVRAnw==,type:str]
darman_password: ENC[AES256_GCM,data:aM6jV2KkNlfZBEDvIZ1VpJl+6VSS/zm0AxAohV+WBhcMYdCmIGrmJYArhxNjN5Kk9TyRrLZVNnpzeM5RB6g394NAhznWHumctMN8fiq3x5/YOXVEn9DGxvc+4wj3E3PWWu44nRmdXHo6Wg==,iv:SBh11ZB34TrzZNxKIw5TKLUFvoC65vMVDTuvfjjKd/g=,tag:dYwn+fdbMAQBOHsP9wasjA==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOWkt2MXlGUVZGUlYyRS81
TDE2aFg5WGF3ZVcyMGEzSlR3aThwWU1QelNzClFYSll1K0kwMEYvQ3o5U2RKdnpu
UVZibUV2Z1FCcCtPRzROaW1RSk85M3cKLS0tIDdnRGpzS2dwYUc4ZlV5M1JlQURy
cWVDb1F2TTBSL2psTFdGaGRzb3JCaEEKBYdo2x/fjnU14hWI6ic3t2zmA8JioxVI
TEH3G9kPfWgcMdh1ORwykNnfcoSZcYM0EjomFDv5ZVjuZvRLDRDblA==
-----END AGE ENCRYPTED FILE-----
recipient: age1cekcqyf7073fsytcjxaa9dr9zwkmn4vjg36rv2tgxdglzfv4jvxqvcj6z2
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRZUFTNVJRdTBJbzZIZHQ4
VWRRSVJTbHFxNVVGTEZGT1ZqZnduQm1OSXg0CjZTU2p2YU9sUWZQTmJwNUFSVUFG
ZmJuOUVoMHZNQjlCeGFOcU9GaE5UUG8KLS0tIGtIL0t2cjM4ekl0clN2bERrRFlH
QW8rZHpwRTJYbWxUbnNTNUplL2dRRmcKujP0nL2Tu8/kDALDQsvEpaMLFWeMAtsk
sHjKfw8VrrmAR4pQf1dsY+wcyh4FsZxhP3Q+QIVq3eCIXS9PeJkGAg==
-----END AGE ENCRYPTED FILE-----
recipient: age1rfcmu6zh40v4260l9hnf8ajs9vly0s06rx3ey76eu78dp9t7getqyhmkut
lastmodified: "2026-07-14T19:48:05Z"
mac: ENC[AES256_GCM,data:ne2YPbCG2U6ix/C+bua28qAEKEMJ4NkEmrg0qFw3iMs8l4gSKL0wVrEu7H6EimCUohdiZ24g4s8euRU60bPNrM1cKkFnDPfkIY4Lc33dI/JrKahjHrEafhpQNy85i2l7oxM1WnHOGfgkQ/XoicwInDwcM5Z8Ks4Sa1JnhYVwCFc=,iv:VnbreolrFp97Caim+sxB5MGW01ir3vyOTL3fJ8DDWwY=,tag:+4TusjszEXQFCUjP3F/IgQ==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.1
+38
View File
@@ -0,0 +1,38 @@
{ pkgs, lib, ... }:
# Desktop applications for a workstation host (currently: terra). Split from
# desktop-hyprland.nix, which owns the *session* (compositor, greeter, audio,
# portals); this owns the things the user actually launches inside it.
{
# gitkraken / jetbrains-toolbox / steam are unfree. Allow exactly those
# rather than flipping allowUnfree globally, so an unfree dep sneaking into
# a server host's closure still fails loudly.
nixpkgs.config.allowUnfreePredicate = pkg:
builtins.elem (lib.getName pkg) [
"gitkraken"
"jetbrains-toolbox"
"steam"
"steam-unwrapped"
"steam-run"
"claude-code"
];
# Steam needs its own module (not just the package): it sets up the FHS
# wrapper, the udev rules for controllers, and the 32-bit graphics stack.
programs.steam = {
enable = true;
remotePlay.openFirewall = true; # in-home streaming
dedicatedServer.openFirewall = false;
};
hardware.graphics.enable32Bit = true; # required by steam's 32-bit games
environment.systemPackages = with pkgs; [
zed-editor
protonplus # manages Proton-GE / Wine-GE builds for steam + lutris
gitkraken
jetbrains-toolbox
kdePackages.dolphin
];
fonts.packages = [ pkgs.nerd-fonts.departure-mono ]; # alacritty's font, see hosts/terra/home.nix
}
+36
View File
@@ -0,0 +1,36 @@
{ pkgs, ... }:
# Hyprland (wayland) desktop: compositor, login manager, audio, portals.
# Reusable for any host that wants a local GUI session (currently: terra).
{
programs.hyprland.enable = true;
services.greetd = {
enable = true;
settings.default_session.command =
"${pkgs.tuigreet}/bin/tuigreet --time --cmd Hyprland";
};
# Audio (pipewire replaces pulseaudio/jack).
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
pulse.enable = true;
};
# Screen-share / file-picker portals for wayland apps.
xdg.portal = {
enable = true;
extraPortals = [ pkgs.xdg-desktop-portal-hyprland ];
};
hardware.graphics.enable = true; # OpenGL/Vulkan for the compositor + apps
services.libinput.enable = true;
environment.systemPackages = with pkgs; [
alacritty
];
networking.networkmanager.enable = true;
}