feat(roles): add rclone-mount role for WebDAV mounts
Some checks failed
CI / check (push) Failing after 12m14s
Some checks failed
CI / check (push) Failing after 12m14s
Add a new system-level role for mounting WebDAV filesystems via rclone. Includes rclone-torbox-setup helper script that uses rbw to bootstrap credentials from Bitwarden. Key features: - Configurable WebDAV URL, username, mount point - VFS cache mode and buffer size tuning for media streaming - RequiresMountsFor option for ZFS pool dependencies - Obscured password storage via environment file Enable on john-endesktop for TorBox WebDAV access by rdt-client and Jellyfin. Mount waits for /media ZFS pool before starting. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -90,6 +90,8 @@ with lib;
|
||||
htop
|
||||
tmux
|
||||
zfs
|
||||
rclone
|
||||
custom.rclone-torbox-setup # Helper script to set up TorBox credentials via rbw
|
||||
];
|
||||
|
||||
# Enable SSH
|
||||
@@ -126,6 +128,26 @@ with lib;
|
||||
|
||||
roles.virtualisation.enable = true;
|
||||
|
||||
# TorBox WebDAV mount for rdt-client and Jellyfin
|
||||
roles.rclone-mount = {
|
||||
enable = true;
|
||||
mounts.torbox = {
|
||||
webdavUrl = "https://webdav.torbox.app";
|
||||
username = "john@ogle.fyi"; # TorBox account email
|
||||
mountPoint = "/media/media/torbox-rclone";
|
||||
environmentFile = "/etc/rclone/torbox.env";
|
||||
vfsCacheMode = "full"; # Best for streaming media
|
||||
dirCacheTime = "5m";
|
||||
extraArgs = [
|
||||
"--buffer-size=64M"
|
||||
"--vfs-read-chunk-size=32M"
|
||||
"--vfs-read-chunk-size-limit=off"
|
||||
];
|
||||
# Wait for ZFS media pool to be mounted before starting
|
||||
requiresMountsFor = [ "/media" ];
|
||||
};
|
||||
};
|
||||
|
||||
# Time zone
|
||||
time.timeZone = "America/Los_Angeles"; # Adjust as needed
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
app-launcher-server = pkgs.callPackage ./app-launcher-server {};
|
||||
claude-code = pkgs.callPackage ./claude-code {};
|
||||
mcrcon-rbw = pkgs.callPackage ./mcrcon-rbw {};
|
||||
rclone-torbox-setup = pkgs.callPackage ./rclone-torbox-setup {};
|
||||
}
|
||||
|
||||
98
packages/rclone-torbox-setup/default.nix
Normal file
98
packages/rclone-torbox-setup/default.nix
Normal file
@@ -0,0 +1,98 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
pkgs.writeShellScriptBin "rclone-torbox-setup" ''
|
||||
set -euo pipefail
|
||||
|
||||
# Default values
|
||||
RBW_ENTRY="''${1:-torbox}"
|
||||
ENV_FILE="''${2:-/etc/rclone/torbox.env}"
|
||||
|
||||
usage() {
|
||||
echo "Usage: rclone-torbox-setup [rbw-entry] [env-file]"
|
||||
echo ""
|
||||
echo "Sets up rclone credentials for TorBox WebDAV mount."
|
||||
echo "Retrieves password from rbw (Bitwarden), obscures it for rclone,"
|
||||
echo "and writes it to the environment file for the systemd service."
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " rbw-entry Name of the Bitwarden entry containing the password (default: torbox)"
|
||||
echo " env-file Path to write the environment file (default: /etc/rclone/torbox.env)"
|
||||
echo ""
|
||||
echo "The Bitwarden entry should contain your TorBox password as the password field."
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " rclone-torbox-setup torbox-password /etc/rclone/torbox.env"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ "''${1:-}" == "-h" ]] || [[ "''${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
echo "rclone TorBox credential setup"
|
||||
echo "=============================="
|
||||
echo ""
|
||||
|
||||
# Check if rbw is available
|
||||
if ! command -v rbw &> /dev/null; then
|
||||
echo "Error: rbw is not available. Please ensure rbw is installed and configured."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if rclone is available
|
||||
if ! command -v rclone &> /dev/null; then
|
||||
echo "Error: rclone is not available. Please ensure rclone is installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Retrieving password from rbw entry: $RBW_ENTRY"
|
||||
|
||||
# Retrieve password from Bitwarden
|
||||
if ! TORBOX_PASS=$(rbw get "$RBW_ENTRY" 2>/dev/null); then
|
||||
echo ""
|
||||
echo "Error: Failed to retrieve password from rbw entry '$RBW_ENTRY'"
|
||||
echo ""
|
||||
echo "Please ensure:"
|
||||
echo " 1. The entry '$RBW_ENTRY' exists in Bitwarden"
|
||||
echo " 2. rbw is unlocked: rbw unlock"
|
||||
echo " 3. rbw is synced: rbw sync"
|
||||
echo ""
|
||||
echo "To create the entry in Bitwarden:"
|
||||
echo " - Name: $RBW_ENTRY"
|
||||
echo " - Password: Your TorBox password"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Password retrieved successfully"
|
||||
|
||||
# Obscure the password for rclone
|
||||
echo "Obscuring password for rclone..."
|
||||
if ! OBSCURED_PASS=$(echo -n "$TORBOX_PASS" | rclone obscure -); then
|
||||
echo "Error: Failed to obscure password with rclone"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create the directory if needed (requires sudo)
|
||||
ENV_DIR=$(dirname "$ENV_FILE")
|
||||
if [[ ! -d "$ENV_DIR" ]]; then
|
||||
echo "Creating directory $ENV_DIR (requires sudo)..."
|
||||
sudo mkdir -p "$ENV_DIR"
|
||||
fi
|
||||
|
||||
# Write the environment file
|
||||
echo "Writing environment file to $ENV_FILE (requires sudo)..."
|
||||
echo "RCLONE_WEBDAV_PASS=$OBSCURED_PASS" | sudo tee "$ENV_FILE" > /dev/null
|
||||
sudo chmod 600 "$ENV_FILE"
|
||||
|
||||
echo ""
|
||||
echo "Setup complete!"
|
||||
echo ""
|
||||
echo "The environment file has been created at: $ENV_FILE"
|
||||
echo "The rclone-mount-torbox systemd service will use this file."
|
||||
echo ""
|
||||
echo "To activate the mount after NixOS rebuild:"
|
||||
echo " sudo systemctl start rclone-mount-torbox"
|
||||
echo ""
|
||||
echo "To check status:"
|
||||
echo " sudo systemctl status rclone-mount-torbox"
|
||||
''
|
||||
@@ -14,6 +14,7 @@ with lib;
|
||||
./nfs-mounts
|
||||
./nvidia
|
||||
./printing
|
||||
./rclone-mount
|
||||
./remote-build
|
||||
./spotifyd
|
||||
./users
|
||||
|
||||
149
roles/rclone-mount/default.nix
Normal file
149
roles/rclone-mount/default.nix
Normal file
@@ -0,0 +1,149 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.roles.rclone-mount;
|
||||
|
||||
# Generate systemd service for a single mount
|
||||
mkMountService = name: mountCfg: {
|
||||
description = "rclone mount for ${name}";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
# Wait for parent mount points (e.g., ZFS pools) to be available
|
||||
unitConfig = mkIf (mountCfg.requiresMountsFor != []) {
|
||||
RequiresMountsFor = mountCfg.requiresMountsFor;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${mountCfg.mountPoint}";
|
||||
ExecStart = concatStringsSep " " ([
|
||||
"${pkgs.rclone}/bin/rclone mount"
|
||||
":webdav:${mountCfg.remotePath}"
|
||||
"${mountCfg.mountPoint}"
|
||||
"--webdav-url=${mountCfg.webdavUrl}"
|
||||
"--webdav-vendor=${mountCfg.webdavVendor}"
|
||||
"--webdav-user=${mountCfg.username}"
|
||||
"--allow-other"
|
||||
"--vfs-cache-mode=${mountCfg.vfsCacheMode}"
|
||||
"--dir-cache-time=${mountCfg.dirCacheTime}"
|
||||
"--poll-interval=${mountCfg.pollInterval}"
|
||||
"--log-level=${mountCfg.logLevel}"
|
||||
] ++ mountCfg.extraArgs);
|
||||
ExecStop = "${pkgs.fuse}/bin/fusermount -uz ${mountCfg.mountPoint}";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
EnvironmentFile = mountCfg.environmentFile;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options.roles.rclone-mount = {
|
||||
enable = mkEnableOption "Enable rclone WebDAV mounts";
|
||||
|
||||
mounts = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
webdavUrl = mkOption {
|
||||
type = types.str;
|
||||
description = "WebDAV server URL (e.g., https://webdav.torbox.app)";
|
||||
};
|
||||
|
||||
webdavVendor = mkOption {
|
||||
type = types.enum [ "other" "nextcloud" "owncloud" "sharepoint" "sharepoint-ntlm" "fastmail" ];
|
||||
default = "other";
|
||||
description = "WebDAV server vendor for optimizations";
|
||||
};
|
||||
|
||||
username = mkOption {
|
||||
type = types.str;
|
||||
description = "WebDAV username (often email address)";
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to environment file containing RCLONE_WEBDAV_PASS.
|
||||
The password should be obscured using: rclone obscure <password>
|
||||
File format: RCLONE_WEBDAV_PASS=<obscured_password>
|
||||
'';
|
||||
};
|
||||
|
||||
mountPoint = mkOption {
|
||||
type = types.str;
|
||||
description = "Local mount point path";
|
||||
};
|
||||
|
||||
remotePath = mkOption {
|
||||
type = types.str;
|
||||
default = "/";
|
||||
description = "Remote path on WebDAV server to mount";
|
||||
};
|
||||
|
||||
vfsCacheMode = mkOption {
|
||||
type = types.enum [ "off" "minimal" "writes" "full" ];
|
||||
default = "full";
|
||||
description = ''
|
||||
VFS cache mode. For streaming media, 'full' is recommended.
|
||||
- off: No caching (direct reads/writes)
|
||||
- minimal: Cache open files only
|
||||
- writes: Cache writes and open files
|
||||
- full: Full caching of all files
|
||||
'';
|
||||
};
|
||||
|
||||
dirCacheTime = mkOption {
|
||||
type = types.str;
|
||||
default = "5m";
|
||||
description = "Time to cache directory entries";
|
||||
};
|
||||
|
||||
pollInterval = mkOption {
|
||||
type = types.str;
|
||||
default = "1m";
|
||||
description = "Poll interval for remote changes";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum [ "DEBUG" "INFO" "NOTICE" "ERROR" ];
|
||||
default = "INFO";
|
||||
description = "rclone log level";
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Extra arguments to pass to rclone mount";
|
||||
};
|
||||
|
||||
requiresMountsFor = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
List of mount points that must be available before this service starts.
|
||||
Use this when the mount point's parent is on a ZFS pool or other filesystem
|
||||
that may not be mounted at boot time.
|
||||
Example: [ "/media" ] to wait for the media ZFS pool to mount.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
description = "Attribute set of rclone WebDAV mounts to configure";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Ensure FUSE is available
|
||||
environment.systemPackages = [ pkgs.rclone pkgs.fuse ];
|
||||
programs.fuse.userAllowOther = true;
|
||||
|
||||
# Create systemd services for each mount
|
||||
systemd.services = mapAttrs' (name: mountCfg:
|
||||
nameValuePair "rclone-mount-${name}" (mkMountService name mountCfg)
|
||||
) cfg.mounts;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user