530 lines
17 KiB
Nix
530 lines
17 KiB
Nix
{ config, lib, osConfig, pkgs, ... }:
|
|
let
|
|
cfg = config.programs.rclone;
|
|
targetName = "rclone";
|
|
manualVendor = "manual";
|
|
owncloudVendor = "owncloud";
|
|
owncloudName = "Owncloud";
|
|
nextcloudVendor = "nextcloud";
|
|
mkIfNotNull = value: name: lib.mkIf (value != null) { ${name} = value; };
|
|
|
|
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;
|
|
};
|
|
|
|
mkUsernameOption = { service }: lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "The user name for logging in to ${service}.";
|
|
default = null;
|
|
};
|
|
|
|
mkPasswordOption = { service, itemKind ? "password" }: lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "The ${itemKind} obscured using the `rclone obscure` command for logging in to ${service}.";
|
|
default = null;
|
|
};
|
|
|
|
mkPasswordFileOption = { service, itemKind ? "password" }: mkFileOption {
|
|
description = "the ${itemKind} obscured using the `rclone obscure` command for logging in to ${service}.";
|
|
};
|
|
|
|
mkServerUsernameOption = { service }: mkUsernameOption { service = "the ${service} server."; };
|
|
mkServerPasswordOption = { service }: mkPasswordOption { service = "the ${service} server."; };
|
|
mkServerPasswordFileOption = { service }: mkPasswordFileOption { service = "the ${service} server."; };
|
|
|
|
mkSystemdDependencyOption =
|
|
{ default, global ? false, ... }: lib.mkOption {
|
|
type = lib.types.attrsOf (lib.types.listOf lib.types.str);
|
|
description = "The systemd services ${
|
|
if global then "all" else "this"
|
|
} sync${
|
|
if global then "s" else ""
|
|
} depend${
|
|
if global then "" else "s"
|
|
} on.";
|
|
example = {
|
|
secrets = [
|
|
"sops-nix.service"
|
|
];
|
|
};
|
|
inherit default;
|
|
};
|
|
|
|
mkProvider = (
|
|
{ config, name, ... }: {
|
|
options = {
|
|
path = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "The path to mount the remote file system to.";
|
|
default = ''"$HOME"/.mnt/${lib.escapeShellArg name}'';
|
|
};
|
|
|
|
autoStart = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = "Whether to start this sync automatically.";
|
|
default = true;
|
|
};
|
|
|
|
systemdDependencies = mkSystemdDependencyOption {
|
|
default = cfg.systemdDependencies;
|
|
};
|
|
|
|
environment = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.envVar;
|
|
description = "The environment variables to pass to the service.";
|
|
default = { };
|
|
};
|
|
|
|
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 = { };
|
|
};
|
|
|
|
secretsScript = lib.mkOption {
|
|
type = lib.types.lines;
|
|
description = "A script for loading secrets before launching the sync.";
|
|
default = "";
|
|
};
|
|
|
|
vfs = {
|
|
enable = lib.mkEnableOption "Virtual File System";
|
|
|
|
mode = lib.mkOption {
|
|
type = lib.types.nullOr (lib.types.enum [
|
|
"minimal"
|
|
"writes"
|
|
"full"
|
|
]);
|
|
description = "The cache mode to use.";
|
|
default = "full";
|
|
};
|
|
|
|
dirCacheTime = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "The time to cache directory entries for.";
|
|
default = null;
|
|
};
|
|
|
|
pollInterval = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "The time to wait between polling for changes.";
|
|
default = null;
|
|
};
|
|
|
|
bufferSize = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "The maximum size of the buffer per size to allocate.";
|
|
default = null;
|
|
};
|
|
|
|
maxAge = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "The maximum age of cached files to keep.";
|
|
default = null;
|
|
};
|
|
};
|
|
|
|
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;
|
|
};
|
|
};
|
|
|
|
config = {
|
|
secretsScript = lib.strings.concatLines
|
|
(builtins.concatLists
|
|
(builtins.attrValues (
|
|
builtins.mapAttrs
|
|
(name: path: [
|
|
"${name}=\"$(cat ${lib.escapeShellArg path})\""
|
|
"export ${name}"
|
|
])
|
|
config.secrets)));
|
|
|
|
args = config.extraArgs ++ (
|
|
let
|
|
vfs = config.vfs;
|
|
in (
|
|
lib.optionals vfs.enable
|
|
(builtins.attrValues (
|
|
lib.attrsets.concatMapAttrs
|
|
(name: value:
|
|
lib.optionalAttrs
|
|
(value != null)
|
|
{ name = "--${name}=${lib.escapeShellArg value}"; })
|
|
{
|
|
vfs-cache-mode = vfs.mode;
|
|
vfs-cache-poll-interval = vfs.pollInterval;
|
|
vfs-cache-max-size = vfs.bufferSize;
|
|
vfs-cache-max-age = vfs.maxAge;
|
|
}))));
|
|
};
|
|
});
|
|
|
|
mkWebdavProvider = (
|
|
{ displayName, vendor ? null, ... }: (
|
|
{ config, ... }: {
|
|
imports = [
|
|
mkProvider
|
|
];
|
|
|
|
options = {
|
|
vendor = lib.mkOption {
|
|
type = lib.types.nullOr (lib.types.enum [
|
|
"fastmail"
|
|
nextcloudVendor
|
|
owncloudVendor
|
|
"sharepoint"
|
|
"sharepoint-ntlm"
|
|
"rclone"
|
|
"other"
|
|
]);
|
|
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;
|
|
};
|
|
|
|
username = mkServerUsernameOption { service = displayName; };
|
|
obscuredPassword = mkServerPasswordOption { service = displayName; };
|
|
obscuredPasswordFile = mkServerPasswordFileOption { service = displayName; };
|
|
|
|
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;
|
|
};
|
|
};
|
|
|
|
config = {
|
|
config = lib.mkMerge [
|
|
{
|
|
type = "webdav";
|
|
url = config.url;
|
|
}
|
|
(mkIfNotNull config.vendor "vendor")
|
|
(mkIfNotNull config.username "user")
|
|
(mkIfNotNull config.obscuredPassword "pass")
|
|
(mkIfNotNull config.bearerToken "bearer_token")
|
|
];
|
|
|
|
secrets = lib.mkMerge [
|
|
(mkIfNotNull config.obscuredPasswordFile "RCLONE_WEBDAV_PASS")
|
|
(mkIfNotNull config.bearerTokenFile "RCLONE_WEBDAV_BEARER_TOKEN")
|
|
];
|
|
};
|
|
}));
|
|
|
|
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 = {
|
|
url = "${lib.strings.removeSuffix "/" config.baseUrl}/remote.php/dav/files/${config.username}";
|
|
};
|
|
});
|
|
|
|
mkProtonProvider = { displayName }: (
|
|
{ config, ... }: {
|
|
imports = [
|
|
mkProvider
|
|
];
|
|
|
|
options = {
|
|
username = mkUsernameOption { service = displayName; };
|
|
obscuredPassword = mkPasswordOption { service = displayName; };
|
|
obscuredPasswordFile = mkPasswordFileOption { service = displayName; };
|
|
|
|
webAuthnToken = lib.mkOption {
|
|
type = lib.types.nullOr (lib.types.strMatching "[0-9]{6}");
|
|
description = "The 2 Factor Authentication code for logging in to ${displayName}.";
|
|
default = null;
|
|
};
|
|
|
|
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")
|
|
];
|
|
};
|
|
});
|
|
|
|
syncProviders = {
|
|
${manualVendor} = {
|
|
displayName = "Custom";
|
|
module = mkProvider;
|
|
};
|
|
|
|
${nextcloudVendor} = rec {
|
|
displayName = "Nextcloud";
|
|
|
|
module = mkOwncloudProvider {
|
|
inherit displayName;
|
|
vendor = nextcloudVendor;
|
|
};
|
|
};
|
|
|
|
${owncloudVendor} = {
|
|
displayName = owncloudName;
|
|
module = mkOwncloudProvider { };
|
|
};
|
|
|
|
proton = rec {
|
|
displayName = "Proton";
|
|
module = mkProtonProvider { inherit displayName; };
|
|
};
|
|
};
|
|
in {
|
|
options = {
|
|
programs.rclone = {
|
|
enable = lib.mkEnableOption "rclone";
|
|
|
|
systemdDependencies = mkSystemdDependencyOption {
|
|
default = {};
|
|
global = true;
|
|
};
|
|
|
|
globalArgs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
description = "The arguments to pass to `rclone mount` for each configuration.";
|
|
default = [ ];
|
|
};
|
|
|
|
configs = (builtins.mapAttrs
|
|
(name: provider: lib.mkOption {
|
|
type = lib.types.attrsOf (lib.types.submodule provider.module);
|
|
description = "The ${provider.displayName} synchronizations to set up.";
|
|
default = { };
|
|
})
|
|
syncProviders);
|
|
};
|
|
};
|
|
|
|
config = {
|
|
home.packages = lib.optionals cfg.enable [
|
|
pkgs.fuse
|
|
pkgs.rclone
|
|
];
|
|
|
|
systemd.user = lib.optionalAttrs cfg.enable {
|
|
enable = true;
|
|
|
|
services = {
|
|
rclone = {
|
|
Unit = {
|
|
Description = "rclone Service Starter";
|
|
Documentation = "man:rclone(1)";
|
|
};
|
|
|
|
Service = {
|
|
Type = "simple";
|
|
|
|
ExecStartPre =
|
|
let
|
|
script = pkgs.writeShellApplication {
|
|
name = "rclone-pre";
|
|
runtimeInputs = [
|
|
pkgs.coreutils
|
|
];
|
|
|
|
text = ''
|
|
sleep 10
|
|
'';
|
|
};
|
|
in
|
|
(lib.getExe script);
|
|
|
|
ExecStart =
|
|
let
|
|
script = pkgs.writeShellApplication {
|
|
name = "rclone";
|
|
runtimeInputs = [
|
|
pkgs.systemd
|
|
];
|
|
text = ''
|
|
systemctl --user start rclone.target
|
|
'';
|
|
};
|
|
in
|
|
(lib.getExe script);
|
|
};
|
|
|
|
Install = {
|
|
WantedBy = [
|
|
"default.target"
|
|
];
|
|
};
|
|
};
|
|
} // (
|
|
lib.attrsets.concatMapAttrs
|
|
(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}";
|
|
After = builtins.concatLists (builtins.attrValues sync.systemdDependencies);
|
|
};
|
|
|
|
Service = {
|
|
Environment = lib.mapAttrsToList
|
|
(key: val: (lib.escapeShellArg "${key}=${val}"))
|
|
sync.environment;
|
|
|
|
ExecStart =
|
|
let
|
|
configFile = pkgs.writeText
|
|
"${serviceName}.conf"
|
|
(lib.generators.toINI { } { ${name} = sync.config; });
|
|
script = pkgs.writeShellApplication {
|
|
name = serviceName;
|
|
|
|
runtimeInputs = [
|
|
pkgs.coreutils
|
|
pkgs.rclone
|
|
(dirOf osConfig.security.wrapperDir)
|
|
];
|
|
|
|
text = ''
|
|
${sync.secretsScript}
|
|
mkdir -p ${sync.path}
|
|
mkdir -p /tmp/rclone
|
|
rclone mount ${
|
|
builtins.concatStringsSep " " (cfg.globalArgs ++ sync.args)
|
|
} --config ${configFile} ${name}: ${sync.path}
|
|
'';
|
|
};
|
|
in
|
|
(lib.getExe script);
|
|
ExecStop =
|
|
let
|
|
script = pkgs.writeShellApplication {
|
|
name = "${serviceName}-stop";
|
|
|
|
runtimeInputs = [
|
|
(dirOf osConfig.security.wrapperDir)
|
|
];
|
|
|
|
text = ''
|
|
furermount -zu ${sync.path}
|
|
'';
|
|
};
|
|
in
|
|
(lib.getExe script);
|
|
};
|
|
|
|
Install = {
|
|
WantedBy = lib.optional sync.autoStart "${targetName}.target";
|
|
};
|
|
};
|
|
})
|
|
configs)
|
|
cfg.configs);
|
|
|
|
targets.${targetName} = {
|
|
Unit = {
|
|
Description = "rclone Mounts";
|
|
Documentation = "man:rclone(1)";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|