Configuration files for different services are rarely independent. For example, in nftables, I might tag traffic with a firewall mark, and that mark is then used by systemd-networkd or in ip routes. Similarly, when the name of the primary network interface changes, multiple services like nftables, postfix, and samba need to be updated.
Requirements
- I want to define core data in one place, then update all config files with a simple command.
- If a configuration file is modified by an external process (for example, a package update from a vendor or distribution), the changes must be handled gracefully. Either the merge should be automatic and permanent, or I should be notified to easily resolve any conflicts.
- It should be obvious within the config file itself what changes I have made.
Existing Solutions
I did some quick survey and found a few options.
1. Templates
These tools render a template using provided data sources. To manage /etc/config.txt
, I would create a /etc/config.txt.template
with all the moving parts marked using the required syntax.
Examples include:
The biggest issue is that the generated config file is no longer the source of truth. This means if the generated file is modified by other tools, those changes will be lost the next time I render the template.
Perhaps these tools are better suited for scenarios like building images with bootc
or mkosi
.
2. Patching
These tools record and apply the diff between a default state and the desired state.
Examples include:
diff
andpatch
- Ansible's
lineinfile
andblockinfile
- Augeas
- crudini
There are two issues with these tools:
The diff is stored separately from the config file, which is hard to read and maintain. I might also need to keep a copy of the original, unpatched file for reference.
The patch might not be reliable if there isn't enough context to locate the exact area for patching. For example, consider a config file like this:
[Config for user A] // many lines use_https = true [Config for user B] // many lines use_https = false
We want to modify the
use_https
setting for user A and generate a diff. Later, if the vendor's config file swaps the order of user A and B, the patch might still apply without error, but it will modify the wrong section!
Note that while Ansible can place markers around managed blocks, it must first insert them. For the initial insertion, it relies on regular expressions (insertafter
and insertbefore
) to find the location, which can be brittle.
3. Generators
NixOS allows you to generate all config files using custom data and functions in the same language.
The biggest issues with this approach are:
You are forced to commit to a specific ecosystem like NixOS or another tool that fully manages your system's configuration.
Merge conflicts almost never happen because your own NixOS configuration is just an override of the default values. This means you aren't notified of potential semantic conflicts. For example, if a default value you were referencing changes upstream, your configuration will adapt silently, which may not be the desired behavior without a manual review.
My Plan
The existing solutions I found almost solve my problem, but not 100%.
The closest approach I found is a combination of:
- Adding a custom, unique anchor comment in the config file.
- Using Ansible's
blockinfile
with the anchor comment forinsertafter
orinsertbefore
.
But I still don't like that the diff is stored separately from the config file. To solve this, my plan is to embed the template directly inside the configuration file, like this:
### BEGIN MANAGED BLOCK
### binds_to = {{ config.permanent_lan_interface.name }}
### END OF TEMPLATE
### END MANAGED BLOCK
I'll then write a script that:
- Deletes all text after
END OF TEMPLATE
. - Parses the template before
END OF TEMPLATE
. - Renders the template using libraries like Jinja.
- Puts the rendered template after
END OF TEMPLATE
.
Final Thoughts
My current plan may not be elegant, but it seems to meet my requirements more effectively than the other solutions.
Meanwhile, I'm still looking for new options. Please let me know if you know any.
Comments