mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
wip: remove very specific special args and unify library functions
This commit is contained in:
parent
dfc3084fe9
commit
68bb9731d3
28 changed files with 594 additions and 644 deletions
38
flake.nix
38
flake.nix
|
@ -68,18 +68,15 @@
|
||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
colmena,
|
colmena,
|
||||||
nixos-generators,
|
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
microvm,
|
microvm,
|
||||||
flake-utils,
|
flake-utils,
|
||||||
agenix-rekey,
|
agenix-rekey,
|
||||||
...
|
...
|
||||||
} @ inputs: let
|
} @ inputs: let
|
||||||
recursiveMergeAttrs = nixpkgs.lib.foldl' nixpkgs.lib.recursiveUpdate {};
|
inherit (nixpkgs) lib;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
extraLib = import ./nix/lib.nix inputs;
|
|
||||||
|
|
||||||
# The identities that are used to rekey agenix secrets and to
|
# The identities that are used to rekey agenix secrets and to
|
||||||
# decrypt all repository-wide secrets.
|
# decrypt all repository-wide secrets.
|
||||||
secretsConfig = {
|
secretsConfig = {
|
||||||
|
@ -87,8 +84,8 @@
|
||||||
extraEncryptionPubkeys = [./secrets/backup.pub];
|
extraEncryptionPubkeys = [./secrets/backup.pub];
|
||||||
};
|
};
|
||||||
|
|
||||||
stateVersion = "23.05";
|
# This is the list of hosts that this flake defines, plus the minimum
|
||||||
|
# amount of metadata that is necessary to instanciate it correctly.
|
||||||
hosts = let
|
hosts = let
|
||||||
nixos = system: {
|
nixos = system: {
|
||||||
type = "nixos";
|
type = "nixos";
|
||||||
|
@ -101,21 +98,30 @@
|
||||||
zackbiene = nixos "aarch64-linux";
|
zackbiene = nixos "aarch64-linux";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# This will process all defined hosts of type "nixos" and
|
||||||
|
# generate the required colmena definition for each host.
|
||||||
|
# We call the resulting instanciations "nodes".
|
||||||
|
# TODO: switch to nixosConfigurations once colmena supports it upstream
|
||||||
colmena = import ./nix/colmena.nix inputs;
|
colmena = import ./nix/colmena.nix inputs;
|
||||||
colmenaNodes = ((colmena.lib.makeHive self.colmena).introspect (x: x)).nodes;
|
colmenaNodes = ((colmena.lib.makeHive self.colmena).introspect (x: x)).nodes;
|
||||||
# Collect all defined microvm nodes from each colmena node
|
|
||||||
microvmNodes = nixpkgs.lib.concatMapAttrs (_: node:
|
# True NixOS nodes can define additional microvms (guest nodes) that are built
|
||||||
nixpkgs.lib.mapAttrs'
|
# together with the true host. We collect all defined microvm nodes
|
||||||
(vm: def: nixpkgs.lib.nameValuePair def.nodeName node.config.microvm.vms.${vm}.config)
|
# from each node here to allow accessing any node via the unified attribute `nodes`.
|
||||||
(node.config.meta.microvms.vms or {}))
|
microvmNodes = lib.flip lib.concatMapAttrs self.colmenaNodes (_: node:
|
||||||
self.colmenaNodes;
|
lib.mapAttrs'
|
||||||
# Expose all nodes in a single attribute
|
(vm: def: lib.nameValuePair def.nodeName node.config.microvm.vms.${vm}.config)
|
||||||
|
(node.config.meta.microvms.vms or {}));
|
||||||
|
|
||||||
|
# All nixosSystem instanciations are collected here, so that we can refer
|
||||||
|
# to any system via nodes.<name>
|
||||||
nodes = self.colmenaNodes // self.microvmNodes;
|
nodes = self.colmenaNodes // self.microvmNodes;
|
||||||
|
|
||||||
# Collect installer packages
|
# For each true NixOS system, we want to expose an installer image that
|
||||||
|
# can be used to do setup on the node.
|
||||||
inherit
|
inherit
|
||||||
(recursiveMergeAttrs
|
(lib.foldl' lib.recursiveUpdate {}
|
||||||
(nixpkgs.lib.mapAttrsToList
|
(lib.mapAttrsToList
|
||||||
(import ./nix/generate-installer.nix inputs)
|
(import ./nix/generate-installer.nix inputs)
|
||||||
self.colmenaNodes))
|
self.colmenaNodes))
|
||||||
packages
|
packages
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
{
|
{
|
||||||
nixos-hardware,
|
inputs,
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
imports = [
|
imports = [
|
||||||
nixos-hardware.common-cpu-intel
|
inputs.nixos-hardware.nixosModules.common-cpu-intel
|
||||||
nixos-hardware.common-gpu-intel
|
inputs.nixos-hardware.nixosModules.common-gpu-intel
|
||||||
nixos-hardware.common-pc-laptop
|
inputs.nixos-hardware.nixosModules.common-pc-laptop
|
||||||
nixos-hardware.common-pc-laptop-ssd
|
inputs.nixos-hardware.nixosModules.common-pc-laptop-ssd
|
||||||
../../modules/optional/hardware/intel.nix
|
../../modules/optional/hardware/intel.nix
|
||||||
../../modules/optional/hardware/physical.nix
|
../../modules/optional/hardware/physical.nix
|
||||||
|
|
||||||
../../modules
|
#../../modules
|
||||||
|
../../modules/config/lib.nix
|
||||||
|
|
||||||
../../modules/optional/boot-efi.nix
|
../../modules/optional/boot-efi.nix
|
||||||
../../modules/optional/initrd-ssh.nix
|
../../modules/optional/initrd-ssh.nix
|
||||||
../../modules/optional/dev
|
../../modules/optional/dev
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
extraLib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
|
@ -10,7 +9,7 @@
|
||||||
m2-ssd = {
|
m2-ssd = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.m2-ssd}";
|
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.m2-ssd}";
|
||||||
content = with extraLib.disko.gpt; {
|
content = with config.lib.disko.gpt; {
|
||||||
type = "table";
|
type = "table";
|
||||||
format = "gpt";
|
format = "gpt";
|
||||||
partitions = [
|
partitions = [
|
||||||
|
@ -21,7 +20,7 @@
|
||||||
boot-ssd = {
|
boot-ssd = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.boot-ssd}";
|
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.boot-ssd}";
|
||||||
content = with extraLib.disko.gpt; {
|
content = with config.lib.disko.gpt; {
|
||||||
type = "table";
|
type = "table";
|
||||||
format = "gpt";
|
format = "gpt";
|
||||||
partitions = [
|
partitions = [
|
||||||
|
@ -31,25 +30,10 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
zpool = with extraLib.disko.zfs; {
|
zpool = with config.lib.disko.zfs; {
|
||||||
rpool = defaultZpoolOptions // {datasets = defaultZfsDatasets;};
|
rpool = defaultZpoolOptions // {datasets = defaultZfsDatasets;};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO remove once this is upstreamed
|
boot.initrd.luks.devices.enc-rpool.allowDiscards = true;
|
||||||
boot.initrd.systemd.services."zfs-import-rpool".after = ["cryptsetup.target"];
|
|
||||||
fileSystems."/state".neededForBoot = true;
|
|
||||||
fileSystems."/persist".neededForBoot = true;
|
|
||||||
|
|
||||||
# After importing the rpool, rollback the root system to be empty.
|
|
||||||
boot.initrd.systemd.services.impermanence-root = {
|
|
||||||
wantedBy = ["initrd.target"];
|
|
||||||
after = ["zfs-import-rpool.service"];
|
|
||||||
before = ["sysroot.mount"];
|
|
||||||
unitConfig.DefaultDependencies = "no";
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
ExecStart = "${pkgs.zfs}/bin/zfs rollback -r rpool/local/root@blank";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
extraLib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
|
@ -9,7 +8,7 @@
|
||||||
main = {
|
main = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.main}";
|
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.main}";
|
||||||
content = with extraLib.disko.gpt; {
|
content = with config.lib.disko.gpt; {
|
||||||
type = "table";
|
type = "table";
|
||||||
format = "gpt";
|
format = "gpt";
|
||||||
partitions = [
|
partitions = [
|
||||||
|
@ -20,27 +19,11 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
zpool = with extraLib.disko.zfs; {
|
zpool = with config.lib.disko.zfs; {
|
||||||
rpool = defaultZpoolOptions // {datasets = defaultZfsDatasets;};
|
rpool = defaultZpoolOptions // {datasets = defaultZfsDatasets;};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
boot.loader.grub.devices = ["/dev/disk/by-id/${config.repo.secrets.local.disk.main}"];
|
boot.loader.grub.devices = ["/dev/disk/by-id/${config.repo.secrets.local.disk.main}"];
|
||||||
boot.initrd.luks.devices.enc-rpool.allowDiscards = true;
|
boot.initrd.luks.devices.enc-rpool.allowDiscards = true;
|
||||||
# TODO remove once this is upstreamed
|
|
||||||
boot.initrd.systemd.services."zfs-import-rpool".after = ["cryptsetup.target"];
|
|
||||||
fileSystems."/state".neededForBoot = true;
|
|
||||||
fileSystems."/persist".neededForBoot = true;
|
|
||||||
|
|
||||||
# After importing the rpool, rollback the root system to be empty.
|
|
||||||
boot.initrd.systemd.services.impermanence-root = {
|
|
||||||
wantedBy = ["initrd.target"];
|
|
||||||
after = ["zfs-import-rpool.service"];
|
|
||||||
before = ["sysroot.mount"];
|
|
||||||
unitConfig.DefaultDependencies = "no";
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
ExecStart = "${pkgs.zfs}/bin/zfs rollback -r rpool/local/root@blank";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
|
inputs,
|
||||||
config,
|
config,
|
||||||
nixos-hardware,
|
|
||||||
nodes,
|
nodes,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
imports = [
|
imports = [
|
||||||
nixos-hardware.common-cpu-intel
|
inputs.nixos-hardware.nixosModules.common-cpu-intel
|
||||||
nixos-hardware.common-pc-ssd
|
inputs.nixos-hardware.nixosModules.common-pc-ssd
|
||||||
../../modules/optional/hardware/intel.nix
|
../../modules/optional/hardware/intel.nix
|
||||||
../../modules/optional/hardware/physical.nix
|
../../modules/optional/hardware/physical.nix
|
||||||
|
|
||||||
|
@ -50,6 +50,13 @@
|
||||||
enable = true;
|
enable = true;
|
||||||
pool = "rpool";
|
pool = "rpool";
|
||||||
};
|
};
|
||||||
|
todo
|
||||||
|
configPath =
|
||||||
|
if nodePath != null && builtins.pathExists (nodePath + "/microvms/${name}") then
|
||||||
|
nodePath + "/microvms/${name}"
|
||||||
|
else if nodePath != null && builtins.pathExists (nodePath + "/microvms/${name}") then
|
||||||
|
nodePath + "/microvms/${name}.nix"
|
||||||
|
else null;
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
kanidm = defaults;
|
kanidm = defaults;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
extraLib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
|
@ -10,7 +9,7 @@
|
||||||
m2-ssd = {
|
m2-ssd = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.m2-ssd}";
|
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.m2-ssd}";
|
||||||
content = with extraLib.disko.gpt; {
|
content = with config.lib.disko.gpt; {
|
||||||
type = "table";
|
type = "table";
|
||||||
format = "gpt";
|
format = "gpt";
|
||||||
partitions = [
|
partitions = [
|
||||||
|
@ -21,7 +20,7 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
zpool = with extraLib.disko.zfs; {
|
zpool = with config.lib.disko.zfs; {
|
||||||
rpool =
|
rpool =
|
||||||
defaultZpoolOptions
|
defaultZpoolOptions
|
||||||
// {
|
// {
|
||||||
|
@ -34,20 +33,5 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO remove once this is upstreamed
|
boot.initrd.luks.devices.enc-rpool.allowDiscards = true;
|
||||||
boot.initrd.systemd.services."zfs-import-rpool".after = ["cryptsetup.target"];
|
|
||||||
fileSystems."/state".neededForBoot = true;
|
|
||||||
fileSystems."/persist".neededForBoot = true;
|
|
||||||
|
|
||||||
# After importing the rpool, rollback the root system to be empty.
|
|
||||||
boot.initrd.systemd.services.impermanence-root = {
|
|
||||||
wantedBy = ["initrd.target"];
|
|
||||||
after = ["zfs-import-rpool.service"];
|
|
||||||
before = ["sysroot.mount"];
|
|
||||||
unitConfig.DefaultDependencies = "no";
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
ExecStart = "${pkgs.zfs}/bin/zfs rollback -r rpool/local/root@blank";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
nodeName,
|
|
||||||
nodes,
|
nodes,
|
||||||
utils,
|
utils,
|
||||||
...
|
...
|
||||||
|
@ -123,7 +122,7 @@ in {
|
||||||
url = "https://${sentinelCfg.networking.providedDomains.loki}";
|
url = "https://${sentinelCfg.networking.providedDomains.loki}";
|
||||||
orgId = 1;
|
orgId = 1;
|
||||||
basicAuth = true;
|
basicAuth = true;
|
||||||
basicAuthUser = "${nodeName}+grafana-loki-basic-auth-password";
|
basicAuthUser = "${config.repo.node.name}+grafana-loki-basic-auth-password";
|
||||||
secureJsonData.basicAuthPassword = "$__file{${config.age.secrets.grafana-loki-basic-auth-password.path}}";
|
secureJsonData.basicAuthPassword = "$__file{${config.age.secrets.grafana-loki-basic-auth-password.path}}";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
{
|
{lib, ...}: {
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
nixos-hardware,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
imports = [
|
imports = [
|
||||||
../../modules/optional/hardware/odroid-n2plus.nix
|
../../modules/optional/hardware/odroid-n2plus.nix
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
# TODO disko
|
||||||
fileSystems = {
|
fileSystems = {
|
||||||
"/" = {
|
"/" = {
|
||||||
device = "rpool/root/nixos";
|
device = "rpool/root/nixos";
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
# State that should be kept across reboots, but is otherwise
|
# State that should be kept across reboots, but is otherwise
|
||||||
# NOT important information in any way that needs to be backed up.
|
# NOT important information in any way that needs to be backed up.
|
||||||
|
fileSystems."/state".neededForBoot = true;
|
||||||
environment.persistence."/state" = {
|
environment.persistence."/state" = {
|
||||||
hideMounts = true;
|
hideMounts = true;
|
||||||
directories =
|
directories =
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# State that should be kept forever, and backed up accordingly.
|
# State that should be kept forever, and backed up accordingly.
|
||||||
|
fileSystems."/persist".neededForBoot = true;
|
||||||
environment.persistence."/persist" = {
|
environment.persistence."/persist" = {
|
||||||
hideMounts = true;
|
hideMounts = true;
|
||||||
files = [
|
files = [
|
||||||
|
|
|
@ -1,16 +1,244 @@
|
||||||
{
|
{
|
||||||
extraLib,
|
|
||||||
inputs,
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}: {
|
}: let
|
||||||
|
inherit
|
||||||
|
(lib)
|
||||||
|
all
|
||||||
|
any
|
||||||
|
assertMsg
|
||||||
|
attrNames
|
||||||
|
attrValues
|
||||||
|
concatLists
|
||||||
|
concatMap
|
||||||
|
concatMapStrings
|
||||||
|
concatStringsSep
|
||||||
|
elem
|
||||||
|
escapeShellArg
|
||||||
|
filter
|
||||||
|
flatten
|
||||||
|
flip
|
||||||
|
foldAttrs
|
||||||
|
foldl'
|
||||||
|
genAttrs
|
||||||
|
genList
|
||||||
|
hasInfix
|
||||||
|
head
|
||||||
|
isAttrs
|
||||||
|
mapAttrs'
|
||||||
|
mergeAttrs
|
||||||
|
min
|
||||||
|
mkMerge
|
||||||
|
mkOptionType
|
||||||
|
nameValuePair
|
||||||
|
optionalAttrs
|
||||||
|
partition
|
||||||
|
range
|
||||||
|
recursiveUpdate
|
||||||
|
removeSuffix
|
||||||
|
reverseList
|
||||||
|
showOption
|
||||||
|
splitString
|
||||||
|
stringToCharacters
|
||||||
|
substring
|
||||||
|
types
|
||||||
|
unique
|
||||||
|
warnIf
|
||||||
|
;
|
||||||
|
in {
|
||||||
# IP address math library
|
# IP address math library
|
||||||
# https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba
|
# https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba
|
||||||
# Plus some extensions by us
|
# Plus some extensions by us
|
||||||
lib = let
|
lib = let
|
||||||
libWithNet = (import "${inputs.lib-net}/net.nix" {inherit lib;}).lib;
|
libWithNet = (import "${inputs.lib-net}/net.nix" {inherit lib;}).lib;
|
||||||
in
|
in
|
||||||
lib.recursiveUpdate libWithNet {
|
recursiveUpdate libWithNet {
|
||||||
|
types = rec {
|
||||||
|
# Checks whether the value is a lazy value without causing
|
||||||
|
# it's value to be evaluated
|
||||||
|
isLazyValue = x: isAttrs x && x ? _lazyValue;
|
||||||
|
# Constructs a lazy value holding the given value.
|
||||||
|
lazyValue = value: {_lazyValue = value;};
|
||||||
|
|
||||||
|
# Represents a lazy value of the given type, which
|
||||||
|
# holds the actual value as an attrset like { _lazyValue = <actual value>; }.
|
||||||
|
# This allows the option to be defined and filtered from a defintion
|
||||||
|
# list without evaluating the value.
|
||||||
|
lazyValueOf = type:
|
||||||
|
mkOptionType rec {
|
||||||
|
name = "lazyValueOf ${type.name}";
|
||||||
|
inherit (type) description descriptionClass emptyValue getSubOptions getSubModules;
|
||||||
|
check = isLazyValue;
|
||||||
|
merge = loc: defs:
|
||||||
|
assert assertMsg
|
||||||
|
(all (x: type.check x._lazyValue) defs)
|
||||||
|
"The option `${showOption loc}` is defined with a lazy value holding an invalid type";
|
||||||
|
types.mergeOneOption loc defs;
|
||||||
|
substSubModules = m: types.uniq (type.substSubModules m);
|
||||||
|
functor = (types.defaultFunctor name) // {wrapped = type;};
|
||||||
|
nestedTypes.elemType = type;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Represents a value or lazy value of the given type that will
|
||||||
|
# automatically be coerced to the given type when merged.
|
||||||
|
lazyOf = type: types.coercedTo (lazyValueOf type) (x: x._lazyValue) type;
|
||||||
|
};
|
||||||
|
misc = rec {
|
||||||
|
# Counts how often each element occurrs in xs
|
||||||
|
countOccurrences = let
|
||||||
|
addOrUpdate = acc: x:
|
||||||
|
acc // {${x} = (acc.${x} or 0) + 1;};
|
||||||
|
in
|
||||||
|
foldl' addOrUpdate {};
|
||||||
|
|
||||||
|
# Returns all elements in xs that occur at least twice
|
||||||
|
duplicates = xs: let
|
||||||
|
occurrences = countOccurrences xs;
|
||||||
|
in
|
||||||
|
unique (filter (x: occurrences.${x} > 1) xs);
|
||||||
|
|
||||||
|
# Concatenates all given attrsets as if calling a // b in order.
|
||||||
|
concatAttrs = foldl' mergeAttrs {};
|
||||||
|
|
||||||
|
# True if the path or string starts with /
|
||||||
|
isAbsolutePath = x: substring 0 1 x == "/";
|
||||||
|
|
||||||
|
# Merges all given attributes from the given attrsets using mkMerge.
|
||||||
|
# Useful to merge several top-level configs in a module.
|
||||||
|
mergeToplevelConfigs = keys: attrs:
|
||||||
|
genAttrs keys (attr: mkMerge (map (x: x.${attr} or {}) attrs));
|
||||||
|
|
||||||
|
# Calculates base^exp, but careful, this overflows for results > 2^62
|
||||||
|
pow = base: exp: foldl' (a: x: x * a) 1 (genList (_: base) exp);
|
||||||
|
|
||||||
|
# Converts the given hex string to an integer. Only reliable for inputs in [0, 2^63),
|
||||||
|
# after that the sign bit will overflow.
|
||||||
|
hexToDec = v: let
|
||||||
|
literalValues = {
|
||||||
|
"0" = 0;
|
||||||
|
"1" = 1;
|
||||||
|
"2" = 2;
|
||||||
|
"3" = 3;
|
||||||
|
"4" = 4;
|
||||||
|
"5" = 5;
|
||||||
|
"6" = 6;
|
||||||
|
"7" = 7;
|
||||||
|
"8" = 8;
|
||||||
|
"9" = 9;
|
||||||
|
"a" = 10;
|
||||||
|
"b" = 11;
|
||||||
|
"c" = 12;
|
||||||
|
"d" = 13;
|
||||||
|
"e" = 14;
|
||||||
|
"f" = 15;
|
||||||
|
"A" = 10;
|
||||||
|
"B" = 11;
|
||||||
|
"C" = 12;
|
||||||
|
"D" = 13;
|
||||||
|
"E" = 14;
|
||||||
|
"F" = 15;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
foldl' (acc: x: acc * 16 + literalValues.${x}) 0 (stringToCharacters v);
|
||||||
|
};
|
||||||
|
disko = {
|
||||||
|
gpt = {
|
||||||
|
partGrub = name: start: end: {
|
||||||
|
inherit name start end;
|
||||||
|
part-type = "primary";
|
||||||
|
flags = ["bios_grub"];
|
||||||
|
};
|
||||||
|
partEfi = name: start: end: {
|
||||||
|
inherit name start end;
|
||||||
|
fs-type = "fat32";
|
||||||
|
bootable = true;
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
partSwap = name: start: end: {
|
||||||
|
inherit name start end;
|
||||||
|
fs-type = "linux-swap";
|
||||||
|
content = {
|
||||||
|
type = "swap";
|
||||||
|
randomEncryption = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
partLuksZfs = name: start: end: {
|
||||||
|
inherit start end;
|
||||||
|
name = "enc-${name}";
|
||||||
|
content = {
|
||||||
|
type = "luks";
|
||||||
|
name = "enc-${name}";
|
||||||
|
extraOpenArgs = ["--allow-discards"];
|
||||||
|
content = {
|
||||||
|
type = "zfs";
|
||||||
|
pool = name;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
zfs = rec {
|
||||||
|
defaultZpoolOptions = {
|
||||||
|
type = "zpool";
|
||||||
|
mountRoot = "/mnt";
|
||||||
|
rootFsOptions = {
|
||||||
|
compression = "zstd";
|
||||||
|
acltype = "posix";
|
||||||
|
atime = "off";
|
||||||
|
xattr = "sa";
|
||||||
|
dnodesize = "auto";
|
||||||
|
mountpoint = "none";
|
||||||
|
canmount = "off";
|
||||||
|
devices = "off";
|
||||||
|
};
|
||||||
|
options.ashift = "12";
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultZfsDatasets = {
|
||||||
|
"local" = unmountable;
|
||||||
|
"local/root" =
|
||||||
|
filesystem "/"
|
||||||
|
// {
|
||||||
|
postCreateHook = "zfs snapshot rpool/local/root@blank";
|
||||||
|
};
|
||||||
|
"local/nix" = filesystem "/nix";
|
||||||
|
"local/state" = filesystem "/state";
|
||||||
|
"safe" = unmountable;
|
||||||
|
"safe/persist" = filesystem "/persist";
|
||||||
|
};
|
||||||
|
|
||||||
|
unmountable = {type = "zfs_fs";};
|
||||||
|
filesystem = mountpoint: {
|
||||||
|
type = "zfs_fs";
|
||||||
|
options = {
|
||||||
|
canmount = "noauto";
|
||||||
|
inherit mountpoint;
|
||||||
|
};
|
||||||
|
# Required to add dependencies for initrd
|
||||||
|
inherit mountpoint;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
secrets = let
|
||||||
|
rageMasterIdentityArgs = concatMapStrings (x: "-i ${escapeShellArg x} ") inputs.self.secretsConfig.masterIdentities;
|
||||||
|
rageExtraEncryptionPubkeys =
|
||||||
|
concatMapStrings (
|
||||||
|
x:
|
||||||
|
if misc.isAbsolutePath x
|
||||||
|
then "-R ${escapeShellArg x} "
|
||||||
|
else "-r ${escapeShellArg x} "
|
||||||
|
)
|
||||||
|
inputs.self.secretsConfig.extraEncryptionPubkeys;
|
||||||
|
in {
|
||||||
|
# TODO replace these by lib.agenix-rekey
|
||||||
|
# The arguments required to de-/encrypt a secret in this repository
|
||||||
|
rageDecryptArgs = "${rageMasterIdentityArgs}";
|
||||||
|
rageEncryptArgs = "${rageMasterIdentityArgs} ${rageExtraEncryptionPubkeys}";
|
||||||
|
};
|
||||||
net = {
|
net = {
|
||||||
cidr = rec {
|
cidr = rec {
|
||||||
# host :: (ip | mac | integer) -> cidr -> ip
|
# host :: (ip | mac | integer) -> cidr -> ip
|
||||||
|
@ -33,7 +261,7 @@
|
||||||
host = i: n: let
|
host = i: n: let
|
||||||
cap = libWithNet.net.cidr.capacity n;
|
cap = libWithNet.net.cidr.capacity n;
|
||||||
in
|
in
|
||||||
assert lib.assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}";
|
assert assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}";
|
||||||
libWithNet.net.cidr.host i n;
|
libWithNet.net.cidr.host i n;
|
||||||
# hostCidr :: (ip | mac | integer) -> cidr -> cidr
|
# hostCidr :: (ip | mac | integer) -> cidr -> cidr
|
||||||
#
|
#
|
||||||
|
@ -55,7 +283,7 @@
|
||||||
# "192.168.1.100"
|
# "192.168.1.100"
|
||||||
# > net.cidr.ip "192.168.1.100"
|
# > net.cidr.ip "192.168.1.100"
|
||||||
# "192.168.1.100"
|
# "192.168.1.100"
|
||||||
ip = x: lib.head (lib.splitString "/" x);
|
ip = x: head (splitString "/" x);
|
||||||
# canonicalize :: cidr -> cidr
|
# canonicalize :: cidr -> cidr
|
||||||
#
|
#
|
||||||
# Replaces the ip of the cidr with the canonical network address
|
# Replaces the ip of the cidr with the canonical network address
|
||||||
|
@ -78,32 +306,31 @@
|
||||||
mergev4 = addrs_: let
|
mergev4 = addrs_: let
|
||||||
# Append /32 if necessary
|
# Append /32 if necessary
|
||||||
addrs = map (x:
|
addrs = map (x:
|
||||||
if lib.hasInfix "/" x
|
if hasInfix "/" x
|
||||||
then x
|
then x
|
||||||
else "${x}/32")
|
else "${x}/32")
|
||||||
addrs_;
|
addrs_;
|
||||||
# The smallest occurring length is the first we need to start checking, since
|
# The smallest occurring length is the first we need to start checking, since
|
||||||
# any greater cidr length represents a smaller address range which
|
# any greater cidr length represents a smaller address range which
|
||||||
# wouldn't contain all of the original addresses.
|
# wouldn't contain all of the original addresses.
|
||||||
startLength = lib.foldl' lib.min 32 (map libWithNet.net.cidr.length addrs);
|
startLength = foldl' min 32 (map libWithNet.net.cidr.length addrs);
|
||||||
possibleLengths = lib.reverseList (lib.range 0 startLength);
|
possibleLengths = reverseList (range 0 startLength);
|
||||||
# The first ip address will be "expanded" in cidr length until it covers all other
|
# The first ip address will be "expanded" in cidr length until it covers all other
|
||||||
# used addresses.
|
# used addresses.
|
||||||
firstIp = ip (lib.head addrs);
|
firstIp = ip (head addrs);
|
||||||
# Return the first (i.e. greatest length -> smallest prefix) cidr length
|
# Return the first (i.e. greatest length -> smallest prefix) cidr length
|
||||||
# in the list that covers all used addresses
|
# in the list that covers all used addresses
|
||||||
bestLength = lib.head (lib.filter
|
bestLength = head (filter
|
||||||
# All given addresses must be contained by the generated address.
|
# All given addresses must be contained by the generated address.
|
||||||
(len:
|
(len:
|
||||||
lib.all
|
all (x:
|
||||||
(x:
|
|
||||||
libWithNet.net.cidr.contains
|
libWithNet.net.cidr.contains
|
||||||
(ip x)
|
(ip x)
|
||||||
(libWithNet.net.cidr.make len firstIp))
|
(libWithNet.net.cidr.make len firstIp))
|
||||||
addrs)
|
addrs)
|
||||||
possibleLengths);
|
possibleLengths);
|
||||||
in
|
in
|
||||||
assert lib.assertMsg (!lib.any (lib.hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses";
|
assert assertMsg (!any (hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses";
|
||||||
if addrs == []
|
if addrs == []
|
||||||
then null
|
then null
|
||||||
else libWithNet.net.cidr.make bestLength firstIp;
|
else libWithNet.net.cidr.make bestLength firstIp;
|
||||||
|
@ -119,32 +346,31 @@
|
||||||
mergev6 = addrs_: let
|
mergev6 = addrs_: let
|
||||||
# Append /128 if necessary
|
# Append /128 if necessary
|
||||||
addrs = map (x:
|
addrs = map (x:
|
||||||
if lib.hasInfix "/" x
|
if hasInfix "/" x
|
||||||
then x
|
then x
|
||||||
else "${x}/128")
|
else "${x}/128")
|
||||||
addrs_;
|
addrs_;
|
||||||
# The smallest occurring length is the first we need to start checking, since
|
# The smallest occurring length is the first we need to start checking, since
|
||||||
# any greater cidr length represents a smaller address range which
|
# any greater cidr length represents a smaller address range which
|
||||||
# wouldn't contain all of the original addresses.
|
# wouldn't contain all of the original addresses.
|
||||||
startLength = lib.foldl' lib.min 128 (map libWithNet.net.cidr.length addrs);
|
startLength = foldl' min 128 (map libWithNet.net.cidr.length addrs);
|
||||||
possibleLengths = lib.reverseList (lib.range 0 startLength);
|
possibleLengths = reverseList (range 0 startLength);
|
||||||
# The first ip address will be "expanded" in cidr length until it covers all other
|
# The first ip address will be "expanded" in cidr length until it covers all other
|
||||||
# used addresses.
|
# used addresses.
|
||||||
firstIp = ip (lib.head addrs);
|
firstIp = ip (head addrs);
|
||||||
# Return the first (i.e. greatest length -> smallest prefix) cidr length
|
# Return the first (i.e. greatest length -> smallest prefix) cidr length
|
||||||
# in the list that covers all used addresses
|
# in the list that covers all used addresses
|
||||||
bestLength = lib.head (lib.filter
|
bestLength = head (filter
|
||||||
# All given addresses must be contained by the generated address.
|
# All given addresses must be contained by the generated address.
|
||||||
(len:
|
(len:
|
||||||
lib.all
|
all (x:
|
||||||
(x:
|
|
||||||
libWithNet.net.cidr.contains
|
libWithNet.net.cidr.contains
|
||||||
(ip x)
|
(ip x)
|
||||||
(libWithNet.net.cidr.make len firstIp))
|
(libWithNet.net.cidr.make len firstIp))
|
||||||
addrs)
|
addrs)
|
||||||
possibleLengths);
|
possibleLengths);
|
||||||
in
|
in
|
||||||
assert lib.assertMsg (lib.all (lib.hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses";
|
assert assertMsg (all (hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses";
|
||||||
if addrs == []
|
if addrs == []
|
||||||
then null
|
then null
|
||||||
else libWithNet.net.cidr.make bestLength firstIp;
|
else libWithNet.net.cidr.make bestLength firstIp;
|
||||||
|
@ -154,7 +380,7 @@
|
||||||
# but yields two separate result for all given ipv4 and ipv6 addresses.
|
# but yields two separate result for all given ipv4 and ipv6 addresses.
|
||||||
# Equivalent to calling mergev4 and mergev6 on a partition individually.
|
# Equivalent to calling mergev4 and mergev6 on a partition individually.
|
||||||
merge = addrs: let
|
merge = addrs: let
|
||||||
v4_and_v6 = lib.partition (lib.hasInfix ":") addrs;
|
v4_and_v6 = partition (hasInfix ":") addrs;
|
||||||
in {
|
in {
|
||||||
cidrv4 = mergev4 v4_and_v6.wrong;
|
cidrv4 = mergev4 v4_and_v6.wrong;
|
||||||
cidrv6 = mergev6 v4_and_v6.right;
|
cidrv6 = mergev6 v4_and_v6.right;
|
||||||
|
@ -186,9 +412,9 @@
|
||||||
# The network and broadcast address should never be used, and we
|
# The network and broadcast address should never be used, and we
|
||||||
# want to reserve the host address for the host. We also convert
|
# want to reserve the host address for the host. We also convert
|
||||||
# any ips to offsets here.
|
# any ips to offsets here.
|
||||||
init = lib.unique (
|
init = unique (
|
||||||
[0 (capacity - 1)]
|
[0 (capacity - 1)]
|
||||||
++ lib.flip map reserved (x:
|
++ flip map reserved (x:
|
||||||
if builtins.typeOf x == "int"
|
if builtins.typeOf x == "int"
|
||||||
then x
|
then x
|
||||||
else -(libWithNet.net.ip.diff baseAddr x))
|
else -(libWithNet.net.ip.diff baseAddr x))
|
||||||
|
@ -197,17 +423,17 @@
|
||||||
nInit = builtins.length init;
|
nInit = builtins.length init;
|
||||||
# Pre-sort all hosts, to ensure ordering invariance
|
# Pre-sort all hosts, to ensure ordering invariance
|
||||||
sortedHosts =
|
sortedHosts =
|
||||||
lib.warnIf
|
warnIf
|
||||||
((nInit + nHosts) > 0.3 * capacity)
|
((nInit + nHosts) > 0.3 * capacity)
|
||||||
"assignIps: hash stability may be degraded since utilization is >30%"
|
"assignIps: hash stability may be degraded since utilization is >30%"
|
||||||
(builtins.sort builtins.lessThan hosts);
|
(builtins.sort builtins.lessThan hosts);
|
||||||
# Generates a hash (i.e. offset value) for a given hostname
|
# Generates a hash (i.e. offset value) for a given hostname
|
||||||
hashElem = x:
|
hashElem = x:
|
||||||
builtins.bitAnd (capacity - 1)
|
builtins.bitAnd (capacity - 1)
|
||||||
(extraLib.hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x)));
|
(misc.hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x)));
|
||||||
# Do linear probing. Returns the first unused value at or after the given value.
|
# Do linear probing. Returns the first unused value at or after the given value.
|
||||||
probe = avoid: value:
|
probe = avoid: value:
|
||||||
if lib.elem value avoid
|
if elem value avoid
|
||||||
# TODO lib.mod
|
# TODO lib.mod
|
||||||
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
|
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
|
||||||
# class of x modulo 2^n, so we can use bitAnd instead.
|
# class of x modulo 2^n, so we can use bitAnd instead.
|
||||||
|
@ -228,12 +454,12 @@
|
||||||
used = [value] ++ used;
|
used = [value] ++ used;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
assert lib.assertMsg (cidrSize >= 2 && cidrSize <= 62)
|
assert assertMsg (cidrSize >= 2 && cidrSize <= 62)
|
||||||
"assignIps: cidrSize=${toString cidrSize} is not in [2, 62].";
|
"assignIps: cidrSize=${toString cidrSize} is not in [2, 62].";
|
||||||
assert lib.assertMsg (nHosts <= capacity - nInit)
|
assert assertMsg (nHosts <= capacity - nInit)
|
||||||
"assignIps: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})";
|
"assignIps: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})";
|
||||||
# Assign an ip in the subnet to each element, in order
|
# Assign an ip in the subnet to each element, in order
|
||||||
(lib.foldl' assignOne {
|
(foldl' assignOne {
|
||||||
assigned = {};
|
assigned = {};
|
||||||
used = init;
|
used = init;
|
||||||
}
|
}
|
||||||
|
@ -244,15 +470,15 @@
|
||||||
# Checks whether the given address (with or without cidr notation) is an ipv4 address.
|
# Checks whether the given address (with or without cidr notation) is an ipv4 address.
|
||||||
isv4 = x: !isv6 x;
|
isv4 = x: !isv6 x;
|
||||||
# Checks whether the given address (with or without cidr notation) is an ipv6 address.
|
# Checks whether the given address (with or without cidr notation) is an ipv6 address.
|
||||||
isv6 = lib.hasInfix ":";
|
isv6 = hasInfix ":";
|
||||||
};
|
};
|
||||||
mac = {
|
mac = {
|
||||||
# Adds offset to the given base address and ensures the result is in
|
# Adds offset to the given base address and ensures the result is in
|
||||||
# a locally administered range by replacing the second nibble with a 2.
|
# a locally administered range by replacing the second nibble with a 2.
|
||||||
addPrivate = base: offset: let
|
addPrivate = base: offset: let
|
||||||
added = libWithNet.net.mac.add base offset;
|
added = libWithNet.net.mac.add base offset;
|
||||||
pre = lib.substring 0 1 added;
|
pre = substring 0 1 added;
|
||||||
suf = lib.substring 2 (-1) added;
|
suf = substring 2 (-1) added;
|
||||||
in "${pre}2${suf}";
|
in "${pre}2${suf}";
|
||||||
# assignMacs :: mac (base) -> int (size) -> [int | mac] (reserved) -> [string] (hosts) -> [mac]
|
# assignMacs :: mac (base) -> int (size) -> [int | mac] (reserved) -> [string] (hosts) -> [mac]
|
||||||
#
|
#
|
||||||
|
@ -272,10 +498,10 @@
|
||||||
# > net.mac.assignMacs "11:22:33:00:00:00" 24 ["11:22:33:1b:bd:ca"] ["a" "b" "c"]
|
# > net.mac.assignMacs "11:22:33:00:00:00" 24 ["11:22:33:1b:bd:ca"] ["a" "b" "c"]
|
||||||
# { a = "11:22:33:1b:bd:cb"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; }
|
# { a = "11:22:33:1b:bd:cb"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; }
|
||||||
assignMacs = base: size: reserved: hosts: let
|
assignMacs = base: size: reserved: hosts: let
|
||||||
capacity = extraLib.pow 2 size;
|
capacity = misc.pow 2 size;
|
||||||
baseAsInt = libWithNet.net.mac.diff base "00:00:00:00:00:00";
|
baseAsInt = libWithNet.net.mac.diff base "00:00:00:00:00:00";
|
||||||
init = lib.unique (
|
init = unique (
|
||||||
lib.flip map reserved (x:
|
flip map reserved (x:
|
||||||
if builtins.typeOf x == "int"
|
if builtins.typeOf x == "int"
|
||||||
then x
|
then x
|
||||||
else libWithNet.net.mac.diff x base)
|
else libWithNet.net.mac.diff x base)
|
||||||
|
@ -284,17 +510,17 @@
|
||||||
nInit = builtins.length init;
|
nInit = builtins.length init;
|
||||||
# Pre-sort all hosts, to ensure ordering invariance
|
# Pre-sort all hosts, to ensure ordering invariance
|
||||||
sortedHosts =
|
sortedHosts =
|
||||||
lib.warnIf
|
warnIf
|
||||||
((nInit + nHosts) > 0.3 * capacity)
|
((nInit + nHosts) > 0.3 * capacity)
|
||||||
"assignMacs: hash stability may be degraded since utilization is >30%"
|
"assignMacs: hash stability may be degraded since utilization is >30%"
|
||||||
(builtins.sort builtins.lessThan hosts);
|
(builtins.sort builtins.lessThan hosts);
|
||||||
# Generates a hash (i.e. offset value) for a given hostname
|
# Generates a hash (i.e. offset value) for a given hostname
|
||||||
hashElem = x:
|
hashElem = x:
|
||||||
builtins.bitAnd (capacity - 1)
|
builtins.bitAnd (capacity - 1)
|
||||||
(extraLib.hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x)));
|
(misc.hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x)));
|
||||||
# Do linear probing. Returns the first unused value at or after the given value.
|
# Do linear probing. Returns the first unused value at or after the given value.
|
||||||
probe = avoid: value:
|
probe = avoid: value:
|
||||||
if lib.elem value avoid
|
if elem value avoid
|
||||||
# TODO lib.mod
|
# TODO lib.mod
|
||||||
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
|
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
|
||||||
# class of x modulo 2^n, so we can use bitAnd instead.
|
# class of x modulo 2^n, so we can use bitAnd instead.
|
||||||
|
@ -315,14 +541,14 @@
|
||||||
used = [value] ++ used;
|
used = [value] ++ used;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
assert lib.assertMsg (size >= 2 && size <= 62)
|
assert assertMsg (size >= 2 && size <= 62)
|
||||||
"assignMacs: size=${toString size} is not in [2, 62].";
|
"assignMacs: size=${toString size} is not in [2, 62].";
|
||||||
assert lib.assertMsg (builtins.bitAnd (capacity - 1) baseAsInt == 0)
|
assert assertMsg (builtins.bitAnd (capacity - 1) baseAsInt == 0)
|
||||||
"assignMacs: the size=${toString size} least significant bits of the base mac address must be 0.";
|
"assignMacs: the size=${toString size} least significant bits of the base mac address must be 0.";
|
||||||
assert lib.assertMsg (nHosts <= capacity - nInit)
|
assert assertMsg (nHosts <= capacity - nInit)
|
||||||
"assignMacs: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})";
|
"assignMacs: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})";
|
||||||
# Assign an ip in the subnet to each element, in order
|
# Assign an ip in the subnet to each element, in order
|
||||||
(lib.foldl' assignOne {
|
(foldl' assignOne {
|
||||||
assigned = {};
|
assigned = {};
|
||||||
used = init;
|
used = init;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
nodeName,
|
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
systemd.network.enable = true;
|
systemd.network.enable = true;
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
hostName = nodeName;
|
|
||||||
useDHCP = lib.mkForce false;
|
useDHCP = lib.mkForce false;
|
||||||
useNetworkd = true;
|
useNetworkd = true;
|
||||||
dhcpcd.enable = false;
|
dhcpcd.enable = false;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
inputs,
|
inputs,
|
||||||
pkgs,
|
pkgs,
|
||||||
stateVersion,
|
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
environment.etc."nixos/configuration.nix".source = pkgs.writeText "configuration.nix" ''
|
environment.etc."nixos/configuration.nix".source = pkgs.writeText "configuration.nix" ''
|
||||||
|
@ -53,6 +52,6 @@
|
||||||
extraSystemBuilderCmds = ''
|
extraSystemBuilderCmds = ''
|
||||||
ln -sv ${pkgs.path} $out/nixpkgs
|
ln -sv ${pkgs.path} $out/nixpkgs
|
||||||
'';
|
'';
|
||||||
inherit stateVersion;
|
stateVersion = "23.11";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
{
|
{
|
||||||
|
config,
|
||||||
inputs,
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
nodePath,
|
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
# Define local repo secrets
|
# Define local repo secrets
|
||||||
repo.secretFiles = let
|
repo.secretFiles = let
|
||||||
local = nodePath + "/secrets/local.nix.age";
|
local = config.node.secretsDir + "/local.nix.age";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
global = ../../secrets/global.nix.age;
|
global = ../../secrets/global.nix.age;
|
||||||
}
|
}
|
||||||
// lib.optionalAttrs (nodePath != null && lib.pathExists local) {inherit local;};
|
// lib.optionalAttrs (lib.pathExists local) {inherit local;};
|
||||||
|
|
||||||
# Setup secret rekeying parameters
|
# Setup secret rekeying parameters
|
||||||
age.rekey = {
|
age.rekey = {
|
||||||
|
@ -24,13 +24,7 @@
|
||||||
# This is technically impure, but intended. We need to rekey on the
|
# This is technically impure, but intended. We need to rekey on the
|
||||||
# current system due to yubikey availability.
|
# current system due to yubikey availability.
|
||||||
forceRekeyOnSystem = builtins.extraBuiltins.unsafeCurrentSystem;
|
forceRekeyOnSystem = builtins.extraBuiltins.unsafeCurrentSystem;
|
||||||
hostPubkey = let
|
hostPubkey = config.node.secretsDir + "/host.pub";
|
||||||
pubkeyPath =
|
|
||||||
if nodePath == null
|
|
||||||
then null
|
|
||||||
else nodePath + "/secrets/host.pub";
|
|
||||||
in
|
|
||||||
lib.mkIf (pubkeyPath != null && lib.pathExists pubkeyPath) pubkeyPath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
age.generators.dhparams.script = {pkgs, ...}: "${pkgs.openssl}/bin/openssl dhparam 4096";
|
age.generators.dhparams.script = {pkgs, ...}: "${pkgs.openssl}/bin/openssl dhparam 4096";
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
extraLib,
|
|
||||||
inputs,
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
microvm,
|
|
||||||
nodeName,
|
|
||||||
nodePath,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
utils,
|
utils,
|
||||||
...
|
...
|
||||||
|
@ -36,7 +32,8 @@
|
||||||
|
|
||||||
parentConfig = config;
|
parentConfig = config;
|
||||||
cfg = config.meta.microvms;
|
cfg = config.meta.microvms;
|
||||||
inherit (config.meta.microvms) vms;
|
nodeName = config.repo.node.name;
|
||||||
|
inherit (cfg) vms;
|
||||||
inherit (config.lib) net;
|
inherit (config.lib) net;
|
||||||
|
|
||||||
# Configuration for each microvm
|
# Configuration for each microvm
|
||||||
|
@ -44,7 +41,7 @@
|
||||||
# Add the required datasets to the disko configuration of the machine
|
# Add the required datasets to the disko configuration of the machine
|
||||||
disko.devices.zpool = mkIf vmCfg.zfs.enable {
|
disko.devices.zpool = mkIf vmCfg.zfs.enable {
|
||||||
${vmCfg.zfs.pool}.datasets."${vmCfg.zfs.dataset}" =
|
${vmCfg.zfs.pool}.datasets."${vmCfg.zfs.dataset}" =
|
||||||
extraLib.disko.zfs.filesystem vmCfg.zfs.mountpoint;
|
config.lib.disko.zfs.filesystem vmCfg.zfs.mountpoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Ensure that the zfs dataset exists before it is mounted.
|
# Ensure that the zfs dataset exists before it is mounted.
|
||||||
|
@ -94,8 +91,9 @@
|
||||||
nodes = mkMerge config.microvm.vms.${vmName}.config.options.nodes.definitions;
|
nodes = mkMerge config.microvm.vms.${vmName}.config.options.nodes.definitions;
|
||||||
|
|
||||||
microvm.vms.${vmName} = let
|
microvm.vms.${vmName} = let
|
||||||
node = import ../../nix/generate-node.nix inputs vmCfg.nodeName {
|
node = import ../../nix/generate-node.nix inputs {
|
||||||
inherit (vmCfg) system configPath;
|
name = vmCfg.nodeName;
|
||||||
|
inherit (vmCfg) system;
|
||||||
};
|
};
|
||||||
mac = (net.mac.assignMacs "02:01:27:00:00:00" 24 [] (attrNames vms)).${vmName};
|
mac = (net.mac.assignMacs "02:01:27:00:00:00" 24 [] (attrNames vms)).${vmName};
|
||||||
in {
|
in {
|
||||||
|
@ -217,7 +215,7 @@
|
||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
# Add the host module, but only enable if it necessary
|
# Add the host module, but only enable if it necessary
|
||||||
microvm.host
|
inputs.microvm.nixosModules.host
|
||||||
# This is opt-out, so we can't put this into the mkIf below
|
# This is opt-out, so we can't put this into the mkIf below
|
||||||
{microvm.host.enable = vms != {};}
|
{microvm.host.enable = vms != {};}
|
||||||
];
|
];
|
||||||
|
@ -289,26 +287,6 @@ in {
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
configPath = mkOption {
|
|
||||||
type = types.nullOr types.path;
|
|
||||||
default =
|
|
||||||
if nodePath != null && builtins.pathExists (nodePath + "/microvms/${name}")
|
|
||||||
then nodePath + "/microvms/${name}"
|
|
||||||
else null;
|
|
||||||
description = mdDoc ''
|
|
||||||
The main configuration directory for this microvm. If not-null, the given
|
|
||||||
directory will automatically be imported as system configuration. It will
|
|
||||||
become the nodePath for the microvm meaning that some machine-specific files
|
|
||||||
may be referenced there automatically (for example host.pub).
|
|
||||||
|
|
||||||
This can also be set to a file, which will then simply be used as the main
|
|
||||||
import for configuration, without setting a nodePath.
|
|
||||||
|
|
||||||
By default this will be set to the current node's <nodePath>/microvms/<vmname>
|
|
||||||
if the current nodePath is non-null and the directory exists.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
mainLinkName = mkOption {
|
mainLinkName = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
|
@ -378,6 +356,6 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// extraLib.mergeToplevelConfigs ["nodes" "disko" "microvm" "systemd"] (mapAttrsToList microvmConfig vms)
|
// config.lib.misc.mergeToplevelConfigs ["nodes" "disko" "microvm" "systemd"] (mapAttrsToList microvmConfig vms)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
nodePath,
|
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
|
@ -37,7 +36,7 @@ in {
|
||||||
|
|
||||||
config = mkIf config.services.nginx.enable {
|
config = mkIf config.services.nginx.enable {
|
||||||
age.secrets."dhparams.pem" = {
|
age.secrets."dhparams.pem" = {
|
||||||
rekeyFile = nodePath + "/secrets/dhparams.pem.age";
|
rekeyFile = config.node.secretsDir + "/dhparams.pem.age";
|
||||||
generator = "dhparams";
|
generator = "dhparams";
|
||||||
mode = "440";
|
mode = "440";
|
||||||
group = "nginx";
|
group = "nginx";
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
nodeName,
|
|
||||||
nodePath,
|
|
||||||
nodes,
|
nodes,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
|
@ -27,7 +25,7 @@ in {
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
age.secrets.promtail-loki-basic-auth-password = {
|
age.secrets.promtail-loki-basic-auth-password = {
|
||||||
rekeyFile = nodePath + "/secrets/promtail-loki-basic-auth-password.age";
|
rekeyFile = config.node.secretsDir + "/promtail-loki-basic-auth-password.age";
|
||||||
generator = "alnum";
|
generator = "alnum";
|
||||||
mode = "440";
|
mode = "440";
|
||||||
group = "promtail";
|
group = "promtail";
|
||||||
|
@ -48,7 +46,7 @@ in {
|
||||||
|
|
||||||
clients = [
|
clients = [
|
||||||
{
|
{
|
||||||
basic_auth.username = "${nodeName}+promtail-loki-basic-auth-password";
|
basic_auth.username = "${config.repo.node.name}+promtail-loki-basic-auth-password";
|
||||||
basic_auth.password_file = config.age.secrets.promtail-loki-basic-auth-password.path;
|
basic_auth.password_file = config.age.secrets.promtail-loki-basic-auth-password.path;
|
||||||
url = "https://${nodes.${cfg.proxy}.config.networking.providedDomains.loki}/loki/api/v1/push";
|
url = "https://${nodes.${cfg.proxy}.config.networking.providedDomains.loki}/loki/api/v1/push";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
nodeName,
|
|
||||||
nodePath,
|
|
||||||
nodes,
|
nodes,
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
|
@ -18,6 +16,7 @@
|
||||||
;
|
;
|
||||||
|
|
||||||
cfg = config.meta.telegraf;
|
cfg = config.meta.telegraf;
|
||||||
|
nodeName = config.repo.node.name;
|
||||||
in {
|
in {
|
||||||
options.meta.telegraf = {
|
options.meta.telegraf = {
|
||||||
enable = mkEnableOption (mdDoc "telegraf to push metrics to influx.");
|
enable = mkEnableOption (mdDoc "telegraf to push metrics to influx.");
|
||||||
|
@ -42,7 +41,7 @@ in {
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
age.secrets.telegraf-influxdb-token = {
|
age.secrets.telegraf-influxdb-token = {
|
||||||
rekeyFile = nodePath + "/secrets/telegraf-influxdb-token.age";
|
rekeyFile = config.node.secretsDir + "/telegraf-influxdb-token.age";
|
||||||
# TODO generator.script = { pkgs, lib, decrypt, deps, ... }: let
|
# TODO generator.script = { pkgs, lib, decrypt, deps, ... }: let
|
||||||
# TODO adminBasicAuth = (builtins.head deps).file;
|
# TODO adminBasicAuth = (builtins.head deps).file;
|
||||||
# TODO adminToken = (builtins.head deps).file; # TODO ..... filter by name?
|
# TODO adminToken = (builtins.head deps).file; # TODO ..... filter by name?
|
||||||
|
|
|
@ -1,54 +1,221 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
extraLib,
|
nodes,
|
||||||
pkgs,
|
pkgs,
|
||||||
nodeName,
|
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
any
|
any
|
||||||
|
assertMsg
|
||||||
attrNames
|
attrNames
|
||||||
attrValues
|
attrValues
|
||||||
|
concatLists
|
||||||
concatMap
|
concatMap
|
||||||
concatMapStrings
|
concatMapStrings
|
||||||
concatStringsSep
|
concatStringsSep
|
||||||
|
escapeShellArg
|
||||||
filter
|
filter
|
||||||
filterAttrs
|
filterAttrs
|
||||||
|
flatten
|
||||||
|
flip
|
||||||
genAttrs
|
genAttrs
|
||||||
head
|
head
|
||||||
|
mapAttrs'
|
||||||
mapAttrsToList
|
mapAttrsToList
|
||||||
mdDoc
|
mdDoc
|
||||||
mergeAttrs
|
mergeAttrs
|
||||||
mkForce
|
mkForce
|
||||||
mkIf
|
mkIf
|
||||||
|
mkMerge
|
||||||
mkOption
|
mkOption
|
||||||
|
nameValuePair
|
||||||
optionalAttrs
|
optionalAttrs
|
||||||
optionals
|
optionals
|
||||||
|
partition
|
||||||
|
removeSuffix
|
||||||
stringLength
|
stringLength
|
||||||
types
|
types
|
||||||
;
|
;
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
(extraLib)
|
(config.lib.misc)
|
||||||
concatAttrs
|
concatAttrs
|
||||||
duplicates
|
duplicates
|
||||||
mergeToplevelConfigs
|
mergeToplevelConfigs
|
||||||
;
|
;
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
(extraLib.types)
|
(config.lib.types)
|
||||||
lazyOf
|
lazyOf
|
||||||
lazyValue
|
lazyValue
|
||||||
;
|
;
|
||||||
|
|
||||||
inherit (config.lib) net;
|
inherit (config.lib) net;
|
||||||
cfg = config.meta.wireguard;
|
cfg = config.meta.wireguard;
|
||||||
|
nodeName = config.repo.node.name;
|
||||||
|
|
||||||
|
libFor = wgName: rec {
|
||||||
|
# Returns the given node's wireguard configuration of this network
|
||||||
|
wgCfgOf = node: nodes.${node}.config.meta.wireguard.${wgName};
|
||||||
|
|
||||||
|
sortedPeers = peerA: peerB:
|
||||||
|
if peerA < peerB
|
||||||
|
then {
|
||||||
|
peer1 = peerA;
|
||||||
|
peer2 = peerB;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
peer1 = peerB;
|
||||||
|
peer2 = peerA;
|
||||||
|
};
|
||||||
|
|
||||||
|
peerPublicKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.pub";
|
||||||
|
peerPublicKeyPath = peerName: inputs.self.outPath + peerPublicKeyFile peerName;
|
||||||
|
|
||||||
|
peerPrivateKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.age";
|
||||||
|
peerPrivateKeyPath = peerName: inputs.self.outPath + peerPrivateKeyFile peerName;
|
||||||
|
peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}";
|
||||||
|
|
||||||
|
peerPresharedKeyFile = peerA: peerB: let
|
||||||
|
inherit (sortedPeers peerA peerB) peer1 peer2;
|
||||||
|
in "/secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age";
|
||||||
|
peerPresharedKeyPath = peerA: peerB: inputs.self.outPath + peerPresharedKeyFile peerA peerB;
|
||||||
|
peerPresharedKeySecret = peerA: peerB: let
|
||||||
|
inherit (sortedPeers peerA peerB) peer1 peer2;
|
||||||
|
in "wireguard-${wgName}-psks-${peer1}+${peer2}";
|
||||||
|
|
||||||
|
# All nodes that are part of this network
|
||||||
|
participatingNodes =
|
||||||
|
filter
|
||||||
|
(n: builtins.hasAttr wgName nodes.${n}.config.meta.wireguard)
|
||||||
|
(attrNames nodes);
|
||||||
|
|
||||||
|
# Partition nodes by whether they are servers
|
||||||
|
_participatingNodes_isServerPartition =
|
||||||
|
partition
|
||||||
|
(n: (wgCfgOf n).server.host != null)
|
||||||
|
participatingNodes;
|
||||||
|
|
||||||
|
participatingServerNodes = _participatingNodes_isServerPartition.right;
|
||||||
|
participatingClientNodes = _participatingNodes_isServerPartition.wrong;
|
||||||
|
|
||||||
|
# Maps all nodes that are part of this network to their addresses
|
||||||
|
nodePeers = genAttrs participatingNodes (n: (wgCfgOf n).addresses);
|
||||||
|
|
||||||
|
externalPeerName = p: "external-${p}";
|
||||||
|
|
||||||
|
# Only peers that are defined as externalPeers on the given node.
|
||||||
|
# Prepends "external-" to their name.
|
||||||
|
externalPeersForNode = node:
|
||||||
|
mapAttrs' (p: nameValuePair (externalPeerName p)) (wgCfgOf node).server.externalPeers;
|
||||||
|
|
||||||
|
# All peers that are defined as externalPeers on any node.
|
||||||
|
# Prepends "external-" to their name.
|
||||||
|
allExternalPeers = concatAttrs (map externalPeersForNode participatingNodes);
|
||||||
|
|
||||||
|
# All peers that are part of this network
|
||||||
|
allPeers = nodePeers // allExternalPeers;
|
||||||
|
|
||||||
|
# Concatenation of all external peer names names without any transformations.
|
||||||
|
externalPeerNamesRaw = concatMap (n: attrNames (wgCfgOf n).server.externalPeers) participatingNodes;
|
||||||
|
|
||||||
|
# A list of all occurring addresses.
|
||||||
|
usedAddresses =
|
||||||
|
concatMap (n: (wgCfgOf n).addresses) participatingNodes
|
||||||
|
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
||||||
|
|
||||||
|
# A list of all occurring addresses, but only includes addresses that
|
||||||
|
# are not assigned automatically.
|
||||||
|
explicitlyUsedAddresses =
|
||||||
|
flip concatMap participatingNodes
|
||||||
|
(n:
|
||||||
|
filter (x: !types.isLazyValue x)
|
||||||
|
(concatLists
|
||||||
|
(nodes.${n}.options.meta.wireguard.type.functor.wrapped.getSubOptions (wgCfgOf n)).addresses.definitions))
|
||||||
|
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
||||||
|
|
||||||
|
# The cidrv4 and cidrv6 of the network spanned by all participating peer addresses.
|
||||||
|
# This also takes into account any reserved address ranges that should be part of the network.
|
||||||
|
networkAddresses =
|
||||||
|
net.cidr.merge (usedAddresses
|
||||||
|
++ concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
||||||
|
|
||||||
|
# The network spanning cidr addresses. The respective cidrv4 and cirdv6 are only
|
||||||
|
# included if they exist.
|
||||||
|
networkCidrs = filter (x: x != null) (attrValues networkAddresses);
|
||||||
|
|
||||||
|
# The cidrv4 and cidrv6 of the network spanned by all reserved addresses only.
|
||||||
|
# Used to determine automatically assigned addresses first.
|
||||||
|
spannedReservedNetwork =
|
||||||
|
net.cidr.merge (concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
||||||
|
|
||||||
|
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
||||||
|
# to each participant that has not explicitly specified an ipv4 address.
|
||||||
|
assignedIpv4Addresses = assert assertMsg
|
||||||
|
(spannedReservedNetwork.cidrv4 != null)
|
||||||
|
"Wireguard network '${wgName}': At least one participating node must reserve a cidrv4 address via `reservedAddresses` so that ipv4 addresses can be assigned automatically from that network.";
|
||||||
|
net.cidr.assignIps
|
||||||
|
spannedReservedNetwork.cidrv4
|
||||||
|
# Don't assign any addresses that are explicitly configured on other hosts
|
||||||
|
(filter (x: net.cidr.contains x spannedReservedNetwork.cidrv4) (filter net.ip.isv4 explicitlyUsedAddresses))
|
||||||
|
participatingNodes;
|
||||||
|
|
||||||
|
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
||||||
|
# to each participant that has not explicitly specified an ipv4 address.
|
||||||
|
assignedIpv6Addresses = assert assertMsg
|
||||||
|
(spannedReservedNetwork.cidrv6 != null)
|
||||||
|
"Wireguard network '${wgName}': At least one participating node must reserve a cidrv6 address via `reservedAddresses` so that ipv4 addresses can be assigned automatically from that network.";
|
||||||
|
net.cidr.assignIps
|
||||||
|
spannedReservedNetwork.cidrv6
|
||||||
|
# Don't assign any addresses that are explicitly configured on other hosts
|
||||||
|
(filter (x: net.cidr.contains x spannedReservedNetwork.cidrv6) (filter net.ip.isv6 explicitlyUsedAddresses))
|
||||||
|
participatingNodes;
|
||||||
|
|
||||||
|
# Appends / replaces the correct cidr length to the argument,
|
||||||
|
# so that the resulting address is in the cidr.
|
||||||
|
toNetworkAddr = addr: let
|
||||||
|
relevantNetworkAddr =
|
||||||
|
if net.ip.isv6 addr
|
||||||
|
then networkAddresses.cidrv6
|
||||||
|
else networkAddresses.cidrv4;
|
||||||
|
in "${net.cidr.ip addr}/${toString (net.cidr.length relevantNetworkAddr)}";
|
||||||
|
|
||||||
|
# Creates a script that when executed outputs a wg-quick compatible configuration
|
||||||
|
# file for use with external peers. This is a script so we can access secrets without
|
||||||
|
# storing them in the nix-store.
|
||||||
|
wgQuickConfigScript = system: serverNode: extPeer: let
|
||||||
|
pkgs = inputs.self.pkgs.${system};
|
||||||
|
snCfg = wgCfgOf serverNode;
|
||||||
|
peerName = externalPeerName extPeer;
|
||||||
|
addresses = map toNetworkAddr snCfg.server.externalPeers.${extPeer};
|
||||||
|
in
|
||||||
|
pkgs.writeShellScript "create-wg-conf-${wgName}-${serverNode}-${extPeer}" ''
|
||||||
|
privKey=$(${pkgs.rage}/bin/rage -d ${config.lib.secrets.rageDecryptArgs} ${escapeShellArg (peerPrivateKeyPath peerName)}) \
|
||||||
|
|| { echo "[1;31merror:[m Failed to decrypt!" >&2; exit 1; }
|
||||||
|
serverPsk=$(${pkgs.rage}/bin/rage -d ${config.lib.secrets.rageDecryptArgs} ${escapeShellArg (peerPresharedKeyPath serverNode peerName)}) \
|
||||||
|
|| { echo "[1;31merror:[m Failed to decrypt!" >&2; exit 1; }
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
[Interface]
|
||||||
|
Address = ${concatStringsSep ", " addresses}
|
||||||
|
PrivateKey = $privKey
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = ${removeSuffix "\n" (builtins.readFile (peerPublicKeyPath serverNode))}
|
||||||
|
PresharedKey = $serverPsk
|
||||||
|
AllowedIPs = ${concatStringsSep ", " networkCidrs}
|
||||||
|
Endpoint = ${snCfg.server.host}:${toString snCfg.server.port}
|
||||||
|
PersistentKeepalive = 25
|
||||||
|
EOF
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
configForNetwork = wgName: wgCfg: let
|
configForNetwork = wgName: wgCfg: let
|
||||||
inherit
|
inherit
|
||||||
(extraLib.wireguard wgName)
|
(libFor wgName)
|
||||||
externalPeerName
|
externalPeerName
|
||||||
externalPeerNamesRaw
|
externalPeerNamesRaw
|
||||||
networkCidrs
|
networkCidrs
|
||||||
|
@ -365,7 +532,7 @@ in {
|
||||||
|
|
||||||
ipv4 = mkOption {
|
ipv4 = mkOption {
|
||||||
type = lazyOf net.types.ipv4;
|
type = lazyOf net.types.ipv4;
|
||||||
default = lazyValue (extraLib.wireguard name).assignedIpv4Addresses.${nodeName};
|
default = lazyValue (libFor name).assignedIpv4Addresses.${nodeName};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
The ipv4 address for this machine. If you do not set this explicitly,
|
The ipv4 address for this machine. If you do not set this explicitly,
|
||||||
a semi-stable ipv4 address will be derived automatically based on the
|
a semi-stable ipv4 address will be derived automatically based on the
|
||||||
|
@ -377,7 +544,7 @@ in {
|
||||||
|
|
||||||
ipv6 = mkOption {
|
ipv6 = mkOption {
|
||||||
type = lazyOf net.types.ipv6;
|
type = lazyOf net.types.ipv6;
|
||||||
default = lazyValue (extraLib.wireguard name).assignedIpv6Addresses.${nodeName};
|
default = lazyValue (libFor name).assignedIpv6Addresses.${nodeName};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
The ipv6 address for this machine. If you do not set this explicitly,
|
The ipv6 address for this machine. If you do not set this explicitly,
|
||||||
a semi-stable ipv6 address will be derived automatically based on the
|
a semi-stable ipv6 address will be derived automatically based on the
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Provides an option to easily rename interfaces by their mac addresses.
|
# Provides an option to easily rename interfaces by their mac addresses.
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
extraLib,
|
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
|
@ -35,7 +34,7 @@ in {
|
||||||
|
|
||||||
config = lib.mkIf (cfg != {}) {
|
config = lib.mkIf (cfg != {}) {
|
||||||
assertions = let
|
assertions = let
|
||||||
duplicateMacs = extraLib.duplicates (attrValues cfg);
|
duplicateMacs = config.lib.misc.duplicates (attrValues cfg);
|
||||||
in [
|
in [
|
||||||
{
|
{
|
||||||
assertion = duplicateMacs == [];
|
assertion = duplicateMacs == [];
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
{
|
{inputs, ...}: {
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
nixos-hardware,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
imports = [
|
imports = [
|
||||||
nixos-hardware.common-pc-ssd
|
inputs.nixos-hardware.nixosModules.common-pc-ssd
|
||||||
./physical.nix
|
./physical.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
nodePath,
|
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
age.secrets.initrd_host_ed25519_key = {
|
age.secrets.initrd_host_ed25519_key = {
|
||||||
rekeyFile = nodePath + "/secrets/initrd_host_ed25519_key.age";
|
rekeyFile = config.node.secretsDir + "/initrd_host_ed25519_key.age";
|
||||||
# Generate only an ssh-ed25519 private key
|
# Generate only an ssh-ed25519 private key
|
||||||
generator.script = {
|
generator.script = {
|
||||||
pkgs,
|
pkgs,
|
||||||
|
|
|
@ -27,4 +27,19 @@
|
||||||
services.telegraf.extraConfig.inputs = lib.mkIf config.services.telegraf.enable {
|
services.telegraf.extraConfig.inputs = lib.mkIf config.services.telegraf.enable {
|
||||||
zfs.poolMetrics = true;
|
zfs.poolMetrics = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# TODO remove once this is upstreamed
|
||||||
|
boot.initrd.systemd.services."zfs-import-rpool".after = ["cryptsetup.target"];
|
||||||
|
|
||||||
|
# After importing the rpool, rollback the root system to be empty.
|
||||||
|
boot.initrd.systemd.services.impermanence-root = {
|
||||||
|
wantedBy = ["initrd.target"];
|
||||||
|
after = ["zfs-import-rpool.service"];
|
||||||
|
before = ["sysroot.mount"];
|
||||||
|
unitConfig.DefaultDependencies = "no";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.zfs}/bin/zfs rollback -r rpool/local/root@blank";
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
extraLib,
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
nodeName,
|
|
||||||
colmenaNodes,
|
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
|
@ -18,10 +16,7 @@
|
||||||
types
|
types
|
||||||
;
|
;
|
||||||
|
|
||||||
inherit
|
nodeName = config.repo.node.name;
|
||||||
(extraLib)
|
|
||||||
mergeToplevelConfigs
|
|
||||||
;
|
|
||||||
in {
|
in {
|
||||||
options.nodes = mkOption {
|
options.nodes = mkOption {
|
||||||
type = types.attrsOf (mkOptionType {
|
type = types.attrsOf (mkOptionType {
|
||||||
|
@ -33,12 +28,12 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
config = let
|
config = let
|
||||||
allNodes = attrNames colmenaNodes;
|
allNodes = attrNames inputs.self.colmenaNodes;
|
||||||
isColmenaNode = elem nodeName allNodes;
|
isColmenaNode = elem nodeName allNodes;
|
||||||
foreignConfigs = concatMap (n: colmenaNodes.${n}.config.nodes.${nodeName} or []) allNodes;
|
foreignConfigs = concatMap (n: inputs.self.colmenaNodes.${n}.config.nodes.${nodeName} or []) allNodes;
|
||||||
toplevelAttrs = ["age" "networking" "systemd" "services"];
|
toplevelAttrs = ["age" "networking" "systemd" "services"];
|
||||||
in
|
in
|
||||||
optionalAttrs isColmenaNode (mergeToplevelConfigs toplevelAttrs (
|
optionalAttrs isColmenaNode (config.lib.misc.mergeToplevelConfigs toplevelAttrs (
|
||||||
foreignConfigs
|
foreignConfigs
|
||||||
# Also allow extending ourselves, in case some attributes from depenent
|
# Also allow extending ourselves, in case some attributes from depenent
|
||||||
# configurations such as containers or microvms are merged to the host
|
# configurations such as containers or microvms are merged to the host
|
||||||
|
|
|
@ -1,19 +1,31 @@
|
||||||
{}
|
{
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
config,
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
lib,
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
...
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
}: let
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
inherit
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
(lib)
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
mdDoc
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
mkDefault
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
mkOption
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
types
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
;
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
|
||||||
# TODO define special args in a more documented and readOnly accessible way
|
|
||||||
|
|
||||||
|
cfg = config.node;
|
||||||
|
in {
|
||||||
|
options.node = {
|
||||||
|
name = mkOption {
|
||||||
|
description = mdDoc "A unique name for this node (host) in the repository. Defines the default hostname, but this can be overwritten.";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
secretsDir = mkOption {
|
||||||
|
description = mdDoc "Path to the secrets directory for this node.";
|
||||||
|
type = types.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
networking.hostName = mkDefault config.node.name;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -6,18 +6,21 @@
|
||||||
inherit
|
inherit
|
||||||
(nixpkgs.lib)
|
(nixpkgs.lib)
|
||||||
filterAttrs
|
filterAttrs
|
||||||
|
flip
|
||||||
mapAttrs
|
mapAttrs
|
||||||
;
|
;
|
||||||
|
|
||||||
nixosNodes = filterAttrs (_: x: x.type == "nixos") self.hosts;
|
nixosNodes = filterAttrs (_: x: x.type == "nixos") self.hosts;
|
||||||
nodes =
|
nodes = flip mapAttrs nixosNodes (name: hostCfg:
|
||||||
mapAttrs
|
import ./generate-node.nix inputs {
|
||||||
(n: v: import ./generate-node.nix inputs n ({configPath = ../hosts/${n};} // v))
|
inherit name;
|
||||||
nixosNodes;
|
inherit (hostCfg) system;
|
||||||
|
modules = [../hosts/${name}];
|
||||||
|
});
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
meta = {
|
meta = {
|
||||||
description = "oddlama's colmena configuration";
|
description = "❄️";
|
||||||
# Just a required dummy for colmena, overwritten on a per-node basis by nodeNixpkgs below.
|
# Just a required dummy for colmena, overwritten on a per-node basis by nodeNixpkgs below.
|
||||||
nixpkgs = self.pkgs.x86_64-linux;
|
nixpkgs = self.pkgs.x86_64-linux;
|
||||||
nodeNixpkgs = mapAttrs (_: node: node.pkgs) nodes;
|
nodeNixpkgs = mapAttrs (_: node: node.pkgs) nodes;
|
||||||
|
|
|
@ -7,35 +7,34 @@
|
||||||
home-manager,
|
home-manager,
|
||||||
impermanence,
|
impermanence,
|
||||||
microvm,
|
microvm,
|
||||||
nixos-hardware,
|
|
||||||
nixos-nftables-firewall,
|
nixos-nftables-firewall,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
...
|
...
|
||||||
} @ inputs: nodeName: {configPath ? null, ...} @ nodeMeta: let
|
} @ inputs: {
|
||||||
inherit (nixpkgs.lib) optional pathIsDirectory;
|
# The name of the generated node
|
||||||
in {
|
name,
|
||||||
inherit (nodeMeta) system;
|
# Additional modules that should be imported
|
||||||
pkgs = self.pkgs.${nodeMeta.system};
|
modules ? [],
|
||||||
|
# The system in use
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
inherit system;
|
||||||
|
pkgs = self.pkgs.${system};
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit (nixpkgs) lib;
|
inherit (nixpkgs) lib;
|
||||||
inherit (self) extraLib nodes stateVersion colmenaNodes;
|
inherit (self) nodes;
|
||||||
inherit inputs nodeName;
|
inherit inputs;
|
||||||
# Only set the nodePath if it is an actual directory
|
|
||||||
nodePath =
|
|
||||||
if builtins.isPath configPath && pathIsDirectory configPath
|
|
||||||
then configPath
|
|
||||||
else null;
|
|
||||||
nixos-hardware = nixos-hardware.nixosModules;
|
|
||||||
microvm = microvm.nixosModules;
|
|
||||||
};
|
};
|
||||||
imports =
|
imports =
|
||||||
[
|
modules
|
||||||
|
++ [
|
||||||
|
{repo.node.name = name;}
|
||||||
agenix.nixosModules.default
|
agenix.nixosModules.default
|
||||||
agenix-rekey.nixosModules.default
|
agenix-rekey.nixosModules.default
|
||||||
disko.nixosModules.disko
|
disko.nixosModules.disko
|
||||||
home-manager.nixosModules.default
|
home-manager.nixosModules.default
|
||||||
impermanence.nixosModules.impermanence
|
impermanence.nixosModules.impermanence
|
||||||
nixos-nftables-firewall.nixosModules.default
|
nixos-nftables-firewall.nixosModules.default
|
||||||
]
|
];
|
||||||
++ optional (configPath != null) configPath;
|
|
||||||
}
|
}
|
||||||
|
|
386
nix/lib.nix
386
nix/lib.nix
|
@ -1,386 +0,0 @@
|
||||||
{
|
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit
|
|
||||||
(nixpkgs.lib)
|
|
||||||
all
|
|
||||||
assertMsg
|
|
||||||
attrNames
|
|
||||||
attrValues
|
|
||||||
concatLists
|
|
||||||
concatMap
|
|
||||||
concatMapStrings
|
|
||||||
concatStringsSep
|
|
||||||
elem
|
|
||||||
escapeShellArg
|
|
||||||
filter
|
|
||||||
flatten
|
|
||||||
flip
|
|
||||||
foldAttrs
|
|
||||||
foldl'
|
|
||||||
genAttrs
|
|
||||||
genList
|
|
||||||
head
|
|
||||||
isAttrs
|
|
||||||
mapAttrs'
|
|
||||||
mergeAttrs
|
|
||||||
mkMerge
|
|
||||||
mkOptionType
|
|
||||||
nameValuePair
|
|
||||||
optionalAttrs
|
|
||||||
partition
|
|
||||||
recursiveUpdate
|
|
||||||
removeSuffix
|
|
||||||
showOption
|
|
||||||
stringToCharacters
|
|
||||||
substring
|
|
||||||
unique
|
|
||||||
;
|
|
||||||
in rec {
|
|
||||||
types = rec {
|
|
||||||
# Checks whether the value is a lazy value without causing
|
|
||||||
# it's value to be evaluated
|
|
||||||
isLazyValue = x: isAttrs x && x ? _lazyValue;
|
|
||||||
# Constructs a lazy value holding the given value.
|
|
||||||
lazyValue = value: {_lazyValue = value;};
|
|
||||||
|
|
||||||
# Represents a lazy value of the given type, which
|
|
||||||
# holds the actual value as an attrset like { _lazyValue = <actual value>; }.
|
|
||||||
# This allows the option to be defined and filtered from a defintion
|
|
||||||
# list without evaluating the value.
|
|
||||||
lazyValueOf = type:
|
|
||||||
mkOptionType rec {
|
|
||||||
name = "lazyValueOf ${type.name}";
|
|
||||||
inherit (type) description descriptionClass emptyValue getSubOptions getSubModules;
|
|
||||||
check = isLazyValue;
|
|
||||||
merge = loc: defs:
|
|
||||||
assert assertMsg
|
|
||||||
(all (x: type.check x._lazyValue) defs)
|
|
||||||
"The option `${showOption loc}` is defined with a lazy value holding an invalid type";
|
|
||||||
nixpkgs.lib.types.mergeOneOption loc defs;
|
|
||||||
substSubModules = m: nixpkgs.lib.types.uniq (type.substSubModules m);
|
|
||||||
functor = (nixpkgs.lib.types.defaultFunctor name) // {wrapped = type;};
|
|
||||||
nestedTypes.elemType = type;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Represents a value or lazy value of the given type that will
|
|
||||||
# automatically be coerced to the given type when merged.
|
|
||||||
lazyOf = type: nixpkgs.lib.types.coercedTo (lazyValueOf type) (x: x._lazyValue) type;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Counts how often each element occurrs in xs
|
|
||||||
countOccurrences = let
|
|
||||||
addOrUpdate = acc: x:
|
|
||||||
acc // {${x} = (acc.${x} or 0) + 1;};
|
|
||||||
in
|
|
||||||
foldl' addOrUpdate {};
|
|
||||||
|
|
||||||
# Returns all elements in xs that occur at least twice
|
|
||||||
duplicates = xs: let
|
|
||||||
occurrences = countOccurrences xs;
|
|
||||||
in
|
|
||||||
unique (filter (x: occurrences.${x} > 1) xs);
|
|
||||||
|
|
||||||
# Concatenates all given attrsets as if calling a // b in order.
|
|
||||||
concatAttrs = foldl' mergeAttrs {};
|
|
||||||
|
|
||||||
# True if the path or string starts with /
|
|
||||||
isAbsolutePath = x: substring 0 1 x == "/";
|
|
||||||
|
|
||||||
# Merges all given attributes from the given attrsets using mkMerge.
|
|
||||||
# Useful to merge several top-level configs in a module.
|
|
||||||
mergeToplevelConfigs = keys: attrs:
|
|
||||||
genAttrs keys (attr: mkMerge (map (x: x.${attr} or {}) attrs));
|
|
||||||
|
|
||||||
# Calculates base^exp, but careful, this overflows for results > 2^62
|
|
||||||
pow = base: exp: foldl' (a: x: x * a) 1 (genList (_: base) exp);
|
|
||||||
|
|
||||||
# Converts the given hex string to an integer. Only reliable for inputs in [0, 2^63),
|
|
||||||
# after that the sign bit will overflow.
|
|
||||||
hexToDec = v: let
|
|
||||||
literalValues = {
|
|
||||||
"0" = 0;
|
|
||||||
"1" = 1;
|
|
||||||
"2" = 2;
|
|
||||||
"3" = 3;
|
|
||||||
"4" = 4;
|
|
||||||
"5" = 5;
|
|
||||||
"6" = 6;
|
|
||||||
"7" = 7;
|
|
||||||
"8" = 8;
|
|
||||||
"9" = 9;
|
|
||||||
"a" = 10;
|
|
||||||
"b" = 11;
|
|
||||||
"c" = 12;
|
|
||||||
"d" = 13;
|
|
||||||
"e" = 14;
|
|
||||||
"f" = 15;
|
|
||||||
"A" = 10;
|
|
||||||
"B" = 11;
|
|
||||||
"C" = 12;
|
|
||||||
"D" = 13;
|
|
||||||
"E" = 14;
|
|
||||||
"F" = 15;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
foldl' (acc: x: acc * 16 + literalValues.${x}) 0 (stringToCharacters v);
|
|
||||||
|
|
||||||
disko = {
|
|
||||||
gpt = {
|
|
||||||
partGrub = name: start: end: {
|
|
||||||
inherit name start end;
|
|
||||||
part-type = "primary";
|
|
||||||
flags = ["bios_grub"];
|
|
||||||
};
|
|
||||||
partEfi = name: start: end: {
|
|
||||||
inherit name start end;
|
|
||||||
fs-type = "fat32";
|
|
||||||
bootable = true;
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
partSwap = name: start: end: {
|
|
||||||
inherit name start end;
|
|
||||||
fs-type = "linux-swap";
|
|
||||||
content = {
|
|
||||||
type = "swap";
|
|
||||||
randomEncryption = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
partLuksZfs = name: start: end: {
|
|
||||||
inherit start end;
|
|
||||||
name = "enc-${name}";
|
|
||||||
content = {
|
|
||||||
type = "luks";
|
|
||||||
name = "enc-${name}";
|
|
||||||
extraOpenArgs = ["--allow-discards"];
|
|
||||||
content = {
|
|
||||||
type = "zfs";
|
|
||||||
pool = name;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
zfs = rec {
|
|
||||||
defaultZpoolOptions = {
|
|
||||||
type = "zpool";
|
|
||||||
mountRoot = "/mnt";
|
|
||||||
rootFsOptions = {
|
|
||||||
compression = "zstd";
|
|
||||||
acltype = "posix";
|
|
||||||
atime = "off";
|
|
||||||
xattr = "sa";
|
|
||||||
dnodesize = "auto";
|
|
||||||
mountpoint = "none";
|
|
||||||
canmount = "off";
|
|
||||||
devices = "off";
|
|
||||||
};
|
|
||||||
options.ashift = "12";
|
|
||||||
};
|
|
||||||
|
|
||||||
defaultZfsDatasets = {
|
|
||||||
"local" = unmountable;
|
|
||||||
"local/root" =
|
|
||||||
filesystem "/"
|
|
||||||
// {
|
|
||||||
postCreateHook = "zfs snapshot rpool/local/root@blank";
|
|
||||||
};
|
|
||||||
"local/nix" = filesystem "/nix";
|
|
||||||
"local/state" = filesystem "/state";
|
|
||||||
"safe" = unmountable;
|
|
||||||
"safe/persist" = filesystem "/persist";
|
|
||||||
};
|
|
||||||
|
|
||||||
unmountable = {type = "zfs_fs";};
|
|
||||||
filesystem = mountpoint: {
|
|
||||||
type = "zfs_fs";
|
|
||||||
options = {
|
|
||||||
canmount = "noauto";
|
|
||||||
inherit mountpoint;
|
|
||||||
};
|
|
||||||
# Required to add dependencies for initrd
|
|
||||||
inherit mountpoint;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
rageMasterIdentityArgs = concatMapStrings (x: ''-i ${escapeShellArg x} '') self.secretsConfig.masterIdentities;
|
|
||||||
rageExtraEncryptionPubkeys =
|
|
||||||
concatMapStrings (
|
|
||||||
x:
|
|
||||||
if isAbsolutePath x
|
|
||||||
then ''-R ${escapeShellArg x} ''
|
|
||||||
else ''-r ${escapeShellArg x} ''
|
|
||||||
)
|
|
||||||
self.secretsConfig.extraEncryptionPubkeys;
|
|
||||||
# The arguments required to de-/encrypt a secret in this repository
|
|
||||||
rageDecryptArgs = "${rageMasterIdentityArgs}";
|
|
||||||
rageEncryptArgs = "${rageMasterIdentityArgs} ${rageExtraEncryptionPubkeys}";
|
|
||||||
|
|
||||||
# TODO merge this into a _meta readonly option in the wireguard module
|
|
||||||
# Wireguard related functions that are reused in several files of this flake
|
|
||||||
wireguard = wgName: rec {
|
|
||||||
# Get access to the networking lib by referring to one of the participating nodes.
|
|
||||||
# Not ideal, but ok.
|
|
||||||
inherit (self.nodes.${head participatingNodes}.config.lib) net;
|
|
||||||
|
|
||||||
# Returns the given node's wireguard configuration of this network
|
|
||||||
wgCfgOf = node: self.nodes.${node}.config.meta.wireguard.${wgName};
|
|
||||||
|
|
||||||
sortedPeers = peerA: peerB:
|
|
||||||
if peerA < peerB
|
|
||||||
then {
|
|
||||||
peer1 = peerA;
|
|
||||||
peer2 = peerB;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
peer1 = peerB;
|
|
||||||
peer2 = peerA;
|
|
||||||
};
|
|
||||||
|
|
||||||
peerPublicKeyFile = peerName: "secrets/wireguard/${wgName}/keys/${peerName}.pub";
|
|
||||||
peerPublicKeyPath = peerName: "${self.outPath}/" + peerPublicKeyFile peerName;
|
|
||||||
|
|
||||||
peerPrivateKeyFile = peerName: "secrets/wireguard/${wgName}/keys/${peerName}.age";
|
|
||||||
peerPrivateKeyPath = peerName: "${self.outPath}/" + peerPrivateKeyFile peerName;
|
|
||||||
peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}";
|
|
||||||
|
|
||||||
peerPresharedKeyFile = peerA: peerB: let
|
|
||||||
inherit (sortedPeers peerA peerB) peer1 peer2;
|
|
||||||
in "secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age";
|
|
||||||
peerPresharedKeyPath = peerA: peerB: "${self.outPath}/" + peerPresharedKeyFile peerA peerB;
|
|
||||||
peerPresharedKeySecret = peerA: peerB: let
|
|
||||||
inherit (sortedPeers peerA peerB) peer1 peer2;
|
|
||||||
in "wireguard-${wgName}-psks-${peer1}+${peer2}";
|
|
||||||
|
|
||||||
# All nodes that are part of this network
|
|
||||||
participatingNodes =
|
|
||||||
filter
|
|
||||||
(n: builtins.hasAttr wgName self.nodes.${n}.config.meta.wireguard)
|
|
||||||
(attrNames self.nodes);
|
|
||||||
|
|
||||||
# Partition nodes by whether they are servers
|
|
||||||
_participatingNodes_isServerPartition =
|
|
||||||
partition
|
|
||||||
(n: (wgCfgOf n).server.host != null)
|
|
||||||
participatingNodes;
|
|
||||||
|
|
||||||
participatingServerNodes = _participatingNodes_isServerPartition.right;
|
|
||||||
participatingClientNodes = _participatingNodes_isServerPartition.wrong;
|
|
||||||
|
|
||||||
# Maps all nodes that are part of this network to their addresses
|
|
||||||
nodePeers = genAttrs participatingNodes (n: (wgCfgOf n).addresses);
|
|
||||||
|
|
||||||
externalPeerName = p: "external-${p}";
|
|
||||||
|
|
||||||
# Only peers that are defined as externalPeers on the given node.
|
|
||||||
# Prepends "external-" to their name.
|
|
||||||
externalPeersForNode = node:
|
|
||||||
mapAttrs' (p: nameValuePair (externalPeerName p)) (wgCfgOf node).server.externalPeers;
|
|
||||||
|
|
||||||
# All peers that are defined as externalPeers on any node.
|
|
||||||
# Prepends "external-" to their name.
|
|
||||||
allExternalPeers = concatAttrs (map externalPeersForNode participatingNodes);
|
|
||||||
|
|
||||||
# All peers that are part of this network
|
|
||||||
allPeers = nodePeers // allExternalPeers;
|
|
||||||
|
|
||||||
# Concatenation of all external peer names names without any transformations.
|
|
||||||
externalPeerNamesRaw = concatMap (n: attrNames (wgCfgOf n).server.externalPeers) participatingNodes;
|
|
||||||
|
|
||||||
# A list of all occurring addresses.
|
|
||||||
usedAddresses =
|
|
||||||
concatMap (n: (wgCfgOf n).addresses) participatingNodes
|
|
||||||
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
|
||||||
|
|
||||||
# A list of all occurring addresses, but only includes addresses that
|
|
||||||
# are not assigned automatically.
|
|
||||||
explicitlyUsedAddresses =
|
|
||||||
flip concatMap participatingNodes
|
|
||||||
(n:
|
|
||||||
filter (x: !types.isLazyValue x)
|
|
||||||
(concatLists
|
|
||||||
(self.nodes.${n}.options.meta.wireguard.type.functor.wrapped.getSubOptions (wgCfgOf n)).addresses.definitions))
|
|
||||||
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
|
||||||
|
|
||||||
# The cidrv4 and cidrv6 of the network spanned by all participating peer addresses.
|
|
||||||
# This also takes into account any reserved address ranges that should be part of the network.
|
|
||||||
networkAddresses =
|
|
||||||
net.cidr.merge (usedAddresses
|
|
||||||
++ concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
|
||||||
|
|
||||||
# The network spanning cidr addresses. The respective cidrv4 and cirdv6 are only
|
|
||||||
# included if they exist.
|
|
||||||
networkCidrs = filter (x: x != null) (attrValues networkAddresses);
|
|
||||||
|
|
||||||
# The cidrv4 and cidrv6 of the network spanned by all reserved addresses only.
|
|
||||||
# Used to determine automatically assigned addresses first.
|
|
||||||
spannedReservedNetwork =
|
|
||||||
net.cidr.merge (concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
|
||||||
|
|
||||||
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
|
||||||
# to each participant that has not explicitly specified an ipv4 address.
|
|
||||||
assignedIpv4Addresses = assert assertMsg
|
|
||||||
(spannedReservedNetwork.cidrv4 != null)
|
|
||||||
"Wireguard network '${wgName}': At least one participating node must reserve a cidrv4 address via `reservedAddresses` so that ipv4 addresses can be assigned automatically from that network.";
|
|
||||||
net.cidr.assignIps
|
|
||||||
spannedReservedNetwork.cidrv4
|
|
||||||
# Don't assign any addresses that are explicitly configured on other hosts
|
|
||||||
(filter (x: net.cidr.contains x spannedReservedNetwork.cidrv4) (filter net.ip.isv4 explicitlyUsedAddresses))
|
|
||||||
participatingNodes;
|
|
||||||
|
|
||||||
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
|
||||||
# to each participant that has not explicitly specified an ipv4 address.
|
|
||||||
assignedIpv6Addresses = assert assertMsg
|
|
||||||
(spannedReservedNetwork.cidrv6 != null)
|
|
||||||
"Wireguard network '${wgName}': At least one participating node must reserve a cidrv6 address via `reservedAddresses` so that ipv4 addresses can be assigned automatically from that network.";
|
|
||||||
net.cidr.assignIps
|
|
||||||
spannedReservedNetwork.cidrv6
|
|
||||||
# Don't assign any addresses that are explicitly configured on other hosts
|
|
||||||
(filter (x: net.cidr.contains x spannedReservedNetwork.cidrv6) (filter net.ip.isv6 explicitlyUsedAddresses))
|
|
||||||
participatingNodes;
|
|
||||||
|
|
||||||
# Appends / replaces the correct cidr length to the argument,
|
|
||||||
# so that the resulting address is in the cidr.
|
|
||||||
toNetworkAddr = addr: let
|
|
||||||
relevantNetworkAddr =
|
|
||||||
if net.ip.isv6 addr
|
|
||||||
then networkAddresses.cidrv6
|
|
||||||
else networkAddresses.cidrv4;
|
|
||||||
in "${net.cidr.ip addr}/${toString (net.cidr.length relevantNetworkAddr)}";
|
|
||||||
|
|
||||||
# Creates a script that when executed outputs a wg-quick compatible configuration
|
|
||||||
# file for use with external peers. This is a script so we can access secrets without
|
|
||||||
# storing them in the nix-store.
|
|
||||||
wgQuickConfigScript = system: serverNode: extPeer: let
|
|
||||||
pkgs = self.pkgs.${system};
|
|
||||||
snCfg = wgCfgOf serverNode;
|
|
||||||
peerName = externalPeerName extPeer;
|
|
||||||
addresses = map toNetworkAddr snCfg.server.externalPeers.${extPeer};
|
|
||||||
in
|
|
||||||
pkgs.writeShellScript "create-wg-conf-${wgName}-${serverNode}-${extPeer}" ''
|
|
||||||
privKey=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPrivateKeyPath peerName)}) \
|
|
||||||
|| { echo "[1;31merror:[m Failed to decrypt!" >&2; exit 1; }
|
|
||||||
serverPsk=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPresharedKeyPath serverNode peerName)}) \
|
|
||||||
|| { echo "[1;31merror:[m Failed to decrypt!" >&2; exit 1; }
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
[Interface]
|
|
||||||
Address = ${concatStringsSep ", " addresses}
|
|
||||||
PrivateKey = $privKey
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = ${removeSuffix "\n" (builtins.readFile (peerPublicKeyPath serverNode))}
|
|
||||||
PresharedKey = $serverPsk
|
|
||||||
AllowedIPs = ${concatStringsSep ", " networkCidrs}
|
|
||||||
Endpoint = ${snCfg.server.host}:${toString snCfg.server.port}
|
|
||||||
PersistentKeepalive = 25
|
|
||||||
EOF
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue