Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

dotfiles.nix wiki

Welcome to the dotfiles.nix documentation.

Nix Knowledge

Package broken, PR merged but no update yet?

Track PR with: https://nixpk.gs/pr-tracker.html

Repository Structure

.
├── flake.nix             # Entry point, input definitions, and output composition
├── lib/                  # Custom Nix library functions
│   ├── default.nix       # Library entry point
│   ├── scanPaths.nix     # Recursive module discovery logic
│   └── helpers.nix       # Flake helpers (makeOverlay, makePackages)
├── modules/              # Reusable modules
│   ├── shared/           # Cross-platform modules
│   │   ├── system/       # System-level config (e.g., core, packages)
│   │   └── home/         # Home Manager config (e.g., shell, editor)
│   └── darwin/           # macOS-specific modules
│       ├── system/       # macOS system settings
│       └── home/         # macOS Home Manager modules
└── hosts/                # Host-specific configurations
    └── mac/              # Configuration for "mac" host
        ├── import-sys.nix # Recursive system import
        └── import-hm.nix  # Recursive home-manager import

External Usage

This flake exposes its modules for consumption by other configurations (like nix-work), allowing for a layered configuration approach where dotfiles.nix provides the base.

Exported Modules

  • sharedModules: Core system configuration, packages, and shared Home Manager modules.
  • macModules: macOS-specific system modules and settings.

Output Structure

The flake outputs are structured to be easily consumed:

outputs = { ... }: {
  sharedModules = [ ... ]; # Base modules
  macModules = [ ... ];    # macOS modules
  lib = { ... };           # Helper library
};

Example: nix-work Consumption

To build a work configuration on top of this base:

{
  inputs.base.url = "github:ojsef39/dotfiles.nix";

  outputs = { base, ... }: {
    darwinConfigurations.workMac = darwin.lib.darwinSystem {
      modules =
        base.outputs.sharedModules
        ++ base.outputs.macModules
        ++ [
          ./work-specific-config.nix
        ];
      specialArgs = {
        baseLib = base.lib;
      };
    };
  };
}

Remote building

(with 1Password as SSH Agent)

nix build .#darwinConfigurations.mac.system --builders 'ssh://<user>@<ip> x86_64-linux,aarch64-darwin'

Caution

Make sure you ran sudo ssh <user>@<ip> first and accept the host key dialog, otherwise remote build will fail as that runs as root (nix daemon).

This only works because of modules/darwin/system/system.nix:

    activationScripts = {
      preActivation.text = ''
        plist="/Library/LaunchDaemons/systems.determinate.nix-daemon.plist"
        desired="/Users/${vars.user.name}/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"

        current=$(/usr/libexec/PlistBuddy -c "Print :EnvironmentVariables:SSH_AUTH_SOCK" "$plist" 2>/dev/null || echo "")

        if [ "$current" != "$desired" ]; then
          /usr/libexec/PlistBuddy -c "Add :EnvironmentVariables dict" "$plist" 2>/dev/null || true
          /usr/libexec/PlistBuddy -c "Set :EnvironmentVariables:SSH_AUTH_SOCK '$desired'" "$plist" 2>/dev/null || \
          /usr/libexec/PlistBuddy -c "Add :EnvironmentVariables:SSH_AUTH_SOCK string '$desired'" "$plist"

          if launchctl print system/systems.determinate.nix-daemon &>/dev/null; then
            launchctl unload /Library/LaunchDaemons/systems.determinate.nix-daemon.plist
            launchctl load /Library/LaunchDaemons/systems.determinate.nix-daemon.plist
          fi
        fi
      '';

      # example for linux
      # systemd.services.nix-daemon = {
      #   environment = {
      #     SSH_AUTH_SOCK = "/run/user/${builtins.toString config.users.users.${username}.uid}/${
      #       config.home-manager.users.${username}.services.ssh-agent.socket
      #     }";
      #   };
      # };
    };
  };

Setup & Deployment

This configuration is managed using nh (Nix Helper) and just.

#!/usr/bin/env just --justfile

alias d := deploy
alias u := upgrade

# macOS need nh darwin switch and NixOS needs nh os switch
nix_cmd := `if [ "$(uname)" = "Darwin" ]; then echo "darwin"; else echo "os"; fi`
nix_host := `if [ "$(uname)" = "Darwin" ]; then echo "mac"; else echo "nixos"; fi`
# Use GITHUB_TOKEN from 1Password to prevent rate limiting
nix_flags := `if [ "${GITHUB_ACTIONS:-}" != "true" ]; then echo '--option access-tokens github.com=$(op read op://Personal/GITHUB_TOKEN/no_access)'; fi`

[doc('HELP')]
default:
    @just --list --list-prefix "    " --list-heading $'🔧 Available Commands:\n'

[group('nix')]
[doc('Deploy system configuration')]
deploy: lint
    # Deploying system configuration without update...
    @git pull || true
    @git add .
    @nh {{nix_cmd}} switch -a -H {{nix_host}} $NIX_GIT_PATH -- {{nix_flags}}

[group('nix')]
[doc('Deploy system configuration')]
deploy-update: lint
    # Deploying system configuration with update...
    @git pull || true
    @git add .
    @nh {{nix_cmd}} switch -u -a -H {{nix_host}} $NIX_GIT_PATH -- {{nix_flags}}

