Potential limitation in handling user configuration via home manager or otherwise

Having run into a somewhat nasty issue with xscreensaver and home manager as reported at:

I found that the only reliable way to manage the state of xscreensaver fully now is to manage ~/.xscreensaver directly. I was hoping there would be a decent way to do like a one shot creation of a file if it doesn’t exist and otherwise leave it alone or something even fancier potentially like an ansible.builtin.lineinfile that would simply allow checking for the presence of specific lines and set them if missing, but leave the rest alone.

The reason for this ultimately is because while managing the file entirely via home manager is somewhat doable, it’s not a great user experience. What I mean by that is, you could create and manage this file entirely with home manager, but then you lose all ability to also open up xscreensaver-demo or xscreensaver-settings to make further modifications in the future as the file is now a symlink to a read-only file in /nix/store.

I’ll rant for a second here and say I think it’s somewhat silly that upstream has chosen to modify the daemon itself in this manner so that it ignores anything being set via xrdb for a code path that basically only ever gets executed once I think anyway at start up. But I’m assuming that by simplifying all of this, the potential attack surface was reduced considerably. At least, that’s what I’m hoping was the reasoning for this admittedly inconvenient change in how xscreensaver handles its own configuration between disparate process invocations.

Anyway, being relatively new to NixOS still, I’m soliciting for advice here about this, because I can’t imagine there aren’t other applications which might have run afoul of something similar and so maybe someone already has a good approach to handling this situation of wanting a user writable configuration file that is still, at least initially or partially, managed via Nix.

I don’t have a solution for this, but if you wanted to be able to at least install an initial configuration if one doesn’t already exist, one potential avenue would be to add your own home-manager activation scripts via home.activation. Certainly far from ideal.

Of course, on that note, a module that tries to apply configuration to the mutable user configuration via activation scripts should also be possible, and while inelegant, it would at least get the job done.

I just create symlinks when programs modify their settings files at runtime. Like btop or htop, for example.

Maybe not ideal, but home.activation is as flexible as it needs to be to solve this, so thanks for that!

  home.activation.xscreensaver = lib.hm.dag.entryAfter ["writeBoundary"] ''
    conf=$HOME/.xscreensaver

    if grep -q '^fade:'$'\t' $conf; then
      run sed -i -e 's/^fade:\t.*$/fade:\t\tFalse/' $conf
    else
      run echo -e 'fade:\t\tFalse' >> $conf
    fi

    if grep -q '^mode:'$'\t' $conf; then
      run sed -i -e 's/^mode:\t.*$/mode:\t\trandom/' $conf
    else
      run echo -e 'mode:\t\trandom' >> $conf
    fi

    if grep -q '^splash:'$'\t' $conf; then
      run sed -i -e 's/^splash:\t.*$/splash:\t\tFalse/' $conf
    else
      run echo -e 'splash:\t\tFalse' >> $conf
    fi

    if grep -q '^timeout:'$'\t' $conf; then
      run sed -i -e 's/^timeout:\t.*$/timeout:\t0:10:00/' $conf
    else
      run echo -e 'timeout:\t0:10:00' >> $conf
    fi

    if /run/current-system/sw/bin/systemctl --user --quiet is-active xscreensaver.service; then
      /run/current-system/sw/bin/systemctl --user restart xscreensaver.service
    fi
  '';

Works just like it needs to by either creating the file anew if it doesn’t exist or by replacing the existing options with the new changes you’ve made since the last run. Obviously if you are managing an option this way and then cease doing so, there’s nothing to automatically revert if back to the default.

1 Like