>~ are noweb placeholders that are expanded from other blocks during tangling.
** Nix & NixOS
* [[[https://nix.dev/][Nix](https://nix.dev/][Nix)]] is used to define packages, environments, and configuration as pure expressions.
* [[[https://nixos.org/][NixOS](https://nixos.org/][NixOS)]] evaluates Nix expressions into a complete system configuration that can be applied by rebuild operations.
** Repository layout and folder conventions
#+HTML:
#+HTML: This repository contains system modules, user modules, and configuration fragments. The following directories are treated as separate layers:
#+HTML: ./configuration (NixOS modules), ./home (Home Manager modules), and ./assets/conf (configuration fragments referenced or deployed by the modules).
#+HTML:
#+HTML:
#+HTML: To keep navigation consistent, the same internal substructure is used in all three locations. Each layer keeps its role; only the internal grouping is standardized.
#+HTML:
** Shared domain folders
#+HTML:
#+HTML: core/ – base settings and common infrastructure
#+HTML: desktop/ – graphical session, compositor, UI components, and integration
#+HTML: apps/ – application enablement and application-level configuration
#+HTML: services/ – background services and daemons
#+HTML: security/ – secrets handling and security-related configuration
#+HTML: dev/ – developer tooling and editor configuration
#+HTML:
** Full tree (including unchanged parts)
#+HTML:
#+HTML: The tree below shows the full repository layout, with the standardized internal structure applied only inside
#+HTML: configuration/, home/, and assets/conf/.
#+HTML:
#+HTML: .
#+HTML: ├── assets
#+HTML: │ ├── background.png
#+HTML: │ ├── conf
#+HTML: │ │ ├── apps
#+HTML: │ │ │ ├── flatpaks.conf
#+HTML: │ │ │ └── packages.conf
#+HTML: │ │ ├── core
#+HTML: │ │ │ └── lightdm.conf
#+HTML: │ │ ├── desktop
#+HTML: │ │ │ ├── hypr
#+HTML: │ │ │ │ ├── bindings.conf
#+HTML: │ │ │ │ └── hyprland.conf
#+HTML: │ │ │ ├── wallpaper
#+HTML: │ │ │ │ └── wallpaper.conf
#+HTML: │ │ │ └── waybar
#+HTML: │ │ │ ├── config.jsonc
#+HTML: │ │ │ └── style.css
#+HTML: │ │ ├── dev
#+HTML: │ │ │ ├── hyprshell
#+HTML: │ │ │ │ ├── config.ron.bak
#+HTML: │ │ │ │ └── styles.css.bak
#+HTML: │ │ │ └── terminal
#+HTML: │ │ │ ├── alacritty.toml
#+HTML: │ │ │ ├── aliases.conf
#+HTML: │ │ │ ├── enabled_shells.conf
#+HTML: │ │ │ ├── kitty.conf
#+HTML: │ │ │ ├── starship.toml
#+HTML: │ │ │ └── zsh.conf
#+HTML: │ │ ├── security
#+HTML: │ │ │ └── ssh
#+HTML: │ │ │ └── ssh-client.conf
#+HTML: │ │ └── services
#+HTML: │ ├── lock.png
#+HTML: │ ├── scripts
#+HTML: │ └── wallpapers
#+HTML: │ ├── gif
#+HTML: │ ├── pictures
#+HTML: │ │ ├── background.png
#+HTML: │ │ ├── background1.png
#+HTML: │ │ ├── background2.png
#+HTML: │ │ ├── background3.png
#+HTML: │ │ ├── background4.png
#+HTML: │ │ └── background5.png
#+HTML: │ └── videos
#+HTML: │ ├── dark_water_large.mp4
#+HTML: │ └── white_blobs_small.mp4
#+HTML: ├── configuration
#+HTML: │ ├── apps
#+HTML: │ │ ├── install_flatpaks.nix
#+HTML: │ │ └── install_packages.nix
#+HTML: │ ├── core
#+HTML: │ │ ├── boot.nix
#+HTML: │ │ ├── files.nix
#+HTML: │ │ ├── locale.nix
#+HTML: │ │ ├── networking.nix
#+HTML: │ │ └── nix-settings.nix
#+HTML: │ ├── default.nix
#+HTML: │ ├── desktop
#+HTML: │ │ ├── audio.nix
#+HTML: │ │ ├── hyprland.nix
#+HTML: │ │ └── login-tuigreeter.nix
#+HTML: │ ├── dev
#+HTML: │ │ └── terminal.nix
#+HTML: │ ├── security
#+HTML: │ └── services
#+HTML: │ └── services.nix
#+HTML: ├── flake.lock
#+HTML: ├── flake.nix
#+HTML: ├── hardware
#+HTML: │ └── hardware.nix
#+HTML: ├── home
#+HTML: │ ├── apps
#+HTML: │ │ ├── defaults-apps.nix
#+HTML: │ │ └── theme.nix
#+HTML: │ ├── default.nix
#+HTML: │ ├── desktop
#+HTML: │ │ ├── hyprexpo.nix
#+HTML: │ │ ├── hyprland.nix
#+HTML: │ │ ├── hyprlock.nix
#+HTML: │ │ ├── hypridle.nix
#+HTML: │ │ ├── hyprshell.nix
#+HTML: │ │ ├── walker.nix
#+HTML: │ │ ├── wallpaper.nix
#+HTML: │ │ └── waybar.nix
#+HTML: │ ├── dev
#+HTML: │ │ ├── alacritty.nix
#+HTML: │ │ ├── dev.nix
#+HTML: │ │ ├── emacs
#+HTML: │ │ │ ├── default.nix
#+HTML: │ │ │ ├── early-init.el
#+HTML: │ │ │ └── init.el
#+HTML: │ │ ├── kitty.nix
#+HTML: │ │ ├── shells.nix
#+HTML: │ │ └── starship.nix
#+HTML: │ └── zsh.nix
#+HTML: ├── LICENSE
#+HTML: ├── machines
#+HTML: │ └── traveldroid
#+HTML: │ ├── configuration.nix
#+HTML: │ ├── hardware-configuration.nix
#+HTML: │ └── home.nix
#+HTML: ├── README.org
#+HTML: ├── secrets
#+HTML: │ └── secrets.yaml
#+HTML: └── user.nix
#+HTML:
** Notes
#+HTML:
#+HTML: - Only the internal layout of
configuration/, home/, and assets/conf/ is standardized; all other paths remain as currently organized.
#+HTML: - The
services/ and security/ folders are included for completeness even if initially empty in some layers.
#+HTML:
~YourNixCode(Input) -> System Configuration~
I use [[https://nixos.wiki/wiki/Flakes][nix flakes]] which means that the entry point for the nix evaluation is a file called ~flake.nix~ which has two parts (among other things)
#+begin_src nix
{
inputs: # describes the function input, consisting mainly of package sources
outputs: # what the function outputs, a nixos configuration in our case
}
#+end_src
Nix flakes is still behind an ~experimental~ flag, but it is de facto the standard by most of the community.
Flakes allow us to pin the input package versions using a ~flake.lock~ file.
This prevents unwanted and surprise updates when rebuilding without changing the configuration.
* TLDR App List
| Window Manager | [[https://hyprland.org/][Hyprland]] |
| Bar | [[https://github.com/Alexays/Waybar][Waybar]] |
| Application Launcher | [[https://github.com/abenz1267/walker][Walker]] |
| Notification Daemon | [[https://github.com/dunst-project/dunst][Dunst]] |
| Terminal Emulator | [[https://alacritty.org/][Alacritty]] |
| Shell | [[https://ohmyz.sh/][Zsh]] and [[https://starship.rs/][Starship]] |
| Text Editor | [[https://www.gnu.org/software/emacs/][Emacs]] |
| File Manager | [[https://docs.xfce.org/xfce/thunar/start][Thunar]] |
| Fonts | [[https://protesilaos.com/codelog/2025-02-12-aporetic-fonts-version-1-1-0/][Aporeti]] |
| Colors | [[https://catppuccin.com/][Catppuccin]] |
| Icons | [[https://nix.catppuccin.com/][Catppuccin Nix ]] |
| Lock Screen | [[https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/][Hyprlock]] |
| Wallpapers | [[https://github.com/hyprwm/hyprpaper][Hyprpaper]] |
* Configuration Variables
I have a bunch of constant strings that I would rather put in a file. Thats what ~user.nix~ is.
The values are imported at the beginning and are available to almost all the functions being called to configure the system.
#+begin_src nix :tangle user.nix
{
system = "x86_64-linux";
username = "henrov";
stateVersion = "25.11";
locale = "nl_NL.UTF-8";
}
#+end_src
* Flake Inputs
The inputs for my system's configuration are very simple
1. [[https://search.nixos.org/packages][nixpkgs]] - the main nix repository of packages. Its huge and growing. Pinned to the unstable release channel.
Sometimes pinned to a specific commit because unstable broke something and the fix hasn't made it into the release yet.
2. [[https://nix-community.github.io/home-manager/][home-manager]] - a nix module that helps keep track of user specific dotfiles and configurations as part of my nix config.
3. [[https://github.com/nix-community/emacs-overlay][emacs-overlay]] - this has more configuration options and generally a newer emacs available provided by the community.
4. [[https://nix.catppuccin.com/][catppuccin]] - nix module that allows everything to be catppuccin themed.
#+begin_src nix :tangle flake.nix :noweb tangle
{
description = "Henrov's nixos configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
emacs-overlay = {
url = "github:nix-community/emacs-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
catppuccin = {
url = "github:catppuccin/nix";
inputs.nixpkgs.follows = "nixpkgs";
};
zen-browser = {
url = "github:youwen5/zen-browser-flake";
inputs.nixpkgs.follows = "nixpkgs";
};
};
<>
}
#+end_src
* Flake Output
Now that the inputs are ready, the outputs define what the system will actually look like. I also define the machines that this configuration specifies early on. Finally, I iterate over the ~machines~ list and pull files from ~/.machines/${name}~ subdirectory. This allows me to have configuration that has machine specific configuration limited to those files while also keeping a modular reusable base.
We also add a devshell that makes editing this repository easier in emacs.
#+name: flake-outputs
#+begin_src nix :noweb no-export
outputs = inputs@{
nixpkgs,
home-manager,
emacs-overlay,
catppuccin,
...
}:
let
user = import ./user.nix;
lib = nixpkgs.lib;
machines = [
"traveldroid"
];
pkgs = import nixpkgs {
inherit (user) system;
};
in
{
nixosConfigurations = builtins.listToAttrs (
builtins.map (machine: {
name = machine;
value = lib.nixosSystem {
modules = [
<>
<>
<>
catppuccin.nixosModules.catppuccin # theme
];
specialArgs = {
hostname = machine;
inherit user;
inherit inputs;
flakeRoot = inputs.self;
};
};
}) machines
);
devShells.${user.system}.default = pkgs.mkShell {
buildInputs = with pkgs; [
nil
nixfmt-rfc-style
];
};
};
#+end_src
Lets look at the individual modules
1. Emacs
The first is the emacs overlay so that it uses the nix-community emacs overlay from the inputs instead of the nixpkgs one.
Overlays are a special nix way to override existing packages within a repository.
#+name: flake-emacs-module
#+begin_src nix
({ ... }: {
nixpkgs.overlays = [ emacs-overlay.overlays.default ];
})
#+end_src
2. Then the machine specific configuration, in this case, just "traveldroid".
#+name: flake-config-module
#+begin_src nix
./machines/${machine}/configuration.nix
#+end_src
3. And finally the home-manager module.
This can be initialized and managed on its own but I'd rather use the ~nixos-rebuild~ command to build everything instead of managing userland dotfiles separately.
#+name: flake-home-module
#+begin_src nix :noweb no-export
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.extraSpecialArgs = {
inherit user inputs;
flakeRoot = inputs.self;
};
<>
<>
}
#+end_src
- Home-Manager will not overwrite existing configuration files and that is good in most cases, but when everything is declarative like it is here, I'd rather that home-manager create a ~.backup~ and replace the file.
#+name: flake-home-backup
#+begin_src nix
home-manager.backupFileExtension = "backup";
#+end_src
- Finally I pull in the machine specific home configuration. Along with the overrides from catppuccin.
#+name: flake-home-config
#+begin_src nix
home-manager.users.${user.username} = {
imports = [
./machines/${machine}/home.nix
catppuccin.homeModules.catppuccin
];
};
#+end_src
** Envrc + Direnv
Editing this file will be much nicer if we have the dev environment configured.
That is done in the devshells section. But to auto load this dev shell, we need a .envrc file.
This tells [[https://direnv.net/][direnv]] to load the devshell in the flake.
#Finally, we also look for a .envrc-private file and try to load that. That contains devshell specific secrets.
#+begin_src nix :tangle .envrc
use flake
watch_file .envrc.private
if [[ -f .envrc.private ]]; then
source_env .envrc.private
fi
#+end_src
* Machines
The individual machines subdirectory is configured as follows :-
#+name: tree
#+begin_src ditaa
+--machine
| +--configuration.nix
| +--home.nix
| +--hardware-configuration.nix
#+end_src
- configuration.nix has the system configuration.
- home.nix has the user level configuration.
- hardware-configuration.nix has the unique hardware configuration.
- Note about imports
~imports = []~ in a nix file will pull in the function/object from the list of files provided.
This imported object (or function result) is just trivially merged into a common object.
We can take a look at that the common hardware options I have for all my machines.
*** Other Utils
**** Updates
To update the computer, I just need to update the ~flake.lock~ file to have references to the latest repository. This is done with :-
#+begin_src bash
nix flake update
#+end_src
* Hardware
I'll let the code comments explain the file here.
#+begin_src nix :tangle hardware/hardware.nix :noweb tangle :mkdirp yes
{ pkgs, lib, user, config, ...} :
{
nixpkgs.hostPlatform = lib.mkDefault user.system; # x86_64-linux
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; # enable power saving on the cpu
# update cpu microcode with firmware that allows redistribution
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
hardware = {
# always enable bluetooth
bluetooth.enable = true;
# always enable graphics drivers and enable a bunch of layers for it (including vulkan validation)
graphics = {
enable = true;
extraPackages = with pkgs; [
vulkan-validation-layers # helps catch and debug vulkan crashes
];
};
};
hardware.enableAllFirmware = true; # enable all firmware regardless of license
}
#+end_src
* Configuration
This section describes the main system configuration for the computers that I have. Nix will look for a ~default.nix~ file if you give it a path to a folder to import. And ~default.nix~ looks as follows :-
#+begin_src nix :tangle configuration/default.nix :noweb tangle :mkdirp yes
{ pkgs, user, ... } :
{
imports = [
./apps/install_flatpaks.nix
./apps/install_packages.nix
./core/files.nix
./core/locale.nix
./core/networking.nix
./core/nix-settings.nix
#./core/login-tuigreeter.nix
./core/login-lightdm.nix
./desktop/audio.nix
./desktop/hyprland.nix
./dev/terminal.nix
./core/boot.nix
./services/services.nix
];
<>
<>
<>
<>
# enable the catppuccin theme for everything with mocha + blue accents
catppuccin.enable = true;
catppuccin.flavor = "mocha";
catppuccin.accent = "blue";
system.stateVersion = user.stateVersion;
}
#+end_src
** Apps section
This section describes a way of installing packages, either through nixpkgs orr flatpak. What apps to instal is decided in the files ./assets/conf/apps/packages.conf and flatpaks.conf
** install_packages.nix
#+begin_src nix :tangle configuration/apps/install_packages.nix :noweb tangle :mkdirp yes
{ config, lib, pkgs, flakeRoot, inputs, ... }:
let
packagesConfPath = flakeRoot + "/assets/conf/apps/packages.conf";
raw = builtins.readFile packagesConfPath;
# IMPORTANT: explicit "\n" so we never accidentally split into characters
rawLines = lib.splitString "\n" raw;
# Guard: if we accidentally split into characters, rawLines length ~= stringLength raw
_guard = assert !(
builtins.stringLength raw > 1 &&
builtins.length rawLines == builtins.stringLength raw
); true;
cleanLine = l:
let
noCR = lib.replaceStrings [ "\r" ] [ "" ] l;
noInlineComment = lib.head (lib.splitString "#" noCR);
in
lib.strings.trim noInlineComment;
entries =
builtins.filter (l: l != "")
(map cleanLine rawLines);
resolvePkg = name:
let
parts = lib.splitString "." name;
found = lib.attrByPath parts null pkgs;
in
if found == null then
throw ''
install_packages.nix: package not found in pkgs
Token : ${builtins.toJSON name}
packages.conf : ${toString packagesConfPath}
Hint : check the attribute name on search.nixos.org/packages
''
else
found;
packages = builtins.seq _guard (map resolvePkg entries);
zenBrowser =
inputs.zen-browser.packages.${pkgs.stdenv.hostPlatform.system}.default;
in
{
environment.systemPackages =
packages
++ [ zenBrowser ];
}
#+end_src
** install_flatpaks.nix
#+begin_src nix :tangle configuration/apps/install_flatpaks.nix :noweb tangle :mkdirp yes
{ config, pkgs, lib, flakeRoot, ... }:
let
moduleName = "install-flatpaks";
flatpakConfPath = flakeRoot + "/assets/conf/apps/flatpaks.conf";
raw = builtins.readFile flatpakConfPath;
# Explicit "\n" so we never accidentally split into characters
rawLines = lib.splitString "\n" raw;
# Guard: if we accidentally split into characters, rawLines length ~= stringLength raw
_guard = assert !(
builtins.stringLength raw > 1 &&
builtins.length rawLines == builtins.stringLength raw
); true;
cleanLine = l:
let
noCR = lib.replaceStrings [ "\r" ] [ "" ] l;
noInlineComment = lib.head (lib.splitString "#" noCR);
in
lib.strings.trim noInlineComment;
entries =
builtins.filter (l: l != "")
(map cleanLine rawLines);
# Flatpak app IDs are reverse-DNS style like org.example.App (at least 2 dots).
# We'll validate and fail early with a clear message.
dotCount = s: builtins.length (lib.splitString "." s) - 1;
isValidId = s:
(dotCount s) >= 2; # matches the error you're seeing: "at least 2 periods"
_validate =
builtins.seq _guard (
builtins.map (id:
if isValidId id then true else
throw ''
${moduleName}: invalid Flatpak ID in flatpaks.conf (needs reverse-DNS with at least 2 dots)
Token : ${builtins.toJSON id}
flatpaks.conf : ${toString flatpakConfPath}
Fix: remove stray tokens/headers, or comment them out with '#'.
''
) entries
);
# Use validated entries
flatpakApps = builtins.seq _validate entries;
syncFlatpaks = pkgs.writeShellScript "sync-flatpaks" ''
set -euo pipefail
# Use the deployed config path (matches environment.etc below)
CONF="/etc/flatpak/flatpaks.conf"
if [[ -f "$CONF" ]]; then
echo "flatpak-sync: using $CONF"
else
echo "flatpak-sync: WARNING: $CONF not found, using embedded list"
fi
if ! flatpak remotes --system --columns=name | grep -qx flathub; then
flatpak remote-add --system --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
fi
desired_apps=(
${lib.concatStringsSep "\n" (map (a: ''"${a}"'') flatpakApps)}
)
for app in "''${desired_apps[@]}"; do
if ! flatpak info --system "$app" >/dev/null 2>&1; then
flatpak install --system -y --noninteractive flathub "$app"
fi
done
'';
in
{
services.flatpak.enable = true;
xdg.portal = {
enable = true;
extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
};
# Deploy the config file for runtime visibility/debugging
environment.etc."flatpak/flatpaks.conf".source = flatpakConfPath;
systemd.services.flatpak-sync = {
description = "Install Flatpak apps listed in flatpaks.conf";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = syncFlatpaks;
};
restartTriggers = [ flatpakConfPath ];
path = [ pkgs.flatpak pkgs.coreutils pkgs.gnugrep pkgs.gnused ];
};
}
#+end_src
** Nix Settings
These are global nix settings that configure the settings for the actual tool.
#+begin_src nix :tangle configuration/core/nix-settings.nix :noweb tangle :mkdirp yes
{ pkgs, user, ... } :
{
nix.settings = {
# enable flakes
experimental-features = ["nix-command" "flakes"];
# add a cache that speed up new applications by downloading binaries
# from the trusted cache instead of compiling from sourcer
substituters = [
"https://nix-community.cachix.org"
];
# trust the cache public key
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
# allow proprietary software on this machine. I'm not a purist.
nixpkgs.config.allowUnfree = true;
# unityhub depends on this... for now
nixpkgs.config.permittedInsecurePackages = [ "libxml2-2.13.8" ];
# this declares how often old configurations are cleared up.
# i cleanup anything older than a week, every week.
nix.gc = {
automatic = true;
options = "--delete-older-than 7d";
dates = "weekly";
};
programs = {
# command line utility that makes applying changes easy and pretty
nh = {
enable = true;
flake = "/home/${user.username}/system";
};
};
}
#+end_src
** Boot
This file has most of the settings the control how the computer boots up.
#+begin_src nix :tangle configuration/core/boot.nix :noweb tangle :mkdirp yes
{ pkgs, ... } :
{
boot = {
initrd = {
verbose = false; # its a lot of logs. dont need it, unless we do.
kernelModules = [ ]; # no kernel modules on boot
};
extraModulePackages = [ ]; # no extra packages on boot either
kernelPackages = pkgs.linuxPackages_latest; # latest greatest linux kernel
kernelParams = [ "silent" ]; # quiet those logs
consoleLogLevel = 0; # quiten more logs
plymouth.enable = true; # graphical boot animation instead
supportedFilesystems = [ "ntfs" ]; # should see the ntfs (windows)
loader = {
systemd-boot.enable = true; # systemd-boot
systemd-boot.configurationLimit = 10;
efi.canTouchEfiVariables = true; # allow editing efi to edit the boot loader
timeout = 5; # grub timeout to make a selection
};
};
}
#+end_src
** Login
Here we control what the login screen would look like. In configuration/default.nix you can choose whether to use tuigreet (very minimalistic) or LightDM (nicer, themeable)
** Tuigreet
Doesn't match the rest of the aesthetic of the system (with hyprland), but I like its simplicity.
#+begin_src nix :tangle configuration/core/login-tuigreeter.nix :noweb tangle :mkdirp yes
{ pkgs, user, ... } :
{
environment.systemPackages = with pkgs; [
tuigreet
];
services.greetd = {
enable = true;
settings = {
default_session = {
command = pkgs.lib.mkForce "${pkgs.tuigreet}/bin/tuigreet --remember --time --time-format '%I:%M %p | %a • %h | %F'";
};
};
};
}
#+end_src
** LightDM
#+begin_src nix :tangle configuration/core/login-lightdm.nix :noweb tangle :mkdirp yes
{ config, pkgs, lib, ... }:
let
lightdmConf = builtins.readFile ../../assets/conf/core/lightdm.conf;
lockPng = ../../assets/lock.png;
greeterConfPath = ../../assets/conf/core/lightdm-gtk-greeter.conf;
greeterRaw = builtins.readFile greeterConfPath;
# Extract "key = value" from the greeter conf.
# Returns null if not found.
getIniValue = key:
let
lines = lib.splitString "\n" greeterRaw;
# Captures the value part (group 0) from a single line.
# We match line-by-line because Nix regex does NOT support PCRE flags like (?s).
m =
let
ms = builtins.filter (x: x != null) (map (line:
builtins.match
("^[[:space:]]*" + key + "[[:space:]]*=[[:space:]]*([^#;]+).*$")
line
) lines);
in
if ms == [] then null else builtins.elemAt ms 0;
in
if m == null then null else lib.strings.trim (builtins.elemAt m 0);
# In your greeter.conf these are *package keys*, not theme names.
themePkgKey = getIniValue "theme-name";
iconPkgKey = getIniValue "icon-theme-name";
cursorPkgKey = getIniValue "cursor-theme-name";
cursorSizeStr = getIniValue "cursor-theme-size";
cursorSize =
if cursorSizeStr == null then null
else lib.toInt (lib.strings.trim cursorSizeStr);
# Map package-keys (from greeter.conf) -> { package, name }
#
# IMPORTANT:
# - "name" must be the real theme/icon/cursor NAME as seen under share/themes or share/icons.
# - "package" is the Nixpkgs derivation providing it.
pkgMap = {
catppuccinThemePkg = {
package = pkgs.catppuccin-gtk.override {
accents = [ "blue" ];
variant = "mocha";
size = "standard";
tweaks = [ ];
};
name = "Catppuccin-Mocha-Standard-Blue-Dark";
};
papirus-icon-theme = {
package = pkgs.papirus-icon-theme;
name = "Papirus-Dark";
};
bibata-cursors = {
package = pkgs.bibata-cursors;
name = "Bibata-Modern-Ice";
};
};
pick = key:
if key == null then
throw "lightdm: missing required key in ${toString greeterConfPath}"
else if !(pkgMap ? "${key}") then
throw "lightdm: unknown package key '${key}' in ${toString greeterConfPath}. Known keys: ${lib.concatStringsSep ", " (builtins.attrNames pkgMap)}"
else
pkgMap."${key}";
themeSel = pick themePkgKey;
iconSel = pick iconPkgKey;
cursorSel = pick cursorPkgKey;
# Rewrite greeter.conf so LightDM sees REAL names, not package keys.
# Also force background to lockPng.
greeterFixed =
''
[greeter]
theme-name = ${themeSel.name}
icon-theme-name = ${iconSel.name}
cursor-theme-name = ${cursorSel.name}
${lib.optionalString (cursorSize != null) "cursor-theme-size = ${toString cursorSize}"}
''
+ "\n"
+ greeterRaw;
in
{
services.greetd.enable = false;
services.xserver = {
enable = true;
desktopManager.xterm.enable = false;
displayManager.lightdm = {
enable = true;
background = lockPng;
greeters.gtk = {
enable = true;
theme = {
name = themeSel.name;
package = themeSel.package;
};
iconTheme = {
name = iconSel.name;
package = iconSel.package;
};
cursorTheme = {
name = cursorSel.name;
package = cursorSel.package;
} // lib.optionalAttrs (cursorSize != null) {
size = cursorSize;
};
# This includes your (rewritten) greeter config.
extraConfig = greeterFixed;
};
extraConfig = lightdmConf;
};
};
programs.hyprland.enable = true;
# Optional: make them available system-wide as well
environment.systemPackages = [
themeSel.package
iconSel.package
cursorSel.package
];
}
#+end_src
** Terminal (default system)
This is the initial system level configuration for the terminal that I use on this machine. Its just [[https://ohmyz.sh/][zsh]].
#+begin_src nix :tangle configuration/dev/terminal.nix :noweb tangle :mkdirp yes
{ pkgs, user, ... }:
{
console.useXkbConfig = true;
users.users.${user.username}.shell = pkgs.zsh;
programs.zsh.enable = true;
environment.shells = [ pkgs.zsh ];
environment.pathsToLink = [ "/share/zsh" ];
}
#+end_src
** Files
I use Thunar as the file explorer. Also setup a few plugins for [[https://docs.xfce.org/xfce/thunar/start][Thunar]] in this config.
Along with that, a few other utilities like zip and enabling services to automount usb drives.
#+begin_src nix :tangle configuration/core/files.nix :noweb tangle :mkdirp yes
{ pkgs, user, config, ... }:
{
environment.systemPackages = with pkgs; [
zip
unzip
p7zip
usbutils
udiskie
file-roller
];
programs.thunar = {
enable = true;
plugins = with pkgs; [
thunar-archive-plugin
thunar-media-tags-plugin
thunar-volman
thunar-vcs-plugin
];
};
programs.xfconf.enable = true; # to save thunar settings
services = {
gvfs.enable = true; # Mount, trash, and other functionalities
tumbler.enable = true; # Thumbnail support for images
udisks2.enable = true; # Auto mount usb drives
};
}
#+end_src
** Locale
I live in Netherlands and would like all my locale and timezone settings to match. Except my default locale.
#+begin_src nix :tangle configuration/core/locale.nix :noweb tangle :mkdirp yes
{ user, ... } :
let
locale = user.locale;
defaultLocale = "nl_NL.UTF-8";
in
{
# Set your time zone.
time.timeZone = "Europe/Amsterdam";
# Select internationalisation properties.
i18n.defaultLocale = defaultLocale;
i18n.extraLocaleSettings = {
LC_ADDRESS = locale;
LC_IDENTIFICATION = locale;
LC_MEASUREMENT = locale;
LC_MONETARY = locale;
LC_NAME = locale;
LC_NUMERIC = locale;
LC_PAPER = locale;
LC_TELEPHONE = locale;
LC_TIME = defaultLocale;
};
}
#+end_src
** Networking
Not much to see here. I want networking to be enabled. I want firewall as well.
#+begin_src nix :tangle configuration/core/networking.nix :noweb tangle :mkdirp yes
{ pkgs, lib, ... }:
{
networking = {
useDHCP = lib.mkDefault true;
networkmanager.enable = true;
networkmanager.wifi.backend = "iwd";
wireless.iwd.enable = true;
wireless.userControlled.enable = true;
firewall = {
enable = true;
# KDE Connect: discovery + encrypted connections
allowedTCPPortRanges = [
{ from = 1714; to = 1764; }
];
allowedUDPPortRanges = [
{ from = 1714; to = 1764; }
];
};
};
environment.systemPackages = with pkgs; [ impala ];
}
#+end_src
** Hyprland
This is a big one because the DE needs so much configuration. This section mostly installs [[https://hyprland.org/][Hyprland]].
The configuration is done in the home manager section.
#+begin_src nix :tangle configuration/desktop/hyprland.nix :noweb tangle :mkdirp yes
{ pkgs, ... }:
{
nix.settings = {
substituters = [ "https://hyprland.cachix.org" ];
trusted-public-keys = [
"hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc="
];
};
services.dbus.enable = true;
security.polkit.enable = true;
services.flatpak.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
wireplumber.enable = true;
};
services.gvfs.enable = true;
xdg.portal = {
enable = true;
extraPortals = with pkgs; [
xdg-desktop-portal-hyprland
xdg-desktop-portal-gtk
];
config.common.default = [ "hyprland" "gtk" ];
};
environment.systemPackages = with pkgs; [
walker
uwsm
hyprland-qtutils
hyprpolkitagent
grimblast
];
programs = {
uwsm.enable = true;
uwsm.waylandCompositors.hyprland = {
prettyName = "Hyprland";
comment = "Hyprland compositor managed by UWSM";
binPath = "/run/current-system/sw/bin/Hyprland";
};
hyprland = {
withUWSM = true;
enable = true;
xwayland.enable = true;
};
};
environment.sessionVariables = {
XDG_SESSION_TYPE = "wayland";
XDG_CURRENT_DESKTOP = "Hyprland";
XDG_SESSION_DESKTOP = "Hyprland";
NIXOS_OZONE_WL = "1";
XCURSOR_SIZE = "24";
};
security.pam.services.hyprlock = { };
# Optional; GNOME-specific (keep only if you really use gnome-keyring integration)
security.pam.services.gdm.enableGnomeKeyring = true;
}
#+end_src
** Services
These are some of the services that I enable at the system level. Explanation in the comments.
#+begin_src nix :tangle configuration/services/services.nix :noweb tangle :mkdirp yes
{ user, ...} :
{
services = {
blueman.enable = true; # bluetooth manager
fwupd.enable = true; # firmware updating service
fstrim.enable = true; # ssd maintenance service
thermald.enable = true; # thermal regulation service
printing.enable = true; # printing services, cups
gnome.gnome-keyring.enable = true; # keyring
flatpak.enable = true; # allow installing things from flatpaks
#flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
# printer discovery
avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
};
virtualisation.docker.enable = true; # enable docker
users.users.${user.username}.extraGroups = [ "docker" ]; # add self to docker user group
}
#+end_src
** Audio
#+begin_src nix :tangle configuration/desktop/audio.nix :noweb tangle :mkdirp yes
{ config, pkgs, lib, ... }:
{
environment.systemPackages = with pkgs; [
pipewire
wireplumber
alsa-utils
pulseaudio
pamixer
pavucontrol
];
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
jack.enable = true;
wireplumber.enable = true;
};
security.rtkit.enable = true;
# Helps on many laptops (Intel SOF etc.)
hardware.enableRedistributableFirmware = true;
# Prefer analog over HDMI/DP in a machine-agnostic way
services.pipewire.wireplumber.extraConfig."51-audio-priorities" = {
"monitor.alsa.rules" = [
# De-prioritize HDMI / DisplayPort sinks
{
matches = [
{ "node.name" = "~alsa_output\\..*HDMI.*"; }
{ "node.name" = "~alsa_output\\..*DisplayPort.*"; }
];
actions.update-props = {
"priority.session" = 100;
"priority.driver" = 100;
};
}
# Prefer analog sinks (speakers/headphones)
{
matches = [
{ "node.name" = "~alsa_output\\..*analog.*"; }
{ "node.name" = "~alsa_output\\..*Headphones.*"; }
{ "node.name" = "~alsa_output\\..*Speaker.*"; }
];
actions.update-props = {
"priority.session" = 2000;
"priority.driver" = 2000;
};
}
];
};
# Optional: clear "sticky" user-selected defaults so priority rules win
systemd.user.services.wireplumber-clear-default-nodes = {
description = "Clear WirePlumber saved default nodes (avoid HDMI becoming sticky)";
after = [ "wireplumber.service" ];
partOf = [ "wireplumber.service" ];
wantedBy = [ "default.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.coreutils}/bin/rm -f %h/.local/state/wireplumber/default-nodes";
};
};
}
#+end_src
** Miscellaneous Packages and Programs
#+name: config-system-packages
#+begin_src nix
environment.systemPackages = with pkgs; [
wget # fetch utility
curl # more fetch utility
binutils # executable utilities, like ld
dmidecode # tool for dumping system info
libnotify # notification daemon
python3 # nice to have this ready for quick things
cacert # certificate authority
remmina # remote desktop app
#rg # ripgrep
wev # for finding keypresses
tree # list foldetrtree's
file # filinfo
htop # sysmonitor
solaar # logitech controller
git # source control
# jetbrains.pycharm # Dev and course environment
];
# to enable icons with wlogout
# https://github.com/catppuccin/nix/issues/584
programs.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
#+end_src
#+name: config-programs
#+begin_src nix
programs = {
nix-ld.enable = true; # helps with linking troubles with dynamic libraries
appimage.enable = true; # allow appimage installations
dconf.enable = true; # to save user settings
gnupg.agent = {
# pgp client
enable = true;
enableSSHSupport = true;
};
firefox.enable = true; # browser
wireshark.enable = true; # vpn
};
#+end_src
** Fonts
Nothing much to see here. I love [[https://protesilaos.com/codelog/2025-02-12-aporetic-fonts-version-1-1-0/][Aporetic]], and I use it everywhere.
#+name: config-fonts
#+begin_src nix
fonts.packages = with pkgs; [
aporetic
nerd-fonts.iosevka
];
#+end_src
** User Config
This creates the user profile that I login with. Initially created during install.
#+name: config-user
#+begin_src nix
users.users.${user.username} = {
isNormalUser = true;
description = "henrov";
extraGroups = [
"networkmanager" # allow editing network connections
"wheel" # can do sudo
"scanner" # access to the network scanner
"lp" # access to the printer
];
};
#+end_src
* Home
I use home-manager to manage my user level dotfiles and configurations.
Most of the "theme" of the system is decided here.
I also use it to install programs that are okay with being installed at the user level instead of the system.
** default.nix
This module will import all necessities.
#+begin_src nix :tangle home/default.nix :noweb tangle :mkdirp yes
{ pkgs, user, ... } :
{
imports = [
#./apps/default-apps.nix
./apps/theme.nix
./desktop/hypridle.nix
./desktop/hyprland.nix
./desktop/hyprexpo.nix
./desktop/hyprlock.nix
./desktop/hyprshell.nix
./desktop/wallpaper.nix
./desktop/waybar.nix
./desktop/walker.nix
./dev/dev.nix
./dev/shells.nix
./dev/starship.nix
./dev/emacs
];
<>
<>
programs.home-manager.enable = true;
}
#+end_src
** Wallpaper
wallpaper.nix installs wpaperd and deploys your wallpaper files from the repo (./assets/conf/desktop/wallpaper/pictures/) into ~/conf/desktop/wallpaper/pictures. It also deploys the default wallpaper configuration from assets/conf/desktop/wallpaper/wallpaper.conf into ~/conf/desktop/wallpaper/wallpaper.conf, which is the file you can edit as a user override.
Finally, it creates a systemd user service (wpaperd.service) that automatically starts wpaperd at login and keeps it running, using your override config so wallpapers rotate according to your settings.
#+begin_src nix :tangle home/desktop/wallpaper.nix :noweb tangle :mkdirp yes
{ config, pkgs, lib, flakeRoot, ... }:
let
repoWallpaperDir = flakeRoot + "/assets/conf/desktop/wallpaper";
repoWallpaperConf = flakeRoot + "/assets/conf/desktop/wallpaper/wallpaper.conf";
userRelRoot = "nixos_conf/wallpaperstuff";
userAbsRoot = "${config.home.homeDirectory}/${userRelRoot}";
userConfPath = "${userAbsRoot}/wallpaper.conf";
# Exclude wallpaper.conf so HM does NOT manage it (avoids backup collisions)
repoWallpapersOnly = lib.cleanSourceWith {
src = repoWallpaperDir;
filter = path: type:
(builtins.baseNameOf path) != "wallpaper.conf";
};
in
{
home.packages = [ pkgs.wpaperd ];
# Sync everything *except* wallpaper.conf into ~/nixos_conf/wallpaperstuff
home.file."${userRelRoot}" = {
source = repoWallpapersOnly;
recursive = true;
};
# Now safely overwrite the config every activation (no HM collision)
home.activation.wallpaperConfForce =
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
mkdir -p "${userAbsRoot}"
install -m 0644 "${repoWallpaperConf}" "${userConfPath}"
'';
systemd.user.services.wpaperd = {
Unit = {
Description = "wpaperd wallpaper daemon";
After = [ "default.target" ];
};
Service = {
Type = "simple";
ExecStart = "${pkgs.wpaperd}/bin/wpaperd --config ${userConfPath}";
Restart = "on-failure";
RestartSec = 1;
};
Install.WantedBy = [ "default.target" ];
};
}
#+end_src
** Waybar
[[./.github/images/waybar.png]]
Mostly styling and enabling modules in the [[https://github.com/Alexays/Waybar][top bar]].
#+begin_src nix :tangle home/desktop/waybar.nix :noweb tangle :mkdirp yes
{ config, lib, pkgs, flakeRoot, ... }:
let
repoWaybarDir = flakeRoot + "/assets/conf/desktop/waybar";
in
{
programs.waybar.enable = true;
# Ensure config matches repo (HM-managed symlink, not user-editable)
xdg.configFile."waybar/config" = {
source = repoWaybarDir + "/config.jsonc";
force = true;
};
# Override HM's internally-generated waybar-style.css derivation
# and use your repo file instead.
xdg.configFile."waybar/style.css" = {
source = lib.mkForce (repoWaybarDir + "/style.css");
force = true;
};
# Prevent HM from also trying to generate style content via programs.waybar.style
# (not strictly required once mkForce is in place, but keeps intent clear)
programs.waybar.style = "";
}
#+end_src
** Lock Screen
The lock screen configured using [[https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock/][hyprlock]].
I use [[https://wiki.hyprland.org/Hypr-Ecosystem/hypridle/][hypridle]] to detect idle time and use wlogout to show a logout menu.
They are configured below.
#+begin_src nix :tangle home/desktop/hyprlock.nix :noweb tangle :mkdirp yes
{config, lib, pkgs, flakeRoot, ... }:
let
lockPngSrc = flakeRoot + "/assets/lock.png";
hyprlockConf = flakeRoot + "/assets/conf/desktop/hypr/hyprlock.conf";
in
{
home.packages = [ pkgs.hyprlock ];
xdg.configFile."hypr/lock.png".source = lockPngSrc;
xdg.configFile."hypr/hyprlock.conf".source = hyprlockConf;
}
#+end_src
** Idle Screen
#+begin_src nix :tangle home/desktop/hypridle.nix :noweb tangle :mkdirp yes{ config, lib, pkgs, flakeRoot,
{ config, lib, pkgs, flakeRoot, ... }:
let
hypridleConf = flakeRoot + "/assets/conf/desktop/hypr/hypridle.conf";
in
{
home.packages = [ pkgs.hypridle ];
xdg.configFile."hypr/hypridle.conf".source = hypridleConf;
}
#+end_src
** Hyprshell
For nice task-starting and -switching
#+begin_src nix :tangle home/desktop/hyprshell.nix :noweb tangle :mkdirp yes{ config, lib, pkgs, flakeRoot,
# home/desktop/hyprshell.nix (Home-Manager module)
{ config, pkgs, lib, flakeRoot, ... }:
let
repoDir = flakeRoot + "/assets/conf/desktop/hypr/hyprshell";
cfgRon = repoDir + "/config.ron";
cssFile = repoDir + "/styles.css";
in
{
xdg.enable = true;
home.packages = [ pkgs.hyprshell ];
# Link repo -> ~/.config/hyprshell/...
xdg.configFile."hyprshell/config.ron".source = cfgRon;
xdg.configFile."hyprshell/styles.css".source = cssFile;
# Autostart (systemd user service)
systemd.user.services.hyprshell = {
Unit = {
Description = "Hyprshell (window switcher / launcher)";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
};
Service = {
ExecStart = "${pkgs.hyprshell}/bin/hyprshell";
Restart = "on-failure";
RestartSec = 1;
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
}
#+end_src
** Hyprland
This configures the desktop environment along with the peripherals. The comments should explain whats happening.
#+begin_src nix :tangle home/desktop/hyprland.nix :noweb tangle :mkdirp yes.
{ config, lib, pkgs, flakeRoot, ... }:
let
hyprConf = flakeRoot + "/assets/conf/desktop/hypr/hyprland.conf";
bindingsConf = flakeRoot + "/assets/conf/desktop/hypr/bindings.conf";
in
{
programs.kitty.enable = true;
wayland.windowManager.hyprland = {
enable = true;
# Load base config + bindings from repo files
extraConfig =
(builtins.readFile hyprConf)
+ "\n\n# --- Repo keybindings ---\n"
+ (builtins.readFile bindingsConf)
+ "\n";
};
xdg.configFile."hypr/scripts/lid-lock.sh" = {
source = flakeRoot + "/assets/conf/desktop/hypr/scripts/lid-lock.sh";
executable = true;
};
xdg.portal = {
enable = true;
extraPortals = with pkgs; [
xdg-desktop-portal-gtk
xdg-desktop-portal-hyprland
];
# GTK als algemene backend (OpenURI is daar betrouwbaar)
config.common.default = [ "gtk" ];
# Hyprland alleen voor screensharing / remote desktop
config.hyprland = {
"org.freedesktop.impl.portal.Screencast" = [ "hyprland" ];
"org.freedesktop.impl.portal.RemoteDesktop" = [ "hyprland" ];
};
};
}
#+end_src
** Walker
[[https://github.com/abenz1267/walker][This]] is how I launch applications. It is bound to Win+Space in the ./asstes/conf/desktop/hypr/bindings.conf.
#+begin_src nix :tangle home/desktop/walker.nix :noweb tangle :mkdirp yes.
{ config, pkgs, lib, inputs ? null, ... }:
let
walkerPkg =
if inputs != null && inputs ? walker
then inputs.walker.packages.${pkgs.system}.default
else pkgs.walker;
elephantPkg =
if inputs != null && inputs ? elephant
then inputs.elephant.packages.${pkgs.system}.default
else pkgs.elephant;
sessionTarget = "graphical-session.target";
in
{
xdg.enable = true;
home.packages = [
walkerPkg
elephantPkg
];
systemd.user.services.elephant = {
Unit = {
Description = "Elephant backend for Walker";
PartOf = [ sessionTarget ];
After = [ sessionTarget ];
};
Service = {
Type = "simple";
ExecStart = "${elephantPkg}/bin/elephant";
Restart = "on-failure";
RestartSec = 1;
# Ensure Elephant can create its socket under:
# /run/user/$UID/elephant/...
RuntimeDirectory = "elephant";
RuntimeDirectoryMode = "0700";
# Light hardening (DO NOT use ProtectSystem=strict here)
NoNewPrivileges = true;
PrivateTmp = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
LockPersonality = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
};
Install = {
WantedBy = [ sessionTarget ];
};
};
systemd.user.services.walker = {
Unit = {
Description = "Walker GApplication service";
PartOf = [ sessionTarget ];
After = [ sessionTarget "elephant.service" ];
Wants = [ "elephant.service" ];
};
Service = {
Type = "simple";
ExecStart = "${walkerPkg}/bin/walker --gapplication-service";
Restart = "on-failure";
RestartSec = 1;
# Light hardening
NoNewPrivileges = true;
PrivateTmp = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
LockPersonality = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
};
Install = {
WantedBy = [ sessionTarget ];
};
};
}
#+end_src
** Theme
I use the [[https://catppuccin.com/][Catppuccin]] almost everywhere. The nix module integrates almost automatically everywhere (except gtk).
You'll notice the color values in multiple places outside this as well.
#+begin_src nix :tangle home/apps/theme.nix :noweb tangle :mkdirp yes.
{ pkgs, ...}:
{
gtk = {
enable = true;
colorScheme = "dark";
theme = {
name = "Catppuccin-GTK-Grey-Dark-Compact";
package = (pkgs.magnetic-catppuccin-gtk.override {
accent = [ "grey" ];
shade = "dark";
tweaks = [ "black" ];
size = "compact";
});
};
iconTheme.name = "Papirus-Dark";
};
catppuccin.enable = true;
catppuccin.flavor = "mocha";
catppuccin.accent = "blue";
catppuccin.gtk.icon.enable = true;
catppuccin.cursors.enable = true;
}
#+end_src
** Default-apps
This is where you can set defaults
#+begin_src nix :tangle home/apps/defaults-apps.nix :noweb tangle :mkdirp yes.
{ config, pkgs, lib, ... }:
{
xdg.mimeApps.enable = true;
xdg.mimeApps.defaultApplications = {
"x-scheme-handler/http" = [ "app.zen_browser.zen.desktop" ];
"x-scheme-handler/https" = [ "app.zen_browser.zen.desktop" ];
"text/html" = [ "app.zen_browser.zen.desktop" ];
};
}
#+end_src
** Hyperexpo
hyprexpo gets installed and configured
#+begin_src nix :tangle home/desktop/hyprexpo.nix :noweb tangle :mkdirp yes.
{ config, lib, pkgs, ... }:
{
wayland.windowManager.hyprland = {
# Load the Hyprexpo plugin (from nixpkgs)
plugins = [
pkgs.hyprlandPlugins.hyprexpo
];
# Append plugin config + keybind after your existing hyprland.conf
extraConfig = lib.mkAfter ''
############################
# Hyprexpo (workspace/window overview)
############################
# Basic plugin config (tweak as you like)
plugin {
hyprexpo {
columns = 3
gaps_in = 5
gaps_out = 20
# Optional; comment out if you don't want it
# workspace_method = center current
}
}
'';
};
}
#+end_src
** Alacritty
Alacritty gets installed and configured
#+begin_src nix :tangle home/dev/alacritty.nix :noweb tangle :mkdirp yes.
{ config, pkgs, lib, flakeRoot, ... }:
let
repoAlacrittyConf = flakeRoot + "/assets/conf/dev/alacritty.toml";
in
{
xdg.enable = true;
programs.alacritty.enable = true;
# Override the config generated by programs.alacritty
xdg.configFile."alacritty/alacritty.toml".source = lib.mkForce repoAlacrittyConf;
catppuccin.alacritty.enable = true;
catppuccin.alacritty.flavor = "mocha";
}
#+end_src
** Dev Tools
All the miscellaneous dev tools on this computer.
#+begin_src nix :tangle home/dev/dev.nix :noweb tangle :mkdirp yes
{ config, pkgs, lib, ... }:
{
programs = {
vscode.enable = true;
vim.enable = true;
ripgrep.enable = true;
btop.enable = true;
fzf = {
enable = true;
enableZshIntegration = true;
enableBashIntegration = true;
};
zoxide = {
enable = true;
enableZshIntegration = true;
enableBashIntegration = true;
};
eza = {
enable = true;
enableZshIntegration = true;
enableBashIntegration = true;
};
direnv = {
enable = true;
enableZshIntegration = true;
enableBashIntegration = true;
nix-direnv.enable = true;
};
# Zsh-specific config belongs here
zsh = {
# for emacs-eat package
initContent = lib.mkOrder 1200 ''
[ -n "$EAT_SHELL_INTEGRATION_DIR" ] && \
source "$EAT_SHELL_INTEGRATION_DIR/zsh"
'';
};
git = {
enable = true;
lfs.enable = true;
};
};
}
#+end_src
** Kitty
Kitty gets installed and configured
#+begin_src nix :tangle home/dev/kitty.nix :noweb tangle :mkdirp yes.
{ config, pkgs, lib, flakeRoot, ... }:
let
repoKittyConf = flakeRoot + "/assets/conf/dev/kitty.conf";
in
{
xdg.enable = true;
programs.kitty.enable = true;
catppuccin.alacritty.enable = true;
catppuccin.alacritty.flavor = "mocha";
syntaxHighlighting.enable = true;
autosuggestion.enable = true;
enableCompletion = true;
xdg.configFile."kitty/kitty.conf".source = repoKittyConf;
}
#+end_src
** Shells
The aliases mentioned in ./assets/conf/dev/terminal/shells.conf will be added to enabled shells
#+begin_src nix :tangle home/dev/shells.nix :noweb tangle :mkdirp yes
# shells.nix — Home-Manager module
#
# Reads:
# ${flakeRoot}/assets/conf/dev/terminal/enabled_shells.conf
# ${flakeRoot}/assets/conf/dev/terminal/aliases.conf
#
# For each enabled shell in [enabled_shells]:
# - installs/enables shell (where HM has an enable option)
# - if ${flakeRoot}/assets/conf/dev/terminal/.conf exists, sources it
# - ensures a *user-editable* aliases file exists in the shell’s default location
# - if a shell is disabled, its aliases file is removed
# .
# Notes on “editable”:
# - We do NOT manage the aliases file with xdg.configFile/home.file (those would be overwritten).
# - Instead, we create/remove files via home.activation (create only if missing).
{ config, pkgs, lib, flakeRoot, ... }:
let
terminalDir = flakeRoot + "/assets/conf/dev/terminal";
enabledFile = terminalDir + "/enabled_shells.conf";
aliasesFile = terminalDir + "/aliases.conf";
trim = lib.strings.trim;
# ---------- minimal INI-ish parser (sections + raw lines) ----------
readMaybe = p: if builtins.pathExists p then builtins.readFile p else "";
normalizeLine = l: trim (lib.replaceStrings [ "\r" ] [ "" ] l);
parseSections = text:
let
lines = map normalizeLine (lib.splitString "\n" text);
isHeader = l:
let s = l;
in lib.hasPrefix "[" s
&& lib.hasSuffix "]" s
&& builtins.stringLength s >= 3;
nameOf = l: lib.removeSuffix "]" (lib.removePrefix "[" l);
folded =
builtins.foldl'
(st: l:
if l == "" then st else
if isHeader l then st // { current = nameOf l; }
else
let
cur = st.current;
prev = st.sections.${cur} or [];
in
st // { sections = st.sections // { ${cur} = prev ++ [ l ]; }; }
)
{ current = "__root__"; sections = {}; }
lines;
in
folded.sections;
enabledSections = parseSections (readMaybe enabledFile);
aliasSections = parseSections (readMaybe aliasesFile);
# [enabled_shells] lines: key = yes/no
enabledShells =
let
raw = enabledSections.enabled_shells or [];
parseKV = l:
let m = builtins.match ''^([A-Za-z0-9_-]+)[[:space:]]*=[[:space:]]*(.*)$'' l;
in if m == null then null else {
k = trim (builtins.elemAt m 0);
v = lib.toLower (trim (builtins.elemAt m 1));
};
kvs = builtins.filter (x: x != null) (map parseKV raw);
in
map (x: x.k) (builtins.filter (x: x.v == "yes" || x.v == "true" || x.v == "1") kvs);
shellEnabled = shell: builtins.elem shell enabledShells;
# ---------- per-shell repo config file (.conf) ----------
shellConfPath = shell: terminalDir + "/${shell}.conf";
shellConfExists = shell: builtins.pathExists (shellConfPath shell);
sourceIfExistsSh = p: ''
if [ -f "${toString p}" ]; then
source "${toString p}"
fi
'';
# ---------- aliases section helpers ----------
secLines = name: aliasSections.${name} or [];
secText = name: lib.concatStringsSep "\n" (secLines name);
# Default alias-file locations
bashAliasesPath = "${config.home.homeDirectory}/.bash_aliases";
zshAliasesPath = "${config.home.homeDirectory}/.zsh_aliases";
fishAliasesPath = "${config.xdg.configHome}/fish/conf.d/aliases.fish";
# Seeds (created once; user can edit afterwards)
bashSeed = ''
# Created once from: ${toString aliasesFile}
# Edit freely; Home Manager will not overwrite this file.
#
${secText "bash_zsh"}
${secText "bash_specific"}
'';
zshSeed = ''
# Created once from: ${toString aliasesFile}
# Edit freely; Home Manager will not overwrite this file.
#
${secText "bash_zsh"}
${secText "zsh_specific"}
'';
# Fish: translate [bash_zsh] POSIX alias lines + append [fish_specific] as-is
parsePosixAlias = l:
let
m = builtins.match ''^[[:space:]]*alias[[:space:]]+([A-Za-z0-9_+-]+)=(.*)$'' l;
in
if m == null then null else
let
name = trim (builtins.elemAt m 0);
rhs0 = trim (builtins.elemAt m 1);
unquote =
if lib.hasPrefix "'" rhs0 && lib.hasSuffix "'" rhs0 then
lib.removeSuffix "'" (lib.removePrefix "'" rhs0)
else if lib.hasPrefix "\"" rhs0 && lib.hasSuffix "\"" rhs0 then
lib.removeSuffix "\"" (lib.removePrefix "\"" rhs0)
else
rhs0;
in
{ inherit name; cmd = unquote; };
escapeForFish = s:
lib.replaceStrings
[ "\\" "\"" "$" "`" ]
[ "\\\\" "\\\"" "\\$" "\\`" ]
s;
fishTranslated =
let
parsed = builtins.filter (x: x != null) (map parsePosixAlias (secLines "bash_zsh"));
in
lib.concatStringsSep "\n" (map (a: ''alias ${a.name} "${escapeForFish a.cmd}"'') parsed);
fishSeed = ''
# Created once from: ${toString aliasesFile}
# Edit freely; Home Manager will not overwrite this file.
status is-interactive; or exit
# Translated from [bash_zsh]:
${fishTranslated}
# From [fish_specific]:
${secText "fish_specific"}
'';
in
{
xdg.enable = true;
# Install/enable shells (no login-shell changes)
programs.bash.enable = shellEnabled "bash";
programs.zsh.enable = shellEnabled "zsh";
programs.fish.enable = shellEnabled "fish";
home.packages =
(lib.optionals (shellEnabled "dash") [ pkgs.dash ]) ++
(lib.optionals (shellEnabled "nushell") [ pkgs.nushell ]);
# Source per-shell repo config (if present) AND source the user alias file (if it exists).
# Important: define each option only ONCE.
programs.bash.bashrcExtra = lib.mkIf (shellEnabled "bash") (lib.mkAfter ''
${lib.optionalString (shellConfExists "bash") (sourceIfExistsSh (shellConfPath "bash"))}
if [ -f "${bashAliasesPath}" ]; then
source "${bashAliasesPath}"
fi
'');
programs.zsh.initContent = lib.mkIf (shellEnabled "zsh") (lib.mkAfter ''
${lib.optionalString (shellConfExists "zsh") (sourceIfExistsSh (shellConfPath "zsh"))}
if [ -f "${zshAliasesPath}" ]; then
source "${zshAliasesPath}"
fi
'');
programs.fish.interactiveShellInit = lib.mkIf (shellEnabled "fish") (lib.mkAfter ''
${lib.optionalString (shellConfExists "fish") ''
if test -f "${toString (shellConfPath "fish")}"
source "${toString (shellConfPath "fish")}"
end
''}
if test -f "${fishAliasesPath}"
source "${fishAliasesPath}"
end
'');
# Create/remove alias files based on enabled shells
home.activation.shellAliasesFiles = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
# bash -------------------------------------------------------
if ${if shellEnabled "bash" then "true" else "false"}; then
cat > "${bashAliasesPath}" <<'EOF'
${bashSeed}
EOF
else
rm -f "${bashAliasesPath}"
fi
# zsh -------------------------------------------------------
if ${if shellEnabled "zsh" then "true" else "false"}; then
cat > "${zshAliasesPath}" <<'EOF'
${zshSeed}
EOF
else
rm -f "${zshAliasesPath}"
fi
# fish -------------------------------------------------------
if ${if shellEnabled "fish" then "true" else "false"}; then
mkdir -p "$(dirname "${fishAliasesPath}")"
cat > "${fishAliasesPath}" <<'EOF'
${fishSeed}
EOF
else
rm -f "${fishAliasesPath}"
fi
# fish
if ${if shellEnabled "fish" then "true" else "false"}; then
mkdir -p "$(dirname "${fishAliasesPath}")"
if [ ! -f "${fishAliasesPath}" ]; then
cat > "${fishAliasesPath}" <<'EOF'
${fishSeed}
EOF
fi
else
rm -f "${fishAliasesPath}"
fi
'';
}
#+end_src
** Zsh
Zsh gets installed and configured
#+begin_src nix :tangle home/dev/zsh.nix :noweb tangle :mkdirp yes.
{ config, pkgs, lib, flakeRoot, ... }:
let
repoZshConf = flakeRoot + "/assets/conf/dev/terminal/zsh.conf";
in
{
programs.zsh = {
enable = true;
syntaxHighlighting.enable = true;
autosuggestions.enable = true;
enableCompletion = true;
autocd = true;
dotDir = config.home.homeDirectory;
# ---- Oh My Zsh ----
oh-my-zsh = {
enable = true;
theme = "";
plugins = [
"git"
"sudo"
"extract"
"colored-man-pages"
"command-not-found"
"history"
"docker"
"kubectl"
# IMPORTANT: these should be last
"zsh-autosuggestions"
"zsh-syntax-highlighting"
];
};
'';
};
}
#+end_src
** Starship
The configuration mentioned in ./assets/conf/dev/terminal/starship.toml will be added to enabled shells
#+begin_src nix :tangle home/dev/starship.nix :noweb tangle :mkdirp yes
{ config, pkgs, lib, flakeRoot, ... }:
let
repoStarshipToml = flakeRoot + "/assets/conf/dev/terminal/starship.toml";
# The exact key that appears in the error:
targetKey = "${config.home.homeDirectory}/.config/starship.toml";
in
{
xdg.enable = true;
programs.starship = {
enable = true;
enableZshIntegration = true;
enableBashIntegration = true;
enableFishIntegration = true;
};
# Force the *actual conflicting option* (home.file."".source)
home.file."${targetKey}".source = lib.mkForce repoStarshipToml;
}
#+end_src
** Other Settings
Some repeated info from the configuration.
*** Home User
#+name: home-user
#+begin_src nix
home.username = "${user.username}";
home.homeDirectory = pkgs.lib.mkDefault "/home/${user.username}";
home.stateVersion = user.stateVersion;
#+end_src
* Emacs
I practically live inside emacs. The configuration for it is a mix between init.el and the nix configuration.
Nix allows me to install emacs packages as part of the configuration which is most of the following file.
I install the nix community provided emacs overlay that lets me have the latest emacs with pgtk ui (for wayland).
Comments describe the emacs package and what it does.
#+begin_src nix :tangle home/dev/emacs/default.nix :noweb tangle :mkdirp yes
{ pkgs, ... }:
{
programs.emacs = {
enable = true;
# install with tree sitter enabled
package = (pkgs.emacs-pgtk.override { withTreeSitter = true; });
extraPackages = epkgs: [
# also install all tree sitter grammars
epkgs.manualPackages.treesit-grammars.with-all-grammars
epkgs.nerd-icons # nerd fonts support
epkgs.doom-modeline # model line
epkgs.diminish # hides modes from modeline
epkgs.eldoc # doc support
epkgs.pulsar # pulses the cursor when jumping about
epkgs.which-key # help porcelain
epkgs.expreg # expand region
epkgs.vundo # undo tree
epkgs.puni # structured editing
epkgs.avy # jumping utility
epkgs.consult # emacs right click
epkgs.vertico # minibuffer completion
epkgs.marginalia # annotations for completions
epkgs.crux # utilities
epkgs.magit # git porcelain
epkgs.nerd-icons-corfu # nerd icons for completion
epkgs.corfu # completion
epkgs.cape # completion extensions
epkgs.orderless # search paradigm
epkgs.yasnippet # snippets support
epkgs.yasnippet-snippets # commonly used snippets
epkgs.rg # ripgrep
epkgs.exec-path-from-shell # load env and path
epkgs.eat # better shell
epkgs.rust-mode # rust mode (when rust-ts doesn't cut it)
epkgs.rustic # more rust things
epkgs.nix-mode # nix lang
epkgs.hcl-mode # hashicorp file mode
epkgs.shell-pop # quick shell popup
epkgs.envrc # support for loading .envrc
epkgs.nixpkgs-fmt # format nix files
epkgs.f # string + file utilities
epkgs.gptel # llm chat (mainly claude)
epkgs.catppuccin-theme # catppuccin theme
epkgs.eldoc-box # docs in a box
epkgs.sideline # mainly for flymake errors on the side
epkgs.sideline-flymake # mainly for flymake errors on the side
epkgs.sideline-eglot # mainly for flymake errors on the side
];
};
home.sessionVariables = {
EDITOR = "emacs";
XDG_SCREENSHOTS_DIR = "~/screenshots";
};
home.file = {
emacs-init = {
source = ./early-init.el;
target = ".emacs.d/early-init.el";
};
emacs = {
source = ./init.el;
target = ".emacs.d/init.el";
};
};
services.nextcloud-client = {
enable = true;
};
}
#+end_src
** Early Initialization
There are some emacs settings that can be configured before the gui shows up.
And some of them help increase performance and let the gui show up that much faster.
These are listed here.
#+begin_src emacs-lisp :tangle home/dev/emacs/early-init.el :mkdirp yes
;;; package --- early init -*- lexical-binding: t -*-
;;; Commentary:
;;; Prevents white flash and better Emacs defaults
;;; Code:
(set-language-environment "UTF-8")
(setq-default
default-frame-alist
'((background-color . "#1e1e2e")
(bottom-divider-width . 1) ; Thin horizontal window divider
(foreground-color . "#bac2de") ; Default foreground color
(fullscreen . maximized) ; Maximize the window by default
(horizontal-scroll-bars . nil) ; No horizontal scroll-bars
(left-fringe . 8) ; Thin left fringe
(menu-bar-lines . 0) ; No menu bar
(right-divider-width . 1) ; Thin vertical window divider
(right-fringe . 8) ; Thin right fringe
(tool-bar-lines . 0) ; No tool bar
(undecorated . t) ; Remove extraneous X decorations
(vertical-scroll-bars . nil)) ; No vertical scroll-bars
user-full-name "Henrov henrov" ; ME!
;; memory configuration
;; Higher garbage collection threshold, prevents frequent gc locks, reset later
gc-cons-threshold most-positive-fixnum
;; Ignore warnings for (obsolete) elisp compilations
byte-compile-warnings '(not obsolete)
;; And other log types completely
warning-suppress-log-types '((comp) (bytecomp))
;; Large files are okay in the new millenium.
large-file-warning-threshold 100000000
;; dont show garbage collection messages at startup, will reset later
garbage-collection-messages nil
;; native compilation
package-native-compile t
native-comp-warning-on-missing-source nil
native-comp-async-report-warnings-errors 'silent
;; Read more based on system pipe capacity
read-process-output-max (max (* 10240 10240) read-process-output-max)
;; scroll configuration
scroll-margin 0 ; Lets scroll to the end of the margin
scroll-conservatively 100000 ; Never recenter the window
scroll-preserve-screen-position 1 ; Scrolling back and forth
;; frame config
;; Improve emacs startup time by not resizing to adjust for custom settings
frame-inhibit-implied-resize t
;; Dont resize based on character height / width but to exact pixels
frame-resize-pixelwise t
;; backups & files
backup-directory-alist '(("." . "~/.backups/")) ; Don't clutter
backup-by-copying t ; Don't clobber symlinks
create-lockfiles nil ; Don't have temp files
delete-old-versions t ; Cleanup automatically
kept-new-versions 6 ; Update every few times
kept-old-versions 2 ; And cleanup even more
version-control t ; Version them backups
delete-by-moving-to-trash t ; Dont delete, send to trash instead
;; startup
inhibit-startup-screen t ; I have already done the tutorial. Twice
inhibit-startup-message t ; I know I am ready
inhibit-startup-echo-area-message t ; Yep, still know it
initial-scratch-message nil ; I know it is the scratch buffer!
initial-buffer-choice nil
inhibit-startup-buffer-menu t
inhibit-x-resources t
initial-major-mode 'fundamental-mode
pgtk-wait-for-event-timeout 0.001 ; faster child frames
ad-redefinition-action 'accept ; dont care about legacy things being redefined
inhibit-compacting-font-caches t
;; tabs
tab-width 4 ; Always tab 4 spaces.
indent-tabs-mode nil ; Never use actual tabs.
;; rendering
cursor-in-non-selected-windows nil ; dont render cursors other windows
;; packages
use-package-always-defer t
load-prefer-newer t
default-input-method nil
use-dialog-box nil
use-file-dialog nil
use-package-expand-minimally t
package-enable-at-startup nil
use-package-enable-imenu-support t
auto-mode-case-fold nil ; No second pass of case-insensitive search over auto-mode-alist.
package-archives '(("melpa" . "https://melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa-stable" . "https://stable.melpa.org/packages/"))
package-archive-priorities '(("gnu" . 99)
("nongnu" . 80)
("melpa" . 70)
("melpa-stable" . 50))
)
;;; early-init.el ends here
#+end_src
** Initialization
Now starts the main emacs configuration.
#+begin_src emacs-lisp :tangle home/dev/emacs/init.el :mkdirp yes
;;; package --- Summary - My minimal Emacs init file -*- lexical-binding: t -*-
;;; Commentary:
;;; Simple Emacs setup I carry everywhere
;;; Code:
(setq custom-file (locate-user-emacs-file "custom.el"))
(load custom-file 'noerror) ;; no error on missing custom file
(require 'package)
(package-initialize)
(defun reset-custom-vars ()
"Resets the custom variables that were set to crazy numbers"
(setopt gc-cons-threshold (* 1024 1024 100))
(setopt garbage-collection-messages t))
(use-package emacs
:custom
(native-comp-async-query-on-exit t)
(read-answer-short t)
(use-short-answers t)
(enable-recursive-minibuffers t)
(which-func-update-delay 1.0)
(visible-bell nil)
(custom-buffer-done-kill t)
(whitespace-line-column nil)
(x-underline-at-descent-line t)
(imenu-auto-rescan t)
(uniquify-buffer-name-style 'forward)
(confirm-nonexistent-file-or-buffer nil)
(create-lockfiles nil)
(make-backup-files nil)
(kill-do-not-save-duplicates t)
(sentence-end-double-space nil)
(treesit-enabled-modes t)
:init
;; base visual
(menu-bar-mode -1) ;; no menu bar
(toggle-scroll-bar -1) ;; no scroll bar
(tool-bar-mode -1) ;; no tool bar either
(blink-cursor-mode -1) ;; stop blinking
;; font of the century
(set-frame-font "Aporetic Sans Mono 12" nil t)
:bind
(("C-" . pixel-scroll-precision) ; dont zoom in please, just scroll
("C-" . pixel-scroll-precision) ; dont zoom in either, just scroll
("C-x k" . kill-current-buffer)) ; kill the buffer, dont ask
:hook
(text-mode . delete-trailing-whitespace-mode)
(prog-mode . delete-trailing-whitespace-mode)
(after-init . global-display-line-numbers-mode) ;; always show line numbers
(after-init . column-number-mode) ;; column number in the mode line
(after-init . size-indication-mode) ;; file size in the mode line
(after-init . pixel-scroll-precision-mode) ;; smooth mouse scroll
(after-init . electric-pair-mode) ;; i mean ... parens should auto create
(after-init . reset-custom-vars)
)
(use-package autorevert
:ensure nil
:custom
(auto-revert-interval 3)
(auto-revert-remote-files nil)
(auto-revert-use-notify t)
(auto-revert-avoid-polling nil)
(auto-revert-verbose t)
:hook
(after-init . global-auto-revert-mode))
(use-package recentf
:ensure nil
:commands (recentf-mode recentf-cleanup)
:hook
(after-init . recentf-mode)
:custom
(recentf-auto-cleanup 'never)
(recentf-exclude
(list "\\.tar$" "\\.tbz2$" "\\.tbz$" "\\.tgz$" "\\.bz2$"
"\\.bz$" "\\.gz$" "\\.gzip$" "\\.xz$" "\\.zip$"
"\\.7z$" "\\.rar$"
"COMMIT_EDITMSG\\'"
"\\.\\(?:gz\\|gif\\|svg\\|png\\|jpe?g\\|bmp\\|xpm\\)$"
"-autoloads\\.el$" "autoload\\.el$"))
:config
;; A cleanup depth of -90 ensures that `recentf-cleanup' runs before
;; `recentf-save-list', allowing stale entries to be removed before the list
;; is saved by `recentf-save-list', which is automatically added to
;; `kill-emacs-hook' by `recentf-mode'.
(add-hook 'kill-emacs-hook #'recentf-cleanup -90))
(use-package savehist
:ensure nil
:commands (savehist-mode savehist-save)
:hook
(after-init . savehist-mode)
:custom
(savehist-autosave-interval 600)
(savehist-additional-variables
'(kill-ring ; clipboard
register-alist ; macros
mark-ring global-mark-ring ; marks
search-ring regexp-search-ring)))
(use-package hl-line
:ensure nil
:custom
(hl-line-sticky-flag nil)
(global-hl-line-sticky-flag nil)
:hook
(after-init . global-hl-line-mode))
(use-package saveplace
:ensure nil
:commands (save-place-mode save-place-local-mode)
:hook
(after-init . save-place-mode)
:custom
(save-place-limit 400))
(use-package nerd-icons
:custom
;; disable bright icon colors
(nerd-icons-color-icons nil))
(use-package doom-modeline
:custom
(inhibit-compacting-font-caches t) ;; speed
(doom-modeline-buffer-file-name-style 'relative-from-project)
(doom-modeline-major-mode-icon nil) ;; distracting icons, no thank you
(doom-modeline-buffer-encoding nil) ;; everything is utf-8 anyway
(doom-modeline-buffer-state-icon nil) ;; the filename already shows me
(doom-modeline-lsp nil) ;; lsp state is too distracting, too often
:hook (after-init . doom-modeline-mode))
(load-theme 'catppuccin :no-confirm)
(use-package diminish :demand t) ;; declutter the modeline
(use-package eldoc
:diminish eldoc-mode
:custom
(eldoc-echo-area-use-multiline-p nil)) ;; docs for everything
(use-package eldoc-box
:defer t
:config
(set-face-background 'eldoc-box-border (catppuccin-color 'green))
(set-face-background 'eldoc-box-body (catppuccin-color 'base))
:bind
(("M-h" . eldoc-box-help-at-point)))
(use-package pulsar
:commands pulsar-global-mode pulsar-recenter-top pulsar-reveal-entry
:init
(defface pulsar-catppuccin
`((default :extend t)
(((class color) (min-colors 88) (background light))
:background ,(catppuccin-color 'sapphire))
(((class color) (min-colors 88) (background dark))
:background ,(catppuccin-color 'sapphire))
(t :inverse-video t))
"Alternative nord face for `pulsar-face'."
:group 'pulsar-faces)
:custom
(pulsar-face 'pulsar-catppuccin)
:hook
(after-init . pulsar-global-mode))
(use-package which-key
:commands which-key-mode
:diminish which-key-mode
:hook
(after-init . which-key-mode))
(use-package expreg
:bind ("M-m" . expreg-expand))
(use-package vundo) ;; undo tree
;; better structured editing
(use-package puni
:commands puni-global-mode
:hook
(after-init . puni-global-mode))
(use-package avy
:bind
("M-i" . avy-goto-char-2)
:custom
(avy-background t))
(use-package consult
:bind
("C-x b" . consult-buffer) ;; orig. switch-to-buffer
("M-y" . consult-yank-pop) ;; orig. yank-pop
("M-g M-g" . consult-goto-line) ;; orig. goto-line
("M-g i" . consult-imenu) ;; consult version is interactive
("M-g r" . consult-ripgrep) ;; find in project also works
:custom
(consult-narrow-key "<"))
(use-package vertico
:commands vertico-mode
:custom
(read-file-name-completion-ignore-case t)
(read-buffer-completion-ignore-case t)
(completion-ignore-case t)
(enable-recursive-minibuffers t)
(minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt))
:init
(vertico-mode)
:hook
(minibuffer-setup-hook . cursor-intangible-mode))
(use-package marginalia
:commands marginalia-mode
:hook (after-init . marginalia-mode))
(use-package crux
:bind
("C-c M-e" . crux-find-user-init-file)
("C-c C-w" . crux-transpose-windows)
("C-c M-d" . crux-find-current-directory-dir-locals-file)
("C-a" . crux-move-beginning-of-line))
(use-package magit
:bind (("C-M-g" . magit-status)))
(use-package nerd-icons-corfu
:commands nerd-icons-corfu-formatter
:defines corfu-margin-formatters)
(use-package corfu
:commands global-corfu-mode
:custom
(corfu-cycle t)
(corfu-auto t)
(corfu-auto-delay 1)
(corfu-auto-prefix 3)
(corfu-separator ?_)
:hook
(after-init . global-corfu-mode)
:config
(add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))
(use-package cape)
(use-package orderless
:custom
(completion-styles '(orderless partial-completion basic))
(completion-category-defaults nil)
(completion-category-overrides nil))
(use-package yasnippet
:commands yas-global-mode
:diminish yas-minor-mode
:hook
(after-init . yas-global-mode))
(use-package yasnippet-snippets :after yasnippet)
(use-package exec-path-from-shell
:commands exec-path-from-shell-initialize
:custom
(exec-path-from-shell-arguments nil)
:hook
(after-init . exec-path-from-shell-initialize))
(use-package nixpkgs-fmt
:custom
(nixpkgs-fmt-command "nixfmt"))
(use-package eat
:bind
(("C-c e p" . eat-project)
("C-c e t" . eat)))
(use-package f :demand t)
(use-package envrc
:commands envrc-global-mode
:hook
(after-init . envrc-global-mode))
(use-package gptel
:commands gptel-make-anthropic f-read-text
:config
(gptel-make-anthropic "Claude"
:stream t :key (f-read-text "/run/secrets/claude_key")))
(use-package sideline-flymake)
(use-package sideline-eglot)
(use-package sideline
:custom
(sideline-backends-right '(sideline-flymake sideline-eglot))
:hook
(eglot-managed-mode . sideline-mode)
(flymake-mode . sideline-mode))
(use-package eglot
:custom
(eglot-extend-to-xref t)
(eglot-ignored-server-capabilities '(:inlayHintProvider))
(jsonrpc-event-hook nil)
:hook
(eglot-managed-mode . eldoc-box-hover-mode)
(before-save . eldoc-format-buffer)
:bind
(:map eglot-mode-map
("C-c l a" . eglot-code-actions)
("C-c l r" . eglot-rename)
("C-c l h" . eldoc)
("C-c l g" . xref-find-references)
("C-c l w" . eglot-reconnect)))
(use-package proced
:custom
(proced-auto-update-flag t)
(proced-auto-update-interval 3)
(proced-enable-color-flag t)
(proced-show-remote-processes t))
(use-package org
:ensure t
:defer t
:commands (org-mode org-capture org-agenda)
:init
(defvar org-journal-file "~/nextcloud/org/journal.org")
(defvar org-archive-file "~/nextcloud/org/archive.org")
(defvar org-notes-file "~/nextcloud/org/notes.org")
(defvar org-inbox-file "~/nextcloud/org/inbox.org")
(defvar org-work-file "~/nextcloud/org/work.org")
(defun my/org-capture-project-target-heading ()
"Determine Org target headings from the current file's project path.
This function assumes a directory structure like '~/projects/COMPANY/PROJECT/'.
It extracts 'COMPANY' and 'PROJECT' to use as nested headlines
for an Org capture template.
If the current buffer is not visi
ting a file within such a
project structure, it returns nil, causing capture to default to
the top of the file."
(when-let* ((path (buffer-file-name))) ; Ensure we are in a file-visiting buffer
(let ((path-parts (split-string path "/" t " ")))
(when-let* ((projects-pos (cl-position "projects" path-parts :test #'string=))
(company (nth (+ 1 projects-pos) path-parts))
(project (nth (+ 2 projects-pos) path-parts)))
;; Return a list of headlines for Org to find or create.
(list company project)))))
:bind
(("C-c c" . org-capture)
("C-c i" . org-store-link)
("C-c a" . org-agenda)
:map org-mode-map
("C-c t" . org-toggle-inline-images)
("C-c l" . org-toggle-link-display))
:custom
(org-agenda-files (list org-inbox-file org-journal-file))
(org-directory "~/nextcloud/org")
(org-default-notes-file org-inbox-file)
(org-archive-location (concat org-archive-file "::* From %s"))
(org-log-done 'time)
(org-log-into-drawer t)
(org-hide-emphasis-markers t)
(org-src-fontify-natively t)
(org-src-tab-acts-natively t)
(org-capture-templates '(("t" "Todo" entry (file org-inbox-file)
"* TODO %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n\n%a\n\n)")
("j" "Journal" entry (file+olp+datetree org-journal-file)
"* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n\n%a\n\n")
("n" "Note" entry (file org-notes-file)
"* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n\n%a\n\n")
("p" "Project Task" item
(file+function org-work-file my/org-capture-project-target-heading)
"* TODO %? \n CLOCK: %U"
))
)
:config
;; Enable syntax highlighting in code blocks
(add-hook 'org-mode-hook 'turn-on-font-lock)
(add-hook 'org-mode-hook 'org-indent-mode))
;; extras
(use-package comp-run
:ensure nil
:config
(push "tramp-loaddefs.el.gz" native-comp-jit-compilation-deny-list)
(push "cl-loaddefs.el.gz" native-comp-jit-compilation-deny-list))
(use-package rustic
:custom
(rustic-lsp-client 'eglot))
(provide 'init)
;;; init.el ends here
#+end_src
* Machines
Only a few more things left. Specifically the machine level extra settings.
** Traveldroid
The configuration for the laptop does not change much. Most changes are because the hardware is different.
*** System Level
Nothing specific for the laptop.
#+begin_src nix :tangle machines/traveldroid/configuration.nix :noweb tangle :mkdirp yes
{ user, ... } : {
imports =
[
./hardware-configuration.nix
../../configuration
];
}
#+end_src
*** Hardware
This is the most different. Mostly taken from hardware-configuration.nix setup at first install.
#+begin_src nix :tangle machines/traveldroid/hardware-configuration.nix :noweb tangle :mkdirp yes
{
hostname,
pkgs,
lib,
modulesPath,
user,
config,
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
../../hardware/hardware.nix
];
boot.initrd.availableKernelModules = [ "xhci_pci" "nvme" "usb_storage" "sd_mod" "rtsx_usb_sdmmc" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/69433a14-fbaf-401b-af85-cd1bbf02b4e2";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/811D-0676";
fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ];
};
swapDevices =
[ { device = "/dev/disk/by-uuid/b6c557c2-7682-460b-a5e7-8f6f2f429a3a"; }
];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
#+end_src
*** Home
This is mostly about configuring the monitor. And laptop specific utilities.
#+begin_src nix :tangle machines/traveldroid/home.nix :noweb tangle :mkdirp yes
{ pkgs, ... }:
{
imports = [
../../home
];
home.packages = with pkgs; [
brightnessctl
];
wayland.windowManager.hyprland = {
extraConfig = ''
# Default portable monitor rule
monitor=DP-1,3840x1080@144,1920x0,1
'';
};
}
#+end_src
* README Utils
*** Headers
This script adds a =DO NOT MODIFY= header to all the generated nix files.
#+NAME: startup
#+BEGIN_SRC emacs-lisp
(progn
(defun add-tangle-headers ()
(message "running in %s" (buffer-file-name))
(when (string= (file-name-extension (buffer-file-name)) "nix")
(goto-char (point-min))
(insert "# WARNING : This file was generated by README.org\n# DO NOT MODIFY THIS FILE!\n# Any changes made here will be overwritten.\n")
(save-buffer))
(save-buffer))
(add-hook 'org-babel-post-tangle-hook 'add-tangle-headers))
#+END_SRC
# Local Variables:
# org-confirm-babel-evaluate: nil
# eval: (progn (org-babel-goto-named-src-block "startup") (org-babel-execute-src-block) (outline-hide-sublevels 1))
# End: