Skip to content
worldofgeesePublic

About

Den mono-repo: unified Nix infrastructure for all hosts

Resources

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

803 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Den

Unified Nix infrastructure for all my machines. Built on Den (aspect-oriented flake-parts framework) with import-tree for automatic module discovery.

What you get

Shell & terminal: zsh (macOS) / bash (Linux) with autocompletions, syntax highlighting, Starship prompt (Dracula theme), WezTerm, tmux with session persistence

Modern coreutils: eza, bat, fd, ripgrep, dust, duf, procs, sd, fzf, zoxide, broot, delta (side-by-side diffs)

History & snippets: atuin (fuzzy synced shell history), navi, pet, cheat

Development: Node.js, Rust (via rustup), Python (via uv), Bun, .NET, Go tooling, Neovim, Zed, Doom Emacs

Cloud & infra: kubectl, Helm, Flux, AWS CLI, Azure CLI, Pulumi, OpenShift, krew, kubie, saml2aws

Git: GPG/SSH commit signing, LFS, lazygit, delta, gh + gh-dash

Containers: Podman, Docker Compose, DevPod, Distrobox, Kind

macOS extras: Homebrew management, Touch ID for sudo (survives tmux), system defaults, AeroSpace tiling, Lix as Nix implementation

Upgrades: Run topgrade --yes to update everything in one go (Nix, Guix, Homebrew, Doom Emacs, Flatpak — then auto-GC)

Hosts

Host Platform Description
mahakala x86_64-linux Personal workstation — Guix System + Guix Home + Home Manager
M-02877 aarch64-darwin Work MacBook — nix-darwin + Home Manager + Homebrew
paphos x86_64-linux Home server — NixOS, Forgejo, Forgesync, Tailscale, agenix secrets (docs/paphos/)
pixel-fold aarch64-linux Android phone — nix-on-droid (proot, not NixOS)
oracle aarch64-linux Oracle Cloud Free Tier A1.Flex — OCI image, OpenTofu, Tailscale peer relay (docs/oracle/, terraform/oracle/README.md)

Prerequisites

  • Nix (or Lix) with flakes enabled
  • just (optional, for deploy shortcuts)
  • Platform-specific: darwin-rebuild (macOS), nixos-rebuild (NixOS), nix-on-droid (Android), guix (Guix System)

Quick start

macOS (M-02877)

# Install Lix
curl -sSf -L https://install.lix.systems/lix | sh -s -- install

# Clone
git clone https://github.com/worldofgeese/den.git ~/.config/home-manager
cd ~/.config/home-manager

# First apply (bootstrap nix-darwin)
nix flake update
sudo -H nix run nix-darwin -- switch --flake .#M-02877

# Subsequent applies
just deploy-darwin

After the first successful rebuild, open a new terminal to pick up the shell configuration.

Linux workstation (mahakala)

mahakala runs Guix System as its base OS with Nix Home Manager layered on top.

# Ensure Nix is installed with flakes
# Clone to the expected path
git clone https://github.com/worldofgeese/den.git ~/.config/home-manager
cd ~/.config/home-manager

# Deploy all three layers
just deploy-mahakala

NixOS server (paphos)

# From any machine with SSH access to paphos
just deploy-paphos

Oracle Cloud Free Tier (oracle)

Scaffold and operations docs: docs/oracle/. OpenTofu details: terraform/oracle/README.md.

just build-oracle-image
cp terraform/oracle/terraform.tfvars.example terraform/oracle/terraform.tfvars
# edit terraform.tfvars (private_key_path, OCIDs, bucket, image_path, ssh_public_key)
just oracle-tofu-init
just oracle-tofu-plan
just oracle-tofu-apply   # explicit — not run automatically
just oracle-tofu-backup-state   # gopass backup — run after apply
just deploy-oracle       # after instance exists — remote nixos-rebuild

Android (pixel-fold)

Must be run on-device from the Nix-on-Droid app (not over SSH — activation requires a real terminal):

git clone https://github.com/worldofgeese/den.git ~/.config/home-manager
cd ~/.config/home-manager
nix-on-droid switch --flake .#pixel-fold

Deploying

# Everything on mahakala (Guix System + Guix Home + Home Manager)
just deploy-mahakala

# Only Home Manager on mahakala
just deploy-mahakala-hm

# Only Guix Home
just deploy-mahakala-guix

# Only Guix System (requires sudo)
just deploy-mahakala-system

# Work MacBook (nix-darwin)
just deploy-darwin

# Home server (remote NixOS)
just deploy-paphos

# Oracle Cloud NixOS (remote aarch64)
just deploy-oracle

# Android phone (on-device only)
just deploy-pixel-fold

Manual fallback (run only if just is unavailable):

home-manager switch --flake .#worldofgeese              # mahakala HM
darwin-rebuild switch --flake .#M-02877                 # macOS
nixos-rebuild switch --flake .#paphos --target-host paphos --use-remote-sudo
nix-on-droid switch --flake .#pixel-fold                # Android (on-device)
sudo guix system reconfigure guix/system.scm            # Guix System
guix home reconfigure guix/home-configuration.scm       # Guix Home

