Using mdns-publisher to hack together mDNS subdomains

Howdy!

I’m an intermittent NixOS user, trying again to make it my daily driver. My ability to debug Nix is limited to repeatedly trying to run nixos-rebuild again and again until something works. Would love to hear tips and tricks other people are using as part of this discussion.

I have a home server with a couple of services exposed. I’ve set up avahi so that I can access my server at home via kitsault.local, but trying to remember all the ports for my various services is a drag! I want to have subdomains for each service, but the kicker is that this seems difficult to accomplish even without the Gates of Nix standing before us.

I started after finding this blog post: Painfully Obvious → Using mDNS aliases within your home network

This is where I discovered mdns-publish, a python package not currently nix-ified. So, to start, and with help from https://nixos.wiki/wiki/Python, I set up a configuration.nix to look like this:

{ config, pkgs, ...}:

	let
	my-python-packages = ps: with ps; [
  # ...
  (
    buildPythonPackage rec {
			pname = "mdns-publisher";
			version = "0.9.2";
      src = fetchPypi {
        inherit pname version;
        sha256 = "sha256-sjCQKnsFR6w3IwCvZ1GDw0VqnLjEbO3ecgKOJrSZj84==";
      };
			doCheck = false;
			propagatedBuildInputs = with pythonPackages; [
				dbus-python
			];
		}
  )
];
in
{

	environment.systemPackages = with pkgs; [
		nssmdns
		avahi
		# mdns-publisher
		(pkgs.python3.withPackages my-python-packages)
	];

	services.avahi = {
		enable = true;
		publish = {
			enable = true;
			addresses = true;
			workstation = true;
		};
		nssmdns = true;
	};


}

I manage my machine states with a single flake.nix, without exposing a lot of unneeded detail, it looks like

{
  inputs.nixpkgs.url = github:NixOS/nixpkgs;

  outputs = { self, nixpkgs, ... }:
    {
      nixosConfigurations.kitsault = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
                  ./mdns-configuration.nix
                 ...
        ];
      };
    };
}

This has gotten me pretty close, I can run the command that mdns-publisher exposes but when I run

sudo mdns-publish-cname test.my-machine.local
sudo mdns-publish-cname test.local

I get

dbus.exceptions.DBusException: org.freedesktop.Avahi.NotPermittedError: Not permitted

And I’m unsure of how to continue - ChatGPT isn’t versed enough in Nix to help. Thoughts?

@ConradMearns I was trying to reproduce the steps on the blog and hit the issue. After a bit of exploration in the Avahi source code and try-and-error with different Avahi configuration options, I figured out we also need to change the default option for userServices from false to true.

  services.avahi = {
    nssmdns4 = true;
    enable = true;
    ipv4 = true;
    ipv6 = true;
    publish = {
      enable = true;
      addresses = true;
      workstation = true;
      userServices = true;
    };
  };

The hint came from here. Avahi makes sure it can publish before creating new entries. But apparently, it not only checks for the disable-publishing configuration entry but also includes the disable-user-service-publishing which is set to yes because of the default value in the NixOS service definition.

Just for the sake of sharing in case someone else is after enabling subdomains using mDNS. I decided to use go-avahi-cname instead of mdns-publisher from the original blog post. Here is my full configuration:

{ pkgs, ... }:
let
  go-avahi-cname =
    with pkgs;
    buildGoModule rec {
      pname = "go-avahi-cname";
      version = "2.0.6";

      src = fetchFromGitHub {
        owner = "grishy";
        repo = "go-avahi-cname";
        rev = "v${version}";
        hash = "sha256-hOX7/9mgWkdm6Rwe5zg0n4WC6y4erilMP5QPEWVSadI=";
      };

      vendorHash = "sha256-EmEnnENKzWUY5djFZlKWNFLkyZ1hzNW+4HF0ui45GjI=";
    };
in

{
  environment.systemPackages = with pkgs; [
    nssmdns
    avahi
    go-avahi-cname
  ];

  services.avahi = {
    nssmdns4 = true;
    enable = true;
    ipv4 = true;
    ipv6 = true;
    publish = {
      enable = true;
      addresses = true;
      workstation = true;
      # This is needed avoid avahi throwing 'Not permitted' while
      # registering new entries (like subdomains by go-avahi-cname)
      userServices = true;
    };
  };

  systemd.services.avahi-cname = {
    description = "Avahi CNAME Publisher";
    wantedBy = [ "multi-user.target" ];
    after = [
      "network.target"
      "avahi-daemon.service"
    ];
    requires = [ "avahi-daemon.service" ];

    serviceConfig = {
      Type = "simple";
      User = "root"; # Needs root for Avahi access
      ExecStart = "${go-avahi-cname}/bin/go-avahi-cname subdomain";
      Restart = "always";
      RestartSec = "10";
    };
  };
}