Skip to main content
ertius.org

artisanal filesystem hacking with systemd

A service I started running some instances of a while ago is RIPE Atlas, a distributed dns/ping/traceroute probing tool run by RIPE. It is pretty easy to deploy - they provide an apt repository, that has a package in it that installs an apt config that gives you access to the packages. Once it's installed, it produces an SSH public key that you paste into a web page and away it goes.

It is however not super flexible to operate. One thing I wanted to do was configure it to use an external resolver instead of the local unbound instance I have that is full of weird config hax for my internal network. As far as I could tell there's no config option anywhere for this and I really wanted to avoid editing any of the files in the package. Once again, systemd saves the day - just put the resolver config in /etc/ripe-atlas/resolv.conf and a drop-in file in /etc/systemd/system/ripe-atlas.service.d/dns.conf:

[Service]
BindReadOnlyPaths=/etc/ripe-atlas/resolv.conf:/etc/resolv.conf

et voila. Once again a thing I would probably not have bothered to do in the pre-systemd era - it is easy to add a "mount --bind" line to a sysvinit script but that would mean hacking a shell script from a package (albeit a conffile), which is annoying going forward (telling dpkg to leave it alone) and much more annoying to automate - telling Ansible to insert a line into a text file in roughly the right place is much more annoying than just dropping two files on disk:

- name: Deploy RIPE Atlas custom resolv.conf
  ansible.builtin.template:
    src: ripe-atlas-resolv.conf.j2
    dest: /etc/ripe-atlas/resolv.conf
    owner: root
    group: root
    mode: '0644'
  notify:
    - "RIPE Atlas : restart service"

- name: Create ripe-atlas service override directory
  ansible.builtin.file:
    path: /etc/systemd/system/ripe-atlas.service.d
    state: directory
    owner: root
    group: root
    mode: '0755'

- name: Deploy ripe-atlas DNS override
  ansible.builtin.template:
    src: ripe-atlas-dns.conf.j2
    dest: /etc/systemd/system/ripe-atlas.service.d/dns.conf
    owner: root
    group: root
    mode: '0644'
  notify:
    - "RIPE Atlas : daemon-reload"
    - "RIPE Atlas : restart service"

Which is actually quite verbose, but is extremely straight-forward and depends on the systemd configuration merge semantics rather than luck at automatically hacking shell scripts. I actually started writing out an equivalent attempt using ansible.builtin.lineinfile to edit an init script to add a mount --bind line after the header (not correctly in the start and torn down in stop since that seems way too hard), then remembered that in that world I'd first need to write enough sh to put this service in a chroot to begin with, so that I could customise the /etc/resolv.conf it saw.