Skip to content

NixOS Configuration

Having first understood the basic concepts from the previous section, we can now configure a Data Mesher cluster.

NixOS module

We start by including the NixOS module in our NixOS configuration:

flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    data-mesher = {
      url = "git+https://git.clan.lol/clan/data-mesher?shallow=1&ref=main";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  output =
    inputs@{ nixpkgs, ... }:
    let
      inherit (nixpkgs) lib;
    in
    {
      nixosConfigurations.basic = lib.nixosSystem {
        modules = [
          inputs.data-mesher.nixosModules.data-mesher # (2)
          {
            config.services.data-mesher = {
              enable = true;
              # elided for brevity
            };
          }
        ];

      };
    };
}
configuration.nix
{
  imports = [
    # Import module
    "${
      (builtins.fetchTarball { url = "https://git.clan.lol/clan/data-mesher/archive/main.tar.gz"; })
    }/modules/nixos/data-mesher.nix"
  ];

  # Configure the service
  config.services.data-mesher = {
    enable = true;
    # elided for brevity
  };
}

Generating Keys

Next, we must generate or make available some keys.

Host

For the host key we can generate a new keypair with:

❯ data-mesher generate keypair

Network

For our networ, we will generate a new keypair the same as with the host.

For our primary network, we will generate a new one, same as with the host key.

1
2
3
4
5
❯ data-mesher generate keypair

           ID:  v8mttEnQXpdJnfH9YLImRRnmvfPx3nkXJoWhgUNdeH0=
   Public Key:  ./network.pub
  Private Key:  ./network.key

Tip

If you are joining an existing network you can simply copy it's public key.

Service Config

Having generated the necessary keys and included the NixOS module in our configuration, the final step is to configure the Data Mesher service.

Note

To keep sensitive information safe, we will use sops-nix.

Here is what a basic service config looks like:

flake.nix
{ config, ... }:
let
  inherit (config.sops) secrets;
in
{
  # Define our secrets.
  config.sops.secrets = {
    # We will use a separately generated host key.
    data_mesher_host_key = {
      owner = config.users.users.data-mesher.name;
      group = config.users.groups.data-mesher.name;
    };

    # We include the network's private key, as we will use it later to automatically inject network settings.
    data_mesher_network_key = {
      owner = config.users.users.data-mesher.name;
      group = config.users.groups.data-mesher.name;
    };

    # And finally we have our network encryption key
    data_mesher_network_encryption_key = {
      owner = config.users.users.data-mesher.name;
      group = config.users.groups.data-mesher.name;
    };
  };

  config.services.data-mesher = {
    enable = true; # Enable the service.
    openFirewall = true; # Open necessary ports.

    settings = {
      host = {
        # A list of hostnames we want to claim.
        names = [
          "mercury"
          "venus"
          "earth"
        ];
        # Our host key.
        key_path = secrets.data_mesher_host_key.path;
      };

      network.id = "QOoO3r3CwULLJbPumoQapoC8nHwMNYpvZj1Hftc6NJw=";

      cluster = {
        # The interface over which cluster communication should be performed.
        # All the ip addresses associate with this interface will be part of our host claim, including both ipv4 and
        # ipv6.
        interface = "tailscale0";

        # A list of bootstrap nodes that act as an initial gateway when joining the cluster.
        # If a bootstrap node is no longer showing up in the cluster membership, we will attempt to connect to it
        # periodically.
        # See https://github.com/hashicorp/memberlist for more information.
        bootstrap_nodes = [
          "192.168.1.1:7946"
          "192.168.1.2:7946"
        ];
      };
    };

    # Optional config to bootstrap a network's settings after the data-mesher instance has started.
    # The alternative is to manually bootstrap it by hand using the cli.
    initNetwork = {
      enable = true;

      # Path to the network's private key, which will be used to sign the settings.
      keyPath = secrets.data_mesher_network_key.path;

      # Top level domain for all host names claimed within the network.
      # e.g. mercury.sol, venus.sol, earth.sol
      tld = "sol";

      # Host claim expiry.
      hostTTL = "168h"; # 7 days

      # A list of host public keys (ed25519),
      # State signed by any of the listed host keys will be considered as authoritative for this network.
      # State not signed by one of these host keys will be discounted when merging.
      # In this example we are using the host key for this instance to also act as a signing key.
      signingKeys = [
        "lADtqXxxM4dLRytVr2xeDEfOXHrWfb7ewB+0pqCJqGo="
      ];
    };
  };
}