Dr460nixed NixOS ❄️
This repository contains a framework for NixOS configuration, featuring opinionated and selected default settings. It also includes my personal NixOS setup, which may serve as inspiration for others.
While starting out with NixOS, reading other people’s flakes proved very helpful, so I’m sharing my own configuration. Those who like the “dr460nized” look of Garuda will definitely enjoy this one ☺️
Overview
This flake provides various NixOS configurations, modules, and helper utilities. Outputs are automatically built and cached via garnix. You can adapt snippets from the documentation to craft your own setup.
There is documentation available, which contains code snippets and other topics that might be useful for customization.
How does it look like?

What kind of configuration is available via code snippets?
- Multiple NixOS configurations such as
cup-dragon,dev-container,dragons-ryzen,dragons-strix,dr460nixed-base,dr460nixed-desktop, andnixos-wsl. - Root-on-ZFS and secure-boot enabled systems via Lanzaboote
- Opt-in persistence through impermanence + ZFS snapshots
- Mesh networked hosts with Tailscale
- Opt-in 2FA protection of ssh and password prompts with Duo Security
- Secrets managed via nix-sops
- Always up-to-date live images automatically built via Github actions
- Custom devshells with fully declarative pre-commit-hooks
- No password prompts when having a Yubikey connected to a device
- Easily remote-controllable machines via KDEConnect and a self-hosted Rustdesk server
- Custom bleeding-edge Mesa builds
Structure
├── compose/
│ └── cup-dragon/
├── docs/
├── garuda-nix-subsystem/
├── home-manager/
├── hosts/
├── lib/
├── maintenance/
├── nixos/
│ ├── modules/
│ └── flake-module.nix
├── overlays/
├── packages/
├── secrets/
└── users/
Credits
Inspiration for parts of this configuration came from these awesome people’s setups ❄️
- https://github.com/PedroHLC/system-setup
- https://github.com/Misterio77/nix-config
- https://github.com/isabelroses/dotfiles
Quick start
Using the installer
An installer has been created to ease the installation process by bootstrapping a basic flake with a personal host and usernames.
Information on how to build an ISO can be found on the here. The installer can also be used on any OS, that has nix available. Find more information about how to proceed here.
Creating your own configuration including sops-nix
This one is the harder way and is mostly suitable for people with a basic understanding of NixOS. Several preparations have to be made to bootstrap a working installation if not using the installer:
- A working
hardware-configuration.nixneeds to be generated for the current machine to replace mine, this includes having already partitioned disks. - The hosts
*.nixconfiguration should be adapted to suit the hardware’s needs, eg. needed kernel modules orservices.xserver.videoDriversshould be fitting - Since
sops-nixis used to handle secrets, my files need to be replaced with your own ones. Usage instructions can be found here, basically one needs to create an age public key from the host’s ed21559 SSH private key, which is then added to.sops.yamlto allow the host to decrypt secrets while booting up. A fitting age key should also be generated and placed in~/.config/sops/age/keys.txtas described in the usage instructions - this allows decrypting the secrets file to edit it. It lives insecrets/global.yamland contains the secrets and can be edited with sopssecrets/global.yaml(opens a terminal text editor). - It might be easier to supply a static password in
users.nixfor bootstrapping since no login will be possible if the secrets management isn’t properly set up yet. I had a few issues with this in the past while setting things up, so I felt giving this advice might help. Usernames are of course also to be changed, as well as SSH public keys.
Then, the bootstrapping process can be started. Here, nix + nixos-install-tools is sufficient to set up your our configuration as follows:
export NIX_CONFIG="experimental-features = nix-command flakes" # if flakes are disabled
nixos-install --flake .#hostname
If the operation succeeds, you will be able to boot into your new installation.
How to proceed from here?
- Adapt the configurations like enabled modules and home-manager configs to your needs
- Set up CI to build your custom system configurations
- Enable secure boot via Lanzaboote
- Add your hosts to Tailscale, if you want to be using it. I can warmly recommend it for connecting with any kind of host!
- Build an ISO to play around with
nix run .#iso - … so much more. It never ends ❄️
Generating images
Images of this flake may easily be built by using nixos-generators.
In our case, we use it as NixOS module to be able to build the system via garuda-nix.lib.garudaSystem.
This is important, as this function exposes all the garuda-nix-subsystem modules for us to consume.
How to get going?
Let’s build a regular dr460nixed ISO!
nix build .#nixosConfigurations.dr460nixed-desktop.config.formats.install-iso
You may also just run nix run .#iso, which builds an image using the install-iso format.
This is pretty much everything needed! The result will be available at ./result, which links to the corresponding path in /nix/store.
Likewise, all other configurations may be built by simply exchanging installer-iso with the desired format.
Depending on what kind of image is being built it is needed to use the dr460nixed-base system to build the image.
nix build .#nixosConfigurations.dr460nixed-base.config.formats.sdcard
For a list of other supported formats please have a look here.
Cross-compiling
It is also possible to build images for other architectures, eg. the Raspberry Pi. To allow cross-compilation, please have a look at how to set up the required options here.
TLDR: add the following to your configuration and apply it:
{
# Enable binfmt emulation of aarch64-linux.
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
}
Provided images
Prebuilt images used to be provided via GitHub actions, though lately builds are seemingly hitting the resource limits of the free GitHub actions VMs. Therefore, ISO builds are currently only provided with new releases. In order to work around the filze size limit of release attachments, ISO files need to need to be downloaded as segments. To join them to a full file, you can do the following:
cat dr460nixed-desktop.iso.part-* > dr460nixed-desktop.iso
The installer
This flake features an installer, which may be used to set up a basic dr460nixed NixOS installation. Both the dr460nixed ISO and regular NixOS live CDs are supported.
The installer uses the files of the templates folder to run nix init -t from and proceeds to customize the installation with users’ choices.
Multiple disk formats are available via pre-configured disko configurations.
Choices during the execution of the script currently include:
- the disk to install NixOS on
- the partition layout to use during the process
- hostname
- username
The installer may only install by wiping the destination disk, custom partition layouts are currently unsupported. Both UEFI and legacy systems are supported. Nix command and flakes need to be enabled.
To begin, simply run the installer:
sudo installer # use this if booted into a dr460nixed ISO
sudo nix run github:dr460nf1r3/dr460nixed#installer # regular NixOS systems
Provide the needed input. After completion, a dr460nixed system is ready for you to use. You may customize it with configurations found in the main repository since the template has been kept as generic as possible.
Users are expected to continue building their own flake after the installation is finished. To do so, the dr460nixed repository has many exemplary configurations available. They may be inspected by browsing the “modules” section of this documentation.
Script
This is a small script which is serving as installer for this project. It basically:
- Sets up an environment to install the system from
- Asks for basic information such as the disk to be used or the username
- Sets up the partition layout via disko and mounting the partitions to
/mnt - Provides a basic, quite generic dr460nixed template and customizes it based on previous choices
- Executes the installation
#!/usr/bin/env bash
set -eo pipefail
# Check for root rights
if [ "$EUID" -ne 0 ]; then
echo "I can only run as root!"
exit 3
fi
# Prepare our environment
prepare() {
# Clone dr460nixed repo if it is not present, otherwise use current dir
if [ ! "$(test -f flake.nix)" ]; then
test -d /tmp/dr460nixed && sudo rm -rf /tmp/dr460nixed
WORK_DIR=/tmp/dr460nixed
git clone https://github.com/dr460nf1r3/dr460nixed.git "$WORK_DIR"
cd "$WORK_DIR"
else
WORK_DIR=$(pwd)
fi
# Ensure needed "experimental" features are always enabled
export NIX_CONFIG="experimental-features = nix-command flakes"
}
# Confirmation prompt
confirm_choices() {
# Continue if the user confirms our choice
read -rp "Are you sure you want to continue? $1 [y/n] " _ANSWER
while [ -z "${KILLIT+x}" ]; do
case "${_ANSWER}" in
y | yes | Y | YES)
KILLIT=1
;;
n | no | N | NO)
exit 1
;;
*)
read -rp "Invalid input. Only yes or no is valid: " _ANSWER
;;
esac
done
}
# Create a runner for disko, directly from git
disko_runner() {
nix run github:nix-community/disko -- --mode disko "$1" --arg disks "[ \"$2\" ]"
}
# Create partitions using disko and mount them to /mnt
disko() {
echo "The following partition layouts are available:
1) BTRFS with subvolumes
2) BTRFS on LUKS with subvolumes
3) Simple EFI
4) ZFS (recommended)
5) ZFS encrypted"
read -rp "Enter the number of the partition layout you want to use: " _LAYOUT
# https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash
while [ -z "${DISKO_MODULE+x}" ]; do
case "${_LAYOUT}" in
1)
DISKO_MODULE=btrfs-subvolumes
;;
2)
DISKO_MODULE=luks-btrfs-subvolumes
;;
3)
DISKO_MODULE=simple-efi
;;
4)
DISKO_MODULE=zfs-encrypted
;;
5)
DISKO_MODULE=zfs
;;
*)
read -rp "Invalid input. Enter the number of the partition layout you want to use: " _LAYOUT
;;
esac
done
while [ -z "${_VALID_DISK+x}" ]; do
read -rp 'Specify the disk you want to use, eg. "nvme0n1": ' DISK
# Disk identifiers should at least be 3 characters long and be present in our system
# this does not prevent all possible errors (eg. nvme0 would be valid) but good enough for now
if [ ${#DISK} -gt 2 ] && lsblk | grep "$DISK"; then
_VALID_DISK=1
else
echo "The disk you entered is invalid! Try again."
fi
done
# Ask whether the hard drive should really be wiped
echo "The disk you chose to format is $DISK."
confirm_choices "This will start the wiping process!"
# Create partitions and set up /mnt
disko_runner ./nixos/modules/disko/"$DISKO_MODULE".nix /dev/"$DISK"
}
# Create initial configuration
create_config() {
NIX_ROOT=/mnt/etc/nixos
read -rp "Enter the hostname you want to use: " HOSTNAME
read -rp "Enter your desired username: " USER
[ -d /sys/firmware/efi ] && SYSTEM_TYPE=systemd-boot || SYSTEM_TYPE=grub
# Create config without filesystems as disko provides those already
# also apply our dr460nixed template
nixos-generate-config --no-filesystems --root /mnt
pushd "$NIX_ROOT" || exit 2
nix flake init --template "$WORK_DIR"#dr460nixed
mv ./hardware-configuration.nix ./nixos/example-host
rm ./configuration.nix # we are using flakes, no need for that anymore
mv ./nixos/example-host ./nixos/"$HOSTNAME"
mv ./nixos/"$HOSTNAME"/example-host.nix ./nixos/"$HOSTNAME"/"$HOSTNAME".nix
sed -i s/example-boot/"$SYSTEM_TYPE"/g ./nixos/"$HOSTNAME"/"$HOSTNAME".nix
sed -i s/example-disk/"$DISK"/g .{/nixos/flake-module.nix,nixos/"$HOSTNAME"/"$HOSTNAME".nix}
sed -i s/example-hostname/"$HOSTNAME"/g {./nixos/flake-module.nix,nixos/"$HOSTNAME"/"$HOSTNAME".nix}
sed -i s/example-layout/"$DISKO_MODULE"/g ./nixos/flake-module.nix
sed -i s/example-user/"$USER"/g ./nixos/modules/users.nix
echo "Configuration successfully created.
You made the following choices:
hostname: $HOSTNAME
user: $USER"
confirm_choices "This starts the installation process."
popd || exit 2
}
# Install basic dr460nixed system
install_system() {
nixos-install --flake "$NIX_ROOT#$HOSTNAME" --verbose
}
# Notices
finish() {
echo "The installation finished successfully. You may now reboot into your new system."
confirm_choices "This will remove the temporary directory an reboot the system."
umount -Rf /mnt
rm -rf "$WORK_DIR"
}
# Actually execute our functions
prepare
disko
create_config
install_system
finish
Apps
{{#include ../../../nixos/modules/apps/cli.nix}}
Auto-upgrade
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.auto-upgrade;
in
{
options.dr460nixed.auto-upgrade = {
enable = lib.mkEnableOption "automatic system upgrades";
};
config = lib.mkIf cfg.enable {
system.autoUpgrade = {
allowReboot = true;
dates = "04:00";
enable = true;
flake = "github:dr460nf1r3/dr460nixed";
randomizedDelaySec = "1h";
rebootWindow = {
lower = "00:00";
upper = "06:00";
};
};
};
}
Boot
{
config,
lib,
pkgs,
...
}:
let
cfgLanza = config.dr460nixed.lanzaboote;
in
{
imports = [
./common.nix
./grub.nix
./lanzaboote.nix
./systemd-boot.nix
];
options.dr460nixed = with lib; {
grub = {
enable = mkOption {
default = false;
type = types.bool;
description = mdDoc "Configures the system to install GRUB to a particular device.";
};
enableCryptodisk = mkOption {
default = false;
type = types.bool;
description = mdDoc "Whether to enable GRUB cryptodisk support.";
};
device = mkOption {
default = "nodev";
type = types.str;
description = mdDoc "Defines which device to install GRUB to.";
};
};
systemd-boot = {
enable = mkOption {
default = false;
type = types.bool;
description = mdDoc "Configures common options for a quiet systemd-boot.";
};
};
lanzaboote = {
enable = mkOption {
default = false;
type = types.bool;
description = mdDoc "Configures common options using Lanzaboote as secure boot manager.";
};
};
};
config = {
environment.systemPackages = lib.mkIf cfgLanza.enable [ pkgs.sbctl ];
};
}
Chromium
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.chromium;
in
{
options.dr460nixed.chromium = {
enable = lib.mkEnableOption "Chromium with a set of default extensions and settings";
};
config = lib.mkIf cfg.enable {
programs.chromium = {
defaultSearchProviderEnabled = true;
defaultSearchProviderSearchURL = "https://searx.garudalinux.org/search?q=%s";
defaultSearchProviderSuggestURL = "https://searx.garudalinux.org/autocomplete?q=%s";
enable = true;
enablePlasmaBrowserIntegration = true;
extensions = [
"bkkmolkhemgaeaeggcmfbghljjjoofoh" # Catppuccin theme
"clngdbkpkpeebahjckkjfobafhncgmne" # Stylus
"doojmbjmlfjjnbmnoijecmcbfeoakpjm" # NoScript
"hlepfoohegkhhmjieoechaddaejaokhf" # Github Refined
"kbfnbcaeplbcioakkpcpgfkobkghlhen" # Grammarly
"mdjildafknihdffpkfmmpnpoiajfjnjd" # Consent-O-Matic
"mnjggcdmjocbbbhaepdhchncahnbgone" # SponsorBlock
"nngceckbapebfimnlniiiahkandclblb" # Bitwarden
];
extraOpts = {
"QuicAllowed" = true;
"RestoreOnStartup" = true;
"ShowHomeButton" = true;
};
};
security.chromiumSuidSandbox.enable = true;
};
}
Common
{
config,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed;
in
{
options.dr460nixed = with lib; {
common = {
enable = mkEnableOption "Whether to enable common system configurations." // {
default = true;
};
};
nodocs = mkOption {
default = true;
type = types.bool;
description = mdDoc ''
Whether to disable the documentation.
'';
};
};
config = lib.mkIf cfg.common.enable {
nixpkgs.config.allowUnfree = true;
# A few kernel tweaks
boot.kernelParams = [ "noresume" ];
# Disable unprivileged user namespaces, unless containers are enabled
security = {
# User namespaces are required for sandboxing
allowUserNamespaces = true;
# This is only required for containers
unprivilegedUsernsClone = config.virtualisation.containers.enable;
# Force-enable the Page Table Isolation (PTI) Linux kernel feature
forcePageTableIsolation = true;
};
# Allow wheel group users to use sudo
security.sudo.execWheelOnly = true;
# This is the default sops file that will be used for all secrets
sops = {
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
defaultSopsFile = ../../../secrets/global.yaml;
};
# Theming
garuda.catppuccin.enable = true;
# Increase open file limit for sudoers
security.pam.loginLimits = [
{
domain = "@wheel";
item = "nofile";
type = "soft";
value = "524288";
}
{
domain = "@wheel";
item = "nofile";
type = "hard";
value = "1048576";
}
];
# Always needed applications
programs = {
git = {
enable = true;
lfs.enable = true;
};
# The GnuPG agent
gnupg.agent = {
enable = true;
pinentryPackage = lib.mkForce pkgs.pinentry-curses;
};
};
# https://gitlab.com/ananicy-cpp/ananicy-cpp/-/issues/40#note_1986279383
systemd.services.ananicy-cpp = {
serviceConfig = {
Delegate-cpu = "cpuset io memory pids";
ExecStartPre = "${pkgs.coreutils}/bin/sleep 30";
};
};
# Who needs documentation when there is the internet? #bl04t3d
documentation = lib.mkIf cfg.nodocs {
dev.enable = false;
doc.enable = false;
enable = true;
info.enable = false;
man.enable = true;
nixos.enable = false;
};
# Enable all hardware drivers
hardware.enableRedistributableFirmware = true;
# No need for that in real NixOS systems
garuda.garuda-nix-manager.enable = false;
# Custom label for boot menu entries (otherwise set to "garuda-nix-subsystem")
system.nixos.label = lib.mkForce (
builtins.concatStringsSep "-" [ "dr460nixed-" ] + config.system.nixos.version
);
};
}
Compose runner
{
lib,
pkgs,
config,
...
}:
let
cfg = config.dr460nixed.compose-runner;
in
{
options.dr460nixed.compose-runner = lib.mkOption (
with lib;
{
type = types.attrsOf (
types.submodule {
options = {
source = mkOption {
default = null;
description = "Folder containing a compose file.";
type = types.path;
};
envfile = mkOption {
default = null;
description = "Direct path to a valid .env file";
type = types.nullOr types.path;
};
};
}
);
default = { };
}
);
config = {
systemd.services = lib.mapAttrs' (
name: value:
lib.nameValuePair ("compose-runner-" + name) (
let
output = derivation {
builder = pkgs.writeShellScript "build" ''
PATH="${pkgs.rsync}/bin:${pkgs.coreutils}/bin:${pkgs.gnused}/bin"
set -e
mkdir "$out"
sed -r 's/(^\s+restart:\s*)(unless-stopped|always)(\s*($|#))/\1on-failure\3/g' "$src/compose.yml" > "$out/compose.yml"
rsync -a "$src/" "$out"
'';
name = "compose-runner-" + name;
src = value.source;
inherit (pkgs.hostPlatform) system;
};
statepath = "/var/compose-runner/${name}";
in
{
description = "Compose runner for ${name}";
path = with pkgs; [
rsync
docker-compose
podman
bash
];
serviceConfig = {
ExecStart = pkgs.writeShellScript ("execstart-compose-runner-" + name) ''
set -e
mkdir -p "${statepath}"
rsync -a --no-owner --size-only "${output}/" "${statepath}"
${lib.optionalString (value.envfile != null) ''
cp "${value.envfile}" "${statepath}/.env"
chmod 600 "${statepath}/.env"
''}
cd "${statepath}"
docker-compose up
'';
ExecStopPost = pkgs.writeShellScript ("execstop-compose-runner-" + name) ''
set -e
cd "${statepath}"
docker-compose down
'';
};
unitConfig = {
After = "docker.socket";
Requisite = "docker.socket";
StopPropagatedFrom = "docker.socket";
};
wantedBy = [ "multi-user.target" ];
}
)
) cfg;
environment.systemPackages = lib.mkIf (cfg != { }) [ pkgs.docker-compose ];
virtualisation.docker.enable = lib.mkIf (cfg != { }) true;
};
}
Desktops
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.desktops;
in
{
imports = [
./kde.nix
./spicetify.nix
./desktop-apps.nix
];
options.dr460nixed.desktops = with lib; {
enable = mkOption {
default = false;
type = types.bool;
description = mdDoc "Whether to enable basic dr460nized desktop theming.";
};
kde = mkOption {
default = false;
type = types.bool;
description = mdDoc "Enable KDE Plasma desktop";
};
spicetify = mkOption {
default = false;
type = types.bool;
description = mdDoc "Enable Spicetify (Spotify theming)";
};
};
config = lib.mkIf cfg.enable {
dr460nixed.desktops = {
kde = true;
spicetify = true;
};
};
}
Dev Container
{
config,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed.dev-container;
in
{
options.dr460nixed.dev-container = {
enable = lib.mkEnableOption "development container configuration";
user = lib.mkOption {
type = lib.types.str;
default = "nixos";
description = lib.mdDoc "The user to create in the dev container.";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
chromium
jetbrains.webstorm
nodejs_latest
];
services = {
desktopManager.plasma6.enable = true;
displayManager.plasma-login-manager.enable = true;
xserver.enable = true;
};
nixpkgs.config.allowUnfree = true;
users.users.${cfg.user} = {
isNormalUser = true;
extraGroups = [ "wheel" ];
password = "password";
};
services.xrdp.enable = true;
services.xrdp.defaultWindowManager = "startplasma-x11";
services.xrdp.openFirewall = true;
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if (
subject.isInGroup("users")
&& (
action.id == "org.freedesktop.login1.reboot" ||
action.id == "org.freedesktop.login1.reboot-multiple-sessions" ||
action.id == "org.freedesktop.login1.power-off" ||
action.id == "org.freedesktop.login1.power-off-multiple-sessions"
)
)
{
return polkit.Result.YES;
}
});
'';
programs.ssh.setXAuthLocation = lib.mkForce true;
system.stateVersion = "26.05";
};
}
Deterministic ids
# https://github.com/oddlama/nix-config/blob/main/modules/system/deteministic-ids.nix
{
lib,
config,
...
}:
let
inherit (lib)
concatLists
flip
mapAttrsToList
mdDoc
mkDefault
mkIf
mkOption
types
;
cfg = config.users.deterministicIds;
in
{
options = {
users.deterministicIds = mkOption {
default = { };
description = mdDoc ''
Maps a user or group name to its expected uid/gid values. If a user/group is
used on the system without specifying a uid/gid, this module will assign the
corresponding ids defined here, or show an error if the definition is missing.
'';
type = types.attrsOf (
types.submodule {
options = {
uid = mkOption {
type = types.nullOr types.int;
default = null;
description = mdDoc "The uid to assign if it is missing in `users.users.<name>`.";
};
gid = mkOption {
type = types.nullOr types.int;
default = null;
description = mdDoc "The gid to assign if it is missing in `users.groups.<name>`.";
};
};
}
);
};
users.users = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
config.uid =
let
deterministicUid = cfg.${name}.uid or null;
in
mkIf (deterministicUid != null) (mkDefault deterministicUid);
}
)
);
};
users.groups = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
config.gid =
let
deterministicGid = cfg.${name}.gid or null;
in
mkIf (deterministicGid != null) (mkDefault deterministicGid);
}
)
);
};
};
config = {
assertions =
concatLists (
flip mapAttrsToList config.users.users (
name: user: [
{
assertion = user.uid != null;
message = "non-deterministic uid detected for '${name}', please assign one via `users.deterministicIds`";
}
{
assertion = !user.autoSubUidGidRange;
message = "non-deterministic subUids/subGids detected for: ${name}";
}
]
)
)
++ flip mapAttrsToList config.users.groups (
name: group: {
assertion = group.gid != null;
message = "non-deterministic gid detected for '${name}', please assign one via `users.deterministicIds`";
}
);
};
}
Development
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.development;
in
{
imports = [
./docker.nix
./podman.nix
./vms.nix
./tools.nix
./jetbrains.nix
];
options.dr460nixed.development = with lib; {
enable = mkOption {
default = false;
type = types.bool;
description = mdDoc ''
Enables commonly used development tools.
'';
};
docker = mkOption {
default = false;
type = types.bool;
description = mdDoc "Enable Docker and containers";
};
podman = mkOption {
default = false;
type = types.bool;
description = mdDoc "Enable Podman and Quadlet containers";
};
vms = mkOption {
default = false;
type = types.bool;
description = mdDoc "Enable VM support (VirtualBox, KVM)";
};
tools = mkOption {
default = false;
type = types.bool;
description = mdDoc "Enable development tools";
};
jetbrains = mkOption {
default = false;
type = types.bool;
description = mdDoc "Enable JetBrains IDEs and Android Studio";
};
user = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc "The user to configure development tools for.";
};
};
config = lib.mkIf cfg.enable {
dr460nixed.development = {
podman = true;
tools = true;
vms = true;
jetbrains = true;
};
};
}
Disko
{
lib,
disks ? [ "/dev/nvme0n1" ],
...
}:
{
disko.devices = {
disk = {
nvme1 = {
device = builtins.elemAt disks 0;
content = {
partitions = {
ESP = {
content = {
format = "vfat";
mountpoint = lib.mkDefault "/boot";
type = "filesystem";
};
size = "1024M";
type = "EF00";
};
zfs = {
size = "100%";
content = {
pool = "zroot";
type = "zfs";
};
};
};
type = "gpt";
};
type = "disk";
};
};
zpool = {
zroot = {
type = "zpool";
rootFsOptions = {
"com.sun:auto-snapshot" = "false";
acltype = "posixacl";
atime = "off";
canmount = "off";
compression = "zstd";
dedup = "on";
devices = "off";
encryption = "aes-256-gcm";
keyformat = "passphrase";
keylocation = "prompt";
mountpoint = "none";
xattr = "sa";
};
postCreateHook = ''
zfs set keylocation=prompt zroot;
'';
datasets = {
"data" = {
options.mountpoint = "none";
type = "zfs_fs";
};
"ROOT" = {
options.mountpoint = "none";
type = "zfs_fs";
};
"ROOT/empty" = {
mountpoint = "/";
options.mountpoint = "legacy";
postCreateHook = ''
zfs snapshot zroot/ROOT/empty@start
'';
type = "zfs_fs";
};
"ROOT/nix" = {
mountpoint = "/nix";
options.mountpoint = "legacy";
type = "zfs_fs";
};
"ROOT/residues" = {
mountpoint = "/var/residues";
options.mountpoint = "legacy";
type = "zfs_fs";
};
"data/persistent" = {
mountpoint = "/var/persistent";
options.mountpoint = "legacy";
type = "zfs_fs";
};
"reserved" = {
options = {
mountpoint = "none";
reservation = "10G";
};
type = "zfs_fs";
};
};
};
};
};
# Needed for impermanence
fileSystems."/var/persistent".neededForBoot = true;
fileSystems."/var/residues".neededForBoot = true;
}
Gaming
{
config,
dragonLib,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed.gaming;
in
{
options.dr460nixed.gaming = with lib; {
enable = mkOption {
default = false;
type = types.bool;
description = mdDoc ''
Whether this device is used for gaming.
'';
};
};
config = lib.mkIf cfg.enable {
programs.gamemode.enable = true;
programs.steam = {
enable = true;
extraCompatPackages = with pkgs; [
proton-cachyos-x86_64_v4
];
};
environment.systemPackages = with pkgs; [
lutris
prismlauncher
(dragonLib.GPUOffloadApp config pkgs prismlauncher "org.prismlauncher.PrismLauncher")
(dragonLib.GPUOffloadApp config pkgs steam "steam")
];
drivers.mesa-git = {
enable = true;
cacheCleanup = {
enable = true;
protonPackage = pkgs.proton-cachyos-x86_64_v4;
};
steamOrphanCleanup = {
enable = true;
};
};
};
}
Hardening
{
config,
# inputs,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed.hardening;
cfgServers = config.dr460nixed.servers.enable;
in
{
options.dr460nixed.hardening = with lib; {
enable = mkOption {
default = true;
example = false;
type = types.bool;
description = mdDoc ''
Whether the operating system should be hardened.
'';
};
};
config = lib.mkIf cfg.enable {
# Disable some of it
# nm-overrides = {
# compatibility = {
# binfmt-misc.enable = true;
# ip-forward.enable = true;
# };
# desktop = {
# allow-multilib.enable = true;
# allow-unprivileged-userns.enable = true;
# home-exec.enable = true;
# tmp-exec.enable = true;
# usbguard-allow-at-boot.enable = true;
# };
# performance = {
# allow-smt.enable = true;
# };
# security = {
# tcp-timestamp-disable.enable = true;
# disable-intelme-kmodules.enable = true;
# };
# };
boot.blacklistedKernelModules = [
# Obscure network protocols
"ax25"
"netrom"
"rose"
# Old or rare or insufficiently audited filesystems
"adfs"
"affs"
"befs"
"bfs"
"btusb"
"cifs"
"cramfs"
"cramfs"
"efs"
"erofs"
"exofs"
"f2fs"
"freevxfs"
"freevxfs"
"gfs2"
"hfs"
"hfsplus"
"hpfs"
"jffs2"
"jfs"
"ksmbd"
"minix"
"nfs"
"nfsv3"
"nfsv4"
"nilfs2"
"omfs"
"qnx4"
"qnx6"
"sysv"
"udf"
"vivid"
];
# Disable root login & password authentication on sshd
# also, apply recommendations of ssh-audit.com
services.openssh = {
extraConfig = ''
AllowTcpForwarding no
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com
PermitTunnel no
'';
settings = {
Ciphers = [
"aes128-ctr"
"aes128-gcm@openssh.com"
"aes256-ctr,aes192-ctr"
"aes256-gcm@openssh.com"
];
KbdInteractiveAuthentication = false;
KexAlgorithms = [
"curve25519-sha256"
"curve25519-sha256@libssh.org"
"diffie-hellman-group16-sha512"
"diffie-hellman-group18-sha512"
"sntrup761x25519-sha512@openssh.com"
];
Macs = [
"hmac-sha2-256-etm@openssh.com"
"hmac-sha2-512"
"hmac-sha2-512-etm@openssh.com"
"umac-128-etm@openssh.com"
];
PasswordAuthentication = false;
PermitRootLogin = "no";
X11Forwarding = false;
};
};
# Client side SSH configuration
programs.ssh = {
ciphers = [
"aes256-gcm@openssh.com"
"aes256-ctr,aes192-ctr"
"aes128-ctr"
"aes128-gcm@openssh.com"
"chacha20-poly1305@openssh.com"
];
hostKeyAlgorithms = [
"ssh-ed25519"
"ssh-ed25519-cert-v01@openssh.com"
"sk-ssh-ed25519@openssh.com"
"sk-ssh-ed25519-cert-v01@openssh.com"
"rsa-sha2-512"
"rsa-sha2-512-cert-v01@openssh.com"
"rsa-sha2-256"
"rsa-sha2-256-cert-v01@openssh.com"
];
kexAlgorithms = [
"curve25519-sha256"
"curve25519-sha256@libssh.org"
"diffie-hellman-group16-sha512"
"diffie-hellman-group18-sha512"
"sntrup761x25519-sha512@openssh.com"
];
knownHosts = {
aur-rsa = {
hostNames = [ "aur.archlinux.org" ];
publicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKF9vAFWdgm9Bi8uc+tYRBmXASBb5cB5iZsB7LOWWFeBrLp3r14w0/9S2vozjgqY5sJLDPONWoTTaVTbhe3vwO8CBKZTEt1AcWxuXNlRnk9FliR1/eNB9uz/7y1R0+c1Md+P98AJJSJWKN12nqIDIhjl2S1vOUvm7FNY43fU2knIhEbHybhwWeg+0wxpKwcAd/JeL5i92Uv03MYftOToUijd1pqyVFdJvQFhqD4v3M157jxS5FTOBrccAEjT+zYmFyD8WvKUa9vUclRddNllmBJdy4NyLB8SvVZULUPrP3QOlmzemeKracTlVOUG1wsDbxknF1BwSCU7CmU6UFP90kpWIyz66bP0bl67QAvlIc52Yix7pKJPbw85+zykvnfl2mdROsaT8p8R9nwCdFsBc9IiD0NhPEHcyHRwB8fokXTajk2QnGhL+zP5KnkmXnyQYOCUYo3EKMXIlVOVbPDgRYYT/XqvBuzq5S9rrU70KoI/S5lDnFfx/+lPLdtcnnEPk=";
};
aur-ed25519 = {
hostNames = [ "aur.archlinux.org" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN";
};
github-rsa = {
hostNames = [ "github.com" ];
publicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=";
};
github-ed25519 = {
hostNames = [ "github.com" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl";
};
gitlab-rsa = {
hostNames = [ "gitlab.com" ];
publicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9";
};
gitlab-ed25519 = {
hostNames = [ "gitlab.com" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf";
};
};
macs = [
"hmac-sha2-512-etm@openssh.com"
"hmac-sha2-256-etm@openssh.com"
"umac-128-etm@openssh.com"
];
};
# Timeout TTY after 1 hour
programs.bash.interactiveShellInit = "if [[ $(tty) =~ /dev\\/tty[1-6] ]]; then TMOUT=3600; fi";
# Don't lock kernel modules, this is also enabled by the hardening profile by default
security.lockKernelModules = false;
# Run security analysis
environment.systemPackages = with pkgs; [ lynis ];
# Technically we don't need this as we use pubkey authentication
services.fail2ban = lib.mkIf cfgServers {
enable = true;
ignoreIP = [
"100.0.0.0/8"
"127.0.0.1/8"
];
};
};
}
Impermanence
{
lib,
config,
options,
...
}:
let
# System-level persistent directories
systemEtcDirs = [
"asusd"
"NetworkManager/system-connections"
"nixos"
"pacman.d/gnupg"
];
systemVarCacheDirs = [ ];
systemVarLibDirs = [
"AccountsService/icons"
"machines"
"NetworkManager"
"sbctl"
"plasmalogin"
"systemd"
"nixos"
"upower"
];
systemVarLibDirsWithPerms = [
{
directory = "iwd";
mode = "u=rwx,g=,o=";
}
];
# Root user persistence
rootDirs = [
{
directory = ".gnupg";
mode = "0700";
}
{
directory = ".ssh";
mode = "0700";
}
];
# User important data directories
userDataDirs = [
".cargo"
".claude"
".config"
".java"
".local/share/direnv"
".local/share/fish"
".local/state"
".pki"
".tldrc"
".wakatime"
"Documents"
"Downloads"
"Music"
"Pictures"
"Projects"
"Sync"
"Videos"
];
# User cache directories to persist (important for IDE caches, etc.)
userCacheDirs = [
".cache/bat"
".cache/.bun"
".cache/bookmarksrunner"
".cache/electron"
".cache/fastfetch"
".cache/github-copilot"
".cache/mesa_shader_cache"
".cache/nix"
".cache/pnpm"
".cache/systemsettings"
".cache/tldr"
];
# User state directories
userStateDirs = [
".local/share/icons/distrobox"
".local/state/syncthing"
".local/state/wireplumber"
];
# User files to persist
userFiles = [
".bash_history"
".wakatime.bdb"
".wakatime.cfg"
];
# User directories with special permissions
userDirsWithPerms = [
{
directory = ".gnupg";
mode = "0700";
}
{
directory = ".local/share/keyrings";
mode = "0700";
}
{
directory = ".ssh";
mode = "0700";
}
];
# Optional service directories (gated by service config)
optionalServiceDirs = [
{
condition = config.security.acme.acceptTerms;
dirs = [
{
directory = "/var/lib/acme";
user = "acme";
group = "acme";
mode = "0755";
}
];
}
{
condition = config.services.printing.enable;
dirs = [
{
directory = "/var/lib/cups";
user = "root";
group = "root";
mode = "0700";
}
];
}
{
condition = config.services.fail2ban.enable;
dirs = [
{
directory = "/var/lib/fail2ban";
user = "fail2ban";
group = "fail2ban";
mode = "0750";
}
];
}
{
condition = config.services.postgresql.enable;
dirs = [
{
directory = "/var/lib/postgresql";
user = "postgres";
group = "postgres";
mode = "0700";
}
];
}
{
condition = config.services.loki.enable;
dirs = [
{
directory = "/var/lib/loki";
user = "loki";
group = "loki";
mode = "0700";
}
];
}
{
condition = config.services.grafana.enable;
dirs = [
{
directory = config.services.grafana.dataDir;
user = "grafana";
group = "grafana";
mode = "0700";
}
];
}
{
condition = config.services.vaultwarden.enable;
dirs = [
{
directory = "/var/lib/vaultwarden";
user = "vaultwarden";
group = "vaultwarden";
mode = "0700";
}
];
}
{
condition = config.services.influxdb2.enable;
dirs = [
{
directory = "/var/lib/influxdb2";
user = "influxdb2";
group = "influxdb2";
mode = "0700";
}
];
}
{
condition = config.services.telegraf.enable;
dirs = [
{
directory = "/var/lib/telegraf";
user = "telegraf";
group = "telegraf";
mode = "0700";
}
];
}
{
condition = config.services.adguardhome.enable;
dirs = [
{
directory = "/var/lib/private/AdGuardHome";
user = "root";
group = "root";
mode = "0700";
}
];
}
{
condition = config.virtualisation.docker.enable;
dirs = [
{
directory = "/var/lib/docker";
user = "root";
group = "root";
mode = "0700";
}
];
}
{
condition = config.virtualisation.podman.enable;
dirs = [ "/var/lib/containers" ];
}
{
condition = config.services.flatpak.enable;
dirs = [ "/var/lib/flatpak" ];
}
{
condition = config.services.fwupd.enable;
dirs = [ "/var/lib/fwupd" ];
}
{
condition = config.virtualisation.libvirtd.enable;
dirs = [ "/var/lib/libvirt" ];
}
{
condition = config.services.tailscale.enable;
dirs = [ "/var/lib/tailscale" ];
}
{
condition = config.services.tailscale.enable;
dirs = [ "/var/cache/tailscale" ];
}
{
condition = config.hardware.bluetooth.enable;
dirs = [ "/var/lib/bluetooth" ];
}
{
condition = config.services.vnstat.enable;
dirs = [ "/var/lib/vnstat" ];
}
];
# Optional user data directories (gated by config)
optionalUserDataDirs = [
{
condition = config.virtualisation.podman.enable;
dirs = [ ".local/share/containers" ];
}
{
condition = config.dr460nixed.development.jetbrains or false;
dirs = [
".local/share/DBeaverData"
".local/share/JetBrains"
".eclipse"
];
}
{
condition = config.dr460nixed.gaming.enable or false;
dirs = [
".local/share/PrismLauncher"
".local/share/Steam"
".local/share/lutris"
".steam"
"Games"
];
}
{
condition = config.dr460nixed.desktops.kde or false;
dirs = [
".local/share/baloo"
".local/share/dolphin"
".local/share/kactivitymanagerd"
".local/share/klipper"
".local/share/knewstuff3"
".local/share/konsole"
".local/share/kpeoplevcard"
".local/share/kscreen"
".local/share/kwalletd"
".local/share/plasma"
".local/share/plasma-systemmonitor"
];
}
{
condition = config.dr460nixed.development.tools or false;
dirs = [
".ansible"
".gemini"
".local/share/heroku"
".local/share/opencode"
".local/share/pnpm"
".local/share/yarn"
".local/share/zed"
".vscode"
];
}
{
condition = config.dr460nixed.development.vms or false;
dirs = [ "VirtualBox VMs" ];
}
{
condition = config.dr460nixed.desktops.enable or false;
dirs = [
".firedragon"
".gitkraken"
".local/share/AyuGramDesktop"
".local/share/Vorta"
".local/share/krita"
".mozilla"
".thunderbird"
];
}
{
condition = config.dr460nixed.sync.nextcloud or config.dr460nixed.misc.nextcloud or false;
dirs = [
".local/share/Nextcloud"
"Nextcloud"
];
}
{
condition = config.dr460nixed.development.enable or false;
dirs = [
".android"
];
}
{
condition = config.dr460nixed.yubikey.enable or false;
dirs = [
".yubico"
];
}
];
# Optional user cache directories (gated by config)
optionalUserCacheDirs = [
{
condition = config.dr460nixed.gaming.enable or false;
dirs = [ ".cache/lutris" ];
}
{
condition = config.dr460nixed.desktops.kde or false;
dirs = [ ".cache/konsole" ];
}
{
condition = config.dr460nixed.development.tools or false;
dirs = [ ".cache/distrobox" ];
}
{
condition = config.dr460nixed.desktops.enable or false;
dirs = [
".cache/BraveSoftware"
".cache/firedragon"
".cache/mozilla"
".cache/thunderbird"
];
}
{
condition = config.dr460nixed.desktops.spicetify or false;
dirs = [ ".cache/spotify" ];
}
{
condition = config.dr460nixed.development.jetbrains or false;
dirs = [ ".cache/JetBrains" ];
}
];
# Flatten optional dirs based on conditions
optionalDirs = lib.concatMap (s: if s.condition then s.dirs else [ ]) optionalServiceDirs;
optionalUserDirs = lib.concatMap (s: if s.condition then s.dirs else [ ]) optionalUserDataDirs;
optionalUserCache = lib.concatMap (s: if s.condition then s.dirs else [ ]) optionalUserCacheDirs;
cfg = config.dr460nixed.impermanence;
in
{
# Persistent files
config = lib.mkIf cfg.enable (
lib.optionalAttrs (options.environment ? persistence) {
environment.persistence."/persist" = {
hideMounts = true;
directories =
map (dir: "/etc/${dir}") systemEtcDirs
# System /var/cache directories
++ map (dir: "/var/cache/${dir}") systemVarCacheDirs
# System /var/lib directories
++ map (dir: "/var/lib/${dir}") systemVarLibDirs
# System /var/lib directories with special permissions
++ (map (entry: {
directory = "/var/lib/${entry.directory}";
user = entry.user or "root";
group = entry.group or "root";
mode = entry.mode or "0755";
}) systemVarLibDirsWithPerms)
# Optional service directories based on enabled services
++ optionalDirs
# Catch-all for /var/cache
++ [ "/var/cache" ];
users = {
"root" = {
directories = rootDirs;
};
}
// (lib.genAttrs cfg.persistentUsers (_name: {
directories =
userDataDirs
# Optional user data directories
++ optionalUserDirs
# Cache directories to persist (IDE caches, etc.)
++ userCacheDirs
# Optional user cache directories
++ optionalUserCache
# State directories
++ userStateDirs
# Directories with special permissions
++ userDirsWithPerms;
files = userFiles;
}));
};
}
);
}
Live CD
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.live-cd;
in
{
options.dr460nixed.live-cd = {
enable = lib.mkEnableOption "live CD applications and configurations";
};
config = lib.mkIf cfg.enable {
# No specific config here from misc.nix other than the option definition
# but it's used elsewhere.
};
}
Locales
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.locales;
de = "de_DE.UTF-8";
defaultLocale = "en_GB.UTF-8";
in
{
options.dr460nixed.locales = with lib; {
enable = mkEnableOption "Whether the operating system be having a default set of locales set." // {
default = true;
};
};
config = lib.mkIf cfg.enable {
time = {
hardwareClockInLocalTime = true;
timeZone = "Europe/Berlin";
};
i18n = {
inherit defaultLocale;
extraLocaleSettings = {
LANG = defaultLocale;
LC_COLLATE = defaultLocale;
LC_CTYPE = defaultLocale;
LC_MESSAGES = defaultLocale;
LC_ADDRESS = de;
LC_IDENTIFICATION = de;
LC_MEASUREMENT = de;
LC_MONETARY = de;
LC_NAME = de;
LC_NUMERIC = de;
LC_PAPER = de;
LC_TELEPHONE = de;
LC_TIME = de;
};
supportedLocales = [
"C.UTF-8/UTF-8"
"de_DE.UTF-8/UTF-8"
"en_GB.UTF-8/UTF-8"
"en_US.UTF-8/UTF-8"
];
};
# Console font
console.keyMap = "de";
};
}
Monitoring
{
config,
lib,
...
}:
let
cfg = config.dr460nixed;
in
{
options.dr460nixed = with lib; {
grafanaStack = {
enable = mkEnableOption "Enables the Grafana stack (Grafana, Prometheus and Loki).";
address = mkOption {
default = "";
type = types.str;
description = mdDoc ''
The address of the Grafana frontend.
'';
};
};
prometheus = {
adguardExporter = {
enable = mkEnableOption "Enables Prometheus' AdGuard home exporter.";
configfile = mkOption {
default = "";
type = types.str;
description = mdDoc ''
The path to the AdGuard home exporter config file.
'';
};
};
blackboxExporter = lib.mkEnableOption "Enables Prometheus' blackbox exporter.";
enable = mkEnableOption "Enables Prometheus' node_exporter.";
nginxExporter = lib.mkEnableOption "Enables Prometheus' Nginx exporter.";
};
promtail = {
enable = mkEnableOption "Enables shipping systemd journal logs to Loki.";
lokiAddress = mkOption {
default = "";
type = types.str;
description = mdDoc ''
The address of the Loki frontend.
'';
};
};
};
config = {
services.prometheus = {
port = 3020;
enable = lib.mkIf cfg.grafanaStack.enable true;
exporters = lib.mkIf cfg.prometheus.enable {
blackbox = lib.mkIf cfg.prometheus.blackboxExporter {
enable = true;
port = 9115;
configFile = "/var/lib/prometheus/blackbox.yml";
};
node = {
port = 3021;
enabledCollectors = [ "systemd" ];
enable = true;
};
nginx = lib.mkIf cfg.prometheus.nginxExporter {
enable = true;
};
};
# ingest the published nodes
scrapeConfigs = [
{
job_name = "nodes";
static_configs = [
{
targets = [
"127.0.0.1:${toString config.services.prometheus.exporters.node.port}"
"100.97.58.140:${toString config.services.prometheus.exporters.node.port}"
];
}
];
}
{
job_name = "adguard";
static_configs = [
{
targets = [
"127.0.0.1:9617"
];
}
];
}
{
job_name = "loki";
static_configs = [
{
targets = [
"${cfg.grafanaStack.address}:8030"
];
}
];
}
{
job_name = "prometheus";
static_configs = [
{
targets = [
"127.0.0.1:9113"
];
}
];
}
];
};
# Loki for system logs
services.loki = lib.mkIf cfg.grafanaStack.enable {
enable = true;
configuration = {
auth_enabled = false;
ingester = {
chunk_idle_period = "1h";
chunk_retain_period = "30s";
chunk_target_size = 999999;
lifecycler = {
address = "${cfg.grafanaStack.address}";
ring = {
kvstore = {
store = "inmemory";
};
replication_factor = 1;
};
};
max_chunk_age = "24h";
};
schema_config = {
configs = [
{
from = "2022-06-06";
store = "boltdb-shipper";
object_store = "filesystem";
schema = "v11";
index = {
prefix = "index_";
period = "24h";
};
}
];
};
server.http_listen_port = 3030;
storage_config = {
boltdb_shipper = {
active_index_directory = "/var/lib/loki/boltdb-shipper-active";
cache_location = "/var/lib/loki/boltdb-shipper-cache";
cache_ttl = "24h";
shared_store = "filesystem";
};
filesystem = {
directory = "/var/lib/loki/chunks";
};
};
limits_config = {
ingestion_burst_size_mb = 512;
ingestion_rate_mb = 1024;
reject_old_samples = true;
reject_old_samples_max_age = "168h";
};
chunk_store_config = {
max_look_back_period = "0s";
};
table_manager = {
retention_deletes_enabled = false;
retention_period = "0s";
};
compactor = {
working_directory = "/var/lib/loki";
shared_store = "filesystem";
compactor_ring = {
kvstore = {
store = "inmemory";
};
};
};
};
};
# promtail: port 3031 (8031)
#
services.promtail = lib.mkIf cfg.promtail.enable {
enable = true;
configuration = {
server = {
http_listen_port = 3031;
grpc_listen_port = 0;
};
clients = [
{
url = "http://${config.dr460nixed.promtail.lokiAddress}:3030/loki/api/v1/push";
}
];
scrape_configs = [
{
job_name = "journal";
journal = {
path = "/var/log/journal";
max_age = "24h";
labels = {
job = "systemd-journal";
host = "${config.networking.hostName}";
};
};
relabel_configs = [
{
source_labels = [ "__journal__hostname" ];
target_label = "host";
}
{
source_labels = [ "__journal_priority" ];
target_label = "priority";
}
{
source_labels = [ "__journal_priority_keyword" ];
target_label = "level";
}
{
source_labels = [ "__journal__systemd_unit" ];
target_label = "unit";
}
{
source_labels = [ "__journal__systemd_user_unit" ];
target_label = "user_unit";
}
{
source_labels = [ "__journal__boot_id" ];
target_label = "boot_id";
}
{
source_labels = [ "__journal__comm" ];
target_label = "command";
}
];
}
];
pipeline_stages = [
{
json.expressions = {
transport = "_TRANSPORT";
unit = "_SYSTEMD_UNIT";
msg = "MESSAGE";
coredump_cgroup = "COREDUMP_CGROUP";
coredump_exe = "COREDUMP_EXE";
coredump_cmdline = "COREDUMP_CMDLINE";
coredump_uid = "COREDUMP_UID";
coredump_gid = "COREDUMP_GID";
};
}
];
};
};
systemd.services.promtail.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
# Grafana on port 3010 (8010)
services.grafana = lib.mkIf cfg.grafanaStack.enable {
enable = true;
provision = {
enable = true;
datasources.settings = {
apiVersion = 1;
datasources = [
{
access = "proxy";
name = "Prometheus";
type = "prometheus";
url = "http://127.0.0.1:${toString config.services.prometheus.port}";
}
{
access = "proxy";
name = "Loki";
type = "loki";
url = "http://127.0.0.1:${toString config.services.loki.configuration.server.http_listen_port}";
}
];
};
};
settings = {
analytics.reporting_enabled = false;
live = {
allowed_origins = [ "http://${config.dr460nixed.grafanaStack.address}:8010" ]; # Needed to get WS to work
};
security.admin_email = "root@dr460nf1r3.org";
server = {
http_addr = "127.0.0.1";
http_port = 3010;
protocol = "http";
rootUrl = "http://${config.dr460nixed.grafanaStack.address}:8010";
};
};
};
# Also enable promtail on the Grafana host
dr460nixed.promtail.enable = lib.mkIf cfg.grafanaStack.enable true;
# Nginx reverse proxy
services.nginx = lib.mkIf cfg.grafanaStack.enable {
enable = true;
upstreams = {
"grafana" = {
servers = {
"127.0.0.1:${toString config.services.grafana.settings.server.http_port}" = { };
};
};
"prometheus" = {
servers = {
"${config.dr460nixed.grafanaStack.address}:${toString config.services.prometheus.port}" = { };
};
};
"loki" = {
servers = {
"127.0.0.1:${toString config.services.loki.configuration.server.http_listen_port}" = { };
};
};
"promtail" = {
servers = {
"127.0.0.1:${toString config.services.promtail.configuration.server.http_listen_port}" = { };
};
};
};
virtualHosts.grafana = {
locations."/" = {
proxyPass = "http://grafana";
extraConfig = ''
proxy_set_header Host $host;
'';
};
locations."/api/live/" = {
proxyPass = "http://grafana";
proxyWebsockets = true;
extraConfig = ''
proxy_set_header Host $host;
'';
};
listen = [
{
addr = "${config.dr460nixed.grafanaStack.address}";
port = 8010;
}
];
};
virtualHosts.prometheus = {
locations."/".proxyPass = "http://prometheus";
listen = [
{
addr = "${config.dr460nixed.grafanaStack.address}";
port = 8020;
}
];
};
virtualHosts.loki = {
locations."/".proxyPass = "http://loki";
listen = [
{
addr = "${config.dr460nixed.grafanaStack.address}";
port = 8030;
}
];
};
virtualHosts.promtail = {
locations."/".proxyPass = "http://promtail";
listen = [
{
addr = "${config.dr460nixed.grafanaStack.address}";
port = 8031;
}
];
};
};
};
}
MSMTP
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.smtp;
in
{
options.dr460nixed.smtp = with lib; {
enable = mkEnableOption "Enable sending mails via CMD using msmtp.";
user = mkOption {
type = types.str;
description = mdDoc "The SMTP user.";
};
host = mkOption {
type = types.str;
default = "mail.garudalinux.net";
description = mdDoc "The SMTP host.";
};
from = mkOption {
type = types.str;
description = mdDoc "The default from address.";
};
passwordeval = mkOption {
type = types.str;
description = mdDoc "The command to evaluate to get the password.";
};
};
config = lib.mkIf cfg.enable {
programs.msmtp = {
enable = true;
setSendmail = true;
defaults = {
aliases = "/etc/aliases";
auth = "login";
port = 465;
tls = "on";
tls_starttls = "off";
tls_trust_file = "/etc/ssl/certs/ca-certificates.crt";
};
accounts = {
default = {
inherit (cfg)
from
host
user
passwordeval
;
};
};
};
environment.etc = {
"aliases".text = ''
root: ${cfg.from}
'';
};
};
}
Networking
{
config,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed;
in
{
networking = {
nameservers = [
"1.1.1.1"
"2606:4700:4700::1111"
"1.0.0.1"
"2606:4700:4700::1001"
];
networkmanager = lib.mkIf cfg.desktops.enable {
# This is required to workaround Tailscale not recovering from net change
# https://github.com/tailscale/tailscale/issues/8223
dispatcherScripts = [
{
source = pkgs.writeScript "restartTailscaled" ''
#!/usr/bin/env ${pkgs.bash}/bin/bash
if [[ "$1" != "wlan0" ]]; then
exit 0
fi
if [[ "$2" == "up" ]]; then
if [[ $(${pkgs.iputils}/bin/ping -W 1 -c 1 garudalinux.org) != 0 ]]; then
logger "Wlan0 up, restarting tailscaled"
${pkgs.systemd}/bin/systemctl restart tailscaled
fi
fi
'';
type = "basic";
}
];
dns = lib.mkForce "none";
enable = true;
};
nftables.enable = true;
};
services = {
openssh.enable = true;
vnstat.enable = true;
};
programs.mosh.enable = true;
}
Nix
{
inputs,
pkgs,
self,
lib,
...
}:
let
corePkgs = import ../apps/core-packages.nix { inherit pkgs lib; };
in
{
config = {
nix = {
extraOptions = ''
http-connections = 0
warn-dirty = false
'';
nixPath = [ "nixpkgs=${inputs.nixpkgs}" ];
settings = {
experimental-features = [
"nix-command"
"flakes"
];
accept-flake-config = true;
keep-derivations = true;
keep-outputs = true;
keep-going = true;
log-lines = 20;
max-jobs = "auto";
sandbox = pkgs.stdenv.isLinux;
flake-registry = "/etc/nix/registry.json";
inherit (inputs.self.dragonLib.binaryCaches) substituters;
inherit (inputs.self.dragonLib.binaryCaches) trusted-public-keys;
};
};
environment = {
etc = with inputs; {
"nix/flake-channels/home-manager".source = home-manager;
"nix/flake-channels/nixpkgs".source = nixpkgs;
"nix/flake-channels/system".source = self;
"nixos/flake".source = self;
};
systemPackages = corePkgs;
};
};
}
NVIDIA
{
config,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed.nvidia;
in
{
options.dr460nixed.nvidia = with lib; {
enable = mkEnableOption "Whether to enable NVIDIA GPU support with proprietary drivers.";
open = lib.mkEnableOption "Whether to use the open-source NVIDIA kernel module (Turing+)." // {
default = true;
};
prime = {
enable = mkEnableOption "Whether to enable PRIME offload for hybrid graphics.";
nvidiaBusId = mkOption {
default = "";
type = types.str;
description = mdDoc ''
Bus ID of the NVIDIA GPU, e.g. "PCI:1:0:0".
'';
};
amdgpuBusId = mkOption {
default = null;
type = types.nullOr types.str;
description = mdDoc ''
Bus ID of the AMD iGPU for AMD+NVIDIA hybrid setups.
'';
};
intelBusId = mkOption {
default = null;
type = types.nullOr types.str;
description = mdDoc ''
Bus ID of the Intel iGPU for Intel+NVIDIA hybrid setups.
'';
};
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
boot = {
initrd.kernelModules = [
"nvidia"
"nvidia_drm"
"nvidia_modeset"
"nvidia_uvm"
];
blacklistedKernelModules = [ "nouveau" ];
};
services.xserver.videoDrivers = [ "nvidia" ];
hardware.nvidia = {
modesetting.enable = true;
inherit (cfg) open;
nvidiaSettings = true;
package = config.boot.kernelPackages.nvidiaPackages.beta;
powerManagement.enable = true;
};
services.lact.enable = true;
hardware.graphics = {
extraPackages = with pkgs; [
nvidia-vaapi-driver
];
};
}
(lib.mkIf cfg.prime.enable {
hardware.nvidia.prime = {
offload = {
enable = true;
enableOffloadCmd = true;
};
inherit (cfg.prime) nvidiaBusId;
amdgpuBusId = lib.mkIf (cfg.prime.amdgpuBusId != null) cfg.prime.amdgpuBusId;
intelBusId = lib.mkIf (cfg.prime.intelBusId != null) cfg.prime.intelBusId;
};
hardware.nvidia.powerManagement.finegrained = true;
})
]
);
}
OCI
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.oci;
in
{
options.dr460nixed.oci = with lib; {
enable = mkEnableOption "Enable common options for Oracle cloud instances.";
};
config = lib.mkIf cfg.enable {
boot = {
kernelParams = [
"nvme.shutdown_timeout=10"
"nvme_core.shutdown_timeout=10"
"libiscsi.debug_libiscsi_eh=1"
"crash_kexec_post_notifiers"
"console=tty1"
"console=ttyS0"
"console=ttyAMA0,115200"
];
loader.grub = {
device = "nodev";
efiInstallAsRemovable = true;
efiSupport = true;
enable = lib.mkForce true;
extraConfig = ''
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
terminal_input --append serial
terminal_output --append serial
'';
splashImage = null;
};
};
# https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/configuringntpservice.htm#Configuring_the_Oracle_Cloud_Infrastructure_NTP_Service_for_an_Instance
networking.timeServers = [ "169.254.169.254" ];
nix.settings.auto-optimise-store = lib.mkForce false;
hardware.cpu = {
amd.updateMicrocode = lib.mkForce false;
intel.updateMicrocode = lib.mkForce false;
};
};
}
Performance
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.performance;
in
{
options.dr460nixed.performance = {
enable = lib.mkEnableOption "performance optimizations";
};
config = lib.mkIf cfg.enable {
garuda.performance-tweaks.enable = true;
};
}
Servers
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.servers;
in
{
options.dr460nixed.servers = with lib; {
enable = mkEnableOption "Whether this device is a server.";
};
config = lib.mkIf cfg.enable {
# The common used config is not available
programs.fish.shellInit = lib.mkForce ''
set fish_greeting
fastfetch -l nixos
'';
# Automatic server upgrades
dr460nixed.auto-upgrade.enable = lib.mkDefault true;
# No custom aliases
dr460nixed.shells.enable = lib.mkDefault false;
# These aren't needed on servers, but default on GNS
garuda = {
audio.pipewire.enable = false;
hardware.enable = false;
networking.enable = false;
};
boot.plymouth.enable = false;
};
}
Shells
{
config,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed.shells;
in
{
options.dr460nixed.shells.enable =
lib.mkEnableOption "Whether the shell should receive our aliases and themes."
// {
default = true;
};
config = lib.mkIf cfg.enable {
programs = {
bash = {
shellAliases = {
"gpl" = "${pkgs.curl}/bin/curl https://www.gnu.org/licenses/gpl-3.0.txt -o LICENSE";
"grep" = "${pkgs.ugrep}/bin/ugrep";
};
};
fish = {
shellAbbrs = {
"gpl" = "${pkgs.curl}/bin/curl https://www.gnu.org/licenses/gpl-3.0.txt -o LICENSE";
};
shellAliases = {
"grep" = "${pkgs.ugrep}/bin/ugrep";
};
useBabelfish = true;
};
};
};
}
Syncthing
{
config,
dragonLib,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed.syncthing;
settingsFormat = pkgs.formats.json { };
in
{
options.dr460nixed.syncthing = {
enable = lib.mkEnableOption "Enable common file synchronisation between devices.";
key = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.str;
description = lib.mdDoc ''
The key to use for Syncthing.
'';
};
cert = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.str;
description = lib.mdDoc ''
The cert to use for Syncthing.
'';
};
devices = lib.mkOption {
default = dragonLib.syncthing.getDevicesFor config.networking.hostName;
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
freeformType = settingsFormat.type;
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
description = lib.mdDoc ''
The name of the device.
'';
};
id = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
The device ID. See <https://docs.syncthing.net/dev/device-ids.html>.
'';
};
autoAcceptFolders = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Automatically create or share folders that this device advertises at the default path.
See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>.
'';
};
};
}
)
);
description = lib.mdDoc ''
The devices to sync with.
'';
};
devicesNames = lib.mkOption {
default = lib.attrNames cfg.devices;
type = lib.types.listOf lib.types.str;
description = lib.mdDoc ''
The names of the devices to sync with.
'';
};
user = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.str;
description = lib.mdDoc ''
The user to run syncthing as.
'';
};
folders = lib.mkOption {
default = { };
type = lib.types.attrsOf (
lib.types.submodule {
options = {
id = lib.mkOption { type = lib.types.str; };
path = lib.mkOption { type = lib.types.str; };
devices = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
ignorePatterns = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
".directory"
".thumbnails"
];
};
};
}
);
description = lib.mdDoc "Folders to sync.";
};
};
config = lib.mkIf cfg.enable {
services.syncthing = {
inherit (cfg) cert;
dataDir = lib.mkIf (cfg.user != null) "/home/${cfg.user}";
enable = true;
inherit (cfg) key;
settings = {
inherit (cfg) devices;
folders = lib.mapAttrs (_name: folder: {
inherit (folder)
id
path
devices
ignorePatterns
;
}) cfg.folders;
options = {
localAnnounceEnabled = true;
urAccepted = -1;
};
};
user = lib.mkIf (cfg.user != null) cfg.user;
};
};
}
Tailscale TLS
{
config,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed.tailscale-tls;
domainExpression =
if cfg.domain-override != null then
cfg.domain-override
else
"$(${pkgs.tailscale}/bin/tailscale cert 2>&1 | grep use | cut -d '\"' -f2)";
in
{
options.dr460nixed.tailscale-tls = with lib; {
enable = mkEnableOption "Automatic Tailscale certificates renewal";
target = mkOption {
type = types.str;
description = "Where to put certificates";
default = "/var/lib/tailscale-tls";
};
mode = mkOption {
type = types.str;
description = "File mode for certificates";
default = "0640";
};
domain-override = mkOption {
type = types.nullOr types.str;
description = "Override domain. Defaults to suggested one by tailscale";
default = null;
};
};
config = lib.mkIf cfg.enable {
users.users.tailscale-tls = {
group = "tailscale-tls";
home = "/var/lib/tailscale-tls";
isSystemUser = true;
};
users.groups.tailscale-tls = { };
systemd.services.tailscale-tls = {
description = "Automatic Tailscale certificates";
after = [
"network-pre.target"
"tailscale.service"
];
wants = [
"network-pre.target"
"tailscale.service"
];
wantedBy = [ "multi-user.target" ];
serviceConfig.Type = "oneshot";
script = ''
status="Starting"
until [ $status = "Running" ]; do
sleep 2
status=$(${pkgs.tailscale}/bin/tailscale status -json | ${pkgs.jq}/bin/jq -r .BackendState)
done
mkdir -p "${cfg.target}"
DOMAIN=${domainExpression}
${pkgs.tailscale}/bin/tailscale cert \
--cert-file "${cfg.target}/cert.crt" \
--key-file "${cfg.target}/key.key" \
"$DOMAIN"
chown -R tailscale-tls:tailscale-tls "${cfg.target}"
chmod ${cfg.mode} "${cfg.target}/cert.crt" "${cfg.target}/key.key"
'';
};
systemd.timers.tailscale-tls = {
description = "Automatic Tailscale certificates renewal";
after = [
"network-pre.target"
"tailscale.service"
];
wants = [
"network-pre.target"
"tailscale.service"
];
wantedBy = [ "multi-user.target" ];
timerConfig = {
OnCalendar = "weekly";
Persistent = "true";
Unit = "tailscale-tls.service";
};
};
};
}
Tailscale
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.tailscale;
in
{
options.dr460nixed.tailscale = with lib; {
enable = mkEnableOption "Tailscale client daemon";
};
config = lib.mkIf cfg.enable {
services.tailscale.enable = true;
networking.firewall.trustedInterfaces = [ "tailscale0" ];
};
}
Tor
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.tor;
in
{
options.dr460nixed.tor = {
enable = lib.mkEnableOption "the Tor network";
};
config = lib.mkIf cfg.enable {
services.tor = {
client.dns.enable = true;
client.enable = true;
enable = true;
torsocks.enable = true;
};
};
}
Users
{
config,
lib,
...
}:
let
inherit (lib) mkOption types mdDoc;
cfg = config.dr460nixed.users;
ifTheyExist = groups: builtins.filter (group: builtins.hasAttr group config.users.groups) groups;
in
{
options.dr460nixed.users = mkOption {
description = mdDoc "User accounts to configure.";
default = { };
type = types.attrsOf (
types.submodule (_: {
options = {
isNormalUser = mkOption {
type = types.bool;
default = true;
description = mdDoc "Whether the user is a normal user.";
};
uid = mkOption {
type = types.nullOr types.int;
default = null;
description = mdDoc "UID for the user.";
};
gid = mkOption {
type = types.nullOr types.int;
default = null;
description = mdDoc "GID for the user.";
};
extraGroups = mkOption {
type = types.listOf types.str;
default = [
"audio"
"video"
"wheel"
];
description = mdDoc "Extra groups for the user.";
};
shellGroups = mkOption {
type = types.listOf types.str;
default = [ ];
description = mdDoc "Groups to add only if they exist on the system.";
};
authorizedKeyFiles = mkOption {
type = types.listOf types.path;
default = [ ];
description = mdDoc "SSH authorized key files.";
};
homeManager = mkOption {
type = types.submodule (_: {
options = {
enable = lib.mkEnableOption "Enable home-manager for this user.";
shellAliases = mkOption {
type = types.attrsOf types.str;
default = { };
description = mdDoc "User-specific shell aliases.";
};
fishAbbreviations = mkOption {
type = types.attrsOf types.str;
default = { };
description = mdDoc "User-specific fish abbreviations.";
};
git = {
userName = mkOption {
type = types.str;
default = "";
description = mdDoc "Git user name.";
};
userEmail = mkOption {
type = types.str;
default = "";
description = mdDoc "Git user email.";
};
signingKey = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc "GPG signing key for git.";
};
};
stateVersion = mkOption {
type = types.str;
default = "26.05";
description = mdDoc "Home-manager state version.";
};
};
});
default = { };
description = mdDoc "Home-manager configuration for this user.";
};
};
})
);
};
config = {
users.deterministicIds =
(lib.mapAttrs (_name: u: {
inherit (u) uid gid;
}) (lib.filterAttrs (_name: u: u.uid != null || u.gid != null) cfg))
// {
acme.uid = 999;
acme.gid = 999;
adbusers.uid = 998;
adbusers.gid = 998;
adguard.uid = 977;
adguard.gid = 977;
anubis.uid = 961;
anubis.gid = 961;
avahi.uid = 997;
avahi.gid = 997;
chaotic_op.uid = 996;
chaotic_op.gid = 996;
cloudflared.uid = 972;
cloudflared.gid = 972;
code-server.uid = 967;
code-server.gid = 967;
dhcpcd.uid = 976;
dhcpcd.gid = 976;
fwupd-refresh.uid = 975;
fwupd-refresh.gid = 975;
flatpak.uid = 974;
flatpak.gid = 974;
forgejo.uid = 963;
forgejo.gid = 963;
gamemode.uid = 969;
gamemode.gid = 969;
geoclue.uid = 959;
geoclue.gid = 959;
git.uid = 960;
git.gid = 960;
grafana.uid = 995;
grafana.gid = 995;
incus-admin.uid = 665;
incus-admin.gid = 665;
influxdb2.uid = 994;
influxdb2.gid = 994;
jellyfin.uid = 970;
jellyfin.gid = 970;
loki.uid = 993;
loki.gid = 993;
mandb.uid = 954;
mandb.gid = 954;
minecraft.uid = 973;
minecraft.gid = 973;
netdata.uid = 979;
netdata.gid = 979;
nixos.uid = 1001;
nixos.gid = 1001;
nm-iodine.uid = 992;
nm-iodine.gid = 992;
node-exporter.uid = 991;
node-exporter.gid = 991;
nscd.uid = 990;
nscd.gid = 990;
plocate.uid = 989;
plocate.gid = 989;
polkituser.uid = 988;
polkituser.gid = 988;
paperless.uid = 966;
paperless.gid = 966;
plasmalogin.uid = 958;
plasmalogin.gid = 958;
pcscd.uid = 953;
pcscd.gid = 953;
podman.uid = 968;
podman.gid = 968;
proc.uid = 971;
proc.gid = 971;
promtail.uid = 987;
promtail.gid = 987;
redis-paperless.uid = 965;
redis-paperless.gid = 965;
resolvconf.uid = 964;
resolvconf.gid = 964;
rtkit.uid = 986;
rtkit.gid = 986;
sshd.uid = 985;
sshd.gid = 985;
systemd-coredump.uid = 984;
systemd-coredump.gid = 984;
systemd-oom.uid = 983;
systemd-oom.gid = 983;
tailscale-tls.uid = 978;
tailscale-tls.gid = 978;
telegraf.uid = 982;
telegraf.gid = 982;
vnstatd.uid = 981;
vnstatd.gid = 981;
wakapi.uid = 962;
wakapi.gid = 962;
wireshark.uid = 957;
wireshark.gid = 957;
wpa_supplicant.uid = 956;
wpa_supplicant.gid = 956;
msr.gid = 955;
};
sops.secrets = {
"passwords/root".neededForUsers = true;
};
users = {
mutableUsers = false;
users = lib.mkMerge [
(lib.mapAttrs (name: u: {
inherit (u) isNormalUser;
extraGroups = u.extraGroups ++ ifTheyExist u.shellGroups;
hashedPasswordFile = config.sops.secrets."passwords/${name}".path;
home = "/home/${name}";
openssh.authorizedKeys.keyFiles = u.authorizedKeyFiles;
autoSubUidGidRange = false;
subGidRanges = lib.mkIf config.virtualisation.podman.enable [
{
count = 65536;
startGid = (if u.uid != null then u.uid else 1000) * 1000;
}
];
subUidRanges = [
{
count = 65536;
startUid = (if u.uid != null then u.uid else 1000) * 1000;
}
];
}) cfg)
{
root = {
hashedPassword = null;
hashedPasswordFile = lib.mkForce config.sops.secrets."passwords/root".path;
};
}
];
};
security.sudo.extraRules = [
{
groups = [ "wheel" ];
commands =
let
currentSystem = "/run/current-system/";
storePath = "/nix/store/";
in
[
{
command = "${storePath}/*/bin/switch-to-configuration";
options = [
"SETENV"
"NOPASSWD"
];
}
{
command = "${currentSystem}/sw/bin/nix-store";
options = [
"SETENV"
"NOPASSWD"
];
}
{
command = "${currentSystem}/sw/bin/nixos-rebuild";
options = [ "NOPASSWD" ];
}
{
command = "${currentSystem}/sw/bin/nix-collect-garbage";
options = [
"SETENV"
"NOPASSWD"
];
}
{
command = "${currentSystem}/sw/bin/systemctl";
options = [ "NOPASSWD" ];
}
];
}
];
};
}
Wireguard
{
config,
lib,
...
}:
(
let
cfg = config.dr460nixed.wireguard;
inherit (lib)
mkOption
mkEnableOption
mkIf
types
mapAttrs'
nameValuePair
;
in
{
options.dr460nixed.wireguard = {
enable = mkEnableOption "WireGuard connections via systemd-networkd";
interfaces = mkOption {
default = { };
type = types.attrsOf (
types.submodule {
options = {
address = mkOption {
type = types.listOf types.str;
description = "List of addresses to assign to the interface.";
};
listenPort = mkOption {
type = types.int;
default = 51820;
description = "Port to listen on for incoming connections.";
};
privateKeySecretName = mkOption {
type = types.str;
description = "The name of the sops secret containing the private key.";
};
firewallMark = mkOption {
type = types.nullOr types.int;
default = null;
description = "Firewall mark to set on packets.";
};
routeTable = mkOption {
type = types.nullOr types.str;
default = "main";
description = "Routing table for the WireGuard interface.";
};
peers = mkOption {
type = types.listOf (
types.submodule {
options = {
publicKey = mkOption {
type = types.str;
description = "Public key of the peer.";
};
allowedIPs = mkOption {
type = types.listOf types.str;
description = "List of IP ranges allowed from this peer.";
};
endpoint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Endpoint address and port of the peer.";
};
persistentKeepalive = mkOption {
type = types.nullOr types.int;
default = null;
description = "Persistent keepalive interval in seconds.";
};
};
}
);
default = [ ];
description = "List of WireGuard peers.";
};
};
}
);
description = "WireGuard interface configurations.";
};
};
config = mkIf cfg.enable {
# Ensure systemd-networkd is used
networking.useNetworkd = true;
systemd.network.enable = true;
# Open firewall ports
networking.firewall.allowedUDPPorts = lib.mapAttrsToList (
_name: value: value.listenPort
) cfg.interfaces;
# systemd-networkd configuration
systemd.network = {
networks = mapAttrs' (
name: value:
(nameValuePair "50-${name}" {
matchConfig.Name = name;
inherit (value) address;
networkConfig.IPv6PrivacyExtensions = "no";
})
) cfg.interfaces;
netdevs = mapAttrs' (
name: value:
(nameValuePair "50-${name}" {
netdevConfig = {
Kind = "wireguard";
Name = name;
};
wireguardConfig = lib.filterAttrs (_n: v: v != null) {
ListenPort = value.listenPort;
PrivateKeyFile = config.sops.secrets."${value.privateKeySecretName}".path;
RouteTable = value.routeTable;
FirewallMark = value.firewallMark;
};
wireguardPeers = map (
peer:
lib.filterAttrs (_n: v: v != null) {
PublicKey = peer.publicKey;
AllowedIPs = peer.allowedIPs;
Endpoint = peer.endpoint;
PersistentKeepalive = peer.persistentKeepalive;
}
) value.peers;
})
) cfg.interfaces;
};
};
}
)
Yubikey
{
config,
lib,
pkgs,
...
}:
let
cfg = config.dr460nixed.yubikey;
in
{
options.dr460nixed.yubikey = {
enable = lib.mkEnableOption "Yubikey support";
};
config = lib.mkIf cfg.enable {
hardware.gpgSmartcards.enable = true;
services.pcscd = {
enable = true;
plugins = [ pkgs.ccid ];
};
services.udev.packages = [ pkgs.yubikey-personalization ];
environment.systemPackages = [ pkgs.yubioath-flutter ];
security.pam.yubico = {
debug = false;
enable = true;
mode = "challenge-response";
};
};
}
ZFS
{
config,
lib,
...
}:
let
cfg = config.dr460nixed.zfs;
in
{
options.dr460nixed.zfs = with lib; {
enable = mkEnableOption "Configures common options for using ZFS on NixOS.";
sendMails = mkOption {
default = false;
type = types.bool;
description = mdDoc ''
Enables sending status reports about ZFS maintenance via email.
'';
};
};
config = lib.mkIf cfg.enable {
boot.supportedFilesystems = [ "zfs" ];
boot.zfs.requestEncryptionCredentials = true;
services.zfs = {
autoScrub = {
enable = true;
interval = "weekly";
};
trim = {
enable = true;
interval = "weekly";
};
};
dr460nixed.smtp.enable = lib.mkIf cfg.sendMails true;
services.zfs.zed.enableMail = lib.mkIf cfg.sendMails false;
services.telegraf.extraConfig.inputs = lib.mkIf config.services.telegraf.enable {
zfs.poolMetrics = true;
};
};
}
Credits
Main sources
A special thanks to PedroHLC, who always gives great advice and who is also the reason I’m using NixOS today. Also, I studied Mysterio77’s and NotAShelf’s Nix configurations while building this one.