refactor: get rid of config folder

This commit is contained in:
Kat Inskip 2022-07-08 17:53:16 -07:00
parent 2606e1d874
commit cb3ae5f434
Signed by: kat
GPG key ID: 465E64DECEA8CF0F
254 changed files with 79 additions and 101 deletions

View file

@ -0,0 +1,58 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.security.pam;
# Implementation Notes
#
# We don't use `environment.etc` because this would require that the user manually delete
# `/etc/pam.d/sudo` which seems unwise given that applying the nix-darwin configuration requires
# sudo. We also can't use `system.patchs` since it only runs once, and so won't patch in the
# changes again after OS updates (which remove modifications to this file).
#
# As such, we resort to line addition/deletion in place using `sed`. We add a comment to the
# added line that includes the name of the option, to make it easier to identify the line that
# should be deleted when the option is disabled.
mkSudoTouchIdAuthScript = isEnabled:
let
file = "/etc/pam.d/sudo";
option = "security.pam.enableSudoTouchIdAuth";
in ''
${if isEnabled then ''
# Enable sudo Touch ID authentication, if not already enabled
if ! grep 'pam_tid.so' ${file} > /dev/null; then
sed -i "" '2i\
auth sufficient pam_tid.so # nix-darwin: ${option}
' ${file}
fi
'' else ''
# Disable sudo Touch ID authentication, if added by nix-darwin
if grep '${option}' ${file} > /dev/null; then
sed -i "" '/${option}/d' ${file}
fi
''}
'';
in
{
options = {
security.pam.enableSudoTouchIdAuth = mkEnableOption ''
Enable sudo authentication with Touch ID
When enabled, this option adds the following line to /etc/pam.d/sudo:
auth sufficient pam_tid.so
(Note that macOS resets this file when doing a system update. As such, sudo
authentication with Touch ID won't work after a system update until the nix-darwin
configuration is reapplied.)
'';
};
config = {
system.activationScripts.extraActivation.text = ''
# PAM settings
echo >&2 "setting up pam..."
${mkSudoTouchIdAuthScript cfg.enableSudoTouchIdAuth}
'';
};
}

39
modules/home/deploy.nix Normal file
View file

@ -0,0 +1,39 @@
{ config, lib, ... }:
/*
This module:
* Provides in-scope TF config for home-manager.
*/
with lib;
let
cfg = config.deploy.tf;
unmergedValues = types.mkOptionType {
name = "unmergedValues";
merge = loc: defs: map (def: def.value) defs;
};
in
{
options.deploy.tf = mkOption {
type = types.submodule {
freeformType = types.attrsOf unmergedValues;
options = {
attrs = mkOption {
type = types.listOf types.str;
default = [ ];
};
out.set = mkOption { type = types.unspecified; };
};
};
};
config = {
deploy.tf = {
attrs = [ "out" "attrs" ];
out.set = removeAttrs cfg cfg.attrs;
};
};
}

View file

@ -0,0 +1,4 @@
{ ... }: {
disabledModules = [
];
}

22
modules/home/displays.nix Normal file
View file

@ -0,0 +1,22 @@
{ config, lib, nixos, ... }: with lib; {
options.hardware.displays = mkOption {
type = with types; attrsOf (submodule ({ config, ... }: {
options = {
pos = mkOption {
type = types.str;
};
res = mkOption {
type = types.str;
};
};
}));
};
config = mkMerge [
{
hardware.displays = nixos.hardware.displays;
}
(mkIf config.wayland.windowManager.sway.enable {
wayland.windowManager.sway.config.output = config.hardware.displays;
})
];
}

54
modules/home/firewall.nix Normal file
View file

@ -0,0 +1,54 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.network.firewall;
in
{
options.network.firewall = {
public.tcp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
public.udp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
private.tcp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
private.udp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
public.tcp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
public.udp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
private.tcp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
private.udp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
public.interfaces = mkOption {
type = types.listOf types.str;
description = "Public firewall interfaces";
default = [ ];
};
private.interfaces = mkOption {
type = types.listOf types.str;
description = "Private firewall interfaces";
default = [ ];
};
};
}

77
modules/home/network.nix Normal file
View file

@ -0,0 +1,77 @@
{ config, nixos, lib, ... }:
with lib;
{
options.network = {
enable = mkEnableOption "Use kat's network module?";
addresses = mkOption {
type = with types; attrsOf (submodule ({ name, ... }: {
options = {
enable = mkEnableOption "Is the system a part of the ${name} network?";
ipv4 = {
enable = mkOption {
type = types.bool;
};
address = mkOption {
type = types.str;
};
};
ipv6 = {
enable = mkOption {
type = types.bool;
};
address = mkOption {
type = types.str;
};
};
prefix = mkOption {
type = types.nullOr types.str;
};
domain = mkOption {
type = types.nullOr types.str;
};
out = {
identifierList = mkOption {
type = types.listOf types.str;
default = if config.enable then singleton config.domain ++ config.out.addressList else [ ];
};
addressList = mkOption {
type = types.listOf types.str;
default = if config.enable then concatMap (i: optional i.enable i.address) [ config.ipv4 config.ipv6 ] else [ ];
};
};
};
}));
};
tf = {
enable = mkEnableOption "Was the system provisioned by terraform?";
ipv4_attr = mkOption {
type = types.str;
};
ipv6_attr = mkOption {
type = types.str;
};
};
dns = {
enable = mkEnableOption "Do you want DNS to be semi-managed through this module?";
isRoot = mkEnableOption "Is this system supposed to be the @ for the domain?";
email = mkOption {
type = types.nullOr types.str;
};
zone = mkOption {
type = types.nullOr types.str;
};
domain = mkOption {
type = types.nullOr types.str;
};
dynamic = mkEnableOption "Enable Glauca Dynamic DNS Updater";
};
};
config = {
network.addresses = nixos.network.addresses or {};
network.tf = nixos.network.tf or {};
network.dns = nixos.network.dns or {};
};
}

43
modules/home/secrets.nix Normal file
View file