[group('nix')]
[doc('Upgrade flake inputs and deploy')]
upgrade: update-refs lint
    @git pull || true
    @git add .
    @nh {{nix_cmd}} switch -u -a -H {{nix_host}} $NIX_GIT_PATH -- {{nix_flags}}
    @git add .
    @if git log -1 --pretty=%B | grep -q "chore(deps): updated inputs and refs"; then \
        echo "Amending previous dependency update commit..."; \
        git commit --amend --no-edit || true; \
    else \
        git commit -m "chore(deps): updated inputs and/or refs" || true; \
    fi


[group('nix')]
[doc('Update every fetcher with its newest commit and hash')]
update-refs:
    # Update current repository
    @kitten @ launch --type=overlay --title="update-nix-fetchgit-all" --copy-env --env SKIP_FF=1 fish -c "cd $NIX_GIT_PATH && update-nix-fetchgit-all"

[group('maintain')]
[doc('Clean and optimise the nix store with nh')]
clean:
    @nh clean all -a -k 2 -K 7d

[group('maintain')]
[doc('Optimise the nix store')]
optimise:
    @nix store optimise -v

[group('maintain')]
[doc('Verify and repair the nix-store')]
repair:
    @sudo nix-store --verify --check-contents --repair || true

[group('maintain')]
[doc('Selectively rollback flake inputs')]
rollback:
    @./scripts/flake-rollback.fish

[group('lint')]
[doc('Lint all nix files using statix and deadnix')]
lint: format
    @nix run {{nix_flags}} nixpkgs#statix -- check .
    @nix run {{nix_flags}} nixpkgs#deadnix -- -eq .

[group('lint')]
[doc('Format files using alejandra')]
format:
    @nix run {{nix_flags}} nixpkgs#alejandra -- .

[group('lint')]
[doc('Show diff between current and commited changes')]
diff:
    git diff ':!flake.lock'

Docker - Building container images with Nix

Example flake showing buildLayeredImage and buildImage with Ubuntu base.

Try it out

cd wiki/docker && nix develop -c $SHELL

or with nix-output-monitor (nom develop -c $SHELL) for a nice overview of the build progress

This will build and load two Docker images:

  • nix-shell:latest - Pure Nix image using buildLayeredImage
  • ubuntu-nix:latest - Ubuntu base with Nix tools using buildImage

flake.nix

{
  description = "Nix Docker image examples";

  inputs = {
    nixpkgs.url = "https://flakehub.com/f/JHOFER-Cloud/NixOS-nixpkgs/0.1.tar.gz"; # usually github:NixOS/nixpkgs/nixpkgs-unstable
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = {
    nixpkgs,
    flake-utils,
    ...
  }:
    flake-utils.lib.eachDefaultSystem (system: let
      pkgs = nixpkgs.legacyPackages.${system};

      # Get Linux packages - works on macOS with Determinate's built-in linux-builder
      # This uses the linux-builder to build natively for Linux (fast, fully cached)
      linuxPkgs = import pkgs.path {system = "x86_64-linux";};

      # Alternative: Cross-compile to Linux (no cache, slower first build)
      # linuxPkgs = pkgs.pkgsCross.musl64;

      # Example 1: buildLayeredImage - pure Nix, from scratch
      layeredImage = pkgs.dockerTools.buildLayeredImage {
        name = "nix-shell";
        tag = "latest";
        contents = [linuxPkgs.busybox];
        config.Cmd = ["/bin/sh"];
      };

      # Example 2: buildImage with Ubuntu base
      ubuntuBase = pkgs.dockerTools.pullImage {
        imageName = "ubuntu";
        imageDigest = "sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b";
        sha256 = "sha256-9vmJV6M21tZPk39lCCdCrwHR55gNzO1ka2De51IR9qs=";
        finalImageTag = "24.04";
      };

      ubuntuImage = pkgs.dockerTools.buildImage {
        name = "ubuntu-nix";
        tag = "latest";
        fromImage = ubuntuBase;
        copyToRoot = pkgs.buildEnv {
          name = "nix-tools";
          paths = [linuxPkgs.busybox];
        };
        config.Cmd = ["/bin/sh"];
      };
    in {
      devShells.default = pkgs.mkShell {
        shellHook = ''
          echo "Loading Docker images..."

          if command -v podman &>/dev/null; then
            podman load < ${layeredImage}
            podman load < ${ubuntuImage}
          elif command -v docker &>/dev/null; then
            docker load < ${layeredImage}
            docker load < ${ubuntuImage}
          else
            echo "No container runtime found"
            exit 1
          fi

          echo "Images loaded: ${layeredImage.imageName}:${layeredImage.imageTag}, ${ubuntuImage.imageName}:${ubuntuImage.imageTag}"
        '';
      };
    });
}

How it works

The flake uses Determinate’s built-in Linux builder on macOS:

linuxPkgs = import pkgs.path {system = "x86_64-linux";};

This builds natively for Linux using the remote builder, which is fast and fully cached.

Cross-compilation Alternative

Alternatively, you can cross-compile using:

linuxPkgs = pkgs.pkgsCross.musl64;

Note: This approach has no binary cache, so first builds will be slower.

Resources