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