Right, so I've been at this Nix thing for a bit now, and I thought it was time to document what I've actually built. Not what I planned to build, not some idealised version – what's actually sitting in ~/.config/nix-config right now, in all its messy, half-finished glory.

The thing is, I've managed to get three different machines configured through a single flake, which feels like an achievement even if I still don't entirely understand how it works. Two of them are even booting successfully.

The Setup: Three Hosts, Two Platforms

Let me lay out what I'm managing here:

macmini – My daily driver. Apple Silicon M2, running macOS with nix-darwin. This one works. I use it every day. It's stable. I'm genuinely quite proud of this one.

laptop – Dell Inspiron 3501 with an i3-1115G4 and 8GB RAM. Running NixOS with GNOME. This also works, when I remember to turn it on. Which is... occasionally.

server – A theoretical NixOS headless server config that exists entirely in my flake but has never actually touched real hardware. It's like Schrödinger's server. The config is there. The modules are there. The README is extremely detailed. But is it a server if it's never been deployed?

(We'll get to that.)

The Structure: Organised Chaos

Here's how I've laid things out, because if I'm going to be confused, I might as well be neatly confused:

~/.config/nix-config/
├── flake.nix              # Where the magic happens (allegedly)
├── flake.lock             # Git's way of saying "you wanted reproducibility, right?"

├── hosts/
│   ├── laptop/            # NixOS + GNOME config
│   ├── server/            # The server that doesn't exist yet
│   └── macmini/           # nix-darwin config (actually works!)

├── modules/
│   ├── common.nix         # Shared stuff: flakes, auto-updates, garbage collection
│   ├── desktop.nix        # GNOME setup for the laptop
│   ├── gaming.nix         # Steam et al (optimistic, really)
│   ├── packages.nix       # All the apps for desktop systems
│   ├── services.nix       # System services
│   ├── users.nix          # User accounts
│   └── darwin/            # macOS-specific modules
│       ├── common.nix
│       ├── homebrew.nix
│       ├── packages.nix
│       └── system.nix

├── home/                  # Home Manager configs
│   ├── home.nix
│   ├── configs/           # Actual config files
│   └── programs/          # Per-app Nix configs

├── secrets/               # ragenix-encrypted secrets
│   ├── secrets.nix        # Public key mappings (safe to commit)
│   ├── setup.sh           # Secret management automation
│   └── age/               # Encrypted .age files

└── settings/              # System settings export/import
    ├── gnome/             # dconf settings for GNOME
    └── darwin/            # macOS defaults

It looks tidy. It looks like I know what I'm doing. This is a lie, but it's a well-organised lie.

The Mac: Actually Working

Let's start with the success story. The Mac mini has been running nix-darwin for... a while now? And it actually works. Like, properly works.

The flake pulls in nixpkgs-darwin (separate from the main nixpkgs for reasons I vaguely understand), sets up Home Manager, and configures everything through a combination of:

  • System-level packages via nix-darwin

  • User-level stuff via Home Manager

  • GUI apps via Homebrew (because some things just don't work well with Nix on macOS)

  • macOS system preferences declaratively

Here's the clever bit: I've got a darwin-export.sh script that dumps my current macOS defaults into Nix syntax. So when I tweak something in System Settings and want to preserve it, I just run the script and commit the output. It's not perfect – macOS has thousands of hidden settings and I've only captured the ones I care about – but it works.

The Homebrew integration is particularly nice. Instead of manually managing casks, I just list them in modules/darwin/homebrew.nix:

# these are not actual definitions
homebrew = {
  enable = true;
  casks = [
    "1password"
    "discord"
    "firefox"
    "spotify"
    # etc.
  ];
};

And nix-darwin handles the rest. When I rebuild, it installs missing casks, removes unwanted ones, and generally keeps everything in sync. No more "did I install this through Homebrew or manually?" confusion.

The rebuild command is straightforward:

sudo darwin-rebuild switch --flake ~/.config/nix-config#macmini

Takes about 30 seconds, applies cleanly, rarely breaks anything. This is the good timeline.

The Laptop: Mostly Working

The Dell Inspiron runs NixOS with GNOME, and it's... fine? It works. GNOME boots, Firefox launches with my extensions pre-installed, VS Code has my settings, Git knows who I am. The wallpaper is set through Home Manager (which still feels needlessly complex for "use this image as my background," but whatever).

The rebuild is slower here:

sudo nixos-rebuild switch --flake ~/.config/nix-config#laptop

Couple of minutes, lots of scrolling output, occasional cryptic errors that resolve themselves on retry. Standard NixOS experience.

I've got a Makefile with shortcuts for common operations:

make switch    # Rebuild and activate
make update    # Update flake inputs
make clean     # Garbage collection

Do I use the Makefile? Sometimes. Do I also just type out the full commands? Also sometimes. Consistency is not my strong suit.

The laptop has automatic daily updates configured in modules/common.nix, which means it theoretically keeps itself current. In practice, I should probably check on it more often. But it's booting from old generations if something breaks, so the safety net exists.

The Gaming Situation (Again)

I added Steam support because I could. Three lines in modules/gaming.nix:

programs.steam = {
  enable = true;
  remotePlay.openFirewall = true;
  dedicatedServer.openFirewall = true;
};

Plus GameMode, MangoHUD, and 32-bit graphics libraries. The whole gaming stack. On a laptop with an i3-1115G4 and integrated graphics.

Have I used it? No. Will I? Probably not. But the option exists, and that's what matters.

The Server: Schrödinger's Config

Here's where it gets interesting. I have an entire server configuration that's never been deployed.

The config exists. hosts/server/default.nix imports a bunch of server-specific modules. There's SSH hardening, fail2ban, firewall configuration, automatic updates, SMART monitoring, SSD optimization. The README is extremely detailed – installation instructions, post-setup verification, common tasks, troubleshooting guides.

I wrote all of this. Tested none of it on actual hardware.

Why? Because I don't have a server. I mean, I could spin one up. Digital Ocean, Linode, whatever. But I haven't. The config just sits there, ready and waiting, like a really well-documented ghost.

The plan (such as it is) involves eventually hosting my own PDS for AT Protocol, maybe running some other services. But that's Future Me's problem. Present Me just maintains the config and hopes it'll work when needed.

There's something slightly absurd about meticulously documenting a deployment process I've never actually run. But that's half the fun of Nix, isn't it? You build the config, version control it, and trust that it'll work when you eventually need it.

(It won't. But the error messages will be entertaining.)

Secrets: Actually Secure (Probably)

One thing I've got working properly: secrets management with ragenix.

This was a learning curve. Not because ragenix is complicated – it's actually quite straightforward – but because understanding age encryption, public keys, identity management, and how it all integrates with NixOS/nix-darwin required reading a lot of documentation I only half-understood.

Here's the setup:

  • I have a master key in ~/.config/age/keys.txt (which is .gitignored and backed up securely)

  • Each machine's SSH host key is converted to an age public key

  • Secrets are defined in secrets/secrets.nix with which keys can decrypt them

  • Encrypted .age files are committed to git (safe because they're encrypted)

  • At build time, NixOS/nix-darwin decrypts secrets to protected locations

I've got actual secrets in there:

  • WiFi password for my home network

  • SSH key passphrases

Nothing sensitive lives in the repo unencrypted. Everything that should be secret is secret. And the setup.sh script automates most of the key management, so I don't have to manually update secrets.nix every time I add a machine.

Does it work? Yes. Do I fully trust I haven't misconfigured something? Not entirely. But the secrets are encrypted, the private keys are secure, and the system boots without errors. That's good enough.

Home Manager: The Unsung Hero

The best part of this whole setup is Home Manager. Seriously.

I've got identical(ish) dotfiles across all three machines – macmini, laptop, and (theoretically) server. Git config, Zsh setup, Starship prompt, fastfetch, VS Code extensions and settings. Everything lives in home/, gets templated with Home Manager, and deploys consistently.

When I tweak my Git config on the Mac, rebuild, commit, and pull on the laptop – boom, same config. When I add a VS Code extension, same thing. It's like having proper dotfile management but with Nix's reproducibility guarantees.

The conditional logic for platform-specific stuff is neat too:

packages = with pkgs; [
  # Cross-platform stuff
  htop
  ripgrep
  fd
] ++ lib.optionals (!isDarwin) [
  # Linux-only
  vlc
  dconf2nix
];

Same config file, different package lists depending on OS. Elegant, in a very Nix sort of way.

Where It Breaks

Let's talk about the failures, because there are plenty.

Rebuild speed on NixOS. It's slow. Not glacial, but slow enough that I notice. Probably due to how I've structured the modules. Probably fixable. Haven't investigated.

File ownership confusion with Home Manager. Sometimes after a rebuild, stuff in ~/.config is owned by root instead of my user. I run sudo chown -R ewan:users ~/.config and it sorts itself out. This should not be necessary. It is necessary.

Flake input drift. When I run nix flake update, half my packages get new versions, and occasionally something breaks. Usually Firefox extensions. I've learned to be careful about updates. Which kind of defeats the point of having automatic updates configured. But here we are.

The secrets learning curve. ragenix works now, but getting there involved a lot of trial and error. The documentation is good, but there's a gap between "here's how age encryption works" and "here's how to integrate this into a NixOS flake with multiple hosts." I bridged that gap through experimentation and generous use of git reset --hard.

The server that doesn't exist. This isn't technically a failure, but it's certainly aspirational. I've spent more time documenting the server than I have actually using any of my deployed configs.

Recent Work: Making It Less Terrible

Looking at my recent commits, I've been doing some cleanup:

  • Refactored the flake to be less repetitive (DRY principles, finally)

  • Centralised SSH authorised keys in one place

  • Removed the VM config that was cluttering things up

  • Fixed Zsh integration (which broke, then got fixed, then broke again, then got fixed properly)

Each change makes the config slightly less of a mess. Slightly more maintainable. Slightly less likely to confuse Future Me.

It's incremental progress. But it's progress.

The Workflow

Here's my actual day-to-day:

On the Mac:

# Edit something in ~/.config/nix-config
# Usually add a package or tweak a setting

sudo darwin-rebuild switch --flake ~/.config/nix-config#macmini

# If that worked:
cd ~/.config/nix-config
git add .
git commit -m "whatever I just changed"
git push

On the laptop (when I remember it exists):

cd ~/.config/nix-config
git pull
make switch

# If it broke:
git reset --hard origin/main
# Select a previous generation from the boot menu

The Mac is stable enough that rebuilds rarely fail. The laptop occasionally requires percussive maintenance. The server... well, there's nothing to break yet.

Why This Is Worth It (Maybe)

Fair question. Is maintaining a complex multi-machine Nix config worth the effort? Especially when two-thirds of it is theoretical?

Honestly? On the Mac, yes. Having a reproducible system configuration, being able to rebuild from scratch in minutes, having my entire setup version-controlled – that's valuable. When the system updates broke something (twice), being able to roll back instantly was worth every hour I spent learning Nix.

On the laptop? Less clear. I don't use it often enough to really appreciate the benefits. But when I do use it, everything works exactly as I expect, which is nice.

The server? It's an investment in Future Me. When I eventually need a server, I'll have a battle-tested config ready to deploy. Whether it actually works remains to be seen.

What's Next

The mental TODO list:

  • Actually deploy the server config (probably Digital Ocean or Hostinger)

  • Figure out why rebuilds are slow and fix it (maybe)

  • Learn enough about systemd to understand what all these services are doing

  • Stop manually chown-ing config files after rebuilds

  • Maybe try tiling window managers? (no, probably not, GNOME is fine.)

  • Document the ragenix setup better for when I inevitably forget how it works

Will I do any of these? Some of them. Probably. Eventually.

But there's also something to be said for not fixing what isn't broken. The Mac works. The laptop works when I need it. The server config is there when I want it. Maybe that's enough for now.

If You Want to Try This

Should you maintain a multi-machine Nix config? Depends what you value.

If you like tinkering, learning bizarre languages, and having complete control over your system configuration – yeah, absolutely, go for it. You'll spend a lot of time confused, but you'll build something genuinely yours.

If you just want a computer that works – install Ubuntu and save yourself the trouble.

The nice thing about Nix is that even when you're completely lost (which is most of the time), you can git reset --hard and start over. That safety net makes experimentation less terrifying. You can try things, break things, and recover from them.

That's powerful.

The Honest Conclusion

~/.config/nix-config is a work in progress. Always will be. The Mac configuration is solid. The laptop configuration is adequate. The server configuration is aspirational.

I understand maybe 65% of what I've built, which is honestly better than I expected.

Is it overkill? Absolutely. Is it worth it? On the Mac, yes. On the laptop, maybe. On the server, ask me again when I've deployed it.

But it's mine. Every bit of this config, even the bits I don't understand, represents something I've learned or something I've decided I want. It's personalised in a way that no default installation could ever be.

Even when I'm making it up as I go.

Which, let's be honest, is most of the time.