Build nixos systems from a local repository

  • 1 min read
  • Tags: 
  • nix

In this post I'll show you how I manage 6 servers and 2 workstations from a single repository.

The repository

I use the old way, I've set up a repo at ~/.config/nixpkgs. In this repository there is all I need for deploying and accessing the servers.

The machines

So if you used ansible you'll find some similarities here. I have a file at the root of the repository:

# ./machines.nix
{
  someName = {
    name = "the name of the machine";
    user = "root";
    address = "the ip address of the machine (not for workstations)";
    vpnAddress = "the vpn ip address";
    hasSwitch = true; # true will generate the associated `switch_` command alias later
    hasSSH = true; # true will generate the associated `ssh_` command alias later
    wgPublicKey = ""; # used for wireguard
    sshPublicKey = ""; # used for secrets (agenix)
  };
}

In this file I can set up all my machines, and using these attributes, we'll see later how to generate de associated commands.

Agenix

I use agenix, so I have also a secrets file:

# ./secrets.nix
let
  machines = import ./machines.nix;

  # Physical machines
  someMachine = machines.someMachine.sshPublicKey;
  someOtherMachine = machines.someOtherMachine.sshPublicKey;
  # workstation1 = ...
  # [...]

  # Roles
  postgres-server = someMachine;

  # Groups
  locals = [ workstation1 workstation2 ];
  postgres-server-group = [ postgres-server ovh01 ] ++ locals;

in
{
  "secrets/postgres-server-masterpassword.age".publicKeys = postgres-server-group;
  # [...]
}

Then I can inject/rotate secrets in all my servers/workstations.

The core: flake.nix

So now the most interesting part. I won't show any real nixos configuration because there is plenty of examples on the web, but just what I found useful to do here.

Brace yourself there is a lot coming.


{
  inputs = {
    nixpkgs-unstable.url = "nixpkgs/nixos-unstable"; # I used both stable and unstable so I've made 2 different variables
    nixpkgs-stable.url = "nixpkgs/nixos-25.05";

    home-manager-unstable.url = "github:nix-community/home-manager";
    home-manager-unstable.inputs.nixpkgs.follows = "nixpkgs-unstable";

    home-manager-stable.url = "github:nix-community/home-manager/release-25.05";
    home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";

    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs =
    { self,
    # [...]
    } @inputs:
    let
      system = "x86_64-linux";
      machines = import ./machines.nix;
      hostsContent = (builtins.map
        (x:
          (if machines.${x}.hasSSH then [
            "${machines.${x}.address} ${x}\n"
          ] else [ ])
        )
        (builtins.attrNames machines)); # Generate hosts files with named machines IPs
    in
    {
      nixosConfigurations = {
        workstation1 =
          let
            nixpkgs = nixpkgs-unstable;
            home-manager = home-manager-unstable;
          in
          nixpkgs.lib.nixosSystem
            (import ./nixos-workstation1.nix
              {
                inherit hostsContent;
                vpnAddress = machines.workstation1.vpnAddress;
              }
              {
                inherit home-manager; inherit inputs; inherit agenix; pkgs = nixpkgs;
              });
        # [...]
      };
    } // flake-utils.lib.eachDefaultSystem
      (system:
      let pkgs = nixpkgs-unstable.legacyPackages.${system};
      in {
        devShells.default = pkgs.mkShell
          {
            buildInputs = with pkgs; [
              agenix.packages.${system}.default

              # this command will nixos-rebuild switch all machines with hasSwitch and a vpnAddress
              (writeShellScriptBin "switch_vpn_all"
                (builtins.toString
                  (builtins.map
                    (x:
                      (if machines.${x}.hasSwitch && builtins.hasAttr "vpnAddress" machines.${x} then [
                        ''
                          echo "[SWITCH ${x} at ${machines.${x}.user}@${machines.${x}.vpnAddress}]";
                          nixos-rebuild switch --target-host ${machines.${x}.user}@${machines.${x}.vpnAddress} --flake "./#${x}";
                        ''
                      ] else [ ])
                    )
                    (builtins.attrNames machines)))
              )

              # this command will nixos-rebuild switch all machines with hasSwitch and an address
              (writeShellScriptBin "switch_all"
                (builtins.toString
                  (builtins.map
                    (x:
                      (if machines.${x}.hasSwitch && builtins.hasAttr "address" machines.${x} then [
                        ''
                          echo "[SWITCH ${x} at ${machines.${x}.user}@${machines.${x}.address}]";
                          nixos-rebuild switch --target-host ${machines.${x}.user}@${machines.${x}.address} --flake "./#${x}";
                        ''
                      ] else [ ])
                    )
                    (builtins.attrNames machines)))
              )

              # print all IPs and machines names
              (writeShellScriptBin "all_ips"
                (builtins.toString
                  (builtins.map
                    (x:
                      (if machines.${x}.hasSwitch then [
                        ''
                          echo "LOCAL ${x}: ${machines.${x}.user}@${machines.${x}.address}"
                        ''
                      ] else [ ]) ++ (if builtins.hasAttr "vpnAddress" machines.${x} then [
                        ''
                          echo "VPN ${x}: ${machines.${x}.user}@${machines.${x}.vpnAddress}"
                        ''
                      ] else [ ])
                    )
                    (builtins.attrNames machines)))
              )
            ] ++
            (builtins.map
              (x:
                (if machines.${x}.hasSwitch then [

                  # each machine can be switched individually
                  (writeShellScriptBin
                    "switch_${x}"
                    ''
                      nixos-rebuild switch --target-host ${machines.${x}.user}@${machines.${x}.address} --flake "./#${x}"
                    '')
                ] else [ ]) ++

                (if builtins.hasAttr "vpnAddress" machines.${x} && machines.${x}.hasSwitch then [

                  # each machine can be switched individually over vpn
                  (writeShellScriptBin
                    "switch_vpn_${x}"
                    ''
                      nixos-rebuild switch --target-host ${machines.${x}.user}@${machines.${x}.vpnAddress} --flake "./#${x}"
                    '')

                ] else [ ]) ++
                (if machines.${x}.hasSSH then [

                  # ssh commands like ssh_machine1
                  (writeShellScriptBin
                    "ssh_${x}"
                    ''
                      ssh ${machines.${x}.user}@${machines.${x}.address}
                    '')

                ] else [ ]) ++
                (if builtins.hasAttr "vpnAddress" machines.${x} && machines.${x}.hasSSH then
                  [
                    # ssh commands like ssh_vpn_machine1 for vpn
                    (writeShellScriptBin "ssh_vpn_${x}"
                      ''
                        ssh ${machines.${x}.user}@${machines.${x}.vpnAddress}
                      '')

                  ]
                else [ ])
              )
              (builtins.attrNames machines));
          };
      });
}