This commit is contained in:
2024-07-29 15:39:54 +03:00
parent 3da42ead11
commit 9ea80639a3
51 changed files with 2040 additions and 1758 deletions

View File

@@ -3,11 +3,25 @@
lib,
pkgs,
...
}: let
}:
let
cfg = config.zfs-root.boot;
inherit (lib) mkIf types mkDefault mkOption mkMerge strings;
inherit (builtins) head toString map tail;
in {
inherit (lib)
mkIf
types
mkDefault
mkOption
mkMerge
strings
;
inherit (builtins)
head
toString
map
tail
;
in
{
options.zfs-root.boot = {
enable = mkOption {
description = "Enable root on ZFS support";
@@ -17,9 +31,10 @@ in {
devNodes = mkOption {
description = "Specify where to discover ZFS pools";
type = types.str;
apply = x:
assert (strings.hasSuffix "/" x
|| abort "devNodes '${x}' must have trailing slash!"); x;
apply =
x:
assert (strings.hasSuffix "/" x || abort "devNodes '${x}' must have trailing slash!");
x;
default = "/dev/disk/by-id/";
};
bootDevices = mkOption {
@@ -28,11 +43,15 @@ in {
};
availableKernelModules = mkOption {
type = types.nonEmptyListOf types.str;
default = ["uas" "nvme" "ahci"];
default = [
"uas"
"nvme"
"ahci"
];
};
kernelParams = mkOption {
type = types.listOf types.str;
default = [];
default = [ ];
};
immutable = mkOption {
description = "Enable root on ZFS immutable root support";
@@ -62,7 +81,7 @@ in {
};
authorizedKeys = mkOption {
type = types.listOf types.str;
default = [];
default = [ ];
};
};
};
@@ -77,7 +96,9 @@ in {
};
}
(mkIf (!cfg.immutable) {
zfs-root.fileSystems.datasets = {"rpool/nixos/root" = "/";};
zfs-root.fileSystems.datasets = {
"rpool/nixos/root" = "/";
};
})
(mkIf cfg.immutable {
zfs-root.fileSystems = {
@@ -100,32 +121,25 @@ in {
})
{
zfs-root.fileSystems = {
efiSystemPartitions =
map (diskName: diskName + cfg.partitionScheme.efiBoot)
cfg.bootDevices;
efiSystemPartitions = map (diskName: diskName + cfg.partitionScheme.efiBoot) cfg.bootDevices;
swapPartitions =
if cfg.partitionScheme ? swap
then map (diskName: diskName + cfg.partitionScheme.swap) cfg.bootDevices
else [];
if cfg.partitionScheme ? swap then
map (diskName: diskName + cfg.partitionScheme.swap) cfg.bootDevices
else
[ ];
};
boot = {
initrd.availableKernelModules = cfg.availableKernelModules;
kernelParams = cfg.kernelParams;
supportedFilesystems = ["zfs"];
supportedFilesystems = [ "zfs" ];
zfs = {
devNodes = cfg.devNodes;
forceImportRoot = mkDefault false;
};
loader = {
efi = {
canTouchEfiVariables =
if cfg.removableEfi
then false
else true;
efiSysMountPoint =
"/boot/efis/"
+ (head cfg.bootDevices)
+ cfg.partitionScheme.efiBoot;
canTouchEfiVariables = if cfg.removableEfi then false else true;
efiSysMountPoint = "/boot/efis/" + (head cfg.bootDevices) + cfg.partitionScheme.efiBoot;
};
generationsDir.copyKernels = true;
grub = {
@@ -135,11 +149,13 @@ in {
copyKernels = true;
efiSupport = true;
zfsSupport = true;
extraInstallCommands = toString (map (diskName: ''
set -x
${pkgs.coreutils-full}/bin/cp -r ${config.boot.loader.efi.efiSysMountPoint}/EFI /boot/efis/${diskName}${cfg.partitionScheme.efiBoot}
set +x
'') (tail cfg.bootDevices));
extraInstallCommands = toString (
map (diskName: ''
set -x
${pkgs.coreutils-full}/bin/cp -r ${config.boot.loader.efi.efiSysMountPoint}/EFI /boot/efis/${diskName}${cfg.partitionScheme.efiBoot}
set +x
'') (tail cfg.bootDevices)
);
};
};
};

View File

@@ -3,20 +3,23 @@
lib,
pkgs,
...
}: let
}:
let
cfg = config.mj.base.btrfssnapshot;
in {
in
{
options.mj.base.btrfssnapshot = {
enable = lib.mkEnableOption "Enable btrfs snapshots";
subvolumes = lib.mkOption {
default = {};
type = with lib.types;
default = { };
type =
with lib.types;
attrsOf (submodule {
options = {
label = lib.mkOption {type = str;};
keep = lib.mkOption {type = int;};
refreshInterval = lib.mkOption {type = str;};
label = lib.mkOption { type = str; };
keep = lib.mkOption { type = int; };
refreshInterval = lib.mkOption { type = str; };
};
});
};
@@ -24,30 +27,22 @@ in {
config = lib.mkIf cfg.enable {
systemd = {
services =
lib.mapAttrs'
(
subvolume: params:
lib.nameValuePair
"btrfs-snapshot-${lib.strings.sanitizeDerivationName subvolume}"
{
description = "${params.label} btrfs snapshot for ${subvolume} (keep ${params.keep}";
serviceConfig.ExecStart = "${pkgs.btrfs-auto-snapshot} --verbose --label=${params.label} --keep=${params.keep} ${subvolume}";
}
);
services = lib.mapAttrs' (
subvolume: params:
lib.nameValuePair "btrfs-snapshot-${lib.strings.sanitizeDerivationName subvolume}" {
description = "${params.label} btrfs snapshot for ${subvolume} (keep ${params.keep}";
serviceConfig.ExecStart = "${pkgs.btrfs-auto-snapshot} --verbose --label=${params.label} --keep=${params.keep} ${subvolume}";
}
);
timers =
lib.mapAttrs'
(
subvolume: params:
lib.nameValuePair
"btrfs-snapshot-${lib.strings.sanitizeDerivationName subvolume}"
{
description = "${params.label} btrfs snapshot for ${subvolume}";
wantedBy = ["timers.target"];
timerConfig.OnCalendar = params.refreshInterval;
}
);
timers = lib.mapAttrs' (
subvolume: params:
lib.nameValuePair "btrfs-snapshot-${lib.strings.sanitizeDerivationName subvolume}" {
description = "${params.label} btrfs snapshot for ${subvolume}";
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = params.refreshInterval;
}
);
};
};
}

View File

@@ -4,9 +4,11 @@
pkgs,
myData,
...
}: let
}:
let
cfg = config.mj;
in {
in
{
imports = [
./boot
./btrfssnapshot
@@ -32,7 +34,7 @@ in {
description = "Time zone for this system";
};
username = lib.mkOption {type = str;};
username = lib.mkOption { type = str; };
skipPerf = lib.mkOption {
type = bool;
@@ -51,7 +53,7 @@ in {
kernelPackages = lib.mkDefault pkgs.linuxPackages;
supportedFilesystems = ["btrfs"];
supportedFilesystems = [ "btrfs" ];
};
nixpkgs.config.allowUnfree = true;
@@ -62,15 +64,15 @@ in {
mj.services.friendlyport.ports = [
{
subnets = [myData.subnets.tailscale.cidr];
tcp = [config.services.iperf3.port];
udp = [config.services.iperf3.port];
subnets = [ myData.subnets.tailscale.cidr ];
tcp = [ config.services.iperf3.port ];
udp = [ config.services.iperf3.port ];
}
];
i18n = {
defaultLocale = "en_US.UTF-8";
supportedLocales = ["all"];
supportedLocales = [ "all" ];
};
nix = {
@@ -80,8 +82,11 @@ in {
options = "--delete-older-than 14d";
};
settings = {
experimental-features = ["nix-command" "flakes"];
trusted-users = [cfg.username];
experimental-features = [
"nix-command"
"flakes"
];
trusted-users = [ cfg.username ];
};
};
@@ -95,7 +100,8 @@ in {
};
environment = {
systemPackages = with pkgs;
systemPackages =
with pkgs;
lib.mkMerge [
[
bc
@@ -165,7 +171,6 @@ in {
smartmontools
unixtools.xxd
bcachefs-tools
nixfmt-rfc-style
sqlite-interactive
# networking
@@ -204,7 +209,7 @@ in {
config.boot.kernelPackages.cpupower
config.boot.kernelPackages.vm-tools
]
(lib.mkIf (!cfg.skipPerf) [config.boot.kernelPackages.perf])
(lib.mkIf (!cfg.skipPerf) [ config.boot.kernelPackages.perf ])
];
};
@@ -233,7 +238,7 @@ in {
chrony = {
enable = true;
servers = ["time.cloudflare.com"];
servers = [ "time.cloudflare.com" ];
};
locate = {

View File

@@ -1,50 +1,60 @@
{
config,
lib,
...
}: let
{ config, lib, ... }:
let
cfg = config.zfs-root.fileSystems;
inherit (lib) types mkDefault mkOption mkMerge mapAttrsToList;
in {
inherit (lib)
types
mkDefault
mkOption
mkMerge
mapAttrsToList
;
in
{
options.zfs-root.fileSystems = {
datasets = mkOption {
description = "Set mountpoint for datasets";
type = types.attrsOf types.str;
default = {};
default = { };
};
bindmounts = mkOption {
description = "Set mountpoint for bindmounts";
type = types.attrsOf types.str;
default = {};
default = { };
};
efiSystemPartitions = mkOption {
description = "Set mountpoint for efi system partitions";
type = types.listOf types.str;
default = [];
default = [ ];
};
swapPartitions = mkOption {
description = "Set swap partitions";
type = types.listOf types.str;
default = [];
default = [ ];
};
};
config.fileSystems = mkMerge (mapAttrsToList (dataset: mountpoint: {
config.fileSystems = mkMerge (
mapAttrsToList (dataset: mountpoint: {
"${mountpoint}" = {
device = "${dataset}";
fsType = "zfs";
options = ["X-mount.mkdir" "noatime"];
options = [
"X-mount.mkdir"
"noatime"
];
neededForBoot = true;
};
})
cfg.datasets
}) cfg.datasets
++ mapAttrsToList (bindsrc: mountpoint: {
"${mountpoint}" = {
device = "${bindsrc}";
fsType = "none";
options = ["bind" "X-mount.mkdir" "noatime"];
options = [
"bind"
"X-mount.mkdir"
"noatime"
];
};
})
cfg.bindmounts
}) cfg.bindmounts
++ map (esp: {
"/boot/efis/${esp}" = {
device = "${config.zfs-root.boot.devNodes}${esp}";
@@ -58,15 +68,16 @@ in {
"X-mount.mkdir"
];
};
})
cfg.efiSystemPartitions);
config.swapDevices = mkDefault (map (swap: {
}) cfg.efiSystemPartitions
);
config.swapDevices = mkDefault (
map (swap: {
device = "${config.zfs-root.boot.devNodes}${swap}";
discardPolicy = mkDefault "both";
randomEncryption = {
enable = true;
allowDiscards = mkDefault true;
};
})
cfg.swapPartitions);
}) cfg.swapPartitions
);
}

View File

@@ -1,13 +1,10 @@
{ config, lib, ... }:
{
config,
lib,
...
}: {
options.mj.base.snapshot = {
enable = lib.mkEnableOption "Enable zfs snapshots";
mountpoints = lib.mkOption {
default = {};
default = { };
type = with lib.types; listOf str;
};
};
@@ -21,23 +18,23 @@
autosnap = true;
autoprune = true;
};
extraArgs = ["--verbose"];
datasets = let
fs_zfs = lib.filterAttrs (_: v: v.fsType == "zfs") config.fileSystems;
mountpoint2fs =
builtins.listToAttrs
(map (mountpoint: {
extraArgs = [ "--verbose" ];
datasets =
let
fs_zfs = lib.filterAttrs (_: v: v.fsType == "zfs") config.fileSystems;
mountpoint2fs = builtins.listToAttrs (
map (mountpoint: {
name = mountpoint;
value = builtins.getAttr mountpoint fs_zfs;
})
config.mj.base.snapshot.mountpoints);
s_datasets =
lib.mapAttrs' (_mountpoint: fs: {
}) config.mj.base.snapshot.mountpoints
);
s_datasets = lib.mapAttrs' (_mountpoint: fs: {
name = fs.device;
value = {use_template = ["prod"];};
})
mountpoint2fs;
in
value = {
use_template = [ "prod" ];
};
}) mountpoint2fs;
in
s_datasets;
};
};

View File

@@ -3,7 +3,8 @@
lib,
myData,
...
}: {
}:
{
config = {
services.openssh = {
enable = true;
@@ -13,9 +14,13 @@
};
};
programs.mosh.enable = true;
programs.ssh.knownHosts = let
sshAttrs = lib.genAttrs ["extraHostNames" "publicKey"] (_: null);
in
programs.ssh.knownHosts =
let
sshAttrs = lib.genAttrs [
"extraHostNames"
"publicKey"
] (_: null);
in
lib.mapAttrs (_name: builtins.intersectAttrs sshAttrs) myData.hosts;
};
}

View File

@@ -3,55 +3,58 @@
lib,
pkgs,
...
}: {
}:
{
# TODO:
# - assert postfix is configured
options.mj.base.unitstatus = with lib.types; {
enable = lib.mkEnableOption "alert by email on unit failure";
email = lib.mkOption {type = str;};
email = lib.mkOption { type = str; };
units = lib.mkOption {
type = listOf str;
default = [];
default = [ ];
};
};
config = lib.mkIf config.mj.base.unitstatus.enable {
systemd.services =
{
"unit-status-mail@" = let
# https://northernlightlabs.se/2014-07-05/systemd-status-mail-on-unit-failure.html
script = pkgs.writeShellScript "unit-status-mail" ''
set -e
MAILTO="${config.mj.base.unitstatus.email}"
UNIT=$1
EXTRA=""
for e in "''${@:2}"; do
EXTRA+="$e"$'\n'
done
UNITSTATUS=$(${pkgs.systemd}/bin/systemctl status "$UNIT" || :)
${pkgs.postfix}/bin/sendmail $MAILTO <<EOF
Subject:Status mail for unit: $UNIT
"unit-status-mail@" =
let
# https://northernlightlabs.se/2014-07-05/systemd-status-mail-on-unit-failure.html
script = pkgs.writeShellScript "unit-status-mail" ''
set -e
MAILTO="${config.mj.base.unitstatus.email}"
UNIT=$1
EXTRA=""
for e in "''${@:2}"; do
EXTRA+="$e"$'\n'
done
UNITSTATUS=$(${pkgs.systemd}/bin/systemctl status "$UNIT" || :)
${pkgs.postfix}/bin/sendmail $MAILTO <<EOF
Subject:Status mail for unit: $UNIT
Status report for unit: $UNIT
$EXTRA
Status report for unit: $UNIT
$EXTRA
$UNITSTATUS
EOF
$UNITSTATUS
EOF
echo -e "Status mail sent to: $MAILTO for unit: $UNIT"
'';
in {
description = "Send an email on unit failure";
serviceConfig = {
Type = "simple";
ExecStart = ''${script} "%i" "Hostname: %H" "Machine ID: %m" "Boot ID: %b" '';
echo -e "Status mail sent to: $MAILTO for unit: $UNIT"
'';
in
{
description = "Send an email on unit failure";
serviceConfig = {
Type = "simple";
ExecStart = ''${script} "%i" "Hostname: %H" "Machine ID: %m" "Boot ID: %b" '';
};
};
};
}
// lib.genAttrs config.mj.base.unitstatus.units (
unit: {
unitConfig = {OnFailure = "unit-status-mail@${unit}.service";};
}
);
// lib.genAttrs config.mj.base.unitstatus.units (unit: {
unitConfig = {
OnFailure = "unit-status-mail@${unit}.service";
};
});
};
}