@ -0,0 +1,43 @@
{ config, nixos, lib, ... }:
with lib;
let
secretType = types.submodule ({ name, ... }: {
options = {
path = mkOption { type = types.str; };
field = mkOption {
type = types.str;
default = "";
};
};
});
repoSecretType = types.submodule ({ name, ... }: {
options = {
source = mkOption {
type = types.path;
};
text = mkOption {
type = types.str;
};
};
});
cfg = config.kw.secrets;
in
{
options.kw = {
secrets = {
variables = mkOption {
type = types.attrsOf secretType;
default = { };
};
repo = mkOption {
type = types.attrsOf repoSecretType;
default = { };
};
};
};
config = {
kw.secrets.repo = nixos.kw.secrets.repo;
};
}

158
modules/home/theme.nix Normal file
View file

@ -0,0 +1,158 @@
{ config, pkgs, lib, ... }:
/*
This module:
* provides a central way to change the font my system uses.
*/
with lib;
let cfg = config.kw.theme; in
{
options.kw.theme = {
enable = mkEnableOption "kat's theme module";
sass = {
variables = mkOption {
type = types.attrsOf types.str;
default = (cfg.base16 // cfg.base16t // {
term_font = cfg.font.termName;
font = cfg.font.name;
font_size = cfg.font.size_css;
});
};
css_style = mkOption {
type = types.enum [ "nested" "compressed" "compact" "expanded" ];
default = "expanded";
};
};
swaylock = mkEnableOption "use swaylock module";
base16 = mkOption {
type = types.attrsOf types.str;
};
base16t = mkOption {
type = types.attrsOf types.str;
};
alpha = mkOption {
type = types.float;
};
font = {
name = mkOption {
type = types.str;
default = "Iosevka SS10";
};
termName = mkOption {
type = types.str;
default = "Iosevka Term SS10";
};
size = mkOption {
type = types.float;
default = 9.0;
};
size_css = mkOption {
type = types.str;
default = "${toString (cfg.font.size + 3)}px";
};
};
};
config = mkIf (cfg.enable) {
kw.theme = {
base16 = lib.mapAttrs' (k: v: lib.nameValuePair k "#${v.hex.rgb}")
(lib.filterAttrs (n: _: lib.hasInfix "base" n) config.lib.arc.base16.schemeForAlias.default);
base16t = lib.mapAttrs' (k: v: lib.nameValuePair "${k}t" "rgba(${toString v.rgb.r}, ${toString v.rgb.g}, ${toString v.rgb.b}, ${toString cfg.alpha})")
(lib.filterAttrs (n: _: lib.hasInfix "base" n) config.lib.arc.base16.schemeForAlias.default);
alpha = 0.7;
};
programs.swaylock = mkIf (cfg.swaylock) {
enable = true;
package = pkgs.swaylock-effects-develop;
args = {
screenshots = true;
daemonize = true;
show-failed-attempts = true;
indicator = true;
indicator-radius = 110;
indicator-thickness = 8;
font = cfg.font.name;
font-size = cfg.font.size_css;
clock = true;
datestr = "%F";
timestr = "%T";
effect-blur = "5x2";
fade-in = 0.2;
};
colors = with cfg.base16; {
key-hl = base0C;
separator = base01;
line = base01;
line-clear = base01;
line-caps-lock = base01;
line-ver = base01;
line-wrong = base01;
ring = base00;
ring-clear = base0B;
ring-caps-lock = base09;
ring-ver = base0D;
ring-wrong = base08;
inside = base00;
inside-clear = base00;
inside-caps-lock = base00;
inside-ver = base00;
inside-wrong = base00;
text = base05;
text-clear = base05;
text-caps-lock = base05;
text-ver = base05;
text-wrong = base05;
};
};
systemd.user.services.swayidle = mkIf (cfg.swaylock) {
Unit = {
Description = "swayidle";
Documentation = [ "man:swayidle(1)" ];
PartOf = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart =
let
lockCommand = config.programs.swaylock.script;
in
''
${pkgs.swayidle}/bin/swayidle -w \
timeout 300 '${lockCommand}' \
timeout 600 'swaymsg "output * dpms off"' \
resume 'swaymsg "output * dpms on"' \
before-sleep '${lockCommand}'
'';
RestartSec = 3;
Restart = "always";
};
Install = { WantedBy = [ "sway-session.target" ]; };
};
lib.kw.sassTemplate = { name, src }:
let
variables = pkgs.writeText "base-variables.sass" ''
${(concatStringsSep "\n" (mapAttrsToList(var: con: "\$${var}: ${con}") cfg.sass.variables))}
'';
source = pkgs.callPackage
({ sass, stdenv }: stdenv.mkDerivation {
inherit name src variables;
nativeBuildInputs = lib.singleton sass;
phases = [ "buildPhase" ];
buildPhase = ''
cat $variables $src > src-mut.sass
sass src-mut.sass $out --sourcemap=none --style=${cfg.sass.css_style}
'';
})
{ };
in
{
inherit source;
text = builtins.readFile source;
};
_module.args = { inherit (config.lib) kw; };
};
}

170
modules/meta/deploy.nix Normal file
View file

