2022-12-29 16:38:58 +00:00
|
|
|
# borrows from:
|
|
|
|
# https://xeiaso.net/blog/paranoid-nixos-2021-07-18
|
|
|
|
# https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/
|
|
|
|
# https://github.com/nix-community/impermanence
|
2023-01-03 14:55:27 +00:00
|
|
|
{ config, lib, pkgs, utils, sane-lib, ... }:
|
2022-12-29 16:38:58 +00:00
|
|
|
|
|
|
|
with lib;
|
|
|
|
let
|
2023-01-03 14:55:27 +00:00
|
|
|
path = sane-lib.path;
|
2022-12-29 16:38:58 +00:00
|
|
|
cfg = config.sane.impermanence;
|
2023-01-03 03:04:17 +00:00
|
|
|
|
2023-01-03 07:04:49 +00:00
|
|
|
storeType = types.submodule {
|
|
|
|
options = {
|
|
|
|
mountpt = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
};
|
|
|
|
prefix = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "/";
|
2023-01-03 07:45:19 +00:00
|
|
|
description = ''
|
|
|
|
optional prefix to strip from children when stored here.
|
|
|
|
for example, prefix="/var/private" and mountpoint="/mnt/crypt/private"
|
|
|
|
would cause /var/private/www/root to be stored at /mnt/crypt/private/www/root instead of
|
|
|
|
/mnt/crypt/private/var/private/www/root.
|
|
|
|
'';
|
2023-01-03 07:04:49 +00:00
|
|
|
};
|
|
|
|
extraOptions = mkOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = [];
|
2023-01-03 07:45:19 +00:00
|
|
|
description = ''
|
|
|
|
extra fstab options to include in all mounts downstream of this store.
|
|
|
|
e.g. ["noauto" "x-systemd.wanted-by=<blah>"] to automount but only after the store is explicitly unlocked.
|
|
|
|
'';
|
2023-01-03 07:04:49 +00:00
|
|
|
};
|
|
|
|
};
|
2023-01-03 03:04:17 +00:00
|
|
|
};
|
2022-12-29 16:38:58 +00:00
|
|
|
|
2022-12-31 09:09:51 +00:00
|
|
|
# options for a single mountpoint / persistence
|
2023-01-03 07:04:49 +00:00
|
|
|
dirEntryOptions = {
|
2022-12-29 16:38:58 +00:00
|
|
|
options = {
|
2022-12-31 09:09:51 +00:00
|
|
|
directory = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
};
|
2022-12-29 16:38:58 +00:00
|
|
|
user = mkOption {
|
2022-12-31 01:04:49 +00:00
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
2022-12-29 16:38:58 +00:00
|
|
|
};
|
|
|
|
group = mkOption {
|
2022-12-31 01:04:49 +00:00
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
2022-12-29 16:38:58 +00:00
|
|
|
};
|
|
|
|
mode = mkOption {
|
2022-12-31 01:04:49 +00:00
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
2022-12-29 16:38:58 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2023-01-03 07:04:49 +00:00
|
|
|
contextualizedDir = types.submodule dirEntryOptions;
|
2022-12-31 09:09:51 +00:00
|
|
|
# allow "bar/baz" as shorthand for { directory = "bar/baz"; }
|
2023-01-03 07:04:49 +00:00
|
|
|
contextualizedDirOrShorthand = types.coercedTo
|
|
|
|
types.str
|
|
|
|
(d: { directory = d; })
|
|
|
|
contextualizedDir;
|
2022-12-29 16:38:58 +00:00
|
|
|
|
2023-01-03 07:04:49 +00:00
|
|
|
# entry whose `directory` is always an absolute fs path
|
|
|
|
# and has an associated `store`
|
|
|
|
contextFreeDir = types.submodule [
|
|
|
|
dirEntryOptions
|
|
|
|
{
|
|
|
|
options = {
|
|
|
|
store = mkOption {
|
|
|
|
type = storeType;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
];
|
2022-12-29 16:38:58 +00:00
|
|
|
|
2023-01-03 07:04:49 +00:00
|
|
|
dirsModule = types.submodule ({ config, ... }: {
|
|
|
|
options = {
|
|
|
|
home = mkOption {
|
|
|
|
description = "directories to persist to disk, relative to a user's home ~";
|
|
|
|
default = {};
|
|
|
|
type = types.submodule {
|
|
|
|
options = {
|
|
|
|
plaintext = mkOption {
|
|
|
|
default = [];
|
|
|
|
type = types.listOf contextualizedDirOrShorthand;
|
|
|
|
description = "directories to persist in cleartext";
|
|
|
|
};
|
|
|
|
private = mkOption {
|
|
|
|
default = [];
|
|
|
|
type = types.listOf contextualizedDirOrShorthand;
|
|
|
|
description = "directories to store encrypted to the user's login password and auto-decrypt on login";
|
|
|
|
};
|
|
|
|
cryptClearOnBoot = mkOption {
|
|
|
|
default = [];
|
|
|
|
type = types.listOf contextualizedDirOrShorthand;
|
|
|
|
description = ''
|
|
|
|
directories to store encrypted to an auto-generated in-memory key and
|
|
|
|
wiped on boot. the main use is for sensitive cache dirs too large to fit in memory.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
sys = mkOption {
|
|
|
|
description = "directories to persist to disk, relative to the fs root /";
|
|
|
|
default = {};
|
|
|
|
type = types.submodule {
|
|
|
|
options = {
|
|
|
|
plaintext = mkOption {
|
|
|
|
default = [];
|
|
|
|
type = types.listOf contextualizedDirOrShorthand;
|
|
|
|
description = "list of directories (and optional config) to persist to disk in plaintext, relative to the fs root /";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
all = mkOption {
|
|
|
|
type = types.listOf contextFreeDir;
|
|
|
|
description = "all directories known to the config. auto-computed: users should not set this directly.";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
config = let
|
|
|
|
mapDirs = relativeTo: store: dirs: (map
|
|
|
|
(d: {
|
|
|
|
inherit (d) user group mode;
|
2023-01-03 14:55:27 +00:00
|
|
|
directory = path.concat [ relativeTo d.directory ];
|
2023-01-03 07:04:49 +00:00
|
|
|
store = cfg.stores."${store}";
|
|
|
|
})
|
|
|
|
dirs
|
|
|
|
);
|
|
|
|
in {
|
|
|
|
all = (mapDirs "/home/colin" "plaintext" config.home.plaintext)
|
|
|
|
++ (mapDirs "/home/colin" "private" config.home.private)
|
|
|
|
++ (mapDirs "/home/colin" "cryptClearOnBoot" config.home.cryptClearOnBoot)
|
|
|
|
++ (mapDirs "/" "plaintext" config.sys.plaintext);
|
|
|
|
};
|
|
|
|
});
|
2022-12-29 16:38:58 +00:00
|
|
|
in
|
|
|
|
{
|
|
|
|
options = {
|
|
|
|
sane.impermanence.enable = mkOption {
|
|
|
|
default = false;
|
|
|
|
type = types.bool;
|
|
|
|
};
|
|
|
|
sane.impermanence.root-on-tmpfs = mkOption {
|
|
|
|
default = false;
|
|
|
|
type = types.bool;
|
2023-01-03 07:04:49 +00:00
|
|
|
description = "define / fs root to be a tmpfs. make sure to mount some other device to /nix";
|
2022-12-31 09:09:51 +00:00
|
|
|
};
|
|
|
|
sane.impermanence.dirs = mkOption {
|
2023-01-03 07:04:49 +00:00
|
|
|
type = dirsModule;
|
|
|
|
default = {};
|
|
|
|
};
|
|
|
|
sane.impermanence.stores = mkOption {
|
|
|
|
type = types.attrsOf storeType;
|
|
|
|
default = {};
|
2023-01-03 07:45:19 +00:00
|
|
|
description = ''
|
|
|
|
map from human-friendly name to a fs sub-tree from which files are linked into the logical fs.
|
|
|
|
'';
|
2022-12-31 09:09:51 +00:00
|
|
|
};
|
2022-12-29 16:38:58 +00:00
|
|
|
};
|
|
|
|
|
2022-12-30 04:35:34 +00:00
|
|
|
imports = [
|
|
|
|
./root-on-tmpfs.nix
|
2023-01-03 07:04:49 +00:00
|
|
|
./stores
|
2022-12-30 04:35:34 +00:00
|
|
|
];
|
2022-12-29 16:38:58 +00:00
|
|
|
|
2023-01-03 08:25:43 +00:00
|
|
|
config = let
|
|
|
|
cfgFor = opt:
|
|
|
|
let
|
|
|
|
store = opt.store;
|
2023-01-03 14:55:27 +00:00
|
|
|
store-rel-path = path.from store.prefix opt.directory;
|
|
|
|
backing-path = path.concat [ store.mountpt store-rel-path ];
|
2022-12-29 16:38:58 +00:00
|
|
|
|
2023-01-03 08:25:43 +00:00
|
|
|
# pass through the perm/mode overrides
|
|
|
|
dir-acl = {
|
|
|
|
user = lib.mkIf (opt.user != null) opt.user;
|
|
|
|
group = lib.mkIf (opt.group != null) opt.group;
|
|
|
|
mode = lib.mkIf (opt.mode != null) opt.mode;
|
2022-12-29 16:38:58 +00:00
|
|
|
};
|
|
|
|
in {
|
2023-01-03 08:25:43 +00:00
|
|
|
# create destination and backing directory, with correct perms
|
|
|
|
sane.fs."${opt.directory}" = {
|
|
|
|
# inherit perms & make sure we don't mount until after the mount point is setup correctly.
|
|
|
|
dir.acl = dir-acl;
|
|
|
|
mount.bind = backing-path;
|
|
|
|
mount.extraOptions = store.extraOptions;
|
|
|
|
};
|
|
|
|
sane.fs."${backing-path}" = {
|
|
|
|
# ensure the backing path has same perms as the mount point.
|
|
|
|
# TODO: maybe we want to do this, crawling all the way up to the store base?
|
|
|
|
# that would simplify (remove) the code in stores/default.nix
|
|
|
|
dir.acl = config.sane.fs."${opt.directory}".dir.acl;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
in mkIf cfg.enable {
|
|
|
|
sane.fs = lib.mkMerge (map (d: (cfgFor d).sane.fs) cfg.dirs.all);
|
|
|
|
};
|
2022-12-29 16:38:58 +00:00
|
|
|
}
|
|
|
|
|