View File

@@ -3,7 +3,8 @@
lib,
myData,
...
}: let
}:
let
cfg = config.mj.base.users;
props = with lib.types; {
hashedPasswordFile = lib.mkOption {
@@ -21,10 +22,11 @@
extraGroups = lib.mkOption {
type = listOf str;
default = [];
default = [ ];
};
};
in {
in
{
options.mj.base.users = with lib.types; {
enable = lib.mkEnableOption "enable motiejus and root";
devTools = lib.mkOption {
@@ -44,33 +46,37 @@ in {
mutableUsers = false;
users = {
${config.mj.username} =
{
isNormalUser = true;
extraGroups = ["wheel" "dialout" "video"] ++ cfg.user.extraGroups;
uid = myData.uidgid.motiejus;
openssh.authorizedKeys.keys = let
${config.mj.username} = {
isNormalUser = true;
extraGroups = [
"wheel"
"dialout"
"video"
] ++ cfg.user.extraGroups;
uid = myData.uidgid.motiejus;
openssh.authorizedKeys.keys =
let
fqdn = "${config.networking.hostName}.${config.networking.domain}";
in
lib.mkMerge [
[
myData.people_pubkeys.motiejus
myData.people_pubkeys.motiejus_work
]
lib.mkMerge [
[
myData.people_pubkeys.motiejus
myData.people_pubkeys.motiejus_work
]
(lib.mkIf (builtins.hasAttr fqdn myData.hosts) [
("from=\"127.0.0.1,::1\" " + myData.hosts.${fqdn}.publicKey)
])
];
}
// lib.filterAttrs (n: v: n != "extraGroups" && v != null) cfg.user or {};
(lib.mkIf (builtins.hasAttr fqdn myData.hosts) [
(''from="127.0.0.1,::1" '' + myData.hosts.${fqdn}.publicKey)
])
];
} // lib.filterAttrs (n: v: n != "extraGroups" && v != null) cfg.user or { };
root = lib.filterAttrs (_: v: v != null) cfg.root;
};
};
home-manager.useGlobalPkgs = true;
home-manager.users.${config.mj.username} = {pkgs, ...}:
home-manager.users.${config.mj.username} =
{ pkgs, ... }:
import ../../../shared/home {
inherit lib;
inherit pkgs;

View File

@@ -1,8 +1,5 @@
{ config, lib, ... }:
{
config,
lib,
...
}: {
options.mj.base.zfs = with lib.types; {
enable = lib.mkEnableOption "Enable common zfs options";
};
@@ -16,6 +13,6 @@
expandOnBoot = "all";
};
mj.base.unitstatus.units = ["zfs-scrub"];
mj.base.unitstatus.units = [ "zfs-scrub" ];
};
}

View File

@@ -3,7 +3,8 @@
lib,
pkgs,
...
}: let
}:
let
mkPreHook = zfs_name: i: ''
set -x
sleep ${toString i}
@@ -15,99 +16,103 @@
"$RUNTIME_DIRECTORY/snapshot"
cd "$RUNTIME_DIRECTORY/snapshot"
'';
in {
in
{
options.mj.base.zfsborg = with lib.types; {
enable = lib.mkEnableOption "backup zfs snapshots with borg";
passwordPath = lib.mkOption {type = str;};
passwordPath = lib.mkOption { type = str; };
sshKeyPath = lib.mkOption {
type = nullOr path;
default = null;
};
dirs = lib.mkOption {
default = {};
default = { };
type = listOf (submodule {
options = {
mountpoint = lib.mkOption {type = path;};
repo = lib.mkOption {type = str;};
paths = lib.mkOption {type = listOf str;};
mountpoint = lib.mkOption { type = path; };
repo = lib.mkOption { type = str; };
paths = lib.mkOption { type = listOf str; };
patterns = lib.mkOption {
type = listOf str;
default = [];
default = [ ];
};
prune = lib.mkOption {
type = anything;
default = {};
default = { };
};
backup_at = lib.mkOption {type = str;};
backup_at = lib.mkOption { type = str; };
};
});
};
};
config = with config.mj.base.zfsborg;
config =
with config.mj.base.zfsborg;
lib.mkIf enable {
systemd.services = lib.listToAttrs (lib.imap0 (
i: attr: let
systemd.services = lib.listToAttrs (
lib.imap0 (
i: attr:
let
svcName = "borgbackup-job-${lib.strings.sanitizeDerivationName attr.mountpoint}-${toString i}";
in
lib.nameValuePair svcName {
serviceConfig.RuntimeDirectory = svcName;
}
)
dirs);
lib.nameValuePair svcName { serviceConfig.RuntimeDirectory = svcName; }
) dirs
);
services.borgbackup.jobs = builtins.listToAttrs (
lib.imap0 (
i: attrs: let
i: attrs:
let
mountpoint = builtins.getAttr "mountpoint" attrs;
fs = builtins.getAttr mountpoint config.fileSystems;
in
assert fs.fsType == "zfs";
assert lib.assertMsg
config.mj.base.unitstatus.enable
assert fs.fsType == "zfs";
assert lib.assertMsg config.mj.base.unitstatus.enable
"config.mj.base.unitstatus.enable must be true";
lib.nameValuePair
"${lib.strings.sanitizeDerivationName mountpoint}-${toString i}"
({
inherit (attrs) repo paths;
lib.nameValuePair "${lib.strings.sanitizeDerivationName mountpoint}-${toString i}" (
{
inherit (attrs) repo paths;
doInit = true;
encryption = {
mode = "repokey-blake2";
passCommand = "cat ${config.mj.base.zfsborg.passwordPath}";
};
extraArgs = "--remote-path=borg1";
compression = "auto,zstd,10";
extraCreateArgs = "--chunker-params buzhash,10,23,16,4095";
startAt = attrs.backup_at;
preHook = mkPreHook fs.device i;
prune.keep = {
within = "1d";
daily = 7;
weekly = 4;
monthly = 3;
};
environment =
{
BORG_HOST_ID = let
h = config.networking;
in "${h.hostName}.${h.domain}@${h.hostId}";
}
// lib.optionalAttrs (sshKeyPath != null) {
BORG_RSH = ''ssh -i "${config.mj.base.zfsborg.sshKeyPath}"'';
};
doInit = true;
encryption = {
mode = "repokey-blake2";
passCommand = "cat ${config.mj.base.zfsborg.passwordPath}";
};
extraArgs = "--remote-path=borg1";
compression = "auto,zstd,10";
extraCreateArgs = "--chunker-params buzhash,10,23,16,4095";
startAt = attrs.backup_at;
preHook = mkPreHook fs.device i;
prune.keep = {
within = "1d";
daily = 7;
weekly = 4;
monthly = 3;
};
environment =
{
BORG_HOST_ID =
let
h = config.networking;
in
"${h.hostName}.${h.domain}@${h.hostId}";
}
// lib.optionalAttrs (attrs ? patterns) {inherit (attrs) patterns;}
// lib.optionalAttrs (attrs ? prune) {inherit (attrs) prune;})
)
dirs
// lib.optionalAttrs (sshKeyPath != null) {
BORG_RSH = ''ssh -i "${config.mj.base.zfsborg.sshKeyPath}"'';
};
}
// lib.optionalAttrs (attrs ? patterns) { inherit (attrs) patterns; }
// lib.optionalAttrs (attrs ? prune) { inherit (attrs) prune; }
)
) dirs
);
mj.base.unitstatus.units = let
sanitized = map lib.strings.sanitizeDerivationName (lib.catAttrs "mountpoint" dirs);
in
mj.base.unitstatus.units =
let
sanitized = map lib.strings.sanitizeDerivationName (lib.catAttrs "mountpoint" dirs);
in
lib.imap0 (i: name: "borgbackup-job-${name}-${toString i}") sanitized;
};
}