There are also shell aliases on M-02877:

fleek-apply     # hostname-agnostic darwin-rebuild
apply-M-02877   # same thing, explicit

Upgrading

Everything at once

topgrade --yes

On mahakala, topgrade delegates the Nix and Guix work to the Justfile rather than its own built-in modules: its nix, home_manager, guix, system, and several other modules are disabled in workstation.nix, and pre-commands run just upgrade-kernel (CachyOS kernel metadata) then just deploy-mahakala (Guix System + Guix Home + Home Manager, including flake input and pinned Rust tool updates). topgrade itself then handles Doom Emacs, Distrobox, Homebrew-in-distrobox, and post-run garbage collection across Nix, Guix, Flatpak, and Podman. On M-02877 it runs just update-darwin (every flake input except the Linux-only ones listed in darwin-skip-inputs) and then just deploy-darwin; its built-in VS Code, VS Code Insiders, Cursor and Microsoft Office steps are disabled because those apps update themselves, and its macOS system step is disabled because installing OS updates needs an admin password and Jamf/Nudge manage them.

Just the Nix configuration

cd ~/.config/home-manager && just update
just deploy-darwin   # or deploy-mahakala-hm, etc.

A single flake input

just update-input nixpkgs

Updating den

# Update den to latest
nix flake update den

# Verify evaluation passes
nix eval .#darwinConfigurations.M-02877.config.home-manager.users --apply builtins.attrNames

# Build and switch
darwin-rebuild build --flake .#M-02877
sudo darwin-rebuild switch --flake .#M-02877

If eval fails after an update, check den's changelog and docs for migration notes:

Key things to know about our den usage:

  • den.batteries.define-user auto-sets home.username and home.homeDirectory based on host class
  • den.schema.user.classes = [ "homeManager" ] enables automatic HM forwarding for all users
  • Never manually import darwinModules.home-manager or wire home-manager.users.<name> — den handles forwarding from den.aspects.<user>.homeManager automatically

Upgrading Lix (macOS)

Lix comes from pkgs.lixPackageSets.latest.lix. Bump nixpkgs to get a newer Lix:

just update-input nixpkgs
just deploy-darwin
nix --version  # reflects the new Lix

Don't run nix upgrade-nix — it doesn't know about Lix's release channel.

Architecture

Every .nix file under modules/ is automatically imported as a top-level flake-parts module (via import-tree). Files prefixed with _ are skipped.

Key concepts

  • den.hosts.<system>.<hostname> — Declares a full system (NixOS or nix-darwin) with users
  • den.homes.<system>.<username> — Declares standalone Home Manager configs (no system management)
  • den.aspects.<name> — Reusable feature bundles with .nixos, .darwin, and .homeManager sub-attributes
  • den.default — Global settings applied to all hosts/homes

Exception: pixel-fold bypasses the Den framework entirely because nix-on-droid requires its own nixOnDroidConfiguration builder that is incompatible with flake-parts host wiring. It uses a raw flake.nixOnDroidConfigurations output and does not receive global Den defaults or aspects.

Module layout

modules/
├── dendritic.nix        # Imports Den framework
├── hosts.nix            # Host/home declarations
├── defaults.nix         # Global defaults + aspect wiring
├── worldofgeese.nix     # Shared user aspect (git, shell tools, direnv, atuin)
├── git-common.nix       # Shared git settings aspect (hosts add identity)
├── terminal.nix         # Shared terminal UX (Dracula delta, previews, persistence)
├── shared-devtools.nix  # Dev tooling shared by mahakala + M-02877
├── workstation.nix      # Desktop/dev packages + topgrade
├── ssh.nix              # Fleet SSH matchBlocks (Tailscale hostnames)
├── server.nix           # Reusable NixOS server aspect
├── overlays.nix         # Nixpkgs overlays (devenv, decapod)
├── mahakala.nix         # Wires worldofgeese → workstation + ssh (mahakala-specific)
├── M-02877/
│   ├── darwin.nix       # macOS system (Lix, Homebrew, Touch ID, zsh, system defaults)
│   └── dktaohan.nix     # Work user (separate git identity, full dev toolchain)
├── paphos/
│   ├── system.nix       # Auto-upgrade, users, sudo, boot
│   ├── hardware.nix     # Disks, LUKS, Dropbear initrd SSH
│   ├── networking.nix   # Tailscale, openssh, locale
│   └── forgejo.nix      # Forgejo + Forgesync + agenix secrets
├── oracle/
│   ├── _configuration.nix # OCI guest: ssh, cloud-init, firewall 22 (import-tree skipped)
│   └── image.nix        # packages.aarch64-linux.oracle-image + nixosConfigurations.oracle
└── pixel-fold/
    ├── system.nix       # nix-on-droid config (custom flake output, pinned inputs)
    └── _home.nix        # Home Manager config (skipped by import-tree, loaded by system.nix)

Secrets

