I discovered Nix last year and have been impressed by its capabilities. Over the past few months, I’ve been working on my own Nix configuration and exploring many excellent repositories. However, as I started building my setup, I encountered some confusion about Nix-Darwin and Home-Manager.
First, Nix-Darwin seems to be designed primarily for system-level configurations on macOS, with its options typically targeting the system domain rather than individual users. My goal is to manage most of my configuration at the user level so I can easily share it across multiple macOS devices under the same user account. It seems that Nix-Darwin alone can’t achieve this.
As a result, I turned to Home-Manager. While it addresses user-specific configurations, I found it insufficient for managing some macOS-specific applications, like Obsidian. Certain macOS-specific software seems to fall outside Home-Manager’s capabilities. How can I resolve these two issues?
In addition, I’m using the Snowfall framework to organize my Nix configuration. While helpful, I’m finding it difficult to effectively reuse Nix code. I’d like to separate system configurations from custom configurations to make them reusable across different devices and user accounts. Are there any libraries or frameworks you would recommend for improving code reusability?
If you want to write a new HM module for the software you use, feel free to do so. Just know that you’ll have to become familiar with how that program stores its config. The app also needs to be able to follow symlinks while not trying to overwrite the target of said symlink (since that target will generally be in the read-only nix store).
Also Obsidian isn’t MacOS specific, btw
Ultimately, the module system itself should already permit that. Splitting things up into separate files allows you to add the common bits to the imports of all your configs. If you need something more, please be more specific, including sharing code.
I use modules to share code between systems. I used to try to be fancy with a home-grown flake framework, but it was harder to understand.
Note that Home Manager can be managed by nix-darwin. I use both together with nix-darwin handling system configuration and Home Manager handling user configuration.
Sorry for not being clear earlier. When I mentioned “specific packages,” I was referring to those not available in Nixpkgs, which I usually install via Homebrew. While reviewing the packages I use daily, I was surprised to find that Raycast is already in Nixpkgs. I’m now trying to learn from its example to make Obsidian work natively with home-manager.
As for sharing configurations, here’s my plan: I have multiple macOS devices to manage—some for server purposes and others for desktop use. While their roles differ, they share several common options that I don’t want to duplicate across configurations. I’m looking for a framework or library that can centralize all shared configurations logically, without simply splitting them into multiple unrelated .nix files.
I’ve open my simple repository here. I using snowfall framework to organize my configurations, but I’m still encountering an issue:
homes/aarch64-darwin/teamo@blaze/default.nix:
{
lib,
namespace,
...
}: let
inherit (lib.${namespace}) enabled;
in {
teamo = {
# I don't know why it doesn't work.
# home = {
# enable = true;
# stateVersion = "24.11";
# };
# user = {
# enable = true;
# email = "1157757077@qq.com";
# };
... other code ...
};
# It should be stay in `home/default.nix`, but it doesn't work, so I have to put it here.
programs.home-manager = enabled;
home.stateVersion = "24.11";
}
modules/home/home/deafult.nix:
{
lib,
config,
namespace,
...
}: let
inherit (lib) mkIf;
inherit (lib.${namespace}) mkStrOpt mkBoolOpt enabled;
cfg = config.${namespace}.home;
in {
options.${namespace}.home = {
enable = mkBoolOpt false "Whether or not enable home configuration.";
stateVersion = mkStrOpt "" "The state version of the home configuration.";
};
config = mkIf cfg.enable {
programs.home-manager = enabled;
home.stateVersion = cfg.stateVersion;
};
}
modules/home/user/default.nix:
{
lib,
config,
system,
namespace,
...
}: let
inherit (lib) mkIf;
inherit (lib.${namespace}) mkStrOpt mkBoolOpt;
inherit (lib.snowfall.systems) isDarwin;
cfg = config.${namespace}.user;
in {
options.${namespace}.user = {
enable = mkBoolOpt false "Whether or not enable user configuration.";
name = mkStrOpt config.snowfall.user.name "The name of the user.";
email = mkStrOpt "" "The email address of the user.";
home = mkStrOpt (
if isDarwin system
then "/Users/${cfg.name}"
else "/home/${cfg.name}"
) "The home directory of the user.";
};
config = mkIf cfg.enable {
home = {
username = cfg.name;
homeDirectory = cfg.home;
};
};
}
Could you help me understand why the code above doesn’t work? I’d greatly appreciate your guidance.
teamo is my namespace, managed by the Snowfall framework. If I uncomment the code, I encounter two errors:
teamo@blaze:~/.config/nix-darwin/ > darwin-rebuild switch --flake .
building the system configuration...
warning: Git tree '/Users/teamo/.config/nix-darwin' is dirty
error:
… while evaluating the attribute 'value'
at /nix/store/awsvw44jla0idziiks2zwgzslfd2dczn-source/lib/modules.nix:816:9:
815| in warnDeprecation opt //
816| { value = addErrorContext "while evaluating the option `${showOption loc}':" value;
| ^
817| inherit (res.defsFinal') highestPrio;
… while evaluating the option `system.build':
… while evaluating the attribute 'mergedValue'
at /nix/store/awsvw44jla0idziiks2zwgzslfd2dczn-source/lib/modules.nix:851:5:
850| # Type-check the remaining definitions, and merge them. Or throw if no definitions.
851| mergedValue =
| ^
852| if isDefined then
… while evaluating definitions from `/nix/store/wy0a6vicf8c9hhnq92p4y58ph06i6ivm-source/modules/environment':
… while evaluating the option `environment.systemPath':
… while evaluating definitions from `/nix/store/wy0a6vicf8c9hhnq92p4y58ph06i6ivm-source/modules/environment':
… while evaluating the option `environment.profiles':
… while evaluating definitions from `/nix/store/wy0a6vicf8c9hhnq92p4y58ph06i6ivm-source/modules/users':
… while evaluating the option `users.users.teamo.packages':
… while evaluating definitions from `/nix/store/cd0q2vk9vqwjg9rcilymm4x8f4n673zi-source/nixos/common.nix':
(stack trace truncated; use '--show-trace' to show the full, detailed trace)
error: The option `home-manager.users.teamo.teamo.home' does not exist. Definition values:
- In `/nix/store/pkx7fh2k4grdxjk6psjlwixfha5gp4n8-q0x3q0gziibwiy687cgjcy78iclgad7a-source/homes/aarch64-darwin/teamo@blaze/default.nix':
{
enable = true;
stateVersion = "24.11";
}
I think I’ve identified the problem. If I modify the file without adding it to the Git stage, the build will succeed initially but later result in an error like this:
warning: Git tree '/Users/teamo/.config/nix-darwin' is dirty
error:
… while evaluating the attribute 'value'
at /nix/store/awsvw44jla0idziiks2zwgzslfd2dczn-source/lib/modules.nix:816:9:
815| in warnDeprecation opt //
816| { value = addErrorContext "while evaluating the option `${showOption loc}':" value;
| ^
817| inherit (res.defsFinal') highestPrio;
… while evaluating the option `system.build':
… while evaluating the attribute 'mergedValue'
at /nix/store/awsvw44jla0idziiks2zwgzslfd2dczn-source/lib/modules.nix:851:5:
850| # Type-check the remaining definitions, and merge them. Or throw if no definitions.
851| mergedValue =
| ^
852| if isDefined then
… while evaluating definitions from `/nix/store/wy0a6vicf8c9hhnq92p4y58ph06i6ivm-source/modules/environment':
… while evaluating the option `environment.systemPath':
… while evaluating definitions from `/nix/store/wy0a6vicf8c9hhnq92p4y58ph06i6ivm-source/modules/environment':
… while evaluating the option `environment.profiles':
… while evaluating definitions from `/nix/store/wy0a6vicf8c9hhnq92p4y58ph06i6ivm-source/modules/users':
… while evaluating the option `users.users.teamo.packages':
… while evaluating definitions from `/nix/store/cd0q2vk9vqwjg9rcilymm4x8f4n673zi-source/nixos/common.nix':
(stack trace truncated; use '--show-trace' to show the full, detailed trace)
error: The option `home-manager.users.teamo.teamo.programs.gui.browser' does not exist. Definition values:
- In `/nix/store/wjhgbvxma3cgqpldmclpk68gz0rrasfs-f0r660mhyj4g66qk186jbm1nnmyrr4sj-source/homes/aarch64-darwin/teamo@blaze/default.nix':
{
chrome = {
enable = true;
};
}
it’s easily to reproduction, you should just make a bran new home-manager module, like this:
I don’t know how snowfall works, but yes, all config files must be in the git index when using flakes in a git repo, since nix will only copy those files to the store.