Compare commits

...

2 Commits

Author SHA1 Message Date
Jan-Bulthuis
d490b648ac feat: Implement NixOS modules for madd server and client 2025-08-10 01:15:36 +02:00
Jan-Bulthuis
4bcb94ab27 fix: Create data_dir if it does not exist 2025-08-10 01:14:56 +02:00
5 changed files with 161 additions and 79 deletions

View File

@ -40,7 +40,13 @@
overlays.madd = final: prev: mkPackages final;
overlays.default = overlays.madd;
nixosModules.madd = import ./nixos.nix { overlay = overlays.madd; };
nixosModules.madd = {
imports = [
./modules/madd-client.nix
./modules/madd-server.nix
];
config.nixpkgs.overlays = [ overlays.default ];
};
nixosModules.default = nixosModules.madd;
};
}

72
modules/madd-client.nix Normal file
View File

@ -0,0 +1,72 @@
{
lib,
pkgs,
config,
...
}:
with lib;
let
cfg = config.services.madd-client;
in
{
options.services.madd-client = {
enable = mkEnableOption "MADD client";
endpoint = mkOption {
type = types.str;
description = "Endpoint for MADD client to connect to.";
};
interface = mkOption {
type = types.str;
description = "Network interface to use for MADD client.";
};
priv-key-file = mkOption {
type = types.str;
default = "/etc/ssh/ssh_host_ed25519_key";
description = "Path to the private SSH key file identifying this machine.";
};
pub-key-file = mkOption {
type = types.str;
default = "${config.services.madd-client.priv-key-file}.pub";
description = "Path to the public SSH key file identifying this machine.";
};
hostname = mkOption {
type = types.str;
default = config.networking.hostName;
description = "Hostname to use for MADD client.";
};
};
config = mkIf cfg.enable {
systemd.services.madd-client = {
description = "MADD Client Service";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
script = ''
function run_update {
ipv4=$(${pkgs.iproute2}/bin/ip -4 addr show dev "${cfg.interface}" | grep -Po 'inet \K[\d.]+' || true)
if [ -n "$ipv4" ]; then
export MADD_ENDPOINT="${cfg.endpoint}";
export MADD_PRIV_KEY="${cfg.priv-key-file}";
export MADD_PUB_KEY="${cfg.pub-key-file}";
export MADD_HOSTNAME="${cfg.hostname}";
export MADD_IP="$ipv4";
${pkgs.madd-client}/bin/madd-client 2>/dev/null ;
fi
}
run_update
${pkgs.iproute2}/bin/ip -4 monitor address label dev "${cfg.interface}" | while read -r event; do
if [[ $event == \[ADDR\]* ]]; then
echo "Detected address change"
run_update
fi
done
'';
};
};
}

79
modules/madd-server.nix Normal file
View File

@ -0,0 +1,79 @@
{
lib,
pkgs,
config,
...
}:
with lib;
let
cfg = config.services.madd-server;
in
{
options.services.madd-server = {
enable = mkEnableOption "MADD server";
settings = {
bind = mkOption {
type = types.str;
default = "0.0.0.0:5301";
description = "Address and port for MADD server to bind to.";
};
zone = mkOption {
type = types.str;
example = "lan.example.com";
description = "DNS zone under which the hosts are registered.";
};
networks = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "10.0.0.0/8" ];
description = "List of subnets to which hostnames can be registered.";
};
registration_limit = mkOption {
type = types.int;
default = 1;
description = "Maximum number of hostnames a single host can register.";
};
dns_server = mkOption {
type = types.str;
example = "localhost:53";
description = "DNS server to use. Must support dynamic updates.";
};
tsig_key_name = mkOption {
type = types.str;
default = "madd";
description = "TSIG key name for DNS updates.";
};
tsig_key_file = mkOption {
type = types.str;
default = "/etc/madd/tsig.key";
description = "Path to the TSIG key file for DNS updates. Must be encoded in base64.";
};
tsig_algorithm = mkOption {
type = types.str;
default = "hmac-sha256";
description = "TSIG algorithm to use for DNS updates.";
};
data_dir = mkOption {
type = types.str;
default = "/var/lib/madd";
description = "Directory where MADD server stores its data.";
};
};
};
config = mkIf cfg.enable {
environment.etc."madd/madd.toml".source = (pkgs.formats.toml { }).generate "madd.toml" cfg.settings;
systemd.services.madd-server = {
description = "MADD Server Service";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
serviceConfig = {
ExecStart = "${pkgs.madd-server}/bin/madd-server";
Restart = "on-failure";
};
};
};
}

View File

@ -1,78 +0,0 @@
{ overlay }:
{
lib,
pkgs,
config,
...
}:
with lib;
{
options.services.madd-client = {
enable = mkEnableOption "MADD client";
endpoint = mkOption {
type = types.str;
description = "Endpoint for MADD client to connect to.";
};
interface = mkOption {
type = types.str;
default = "eth0";
description = "Network interface to use for MADD client.";
};
priv-key-file = mkOption {
type = types.str;
default = "/etc/ssh/ssh_host_ed25519_key";
description = "Path to the private SSH key file identifying this machine.";
};
pub-key-file = mkOption {
type = types.str;
default = "${config.services.madd-client.priv_key_file}.pub";
description = "Path to the public SSH key file identifying this machine.";
};
hostname = mkOption {
type = types.str;
default = config.networking.hostName;
description = "Hostname to use for MADD client.";
};
};
options.services.madd-server = {
enable = mkEnableOption "MADD server";
};
config = {
nixpkgs.overlays = [ overlay ];
}
// (
let
cfg = config.services.madd-client;
in
optionalAttrs config.madd-client.enable {
systemd.services.madd-client = {
description = "MADD Client Service";
wantedBy = [ "multi-user.target" ];
before = [ "network-pre.target" ];
requires = [ "network-pre.target" ];
script = ''
${pkgs.iproute2}/bin/ip -4 monitor address label dev "${cfg.interface}" | while read -r event; do
if [[ $event == \[ADDR\]* ]]; then
ipv4=$(${pkgs.iproute2}/bin/ip -4 addr show dev "${cfg.interface}" | grep -Po 'inet \K[\d.]+')
if [ -n "$ipv4" ]; then
export MADD_ENDPOINT="${cfg.endpoint}"
export MADD_PRIV_KEY="${cfg.priv-key-file}"
export MADD_PUB_KEY="${cfg.pub-key-file}"
export MADD_HOSTNAME="${cfg.hostname}"
export MADD_IP="$ipv4"
${pkgs.madd-client}/bin/madd-client
fi
done
'';
};
}
)
// (optionalAttrs config.madd-server.enable {
});
}

View File

@ -267,6 +267,9 @@ async fn init_dns_registrations(
async fn write_registrations(registrations: &Registrations, config: &Config) {
let path = config.data_dir.join("registrations.toml");
let contents = toml::to_string(registrations).unwrap();
if !config.data_dir.exists() {
tokio::fs::create_dir_all(&config.data_dir).await.unwrap();
}
tokio::fs::write(path, contents).await.unwrap();
}