Secrets are managed with agenix. Encrypted .age files live in secrets/ and are decrypted at activation time on the target host.

To edit a secret:

nix run github:ryantm/agenix -- -e secrets/forgejo-runner-token.age

Keys authorized to decrypt are declared in secrets/secrets.nix.

secretspec holds per-user API secrets, which are declared in secretspec.toml. Every secret goes through the personal provider alias. Home Manager (modules/shared-devtools.nix) generates ~/.config/secretspec/config.toml on each host to define it:

  • M-02877: personal is secretspec.age, an age-encrypted file committed next to secretspec.toml. It is encrypted to the two post-quantum public keys in secretspec.age.recipients:
    • this Mac's Secure Enclave key (age-plugin-se, Homebrew). It decrypts with no dialog and never touches the Keychain, so secretspec rebuilds no longer prompt (cachix/secretspec#438).
    • a backup key that lives only in your password manager.
  • Linux hosts: personal is the system keyring.

The Secure Enclave key can't leave the machine, and it has no access control, so any process running as you can decrypt while on this Mac. The backup key is the only way to read the file anywhere else. Keep it in a password manager.

On a new or rebuilt Mac, deploy, then re-key the file to that Mac's Secure Enclave:

pbpaste | just secretspec-se-setup   # after copying the backup key from the password manager
git add secretspec.age secretspec.age.recipients && git commit

After just secretspec-se-setup, secretspec set NAME writes to both recipients as usual.

Guix

The guix/ directory contains GNU Guix configurations for the mahakala workstation (system + home). These are managed independently from Nix. Use the Justfile recipes:

just deploy-mahakala-guix     # Guix Home only
just deploy-mahakala-system   # Guix System only (requires sudo)

Manual fallback (run only if just is unavailable):

sudo guix system reconfigure guix/system.scm
guix home reconfigure guix/home-configuration.scm

Guix manages: kernel, desktop environment (GNOME), system services (PipeWire, GPG agent, dbus), fonts, and desktop packages (Emacs, Podman, Steam, browsers).

Nix Home Manager manages: shell development tools, overlays, direnv, starship, atuin, and dev-oriented CLI packages.

The two coexist via ~/.nix-profile being sourced in the Guix shell profile.

nix-on-droid pinning

The pixel-fold configuration uses pinned versions of nixpkgs and home-manager (nixpkgs-nod and home-manager-nod inputs) rather than following the main nixpkgs-unstable. This works around a proot pty bug where glibc 2.42's TCGETS2 ioctl causes "getting pseudoterminal attributes: Permission denied" during activation.

The pins match nix-on-droid release-24.05's tested versions. Remove them once PR #529 (proot-termux update) is merged and you can reinstall from a new bootstrap zip.

Important: nix-on-droid switch must be run on-device (from the Nix-on-Droid app), not over SSH — the activation step requires a proper terminal.

Troubleshooting

error: getting status of '/nix/store/.../flake.nix': No such file or directory

Nix flakes require all referenced files to be tracked by git:

git add -A && git commit -m "track new files"

Unexpected files in /etc on first macOS apply

The Lix installer's /etc/nix/nix.conf conflicts with nix-darwin. Move it aside:

sudo mv /etc/nix/nix.conf /etc/nix/nix.conf.before-nix-darwin
just deploy-darwin

experimental Lix feature 'nix-command' is disabled

Happens during bootstrap before nix-darwin writes its own nix.conf. Pass the flag inline:

sudo -H nix --extra-experimental-features "nix-command flakes" run nix-darwin -- switch --flake .

After the first successful rebuild this goes away.

Hash mismatch / stale lock file

nix flake update

Guix activation fails with "Read-only file system" on ~/.ssh/

This happens when Home Manager's programs.ssh creates symlinks in ~/.ssh/ that Guix's activation script then tries to chmod. The Guix config already skips symlinks — if you see this, ensure guix/home-configuration.scm has the (unless (symbolic-link? file) ...) guard in the ssh-permissions service.

nix-on-droid "getting pseudoterminal attributes: Permission denied"

This is the proot pty bug. Ensure you're using the pinned inputs (nixpkgs-nod / home-manager-nod). If you've accidentally updated the lock, restore the pins:

nix flake update nixpkgs-nod home-manager-nod nix-on-droid

If you see this after a Nix-on-Droid app update, you may need to reinstall from a fresh bootstrap zip (wipes app data).

Uninstall

macOS (nix-darwin + Lix)

nix --extra-experimental-features "nix-command flakes" run nix-darwin#darwin-uninstaller
/nix/lix-installer uninstall

If you get "volume in use":

sudo diskutil unmountDisk force /nix && sudo diskutil apfs deleteVolume "Nix Store"

Linux (Home Manager + Nix)

home-manager uninstall
/nix/nix-installer uninstall  # or /nix/lix-installer uninstall

nix-on-droid

Uninstall the Nix-on-Droid app from Android settings.

Reference

About

Den mono-repo: unified Nix infrastructure for all hosts

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages