This repository contains my personal system configurations for multiple machines using Nix, managed through the Den aspect-oriented configuration framework.
- harmony (x86_64-linux): Home server running media services, Minecraft servers, and infrastructure
- melaan (x86_64-linux): Framework laptop with GNOME desktop
- OMARSHAL-M-T2QF (aarch64-darwin): MacBook with development environment
- [email protected] (x86_64-linux): Standalone Home Manager config reusing the
oscaraspect for a work machine
- Nix with flakes enabled
- Appropriate system (NixOS, macOS, or Linux for home-manager only)
git clone https://github.com/OscarMarshall/dotfiles.git
cd dotfilesNixOS systems (harmony, melaan):
sudo nixos-rebuild switch --flake .#<hostname>macOS systems (OMARSHAL-M-T2QF):
darwin-rebuild switch --flake .#OMARSHAL-M-T2QFStandalone Home Manager ([email protected]):
home-manager switch --flake .#"[email protected]"# nix flake check currently fails due to cross-architecture issues
# Use platform-specific builds instead:
nix build .#nixosConfigurations.harmony.config.system.build.toplevel
nix build .#nixosConfigurations.melaan.config.system.build.toplevel
nix build .#darwinConfigurations.OMARSHAL-M-T2QF.config.system.build.toplevel
# Show available outputs
nix flake show
# Format code
nix fmtThis repository uses Den, an aspect-oriented configuration system built on flake-parts. Configuration is organized into composable aspects:
modules/aspects/hosts/: Host-specific configurations (one aspect per host)modules/aspects/users/: User-specific configurations (one aspect per user)modules/aspects/my/: Reusable service and feature aspects (~55 total)modules/aspects/defaults.nix: Default includes applied to all configurations
Each aspect can provide configuration for different targets using these classes:
os: Applies to both NixOS and Darwin (avoids duplicating identical config innixosanddarwin)nixos: NixOS-specific configuration onlydarwin: macOS (nix-darwin) specific configuration onlyhomeManager: Home Manager configuration (cross-platform user environment)hmLinux/hmDarwin: Platform-specific Home Manager classes forwarded intohomeManagerbymodules/aspects/defaults.nix
Each host declares which services and features to enable:
# modules/aspects/hosts/harmony/harmony.nix
den.aspects.harmony = {
includes = with my; [
nginx
(minecraft-servers { administrators = [ "oscar" ]; })
(qbittorrent { administrators = [ "oscar" ]; })
plex
# ... more aspects
];
};Each user declares their environment and applications:
# modules/aspects/users/oscar/oscar.nix
den.aspects.oscar = { host, lib, ... }: {
user.description = "Oscar Marshall";
includes = [
my.emacs
my.git
] ++ lib.optionals (host.graphical or false) [ my.discord my.ghostty ];
};Use an aspect function signature ({ host, lib, ... }:) when you need context-aware conditional logic.
- Media: Plex, Tautulli, Radarr, Sonarr, Prowlarr, Unpackerr, Autobrr, Cross-seed
- Documents: Paperless-ngx, behind Authentik forward-auth
- Downloads: native qBittorrent confined with VPN-Confinement
- Gaming: Minecraft servers
- Home Automation: Home Assistant, with SSO via Authentik's
auth_oidcintegration - Infrastructure: Nginx reverse proxy with Let's Encrypt, Samba file sharing, ZFS storage, offsite backups (Restic/Backblaze B2)
The VPN input and service confinement opt-in are provided by a reusable my.vpn-confinement aspect, while qBittorrent
declares the proton0 namespace directly in its own aspect.
- GNOME on melaan (Wayland, via NixOS)
- macOS desktop: Fonts, Homebrew-based applications, and Nix-managed development environment on OMARSHAL-M-T2QF
- Applications: Emacs, Ghostty terminal, Zen Browser, Discord, Steam, Krita, PrusaSlicer
- Framework laptop support via nixos-hardware
- Emacs with doom configuration
- Git with per-machine configuration
- GPG and SSH setup
- Shell: Fish shell via Home Manager
Secrets are managed with ragenix (age encryption) extended by
agenix-rekey. A YubiKey acts as the single master identity; host keys are
derived automatically. Primitive secrets are encrypted to the YubiKey. Mark a primitive secret intermediary = true
only if it is exclusively consumed by generators (never referenced directly by services). Template secrets (env files,
JSON configs) are generated from primitives and then rekeyed per host.
The dev shell (automatically activated by direnv via the .envrc in the repo root) provides the
agenix CLI tool from agenix-rekey, which is the single script needed to add/update/generate/rekey secrets.
Note: Editing primitive secrets, running
agenix generate, and runningagenix rekeyrequire physical YubiKey access and must be performed by a human.
# Edit or create a primitive secret (human only — requires YubiKey)
agenix edit secrets/<name>.age
git add secrets/<name>.age
# Generate template secrets from primitives (human only — requires YubiKey)
agenix generate -a
# Rekey all secrets for all hosts and commit (human only — requires YubiKey)
agenix rekey -a- Primitive secrets (
secrets/*.age): encrypted to the YubiKey master identity. Addintermediary = trueonly if the secret is exclusively consumed by generators. - Template secrets: generated from primitives by
agenix generateintosecrets/generated/, then rekeyed viaagenix rekey -aintosecrets/rekeyed/<hostname>/for the host's OS-level secrets, andsecrets/rekeyed/<hostname>-home-<username>/for a user's embedded Home Manager secrets. These are kept as separate directories (rather than sharing one) becauseagenix rekey's local storage mode deletes any file in a directory it doesn't recognize as one of that node's own secrets — sharing a directory between the OS and Home Manager nodes would cause each to delete the other's secrets as "orphans". secretsclass: use in host/user/service aspects to declare secrets — preferred over settingage.secretsdirectly. Forwarded toage.secretson all platforms (NixOS, Darwin, Home Manager) bydefaults.nix.nixosSecretsclass: used in user/host aspects for NixOS-only secrets (e.g. hashed passwords). Forwarded only tonixos.age.secrets, never to Darwin or Home Manager configs. Prefersecretsunless the secret must be excluded from non-NixOS hosts.
Each host that consumes rekeyed secrets must declare age.rekey.hostPubkey in its host aspect.
Some live services aren't configured through Nix directly, but through their own APIs — Authentik SSO clients, *arr app
wiring, DNS records, the Meraki router. These are managed as Terraform/OpenTofu config instead, generated from Nix via
terranix (see modules/terranix.nix). Any aspect can contribute resources by declaring a
terranix field, the same way it would declare nixos/darwin/homeManager.
Each host that uses this gets its own nix run .#<host>-tf* commands (currently only harmony-tf):
nix run .#harmony-tf.plan # preview changes
nix run .#harmony-tf # apply
nix run .#harmony-tf.destroy
nix develop .#harmony-tf # shell with opentofu
nix build .#harmony-tf.config # inspect the generated config.tf.jsonThese only work when run on the host itself (e.g. on harmony, never from a dev laptop): both Terraform state and the
decrypted secrets env file live on the host's own local ZFS dataset//run/agenix, not in this repo or on the YubiKey
master identity. harmony-tf-apply.service also plans and applies automatically on every nixos-rebuild switch that
changes anything Terraform-relevant, so running these by hand is normally only needed to preview a change or investigate
a failure — a plan containing any destroy action is never auto-applied; it fails the service instead, surfaced through
the same Netdata → Discord alerting used for host health.
Any ZFS dataset can opt into offsite backup by setting backup = true; (or a pkgs: {...} function, for datasets that
need pkgs-derived overrides like a database dump prepare/cleanup pair) on its dataset declaration — see
modules/aspects/my/zfs.nix for the full dataset record shape. The my.backup aspect
(modules/aspects/my/backup.nix) picks up every opted-in dataset on a host and turns it into a
services.restic.backups job against a Backblaze B2 bucket, with a 30-day
Object Lock retention policy so even a compromised restic key can't delete recent backups.
# modules/aspects/hosts/<hostname>/<hostname>.nix
(backup {
bucket = "<globally-unique-b2-bucket-name>";
applicationKeyId = "<bucket-scoped Backblaze application key ID>"; # omit (defaults to null) until the key exists
})Credentials: an account-level Backblaze application key (shared across every host — there's one Backblaze account, so
its ID is a plain constant in backup.nix itself, not a per-host parameter) authenticates the b2 Terraform provider
that creates the bucket; a second, bucket-scoped key (per host, named backup-b2-application-key-<hostname>) is what
restic actually uses day to day. The bucket-scoped key can't exist before its bucket does, so a new host's bootstrap
order is: wire in my.backup with just bucket set, nix run .#<hostname>-tf to create the bucket (see
Infrastructure as Code above), create the bucket-scoped key against it, then
set applicationKeyId and rebuild. Until applicationKeyId is set, no restic jobs are scheduled at all — see
backup.nix's own comments for why that matters.
GitHub automation:
- Dependabot handles GitHub Actions and Nix (
flake.lock) updates. - Dependabot PRs are automatically set to auto-merge once required checks pass.
- Renovate is kept only for Docker image updates referenced from Nix files.
nix flake updatenix flake update <input-name>The flake.nix is auto-generated by flake-file. After modifying inputs in
modules/inputs.nix:
nix run .#write-flake- Create
modules/aspects/my/<service>.nix - Define the aspect (with parameters if needed)
- Include in host's aspect:
modules/aspects/hosts/<hostname>/<hostname>.nix
- Create
modules/aspects/hosts/<hostname>/<hostname>.nix - Add a NixOS
hardware-configuration.nixfor the host - Declare the host in
modules/den.nix
- Create
modules/aspects/hosts/<hostname>/<hostname>.nix - Configure the host-specific nix-darwin options in your aspect
- Declare the host in
modules/den.nix
- Create
modules/aspects/users/<username>/<username>.nix - Add user to host declarations in
modules/den.nix
- Den Documentation - Aspect system patterns and usage
- Dendritic Template - Template this repo is based on
- NixOS Manual - NixOS configuration reference
- Home Manager Manual - Home Manager options
- nix-darwin Manual - macOS system configuration
This is a personal configuration repository. Feel free to use it as reference or template for your own configurations.