My NixOS dotfile flake for both systems and users. Each system/user is built upon custom modules abstracting NixOS/home-manager. The modules are meant to be easily shareable and should just work by taking into account the systemwide configuration. There are modules tailored towards server, desktop, and laptop use.
Note: One of my flake inputs points towards a private git repository of secrets. Thus you will not be able to clone and run this repo without some changes. See the notes on my secrets config below.
Follow along with my NixOS evolution and things I learn along the way: NixOS Desktop Series
I use a variety of custom flakes, but these are the ones I have written myself!
jdpkgs: A variety of programs that I have packaged and/or created useful wrappers for.
neovim-flake: A configurable flake for noevim.
Some cool excerpts of my dotfiles
- Erase your darlings inspired stateless machine utilizing
impermanence
and ZFS snapshots - Encrypted ZFS pool (and encrypted swap) with initrd SSH support for decryption.
- See ./modules/system/ssh, ./modules/system/boot, and ./modules/system/impermanence
Erase your darlings - Graham Christensen
- Working GTK/QT theming, icons, and cursors on wayland. See ./modules/users/graphical/shared.nix.
- Reproducible firefox with plugins. See ./modules/users/graphical/applications/firefox.nix.
- Greetd login that starts up sway. See ./modules/users/graphical/wayland.nix
- Mounted on login onedrive filesystem using onedriver and a systemd-unit. See ./modules/users/office365/default.nix
Automagic wireguard setup with tagging and DNS. Each peer provides a list of tags (and an optional endpoint address associated with the peer list).
An example configuration that would be passed into every system (no changes necessary between systems):
{
wireguardConf = {
enable = true;
interface = "thevoid";
peers = {
"server" = {
wgAddrV4 = "10.55.1.1";
publicKey = secrets.wireguard.phone.publicKey;
tags = [{ name = "net"; }];
};
"framework" = {
wgAddrV4 = "10.55.1.2";
interfaceMask = 16;
listenPort = 51820;
privateKeyPath = "/etc/wireguard/private_key";
privateKeyAge = secrets.wireguard.framework.secret.file;
publicKey = secrets.wireguard.framework.publicKey;
tags = [{ name = "net"; } { name = "home"; ipAddr = "172.26.40.247"; }];
};
"desktop" = {
wgAddrV4 = "10.55.0.1";
interfaceMask = 16;
listenPort = 51820;
firewall.allowedTCPPorts = [ 8080 ];
privateKeyPath = "/etc/wireguard/private_key";
privateKeyAge = secrets.wireguard.desktop.secret.file;
publicKey = secrets.wireguard.desktop.publicKey;
tags = [{ name = "home"; ipAddr = "172.26.26.90"; } { name = "net"; }];
};
"vps" = {
# config
# ...
tags = [{ name = "net"; ipAddr = "123.45.67.89"; }];
};
};
};
}
The name of the peer must match the hostname of the machine. Firewall ports for wireguard are automatically opened up using the configured network interfaces from another module (./modules/system/networking). Age encrypted secrets wireguard keys are automatically setup with agenix
(see notes on secrets management below). Peer lists are automatically set up using the tags list.
How peer tags work:
- Each tag for a peer signifies it belongs to that tag and will connect to all machines with corresponding tags.
- Tags are read from first to last according to the host's tag list. So
framework
would use thenet
config fromdesktop
, whiledesktop
would use thehome
config fromframework
. Bothframework
anddesktop
would havevps
andphone
in their peer conig as they all belong tonet
.
See ./modules/system/wireguard.
Inspired by xe's post.
System secrets are managed using agenix
and user secrets using homeage
. They are set up in an external (private) repository called secrets
and pulled in as a flake input. All secrets are age encrypted files. The encrypted files get put into the nix store and they are decrypted at runtime and are tracked by the git repository so they can be pulled in by the flake. The secrets
repository has two .nix
files, config.nix
and secrets.nix
. config.nix
contains the configurations. All secrets are stored following attribute (must be named secret):
secret = {
publicKeys = [ key1 key2 ... ];
file = ./example/secret;
};
It must be named secret
so the secrets.nix
file can extract and transform them to be agenix cli compatible. The follow secrets.nix
file does the conversion (can be copy/pasted):
with builtins; let
config = import ./config.nix;
nameValuePair = name: value: {inherit name value;};
filterAttrs = pred: set:
listToAttrs (concatMap (name: let
v = set.${name};
in
if pred name v
then [(nameValuePair name v)]
else []) (attrNames set));
collect = pred: attrs:
if pred attrs
then [attrs]
else if builtins.isAttrs attrs
then builtins.concatMap (collect pred) (builtins.attrValues attrs)
else [];
relPath = path: replaceStrings [(toString ./.)] ["."] (toString path);
secretToOutput = attr:
nameValuePair
(relPath attr.secret.file)
{
publicKeys = attr.secret.publicKeys;
};
collectSecrets =
builtins.listToAttrs
(builtins.map
secretToOutput
(collect
(x: x ? "secret")
config));
in
collectSecrets
A more complete example config.nix
:
let
age = {
system = {
# The desktop public key used for decryption (manually installed)
desktop = {
publicKey = "....";
privateKeyPath = "....."; # for identity path config
};
# The server public key used for decryption (manually installed)
server = {
publicKey = "....";
privateKeyPath = ".....";
};
};
};
in
{
inherit age;
ssh = {
jd = {
publicKey = "";
secret = {
publicKeys = with (age.system); [ desktop.publicKey ];
file = ./ssh/jd_private_key;
};
};
};
wireguard = {
desktop = {
publicKey = "";
secret = {
publicKeys = with (age.system); [ desktop.publicKey ];
file = ./wireguard/desktop_private_key;
};
};
server = {
publicKey = "";
secret = {
# Be able to edit it on desktop
publicKeys = with (age.system); [ desktop.publicKey server.publicKey ];
file = ./wireguard/chairlift_private_key;
};
};
};
}
A heavily evolved version of Wil Taylor's dotfiles. Would not have the setup I have today without his youtube series and repo as a guide when I was first starting out.