chore: initial commit

This commit is contained in:
2026-07-05 15:40:35 +02:00
commit aee4d0aa15
12 changed files with 940 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# nix build symlinks
result
result-*
# direnv
.direnv/
# local, machine-specific Claude Code settings (permissions etc.)
.claude/settings.local.json
+47
View File
@@ -0,0 +1,47 @@
# Flake-based NixOS + home-manager config (public half)
Usage, quick-start, and how to consume the modules are in `README.md` — read it first.
This is the **public** half of a two-repo split: **reusable modules** + an `example`
template host. The matching **private** repo holds the real inventory
(`nixosConfigurations.<host>`, `hosts/<host>/`, SSH/Syncthing topology) and imports this
one as a flake input. Deploy happens from the private repo, not here.
**Editing invariant — pick the layer before adding config:**
- generic/reusable → a module here (`modules/*.nix`, `nixosModules.*` / `homeModules.*`);
make machine/user specifics **options with defaults**, never hard-code personal values.
- machine list / hostnames / topology (SSH hosts, Syncthing peers, IPs) → the private
inventory repo, never here. The `example` host carries only placeholders.
- secrets (keys, tokens, passwords) → **neither** repo; runtime files or a secrets manager
decrypting to a path. See the SSH section.
## Maxim: keep $HOME clean (XDG)
Standing principle for every addition: minimise dotfiles/dirs in `$HOME`. Route a tool's
config → `~/.config`, state/history → `~/.local/state`, cache → `~/.cache`, data →
`~/.local/share`, normally by setting env vars in `home.sessionVariables`
(use `config.xdg.{config,state,cache,data}Home`). Prefer this to letting a tool litter `~`.
Cannot be relocated, leave alone: `~/.zshenv` (bootstraps `ZDOTDIR`), `~/.nix-profile`
and `~/.nix-defexpr` (nix), `~/.icons` (cursor theme — spec mandates `~/.icons`),
`~/.pki/nssdb` (NSS cert/key DB — path hardcoded in NSS, ignores XDG; created by any
Chromium/NSS/CEF app, e.g. grayjay's embedded webview).
## Desktop environment
Wayland. Compositor **Hyprland**, bar **Waybar**, terminal **foot** — all configured
declaratively in `modules/home.nix` (`wayland.windowManager.hyprland`, `programs.waybar`,
`programs.foot`). Font is JetBrainsMono Nerd Font; Waybar/foot use Nerd Font glyphs
(nf-md set) for icons, so new glyphs should come from that set for visual consistency.
Hyprland uses the **master** layout and a **German** keyboard (`kb_layout = "de"`).
Look deliberately minimal: animations, blur, rounding, and shadows are all turned
**off** on purpose — don't reintroduce them when adding desktop tweaks.
## Rust dev environments
Before setting up Rust for a project, read the `devShells` comment in `flake.nix`.
Short version: default is the shared `rust` devShell (`use flake ~/.nixos-config#rust`
via direnv); give a project its own `flake.nix` only when it needs its own pinned Rust
version or native C deps. The comment explains the tradeoff.
+73
View File
@@ -0,0 +1,73 @@
# NixOS + home-manager flake
A flake-based NixOS and home-manager configuration: a set of **reusable modules** plus a
**ready-to-run example host** that assembles them. Use it either way:
- as a **starting template** — clone, swap in your hardware, rebuild; or
- as **modules** — import them into your own (private) flake.
## Quick start (template)
```sh
nix flake init -t github:<owner>/<repo> # or: git clone …
# generate hardware config for THIS machine (the one file you must replace):
sudo nixos-generate-config --show-hardware-config > hosts/example/hardware-configuration.nix
# set your hostName + username in hosts/example/default.nix, then:
sudo nixos-rebuild switch --flake .#example
```
`nixosConfigurations.example` is a real, buildable machine assembled from the modules in
`modules/`. It doubles as living documentation: it shows exactly how the modules wire
together, so there's nothing to reverse-engineer. The only genuinely machine-specific file
is `hardware-configuration.nix` (NixOS generates it); everything else works from defaults
you can tweak.
## Import the modules into your own flake
Add this repo as a flake input and import its modules from your own configuration:
```nix
{
inputs.config.url = "github:<owner>/<repo>";
# …
outputs = { nixpkgs, config, home-manager, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
config.nixosModules.default
./hosts/myhost # your hardware + hostname
home-manager.nixosModules.home-manager
{ home-manager.users.me.imports = [ config.homeModules.default ]; }
];
};
};
}
```
Your machine list, hostnames, and any private specifics stay in *your* flake — you pull
only the generic modules from here.
## Architecture: three layers
Kept apart on purpose. When adding config, decide which layer it belongs to:
1. **Modules — generic, public (this repo).** Reusable building blocks under `modules/`,
exposed as `nixosModules.*` / `homeModules.*`. Anything machine- or user-specific is a
module **option with a sane default** — never hard-code personal values into a module.
2. **Inventory — topology, private (your flake).** The actual machines:
`nixosConfigurations.<host>`, per-host `hosts/<host>/` (hardware, hostname), and
topology-revealing config (SSH hosts, Syncthing peers, internal IPs). This is what you
deploy from; it imports the modules above. Publish the modules, keep the inventory in a
private repo. The `example` host here is a placeholder-filled stand-in for it.
3. **Secrets — neither repo.** Private keys, tokens, passwords never enter *any* repo.
Provide them as runtime files (`0600`, out-of-band) or via a secrets manager that
decrypts to a runtime path; nix references only the path.
## Layout
```
flake.nix # inputs + outputs (nixosModules, homeModules, example host, devShells)
modules/ # the reusable library
hosts/example/ # the template host (placeholder hardware-configuration.nix)
```
Shared `*.nix` / `*.lock` live at the top level; per-host files under `hosts/<host>/`.
Generated
+70
View File
@@ -0,0 +1,70 @@
{
"nodes": {
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1782704057,
"narHash": "sha256-G1I1gd32F7mp9LAe1DaZ4ZL7NX5gyiKwdCMwro1Vrck=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "868d0a692de703c2de98fab61968e4e310b7c28e",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-26.05",
"repo": "home-manager",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1782691344,
"narHash": "sha256-i5nw9BYYsMDAaOC4J+JmTof6b2GhlyH076awYRNrTV8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1f01958ffb5b3545c96d9ef2f4e24c5e5e1eb846",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-26.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"home-manager": "home-manager",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1782875958,
"narHash": "sha256-5eqDcnBjb1424HRQdnhuhNOBZguq1Z2tqSa2OMF/m2c=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "13139aefa973f3d96c60c0fbab801de058ae25ca",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+98
View File
@@ -0,0 +1,98 @@
{
description = "Reusable NixOS + home-manager modules, with an example host";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
home-manager = {
url = "github:nix-community/home-manager/release-26.05";
inputs.nixpkgs.follows = "nixpkgs"; # Use our nixpkgs, not any pinned by home-manager.
};
# rust-overlay: precise, NixOS-patched Rust toolchains (any stable/nightly,
# any component/target) exposed as pkgs.rust-bin. Used by the `rust` devShell
# below. `follows` so it shares our nixpkgs. Bump with
# `nix flake update rust-overlay`.
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs@{ self, nixpkgs, home-manager, rust-overlay, ... }:
let
system = "x86_64-linux";
in {
# ---- Reusable modules ----------------------------------------------
# Consume these from your own (private) inventory flake:
# imports = [ config.nixosModules.default ];
# home-manager.users.<you>.imports = [ config.homeModules.default ];
nixosModules.default = import ./modules/nixos.nix;
homeModules.default = import ./modules/home.nix;
# ---- The example / template host -----------------------------------
# Real and buildable; dogfoods the modules above. Clone, replace
# hosts/example/hardware-configuration.nix, set hostName + user, rebuild.
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
self.nixosModules.default
./hosts/example
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.user.imports = [
self.homeModules.default
./hosts/example/home.nix
];
}
];
};
# ---- Template: `nix flake init -t github:<owner>/<repo>` -----------
templates.default = {
path = ./.;
description = "NixOS + home-manager flake: module library + example host";
};
# ---- Rust dev shells -------------------------------------------------
# DEFAULT for Rust work: one shared toolchain, used from any project
# WITHOUT giving that project its own flake. In the project dir:
#
# echo 'use flake <this-flake>#rust' > .envrc && direnv allow
#
# (or one-off, no direnv: `nix develop <this-flake>#rust`). Then
# cargo/rustc/clippy/rustfmt/rust-analyzer appear on entry and vanish on
# exit. direnv itself is enabled in modules/home.nix (programs.direnv). The
# Rust version pin rides on THIS flake's flake.lock; bump it with
# `nix flake update rust-overlay`.
#
# LIMITATION (the catch): a flake can only read files inside its own tree,
# so this shell CANNOT read a project's local rust-toolchain.toml — the
# `./` resolves to this flake, not the project dir. Every consumer
# therefore gets this ONE toolchain. Want a few fixed alternates? Add more
# named shells here (e.g. rust-nightly) and `use flake ...#name`.
#
# Give a project its OWN flake instead when it needs its own Rust version
# driven by ITS OWN rust-toolchain.toml, OR native (C) deps (openssl,
# pkg-config, protobuf, libclang...). That repo gets its own flake.nix +
# committed flake.lock; there `rust-bin.fromRustupToolchainFile
# ./rust-toolchain.toml` works (now `./` is that repo) and `buildInputs`
# holds the C libs — per-repo and reproducible for anyone who clones it.
devShells.${system} =
let
pkgs = import nixpkgs {
inherit system;
overlays = [ rust-overlay.overlays.default ];
};
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" ];
};
in {
rust = pkgs.mkShell {
packages = [ rustToolchain ];
};
};
};
}
+24
View File
@@ -0,0 +1,24 @@
# Example host — the template's "bin". Real, buildable, assembled from the
# public modules; filled with placeholders where your inventory would go.
# To adopt: replace hardware-configuration.nix (see its header), set hostName +
# the user account below, then `nixos-rebuild switch --flake .#example`.
{ pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
networking.hostName = "example";
# First NixOS version installed on this machine. Do not bump casually — see
# `man configuration.nix` (system.stateVersion).
system.stateVersion = "26.05";
# The login account. Rename to taste; the home-manager side is wired in
# flake.nix (home-manager.users.user) and hosts/example/home.nix.
users.users.user = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "video" "input" ];
shell = pkgs.zsh;
};
}
+37
View File
@@ -0,0 +1,37 @@
# ┌───────────────────────────────────────────────────────────────────────┐
# │ PLACEHOLDER — REPLACE THIS FILE. │
# │ │
# │ Generate the real one for YOUR machine: │
# │ sudo nixos-generate-config --show-hardware-config \ │
# │ > hosts/example/hardware-configuration.nix │
# │ │
# │ The values below are dummies. They let the flake EVALUATE and BUILD │
# │ (UUIDs are just strings until activation), so `nix flake check` stays │
# │ green — but this config will NOT boot on real hardware until replaced. │
# └───────────────────────────────────────────────────────────────────────┘
{ lib, modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/0000-0000";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
+17
View File
@@ -0,0 +1,17 @@
# Example home identity — the per-user placeholders that a real inventory fills
# in. Imported alongside the generic homeModules.default (see flake.nix).
{ ... }:
{
home.username = "user";
home.homeDirectory = "/home/user";
home.stateVersion = "26.05";
# Git identity. Set to your own; leaks into commit authorship anyway, so it
# belongs with the person, not the shared module.
programs.git.settings.user = {
name = "Example User";
email = "user@example.com";
};
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
# Interactive Python REPL startup. Wired via $PYTHONSTARTUP (see home.nix).
# Persists REPL history to $PYTHON_HISTFILE (XDG cache), creating the dir.
from pathlib import Path
import os
import atexit
import readline
history = os.getenv('PYTHON_HISTFILE')
if history:
try:
Path(history).parent.mkdir(parents=True, exist_ok=True)
readline.read_history_file(history)
except OSError:
pass
def write_history():
try:
readline.write_history_file(history)
except OSError:
pass
atexit.register(write_history)
+32
View File
@@ -0,0 +1,32 @@
# Generic GPG tooling: agent + pinentry + XDG-relocated homedir. No keys and no
# signing identity — those are personal (→ inventory). Enabling this alone is
# harmless: a user with no key just has an idle agent.
{ config, pkgs, ... }:
{
programs.gpg = {
enable = true;
# Keep GnuPG out of ~/.gnupg (XDG maxim). Secret keys are imported into this
# homedir out-of-band — mutable, NEVER in the nix store.
homedir = "${config.xdg.dataHome}/gnupg";
settings = {
no-comments = true;
keyid-format = "0xlong";
with-fingerprint = true;
};
};
services.gpg-agent = {
enable = true;
# GUI pinentry → pops on the Wayland session even when signing is triggered
# non-interactively (e.g. an agent-run `git commit`). Qt is already pulled in
# by keepassxc, so this is cheap.
pinentry.package = pkgs.pinentry-qt;
# sk-based ssh keys use the normal ssh-agent; don't let gpg-agent hijack
# SSH_AUTH_SOCK.
enableSshSupport = false;
defaultCacheTtl = 1800; # 30 min
maxCacheTtl = 7200; # 2 h
};
}
+407
View File
@@ -0,0 +1,407 @@
# Generic user-level (home-manager) config. Reusable across users — nothing
# user-specific here. Identity (home.username/homeDirectory/stateVersion, git
# user.name/email), host topology (SSH hosts, Syncthing peers) and personal app
# choices (grayjay, Claude Code, …) live in the inventory, not in this module.
{ config, pkgs, ... }:
let
terminal = "foot";
browser = "librewolf";
editor = "nvim";
in
{
imports = [ ./gpg.nix ];
home.sessionVariables = {
TERMINAL = terminal;
BROWSER = browser;
EDITOR = editor;
# Keep $HOME clean: route node/npm out of ~ into XDG dirs.
NODE_REPL_HISTORY = "${config.xdg.stateHome}/node_repl_history";
NPM_CONFIG_CACHE = "${config.xdg.cacheHome}/npm";
NPM_CONFIG_USERCONFIG = "${config.xdg.configHome}/npm/npmrc";
# Python REPL: UTF-8 I/O, custom startup file, XDG-routed history.
PYTHONIOENCODING = "UTF-8";
PYTHONSTARTUP = "${config.xdg.configHome}/python/startup";
PYTHON_HISTFILE = "${config.xdg.cacheHome}/python/history";
# psql / wget / readline configs, XDG-routed (the rc files are declared
# via xdg.configFile below).
PSQLRC = "${config.xdg.configHome}/psql/rc";
WGETRC = "${config.xdg.configHome}/wget/wgetrc";
INPUTRC = "${config.xdg.configHome}/readline/inputrc";
# Rust/cargo: keep registry cache, git checkouts and `cargo install` bins
# out of ~/.cargo (the toolchain itself comes from the flake's rust devShell).
CARGO_HOME = "${config.xdg.dataHome}/cargo";
# Mute the harmless "Wayland does not support QWindow::requestActivate()"
# spam Qt apps (keepassxc) print; scoped to that one log category only.
QT_LOGGING_RULES = "qt.qpa.wayland=false";
};
# Sets XDG_{CONFIG,DATA,CACHE}_HOME and enables xdg.configFile management.
xdg.enable = true;
home.packages = with pkgs; [
librewolf
keepassxc # config in ~/.config/keepassxc
# claude-code is installed by programs.claude-code below (its wrapped
# finalPackage) — do NOT also list it here, or buildEnv conflicts on the
# wrapper binary.
nodejs
highlight # `ccat` alias: syntax-highlighted cat
];
# ---- Per-tool config files, routed into XDG dirs ---------------------
xdg.configFile = {
"python/startup".source = ./assets/python-startup.py;
# psql: unlimited, per-database history file (kept out of $HOME).
"psql/rc".text = ''
\set HISTFILE ${config.xdg.cacheHome}/psql/history-:DBNAME
\set HISTSIZE -1
'';
# wget: keep its HSTS db in the cache dir, not ~/.
"wget/wgetrc".text = ''
hsts-file=${config.xdg.cacheHome}/wget/hsts
'';
# readline: affects any libreadline program (python, psql, bash...).
# zsh ignores it — it uses ZLE (see programs.zsh below). Binds Up/Down to
# prefix history search with the cursor jumped to end of line.
"readline/inputrc".text = ''
$include /etc/inputrc
"\C-x\C-p": history-search-backward
"\C-x\C-n": history-search-forward
"\e[A": "\C-x\C-p\C-e"
"\e[B": "\C-x\C-n\C-e"
"\eOA": "\C-x\C-p\C-e"
"\eOB": "\C-x\C-n\C-e"
'';
};
programs.home-manager.enable = true;
programs.git = {
enable = true;
settings = {
pull.rebase = true;
init.defaultBranch = "master";
# Detect whitespace errors; speed up untracked-file scans.
apply.whitespace = "fix";
core.whitespace = "space-before-tab,-indent-with-non-tab,trailing-space";
core.untrackedCache = true;
branch.sort = "-committerdate";
color.ui = "auto";
diff.renames = "copies";
help.autocorrect = 1;
merge.log = true;
push = { default = "simple"; followTags = true; };
submodule.recurse = true;
# github: / gh: / gist: remote shorthands.
url."git@github.com:".insteadOf = "gh:";
url."git://github.com/".insteadOf = "github:";
alias = {
l = "log --pretty=oneline -n 20 --graph --abbrev-commit";
s = "status -s";
d = "!\"git diff-index --quiet HEAD -- || clear; git --no-pager diff --patch-with-stat\"";
p = "pull --recurse-submodules";
c = "clone --recursive";
ca = "!git add -A && git commit -av";
go = "!f() { git checkout -b \"$1\" 2> /dev/null || git checkout \"$1\"; }; f";
amend = "commit --amend --reuse-message=HEAD";
reb = "!r() { git rebase -i HEAD~$1; }; r";
aliases = "config --get-regexp alias";
contributors = "shortlog --summary --numbered";
whoami = "config user.email";
# Delete local branches already merged into the current branch.
dm = "!git branch --merged | grep -v '\\*' | xargs -n 1 git branch -d";
};
};
};
programs.foot = {
enable = true;
settings = {
main = {
font = "JetBrainsMono Nerd Font:size=11";
pad = "8x8";
};
colors-dark = {
alpha = 0.9;
};
};
};
# App launcher ($mod+d in Hyprland). Wayland-native, launches apps detached
# so their stderr goes to the journal, not whatever terminal you're in.
programs.fuzzel = {
enable = true;
settings.main.font = "JetBrainsMono Nerd Font:size=11";
};
programs.fzf.enable = true; # provides the ^f "cd into fzf dir" binding
# direnv + nix-direnv: a project's `.envrc` with `use flake` auto-loads any
# devShell on cd-in (and caches the eval) — general per-directory environments,
# not tied to one language. First consumer is the shared Rust dev shell (see
# the `devShells` comment in flake.nix). Zsh integration is on by default.
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
programs.zsh = {
enable = true;
autosuggestion.enable = true;
syntaxHighlighting.enable = true;
enableCompletion = true;
# Keep the completion dump in the cache dir, not ~/.zcompdump.
completionInit = ''
_zcd="${config.xdg.cacheHome}/zsh"; [ -d "$_zcd" ] || mkdir -p "$_zcd"
autoload -U compinit && compinit -d "$_zcd/zcompdump"
'';
defaultKeymap = "viins"; # vi keybindings
history = {
path = "${config.xdg.cacheHome}/zsh/history";
size = 1000000;
save = 1000000;
ignoreAllDups = true; # drop older duplicate of a re-run command
ignoreSpace = true; # don't record space-prefixed commands
share = true;
};
shellAliases = {
vim = "nvim";
vimdiff = "nvim -d";
cp = "cp -iv";
mv = "mv -iv";
rm = "rm -vI";
mkd = "mkdir -pv";
ls = "ls -hN --color=auto --group-directories-first";
grep = "grep --color=auto";
diff = "diff --color=auto";
ccat = "highlight --out-format=ansi";
ka = "killall";
g = "git";
e = editor;
v = editor;
};
# Interactive bits with no first-class HM option: prompt, autocd,
# completion-menu styling, vi cursor shaping, and a few keybinds.
initContent = ''
# Colored prompt: [user@host ~]$
autoload -U colors && colors
PS1="%B%{$fg[red]%}[%{$fg[yellow]%}%n%{$fg[green]%}@%{$fg[blue]%}%M %{$fg[magenta]%}%~%{$fg[red]%}]%{$reset_color%}$%b "
setopt autocd interactive_comments
stty stop undef # free up ctrl-s
export KEYTIMEOUT=1
# Completion menu: arrow-select, include dotfiles, vi keys in the menu.
zstyle ':completion:*' menu select
zmodload zsh/complist
_comp_options+=(globdots)
bindkey -M menuselect 'h' vi-backward-char
bindkey -M menuselect 'k' vi-up-line-or-history
bindkey -M menuselect 'l' vi-forward-char
bindkey -M menuselect 'j' vi-down-line-or-history
bindkey -v '^?' backward-delete-char
bindkey '^[[P' delete-char
# Beam cursor in insert mode, block in normal mode.
function zle-keymap-select () {
case $KEYMAP in
vicmd) echo -ne '\e[1 q';;
viins|main) echo -ne '\e[5 q';;
esac
}
zle -N zle-keymap-select
zle-line-init() { zle -K viins; echo -ne "\e[5 q"; }
zle -N zle-line-init
echo -ne '\e[5 q'
preexec() { echo -ne '\e[5 q'; }
# Edit current line in $EDITOR with ctrl-e; quick bc and fzf-cd binds.
autoload edit-command-line; zle -N edit-command-line
bindkey '^e' edit-command-line
bindkey -s '^a' 'bc -lq\n'
bindkey -s '^f' 'cd "$(dirname "$(fzf)")"\n'
# Up/Down: search history by the text already typed before the cursor,
# then place the cursor at the end of the matched line.
autoload -U history-search-end
zle -N history-beginning-search-backward-end history-search-end
zle -N history-beginning-search-forward-end history-search-end
bindkey "^[[A" history-beginning-search-backward-end
bindkey "^[[B" history-beginning-search-forward-end
'';
};
home.pointerCursor = {
name = "Adwaita";
package = pkgs.adwaita-icon-theme;
size = 24;
gtk.enable = true;
};
gtk = {
enable = true;
theme = {
name = "Adwaita-dark";
package = pkgs.gnome-themes-extra;
};
# Keep the GTK2 rc out of $HOME (default ~/.gtkrc-2.0). Setting this also
# makes home-manager export GTK2_RC_FILES so GTK2 apps still find it.
gtk2.configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc";
};
wayland.windowManager.hyprland = {
enable = true;
configType = "hyprlang";
settings = {
monitor = ",preferred,auto,1";
general = {
layout = "master";
gaps_in = 5;
gaps_out = 10;
border_size = 2;
};
decoration = {
rounding = 0;
shadow.enabled = false;
blur.enabled = false;
};
animations = {
enabled = false;
};
input = {
kb_layout = "de";
follow_mouse = 1;
};
"$mod" = "SUPER";
bind = [
"$mod, Return, exec, footclient"
"$mod, q, killactive"
"$mod SHIFT, q, exit"
"$mod, f, fullscreen, 1"
"$mod SHIFT, f, fullscreen, 0"
"$mod, w, exec, ${browser}"
"$mod, d, exec, fuzzel"
"$mod, j, layoutmsg, cyclenext"
"$mod SHIFT, j, layoutmsg, swapnext"
"$mod, k, layoutmsg, cycleprev"
"$mod SHIFT, k, layoutmsg, swapprev"
"$mod, h, layoutmsg, splitratio -0.05"
"$mod, l, layoutmsg, splitratio +0.05"
] ++ (
# Dynamic definitions for viewing workspaces 1 to 9 and moving windows between them.
# References physical key codes for robustness with the SHIFT key, e.g. `code:10` instead of `1`.
builtins.concatLists (
builtins.genList (i:
let target = i + 1; in [
"$mod, code:1${toString i}, workspace, ${toString target}"
"$mod SHIFT, code:1${toString i}, movetoworkspace, ${toString target}"
]
) 9
)
) ++ [
", XF86MonBrightnessUp, exec, brightnessctl set 15%+"
", XF86MonBrightnessDown, exec, brightnessctl set 15%-"
", XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"
", XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 3%+"
", XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 3%-"
];
exec-once = [
"waybar"
# foot in server/daemon mode: $mod+Return runs `footclient`, which
# attaches to this one process → faster new-window startup, shared RAM.
# Uses foot's default socket, which footclient also defaults to.
# Tradeoff: if the server dies, all foot windows die with it.
"foot --server"
];
};
};
programs.waybar = {
enable = true;
settings = {
mainBar = {
layer = "top";
position = "top";
height = 28;
modules-left = [ "hyprland/workspaces" ];
modules-center = [ "clock" ];
modules-right = [ "pulseaudio" "network" "battery" ];
"hyprland/workspaces" = {
format = "{id}";
};
clock = {
format = "{:%H:%M %a %d %b}";
};
pulseaudio = {
format = "{volume}% {icon}";
format-muted = "󰝟";
format-icons = {
default = [ "󰕿" "󰖀" "󰕾" ];
headphone = "󰋋";
};
};
network = {
format-wifi = "{essid} 󰖩";
format-ethernet = "eth 󰈀";
format-disconnected = "󰖪";
};
battery = {
states = {
warning = 30;
critical = 15;
};
format = "{capacity}% {icon}";
format-charging = "{capacity}% 󰂄";
format-plugged = "{capacity}% 󰚥";
format-icons = [ "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹" ];
};
};
};
style = ''
* {
font-family: "JetBrainsMono Nerd Font";
font-size: 13px;
}
window#waybar {
background: #222222;
color: #bbbbbb;
}
#pulseaudio, #network, #battery {
padding: 0 12px;
}
#battery.warning {
color: #e5c07b;
}
#battery.critical {
color: #e06c75;
}
#battery.charging {
color: #98c379;
}
'';
};
}
+100
View File
@@ -0,0 +1,100 @@
# Generic system-level config. Reusable across hosts — nothing machine- or
# user-specific here. The user account, hostname and system.stateVersion live in
# the per-host inventory (hosts/<host>/), not in this module.
{ config, lib, pkgs, ... }:
{
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Deduplicate identical store paths via hardlinks.
nix.settings.auto-optimise-store = true;
# Weekly GC of generations older than 30 days (frees disk + prunes stale boot
# entries). Flat age cutoff — Nix has no snapper-style tiered retention.
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
# Bootloader systemd-boot much simpler than GRUB.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# Show only the newest 5 generations in the boot menu (display cap only; GC
# above does the actual deletion).
boot.loader.systemd-boot.configurationLimit = 5;
# Use latest kernel.
boot.kernelPackages = pkgs.linuxPackages_latest;
# Configure network connections interactively with nmcli or nmtui.
networking.networkmanager.enable = true;
networking.firewall = {
allowedTCPPorts = [
22000 # syncthing sync transport (TCP)
];
allowedUDPPorts = [
22000 # syncthing sync transport (QUIC)
21027 # syncthing LAN discovery
];
};
# Time zone. Template default — override per host/region.
time.timeZone = "Europe/Berlin";
# Internationalisation properties. Template defaults — override per region.
i18n.defaultLocale = "en_US.UTF-8";
console = {
font = "Lat2-Terminus16";
keyMap = "de";
};
# Programs.
programs.zsh.enable = true;
programs.hyprland.enable = true;
programs.dconf.enable = true;
# Packages installed in system profile.
environment.systemPackages = with pkgs; [
curl
git
neovim
wget
];
nixpkgs.config.allowUnfree = true;
# Install nice font.
fonts.packages = with pkgs; [
nerd-fonts.jetbrains-mono
];
# Enable sound.
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
security.rtkit.enable = true;
# Cache sudo credentials for 30 min per terminal (default 5). Tradeoff: an
# unlocked, unattended terminal can sudo without re-auth for that window.
security.sudo.extraConfig = "Defaults timestamp_timeout=30";
# Login manager.
services.greetd = {
enable = true;
settings = {
default_session = {
command = "${pkgs.tuigreet}/bin/tuigreet --time --remember --cmd start-hyprland";
user = "greeter";
};
};
};
# XDG portals (screen sharing, file pickers, and so on).
xdg.portal = {
enable = true;
extraPortals = [ pkgs.xdg-desktop-portal-hyprland ];
};
}