adapt generator modules from clan-core modules

see https://git.clan.lol/clan/clan-core/src/branch/main/clanServices
This commit is contained in:
Kiara Grouwstra 2025-08-24 11:11:10 +02:00
parent 9c9be8e258
commit 93922fa925
Signed by: kiara
SSH key fingerprint: SHA256:COspvLoLJ5WC5rFb9ZDe5urVCkK4LJZOsjfF4duRJFU
15 changed files with 511 additions and 0 deletions

View file

@ -16,6 +16,7 @@
in {
nixosModules.default = { imports = [ ./options.nix ]; };
nixosModules.backend-on-machine = { imports = [ ./backends/on-machine.nix ]; };
nixosModules.generators = { imports = [ ./generators ]; };
# TODO fix tests
checks = forAllSystems (system: let
tests = {

11
generators/default.nix Normal file
View file

@ -0,0 +1,11 @@
{
imports = [
./disk-id
./garage
./machine-id
./root-password
./sshd
./state-version
./user-password
];
}

View file

@ -0,0 +1,30 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.vars.disk-id;
in
{
options.vars.disk-id.enable = lib.mkEnableOption "Generates a uuid for use in disk device naming";
config = lib.mkIf cfg.enable {
vars.generators.disk-id = {
files.diskId.secret = false;
runtimeInputs = [
pkgs.coreutils
pkgs.bash
];
script = ''
uuid=$(bash ${./uuid4.sh})
# Remove the hyphens from the UUID
uuid_no_hyphens=$(echo -n "$uuid" | tr -d '-')
echo -n "$uuid_no_hyphens" > "$out/diskId"
'';
};
disko.devices.disk."main".name = "main-" + config.vars.generators.disk-id.files.diskId.value;
};
}

View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Read 16 bytes from /dev/urandom
uuid=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | od -An -tx1 | tr -d ' \n')
# Break the UUID into pieces and apply the required modifications
byte6=${uuid:12:2}
byte8=${uuid:16:2}
# Construct the correct version and variant
hex_byte6=$(printf "%x" $((0x$byte6 & 0x0F | 0x40)))
hex_byte8=$(printf "%x" $((0x$byte8 & 0x3F | 0x80)))
# Rebuild the UUID with the correct fields
uuid_v4="${uuid:0:12}${hex_byte6}${uuid:14:2}${hex_byte8}${uuid:18:14}"
# Format the UUID correctly 8-4-4-4-12
uuid_formatted="${uuid_v4:0:8}-${uuid_v4:8:4}-${uuid_v4:12:4}-${uuid_v4:16:4}-${uuid_v4:20:12}"
echo -n "$uuid_formatted"

View file

@ -0,0 +1,59 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.vars.garage;
in
{
options.vars.garage.enable = lib.mkEnableOption ''
S3-compatible object store for small self-hosted geo-distributed deployments.
This module generates garage-specific keys automatically.
Options: [NixosModuleOptions](https://search.nixos.org/options?channel=unstable&size=50&sort=relevance&type=packages&query=garage)
Documentation: https://garagehq.deuxfleurs.fr/
'';
config = lib.mkIf cfg.enable {
systemd.services.garage.serviceConfig = {
LoadCredential = [
"rpc_secret_path:${config.vars.generators.garage-shared.files.rpc_secret.path}"
"admin_token_path:${config.vars.generators.garage.files.admin_token.path}"
"metrics_token_path:${config.vars.generators.garage.files.metrics_token.path}"
];
Environment = [
"GARAGE_ALLOW_WORLD_READABLE_SECRETS=true"
"GARAGE_RPC_SECRET_FILE=%d/rpc_secret_path"
"GARAGE_ADMIN_TOKEN_FILE=%d/admin_token_path"
"GARAGE_METRICS_TOKEN_FILE=%d/metrics_token_path"
];
};
vars.generators.garage = {
files.admin_token = { };
files.metrics_token = { };
runtimeInputs = [
pkgs.coreutils
pkgs.openssl
];
script = ''
openssl rand -base64 -out "$out"/admin_token 32
openssl rand -base64 -out "$out"/metrics_token 32
'';
};
vars.generators.garage-shared = {
share = true;
files.rpc_secret = { };
runtimeInputs = [
pkgs.coreutils
pkgs.openssl
];
script = ''
openssl rand -hex -out "$out"/rpc_secret 32
'';
};
};
}

View file

@ -0,0 +1,46 @@
{
config,
lib,
pkgs,
...
}:
let
var = config.vars.generators.machine-id.files.machineId or { };
cfg = config.vars.machine-id;
in
{
options.vars.machine-id.enable = lib.mkEnableOption "Sets the /etc/machine-id and exposes it as a nix option";
config = lib.mkIf cfg.enable (lib.mkMerge [
(lib.mkIf ((var.value or null) != null) {
assertions = [
{
assertion = lib.stringLength var.value == 32;
message = "machineId must be exactly 32 characters long.";
}
];
boot.kernelParams = [
''systemd.machine_id=${var.value}''
];
environment.etc."machine-id" = {
text = var.value;
};
})
{
vars.generators.machine-id = {
files.machineId.secret = false;
runtimeInputs = [
pkgs.coreutils
pkgs.bash
];
script = ''
uuid=$(bash ${./uuid4.sh})
# Remove the hyphens from the UUID
uuid_no_hyphens=$(echo -n "$uuid" | tr -d '-')
echo -n "$uuid_no_hyphens" > "$out/machineId"
'';
};
}
]);
}

View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Read 16 bytes from /dev/urandom
uuid=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | od -An -tx1 | tr -d ' \n')
# Break the UUID into pieces and apply the required modifications
byte6=${uuid:12:2}
byte8=${uuid:16:2}
# Construct the correct version and variant
hex_byte6=$(printf "%x" $((0x$byte6 & 0x0F | 0x40)))
hex_byte8=$(printf "%x" $((0x$byte8 & 0x3F | 0x80)))
# Rebuild the UUID with the correct fields
uuid_v4="${uuid:0:12}${hex_byte6}${uuid:14:2}${hex_byte8}${uuid:18:14}"
# Format the UUID correctly 8-4-4-4-12
uuid_formatted="${uuid_v4:0:8}-${uuid_v4:8:4}-${uuid_v4:12:4}-${uuid_v4:16:4}-${uuid_v4:20:12}"
echo -n "$uuid_formatted"

9
generators/options.nix Normal file
View file

@ -0,0 +1,9 @@
{
lib,
...
}:
{
options.vars = {
unattended = lib.mkEnableOption "Whether to default to generating values unattended, rather than prompting for desired values.";
};
}

View file

@ -0,0 +1,60 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.vars.root-password;
in
{
imports = [
../options.nix
];
options.vars.root-password = {
enable = lib.mkEnableOption "Automatically generates and configures a password for the root user.";
prompt = lib.mkOption {
type = lib.types.bool;
default = !config.vars.unattended;
example = true;
description = "Whether the user should be prompted.";
};
};
config = lib.mkIf cfg.enable {
users.mutableUsers = false;
users.users.root.hashedPasswordFile =
config.vars.generators.root-password.files.password-hash.path;
vars.generators.root-password = {
files.password-hash = {
neededFor = "users";
};
files.password-hash.restartUnits = lib.optional (config.services.userborn.enable) "userborn.service";
files.password = {
deploy = false;
};
runtimeInputs = [
pkgs.coreutils
pkgs.mkpasswd
pkgs.xkcdpass
];
prompts = lib.mkIf cfg.prompt {
password = {
type = "hidden";
persist = true;
description = "You can autogenerate a password, if you leave this prompt blank.";
};
};
script = ''
prompt_value=${if cfg.prompt then ''$(cat "$prompts"/password)'' else ""}
if [[ -n "''${prompt_value-}" ]]; then
echo "$prompt_value" | tr -d "\n" > "$out"/password
else
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > "$out"/password
fi
mkpasswd -s -m sha-512 < "$out"/password | tr -d "\n" > "$out"/password-hash
'';
};
};
}

View file

@ -0,0 +1,6 @@
{ ... }:
{
imports = [
./shared.nix
];
}

View file

@ -0,0 +1,6 @@
{
imports = [
./client.nix
./server.nix
];
}

103
generators/sshd/server.nix Normal file
View file

@ -0,0 +1,103 @@
{
config,
pkgs,
lib,
...
}:
let
stringSet = list: builtins.attrNames (builtins.groupBy lib.id list);
domains = stringSet config.vars.sshd.certificate.searchDomains;
cfg = config.vars.sshd;
name = config.networking.hostName;
in
{
imports = [ ../shared.nix ];
options = {
vars.sshd = {
enable = lib.mkEnableOption "Set up the opensshd service, generating a host key for the machine.";
hostKeys.rsa.enable = lib.mkEnableOption "Generate RSA host key";
};
};
config = lib.mkIf cfg.enable {
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
settings.HostCertificate = lib.mkIf (
cfg.certificate.searchDomains != [ ]
) config.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path;
hostKeys =
[
{
path = config.vars.generators.openssh.files."ssh.id_ed25519".path;
type = "ed25519";
}
]
++ lib.optional cfg.hostKeys.rsa.enable {
path = config.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
type = "rsa";
};
};
vars.generators.openssh = {
files."ssh.id_ed25519" = { };
files."ssh.id_ed25519.pub".secret = false;
runtimeInputs = [
pkgs.coreutils
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/ssh.id_ed25519
'';
};
programs.ssh.knownHosts.sshd-self-ed25519 = {
hostNames = [
"localhost"
config.networking.hostName
] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
publicKey = config.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
};
vars.generators.openssh-rsa = lib.mkIf config.vars.sshd.hostKeys.rsa.enable {
files."ssh.id_rsa" = { };
files."ssh.id_rsa.pub".secret = false;
runtimeInputs = [
pkgs.coreutils
pkgs.openssh
];
script = ''
ssh-keygen -t rsa -b 4096 -N "" -f "$out"/ssh.id_rsa
'';
};
vars.generators.openssh-cert = lib.mkIf (cfg.certificate.searchDomains != [ ]) {
files."ssh.id_ed25519-cert.pub".secret = false;
dependencies = [
"openssh"
"openssh-ca"
];
validation = {
inherit name;
domains = lib.genAttrs config.vars.sshd.certificate.searchDomains lib.id;
};
runtimeInputs = [
pkgs.openssh
pkgs.jq
];
script = ''
ssh-keygen \
-s $in/openssh-ca/id_ed25519 \
-I ${name} \
-h \
-n ${lib.concatMapStringsSep "," (d: "${name}.${d}") domains} \
$in/openssh/ssh.id_ed25519.pub
mv $in/openssh/ssh.id_ed25519-cert.pub "$out"/ssh.id_ed25519-cert.pub
'';
};
};
}

View file

@ -0,0 +1,44 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.vars.sshd.certificate;
in
{
options.vars.sshd.certificate = {
enable = lib.mkEnableOption "Add machines to the known hosts, enabling secure remote access to them over ssh.";
searchDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "mydomain.com" ];
description = "List of domains to include in the certificate. This option will prepend the machine name in front of each domain before adding it to the certificate.";
};
};
config = lib.mkIf cfg.enable {
vars.generators.openssh-ca =
lib.mkIf (config.vars.sshd.certificate.searchDomains != [ ])
{
share = true;
files.id_ed25519.deploy = false;
files."id_ed25519.pub" = {
deploy = false;
secret = false;
};
runtimeInputs = [
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/id_ed25519
'';
};
programs.ssh.knownHosts.ssh-ca = lib.mkIf (config.vars.sshd.certificate.searchDomains != [ ]) {
certAuthority = true;
extraHostNames = builtins.map (domain: "*.${domain}") config.vars.sshd.certificate.searchDomains;
publicKey = config.vars.generators.openssh-ca.files."id_ed25519.pub".value;
};
};
}

View file

@ -0,0 +1,22 @@
{ config, lib, ... }:
let
var = config.vars.generators.state-version.files.version or { };
cfg = config.vars.state-version;
in
{
options.vars.state-version.enable = lib.mkEnableOption "Automatically generate the state version of the nixos installation.";
config = lib.mkIf cfg.enable {
system.stateVersion = lib.mkDefault (lib.removeSuffix "\n" var.value);
vars.generators.state-version = {
files.version = {
secret = false;
value = lib.mkDefault config.system.nixos.release;
};
runtimeInputs = [ ];
script = ''
echo -n ${config.system.stateVersion} > "$out"/version
'';
};
};
}

View file

@ -0,0 +1,74 @@
{
pkgs,
config,
lib,
...
}:
let
cfg = config.vars.user-password;
in
{
imports = [
../options.nix
];
options.vars.user-password = {
enable = lib.mkEnableOption ''
Automatically generates and configures a password for a user.
This will set `mutableUsers` to `false`, meaning you can not manage user passwords through `passwd` anymore.
'';
users = lib.mkOption {
type = lib.types.attrsOf (lib.submodule {
options = {
prompt = lib.mkOption {
type = lib.types.bool;
default = !config.vars.unattended;
example = true;
description = "Whether the user should be prompted.";
};
};
});
description = "The users for which to generate passwords.";
example = "{ alice = { }; }";
};
};
config = lib.mkIf cfg.enable {
users.mutableUsers = false;
users.users = lib.mapAttrs (userName: _: {
hashedPasswordFile = config.vars.generators."user-password-${userName}".files.user-password-hash.path;
isNormalUser = lib.mkDefault true;
}) cfg.users;
vars.generators = lib.mapAttrs (userName: user: {
"user-password-${userName}" = {
files.user-password-hash.neededFor = "users";
files.user-password-hash.restartUnits = lib.optional (config.services.userborn.enable) "userborn.service";
prompts = lib.mkIf user.prompt {
"user-password-${userName}" = {
type = "hidden";
persist = true;
description = "You can autogenerate a password, if you leave this prompt blank.";
files.user-password.deploy = false;
};
};
runtimeInputs = [
pkgs.coreutils
pkgs.xkcdpass
pkgs.mkpasswd
];
script = ''
prompt_value=${if user.prompt then ''$(cat "$prompts"/user-password)'' else ""}
if [[ -n "''${prompt_value-}" ]]; then
echo "$prompt_value" | tr -d "\n" > "$out"/user-password
else
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > "$out"/user-password
fi
mkpasswd -s -m sha-512 < "$out"/user-password | tr -d "\n" > "$out"/user-password-hash
'';
};
}) cfg.users;
};
}