Clan
Clan is a peer-to-peer computer management framework that empowers you to selfhost in a reliable and scalable way.
Whether you’re running a homelab or maintaining critical computing infrastructure, Clan will help reduce maintenance burden by allowing a git repository to define your whole network of computers.
Setup
Create a new clan flake:
Note: This creates a new directory in your current location
nix run https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli --refresh -- flakes createYour new directory, my-clan, should contain the following structure:
my-clan/
├── clan.nix
├── flake.lock
├── flake.nix
├── modules/
└── sops/
You can also bring in existing NixOS configurations.
{
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
inputs.clan-core = {
url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, clan-core, ... }:
let
clan = clan-core.lib.clan {
self = self; # this needs to point at the repository root
specialArgs = {};
meta.name = throw "Change me to something unique";
machines = {
berlin = {
nixpkgs.hostPlatform = "x86_64-linux";
imports = [ ./machines/berlin/configuration.nix ];
};
cologne = {
nixpkgs.hostPlatform = "x86_64-linux";
imports = [ ./machines/cologne/configuration.nix ];
};
};
};
in
{
inherit (clan.config) nixosConfigurations nixosModules clanInternals;
clan = clan.config;
};
}Auto Include
Every folder under machines/{machineName} is automatically registered as a Clan machine.
The following files are detected and imported for every Clan machine:
machines/{machineName}/configuration.nix: Main configuration file for the machine.machines/{machineName}/hardware-configuration.nixHardware-specific configuration generated by NixOS.machines/{machineName}/facter.json: Contains system facts. Automatically generated — see nixos-facter for details.machines/{machineName}/disko.nix: Disk layout configuration. See the disko quickstart for more info.
Other Auto-included Files:
inventory.json: Managed by Clan’s API. Merges withclan.inventoryto extend the inventory..clan-flake: Sentinel` file to be used to locate the root of a Clan repository. Falls back to .git, .hg, .svn, or flake.nix if not found.
Machines
Machines can be added using the following methods
- Create a file
machines/{machine_name}/configuration.nix - Imperative via cli command:
clan machines create - Editing nix expressions in
flake.nixSeeclan-core.lib.clan
# clan.nix
{
inventory.machines = {
# Define a machine
jon = { };
};
# Additional NixOS configuration can be added here.
machines = {
# jon = { config, ... }: {
# environment.systemPackages = [ pkgs.asciinema ];
# };
};
}The option: inventory.machines.<name> is used to define metadata about the machine That includes for example deploy.targethost machineClass or tags:
# clan.nix
{
inventory.machines = {
jon = {
# Define tags here (optional)
tags = [ ]; #
};
sara = {
deploy.targetHost = "root@sara";
tags = [ ];
};
};
# Define additional nixosConfiguration here
# Or in /machines/jon/configuration.nix (autoloaded)
machines = {
jon = { config, pkgs, ... }: {
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC..." # elided
];
};
};
}Update Machines
The Clan command line interface enables you to update machines remotely over SSH.
Execute the following command to update the specified machine:
clan machines update jonAll machines can be updated simultaneously by omitting the machine name:
clan machines updateVars
The vars system is clan’s declarative solution for managing generated files, secrets, and dynamic configuration in your NixOS deployments. It eliminates the manual steps of generating credentials, certificates, and other dynamic values by automating these processes within your infrastructure-as-code workflow.
Before Vars: Manual Secret Management
Traditional NixOS deployments require manual steps for secrets and generated files:
# Generate password hash manually
mkpasswd -m sha-512 > /tmp/root-password-hash# Copy hash into configuration
users.users.root.hashedPasswordFile = "/tmp/root-password-hash";This approach has several problems:
- Not reproducible: Manual steps vary between team members
- Hard to maintain: Updating secrets requires remembering manual commands
- Deployment friction: Secrets must be managed outside of your configuration
- Team collaboration issues: Sharing credentials securely is complex
After Vars: Declarative Generation
With vars, the same process becomes declarative and automated:
clan.core.vars.generators.root-password = {
prompts.password.description = "Root password";
prompts.password.type = "hidden";
files.hash.secret = false;
script = "mkpasswd -m sha-512 < $prompts/password > $out/hash";
runtimeInputs = [ pkgs.mkpasswd ];
};
users.users.root.hashedPasswordFile =
config.clan.core.vars.generators.root-password.files.hash.path;Core Benefits:
- 🔄 Reproducible: Same inputs always produce the same outputs
- 📝 Declarative: Defined alongside your NixOS configuration
- 🔐 Secure: Automatic secret storage and encrypted deployment
- 👥 Collaborative: Built-in sharing for team environments
- 🚀 Automated: No manual intervention required for deployments
- 🔗 Integrated: Works seamlessly with clan’s deployment workflow
Usage
Define how to create files from inputs:
- Prompts: Values requested from users
- Scripts: Generation logic
- Dependencies: Other generators this depends on
- Outputs: Files that get created
Here’s a complete example showing password generation and usage:
# generator.nix
{ config, pkgs, ... }: {
clan.core.vars.generators.user-password = {
prompts.password = {
description = "User password";
type = "hidden";
};
files.hash = { secret = false; };
script = ''
mkpasswd -m sha-512 < $prompts/password > $out/hash
'';
runtimeInputs = [ pkgs.mkpasswd ];
};
users.users.myuser = {
hashedPasswordFile =
config.clan.core.vars.generators.user-password.files.hash.path;
};
}# Generate the password
clan vars generate my-machine
# Deploy to machine
clan machines update my-machineDeclare a generator
In this example, a vars generator is used to:
- prompt the user for the password
- run the required
mkpasswdcommand to generate the hash - store the hash in a file
- expose the file path to the nixos configuration
- Create a new nix file
root-password.nixwith the following content and import it into yourconfiguration.nix
{config, pkgs, ...}: {
clan.core.vars.generators.root-password = {
# prompt the user for a password
# (`password-input` being an arbitrary name)
prompts.password-input.description = "the root user's password";
prompts.password-input.type = "hidden";
# don't store the prompted password itself
prompts.password-input.persist = false;
# define an output file for storing the hash
files.password-hash.secret = false;
# define the logic for generating the hash
script = ''
cat $prompts/password-input | mkpasswd -m sha-512 > $out/password-hash
'';
# the tools required by the script
runtimeInputs = [ pkgs.mkpasswd ];
};
# ensure users are immutable (otherwise the following config might be ignored)
users.mutableUsers = false;
# set the root password to the file containing the hash
users.users.root.hashedPasswordFile =
# clan will make sure, this path exists
config.clan.core.vars.generators.root-password.files.password-hash.path;
}Inspect the status
Executing clan vars list, you should see the following:
$ clan vars list my_machine
root-password/password-hash: <not set>
…indicating that the value password-hash for the generator root-password is not set yet.
Generate the values
This step is not strictly necessary, as deploying the machine via clan machines update would trigger the generator as well.
To run the generator, execute clan vars generate for your machine
$ clan vars generate my_machine
Enter the value for root-password/password-input (hidden):
After entering the value, the updated status is reported:
Updated var root-password/password-hash
old: <not set>
new: $6$RMats/YMeypFtcYX$DUi...
Observe the changes
With the last step, a new file was created in your repository: vars/per-machine/my-machine/root-password/password-hash/value
If the repository is a git repository, a commit was created automatically:
$ git log -n1
commit ... (HEAD -> master)
Author: ...
Date: ...
Update vars via generator root-password for machine grmpf-nix
Update the machine
clan machines update my_machine
Share root password between machines
If we just imported the root-password.nix from above into more machines, clan would ask for a new password for each additional machine.
If the root password instead should only be entered once and shared across all machines, the generator defined above needs to be declared as shared, by adding share = true to it:
{config, pkgs, ...}: {
clan.core.vars.generators.root-password = {
share = true;
# ...
}
}Importing that shared generator into each machine, will ensure that the password is only asked once the first machine gets updated and then re-used for all subsequent machines.
Change the root password
Changing the password can be done via this command. Replace my-machine with your machine. If the password is shared, just pick any machine that has the generator declared.
$ clan vars generate my-machine --generator root-password --regenerate
...
Enter the value for root-password/password-input (hidden):
Input received. Processing...
...
Updated var root-password/password-hash
old: $6$tb27m6EOdff.X9TM$19N...
new: $6$OyoQtDVzeemgh8EQ$zRK...
Deploy
Flash Installer
To install Clan on physical machines, you need to use our custom installer image. This is necessary for proper installation and operation.
We recommend to build your own installer:
clan flash write --flake https://git.clan.lol/clan/clan-core/archive/main.tar.gz \
--ssh-pubkey $HOME/.ssh/id_ed25519.pub \
--keymap us \
--language en_US.UTF-8 \
--disk main /dev/sd<X> \
flash-installerReplace $HOME/.ssh/id_ed25519.pub with a path to your SSH public key. Replace /dev/sd<X> with the drive path you want to flash.
Specifying the wrong device can lead to unrecoverable data loss. The clan flash utility will erase the disk. Make sure to specify the correct device
SSH-Pubkey Option
To add an ssh public key into the installer image append the option: --ssh-pubkey <pubkey_path>
If you do not have an ssh key yet, you can generate one with ssh-keygen -t ed25519 command. This ssh key will be installed into the root user.
Connect to the installer
On boot, the installer will display on-screen the IP address it received from the network. If you need to configure Wi-Fi first, refer to the next section. If Multicast-DNS (Avahi) is enabled on your own machine, you can also access the installer using the flash-installer.local address.
List Keymaps
You can get a list of all keymaps with the following command:
clan flash list keymapsList Languages
You can get a list of all languages with the following command:
clan flash list languagesWiFi
If you don’t have access via LAN the Installer offers support for connecting via Wifi.
iwctlThis will enter iwd
[iwd]#
Now run the following command to connect to your Wifi:
# Identify your network device.
device list
# Replace 'wlan0' with your wireless device name
# Find your Wifi SSID.
station wlan0 scan
station wlan0 get-networks
# Replace your_ssid with the Wifi SSID
# Connect to your network.
station wlan0 connect your_ssid
# Verify you are connected
station wlan0 showIf the connection was successful you should see something like this:
State connected
Connected network FRITZ!Box (Your router device)
IPv4 address 192.168.188.50 (Your new local ip)
Press Ctrl+D to exit IWD.
Important Press
Ctrl+Dagain to update the displayed QR code and connection information.
You’re all set up
Install
The installer will randomly generate a password and local addresses on boot, then run a SSH server with these preconfigured. The installer shows its deployment relevant information in two formats, a text form, as well as a QR code.
This is an example of the booted installer.
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ ┌───────────────────────────┐ │
│ │███████████████████████████│ # This is the QR Code │
│ │██ ▄▄▄▄▄ █▀▄█▀█▀▄█ ▄▄▄▄▄ ██│ │
│ │██ █ █ █▀▄▄▄█ ▀█ █ █ ██│ │
│ │██ █▄▄▄█ █▀▄ ▀▄▄▄█ █▄▄▄█ ██│ │
│ │██▄▄▄▄▄▄▄█▄▀ ▀▄▀▄█▄▄▄▄▄▄▄██│ │
│ │███▀▀▀ █▄▄█ ▀▄ ▄▀▄█ ███│ │
│ │██▄██▄▄█▄▄▀▀██▄▀ ▄▄▄ ▄▀█▀██│ │
│ │██ ▄▄▄▄▄ █▄▄▄▄ █ █▄█ █▀ ███│ │
│ │██ █ █ █ █ █ ▄▄▄ ▄▀▀ ██│ │
│ │██ █▄▄▄█ █ ▄ ▄ ▄ ▀█ ▄███│ │
│ │██▄▄▄▄▄▄▄█▄▄▄▄▄▄█▄▄▄▄▄█▄███│ │
│ │███████████████████████████│ │
│ └───────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │Root password: cheesy-capital-unwell # password (2) │ │
│ │Local network addresses: │ │
│ │enp1s0 UP 192.168.178.169/24 metric 1024 fe80::21e:6ff:fe45:3c92/64 │ │
│ │enp2s0 DOWN │ │
│ │wlan0 DOWN # connect to wlan (3) │ │
│ │Onion address: 6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion │ │
│ │Multicast DNS: nixos-installer.local │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ Press 'Ctrl-C' for console access │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
Generate hardware report
The following command will generate a hardware report with nixos-facter and writes it back into your machine folder. The —phases kexec flag makes sure we are not yet formatting anything, instead if the target system is not a NixOS machine it will use kexec to switch to a NixOS kernel.
Copy the JSON string contained in the QR Code and provide its path or paste it directly:
clan machines install [MACHINE] --json [JSON] \
--update-hardware-config nixos-facter \
--phases kexecor manually with password
clan machines install [MACHINE] \
--update-hardware-config nixos-facter \
--phases kexec \
--target-host root@192.168.178.169Disk Format
By default clan uses disko which allows for declarative disk partitioning.
To see what disk templates are available run:
$ clan templates list
To setup a disk schema for a machine run
clan templates apply disk single-disk jon --set mainDisk ""Which should fail and give the valid options for the specific hardware:
Invalid value for placeholder mainDisk - Valid options:
/dev/disk/by-id/nvme-WD_PC_SN740_SDDQNQD-512G-1201_232557804368
Re-run the command with the correct disk:
clan templates apply disk single-disk jon --set mainDisk "/dev/disk/by-id/nvme-WD_PC_SN740_SDDQNQD-512G-1201_232557804368"Should now be successful
Applied disk template 'single-disk' to machine 'jon'
A disko.nix file should be created in machines/jon. You can have a look and customize it if needed.
Danger: Don’t change the disko.nix after the machine is installed for the first time, unless you really know what you are doing. Changing disko configuration requires wiping and reinstalling the machine.
Run Install
This command is destructive and will format your disk and install NixOS on it! It is equivalent to appending --phases kexec,disko,install,reboot.
clan machines install [MACHINE] --target-host root@<IP>