@ -0,0 +1,170 @@
{ inputs, config, pkgs, lib, ... }:
/*
This module:
* makes tf-nix a part of the meta config
* handles the trusted import for tf-nix
* provides the target interface
* imports the per-host TF config for each target
*/
with lib;
let
cfg = config.deploy;
meta = config;
tfModule = { lib, ... }: with lib; {
config._module.args = {
pkgs = mkDefault pkgs;
};
};
tfType = types.submoduleWith {
modules = [
tfModule
"${toString inputs.tf-nix}/modules"
];
specialArgs = {
meta = config;
};
shorthandOnlyDefinesConfig = true;
};
in
{
imports = [
"${toString inputs.tf-nix}/modules/run.nix"
] ++ (optional (builtins.pathExists ../../tf/tf.nix) (../../tf/tf.nix));
options = {
deploy = {
dataDir = mkOption {
type = types.path;
default = ../../tf;
};
local = {
isRoot = mkOption {
type = types.bool;
default = builtins.getEnv "HOME_UID" == "0";
};
hostName = mkOption {
type = types.nullOr types.str;
default =
let
hostName = builtins.getEnv "HOME_HOSTNAME";
in
if hostName == "" then null else hostName;
};
};
targets =
let
type = types.submodule ({ config, name, ... }: {
options = {
enable = mkEnableOption "Enable the target" // { default = true; };
name = mkOption {
type = types.str;
default = name;
};
nodeNames = mkOption {
type = types.listOf types.str;
default = [ ];
};
tf = mkOption {
type = tfType;
default = { };
};
};
config.tf = mkMerge (singleton
({ ... }: {
imports = [
../../tf.nix
];
deploy.gcroot = {
name = mkDefault "kw-${config.name}";
user = mkIf (builtins.getEnv "HOME_USER" != "") (mkDefault (builtins.getEnv "HOME_USER"));
};
providers.local = { };
deps = {
select.allProviders = true;
enable = true;
/*
apply = {
doneCommand = ''
git -C "${cfg.dataDir}" add -A
git -C "${cfg.dataDir}" commit -m "${config.name}: $(date +'%F %T')"
git -C "${cfg.dataDir}" push
'';
};
*/
};
terraform = {
version = "1.0";
prettyJson = true;
logPath = cfg.dataDir + "/terraform-${config.name}.log";
dataDir = cfg.dataDir + "/tfdata/${config.name}";
environment.TF_CLI_ARGS_apply = "-backup=-";
environment.TF_CLI_ARGS_taint = "-backup=-";
};
state = {
file = cfg.dataDir + "/terraform-${config.name}.tfstate";
};
runners = {
lazy = {
inherit (meta.runners.lazy) file args;
attrPrefix = "deploy.targets.${name}.tf.runners.run.";
};
run = {
apply.name = "${name}-apply-uw";
terraform.name = "${name}-tf";
myApply = {
name = "${name}-apply";
command = let
path = toString cfg.dataDir;
in ''
set -e
git -C "${path}" pull
${config.tf.runners.run.apply.package}/bin/${config.tf.runners.run.apply.executable}
git -C "${path}" add -A
git -C "${path}" commit -m "${config.name}: $(date +'%F %T')"
git -C "${path}" push --force
'';
};
};
};
continue.envVar = "TF_NIX_CONTINUE_${replaceStrings [ "-" ] [ "_" ] config.name}";
}) ++ map (nodeName: mapAttrs (_: mkMerge) meta.network.nodes.nixos.${nodeName}.deploy.tf.out.set) config.nodeNames);
});
in
mkOption {
type = types.attrsOf type;
default = { };
};
};
};
config = {
deploy.targets =
let
nodeNames = attrNames config.network.nodes.nixos;
targets = config.deploy.targets;
explicitlyDefinedHosts = concatLists (mapAttrsToList (targetName: target: remove targetName target.nodeNames) config.deploy.targets);
in
genAttrs nodeNames (nodeName: {
enable = mkDefault (! elem nodeName explicitlyDefinedHosts);
nodeNames = singleton nodeName;
});
runners = {
run = mkMerge (mapAttrsToList
(targetName: target: mapAttrs'
(k: run:
nameValuePair run.name run.set
)
target.tf.runners.run)
(filterAttrs (_: v: v.enable) cfg.targets));
lazy.run = mkMerge (mapAttrsToList
(targetName: target: mapAttrs'
(k: run:
nameValuePair run.name run.set
)
target.tf.runners.lazy.run)
(filterAttrs (_: v: v.enable) cfg.targets));
};
};
}

56
modules/meta/imports.nix Normal file
View file

@ -0,0 +1,56 @@
{ config, lib, profiles, root, ... }:
with lib;
{
options = {
lib = mkOption {
type = types.attrsOf (types.attrsOf types.unspecified);
};
network.importing = {
nixosImports = mkOption {
type = types.listOf types.str;
};
darwinImports = mkOption {
type = types.listOf types.str;
};
homeImports = mkOption {
type = types.listOf types.str;
};
users = mkOption {
type = types.listOf types.str;
};
};
};
config = {
network.importing = {
nixosImports = mkDefault (map (path: toString path) [
(root + "/nodes/nixos/HN.nix")
(root + "/nodes/nixos/HN/nixos.nix")
(root + "/trusted/nodes/nixos/HN/nixos.nix")
]);
darwinImports = mkDefault (map (path: toString path) [
(root + "/nodes/darwin/HN.nix")
(root + "/nodes/darwin/HN/darwin.nix")
(root + "/trusted/nodes/darwin/HN/darwin.nix")
]);
homeImports = mkDefault (map (path: toString path) [
(root + "/nodes/nixos/HN/home.nix")
(root + "/nodes/darwin/HN/home.nix")
(root + "/trusted/nodes/HN/home.nix")
]);
users = mkDefault (singleton "kat");
};
lib.kw.nixosImport = hostName: lib.nodeImport {
inherit (config.network.importing) nixosImports homeImports users;
inherit profiles hostName;
};
lib.kw.darwinImport = hostName: lib.nodeImport {
nixosImports = config.network.importing.darwinImports;
profiles = profiles // { base = {}; };
inherit (config.network.importing) homeImports users;
inherit hostName;
};
_module.args = { inherit (config.lib) kw; };
};
}

136
modules/meta/network.nix Normal file
View file

