DEV Community

Alexander Shagov
Alexander Shagov

Posted on

Home Manager: never set up a laptop twice

How I restore my entire Mac user environment from one Git repo with a single command


I wrote earlier about why I use Nix on macOS. This is the follow-up: the same idea, applied to the whole machine instead of one project.

My entire user environment lives in a single Git repo. Packages, shell config, aliases, git, tmux, the editor. Everything I care about, basically. When I get a new Mac, I clone it, run one command, and I'm back.


The recipe is short:

  1. Install Nix (the Determinate installer, more on that in a sec).
  2. Clone the config repo into ~/.config/home-manager.
  3. Run home-manager switch.
# flakes on by default
curl -fsSL https://install.determinate.systems/nix | sh -s -- install

git clone https://github.com/you/your-config ~/.config/home-manager
cd ~/.config/home-manager

home-manager switch
Enter fullscreen mode Exit fullscreen mode

That last line does the real work. It builds the whole environment from flake.nix and home.nix and installs it. If I nuke the machine and start over, I just pull the repo, run the command, and end up on the exact same setup, because flake.lock pins every input to a fixed version.


Here's the config I actually run. The flake just declares inputs and points at home.nix:

{
  description = "Home Manager configuration of alexlaptop";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { nixpkgs, home-manager, ... }:
    let
      system = "x86_64-darwin";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      homeConfigurations."alexlaptop" = home-manager.lib.homeManagerConfiguration {
        inherit pkgs;
        modules = [ ./home.nix ];
      };
    };
}
Enter fullscreen mode Exit fullscreen mode

And home.nix is where everything I want lives: packages, aliases, shell, tools:

{ config, pkgs, ... }:

let
  shellAliases = {
    gs = "git status";
    gcm = "git checkout main && git pull";
    gc  = "git checkout";
    be  = "bundle exec";
    bi  = "bundle install";
  };
in
{
  home.username = "alexlaptop";
  home.homeDirectory = "/Users/alexlaptop";
  home.stateVersion = "26.05";

  home.packages = with pkgs; [
    ripgrep fd fzf bat tree jq htop
    direnv tmux
    ffmpeg_6 poppler-utils exiftool
    emacs-pgtk
    # ai
    claude-code
  ];

  home.shellAliases = shellAliases;
  home.sessionVariables = { EDITOR = "emacs"; };

  programs.home-manager.enable = true;

  programs.zsh = {
    enable = true;
    inherit shellAliases;
    initContent = ''
      autoload -Uz vcs_info
      precmd() { vcs_info; }
      zstyle ':vcs_info:*' enable git
      zstyle ':vcs_info:*' formats ' (%b)'
      setopt prompt_subst
      PROMPT='%~''${vcs_info_msg_0_} %# '
    '';
  };

  programs.direnv = {
    enable = true;
    nix-direnv.enable = true;
  };

  programs.fzf = {
    enable = true;
    enableZshIntegration = true;
  };

  programs.tmux = {
    enable = true;
    extraConfig = ''
      unbind C-b
      set -g prefix C-z
      bind C-z send-prefix
    '';
  };
}
Enter fullscreen mode Exit fullscreen mode

One thing worth saying is that I don't use nix-darwin. Home Manager looks after my user environment (the tools, the shell, the dotfiles). It doesn't touch macOS itself. Which is fine by me, honestly, because the things that drift between machines and eat my afternoon are exactly the things Home Manager already handles. If I ever want the system layer on top of that, nix-darwin is the obvious next step. Or the full NixOS, on a separate Linux laptop.. I just never felt the need.


A quick word on the installer, because it matters more than I expected. I use Determinate Systems' one rather than the stock Nix installer.

The main reason is flakes. Determinate turns on flakes and nix-command out of the box. By 2026 flakes are just how Nix works (every real project uses them), even though upstream still slaps the "experimental" label on them. That label's been sitting there since 2021 and at this point it's mostly inertia.

The other two are practical. Stock Nix had a nasty habit of breaking after a major macOS update. Determinate runs its own daemon, and I haven't had a dead /nix since I switched. And if I ever want out, every change is logged to /nix/receipt.json, so sudo /nix/nix-installer uninstall reverses the whole configuration cleanly.


Is it worth it? Well.. I'll be honest here, the Nix language is genuinely weird. The error messages are rough, and option names get renamed between releases, which is annoying. But still it is worth it. I don't think I'll ever spend an afternoon setting up a laptop again. Packages, dotfiles, the whole setup, exactly where I left them, from one repo and one command.

If you're interested, check the first steps article, understand the vocabulary before jumping into the configs, it's easy to get lost even though it boils down to few commands and files eventually.

You might also visit my personal blog for more topics (not only nix-related).

Thanks!

Top comments (0)