iTranslated by AI
Managing Multiple Linux/Mac Environments with 1 Repository and 1 Command (For Nix Beginners)
Introduction
In this article, I will explain how I manage my user environment, which also serves as a personal memo.
When using multiple PCs, it is tedious to manually install tools like Git on each one or to copy and maintain .gitconfig files. Moreover, managing separate dotfiles repositories for WSL and Mac is also a hassle.
To address this, I use home-manager to manage my user environment.
I have structured my settings so that I can build user environments for multiple Linux/Mac machines using 1 repository + 1 command.
In this article, I will show you how to set up your environment so that by simply running git clone <dotfiles> and home-manager switch, you can construct your user environment (installing tools like Git, placing .gitconfig, etc.) tailored to your specific machine.
Article Structure
We will proceed in the following order:
-
- Preparing the New Environment (WSL Ubuntu 22.04)
-
- Installing Nix and home-manager
-
- Configuring home-manager
-
- Applying the User Environment
-
- Managing Mac and WSL Environments Simultaneously
In this article, we will set up a new Ubuntu environment in WSL as a sample. After the WSL user environment is complete, we will configure settings to manage both Mac and WSL environments simultaneously.
Verification Environment
I will use Windows 11 WSL2 Ubuntu 22.04. Details are collapsed as they are lengthy.
Verification Environment
Windows Information.
> systeminfo | findstr /B /C:"OS"
OS Name: Microsoft Windows 11 Home
OS Version: 10.0.26100 N/A Build 26100
# Omitted
> wsl --version
WSL Version: 2.6.1.0
Kernel Version: 6.6.87.2-1
WSLg Version: 1.0.66
MSRDC Version: 1.2.6353
Direct3D Version: 1.611.1-81528511
DXCore Version: 10.0.26100.1-240331-1435.ge-release
Windows Version: 10.0.26100.7171
WSL Ubuntu Information.
$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Ubuntu 22.04.5 LTS"
# After installing Nix and home-manager
$ nix --version
nix (Determinate Nix 3.13.2) 2.32.4
$ home-manager --version
26.05-pre
1. Preparing the New Environment (WSL Ubuntu 22.04)
Install Ubuntu-22.04 on WSL.
> wsl --install Ubuntu-22.04
# ...
Enter new UNIX username: ryu_trifolium
# ...
From here on, operations will be within WSL.
wsl -d Ubuntu-22.04
2. Installing Nix and home-manager
2.1 Nix
We will use the installer from Determinate Systems to easily install Nix with a single command.
Run the following command.
You will be prompted to confirm the installation; enter y.
curl -fsSL https://install.determinate.systems/nix | sh -s -- install --determinate
To activate the Nix command, either restart your shell or run the following command:
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
Execution Log
Collapsed as it is long.
$ curl -fsSL https://install.determinate.systems/nix | sh -s -- install --determinate
info: downloading installer
INFO nix-installer v3.13.2
`nix-installer` needs to run as `root`, attempting to escalate now via `sudo`...
[sudo] password for ryu_trifolium:
INFO nix-installer v3.13.2
Nix install plan (v3.13.2)
Planner: linux
Configured settings:
* determinate_nix: true
Planned actions:
* Create directory `/nix`
* Install Determinate Nixd
* Extract the bundled Nix (originally from /nix/store/i130n1p2i0l1kyx0xa9fcn4d8jjrjb4b-nix-binary-tarball-3.13.2/nix-3.13.2-x86_64-linux.tar.xz) to `/nix/temp-install-dir`
* Create a directory tree in `/nix`
* Synchronize /nix/var ownership
* Move the downloaded Nix into `/nix`
* Synchronize /nix/store ownership
* Create build users (UID 30001-30032) and group (GID 30000)
* Setup the default Nix profile
* Place the Nix configuration in `/etc/nix/nix.conf`
* Configure the shell profiles
* Configure the Determinate Nix daemon
* Remove directory `/nix/temp-install-dir`
Proceed? ([Y]es/[n]o/[e]xplain): y
INFO Step: Create directory `/nix`
INFO Step: Install Determinate Nixd
INFO Step: Provision Nix
INFO Step: Create build users (UID 30001-30032) and group (GID 30000)
INFO Step: Configure Nix
INFO Step: Create directory `/etc/tmpfiles.d`
INFO Step: Configure the Determinate Nix daemon
INFO Step: Remove directory `/nix/temp-install-dir`
Nix was installed successfully!
To get started using Nix, open a new shell or run `. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh`
2.2 home-manager
Install according to the "Nix Flakes - Standalone setup" section of the official manual.
Run the following command:
nix run home-manager/master -- init
This will generate home.nix and flake.nix inside the /home/ryu_trifolium/.config/home-manager/ folder.
3. Setting up home-manager
Define your user environment settings in home.nix.
Specify which .nix files to load in flake.nix.
Edit the files using your preferred editor.
cd /home/ryu_trifolium/.config/home-manager
code .
3.1 home.nix
The automatically generated home.nix includes configuration instructions in the form of # comments, so please feel free to read them if you are interested.
Here, we will configure Git, Bash, and the .gitconfig, .bashrc, and .profile files.
I have also added an environment variable named GREETING for demonstration purposes.
{ config, pkgs, ... }:
{
home.username = "ryu_trifolium";
home.homeDirectory = "/home/ryu_trifolium";
home.stateVersion = "25.11";
home.packages = [
pkgs.git
pkgs.bash
];
home.file = {
".gitconfig".source = ./git/.gitconfig;
".bashrc".source = ./bash/.bashrc;
".profile".source = ./bash/.profile;
};
home.sessionVariables = {
GREETING = "Hello Nix";
};
programs.home-manager.enable = true;
}
Detailed explanations of the settings follow below.
3.1.1 User and Version Information
home.username = "ryu_trifolium";
home.homeDirectory = "/home/ryu_trifolium";
home.stateVersion = "25.11";
You can leave these as they were automatically generated.
When updating home-manager, update the stateVersion. Please check the release notes for the appropriate value.
Note that I will explain how to perform updates later.
3.1.2 Managing Tools
home.packages = [
pkgs.git
pkgs.bash
];
List the tools you want to use in your user environment here.
You can search for available tools at NixOS Search - Packages. Since there are about 100,000 tools registered in the NixOS/nixpkgs repository, you should be able to find most of them.
Example search
Open the site and enter vim in the search bar.
Select unstable for the Channel.
I will skip the explanation of Channels, but if you are not using NixOS,
unstableis fine.
Click on vim (the part in blue text) and check the command listed under How to install vim?.
If you write the string following -p in nix-shell -p vim into your home.nix, you can install vim.