@ -0,0 +1,136 @@
{ pkgs, inputs, lib, meta, config, ... }:
/*
This module:
* Makes hosts nixosModules.
* Manages module imports and specialArgs.
* Builds network.nodes.
*/
with lib;
{
options.network = {
nixos = {
extraModules = mkOption {
type = types.listOf types.unspecified;
default = [ ];
};
specialArgs = mkOption {
type = types.attrsOf types.unspecified;
default = { };
};
modulesPath = mkOption {
type = types.path;
default = toString (pkgs.path + "/nixos/modules");
};
};
darwin = {
extraModules = mkOption {
type = types.listOf types.unspecified;
default = [ ];
};
specialArgs = mkOption {
type = types.attrsOf types.unspecified;
default = { };
};
modulesPath = mkOption {
type = types.path;
default = toString (inputs.darwin + "/modules");
};
};
nodes.nixos =
let
nixosModule = { name, config, meta, modulesPath, lib, ... }: with lib; {
options = {
nixpkgs.crossOverlays = mkOption {
type = types.listOf types.unspecified;
default = [ ];
};
};
config = {
nixpkgs = {
system = mkDefault pkgs.system;
pkgs =
let
pkgsReval = import pkgs.path {
inherit (config.nixpkgs) localSystem crossSystem crossOverlays;
inherit (pkgs) overlays config;
};
in
mkDefault (if config.nixpkgs.config == pkgs.config && config.nixpkgs.system == pkgs.targetPlatform.system then pkgs else pkgsReval);
};
};
};
nixosType =
let
baseModules = import (config.network.nixos.modulesPath + "/module-list.nix");
in
types.submoduleWith {
modules = baseModules
++ singleton nixosModule
++ config.network.nixos.extraModules;
specialArgs = {
inherit baseModules;
inherit (config.network.nixos) modulesPath;
} // config.network.nixos.specialArgs;
};
in
mkOption {
type = types.attrsOf nixosType;
default = { };
};
nodes.darwin =
let
darwinModule = { name, config, meta, modulesPath, lib, ... }: with lib; {
config = {
_module.args.pkgs = pkgs;
nixpkgs = {
system = mkDefault pkgs.system;
};
};
};
darwinType =
let
baseModules = import (config.network.darwin.modulesPath + "/module-list.nix");
in
types.submoduleWith {
modules = baseModules
++ singleton darwinModule
++ config.network.darwin.extraModules;
specialArgs = {
inherit baseModules;
inherit (config.network.darwin) modulesPath;
} // config.network.darwin.specialArgs;
};
in
mkOption {
type = types.attrsOf darwinType;
default = { };
};
};
config.network = {
darwin = {
extraModules = [
inputs.home-manager.darwinModules.home-manager
meta.modules.darwin
];
specialArgs = {
inherit (config.network) nodes;
inherit inputs meta;
};
};
nixos = {
extraModules = [
inputs.home-manager.nixosModules.home-manager
meta.modules.nixos
];
specialArgs = {
inherit (config.network) nodes;
inherit inputs meta;
};
};
};
}

5
modules/meta/secrets.nix Normal file
View file

@ -0,0 +1,5 @@
{ config, lib, ... }: with lib; {
options.kw.secrets.command = mkOption {
type = types.str;
};
}

176
modules/nixos/bird.nix Normal file
View file

@ -0,0 +1,176 @@
{ config, lib, pkgs, ... }:
with lib;
let
bcfg = config.network.bird;
cfg = config.network.bird.ospf;
in
{
options.network.bird = {
routerId = mkOption {
type = types.nullOr types.str;
default = null;
description = "Router ID to use. Must be an IPv4 address.";
};
kernel4Config = mkOption {
type = types.lines;
default = ''
ipv4 {
import none;
export filter {
if source = RTS_STATIC then reject;
accept;
};
};
scan time 15;
'';
};
kernel6Config = mkOption {
type = types.lines;
default = ''
ipv6 {
import none;
export filter {
if source = RTS_STATIC then reject;
accept;
};
};
scan time 15;
'';
};
staticRoutes4 = mkOption {
type = types.listOf types.str;
default = [ ];
};
extraStatic4 = mkOption {
type = types.lines;
default = "";
};
staticRoutes6 = mkOption {
type = types.listOf types.str;
default = [ ];
};
extraStatic6 = mkOption {
type = types.lines;
default = "";
};
};
options.network.bird.ospf = {
enable = mkEnableOption "OSPF-based network routing";
protocols = mkOption {
default = { };
type = types.attrsOf (types.submodule {
options = {
version = mkOption { type = types.enum [ 2 3 ]; default = 2; };
extra = mkOption { type = types.lines; default = ""; };
areas = mkOption {
description = "areas to configure in bird";
default = { };
type = types.attrsOf (types.submodule {
options = {
extra = mkOption { type = types.lines; default = ""; };
networks = mkOption {
description = "Definition of area IP ranges. This is used in summary LSA origination.";
type = types.listOf types.str;
default = [ ];
};
external = mkOption {
description = "Definition of external area IP ranges for NSSAs. This is used for NSSA-LSA translation.";
type = types.listOf types.str;
default = [ ];
};
interfaces = mkOption {
description = "Interfaces to assign to the area";
type = types.attrsOf (types.submodule {
options = {
cost = mkOption { type = types.int; default = 10; };
poll = mkOption { type = types.int; default = 20; };
retransmit = mkOption { type = types.int; default = 5; };
priority = mkOption { type = types.int; default = 1; };
deadCount = mkOption { type = types.int; default = 4; };
type = mkOption {
type = types.enum [
null
"broadcast"
"bcast"
"pointopoint"
"ptp"
"nonbroadcast"
"nbma"
"pointomultipoint"
"ptmp"
];
default = null;
};
extra = mkOption { type = types.lines; default = ""; };
};
});
};
};
});
};
};
});
};
};
config = mkIf cfg.enable {
services.bird2 = {
enable = true;
config = ''
${optionalString (bcfg.routerId != null) "router id ${bcfg.routerId};"}
protocol device {
scan time 10;
}
protocol kernel kernel4 {
${bcfg.kernel4Config}
}
protocol kernel kernel6 {
${bcfg.kernel6Config}
}
protocol static static4 {
ipv4 { import all; export none; };
${concatMapStringsSep "\n" (x: "route ${x};") bcfg.staticRoutes4}
${bcfg.extraStatic4}
}
protocol static static6 {
ipv6 { import all; export none; };
${concatMapStringsSep "\n" (x: "route ${x};") bcfg.staticRoutes6}
${bcfg.extraStatic6}
}
${concatStringsSep "\n" (mapAttrsToList (protoName: proto: ''
protocol ospf v${toString proto.version} ${protoName} {
${concatStringsSep "\n" (mapAttrsToList (areaName: area: ''
area ${areaName} {
${optionalString
(area.networks != [])
"networks { ${concatStringsSep "\n" (map (x: "${x};") area.networks)} };"}
${optionalString
(area.external != [])
"external { ${concatStringsSep "\n" (map (x: "${x};") area.external)} };"}
${concatStringsSep "\n" (mapAttrsToList (ifacePattern: iface: ''
interface "${ifacePattern}" {
cost ${toString iface.cost};
poll ${toString iface.poll};
retransmit ${toString iface.retransmit};
priority ${toString iface.priority};
dead count ${toString iface.deadCount};
${optionalString (iface.type != null) "type ${iface.type};"}
${iface.extra}
};
'') area.interfaces)}
${area.extra}
};
'') proto.areas)}
${proto.extra}
}
'') cfg.protocols)}
'';
};
};
}

84
modules/nixos/deploy.nix Normal file
View file

@ -0,0 +1,84 @@
{ tf, target, name, meta, config, lib, ... }:
/*
This module:
* aliases <hostname>.system.build.toplevel to <hostname>.deploy.system for ease of use.
* marries meta config to NixOS configs for each host.
* provides in-scope TF config in NixOS and home-manager, instead of only as a part of meta config.
*/
with lib;
let
cfg = config.deploy;
unmergedValues = types.mkOptionType {
name = "unmergedValues";
merge = loc: defs: map (def: def.value) defs;
};
in
{
options.deploy = {
targetName = mkOption {
type = types.nullOr types.str;
default = null;
};
system = mkOption {
type = types.unspecified;
readOnly = true;
};
};
options.deploy.tf = mkOption {
type = types.submodule {
inherit (unmerged) freeformType;
options = {
import = mkOption {
type = types.attrsOf types.unspecified;
default = [ ];
};
imports = mkOption {
type = types.listOf types.str;
description = "Other targets to depend on";
default = [ ];
};
attrs = mkOption {
type = types.listOf types.str;
default = [ ];
};
out.set = mkOption { type = types.unspecified; };
};
};
};
config = {
deploy = {
system = config.system.build.toplevel;
targetName = let targetsList = attrNames (filterAttrs (_: target: target.enable && elem name target.nodeNames) meta.deploy.targets); in
if (builtins.length targetsList == 0) then null
else lib.warnIf (builtins.length targetsList > 1) "The host ${name} is assigned to several targets: ${concatMapStrings (x: "${x},") targetsList}." (head targetsList);
};
deploy.tf = mkMerge (singleton
(lib.mkIf (config.deploy.targetName != null) {
attrs = [ "import" "imports" "out" "attrs" ];
import = genAttrs cfg.tf.imports (target: meta.deploy.targets.${target}.tf);
out.set = removeAttrs cfg.tf cfg.tf.attrs;
deploy.systems.${config.networking.hostName} =
with tf.resources; {
isRemote =
(config.networking.hostName != builtins.getEnv "HOME_HOSTNAME");
nixosConfig = config;
connection = tf.resources.${config.networking.hostName}.connection.set;
triggers.copy.${config.networking.hostName} =
tf.resources.${config.networking.hostName}.refAttr "id";
triggers.secrets.${config.networking.hostName} =
tf.resources.${config.networking.hostName}.refAttr "id";
};
}) ++ mapAttrsToList
(_: user:
mapAttrs (_: mkMerge) user.deploy.tf.out.set)
config.home-manager.users);
_module.args.target = mapNullable (targetName: meta.deploy.targets.${targetName}) cfg.targetName;
_module.args.tf = mapNullable (target: target.tf) target;
};
}

View file

@ -0,0 +1,2 @@
{ inputs, ... }: {
}

View file

@ -0,0 +1,14 @@
{ config, lib, ... }: with lib; {
options.hardware.displays = mkOption {
type = with types; attrsOf (submodule ({ config, ... }: {
options = {
pos = mkOption {
type = types.str;
};
res = mkOption {
type = types.str;
};
};
}));
};
}

View file

@ -0,0 +1,82 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.network.firewall;
in
{
options.network.firewall = {
public.tcp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
public.udp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
private.tcp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
private.udp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
public.tcp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
public.udp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
private.tcp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
private.udp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
public.interfaces = mkOption {
type = types.listOf types.str;
description = "Public firewall interfaces";
default = [ ];
};
private.interfaces = mkOption {
type = types.listOf types.str;
description = "Private firewall interfaces";
default = [ ];
};
};
config = {
network.firewall = mkMerge (mapAttrsToList (_: user: user.network.firewall) config.home-manager.users);
networking.firewall.interfaces =
let
fwTypes = {
ports = "Ports";
ranges = "PortRanges";
};
interfaceDef = visibility:
listToAttrs (flatten (mapAttrsToList
(type: typeString:
map
(proto: {
name = "allowed${toUpper proto}${typeString}";
value = cfg.${visibility}.${proto}.${type};
}) [ "tcp" "udp" ])
fwTypes));
interfaces = visibility:
listToAttrs
(map (interface: nameValuePair interface (interfaceDef visibility))
cfg.${visibility}.interfaces);
in
mkMerge (map (visibility: interfaces visibility) [ "public" "private" ]);
};
}

259
modules/nixos/network.nix Normal file
View file

