Home manager module conflict
It seems like we can't use the home manager module alongside the nixosModule, while this is most likely misconfiguration, I wonder if anyone's run into this?
error: The option `age.identityPaths' in `/nix/store/5mg8m013dk1dv3sl934ki6nbldgv23yf-source/modules/age-home.nix' is already declared in `/nix/store/5mg8m013dk1dv3sl934ki6nbldgv23yf-source/modules/age.nix
Same error here
possible dup: #263
If this can help you I'm using both modules as I have system secrets and personal secrets.
What is do to avoid your error which I already encountered is this:
- I load the nixosModule in my flake and pass the agenix input as extraSpecialArgs of my home-manager module like this :
modules =
[
agenix.nixosModules.default
home-manager.nixosModules.home-manager
{
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
users.whateverUser = import ./config/home/home.nix;
extraSpecialArgs = {
inherit unstablePkgs;
agenix = agenix;
};
};
}
.......
- Then in my
home.nix(or wherever you put this if you split your whole configuration) I import the homeManagerModules:
{ agenix, ... }:
{
imports = [
agenix.homeManagerModules.default
];
age.identityPaths = [ "whereverIsMySecretsSSHKey" ];
age.secrets = { Your secrets definition };
}
This works well on my setups. Maybe this is completely stupid and I would be happy to know the correct way of doing this, but including both modules on flake level ended up in the same issue you were presented.
Hope this helps.
@Erwyn: I tried your idea, but I always run into errors like this:
"\${XDG_RUNTIME_DIR}/agenix/xxx" is not of type `absolute path'.
when using expressions like home.file."xxx".source = config.age.secrets."xxx".path;
The problem is that, if using home-manager as a nixos module, there is no XDG_RUNTIME_DIR (the default path for the agenix secrets) for a normal user to evaluate, since I run nixos rebuild as root. I tried setting age.secretsDir to an absolute path inside my home, but that results in an impurity error like this:
error: access to absolute path '/home' is forbidden in pure evaluation mode (use '--impure' to override)
So, how are you actually able to use those user specific secrets? Can please you provide an example?
I tried setting
age.secretsDirto an absolute path inside my home, but that results in an impurity error
I think the thing to understand is that age.secretsDir is actually a string, not a path. If you do
age.secretsDir = /home/foo/bar; # WILL NOT WORK!
It will try to resolve the path (and will get an impurity error), then convert the resolved path to a string.
If you set it directly to a string, like
age.secretsDir = "/home/foo/bar";
It should work fine.
I do recommend using the expansion of $XDG_RUNTIME_DIR as the prefix of the ~path~string, though, because it should have good security properties. It’s annoying that, you need to manually set it to the expansion of something on your system for purity’s sake, the same way you do for home.homeDirectory / $HOME.
Hey @juk0de ,
You can also directly use Agenix capacity to create links to the right places: https://github.com/ryantm/agenix?tab=readme-ov-file#agesecretsnamepath
I do recommend using the expansion of
$XDG_RUNTIME_DIRas the prefix of the ~path~string, though, because it should have good security properties. It’s annoying that, you need to manually set it to the expansion of something on your system for purity’s sake, the same way you do forhome.homeDirectory/$HOME.
@sellout could you explain further what you mean by "using the expansion of $XDG_RUNTIME_DIR" ? I’m not sure to understand
Thanks @sellout and @Erwyn!
If you set it directly to a string [...] it should work fine.
Unfortunately it does not. I've set it to:
age.secretsDir = "${config.home.homeDirectory}/.agenix/secrets";
but it results in the same error:
error: access to absolute path '/home' is forbidden in pure evaluation mode (use '--impure' to override)
Setting age.secrets.<name>.path also results in the same error.
Are you sure you don't use --impure (or an equal setting)? I'm using flakes with home-manager as a module, and additionally import it my user's home.nix for the personal secrets. While the home-manager import works (using @Erwyn's approach), I'm completely unable to actually use / decrypt those secrets in home-manager. I either get the "impure" or the "not of type 'absolute path'" error. Secrets managed by the nixos agenix module work fine...
Are you sure you don't use
--impure(or an equal setting)?
No, I’m definitely not.
What I do is as described in https://github.com/ryantm/agenix/issues/305#issuecomment-2603003925
my age.secrets is something like this:
age.secrets = {
my-secret = {
file = ./secrets/my-secret.age;
path = "${config.home.homeDirectory}/where/I/want/my/secret/to/end/up";
mode = "600";
};
}
Now, as you seem to point out an xdg issue, do you have it enabled in your homeManager like:
xdg = {
enable = true;
}
Now, as you seem to point out an xdg issue, do you have it enabled in your homeManager like:
Yes, it's enabled. I'm also using xdg.autostart and it works as expected. But I don't see the connection between xdg and my issue, tbh.
Now, as you seem to point out an xdg issue, do you have it enabled in your homeManager like:
Yes, it's enabled. I'm also using
xdg.autostartand it works as expected. But I don't see the connection betweenxdgand my issue, tbh.
I don’t either, I may have misunderstood I thought you had that in mind so just in case I wanted to check with you.
Do you happen to have a shareable config that I can look at ? Maybe I can spot something that would explain the difference between our setups.
@Erwyn
could you explain further what you mean by "using the expansion of
$XDG_RUNTIME_DIR" ? I’m not sure to understand
Sure. I mean that if you
$ echo $XDG_RUNTIME_DIR
/run/user/1234
this output should be the start of what you override age.secretsDir to[^1]. E.g., age.secretsDir = "/run/user/1234/agenix" is good, but age.secretsDir = "${home.homeDirectory}/agenix" isn’t as good. This is because the XDG basedir spec defines certain requirements for XDG_RUNTIME_DIR to improve its security, making it a good place to keep unencrypted secrets:
[^1]: In #300, I mention how I defined an xdg.runtimeDir option to use for this purpose, which has to be set manually, but then in the activation script I assert that it’s actually the same as XDG_RUNTIME_DIR.
$XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user, and they MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
The lifetime of the directory MUST be bound to the user being logged in. It MUST be created when the user first logs in and if the user fully logs out the directory MUST be removed. If the user logs in more than once they should get pointed to the same directory, and it is mandatory that the directory continues to exist from their first login to their last logout on the system, and not removed in between. Files in the directory MUST not survive reboot or a full logout/login cycle.
The directory MUST be on a local file system and not shared with any other system. The directory MUST be fully-featured by the standards of the operating system. More specifically, on Unix-like operating systems AF_UNIX sockets, symbolic links, hard links, proper permissions, file locking, sparse files, memory mapping, file change notifications, a reliable hard link count must be supported, and no restrictions on the file name character set should be imposed. Files in this directory MAY be subjected to periodic clean-up. To ensure that your files are not removed, they should have their access time timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file.
Since this is also the default used by agenix (but storing the environment variable in the value, which is a problem), it appears that agenix handles those requirements (like ensuring the files are re-created when the user logs in after having “fully” logged out).
The properties of XDG_RUNTIME_DIR are also why I prefer referring to the path (fooFile = config.age.secrets.foo.path) rather than redefining the path (age.secrets.foo.path = "${home.homeDirectory}/.foo") when I can.
@juk0de
I’m sorry, I realize that I misunderstood your issue.
The problem I had in #300 is different and crops up when you want to write age.secrets.foo.path to a file which won’t be processed by the shell.
The problem you’re having here is that (without --impure) the source of a file has to live in the Nix store. And be glad it didn’t work, because you don’t want your decrypted secrets in the world-readable Nix store.
I think you do want the solution that @Erwyn proposed. Instead of
home.file."xxx".source = config.age.secrets."xxx".path;
you should be doing
config.age.secrets."xxx".path = "${home.homeDirectory}/xxx";
The former way is telling Home Manager that ~/xxx should be a symlink to your decrypted secrets in the Nix store (which, again, is not good). The latter is telling Home Manager to decrypt the secrets directly to ~/xxx[^1].
I do use Home Manager both on its own and as a NixOS module, with agenix in both places with no problem, and without --impure. And I think @Erwyn’s solution for that (before you posted about the absolute path issue) is also the right one.
[^1]: I haven’t tried to make this happen, but given my previous comment about XDG_RUNTIME_DIR, it seems like a desirable third option might be to have the secrets decrypted to age.secretsDir no matter what, and then symlink to ~/xxx. This would mean that when you’re not logged in, the symlink is broken, but logging in should restore it until you’re fully logged out again.
Do you happen to have a shareable config that I can look at ? Maybe I can spot something that would explain the difference between our setups.
@Erwyn: I don't store my config in a public repo, but I'll try to provide an overview of what it looks like:
flake.nix:
...
agenix = {
url = "github:ryantm/agenix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.darwin.follows = "";
};
...
outputs =
inputs@{
nixpkgs,
home-manager,
agenix,
rycee-firefox-addons,
...
}:
let
...
def_modules = [
home-manager.nixosModules.default
{
home-manager = {
extraSpecialArgs = {
inherit firefox-addons buildXpiAddon agenix;
};
useGlobalPkgs = true;
useUserPackages = true;
backupFileExtension = "hm_backup";
};
}
# nixos agenix module
agenix.nixosModules.default
{
age.identityPaths = [ "/etc/ssh/id_agenix" ];
age.secrets.smbcredentials.file = ./secrets/smbcredentials.age;
}
];
...
in
{
nixosConfigurations = {
"lappi" = nixpkgs.lib.nixosSystem {
inherit system specialArgs;
modules = def_modules ++ [
./hosts/lappi/configuration.nix
];
};
};
}
home.nix:
{ agenix, config, ... }:
{
home = {
...
};
imports = [
agenix.homeManagerModules.default
...
]
age = {
identityPaths = [ "${config.home.homeDirectory}/.ssh/id_ed25519" ];
# -> IMPURITY ERROR <-
secretsDir = "${config.home.homeDirectory}/.agenix/secrets";
secrets = {
"duply.home.credentials" = {
file = ./home.remrot.secrets/duply.home.credentials.age;
# -> IMPURITY ERROR <-
path = "${config.home.homeDirectory}/.agenix/secrets/duply.home.credentials";
};
};
};
...
}
duply.nix:
{ config, ... }:
{
home = {
file = {
".duply/home/conf".source = ./home.remrot.files/duply/home/conf;
".duply/home/exclude".source = ./home.remrot.files/duply/home/exclude;
".duply/home/credentials".source = config.age.secrets."duply.home.credentials".path;
};
};
}
No matter if I set secretsDir or secrets.<name>.path, both result in the same impurity error:
error: access to absolute path '/home' is forbidden in pure evaluation mode (use '--impure' to override)
If I remove both lines, I get the "\${XDG_RUNTIME_DIR}/agenix/xxx" is not of type 'absolute path'. error. 🤷♂️
@sellout: as you can see, setting age.secrets."xxx".path instead of age.secretsDir does not make any difference.
@sellout there might be something to consider here. I’ve found that HomeManager has a config.lib.file.mkOutOfStoreSymlink that can be used for the home.file.<name>.source. That could be a way to achieve what you described. Another way could be using tmpfiles^1.
@juk0de I think your problem is duply.nix. Remove anything related to your secrets from there, your secrets and where they should end up are entirely dealt with in the home.nix file.
I also just noticed, remove your secretsDir = "${config.home.homeDirectory}/.agenix/secrets";. secrets."duply.home.credentials".file should tell where the .age file is relatively to your current home.nix file, and secrets."duply.home.credentials".path should tell where you want to find the decrypted version of your .age file once the generation is applied.
@sellout after checking on my side, setting age.secrets.foo.path actually does create a symlink to the correct file. So you are still benefiting the XDG directory properties. The decrypted file itself is still stored in the /run/user/xxx.
@Erwyn
I think your problem is duply.nix. Remove anything related to your secrets from there, your secrets and where they should end up are entirely dealt with in the home.nix file.
You're right, thanks! Removing the home.file symlink creation got rid of the impurity error. My idea was to create a symlink from the decrypted file to the location where I actually need it. But maybe I'd have to use mkOutOfStoreSymlink for that. Anyway, if I can set the final path directly in the secret, that's also fine.
However, it still does not fully work: even though nixos rebuild switch now succeeds, the file is not decrypted. I.e. the destination folder ~/.agenix/secrets/ is empty and the file is also not in /run/user/1000 (the encrypted file is in the store, though).
I’ve edited my previous answer in the meantime, have you tried it ?
Mainly:
I also just noticed, remove your
secretsDir = "${config.home.homeDirectory}/.agenix/secrets";.secrets."duply.home.credentials".fileshould tell where the.agefile is relatively to your currenthome.nixfile, andsecrets."duply.home.credentials".pathshould tell where you want to find the decrypted version of your.agefile once the generation is applied.
Yes, I only use this in home.nix now:
age = {
identityPaths = [ "${config.home.homeDirectory}/.ssh/id_ed25519" ];
secrets = {
"duply.home.credentials" = {
file = ./home.remrot.secrets/duply.home.credentials.age;
path = "${config.home.homeDirectory}/.agenix/secrets/duply.home.credentials";
};
};
};
And this is the output of nixos rebuild switch:
building the system configuration...
activating the configuration...
[agenix] creating new generation in /run/agenix.d/2
[agenix] decrypting secrets...
decrypting '/nix/store/yadwck6jvla3h6q4z6s2wghx5ppwvxcm-smbcredentials.age' to '/run/agenix.d/2/smbcredentials'...
[agenix] symlinking new secrets to /run/agenix (generation 2)...
[agenix] removing old secrets (generation 1)...
[agenix] chowning...
setting up /etc...
reloading user units for remrot...
reloading user units for gdm...
restarting sysinit-reactivation.target
the following new units were started: NetworkManager-dispatcher.service, sysinit-reactivation.target, systemd-ask-password-console.path, systemd-tmpfiles-resetup.service
Done. The new configuration is /nix/store/fwckaays2s95q1ifx179wajqfy49hccm-nixos-system-lappivm-25.05.20250710.10e6872
But the file is not decrypted.
Btw, I've never actually used both age.secretsDir and secrets.<name>.path at the same time. I've only uncommented them in the snippets above.
After re-reading the home-manager section in the agenix docs, I wonder if my approach is actually supported. It says:
When you run
home-manager switch, your secrets will be decrypted to a user-specific directory
But I only run nixos rebuild switch (as root), because I'm using home-manager as a nixos module. Is this actually supposed to work?
But I only run
nixos rebuild switch(asroot), because I'm using home-manager as a nixos module. Is this actually supposed to work?
Yes it does work, it is how I’m using it as well.
Regarding:
age = { identityPaths = [ "${config.home.homeDirectory}/.ssh/id_ed25519" ]; secrets = { "duply.home.credentials" = { file = ./home.remrot.secrets/duply.home.credentials.age; path = "${config.home.homeDirectory}/.agenix/secrets/duply.home.credentials"; }; }; };
- could you tell us where is the
duply.home.credentials.agefile located relatively to yourhome.nixfile ? - also,
duply.home.credentials.ageis a file not a directory right ?
could you tell us where is the duply.home.credentials.age file located relatively to your home.nix file ?
Sure:
root nixos/modules/home-manager [+]
✦ ➜ ls -l home.nix
.rw-r--r-- 1,8k root 14 Okt 07:55 home.nix
root nixos/modules/home-manager [+]
✦ ➜ ls -l ./home.remrot.secrets/duply.home.credentials.age
.rw-r--r-- 476 root 13 Okt 11:58 ./home.remrot.secrets/duply.home.credentials.age
As you can see, the relative path to home.nix is correct.
also, duply.home.credentials.age is a file not a directory right ?
Yes, it's a file. Also, if you look at the output of nixos rebuild above, you'll notice that agenix works correctly for system wide credentials but there is no output regarding the agenix.home module. I've tried decrypting the file manually (agenix -d) and that works. I feel like I'm missing sth, and it's probably sth. simple and stupid, but I'm running out of ideas...
Also, if you look at the output of
nixos rebuildabove, you'll notice thatagenixworks correctly for system wide credentials but there is no output regarding theagenix.homemodule.
It’s not producing an output of it own for me either. What you can do though is checking the status/log of the associated unit. As your user run:
systemctl status --user agenix.service
Thanks a lot @Erwyn! Without your help, I would have probably given up by now. I've re-read the nixos rebuild output and the docs, but that user specific agenix service is not mentioned anywhere. Changing that would be a huge usability improvement!
Anyway, after learning about that service, it was easy to spot the issue: my SSH key is password protected and that's not supported. After changing the key, it now finally works 🎉