NixOSConfig/lib/modules/rclone.nix

427 lines
14 KiB
Nix
Raw Normal View History

2024-05-14 11:18:38 +00:00
{ config, lib, pkgs, ... }:
let
cfg = config.programs.rclone;
targetName = "rclone";
2024-05-15 22:51:36 +00:00
manualVendor = "manual";
2024-05-15 22:38:44 +00:00
owncloudVendor = "owncloud";
owncloudName = "Owncloud";
nextcloudVendor = "nextcloud";
2024-05-15 23:04:09 +00:00
mkIfNotNull = value: name: lib.mkIf (value != null) { ${name} = value; };
2024-05-15 23:43:40 +00:00
mkFileOption = { description }: lib.mkOption {
type = lib.types.nullOr (lib.types.either lib.types.path lib.types.str);
description = "The path to a file containing ${description}";
default = null;
};
2024-05-15 23:16:12 +00:00
mkUsernameOption = { service }: lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The user name for logging in to ${service}.";
default = null;
};
2024-05-15 23:43:40 +00:00
mkPasswordOption = { service, itemKind ? "password" }: lib.mkOption {
2024-05-15 23:16:12 +00:00
type = lib.types.nullOr lib.types.str;
2024-05-15 23:43:40 +00:00
description = "The ${itemKind} obscured using the `rclone obscure` command for logging in to ${service}.";
2024-05-15 23:16:12 +00:00
default = null;
};
2024-05-15 23:43:40 +00:00
mkPasswordFileOption = { service, itemKind ? "password" }: mkFileOption {
description = "the ${itemKind} obscured using the `rclone obscure` command for logging in to ${service}.";
2024-05-15 23:16:12 +00:00
};
mkServerUsernameOption = { service }: mkUsernameOption { service = "the ${service} server."; };
mkServerPasswordOption = { service }: mkPasswordOption { service = "the ${service} server."; };
mkServerPasswordFileOption = { service }: mkPasswordFileOption { service = "the ${service} server."; };
2024-05-15 08:01:13 +00:00
mkSystemdDependencyOption =
2024-05-15 16:16:45 +00:00
{ default, global ? false, ... }: lib.mkOption {
2024-05-15 08:01:13 +00:00
type = lib.types.attrsOf (lib.types.listOf lib.types.str);
2024-05-15 16:16:45 +00:00
description = "The systemd services ${
if global then "all" else "this"
} sync${
if global then "s" else ""
} depend${
if global then "" else "s"
} on.";
2024-05-15 08:01:13 +00:00
example = {
secrets = [
"sops-nix.service"
];
};
inherit default;
};
2024-05-15 21:21:00 +00:00
mkProvider = (
2024-05-16 14:52:47 +00:00
{ config, name, ... }: {
2024-05-14 11:18:38 +00:00
options = {
path = lib.mkOption {
type = lib.types.str;
description = "The path to mount the remote file system to.";
2024-05-16 14:52:47 +00:00
default = "$HOME/.mnt/${lib.escapeShellArg name}";
};
2024-05-14 11:18:38 +00:00
autoStart = lib.mkOption {
type = lib.types.bool;
description = "Whether to start this sync automatically.";
default = true;
};
systemdDependencies = mkSystemdDependencyOption {
default = cfg.systemdDependencies;
2024-05-14 11:18:38 +00:00
};
environment = lib.mkOption {
type = lib.types.attrsOf lib.types.envVar;
description = "The environment variables to pass to the service.";
2024-05-15 21:21:00 +00:00
default = { };
2024-05-14 11:18:38 +00:00
};
secrets = lib.mkOption {
type = lib.types.attrsOf (lib.types.either lib.types.path lib.types.str);
description = "A set of environment variables to load from files.";
default = { };
};
2024-05-14 12:23:30 +00:00
secretsScript = lib.mkOption {
2024-05-14 11:18:38 +00:00
type = lib.types.lines;
2024-05-14 12:23:30 +00:00
description = "A script for loading secrets before launching the sync.";
default = "";
2024-05-14 11:18:38 +00:00
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "A set of additional arguments to pass to `rclone mount`.";
default = [];
};
config = lib.mkOption {
type = lib.types.attrs;
description = "The rclone config to use for creating the mount.";
visible = false;
};
args = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "The arguments to pass to `rclone mount`.";
visible = false;
};
2024-05-14 11:18:38 +00:00
};
config = {
2024-05-15 22:20:06 +00:00
secretsScript = lib.strings.concatLines
(builtins.attrValues (
builtins.mapAttrs
2024-05-16 00:19:06 +00:00
(name: path: "export ${name}=\"$(cat ${lib.escapeShellArg path})\"")
config.secrets));
args = config.extraArgs;
};
2024-05-15 21:21:00 +00:00
});
mkWebdavProvider = (
2024-05-15 22:40:04 +00:00
{ displayName, vendor ? null, ... }: (
2024-05-15 21:21:00 +00:00
{ config, ... }: {
imports = [
mkProvider
];
2024-05-15 21:24:54 +00:00
options = {
2024-05-15 21:21:00 +00:00
vendor = lib.mkOption {
2024-05-15 22:40:04 +00:00
type = lib.types.nullOr (lib.types.enum [
"fastmail"
nextcloudVendor
owncloudVendor
"sharepoint"
"sharepoint-ntlm"
"rclone"
"other"
]);
2024-05-15 21:21:00 +00:00
description = "The vendor of the WebDAV share.";
default = vendor;
};
url = lib.mkOption {
type = lib.types.str;
description = "The WebDAV URL of the ${displayName} server.";
default = null;
};
2024-05-15 23:16:12 +00:00
username = mkServerUsernameOption { service = displayName; };
obscuredPassword = mkServerPasswordOption { service = displayName; };
obscuredPasswordFile = mkServerPasswordFileOption { service = displayName; };
2024-05-15 22:44:56 +00:00
bearerToken = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The bearer token for logging in to the ${displayName} server.";
default = null;
};
bearerTokenFile = lib.mkOption {
type = lib.types.nullOr (lib.types.either lib.types.path lib.types.str);
description = "The path to a file containing the bearer token for logging in to the ${displayName} server.";
default = null;
};
2024-05-15 21:21:00 +00:00
};
config = {
config = lib.mkMerge [
2024-05-16 00:16:29 +00:00
{
type = "webdav";
url = config.url;
}
2024-05-15 23:04:09 +00:00
(mkIfNotNull config.vendor "vendor")
(mkIfNotNull config.username "user")
(mkIfNotNull config.obscuredPassword "pass")
(mkIfNotNull config.bearerToken "bearer_token")
2024-05-15 21:21:00 +00:00
];
2024-05-15 23:04:09 +00:00
secrets = lib.mkMerge [
(mkIfNotNull config.obscuredPasswordFile "RCLONE_WEBDAV_PASS")
(mkIfNotNull config.bearerTokenFile "RCLONE_WEBDAV_BEARER_TOKEN")
];
2024-05-15 21:21:00 +00:00
};
}));
2024-05-15 22:38:44 +00:00
mkOwncloudProvider = { displayName ? owncloudName, vendor ? owncloudVendor }: (
{ config, ... }: {
imports = [
(mkWebdavProvider { inherit displayName vendor; })
];
options = {
baseUrl = lib.mkOption {
type = lib.types.str;
description = "The base url of the ${displayName} server for automatically determining the WebDAV url.";
};
};
config = {
2024-05-16 00:15:23 +00:00
url = "${lib.strings.removeSuffix "/" config.baseUrl}/remote.php/dav/files/${config.username}";
};
});
2024-05-15 23:43:40 +00:00
mkProtonProvider = { displayName }: (
{ config, ... }: {
imports = [
mkProvider
];
options = {
username = mkUsernameOption { service = displayName; };
obscuredPassword = mkPasswordOption { service = displayName; };
obscuredPasswordFile = mkPasswordFileOption { service = displayName; };
webAuthnToken = lib.mkOption {
type = lib.types.strMatching "[0-9]{6}";
description = "The 2 Factor Authentication code for logging in to ${displayName}.";
};
webAuthnTokenFile = mkFileOption {
description = "the 2 Factor Authentication code for logging in to ${displayName}.";
};
clientID = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The client key.";
default = null;
};
accessToken = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The access token.";
default = null;
};
refreshToken = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The refresh token.";
default = null;
};
saltedKeyPass = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The salted key pass.";
default = null;
};
enableCaching = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
description = "Whether to enable ${displayName}'s integrated caching.";
default = null;
};
mailboxPassword = mkPasswordOption { service = displayName; itemKind = "mailbox password"; };
mailboxPasswordFile = mkPasswordFileOption { service = displayName; itemKind = "mailbox password"; };
clientIDFile = mkFileOption { description = "the client key."; };
accessTokenFile = mkFileOption { description = "the access token."; };
refreshTokenFile = mkFileOption { description = "the refresh token."; };
saltedKeyPassFile = mkFileOption { description = "the salted key pass."; };
};
config = {
config = lib.mkMerge [
{ type = "protondrive"; }
(mkIfNotNull config.username "username")
(mkIfNotNull config.obscuredPassword "password")
(mkIfNotNull config.webAuthnToken "2fa")
(mkIfNotNull config.mailboxPassword "mailbox_password")
(mkIfNotNull config.clientID "client_uid")
(mkIfNotNull config.accessToken "client_access_token")
(mkIfNotNull config.refreshToken "client_refresh_token")
(mkIfNotNull config.saltedKeyPass "client_salted_key_pass")
(mkIfNotNull config.enableCaching "enable_caching")
];
secrets = lib.mkMerge [
(mkIfNotNull config.obscuredPasswordFile "RCLONE_PROTONDRIVE_PASSWORD")
(mkIfNotNull config.webAuthnTokenFile "RCLONE_PROTONDRIVE_2FA")
(mkIfNotNull config.mailboxPasswordFile "RCLONE_PROTONDRIVE_MAILBOX_PASSWORD")
(mkIfNotNull config.clientIDFile "RCLONE_PROTONDRIVE_CLIENT_UID")
(mkIfNotNull config.accessTokenFile "RCLONE_PROTONDRIVE_CLIENT_ACCESS_TOKEN")
(mkIfNotNull config.refreshTokenFile "RCLONE_PROTONDRIVE_CLIENT_REFRESH_TOKEN")
(mkIfNotNull config.saltedKeyPassFile "RCLONE_PROTONDRIVE_CLIENT_SALTED_KEY_PASS")
];
};
});
2024-05-15 21:21:00 +00:00
syncProviders = {
2024-05-15 22:51:36 +00:00
${manualVendor} = {
displayName = "Custom";
module = mkProvider;
};
2024-05-15 22:38:44 +00:00
${nextcloudVendor} = rec {
2024-05-15 21:21:00 +00:00
displayName = "Nextcloud";
module = mkOwncloudProvider {
2024-05-15 21:21:00 +00:00
inherit displayName;
2024-05-15 22:38:44 +00:00
vendor = nextcloudVendor;
2024-05-15 15:22:34 +00:00
};
2024-05-15 21:21:00 +00:00
};
2024-05-15 22:34:29 +00:00
2024-05-15 22:38:44 +00:00
${owncloudVendor} = {
2024-05-15 22:34:29 +00:00
displayName = owncloudName;
module = mkOwncloudProvider { };
};
2024-05-15 23:43:40 +00:00
proton = rec {
displayName = "Proton";
module = mkProtonProvider { inherit displayName; };
};
2024-05-15 21:21:00 +00:00
};
2024-05-14 11:18:38 +00:00
in {
options = {
programs.rclone = {
enable = lib.mkEnableOption "rclone";
2024-05-14 11:18:38 +00:00
systemdDependencies = mkSystemdDependencyOption {
default = {};
2024-05-15 16:16:45 +00:00
global = true;
};
2024-05-15 13:54:03 +00:00
configs = (builtins.mapAttrs
2024-05-15 21:21:00 +00:00
(name: provider: lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule provider.module);
description = "The ${provider.displayName} synchronizations to set up.";
default = { };
2024-05-15 13:54:03 +00:00
})
syncProviders);
};
};
config = {
2024-05-16 00:23:59 +00:00
home.packages = lib.optionals cfg.enable [
pkgs.fuse
pkgs.rclone
];
systemd.user = lib.optionalAttrs cfg.enable {
enable = true;
services = {
rclone = {
Unit = {
Description = "rclone Starter";
Documentation = "man:rclone(1)";
};
Service = {
Type = "simple";
ExecStartPre =
let
script = pkgs.writeShellScriptBin "rclone-pre" ''
sleep 10
'';
in
(lib.getExe script);
ExecStart =
let
script = pkgs.writeShellScriptBin "rclone" ''
systemctl --user start rclone.target
'';
in
(lib.getExe script);
};
};
} // (
lib.attrsets.concatMapAttrs
2024-05-15 13:54:03 +00:00
(providerName: configs:
lib.attrsets.concatMapAttrs
(name: sync:
let
serviceName = "rclone-${providerName}-sync-${name}";
in {
${serviceName} = {
Unit = {
Description = "rclone sync service for ${name} at using ${providerName}";
2024-05-16 00:16:53 +00:00
After = builtins.concatLists (builtins.attrValues sync.systemdDependencies);
2024-05-15 13:54:03 +00:00
};
Service = {
Environment = lib.mapAttrsToList
(key: val: (lib.escapeShellArg "${key}=${val}"))
sync.environment;
ExecStart =
let
configFile = pkgs.writeText
"${serviceName}.conf"
2024-05-15 23:58:46 +00:00
(lib.generators.toINI { } { ${name} = sync.config; });
2024-05-15 13:54:03 +00:00
script = pkgs.writeShellScriptBin serviceName ''
${sync.secretsScript}
mkdir -p ${sync.path}
mkdir -p /tmp/rclone
${lib.getExe pkgs.rclone} mount --config ${configFile} ${name}: ${sync.path}
'';
in
(lib.getExe script);
ExecStop =
let
script = pkgs.writeShellScriptBin "${serviceName}-stop" ''
furermount -zu ${sync.path}
2024-05-15 13:54:03 +00:00
'';
in
(lib.getExe script);
};
Install = {
WantedBy = lib.optional sync.autoStart "${targetName}.target";
};
};
2024-05-15 13:54:03 +00:00
})
configs)
cfg.configs);
targets.${targetName} = {
Unit = {
Description = "rclone Mounts";
Documentation = "man:rclone(1)";
};
};
};
};
}