chore: initial commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# nix build symlinks
|
||||
result
|
||||
result-*
|
||||
|
||||
# direnv
|
||||
.direnv/
|
||||
|
||||
# local, machine-specific Claude Code settings (permissions etc.)
|
||||
.claude/settings.local.json
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -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 ];
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user