@ -0,0 +1,259 @@
{ config, lib, tf, pkgs, ... }:
with lib;
let
cfg = config.network;
in
{
options.network = {
enable = mkEnableOption "Use kat's network module?";
addresses = mkOption {
type = with types; attrsOf (submodule ({ name, options, config, ... }: {
options = {
enable = mkEnableOption "Is it a member of the ${name} network?";
nixos = {
ipv4 = {
enable = mkOption {
type = types.bool;
default = options.nixos.ipv4.address.isDefined;
};
selfaddress = mkOption {
type = types.str;
};
address = mkOption {
type = types.str;
};
};
ipv6 = {
enable = mkOption {
type = types.bool;
default = options.nixos.ipv6.address.isDefined;
};
selfaddress = mkOption {
type = types.str;
};
address = mkOption {
type = types.str;
};
};
};
tf = {
ipv4 = {
enable = mkOption {
type = types.bool;
default = options.tf.ipv4.address.isDefined;
};
address = mkOption {
type = types.str;
};
};
ipv6 = {
enable = mkOption {
type = types.bool;
default = options.tf.ipv6.address.isDefined;
};
address = mkOption {
type = types.str;
};
};
};
prefix = mkOption {
type = types.nullOr types.str;
};
subdomain = mkOption {
type = types.nullOr types.str;
};
domain = mkOption {
type = types.nullOr types.str;
default = "${config.subdomain}.${cfg.dns.domain}";
};
target = mkOption {
type = types.nullOr types.str;
default = "${config.domain}.";
};
out = {
identifierList = mkOption {
type = types.listOf types.str;
default = optionals config.enable (singleton config.domain ++ config.out.addressList);
};
addressList = mkOption {
type = types.listOf types.str;
default = optionals config.enable (concatMap (i: optional i.enable i.address) [ config.nixos.ipv4 config.nixos.ipv6 ]);
};
};
};
}));
};
extraCerts = mkOption {
type = types.attrsOf types.str;
default = { };
};
tf = {
enable = mkEnableOption "Was the system provisioned by terraform?";
ipv4_attr = mkOption {
type = types.nullOr types.str;
default = null;
};
ipv6_attr = mkOption {
type = types.nullOr types.str;
default = null;
};
};
dns = {
enable = mkEnableOption "Do you want DNS to be semi-managed through this module?";
isRoot = mkEnableOption "Is this system supposed to be the @ for the domain?";
email = mkOption {
type = types.nullOr types.str;
};
zone = mkOption {
type = types.nullOr types.str;
};
domain = mkOption {
type = types.nullOr types.str;
};
};
};
config =
let
networks = cfg.addresses;
networksWithDomains = filterAttrs (_: v: v.enable) networks;
in
mkIf cfg.enable {
lib.kw.virtualHostGen = args: virtualHostGen ({ inherit config; } // args);
network = {
dns = {
domain = builtins.substring 0 ((builtins.stringLength cfg.dns.zone) - 1) cfg.dns.zone;
};
addresses = lib.mkMerge [
(mkIf (!cfg.tf.enable) (genAttrs [ "private" "public" "yggdrasil" ] (network: {
tf = {
ipv4.address = mkIf (cfg.addresses.${network}.nixos.ipv4.enable) cfg.addresses.${network}.nixos.ipv4.address;
ipv6.address = mkIf (cfg.addresses.${network}.nixos.ipv6.enable) cfg.addresses.${network}.nixos.ipv6.address;
};
})))
(mkIf cfg.tf.enable (genAttrs ["yggdrasil" ] (network: {
tf = {
ipv4.address = mkIf (cfg.addresses.${network}.nixos.ipv4.enable) cfg.addresses.${network}.nixos.ipv4.address;
ipv6.address = mkIf (cfg.addresses.${network}.nixos.ipv6.enable) cfg.addresses.${network}.nixos.ipv6.address;
};
})))
(mkIf cfg.tf.enable {
public = {
tf = {
ipv4.address = mkIf (cfg.tf.ipv4_attr != null) (tf.resources.${config.networking.hostName}.refAttr cfg.tf.ipv4_attr);
ipv6.address = mkIf (cfg.tf.ipv6_attr != null) (tf.resources.${config.networking.hostName}.refAttr cfg.tf.ipv6_attr);
};
nixos = {
ipv4.selfaddress = mkIf (tf.state.enable && cfg.tf.ipv4_attr != null) (tf.resources.${config.networking.hostName}.getAttr cfg.tf.ipv4_attr);
ipv6.selfaddress = mkIf (tf.state.enable && cfg.tf.ipv6_attr != null) (tf.resources.${config.networking.hostName}.getAttr cfg.tf.ipv6_attr);
ipv4.address = mkIf (tf.state.resources ? ${tf.resources.${config.networking.hostName}.out.reference} && cfg.tf.ipv4_attr != null) (tf.resources.${config.networking.hostName}.importAttr cfg.tf.ipv4_attr);
ipv6.address = mkIf (tf.state.resources ? ${tf.resources.${config.networking.hostName}.out.reference} && cfg.tf.ipv6_attr != null) (tf.resources.${config.networking.hostName}.importAttr cfg.tf.ipv6_attr);
};
};
})
({
private = {
prefix = "int";
subdomain = "${config.networking.hostName}.${cfg.addresses.private.prefix}";
};
yggdrasil = {
enable = cfg.yggdrasil.enable;
prefix = "ygg";
subdomain = "${config.networking.hostName}.${cfg.addresses.yggdrasil.prefix}";
};
public = {
subdomain = config.networking.hostName;
};
})
(mkIf cfg.yggdrasil.enable {
yggdrasil.nixos.ipv6.address = cfg.yggdrasil.address;
})
];
};
networking.domain = mkDefault (if cfg.addresses.public.enable then cfg.dns.domain
else if cfg.addresses.private.enable then "${cfg.addresses.private.prefix}.${cfg.dns.domain}" else "");
deploy.tf.dns.records =
let
recordsV4 = mapAttrs'
(n: v:
nameValuePair "node_${n}_${config.networking.hostName}_v4" {
inherit (v.tf.ipv4) enable;
inherit (cfg.dns) zone;
domain = v.subdomain;
a = { inherit (v.tf.ipv4) address; };
})
networksWithDomains;
recordsV6 = mapAttrs'
(n: v:
nameValuePair "node_${n}_${config.networking.hostName}_v6" {
inherit (v.tf.ipv6) enable;
inherit (cfg.dns) zone;
domain = v.subdomain;
aaaa = { inherit (v.tf.ipv6) address; };
})
networksWithDomains;
in
mkMerge (map (record: mkIf cfg.dns.enable record) [
recordsV4
recordsV6
(mkIf cfg.dns.isRoot {
"node_root_${config.networking.hostName}_v4" = {
inherit (cfg.addresses.public) enable;
inherit (cfg.dns) zone;
a = { inherit (cfg.addresses.public.tf.ipv4) address; };
};
"node_root_${config.networking.hostName}_v6" = {
inherit (cfg.addresses.public) enable;
inherit (cfg.dns) zone;
aaaa = { inherit (cfg.addresses.public.tf.ipv6) address; };
};
})
]);
security.acme.certs = mkMerge (map (cert: mkIf cfg.dns.enable cert) [
(mkIf config.services.nginx.enable (mapAttrs'
(n: v:
nameValuePair "${n}_${config.networking.hostName}" {
inherit (v) domain;
dnsProvider = "rfc2136";
credentialsFile = config.secrets.files.dns_creds.path;
group = mkDefault "nginx";
})
networksWithDomains))
(mapAttrs'
(n: v:
nameValuePair "${n}" {
domain = v;
dnsProvider = "rfc2136";
credentialsFile = config.secrets.files.dns_creds.path;
group = mkDefault "nginx";
})
cfg.extraCerts)
]);
services.nginx.virtualHosts = mkMerge (map (host: mkIf cfg.dns.enable host) [
(mkIf config.services.nginx.enable (mapAttrs'
(n: v:
nameValuePair v.domain {
useACMEHost = "${n}_${config.networking.hostName}";
forceSSL = true;
})
networksWithDomains))
(mapAttrs'
(n: v:
nameValuePair v {
useACMEHost = "${n}";
forceSSL = true;
})
cfg.extraCerts)
]);
_module.args = { inherit (config.lib) kw; };
};
}

131
modules/nixos/nftables.nix Normal file
View file

@ -0,0 +1,131 @@
{ pkgs, lib, config, modulesPath, ... }:
let
fwcfg = config.networking.firewall;
cfg = config.networking.nftables;
doDocker = config.virtualisation.docker.enable && cfg.generateDockerRules;
mkPorts = cond: ports: ranges: action: let
portStrings = (map (range: "${toString range.from}-${toString range.to}") ranges)
++ (map toString ports);
in lib.optionalString (portStrings != []) ''
${cond} dport { ${lib.concatStringsSep ", " portStrings} } ${action}
'';
ruleset = ''
table inet filter {
chain input {
type filter hook input priority filter
policy ${cfg.inputPolicy}
icmpv6 type { echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, packet-too-big } accept
icmp type echo-request accept
ct state invalid drop
ct state established,related accept
iifname { ${
lib.concatStringsSep "," (["lo"] ++ fwcfg.trustedInterfaces)
} } accept
${mkPorts "tcp" fwcfg.allowedTCPPorts fwcfg.allowedTCPPortRanges "accept"}
${mkPorts "udp" fwcfg.allowedUDPPorts fwcfg.allowedUDPPortRanges "accept"}
${
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: ifcfg:
mkPorts "iifname ${name} tcp" ifcfg.allowedTCPPorts ifcfg.allowedTCPPortRanges "accept"
+ mkPorts "iifname ${name} udp" ifcfg.allowedUDPPorts ifcfg.allowedUDPPortRanges "accept"
) fwcfg.interfaces)
}
# DHCPv6
ip6 daddr fe80::/64 udp dport 546 accept
${cfg.extraInput}
counter
}
chain output {
type filter hook output priority filter
policy ${cfg.outputPolicy}
${cfg.extraOutput}
counter
}
chain forward {
type filter hook forward priority filter
policy ${cfg.forwardPolicy}
${lib.optionalString doDocker ''
oifname docker0 ct state invalid drop
oifname docker0 ct state established,related accept
iifname docker0 accept
''}
${cfg.extraForward}
counter
}
}
${lib.optionalString doDocker ''
table ip nat {
chain docker-postrouting {
type nat hook postrouting priority 10
iifname docker0 masquerade
}
}
''}
${cfg.extraConfig}
'';
in {
options = with lib; {
networking.nftables = {
extraConfig = mkOption {
type = types.lines;
default = "";
};
extraInput = mkOption {
type = types.lines;
default = "";
};
extraOutput = mkOption {
type = types.lines;
default = "";
};
extraForward = mkOption {
type = types.lines;
default = "";
};
inputPolicy = mkOption {
type = types.str;
default = "drop";
};
outputPolicy = mkOption {
type = types.str;
default = "accept";
};
forwardPolicy = mkOption {
type = types.str;
default = "accept";
};
generateDockerRules = mkOption {
type = types.bool;
default = true;
};
};
};
config = lib.mkIf cfg.enable {
networking.firewall.enable = false;
networking.nftables = {
inherit ruleset;
};
virtualisation.docker = lib.mkIf doDocker {
extraOptions = "--iptables=false";
};
};
}

