Commit 9031382
Changed files (15)
modules
core
disk
hosts
cyrene
hyacine
kevin
mobius
tribios
secret
modules/core/openssh.nix
@@ -2,7 +2,21 @@
den,
lib,
...
-}: {
+}: let
+ hostKeys = [
+ {
+ path = "/etc/ssh/ssh_host_ed25519_key";
+ type = "ed25519";
+ }
+ {
+ path = "/etc/ssh/ssh_host_rsa_key";
+ type = "rsa";
+ bit = 4096;
+ }
+ ];
+
+ hostKeyFiles = builtins.concatMap (key: [key.path] ++ ["${key.path}.pub"]) hostKeys;
+in {
den.aspects.core.includes = [den.aspects.core.openssh];
den.aspects.core.openssh = {
@@ -21,22 +35,33 @@
};
};
+ persist = {
+ files =
+ map (file: {
+ inherit file;
+ mode = "06${
+ if lib.strings.hasSuffix ".pub" file
+ then "44"
+ else "00"
+ }";
+ })
+ hostKeyFiles;
+ };
+ persistHome = {
+ directories = [
+ {
+ directory = ".ssh";
+ mode = "0700";
+ }
+ ];
+ };
+
nixos = {host, ...}: {
services.openssh = {
enable = host.settings.core.openssh.server.enable;
ports = [host.settings.core.openssh.server.port];
generateHostKeys = true;
- hostKeys = [
- {
- path = "/etc/ssh/ssh_host_ed25519_key";
- type = "ed25519";
- }
- {
- path = "/etc/ssh/ssh_host_rsa_key";
- type = "rsa";
- bit = 4096;
- }
- ];
+ inherit hostKeys;
settings = {
PermitRootLogin = "prohibit-password";
PasswordAuthentication = false;
@@ -55,8 +80,10 @@
environment.enableAllTerminfo = host.settings.core.openssh.server.enable;
};
- user = {user, ...}: {
- openssh.authorizedKeys.keys = user.identity.sshKeys;
+ provides.to-users = {
+ user = {user, ...}: {
+ openssh.authorizedKeys.keys = user.identity.sshKeys;
+ };
};
};
}
modules/disk/preservation/btrfs.nix
@@ -0,0 +1,94 @@
+{
+ den,
+ lib,
+ ...
+}: {
+ den.aspects.disk.preservation.btrfs = {
+ settings.host = {
+ partId = lib.mkOption {
+ type = lib.types.str;
+ default = "";
+ description = ''
+ Disk part id (e.g., "/dev/disk/by-partuuid/...").
+ If not set, auto-detect when aspects.disk.disko is included.
+ '';
+ };
+ preserveTime = lib.mkOption {
+ type = lib.types.ints.positive;
+ default = 30;
+ description = ''
+ Time old root/home subvolumes preserved.
+ '';
+ };
+ };
+
+ nixos = {host, ...}: let
+ cfg = host.settings.disk.preservation.btrfs;
+
+ partDevice =
+ if cfg.partId != ""
+ then
+ if lib.hasPrefix "/dev/" cfg.partId
+ then cfg.partId
+ else "/dev/disk/by-partuuid/" + cfg.partId
+ else if (host.hasAspect den.aspects.disk.disko)
+ then "/dev/disk/by-partlabel/disk-main-nixos"
+ else abort "Host not included disk.disko aspect, Please set settings.disk.preservation.btrfs.partId.";
+
+ partDeviceUnit =
+ lib.removePrefix "-" (
+ lib.replaceStrings ["-" "/"] ["\\x2d" "-"] partDevice
+ )
+ + ".device";
+
+ script = subvol: ''
+ mkdir -p /mnt
+
+ mount ${partDevice} /mnt
+
+ mkdir -p /mnt/${subvol}_old
+ timestamp=$(date "+%Y-%m-%-d_%H:%M:%S")
+ rm -rf "/mnt/${subvol}_old/$timestamp"
+ mv /mnt/${subvol} /mnt/${subvol}_old/$timestamp
+
+ delete_subvolume_recursively() {
+ IFS=$'\n'
+ for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
+ delete_subvolume_recursively "/mnt/$i"
+ done
+ btrfs subvolume delete "$1"
+ }
+
+ for i in $(find /mnt/${subvol}_old -maxdepth 1 -mtime +${toString cfg.preserveTime}); do
+ delete_subvolume_recursively "$i"
+ done
+
+ btrfs subvolume create /mnt/${subvol}
+ umount /mnt
+ '';
+ in {
+ boot.initrd.systemd.services = lib.mkIf (host.hasAspect den.aspects.disk.btrfs) {
+ rollback-btrfs-root = {
+ description = "Rollback and clean old btrfs root subvolumes";
+ wantedBy = ["initrd.target"];
+ before = ["sysroot.mount"];
+ after = [partDeviceUnit];
+ requires = [partDeviceUnit];
+ unitConfig.DefaultDependencies = false;
+ serviceConfig.Type = "oneshot";
+ script = script "root";
+ };
+ rollback-btrfs-home = {
+ description = "Rollback and clean old btrfs home subvolumes";
+ wantedBy = ["initrd.target"];
+ before = ["home.mount"];
+ after = [partDeviceUnit "rollback-btrfs-root.service"];
+ requires = [partDeviceUnit];
+ unitConfig.DefaultDependencies = false;
+ serviceConfig.Type = "oneshot";
+ script = script "home";
+ };
+ };
+ };
+ };
+}
modules/disk/preservation/default.nix
@@ -0,0 +1,94 @@
+{
+ den,
+ lib,
+ ...
+}: let
+ # A single preserved file or directory entry, shaped like
+ # `preservation.preserveAt."/…".files` / `.directories` entries.
+ # Declared fields are checked, unknown fields pass through.
+ mkEntryType = kind:
+ lib.types.submodule {
+ freeformType = lib.types.attrsOf lib.types.anything;
+ options.${kind} = lib.mkOption {
+ type = lib.types.str;
+ };
+ };
+
+ directoryEntry = mkEntryType "directory";
+ fileEntry = mkEntryType "file";
+
+ # Carrier options in the shape of `preservation.preserveAt."/…".users.*`:
+ #
+ # persistence."<name>" = {
+ # directories = [ … ];
+ # files = [ … ];
+ # };
+ persistenceOptions = lib.types.attrsOf (lib.types.submodule {
+ options = {
+ directories = lib.mkOption {
+ type = lib.types.listOf (lib.types.coercedTo lib.types.str (d: {directory = d;}) directoryEntry);
+ default = [];
+ };
+ files = lib.mkOption {
+ type = lib.types.listOf (lib.types.coercedTo lib.types.str (f: {file = f;}) fileEntry);
+ default = [];
+ };
+ };
+ });
+in {
+ flake-file.inputs.preservation = {
+ url = "github:nix-community/preservation";
+ };
+
+ den.aspects.disk.preservation = let
+ homeFiles = {
+ files = [
+ ".bash_history"
+ ];
+ };
+ in {
+ includes = with den.aspects.disk.preservation; [
+ persist-collector
+
+ btrfs
+ ];
+
+ persist = {
+ directories = [
+ "/etc/NetworkManager/system-connections"
+ ];
+ files = [
+ "/root/.bash_history"
+ ];
+ };
+ cache = {
+ directories = [
+ "/var/lib/nixos"
+ "/srv"
+ ];
+ };
+ persistHome = homeFiles;
+
+ provides.to-users = {
+ # Re-emit persistHome at user scopes: the aspect itself only runs at
+ # host scope, so its own `persistHome` attribute alone would not land.
+ persistHome = homeFiles;
+
+ hjem = {lib, ...}: {
+ options.environment.persistence = lib.mkOption {
+ type = persistenceOptions;
+ default = {};
+ description = ''
+ Preservation-shaped persistence carriers for this hjem user,
+ translated into `preservation.preserveAt` on the host.
+ '';
+ };
+
+ config = {
+ environment.persistence.persist = {};
+ environment.persistence.cache = {};
+ };
+ };
+ };
+ };
+}
modules/disk/preservation/quirks.nix
@@ -0,0 +1,82 @@
+{inputs, ...}: let
+in {
+ den.quirks.persist.description = "Persistent directories/files collected from aspects (host)";
+ den.quirks.cache.description = "Cache directories/files collected from aspects (host)";
+ den.quirks.persistHome.description = "Persistent directories/files collected from aspects (hjem)";
+ den.quirks.cacheHome.description = "Cache directories/files collected from aspects (hjem)";
+
+ den.aspects.disk.preservation.persist-collector = {
+ nixos = {
+ persist,
+ cache,
+ lib,
+ config,
+ ...
+ }: let
+ mergePersist = entries: {
+ directories = lib.unique (lib.concatMap (e: e.directories or []) entries);
+ files = lib.unique (lib.concatMap (e: e.files or []) entries);
+ };
+
+ persistData = mergePersist persist;
+ cacheData = mergePersist cache;
+ persistFiles = persistData.files;
+ cacheFiles = cacheData.files;
+
+ # Merge per-user persistence collected from hjem scopes.
+ relayUsers = prefix: let
+ fromHjem = lib.filterAttrs (_: u: (u.environment.persistence or {}) ? ${prefix}) (config.hjem.users or {});
+ merge = userName: fromHjem.${userName}.environment.persistence.${prefix};
+ in
+ lib.mapAttrs (userName: _: merge userName) fromHjem;
+ in {
+ imports = [inputs.preservation.nixosModules.preservation];
+
+ preservation.enable = true;
+ preservation.preserveAt = {
+ "/persist" = {
+ commonMountOptions = ["x-gvfs-hide"];
+ directories = persistData.directories;
+ files = persistFiles;
+ users = relayUsers "persist";
+ };
+ "/cache" = {
+ commonMountOptions = ["x-gvfs-hide"];
+ directories = cacheData.directories;
+ files = cacheFiles;
+ users = relayUsers "cache";
+ };
+ };
+ };
+
+ provides.to-users = {
+ hjem = {
+ persistHome,
+ cacheHome,
+ lib,
+ config,
+ ...
+ }: let
+ _removeHomePrefix = path: lib.removePrefix config.directory path;
+ removeHomePrefix = path:
+ if lib.typeOf path == "string"
+ then _removeHomePrefix path
+ else path // {directory = _removeHomePrefix path.directory;};
+ removeHomePrefixes = paths: lib.map removeHomePrefix paths;
+ mergePersist = entries: {
+ directories = lib.unique (lib.concatMap (e: removeHomePrefixes (e.directories or [])) entries);
+ files = lib.unique (lib.concatMap (e: removeHomePrefixes (e.files or [])) entries);
+ };
+ in {
+ environment.persistence.persist = {
+ directories = (mergePersist persistHome).directories;
+ files = (mergePersist persistHome).files;
+ };
+ environment.persistence.cache = {
+ directories = (mergePersist cacheHome).directories;
+ files = (mergePersist cacheHome).files;
+ };
+ };
+ };
+ };
+}
modules/disk/btrfs-disko.nix
@@ -0,0 +1,156 @@
+{
+ lib,
+ den,
+ ...
+}: {
+ den.aspects.disk.btrfs-disko = {
+ includes = [
+ den.aspects.disk.disko
+ den.aspects.disk.btrfs
+ ];
+
+ settings.host = {
+ deviceId = lib.mkOption {
+ type = lib.types.str;
+ default = "";
+ description = ''
+ Disk device id (e.g., "ata-..." or "/dev/disk/by-id/...").
+ If not set, auto-detects a single non-USB disk via facter.
+ '';
+ };
+ swapSize = lib.mkOption {
+ type = lib.types.int;
+ default = 0;
+ description = "Size of swap in MiB, 0 disables swap.";
+ };
+ grubPart = lib.mkOption {
+ type = lib.types.nullOr lib.types.bool;
+ default = null;
+ description = "Whether to creat 1M part for GRUB. Set null to auto detect";
+ };
+ };
+
+ nixos = {
+ config,
+ host,
+ ...
+ }: let
+ cfg = host.settings.disk.btrfs-disko;
+
+ diskDevice =
+ if cfg.deviceId != ""
+ then
+ if lib.hasPrefix "/dev/" cfg.deviceId
+ then cfg.deviceId
+ else "/dev/disk/by-id/" + cfg.deviceId
+ else let
+ native-disks = builtins.filter (f: f.driver != "usb-storage") config.hardware.facter.report.hardware.disk;
+ disk-labels =
+ map (
+ disk:
+ builtins.head (
+ builtins.filter (f: builtins.substring 0 16 f == "/dev/disk/by-id/") disk.unix_device_names
+ )
+ )
+ native-disks;
+ in
+ if (builtins.length disk-labels == 1)
+ then (builtins.head disk-labels)
+ else
+ abort (
+ "Multiple disks found. Please set settings.disk.btrfs-disko.deviceId. Found: "
+ + toString disk-labels
+ );
+
+ defaultBtrfsOpts = [
+ "defaults"
+ "compress=zstd:1"
+ "ssd"
+ "discard=async"
+ ];
+ in {
+ disko.devices = {
+ disk = {
+ main = {
+ device = diskDevice;
+ type = "disk";
+ content = {
+ type = "gpt";
+ partitions = {
+ boot =
+ lib.mkIf
+ (
+ if cfg.grubPart == null
+ then !config.hardware.facter.detected.uefi.supported
+ else cfg.grubPart
+ )
+ {
+ label = "GRUB";
+ size = "1M";
+ type = "EF02";
+ priority = 0;
+ };
+ ESP = {
+ label = "boot";
+ name = "ESP";
+ size = "1G";
+ type = "EF00";
+ priority = 1;
+ content = {
+ type = "filesystem";
+ format = "vfat";
+ mountpoint = "/boot";
+ mountOptions = ["defaults" "umask=0077"];
+ };
+ };
+ nixos = {
+ size = "100%";
+ priority = 2;
+ content = {
+ type = "btrfs";
+ subvolumes =
+ {
+ "/root" = {
+ mountpoint = "/";
+ mountOptions = defaultBtrfsOpts ++ ["noatime"];
+ };
+ "/home" = {
+ mountpoint = "/home";
+ mountOptions = defaultBtrfsOpts ++ ["noatime"];
+ };
+ "/nix" = {
+ mountpoint = "/nix";
+ mountOptions = defaultBtrfsOpts ++ ["noatime"];
+ };
+ "/persist" = lib.mkIf (host.hasAspect den.aspects.disk.preservation) {
+ mountpoint = "/persist";
+ mountOptions = defaultBtrfsOpts;
+ };
+ "/cache" = lib.mkIf (host.hasAspect den.aspects.disk.preservation) {
+ mountpoint = "/cache";
+ mountOptions = defaultBtrfsOpts;
+ };
+ }
+ // lib.optionalAttrs (cfg.swapSize > 0) {
+ "@swap" = {
+ mountpoint = "/swap";
+ swap.swapfile.size = "${toString cfg.swapSize}M";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+
+ fileSystems = {
+ "/nix".neededForBoot = true;
+ "/home".neededForBoot = true;
+ "/persist".neededForBoot = true;
+ "/cache".neededForBoot = true;
+ };
+ };
+ };
+}
modules/disk/btrfs.nix
@@ -0,0 +1,12 @@
+{
+ den.aspects.disk.btrfs = {
+ nixos = {
+ boot.supportedFilesystems.btrfs = true;
+
+ services.btrfs.autoScrub = {
+ enable = true;
+ fileSystems = ["/"];
+ };
+ };
+ };
+}
modules/disk/disko.nix
@@ -0,0 +1,12 @@
+{inputs, ...}: {
+ flake-file.inputs.disko = {
+ url = "github:nix-community/disko";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ den.aspects.disk.disko = {
+ nixos = {
+ imports = [inputs.disko.nixosModules.disko];
+ };
+ };
+}
modules/hosts/cyrene/file-system.nix
@@ -0,0 +1,15 @@
+{den, ...}: {
+ den.hosts.cyrene.settings = {
+ disk.btrfs-disko = {
+ deviceId = "/dev/vda";
+ grubPart = true;
+ };
+ };
+
+ den.aspects.cyrene = {
+ includes = [
+ den.aspects.disk.btrfs-disko
+ den.aspects.disk.preservation
+ ];
+ };
+}
modules/hosts/hyacine/file-system.nix
@@ -0,0 +1,15 @@
+{den, ...}: {
+ den.hosts.hyacine.settings = {
+ disk.btrfs-disko = {
+ deviceId = "/dev/vda";
+ grubPart = true;
+ };
+ };
+
+ den.aspects.hyacine = {
+ includes = [
+ den.aspects.disk.btrfs-disko
+ den.aspects.disk.preservation
+ ];
+ };
+}
modules/hosts/kevin/file-system.nix
@@ -0,0 +1,15 @@
+{den, ...}: {
+ den.hosts.kevin.settings = {
+ disk.btrfs-disko = {
+ deviceId = "/dev/disk/by-id/nvme-Samsung_SSD_990_EVO_Plus_2TB_S7U7NJ0XC02928B";
+ swapSize = 32 * 1024; # 32GiB
+ };
+ };
+
+ den.aspects.kevin = {
+ includes = [
+ den.aspects.disk.btrfs-disko
+ den.aspects.disk.preservation
+ ];
+ };
+}
modules/hosts/mobius/file-system.nix
@@ -0,0 +1,14 @@
+{den, ...}: {
+ den.hosts.mobius.settings = {
+ disk.btrfs-disko = {
+ swapSize = 16 * 1024; # 16GiB
+ };
+ };
+
+ den.aspects.mobius = {
+ includes = [
+ den.aspects.disk.btrfs-disko
+ den.aspects.disk.preservation
+ ];
+ };
+}
modules/hosts/tribios/file-system.nix
@@ -0,0 +1,62 @@
+{den, ...}: {
+ den.hosts.tribios = {
+ settings.disk.preservation.btrfs.partId = "/dev/disk/by-uuid/9886235f-5a74-41e8-919b-9e4096a7cb9e";
+ };
+
+ den.aspects.tribios = {
+ # Manually manage file system.
+ # Since tribios has edk2-rk3588 firmware in main storage.
+ # Using disko will break this.
+ includes = [
+ # den.aspects.disk.btrfs-disko
+ den.aspects.disk.btrfs
+ den.aspects.disk.preservation
+ ];
+
+ nixos = let
+ defaultBtrfsOpts = [
+ "defaults"
+ "compress=zstd:1"
+ "ssd"
+ "discard=async"
+ ];
+ in {
+ fileSystems = {
+ "/boot" = {
+ device = "/dev/disk/by-uuid/A0F7-6D8C";
+ fsType = "vfat";
+ options = ["fmask=0022" "dmask=0022"];
+ };
+ "/" = {
+ device = "/dev/disk/by-uuid/9886235f-5a74-41e8-919b-9e4096a7cb9e";
+ fsType = "btrfs";
+ options = ["subvol=/root"] ++ defaultBtrfsOpts;
+ };
+ "/nix" = {
+ device = "/dev/disk/by-uuid/9886235f-5a74-41e8-919b-9e4096a7cb9e";
+ fsType = "btrfs";
+ options = ["subvol=/nix" "noatime"] ++ defaultBtrfsOpts;
+ neededForBoot = true;
+ };
+ "/home" = {
+ device = "/dev/disk/by-uuid/9886235f-5a74-41e8-919b-9e4096a7cb9e";
+ fsType = "btrfs";
+ options = ["subvol=/home"] ++ defaultBtrfsOpts;
+ neededForBoot = true;
+ };
+ "/persist" = {
+ device = "/dev/disk/by-uuid/9886235f-5a74-41e8-919b-9e4096a7cb9e";
+ fsType = "btrfs";
+ options = ["subvol=/persist"] ++ defaultBtrfsOpts;
+ neededForBoot = true;
+ };
+ "/cache" = {
+ device = "/dev/disk/by-uuid/9886235f-5a74-41e8-919b-9e4096a7cb9e";
+ fsType = "btrfs";
+ options = ["subvol=/cache"] ++ defaultBtrfsOpts;
+ neededForBoot = true;
+ };
+ };
+ };
+ };
+}
modules/secret/default.nix
@@ -64,6 +64,7 @@
} @ args: let
cfg = host.settings.secret;
dummyPubkey = "age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290gq";
+ persistPrefix = lib.optionalString (host.hasAspect den.aspects.disk.preservation) "/persist";
in {
imports = [
(import (inputs.vaultix.outPath + "/module") (args // {inherit self;}))
@@ -79,7 +80,7 @@
then cfg.pubKey
else dummyPubkey;
- hostKeys = config.services.openssh.hostKeys;
+ hostKeys = map (key: key // {path = persistPrefix + key.path;}) config.services.openssh.hostKeys;
};
};
flake.lock
@@ -74,6 +74,26 @@
"type": "github"
}
},
+ "disko": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1781152676,
+ "narHash": "sha256-RxWs5ND31KzTG7wvMM+PMfUjyNpmIEr999lqNARaM5o=",
+ "owner": "nix-community",
+ "repo": "disko",
+ "rev": "ff8702b4de27f72b4c78573dfb89ec74e36abdf1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "disko",
+ "type": "github"
+ }
+ },
"flake-compat": {
"locked": {
"lastModified": 1777699697,
@@ -281,11 +301,27 @@
"type": "github"
}
},
+ "preservation": {
+ "locked": {
+ "lastModified": 1757436102,
+ "narHash": "sha256-mMI9IanU+Xw+pVogD2oT0I2kTmvz2Un/Apc5+CwUpEY=",
+ "owner": "nix-community",
+ "repo": "preservation",
+ "rev": "93416f4614ad2dfed5b0dcf12f27e57d27a5ab11",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "preservation",
+ "type": "github"
+ }
+ },
"root": {
"inputs": {
"den": "den",
"deploy-rs": "deploy-rs",
"devshell": "devshell",
+ "disko": "disko",
"flake-compat": "flake-compat",
"flake-file": "flake-file",
"flake-parts": "flake-parts",
@@ -295,6 +331,7 @@
"nixpkgs": "nixpkgs",
"nur": "nur",
"nur-hpcesia": "nur-hpcesia",
+ "preservation": "preservation",
"vaultix": "vaultix"
}
},
flake.nix
@@ -18,6 +18,10 @@
url = "github:numtide/devshell";
inputs.nixpkgs.follows = "nixpkgs";
};
+ disko = {
+ url = "github:nix-community/disko";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
flake-compat.url = "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz";
flake-file.url = "github:denful/flake-file";
flake-parts = {
@@ -45,6 +49,7 @@
url = "https://codeberg.org/HPCesia/nur-packages/archive/main.tar.gz";
inputs.nixpkgs.follows = "nixpkgs";
};
+ preservation.url = "github:nix-community/preservation";
vaultix = {
url = "github:HPCesia/vaultix";
inputs = {