depot/modules/external-storage/default.nix

157 lines
4.7 KiB
Nix
Raw Normal View History

2023-08-23 01:55:48 +03:00
{ config, lib, pkgs, ... }:
let
s3qlWithSystemd = pkgs.s3ql.overrideAttrs (old: {
propagatedBuildInputs = old.propagatedBuildInputs ++ [
pkgs.python3Packages.systemd
];
});
cfg = config.services.external-storage;
create = lib.flip lib.mapAttrs';
in
{
imports = [
./strict-mounts.nix
];
2023-08-23 01:55:48 +03:00
options = {
services.external-storage = {
fileSystems = lib.mkOption {
description = "S3QL-based filesystems on top of CIFS mountpoints.";
default = {};
type = with lib.types; lazyAttrsOf (submodule ./filesystem-type.nix);
};
underlays = lib.mkOption {
description = "CIFS underlays for S3QL filesystems.";
default = {};
type = with lib.types; lazyAttrsOf (submodule ./underlay-type.nix);
};
};
};
config = {
boot.supportedFilesystems = [ "cifs" ];
age.secrets = lib.mkMerge [
(create cfg.underlays (name: ul: lib.nameValuePair "cifsCredentials-${name}" { file = ul.credentialsFile; }))
(create cfg.fileSystems (name: fs: lib.nameValuePair "storageEncryptionKey-${name}" { file = fs.encryptionKeyFile; }))
];
fileSystems = create cfg.underlays (name: ul: {
name = ul.mountpoint;
value = {
fsType = "cifs";
device = "//${ul.host}/${ul.storageBoxAccount}-${ul.subUser}${ul.path}";
options = [
"credentials=${config.age.secrets."cifsCredentials-${name}".path}"
"dir_mode=0700"
"file_mode=0600"
"uid=${toString ul.uid}"
"gid=${toString ul.gid}"
"forceuid"
"forcegid"
2023-08-23 01:55:48 +03:00
"seal"
"hard"
"resilienthandles"
"cache=loose"
2023-08-23 01:55:48 +03:00
"_netdev"
"x-systemd.automount"
];
};
});
systemd = {
tmpfiles.rules = lib.mapAttrsToList (_: fs: "d '${fs.cacheDir}' 0700 root root - -") cfg.fileSystems;
mounts = lib.mapAttrsToList (name: fs: {
where = fs.mountpoint;
what = name;
requires = [ "${fs.unitName}.service" ];
after = [ "${fs.unitName}.service" ];
}) cfg.fileSystems;
services = create cfg.fileSystems (name: fs: {
name = fs.unitName;
value = let
underlayPath = cfg.underlays.${fs.underlay}.mountpoint;
in {
description = fs.unitDescription;
wantedBy = [ "multi-user.target" ];
wants = [ "remote-fs.target" ];
before = [ "remote-fs.target" ];
# used by umount.s3ql
path = with pkgs; [
psmisc
util-linux
];
unitConfig.RequiresMountsFor = underlayPath;
serviceConfig = let
commonOptions = [
"--cachedir" fs.cacheDir
"--authfile" config.age.secrets."storageEncryptionKey-${name}".path
];
in {
Type = "notify";
ExecStartPre = map lib.escapeShellArgs [
[
(pkgs.writeShellScript "create-s3ql-filesystem" ''
if ! test -e ${underlayPath}/s3ql_passphrase; then
echo Creating new S3QL filesystem on ${underlayPath}
${pkgs.gnugrep}/bin/grep -m1 fs-passphrase: '${config.age.secrets."storageEncryptionKey-${name}".path}' \
| cut -d' ' -f2- \
| ${s3qlWithSystemd}/bin/mkfs.s3ql ${lib.escapeShellArgs commonOptions} -L '${name}' 'local://${underlayPath}'
fi
'')
]
[
"${pkgs.coreutils}/bin/install" "-dm755" fs.mountpoint
]
([
"${s3qlWithSystemd}/bin/fsck.s3ql"
"local://${underlayPath}"
"--compress" "none"
] ++ commonOptions)
];
ExecStart = lib.escapeShellArgs ([
"${s3qlWithSystemd}/bin/mount.s3ql"
"local://${underlayPath}"
fs.mountpoint
"--fs-name" "${fs.unitName}"
"--allow-other"
"--systemd" "--fg"
"--log" "none"
"--compress" "none"
] ++ commonOptions);
ExecStop = lib.escapeShellArgs [
"${s3qlWithSystemd}/bin/umount.s3ql"
"--log" "none"
fs.mountpoint
];
# fsck and unmounting might take a while
TimeoutStartSec = "6h";
TimeoutStopSec = "900s";
# s3ql only handles SIGINT
KillSignal = "SIGINT";
Restart = "on-failure";
RestartSec = "10s";
# see https://www.rath.org/s3ql-docs/man/fsck.html
SuccessExitStatus = [ 128 ];
2023-08-23 01:55:48 +03:00
};
};
});
};
};
}