Nix automatically installs tools according to the platform, but some tools may not support every platform. Please double-check if your PC is supported, for example aarch64-darwin for M1 Macs.
Most cases are supported in standard Windows WSL environments (x86_64-linux).
I have the impression that some tools are occasionally unsupported on M1 Macs (aarch64-darwin). I am not familiar with others, but the level of support seems to be x86_64-linux > aarch64-darwin > others.

How to write pkgs.git
If you see other people's settings, they might be written as follows:
home.packages = with pkgs; [
git
bash
];
This is equivalent to:
home.packages = [
pkgs.git
pkgs.bash
];
3.1.3 Managing Configuration Files
home.file = {
".gitconfig".source = ./git/.gitconfig;
".bashrc".source = ./bash/.bashrc;
".profile".source = ./bash/.profile;
};
You can specify configuration files here. This implies that the file located at ./git/.gitconfig will be used as .gitconfig.
The files are specified using paths relative to home.nix.
In my case, I copied the .gitconfig I use daily. I also copied my existing ~/.bashrc for Bash. Since environment variables set by home-manager will not be reflected in Bash as-is, I added configuration to .bashrc.
$ mkdir bash
$ cp ~/.bashrc bash/.bashrc
$ cat << 'EOF' >> bash/.bashrc
# for home-manager
. "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh"
EOF
~/.config/home-manager/
├── bash
│ ├── .bashrc
│ └── .profile
├── flake.nix
├── git
│ └── .gitconfig
└── home.nix
There are multiple ways to specify configuration files; for other methods, please see here:
Technical supplement
Adding . "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh" to .profile is the official explanation, but on WSL, it is inconvenient because the environment is not reflected without re-logging in. Therefore, I added it to .bashrc.
Also, I found options like programs.bash.bashrcExtra and programs.bash.profileExtra.
If you use these, you might not need to add . "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh".
I haven't actually tried this, so I have no proof.
I wanted to keep the appearance as simple as possible this time, so I deliberately chose the method of using home.file.".profile".source.
3.1.4 Managing Environment Variables
home.sessionVariables = {
GREETING = "Hello Nix";
};
You can set environment variables as shown above.
3.2 flake.nix
The automatically generated state works fine as is.
Parts like system = "x86_64-linux" and homeConfigurations."ryu_trifolium" are automatically configured according to the execution environment.
When managing both WSL and Mac environments with a single
flake.nixin the latter part of the article, we will manually edit these parts.
{
description = "Home Manager configuration of ryu_trifolium";
inputs = {
# Specify the source of Home Manager and Nixpkgs.
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-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
homeConfigurations."ryu_trifolium" = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
# Specify your home configuration modules here, for example,
# the path to your home.nix.
modules = [ ./home.nix ];
# Optionally use extraSpecialArgs
# to pass through arguments to home.nix
};
};
}
4. Applying the User Environment
Run the following command.
nix run home-manager/master -- init --switch -b backup
Explanation of the command
The official reference specifies nix run home-manager/master -- init --switch, but I have added -b backup.
In this case, I copied .bashrc from ~/.bashrc.
If you write home.file.".bashrc".source = ./bash/.bashrc;, home-manager will try to place .bashrc in ~/.
Therefore, this would cause an error due to a collision with the existing file.
By adding -b backup, it renames the original file to <filename>.backup before placing the new .bashrc.
From next time, you can update your user environment after updating home.nix with the following command:
home-manager switch
4.1 Updating home-manager and tools
To update the home-manager base and the tools introduced via home-manager, run the following commands.
Execute these in the directory containing home-manager/flake.nix.
$ nix flake update
# To update only home-manager
$ nix flake lock --update-input home-manager
# To update only tools introduced by home-manager
$ nix flake lock --update-input nixpkgs
After updating the tools with the commands above, run switch to apply them to your user environment.
home-manager switch
When updating the home-manager base, be sure to check the release notes and update the stateVersion.
home.stateVersion = "25.11";
5. Managing Mac and WSL Environments Simultaneously
Assuming that we will manage the Mac environment as well, in addition to the WSL environment created earlier.
We will proceed with the following steps:
- Prepare the dotfiles repository
- Create a file defining common settings (
common.nix) - Create files defining environment-specific settings (
wsl.nix,mac.nix) - Verify the
USERandHOSTNAMEof each environment - Edit
flake.nix - Apply changes with
home-manager switch
home-manager/
├─ home/
│ ├─ common.nix
│ ├─ mac.nix
│ └─ wsl.nix
├─ flake.nix
├─ git/
├─ bash/
└─ zsh/
5.1 Preparing the dotfiles repository
Manage the ~/.config/home-manager/ directory we worked on earlier using Git.
You can manage it directly within the folder, or as I do, manage it in ~/work/dotfiles and create a symbolic link from dotfiles/home-manager to ~/.config/home-manager.
Feel free to use whichever method you prefer.
5.2 Creating the file for common settings
Create a file named common.nix that extracts only the common elements among settings described in home.nix.
home-manager/
└─ home/
+ └─ common.nix
{ config, pkgs, ... }:
{
programs.home-manager.enable = true;
home.stateVersion = "25.11";
home.packages = with pkgs; [
git
];
home.file = {
".gitconfig".source = ../git/.gitconfig;
};
}
5.3 Creating files for environment-specific settings
Create wsl.nix and mac.nix, which are configuration files dedicated to WSL and MacBook respectively.
Specify the appropriate values for home.username and home.homeDirectory according to your environment.
home-manager/
└─ home/
├─ common.nix
+ ├─ mac.nix
+ └─ wsl.nix
{ config, pkgs, ... }:
{
home.username = "ryu_trifolium";
home.homeDirectory = "/home/ryu_trifolium";
home.packages = with pkgs; [
bash
# Specify other tools used only on WSL
];
home.file = {
".bashrc".source = ../bash/.bashrc;
".profile".source = ../bash/.profile;
};
}
{ config, pkgs, ... }:
{
home.username = "ryu";
home.homeDirectory = "/Users/ryu";
home.packages = with pkgs; [
zsh
# Specify other tools used only on Mac
];
home.file = {
".zshrc".source = ../zsh/.zshrc;
".profile".source = ../zsh/.profile;
};
}
5.4 Verifying USER and HOSTNAME for each environment
The following commands are examples from my environment.
Note them down as they will be used in the next step.
$ echo $USER
ryu_trifolium
$ hostname
main
$ echo $USER
ryu
$ hostname
MacBook.local
5.5 Editing flake.nix
By writing homeConfigurations.USER@HOSTNAME in flake.nix, you can specify configurations for a particular environment.
For example, my WSL environment looks like this:
# USER = ryu_trifolium, HOSTNAME = main
homeConfigurations."ryu_trifolium@main" = home-manager.lib.homeManagerConfiguration {
# Specify the system for your environment; for M1 Mac, use aarch64-darwin
pkgs = nixpkgs.legacyPackages.x86_64-linux;
# Specify the configuration files to load
modules = [
./home/common.nix
./home/wsl.nix
];
};
Here is the full example code for flake.nix:
home-manager/
├─ home/
│ ├─ common.nix
│ ├─ mac.nix
│ └─ wsl.nix
+└─ flake.nix
{
description = "Home Manager configuration";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
nixpkgs,
home-manager,
...
}:
{
homeConfigurations = {
# Main desktop PC, Windows 11, WSL (Ubuntu 22.04.5 LTS)
"ryu_trifolium@main" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.x86_64-linux;
modules = [
./home/common.nix
./home/wsl.nix
];
};
# MacBook Pro M1
"ryu@MacBook.local" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.aarch64-darwin;
modules = [
./home/common.nix
./home/mac.nix
];
};
};
};
}
5.6 Applying the environment
You can update the environment with the following command:
It automatically detects and loads the homeConfigurations that matches the USER and HOSTNAME of the command execution environment.
home-manager switch
In this case, since we are executing it on WSL, the settings written in wsl.nix and common.nix are applied.
5.7 Building the user environment on Mac
Assuming Nix is not already present on your Mac, user environment setup can be completed in a few commands:
# Install Nix
curl -fsSL https://install.determinate.systems/nix | sh -s -- install --determinate
# Place the contents of ~/.config/home-manager/ into the home-manager/ managed by dotfiles
git clone <your_dotfiles>
ln -s ~/work/dotfiles/home-manager ~/.config/home-manager
# Apply environment
home-manager switch
Conclusion
This completes the setup.
While "package manager Nix" might sound intimidating due to the need to write settings in the purely functional "Nix language," I hope this example conveys that it is not that difficult when simply managing a user environment with home-manager.
As an aside, using the devShell feature of Flakes, you can also build development environments with a similar syntax.
devShells.default = pkgs.mkShell {
packages = [
pkgs.terraform
pkgs.azure-cli
pkgs.go-task
];
};
If you are interested, the following books are very easy to understand, so please read them.
I believe my dotfiles management is not fully polished yet, so if there are any parts that seem "off," I would appreciate your comments.
(Reference) About homeConfigurations
I searched for details on homeConfigurations using human and AI, but could not find official documentation.
The following is an old thread from 2022, which mentions that the logic is not written in the manual, and I was in the same situation.
It should be explained in the manual: https://nix-community.github.io/home-manager/index.html#ch-nix-flakes
The extra logic around managing user+hostname has gone undocumented though. The scripts for this are easy to read if you need more detail.
Therefore, I checked the source code.
It appears to be a feature of the home-manager switch command.
The following content is based on my speculation, so please take it with a grain of salt.
This file: home-manager/home-manager from which the following code excerpt is taken seems to be the relevant logic.
#!@bash@/bin/bash
# Omitted...
function setFlakeAttribute() {
# Omitted...
local name="$USER"
# Check FQDN, long, and short hostnames; long first to preserve
# pre-existing behaviour in case both happen to be defined.
for n in "$USER@$(hostname -f)" "$USER@$(hostname)" "$USER@$(hostname -s)"; do
if [[ "$(nix eval "$flake#homeConfigurations" --apply "x: x ? \"$(escapeForNix "$n")\"")" == "true" ]]; then
name="$n"
if [[ -v VERBOSE ]]; then
echo "Using flake homeConfiguration for $name"
fi
fi
done
# Omitted...
In function doSwitch(), function setFlakeAttribute() and function doBuildFlake() are called in that order.
Based on the processing of function setFlakeAttribute(), it seems the rules are as follows:
- It checks if the name in
flake.nix'shomeConfigurationsmatches any of the following 3 patterns:$USER@$(hostname -f)$USER@$(hostname)$USER@$(hostname -s)
- If there is a match, it assigns the name to
name. - -> Uses the information in
homeConfiguration.namein subsequent processes.
This mechanism allows it to automatically read the USER and HOSTNAME of the environment and switch the configuration file to load.
(Reference) Other methods
I considered other methods while finding this one.
I'll leave a brief memo here for my own reference.
I investigated how to implement Method 3 more easily, and this led me to my current approach.
1. Create a dotfiles repository per OS and prepare home.nix for each
- Cannot share common settings between WSL and Mac; management costs are high.
2. Prepare home.nix in a single dotfiles repository and try to use if-statements
- Not elegant (subjective)
- Poor readability because descriptions for each environment are mixed together.
- Reference: Section "I want to write branches for each OS?"
3. Support multiple platforms in a single home.nix
- The
home-manager switch --flake .#hoge-user --impurecommand is long, and it becomesimpure. - Settings cannot be separated by environment.
- Reference: Section "Trying to make it look nice"
Discussion