#+title: Droidnix: A Dendritic NixOS + Home Manager Configuration #+author: Henro Veijer #+options: toc:t num:nil htmlize:nil #+language: en #+html_head: #+HTML_HEAD: #+HTML_HEAD: * Shortcuts [[#introduction][Introduction]] [[#the-assets-folder][The Assets Folder]] [[#the-actual-code][The Actual Code]] --- * Introduction :intro: :PROPERTIES: :CUSTOM_ID: introduction :END: ** What is Droidnix Droidnix is a modular, declarative NixOS + Home Manager configuration system. It allows users to choose between =Hyprland= and =Mangowc= as their window manager, with shared and WM-specific configurations managed via Emacs Org and Nix Flakes. The project is designed for reproducibility, maintainability, and cross-machine compatibility. *** Installed components: **** Core **** Hyprland **** Mangowc *** Goals, project Structure, import hierarchy This project uses a **modular NixOS configuration** with **Hyprland** and **MangoWC** support, designed for **literate programming** and **cross-device reusability**. The Droidnix repository is organized into two main parts: 1. =.assets/=: Static, non-generated files (e.g., configs, scripts, themes). 2. Generated folders (=system=, =hyprland=, =mangowc=): NixOS and Home Manager configurations, generated from Org files. #+title: NixOS Configuration Structure * Root Level - =flake.nix= is the entry point and imports: - =generated/assets/= - =generated//modules/= - =generated/hosts/= * Generated Structure The =generated/= directory contains all generated configurations, divided into three main groups: =system=, =hyprland=, and =mangowc=. ** First Setup 1. Clone this repository. 2. Run the setup script: =./setup_droid=. 3. Edit =.assets/system/conf/base.conf= to choose your window manager (=wm = "hyprland"= or =wm = "mangowc"=). 4. Tangle this Org file to generate Nix configurations: =C-c C-v t= in Emacs or use this: =emacs README.org --batch -f org-babel-tangle && emacs --batch --eval "(setq org-html-htmlize-output-type nil)" README.org -f org-html-export-to-html= 5. Build and switch: =sudo nixos-rebuild switch --flake .#=. --- * The Assets Folder :assets: :PROPERTIES: :CUSTOM_ID: the-assets-folder :END: The =.assets/= folder contains all static files, such as configs, scripts, and themes. These files are not generated and can be edited directly. * The Actual Code :code: :PROPERTIES: :CUSTOM_ID: the-actual-code :END: This section contains the Org blocks for tangling Nix code into the generated folders. ** =flake.nix= The Nix flake definition for Droidnix. #+BEGIN_SRC nix :tangle flake.nix :noweb tangle :mkdirp yes :eval never-html { description = "Droidnix: A dendritic NixOS + Home Manager configuration"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; home-manager = { url = "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; }; import-tree.url = "github:vic/import-tree"; stylix = { url = "github:nix-community/stylix"; inputs.nixpkgs.follows = "nixpkgs"; }; zen-browser = { url = "github:youwen5/zen-browser-flake"; inputs.nixpkgs.follows = "nixpkgs"; }; hyprland.url = "github:hyprwm/Hyprland"; }; outputs = { self, nixpkgs, home-manager, import-tree, stylix, hyprland, zen-browser, ... }: let system = "x86_64-linux"; flakeRoot = self; in { nixosConfigurations = { traveldroid = nixpkgs.lib.nixosSystem { inherit system; modules = [ ./generated/hosts/traveldroid/host.nix ]; specialArgs = { inherit flakeRoot; inherit home-manager import-tree stylix hyprland zen-browser; }; }; }; }; } #+END_SRC ** =generated/hosts/traveldroid/host.nix= #+BEGIN_SRC nix :tangle generated/hosts/traveldroid/host.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, flakeRoot, import-tree, home-manager, ... }: let hostname = "traveldroid"; modulesPath = "${flakeRoot}/generated/modules/${hostname}"; usersPath = "${flakeRoot}/generated/users"; hostModules = import-tree modulesPath; globalUsers = import-tree usersPath; allModules = hostModules.imports ++ globalUsers.imports; in { ################################# # Core system config ################################# networking.hostName = hostname; system.stateVersion = "26.05"; ################################# # Imports ################################# imports = [ ./boot.nix ./hardware-configuration.nix # REQUIRED for Home Manager home-manager.nixosModules.home-manager ] ++ allModules; ################################# # Home Manager integration ################################# home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; } #+END_SRC ** =generated/hosts/traveldroid/hardware-configuration.nix= 1. Boot into NixOS Live ISO or your installed system. 2. Open a terminal. 3. Run: sudo nixos-generate-config --root /mnt (Omit --root /mnt if already running NixOS.) #+BEGIN_SRC nix :tangle generated/hosts/traveldroid/hardware-configuration.nix :noweb tangle :mkdirp yes :eval never-html { 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 ** =generated/hosts/traveldroid/boot.nix= #+BEGIN_SRC nix :tangle generated/hosts/traveldroid/boot.nix :noweb tangle :mkdirp yes :eval never-html { config, pkgs, lib, flakeRoot, ... }: let hostname = config.networking.hostName or "traveldroid"; # Build GRUB theme from your assets grubTheme = pkgs.stdenv.mkDerivation { name = "droidnix-grub-theme"; src = "${flakeRoot}/assets/traveldroid/theming/boot"; installPhase = '' mkdir -p $out # GRUB expects this exact name cp theming.txt $out/theme.txt # Assets cp background.png $out/ ''; }; in { ################################# # Bootloader (UEFI + GRUB) ################################# boot.loader = { efi = { canTouchEfiVariables = true; efiSysMountPoint = "/boot"; }; grub = { enable = true; efiSupport = true; device = "nodev"; useOSProber = true; # Keep multiple generations (fixes menu issue) configurationLimit = 10; # ✅ Fully declarative theme theme = grubTheme; }; timeout = 5; }; ################################# # Kernel / initrd ################################# boot.kernelPackages = pkgs.linuxPackages_latest; boot.kernelParams = [ "quiet" "splash" "udev.log_level=3" "rd.systemd.show_status=false" ]; boot.consoleLogLevel = 0; boot.initrd.systemd.enable = true; boot.initrd.availableKernelModules = [ "xhci_pci" "nvme" "usb_storage" "sd_mod" "rtsx_usb_sdmmc" ]; boot.kernelModules = [ "kvm-intel" ]; ################################# # Plymouth ################################# boot.plymouth = { enable = true; theme = "rings"; themePackages = [ (pkgs.adi1090x-plymouth-themes.override { selected_themes = [ "rings" ]; }) ]; }; ################################# # CPU / platform ################################# hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; } #+END_SRC * Following are the imported modules * generated/traveldroid/modules/apps ** =generated/modules/traveldroid/apps/packages.nix= This installs a list of apps #+BEGIN_SRC nix :tangle generated/modules/traveldroid/apps/packages.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, flakeRoot, ... }: let ################################# # Read package list from config file ################################# packagesConfPath = "${flakeRoot}/assets/traveldroid/conf/packages.conf"; raw = builtins.readFile packagesConfPath; rawLines = lib.splitString "\n" raw; # Guard against splitting into characters accidentally _guard = assert !(builtins.stringLength raw > 1 && builtins.length rawLines == builtins.stringLength raw); true; # Clean each line: remove CRs, comments, trim whitespace cleanLine = line: let noCR = lib.replaceStrings [ "\r" ] [ "" ] line; noInlineComment = lib.head (lib.splitString "#" noCR); in lib.strings.trim noInlineComment; # Filter out empty lines entries = builtins.filter (l: l != "") (map cleanLine rawLines); # Resolve attribute paths in pkgs resolvePkg = name: let parts = lib.splitString "." name; found = lib.attrByPath parts null pkgs; in if found == null then (throw '' packages.nix: package not found in pkgs Token : ${builtins.toJSON name} packages.conf : ${packagesConfPath} Hint : check the attribute name on search.nixos.org/packages '') else found; # Final system-wide package list packages = builtins.seq _guard (map resolvePkg entries); in { ################################# # Allow unfree packages globally ################################# nixpkgs.config = { allowUnfree = true; }; ################################# # System packages ################################# environment.systemPackages = packages; } #+END_SRC ** =generated/modules/traveldroid/apps/kitty.nix= This file sets up Kitty terminal #+BEGIN_SRC nix :tangle generated/modules/traveldroid/apps/kitty.nix :noweb tangle :mkdirp yes :eval never-html { lib, pkgs, config, ... }: let ################################# # Determine default username ################################# username = config.defaultUser or "henrov"; moduleName = "kitty"; ################################# # Paths to assets ################################# assetPath = ../../../assets/traveldroid/conf/${moduleName}; programFiles = builtins.readDir assetPath; # Convert asset files into a nix attribute set files = lib.genAttrs (builtins.attrNames programFiles) (name: { source = "${assetPath}/${name}"; }); in { ################################# # System-wide packages ################################# environment.systemPackages = [ pkgs.kitty ]; ################################# # Home Manager user configuration ################################# _module.args.hmUsers = { ${username} = { # Enable Kitty through Home Manager programs.kitty.enable = true; # Extra user-specific config snippet programs.kitty.extraConfig = '' # Include the Catppuccin-Mocha theme include themes/Catppuccin-Mocha.conf ''; # Map all asset files into ~/.config/kitty/ home.file = lib.mkMerge ( map (name: { ".config/${moduleName}/${name}" = { source = files.${name}.source; }; }) (builtins.attrNames files) ); }; }; } #+END_SRC ** =generated/modules/traveldroid/apps/starship.nix= This file sets up starship prompt #+BEGIN_SRC nix :tangle generated/modules/traveldroid/apps/starship.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, flakeRoot, ... }: let # Default username fallback username = config.defaultUser or "henrov"; # Path to the starship config in assets starshipConfSrc = "${flakeRoot}/assets/traveldroid/conf/starship.toml"; in { ################################# # Enable Starship system-wide ################################# environment.systemPackages = [ pkgs.starship ]; ################################# # Home Manager user configuration ################################# _module.args.hmUsers = { ${username} = { programs.starship = { enable = true; }; # Copy the starship.toml from assets to ~/.config/starship.toml home.file = { ".config/starship.toml" = { source = starshipConfSrc; }; }; }; }; } #+END_SRC * generated/modules/traveldroid/desktop ** =generated/modules/traveldroid/desktop/fonts.nix= This file installs and configures fonts #+BEGIN_SRC nix :tangle generated/modules/traveldroid/desktop/fonts.nix :noweb tangle :mkdirp yes :eval never-html { lib, pkgs, config, ... }: { fonts.packages = with pkgs; [ nerd-fonts.iosevka nerd-fonts.fira-code ]; } #+END_SRC ** =generated/modules/traveldroid/desktop/hyprland.nix= Setting up Hyprland #+BEGIN_SRC nix :tangle generated/modules/traveldroid/desktop/hyprland.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, flakeRoot, home-manager, inputs, ... }: let username = config.defaultUser or "henrov"; assetPath = "${flakeRoot}/assets/traveldroid/conf/hypr/"; # Read all files in the asset directory assetFiles = builtins.attrNames (builtins.readDir assetPath); # Convert files to Home Manager xdg config entries hyprFiles = lib.genAttrs assetFiles (f: { # Destination path in home directory name = ".config/hypr/${f}"; # Source file path value = { source = "${assetPath}/${f}"; }; }); # Determine Hyprland package hyprlandPkg = pkgs.hyprland or pkgs.hyprland-git or inputs.hyprland.packages.${pkgs.system}.default; in { environment.systemPackages = [ hyprlandPkg ]; _module.args.hmUsers = { ${username} = { home.packages = [ hyprlandPkg ]; # Merge all files in the asset folder into ~/.config/hypr/ home.file = lib.mkMerge hyprFiles; # Optional: Hyprland settings settings.general."col.active_border" = "0xff97cbcd 0xff89b4fa"; }; }; } #+END_SRC ** =generated/modules/traveldroid/desktop/stylix.nix= #+BEGIN_SRC nix :tangle generated/modules/traveldroid/desktop/stylix.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, flakeRoot, ... }: let # Determine the default username from host config username = config.defaultUser or "henrov"; moduleName = "stylix"; assetPath = "${flakeRoot}/assets/traveldroid/conf/"; # Read all files in the asset directory programFiles = builtins.readDir assetPath; files = lib.genAttrs (builtins.attrNames programFiles) (name: { source = "${assetPath}/${name}"; }); # Optional stylix.conf stylixConfFile = "${assetPath}/stylix.conf"; stylixConf = if builtins.pathExists stylixConfFile then builtins.readFile stylixConfFile else ""; # Cursor defaults cursorName = "phinger-cursors-light"; cursorSize = 24; in { ############################ # System-level packages ############################ environment.systemPackages = [ pkgs.feh pkgs.st ]; ############################ # Home Manager user config ############################ _module.args.hmUsers = { ${username} = { # Map all config files into ~/.config/stylix/ xdg.configFile = lib.mapAttrs' (name: value: { name = "${moduleName}/${name}"; value = { inherit (value) source; }; }) files; # Environment variables for the session home.sessionVariables = { STYLIX_CONF = "$HOME/.config/stylix/stylix.conf"; XCURSOR_THEME = cursorName; XCURSOR_SIZE = toString cursorSize; HYPRCURSOR_THEME = cursorName; HYPRCURSOR_SIZE = toString cursorSize; }; }; }; } #+END_SRC ** =generated/modules/traveldroid/desktop/waybar.nix= This file installs and configures waybar #+BEGIN_SRC nix :tangle generated/modules/traveldroid/desktop/waybar.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, flakeRoot, ... }: let username = config.defaultUser or "henrov"; moduleName = "waybar"; assetPath = "${flakeRoot}/assets/traveldroid/conf/${moduleName}"; in { ################################# # System packages ################################# environment.systemPackages = [ pkgs.waybar ]; ################################# # Home Manager integration ################################# _module.args.hmUsers = { ${username} = { # Install Waybar for the user as well (optional but recommended) home.packages = [ pkgs.waybar ]; # Place config files into ~/.config/waybar/ xdg.configFile = { "${moduleName}/config".source = "${assetPath}/config"; "${moduleName}/style.css".source = "${assetPath}/style.css"; }; }; }; } #+END_SRC ** =generated/modules/traveldroid/desktop/wayland.nix= #+BEGIN_SRC nix :tangle generated/modules/traveldroid/desktop/wayland.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, ... }: { ################################# # Core Wayland packages ################################# environment.systemPackages = with pkgs; [ wayland wl-clipboard # optional but commonly used for copy/paste ]; ################################# # Optional: enable graphics stack ################################# hardware.graphics.enable = true; ################################# # Optional session variables for Wayland ################################# environment.sessionVariables = { # Forces some apps to use Wayland NIXOS_OZONE_WL = "1"; }; } #+END_SRC ** =generated/modules/traveldroid/desktop/xdg.nix= This sets the XDG implementation #+BEGIN_SRC nix :tangle generated/modules/traveldroid/desktop/xdg.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, inputs, ... }: let ################################# # Default username fallback ################################# username = config.defaultUser or "henrov"; ################################# # Determine XDG portal package ################################# xdgPortalHyprlandPkg = pkgs.xdg-desktop-portal-hyprland or inputs.xdgPortalHyprland.packages.${pkgs.system}.default; in { ################################# # System-wide packages ################################# environment.systemPackages = [ xdgPortalHyprlandPkg ]; ################################# # Home Manager user config ################################# _module.args.hmUsers = { ${username} = { home.packages = [ xdgPortalHyprlandPkg ]; # Enable XDG portal integration for Hyprland xdg.portal = { enable = true; extraPortals = [ xdgPortalHyprlandPkg ]; config.hyprland = { "org.freedesktop.impl.portal.Screencast" = [ "hyprland" ]; }; }; }; }; } #+END_SRC * generated/modules/traveldroid/system ** =generated/modules/traveldroid/system/networking.nix= This sets the networking. #+BEGIN_SRC nix :tangle generated/modules/traveldroid/system/networking.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, ... }: { ################################# # Networking core ################################# networking = { # Let DHCP be default unless overridden elsewhere useDHCP = lib.mkDefault true; # Hostname comes from host.nix, do NOT redefine here ################################# # NetworkManager (primary stack) ################################# networkmanager = { enable = true; # Use iwd backend for WiFi wifi.backend = "iwd"; }; ################################# # iwd (WiFi daemon) ################################# wireless.iwd = { enable = true; # Allow user control via NM / CLI settings.General.EnableNetworkConfiguration = true; }; ################################# # Firewall ################################# firewall = { enable = true; # KDE Connect support allowedTCPPortRanges = [ { from = 1714; to = 1764; } ]; allowedUDPPortRanges = [ { from = 1714; to = 1764; } ]; }; }; ################################# # System packages ################################# environment.systemPackages = [ pkgs.networkmanager ]; } #+END_SRC ** =generated/modules/traveldroid/system/nix.nix= #+BEGIN_SRC nix :tangle generated/modules/traveldroid/system/nix.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, ... }: { nix.settings = { experimental-features = [ "nix-command" "flakes" ]; download-buffer-size = 536870912; # 512 MB cores = 2; max-jobs = 1; }; } #+END_SRC ** =generated/modules/traveldroid/system/login-tuigreet.nix= This sets up tuigreeter which is not fancy but imo fits the aesthetic I am aiming for #+BEGIN_SRC nix :tangle generated/modules/traveldroid/system/login-tuigreet.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, flakeRoot, ... }: let username = config.defaultUser or "henrov"; in { ################################# # Install tuigreet ################################# environment.systemPackages = [ pkgs.tuigreet ]; ################################# # Greetd (simple + reliable) ################################# services.greetd = { enable = true; settings.default_session = { user = "greeter"; command = '' ${pkgs.tuigreet}/bin/tuigreet \ --remember \ --time \ --time-format "%I:%M %p | %a • %h | %F" \ --cmd ${pkgs.hyprland}/bin/Hyprland ''; }; }; } #+END_SRC * generated/users ** =generated/users/copy_2_home.nix= This copies stuff to the user home-folder #+BEGIN_SRC nix :tangle generated/users/copy_2_home.nix :noweb tangle :mkdirp yes :eval never-html { config, pkgs, lib, flakeRoot, ... }: let username = config.users.users.defaultUser or "henrov"; homeDir = "/home/${username}"; assetPath = "${flakeRoot}/assets/copy_2_home"; in { environment.systemPackages = [ pkgs.rsync ]; systemd.services.copyAssets = { description = "Copy assets to ${username}'s home directory"; wantedBy = [ "multi-user.target" ]; # oneshot service runs once at boot serviceConfig.Type = "oneshot"; # Always use /bin/sh -c for multi-line commands serviceConfig.ExecStart = '' /bin/sh -c ' echo "Copying assets from ${assetPath} to ${homeDir} ..." if [ ! -d "${assetPath}" ]; then echo "ERROR: ${assetPath} does not exist" exit 1 fi mkdir -p "${homeDir}" chown ${username}:${username} "${homeDir}" ${pkgs.rsync}/bin/rsync -a --no-owner --no-group "${assetPath}/" "${homeDir}/" echo "Done copying assets." ' ''; }; } #+END_SRC ** =generated/users/henrov.nix= This is the default user, just search and replace henrov another name if you want to change #+BEGIN_SRC nix :tangle generated/users/henrov.nix :noweb tangle :mkdirp yes :eval never-html { lib, config, pkgs, ... }: let username = "henrov"; in { ################################# # NixOS system user ################################# users.users.${username} = { isNormalUser = true; home = "/home/${username}"; hashedPassword = "$6$S7iShgBxB.77CwmP$i0njK.2r3OL5UEvgZbmwZ0rnpZ4QyJcv8p9uCmJ4AiVPSMXkQkIwMLzyAOnJ0q8.tPLIp/7EquEIZeK8qbmgw/"; extraGroups = [ "wheel" "networkmanager" ]; }; ################################# # Home Manager user definition ################################# _module.args.hmUsers = { ${username} = { # Minimal required home.username = username; home.homeDirectory = "/home/${username}"; home.stateVersion = "26.05"; # Add user-specific packages here home.packages = [ ]; # Add user dotfiles, session variables, etc. here if needed home.file = { # Example: # ".bashrc" = { source = /path/to/bashrc; }; }; }; }; } #+END_SRC