How ensure a single line in a dotfile? (Home Manager)

I want to ensure that a configuration file in my home directory contains a certain line. IIUC, it’s only possible to manage the entire file, namely set its content either via home.file.<name>.text or via home.file.<name>.source and .target (see related HM documentation).

What would I do if I only cared about a single line in the file, and leave the rest of the file untouched? Similar to Ansible’s lineinfile and blockinfile modules. Is that made impossible by design or philosophy?

I believe, this is a very common use case, and I’d like to better understand why Home Manager doesn’t support this out-of-the-box. Most importantly, I hope to avoid a custom scripted solution. How do other people solve this problem, usually?

It’s not honestly I think a common use-case for people going full nix. They want to arrive at a target state. It’s hard to do that if you are doing things like “I don’t care what else is in this file as long as this one line is”.

My experience of using things like lineinfile is that they are exceptionally brittle and success depends on writing a good regex which, to trot out an oft-quoted jwz aphorism, means that “now you have two problems”. Generally I’ve been exceptionally happy throwing ansible in the bin 4 years ago when we (work) transitioned to all nix all the time and I can honestly say I have never even thought about lineinfile until reading this post.

EDIT: I don’t want to derail (much) the conversation, but I am curious as to what exactly is leading you to want this tool? You can of course write an activation script to apply some awk magic to a file, but I would definitely not do that.

EDIT2: If I am taking control of the configuration of something I usually grab the entire file, template it if I need to then stick it under source having read the manual for whatever it is I am using, which I then ignore until the thing is updated, and then I review the change log for new and interesting configuration I might want to apply.

1 Like

You’re right, and that’s what I also guessed. In the best case you control the entire file. (Though, I’m wondering if you only talk about servers or also desktop machines.)

Irrelevant User Changes

My specific use case is a desktop application that writes its settings into ~/.config/appname.conf. For some reason, the configuration file includes things like the plugins that the application uses and the window size and position.

The latter certainly changes every time the user moves the application window or changes its size. Why the heck should I care about taking control over that?!? :smirk: – This seems like a valid use case for leaving users freedom, for ignoring ever-changing values. And yak, it does make sense that the application overwrites those changes every time.

When Applications Can’t Handle It

The main problem here is really that the configuration file becomes read-only and it depends on the application whether it can deal with that. In the case of Paperwork, this results in the application being unable to start up. This is certainly an application design issue, but it’s happening and it’s not easy to fix without the application maintainer’s collaboration.

$ paperwork-gtk 
[INFO  ] [openpaperwork_core.config     ] Loading configuration for paperwork2
[INFO  ] [openpaperwork_core.config.backend.configparser] Loading configuration 'file:///home/peter/.config/paperwork2.conf' ...
[INFO  ] [openpaperwork_core.config.backend.configparser] Writing configuration 'file:///home/peter/.config/paperwork2.conf' ...
[ERROR ] [openpaperwork_core.logs.print ] === UNCAUGHT EXCEPTION ===
Traceback (most recent call last):
  File "/nix/store/05d6yqh6z3qpg2l75w0a1xll6d9phibj-paperwork-2.2.5/bin/.paperwork-gtk-wrapped", line 9, in <module>
    sys.exit(main())
             ^^^^^^
...
  File "/nix/store/xl8wvki47jl7700hz2i714psqdv3789h-python3.12-openpaperwork-core-2.2.5/lib/python3.12/site-packages/openpaperwork_core/config/backend/configparser.py", line 257, in config_backend_save
    with self.core.call_success("fs_open", config_path, 'w') as fd:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/xl8wvki47jl7700hz2i714psqdv3789h-python3.12-openpaperwork-core-2.2.5/lib/python3.12/site-packages/openpaperwork_core/__init__.py", line 503, in call_success
    r = callback(*args, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/xl8wvki47jl7700hz2i714psqdv3789h-python3.12-openpaperwork-core-2.2.5/lib/python3.12/site-packages/openpaperwork_core/fs/python.py", line 38, in fs_open
    return codecs.open(path, mode, encoding='utf-8')
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen codecs>", line 918, in open
OSError: [Errno 30] Read-only file system: '/home/peter/.config/paperwork2.conf'
[ERROR ] [openpaperwork_core.logs.print ] ==========================

Alternative Solution

This problem is easier to handle when the configuration items are written separately, e.g. when there is not a monolithic file being used but a registry of configuration items like dconf (I already suggested that change).

I hope it’s easier to see now, why enforcing a single configuration item makes sense—or is unfurtunately needed.

I only really see two options:

  1. If that particular application has an include feature, you can control the file with the one line you need plus the include statement or vice versa have a dot file that includes your nix controlled file.
  2. Just keep a small dotfile repository for the few applications that have not ideal configuration setups and don’t worry too much about it.

I run nixos on the server farm and on my desktop (+hm) and hm on my work macbook. I just don’t use any applications that are that badly behaved.

As I say I mentioned in my first reply if I were obliged (by external forces) I would write a module backed by an activation script that knows how to perform the state manipulation I wanted. I cannot imagine this ever going upstream though, because no one is going to want maintain it (or take responsibility when a bug inevitably causes it to trash a file).

EDIT: if paperwork doesn’t want to overwrite the config and is merely annoyed by it being read only you can use mkOutOfStoreSymlink, but that’s playing with fire.

EDIT2: I also just I guess have to say that the major problem with this is it violates the single writer principle. If an app wants to be in charge of reading and writing the configuration then I need to get the out of the way. Who knows if down the line my writes are going to interfere with its. At best I’d want a script that populates an initial config (i.e. write file on non-existence). I would not go in and continuously try to meddle with a file that the application clearly thinks it owns.

Or 3. patch the app, I did that with an app that I didn’t want to configure in the UI.

I mark “patching” as the solution. This is certainly a bit hard to do for someone who is only focused on installing software and tweaking its configuration.

The bottomline for me is, applications need to be prepared to work on NixOS. If they are designed well that’s a given. With NixOS becoming more widespread, developers need to be aware that their software may run on systems that control (parts of) their configuration.


Related thread in this regard: “Developing for NixOS