home-manager
home-manager copied to clipboard
feature: add an option `home.file.<name>.mode` that allows copy instead of linking and permission setting.
Description
This option should default to "symlink"
, which instruct home-manager to create a symbolic link at the specified location to be compatible to the current behavior. When set to an octal-mode, e. g. "0600"
, the content will be copied from the source to the specified location and chmod
to the value given.
This issue generalize the goal of #1145, while adding the ability to set permissions and to align with the NixOS option environment.etc.<name>.mode
.
Inspired by https://github.com/nix-community/home-manager/pull/1145#issuecomment-1175795593
Thank you for your contribution! I marked this issue as stale due to inactivity. Please be considerate of people watching this issue and receiving notifications before commenting 'I have this issue too'. We welcome additional information that will help resolve this issue. Please read the relevant sections below before commenting.
If you are the original author of the issue
- If this is resolved, please consider closing it so that the maintainers know not to focus on this.
- If this might still be an issue, but you are not interested in promoting its resolution, please consider closing it while encouraging others to take over and reopen an issue if they care enough.
- If you know how to solve the issue, please consider submitting a Pull Request that addresses this issue.
If you are not the original author of the issue
- If you are also experiencing this issue, please add details of your situation to help with the debugging process.
- If you know how to solve the issue, please consider submitting a Pull Request that addresses this issue.
Memorandum on closing issues
Don't be afraid to manually close an issue, even if it holds valuable information. Closed issues stay in the system for people to search, read, cross-reference, or even reopen – nothing is lost! Closing obsolete issues is an important way to help maintainers focus their time and effort.
Some extra things to consider:
environment.etc
creates an immutabe dilectory /etc/static
, and renew the files during boot.
It would be great to somehow create such option to renew in the beginning of each user session.
I want to add a possible usecase for this:
home.file.".ssh/authorized_keys" = {
source = dotfiles/ssh/authorized_keys;
mode = "600";
};
It should be possible to specify modes for directories too (eg. for ~/.ssh/
)
Thank you for your contribution! I marked this issue as stale due to inactivity. Please be considerate of people watching this issue and receiving notifications before commenting 'I have this issue too'. We welcome additional information that will help resolve this issue. Please read the relevant sections below before commenting.
If you are the original author of the issue
- If this is resolved, please consider closing it so that the maintainers know not to focus on this.
- If this might still be an issue, but you are not interested in promoting its resolution, please consider closing it while encouraging others to take over and reopen an issue if they care enough.
- If you know how to solve the issue, please consider submitting a Pull Request that addresses this issue.
If you are not the original author of the issue
- If you are also experiencing this issue, please add details of your situation to help with the debugging process.
- If you know how to solve the issue, please consider submitting a Pull Request that addresses this issue.
Memorandum on closing issues
Don't be afraid to manually close an issue, even if it holds valuable information. Closed issues stay in the system for people to search, read, cross-reference, or even reopen – nothing is lost! Closing obsolete issues is an important way to help maintainers focus their time and effort.
Trying to create a common SSH key managed via home-manager
, both public and private:
home.file = {
".local/share/applications/defaults.list" = {
text = ''
[Default Applications]
application/pdf=zathura.desktop
'';
};
".ssh/id_ed25519.pub" .source = ./id_ed25519.pub;
".ssh/id_ed25519" .source = ./id_ed25519;
".ssh/authorized_keys".source = ./authorized_keys;
};
Trying to ssh ::1
fails:
Sep 13 11:59:13 nuc10 sshd[42553]: Authentication refused: bad ownership or modes for directory /nix/store
If I plug in my yubikey, which is in the authorized_keys
file, it works, but there are some complaints:
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Offering public key: /home/dap/.ssh/id_ed25519 ED25519 SHA256:cVmyH2pcn9NX+GjMl4r0iYzTAxS8uLMwflkzRtTyX4I agent
debug2: we sent a publickey packet, wait for reply
The above is the id_ed25519 key failing. I have removed the symlink and replaced the original file (from the *.backup) and reloaded with ssh-add
but it still doesn't like it. Below is continuing with the Yubikey:
debug1: Authentications that can continue: publickey
debug1: Offering public key: cardno:15_204_063 RSA SHA256:qv46ADzIR/iYCwxgGcRhgbvDjBlPOm8tn6LgIfnlNRY agent
debug2: we sent a publickey packet, wait for reply
debug1: Server accepts key: cardno:15_204_063 RSA SHA256:qv46ADzIR/iYCwxgGcRhgbvDjBlPOm8tn6LgIfnlNRY agent
Authenticated to ::1 ([::1]:22) using "publickey".
debug1: channel 0: new session [client-session] (inactive timeout: 0)
debug2: channel 0: send open
debug1: Requesting [email protected]
debug1: Entering interactive session.
debug1: pledge: filesystem
debug1: client_input_global_request: rtype [email protected] want_reply 0
debug1: client_input_hostkeys: searching /home/dap/.ssh/known_hosts for ::1 / (none)
debug1: client_input_hostkeys: searching /home/dap/.ssh/known_hosts2 for ::1 / (none)
debug1: client_input_hostkeys: hostkeys file /home/dap/.ssh/known_hosts2 does not exist
debug1: client_input_hostkeys: no new or deprecated keys from server
debug1: Remote: Ignored authorized keys: bad ownership or modes for directory /nix/store
debug1: Remote: Ignored authorized keys: bad ownership or modes for directory /nix/store
debug1: Remote: /etc/ssh/authorized_keys.d/dap:3: key options: agent-forwarding port-forwarding pty user-rc x11-forwarding
debug1: Remote: Ignored authorized keys: bad ownership or modes for directory /nix/store
debug1: Remote: /etc/ssh/authorized_keys.d/dap:3: key options: agent-forwarding port-forwarding pty user-rc x11-forwarding
Just learned about /etc/ssh/authorized_keys.d/dap
. I was trying to add them in the nixos
config, and it looks like something got added there, but i am in the process of moving everything to be under home-manager
so those will be deleted shortly. Ah yes, I see now. I added the new id_ed25529.pub
only to the home-manager
attempt at managing this stuff, so the /etc/ssh/*.d/dap
file does not have the key, so that is why works for yubikey and not new key.
Anyway, i vote for some way to handle this for all files one might want in ~/.ssh
.
Update: did a nixos-rebuild test
and sure 'nuff:
NIXOS_CONFIG=${PWD}/configuration.nix nixos-rebuild --use-remote-sudo test
building Nix...
building the system configuration...
activating the configuration...
setting up /etc...
removing obsolete file ‘/etc/ssh/authorized_keys.d/dap’...
reloading user units for dap...
setting up tmpfiles
the following new units were started: libvirtd.service
┌──(dap nuc10)-[~/proj/dotfiles/etc/nixos]
└─% ssh ::1
dap@::1: Permission denied (publickey).
Workaround: it appears a workaround for the authorized_keys file is to manage it in nixos
.
Bumping this issue — while symlinking config files often works, some software will just crash/do very weird things when they encounter read-only symlinks.
The two offenders I've encountered so far are:
- Vesktop, who crashes because it cannot write to its config file, and
- Fcitx5, which defaults to OVERWRITING the config file if it fails to write to the file directly. (See inputmethodmanager.cpp).
Copying the file would resolve both issues
Fcitx5, which defaults to OVERWRITING the config file if it fails to write to the file directly. (See inputmethodmanager.cpp).
A workaround for the Fcitx5 case that seems to work for me is to symlink the parent directory instead of just the file itself, like so:
xdg.configFile.fcitx5.source = ./fcitx5-config-dir; # contains config and profile
This will prevent Fcitx5 from being able to create any files in that directory though, so it's not a great solution.
Fcitx5, which defaults to OVERWRITING the config file if it fails to write to the file directly. (See inputmethodmanager.cpp).
A workaround for the Fcitx5 case that seems to work for me is to symlink the parent directory instead of just the file itself, like so:
xdg.configFile.fcitx5.source = ./fcitx5-config-dir; # contains config and profile
This will prevent Fcitx5 from being able to create any files in that directory though, so it's not a great solution.
Yeah definitely not ideal, but I'll give it a try — I'm getting really tired of typing rm ~/.config/fcitx5/profile{,.backup}
now haha
I've actually been trying to implement this as a small proof of concept — there are some complications, though: mode
will pretty much just supersede the executable
option which we have to find some way to polyfill onto the new option, and I'm not exactly sure how that would play out.
Under current behavior, if the source file is already executable, the target file is simply a symlink — but if it's not, then a copy is made with the executable bit turned on. While mode
would make this a LOT more consistent — symlink
always symlinks and using an octal mode always creates a copy, this might break some modules that rely on this behavior. Not sure what's the best course of action
Another thing worth pondering about is that recursive
currently only makes the difference between lndir
and ln -s
when it comes to symlinking, but with copying it really doesn't make sense to just copy a directory. We might have to make non-symlink mode imply recursive copying by just simply ignoring recursive
. That feels kinda like a footgunable design though (say, if you accidentally set it to copy like hundreds of MB of stuff on every single activation), and I think a better way would be to enforce recursive
for directories in copy mode via some sort of warning, so that you know what you're doing. It's definitely up for debate though
some way to polyfill onto the new option
IMHO, in order to keep some level of sanity/usability executable
should be deprecated and be the same as setting mode
to 550 (or 555, IIUC an executable
symlink nowadays is effectively 555). I can't think of a use case where it would make any actual difference to copy the file instead of symlinking it.
with copying it really doesn't make sense to just copy a directory
There are use cases where one wants to set a specific mode for a directory (think, 700 for ~/.ssh
) but does not care about its contents (~/.ssh/config
can be a symlink into the nix store), but, again, I don't see a case where it would do any harm to copy the files instead of symlinking them so I guess it's ok to copy recursively.
About having copies instead of symlinks, maybe (not sure) it would mean one gets an extra warning when updating the nix config in a way that impact the file (if nix doesn't compare its contents with the current generation), but I don't think that would be terrible (actually, I would personally appreciate it). Anyway, IMHO the hassle would be far out-weighted by the benefit of being able to tinker with the file contents without having to manually replace the symlink with a copy or editing the nix config and applying it.
eg: say you want to add a new git alias, wouldn't be preferrable to just edit ~/.config/git/config
, test the alias out until you are satisfied, and eventually change the nix config once instead of iterating by repeatedly applying changes to the nix config (which BTW creates a lot of extra generations?)
eg: say you want to add a new git alias, wouldn't be preferrable to just edit
~/.config/git/config
, test the alias out until you are satisfied, and eventually change the nix config once instead of iterating by repeatedly applying changes to the nix config (which BTW creates a lot of extra generations?)
Yeah this is what I've been thinking about a LOT recently — the development feedback loop with HM has definitely been hampered with just symlinks. I'm at generation 220-ish right now and most of them are largely similar except for a few tweaks in configuration.
Would be nice if we have some kind of mechanism that could "commit" changes to tracked files, where the state of the local copy is diff'd against what HM would generate, and if there's any discrepancy HM can then report back to the user that they might want to change the config within Nix to make it reproducible (similar to the git tree dirty message). Like:
warning: local copy of file '/home/somebody/.config/fcitx5/profile' does not match file built by Home Manager
as specified by `xdg.configFile."fcitx5/profile"`, defined in `home-manager/modules/programs/fcitx5.nix`
please run `home-manager diff .config/fcitx5/profile` and update definition accordingly, or suppress this warning by setting ``xdg.configFile."fcitx5/profile".allowLocalCopyMismatch = true`
Obviously there's room to discuss the specific terminology or the mechanism, but I think this will overall (when combined with the mode setting) make HM much more user-friendly and versatile
I sincerely hope this gets implemented as fast as possible. This has caused me major headaches multiple times with programs that expect a writable config file. Even just the basic functionality of copying a file with default home directory permissions would be a significant upgrade in some cases, as some programs just do not work properly without a writable config file. Further functionality can be added on and iterated on later, as can the different modes of failure, but adding functionality to just copy a file with any sane permissions to a directory is sorely needed.
I want to add another usecase beyond the ssh authorized_keys: I am struggeling to add a known_hosts file for xfreerdp.
I am wondering: Is there a general strategy in NixOS how to deal with config files that are supplied declaratively and extended by application on runtime? Many of my tools just do not save their config on close because the nix-supplied config is read-only.
However, this strategy is very annoying for usecases like: As company administrator I want to supply pre-configured database client configuration files which point at proper database endpoints. The users should be able to extend these pre-configurations with their own settings, particularly credentials like username. Is this general problem solved somehow in nixos?
I was thinking about hm activation-scripts to copy such files into place manually if they don't exist...
For what it's worth, while it's not a general NixOS strategy, I use an hm activation modification to get around the issue for now. I can't remember where I initially found this, but I stole somebody's solution for having a writable vscode settings file with something like:
home = {
activation = {
removeExistingVSCodeSettings = lib.hm.dag.entryBefore [ "checkLinkTargets" ] ''
rm -rf "${userFilePath}"
'';
overwriteVSCodeSymlink = let
userSettings = config.programs.vscode.userSettings;
jsonSettings = pkgs.writeText "tmp_vscode_settings" (builtins.toJSON userSettings);
in lib.hm.dag.entryAfter [ "linkGeneration" ] ''
rm -rf "${userFilePath}"
cat ${jsonSettings} | ${pkgs.jq}/bin/jq --monochrome-output > "${userFilePath}"
'';
};
};
which manually removes the previous settings file and then takes the HM config for vscode and manually places it into userFilePath
, which one would assign to whatever vscode is expecting on your system.
For something that simply needs a config file to be writable or executable like yabai on mac, I've made it work with:
home.activation = {
removeExistingYabaiRC = lib.hm.dag.entryBefore [ "checkLinkTargets" ] ''
rm -rf "/Users/me/.yabairc"
'';
copyYabaiRC = let
newYabai = pkgs.writeText "tmp_yabairc" (builtins.readFile ./yabairc);
in lib.hm.dag.entryAfter [ "linkGeneration" ] ''
rm -rf "/Users/me/.yabairc"
cp "${newYabai}" "/Users/me/.yabairc"
chmod +x "/Users/me/.yabairc"
'';
};
I am wondering: Is there a general strategy in NixOS how to deal with config files that are supplied declaratively and extended by application on runtime? Many of my tools just do not save their config on close because the nix-supplied config is read-only.
However, this strategy is very annoying for usecases like: As company administrator I want to supply pre-configured database client configuration files which point at proper database endpoints. The users should be able to extend these pre-configurations with their own settings, particularly credentials like username. Is this general problem solved somehow in nixos?
I was thinking about hm activation-scripts to copy such files into place manually if they don't exist...
Interesting thought. Some kind of init provisioning. Then again you might want to later change those "init" configs - depends a bit on the exact use case. If it's really just init config I'm not sure if home manager is the right place.
Usually I'd resort to a conf.d
approach with read-only configs and writable additional config files which are read in order, imported or whatever. Super easy if the tool already supports it. If you want to maintain parts of the config read-only while others are writable but the tool does not support that we are entering interesting territory where we'd need some kind of strateg(y|ies) to merge configs (e.g. in conf.d
lexicographical either silently overrides or causes a conflict)
This issue has been mentioned on NixOS Discourse. There might be relevant details there:
https://discourse.nixos.org/t/how-can-i-copy-an-ssh-key-into-ssh/39488/1
Another use case: handling encrypted secrets.
I'm using agenix to encrypt/decrypt secrets in my Nix build. The secrets are decrypted into plaintext files at activation time, which means that they're not available during the build.
It would be nice to define secrets that are shared by multiple configurations, like shared API keys, once in my agenix secrets, then inject them into each configuration file that I create using home.file
using an activation hook. But I can't do that, because the file created is just a symlink to the read-only store.
@WildfireXIII's workaround works, but for now, I'm just copying the same shared secrets into every config file that uses it. I treat those config files as secrets rather than the keys that go into them. That way, I can just point my home.file.<file>.source
at the config file secret and there's no need to modify it after activation.
Not ideal because if I change my keys, then I have to remember to change them in every config filed using them. It would be more elegant and maintainable to inject just the keys post-activation into the unencrypted configs. But it works for now.
Yet another use case: theming for Flatpak apps.
Files in ~/.config/gtk-3.0
, ~/.config/gtk-4.0
, ~/.config/Kvantum
, etc. aren't readable by Flatpak apps if they are symlinks to files the app does not have access to in the sandbox (i.e. the Nix store, in HM's case)*. Being able to copy instead of link files would avoid having to use hacky workarounds to get theming to play nice.
*Interestingly, if the file ~/.config/kdeglobals
(KDE system settings) is a symlink, it seems to get resolved by Flatpak for apps using the KDE runtime even if it points to a directory not normally accessible by the app. This seems to be a feature of the XDG settings portal(?).
A way to do this currently is to use the onChange
attribute. You configure your file like normal, but you give it a different name (I add the prefix HomeManagerInit_
). Then, in the onChange
attribute, you use shell commands to copy the symlink created by home manager, giving it the right name and making the file writable. You might also have to remove the file that was created by a previous execution of onChange
.
Here is an example:
xdg.configFile."nomacs/HomeManagerInit_ImageLounge.conf" = {
text = ''
[DisplaySettings]
bgColorNoMacsRGBA=4281545523
bgColorWidgetRGBA=2852126720
fontColorRGBA=4292730333
highlightColorRGBA=4278233855
iconColorRGBA=4292730333
themeName312=Dark-Theme.css
'';
onChange = ''
rm -f ${config.xdg.configHome}/nomacs/ImageLounge.conf
cp ${config.xdg.configHome}/nomacs/HomeManagerInit_ImageLounge.conf ${config.xdg.configHome}/nomacs/ImageLounge.conf
chmod u+w ${config.xdg.configHome}/nomacs/ImageLounge.conf
'';
};
Disregard anything further down, I misunderstood the home.file.<name>.target
option. The linked solution for ssh works!
A way to do this currently is to use the
onChange
attribute. You configure your file like normal, but you give it a different name (I add the prefixHomeManagerInit_
).
I'd love to do that, but the file path for the ssh config cannot be modified when using program.ssh
.
This comment seems to use that exact trick somehow, but I can't figure out, how to do that.
Relevant line in ssh.nix: https://github.com/nix-community/home-manager/blob/d6bb9f934f2870e5cbc5b94c79e9db22246141ff/modules/programs/ssh.nix#L512
I'm trying to have some .desktop files managed by home-manager and KDE really doesn't like it when they're symlinks. this feature would be perfect.