nix-community / home-manager

Manage a user environment using Nix [maintainer=@rycee]
https://nix-community.github.io/home-manager/
MIT License
6.7k stars 1.76k forks source link

feature: add an option `home.file.<name>.mode` that allows copy instead of linking and permission setting. #3090

Open ShamrockLee opened 2 years ago

ShamrockLee commented 2 years ago

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

stale[bot] commented 1 year ago

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.

ShamrockLee commented 1 year ago

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.

lukas-mertens commented 1 year ago

I want to add a possible usecase for this:

home.file.".ssh/authorized_keys" = {
      source = dotfiles/ssh/authorized_keys;
      mode = "600";
    };
giorgiga commented 1 year ago

It should be possible to specify modes for directories too (eg. for ~/.ssh/)

stale[bot] commented 1 year ago

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.

permezel commented 1 year ago

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 no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: filesystem
debug1: client_input_global_request: rtype hostkeys-00@openssh.com 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.

pluiedev commented 10 months ago

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:

Copying the file would resolve both issues

mtoohey31 commented 10 months ago

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.

pluiedev commented 10 months ago

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

pluiedev commented 10 months ago

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

pluiedev commented 10 months ago

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

giorgiga commented 10 months ago

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?)

pluiedev commented 10 months ago

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

OreoLamp commented 9 months ago

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.

MartinLoeper commented 9 months ago

I want to add another usecase beyond the ssh authorized_keys: I am struggeling to add a known_hosts file for xfreerdp.

MartinLoeper commented 9 months ago

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...

WarmCyan commented 9 months ago

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"
  '';
};
w4tsn commented 7 months ago

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)

nixos-discourse commented 7 months ago

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

ersinakinci commented 7 months ago

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.

Liassica commented 6 months ago

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(?).

RmiTtro commented 5 months ago

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
  '';
};
AlexLJordan commented 5 months ago

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 prefix HomeManagerInit_).

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

SIGSTACKFAULT commented 4 months ago

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. turns out this isn't a real problem and you just have to relog.

rabejens commented 2 months ago

My special use case is that I want to use the same SSH keys as I use in PowerShell in NixOS WSL as well. Here is my solution:

    ".ssh/dummy" = {
      text = "dummy";
      onChange = ''
        cp /mnt/c/Users/MyUserName/.ssh/id_* ~/.ssh/
        chmod 0400 ~/.ssh/id_*
      '';
    };

This will create a dummy file in the Nix store and with it the .ssh directory and in the onChange phase, the "real" ones are copied over. Must be ran in impure mode though.

crabdancing commented 2 weeks ago

I need this too. My use case is for programs that are tricky/uncooperative with read-only files, e.g. vscodium.