View file

@ -0,0 +1,51 @@
{ config, lib, ... }:
with lib;
let
cfg = config.networking.policyrouting;
ruleOpts = { ... }: {
options = {
prio = mkOption {
type = types.int;
};
rule = mkOption {
type = types.str;
};
};
};
in
{
options = {
networking.policyrouting = {
enable = mkEnableOption "Declarative Policy-Routing";
rules = mkOption {
type = with types; listOf (submodule ruleOpts);
default = [ ];
};
rules6 = mkOption {
type = with types; listOf (submodule ruleOpts);
default = [ ];
};
rules4 = mkOption {
type = with types; listOf (submodule ruleOpts);
default = [ ];
};
};
};
config = mkIf cfg.enable {
networking.policyrouting.rules = [
{ rule = "lookup main"; prio = 32000; }
];
networking.localCommands = ''
set -x
ip -6 rule flush
ip -4 rule flush
${concatMapStringsSep "\n" ({ prio, rule }: "ip -6 rule add ${rule} prio ${toString prio}") (cfg.rules ++ cfg.rules6)}
${concatMapStringsSep "\n" ({ prio, rule }: "ip -4 rule add ${rule} prio ${toString prio}") (cfg.rules ++ cfg.rules4)}
'';
};
}

57
modules/nixos/secrets.nix Normal file
View file

