nix-files/modules/impermanence/crypt.nix

119 lines
4.0 KiB
Nix

{ config, lib, pkgs, utils, ... }:
let
store = rec {
device = "/mnt/impermanence/crypt/clearedonboot";
mount-unit = "${utils.escapeSystemdPath device}.mount";
underlying = {
path = "/nix/persist/crypt/clearedonboot";
# TODO: consider moving this to /tmp, but that requires tmp be mounted first?
key = "/mnt/impermanence/crypt/clearedonboot.key";
};
};
prepareEncryptedClearedOnBoot = pkgs.writeShellApplication {
name = "prepareEncryptedClearedOnBoot";
runtimeInputs = with pkgs; [ gocryptfs ];
text = ''
backing="$1"
passfile="$2"
if ! test -e "$passfile"
then
# if the key doesn't exist, it's probably not mounted => delete the backing dir
rm -rf "''${backing:?}"/*
# generate key. we can "safely" keep it around for the lifetime of this boot
umask 266
dd if=/dev/random bs=128 count=1 | base64 --wrap=0 > "$passfile"
umask 022
# initialize the crypt store
gocryptfs -quiet -passfile "$passfile" -init "$backing"
fi
'';
};
private-mount-unit = ''${utils.escapeSystemdPath "/home/colin/private"}.mount'';
in lib.mkIf config.sane.impermanence.enable
{
systemd.services."prepareEncryptedClearedOnBoot" = rec {
description = "prepare keys for ${store.device}";
serviceConfig.ExecStart = ''
${prepareEncryptedClearedOnBoot}/bin/prepareEncryptedClearedOnBoot ${store.underlying.path} ${store.underlying.key}
'';
serviceConfig.Type = "oneshot";
# remove implicit dep on sysinit.target
unitConfig.DefaultDependencies = "no";
# we need the key directory to be created, and the backing directory to exist
after = [
config.sane.fs."${store.underlying.path}".unit
# TODO: "${parentDir store.device}"
config.sane.fs."/mnt/impermanence/crypt".unit
];
wants = after;
# make sure the encrypted file system is mounted *after* its keys have been generated.
before = [ store.mount-unit ];
wantedBy = before;
};
fileSystems."${store.device}" = {
device = store.underlying.path;
fsType = "fuse.gocryptfs";
options = [
"nodev"
"nosuid"
"allow_other"
"passfile=${store.underlying.key}"
"defaults"
];
noCheck = true;
};
sane.fs."${store.device}" = {
# ensure the fs is mounted only after the mountpoint directory is created
dir.reverseDepends = [ store.mount-unit ];
# HACK: this fs entry is provided by our mount unit.
mount.unit = store.mount-unit;
};
sane.fs."${store.underlying.path}" = {
# don't mount until after the backing dir is setup correctly.
# TODO: this isn't necessary? the mount-unit already depends on prepareEncryptedClearOnBoot
# which depends on the underlying path?
dir.reverseDepends = [ store.mount-unit ];
};
fileSystems."/home/colin/private" = {
device = "/nix/persist/home/colin/private";
fsType = "fuse.gocryptfs";
options = [
"noauto" # don't try to mount, until the user logs in!
"allow_other" # root ends up being the user that mounts this, so need to make it visible to `colin`.
"nodev"
"nosuid"
"quiet"
"defaults"
];
noCheck = true;
};
sane.fs."/home/colin/private" = {
dir.reverseDepends = [
# mounting relies on the mountpoint first being created.
private-mount-unit
# ensure the directory is created during boot, and before user logs in.
"multi-user.target"
];
# HACK: this fs entry is provided by the mount unit.
unit = private-mount-unit;
};
sane.fs."/nix/persist/home/colin/private" = {
dir.reverseDepends = [
# the mount unit relies on the source having first been created.
# (it also relies on the cryptfs having been seeded -- which we can't verify here).
private-mount-unit
# ensure the directory is created during boot, and before user logs in.
"multi-user.target"
];
};
# TODO: could add this *specifically* to the .mount file for the encrypted fs?
environment.systemPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs
}