@ -0,0 +1,57 @@
{ config, lib, meta, ... }:
with lib;
let
secretType = types.submodule ({ name, ... }: {
options = {
path = mkOption { type = types.str; };
field = mkOption {
type = types.str;
default = "";
};
};
});
repoSecretType = types.submodule ({ name, ... }: {
options = {
source = mkOption {
type = types.path;
};
text = mkOption {
type = types.str;
};
};
});
mcfg = meta.kw.secrets;
cfg = config.kw.secrets;
in
{
options.kw = {
secrets = {
variables = mkOption {
type = types.attrsOf secretType;
default = { };
};
repo = mkOption {
type = types.attrsOf repoSecretType;
default = { };
};
};
};
config = lib.mkMerge [
{
kw.secrets.variables = lib.mkMerge (mapAttrsToList (username: user: user.kw.secrets.variables) config.home-manager.users);
}
(mkIf (cfg.variables != { }) {
deploy.tf.variables = mapAttrs'
(name: content:
nameValuePair name ({
value.shellCommand = "${mcfg.command} ${content.path}" + optionalString (content.field != "") " -f ${content.field}";
type = "string";
sensitive = true;
})
)
cfg.variables;
})
];
}

145
modules/nixos/yggdrasil.nix Normal file
View file

@ -0,0 +1,145 @@
{ config, meta, lib, pkgs, ... }:
with lib;
let
cfg = config.network.yggdrasil;
calcAddr = pubkey: lib.readFile (pkgs.runCommandNoCC "calcaddr-${pubkey}" { } ''
echo '{ SigningPublicKey: "${pubkey}" }' | ${config.services.yggdrasil.package}/bin/yggdrasil -useconf -address | tr -d '\n' > $out
'').outPath;
in
{
options.network.yggdrasil = {
enable = mkEnableOption "Enable the yggdrasil-based private hexnet";
pubkey = mkOption {
type = types.str;
description = "Public key of this node";
};
address = mkOption {
type = types.str;
#description = "Main Yggdrasil address. Set automatically";
#default = calcAddr cfg.signingPubkey;
default = "";
};
trust = mkOption {
type = types.bool;
description = "Open Firewall completely for the network";
default = false;
};
listen = {
enable = mkOption {
type = types.bool;
description = "Allow other hosts in the network to connect directly";
default = false;
};
endpoints = mkOption {
type = types.listOf types.str;
description = "Endpoints to listen on";
default = [ ];
};
};
tunnel = {
localV6 = mkOption {
type = types.listOf types.str;
description = "v6 subnets to expose";
default = [ ];
};
localV4 = mkOption {
type = types.listOf types.str;
description = "v4 subnets to expose";
default = [ ];
};
remoteV6 = mkOption {
type = types.attrsOf types.str;
description = "Extra v6 subnets to route";
default = { };
};
remoteV4 = mkOption {
type = types.attrsOf types.str;
description = "Extra v4 subnets to route";
default = { };
};
};
extra = {
pubkeys = mkOption {
type = types.attrsOf types.str;
description = "Additional hosts to allow into the network. Keys won't be added to definition host.";
default = { };
example = { host = "0000000000000000000000000000000000000000000000000000000000000000"; };
};
addresses = mkOption {
type = types.attrsOf types.str;
internal = true;
default = mapAttrs (_: c: calcAddr c) cfg.extra.pubkeys;
};
localV6 = mkOption {
type = types.listOf types.str;
description = "v6 subnets to expose, but not route";
default = [ ];
};
localV4 = mkOption {
type = types.listOf types.str;
description = "v4 subnets to expose, but not route";
default = [ ];
};
};
extern = {
pubkeys = mkOption {
type = types.attrsOf types.str;
description = "Additional hosts to allow into the network. Keys won't be added to definition host.";
default = { };
example = { host = "0000000000000000000000000000000000000000000000000000000000000000"; };
};
endpoints = mkOption {
type = types.listOf types.str;
description = "Endpoints to listen on";
default = [ ];
};
};
};
config = mkIf cfg.enable (
let
yggConfigs = filter
(
c: c.enable && (cfg.pubkey != c.pubkey)
)
(
mapAttrsToList (_: node: node.network.yggdrasil or { enable = false; pubkey = null; }) meta.network.nodes.nixos
);
pubkeys = flatten ((filter (n: n != "0000000000000000000000000000000000000000000000000000000000000000") (attrValues cfg.extern.pubkeys)) ++ (map (c: [ c.pubkey ] ++ (attrValues c.extra.pubkeys)) yggConfigs));
in
{
assertions = [
{
assertion = !(cfg.listen.enable && (cfg.listen.endpoints == [ ]));
message = "Specify network.yggdrasil.listen.endpoints";
}
];
networking.firewall.trustedInterfaces = mkIf cfg.trust [ "yggdrasil" ];
services.yggdrasil = {
enable = true;
persistentKeys = true;
config = {
AllowedPublicKeys = pubkeys;
IfName = "yggdrasil";
Listen = cfg.listen.endpoints;
Peers = lib.flatten (cfg.extern.endpoints ++ (map (c: c.listen.endpoints) (filter (c: c.listen.enable) yggConfigs)));
};
};
system.build.yggdrasilTemplate =
let
json = builtins.toJSON {
inherit (config.services.yggdrasil.config) Peers SessionFirewall TunnelRouting;
PublicKey = "";
PrivateKey = "";
};
in
pkgs.runCommandNoCC "yggdrasil-template.json" { }
"echo '${json}' | ${config.services.yggdrasil.package}/bin/yggdrasil -useconf -normaliseconf > $out";
}
);
}