diff --git a/hosts/fw/configuration.nix b/hosts/fw/configuration.nix index be2f706..9437bb2 100644 --- a/hosts/fw/configuration.nix +++ b/hosts/fw/configuration.nix @@ -49,7 +49,7 @@ ./modules/firefox-sync.nix ./modules/fivefilters.nix - ./modules/pyload + ./modules/pyload.nix # home assistant ./modules/home-assistant diff --git a/hosts/fw/modules/pyload.nix b/hosts/fw/modules/pyload.nix new file mode 100644 index 0000000..616e9e5 --- /dev/null +++ b/hosts/fw/modules/pyload.nix @@ -0,0 +1,300 @@ +{ config, pkgs, ... }: +let + cids = import ./staticids.nix; + networkPrefix = config.networkPrefix; + + # FileBot post-processing script + filebotScript = pkgs.writeShellScript "filebot-process.sh" '' + #!/usr/bin/env bash + set -euo pipefail + + # FileBot AMC script for automated media organization + # Arguments: $1 = download directory (passed by pyload) + + DOWNLOAD_DIR="''${1:-/downloads}" + OUTPUT_DIR="/multimedia" + LOG_FILE="/var/lib/filebot/amc.log" + EXCLUDE_LIST="/var/lib/filebot/amc-exclude-list.txt" + + # Ensure log directory exists + mkdir -p "$(dirname "$LOG_FILE")" + touch "$EXCLUDE_LIST" + + echo "$(date): Starting FileBot processing for: $DOWNLOAD_DIR" >> "$LOG_FILE" + + # Run FileBot AMC script + ${pkgs.filebot}/bin/filebot \ + -script fn:amc \ + --output "$OUTPUT_DIR" \ + --action move \ + --conflict auto \ + -non-strict \ + --log-file "$LOG_FILE" \ + --def \ + excludeList="$EXCLUDE_LIST" \ + movieFormat="$OUTPUT_DIR/movies/{n} ({y})/{n} ({y}) - {vf}" \ + seriesFormat="$OUTPUT_DIR/tv-shows/{n}/Season {s.pad(2)}/{n} - {s00e00} - {t}" \ + ut_dir="$DOWNLOAD_DIR" \ + ut_kind=multi \ + clean=y \ + skipExtract=y + + # Clean up empty directories + find "$DOWNLOAD_DIR" -type d -empty -delete 2>/dev/null || true + + echo "$(date): FileBot processing completed" >> "$LOG_FILE" + ''; + + pyloadUser = { + isSystemUser = true; + uid = cids.uids.pyload; + group = "pyload"; + home = "/var/lib/pyload"; + createHome = true; + }; + pyloadGroup = { + gid = cids.gids.pyload; + }; + + jellyfinUser = { + isSystemUser = true; + uid = cids.uids.jellyfin; + group = "jellyfin"; + home = "/var/lib/jellyfin"; + createHome = true; + extraGroups = [ "render" "video" ]; + }; + jellyfinGroup = { + gid = cids.gids.jellyfin; + }; + + filebotUser = { + isSystemUser = true; + uid = cids.uids.filebot; + group = "filebot"; + home = "/var/lib/filebot"; + createHome = true; + extraGroups = [ "pyload" "jellyfin" ]; # Access to both download and media directories + }; + filebotGroup = { + gid = cids.gids.filebot; + }; +in +{ + users.users.pyload = pyloadUser; + users.groups.pyload = pyloadGroup; + users.users.jellyfin = jellyfinUser; + users.groups.jellyfin = jellyfinGroup; + users.users.filebot = filebotUser; + users.groups.filebot = filebotGroup; + + # Create the directory structure on the host + systemd.tmpfiles.rules = [ + "d /var/lib/downloads 0755 pyload pyload - -" + "d /var/lib/multimedia 0775 root jellyfin - -" + "d /var/lib/multimedia/movies 0775 jellyfin jellyfin - -" + "d /var/lib/multimedia/tv-shows 0775 jellyfin jellyfin - -" + "d /var/lib/multimedia/music 0755 jellyfin jellyfin - -" + "d /var/lib/jellyfin 0755 jellyfin jellyfin - -" + "d /var/lib/filebot 0755 filebot filebot - -" + ]; + + # FileBot license secret + sops.secrets.filebot-license = { + mode = "0440"; + owner = config.users.users.root.name; + group = config.users.groups.root.name; + }; + + containers.pyload = { + autoStart = true; + ephemeral = false; + privateNetwork = true; + hostBridge = "server"; + hostAddress = "${networkPrefix}.97.1"; + localAddress = "${networkPrefix}.97.11/24"; + + # GPU device passthrough for hardware transcoding + allowedDevices = [ + { + modifier = "rwm"; + node = "/dev/dri/card0"; + } + { + modifier = "rwm"; + node = "/dev/dri/renderD128"; + } + ]; + + bindMounts = { + "/dev/dri" = { + hostPath = "/dev/dri"; + isReadOnly = false; + }; + "/run/opengl-driver" = { + hostPath = "/run/opengl-driver"; + isReadOnly = true; + }; + "/nix/store" = { + hostPath = "/nix/store"; + isReadOnly = true; + }; + "/var/lib/pyload" = { + hostPath = "/var/lib/pyload"; + isReadOnly = false; + }; + "/var/lib/jellyfin" = { + hostPath = "/var/lib/jellyfin"; + isReadOnly = false; + }; + "/downloads" = { + hostPath = "/var/lib/downloads"; + isReadOnly = false; + }; + "/multimedia" = { + hostPath = "/var/lib/multimedia"; + isReadOnly = false; + }; + "/var/lib/filebot" = { + hostPath = "/var/lib/filebot"; + isReadOnly = false; + }; + "/var/lib/filebot/license.psm" = { + hostPath = config.sops.secrets.filebot-license.path; + isReadOnly = true; + }; + }; + + config = { lib, config, pkgs, ... }: { + nixpkgs.overlays = [ + (import ../utils/overlays/packages.nix) + ]; + + + nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ + "unrar" + "filebot" + ]; + + environment.systemPackages = with pkgs; [ + unrar # Required for RAR archive extraction + filebot # Automated media file organization + ]; + + # Intel graphics support for hardware transcoding + hardware.graphics = { + enable = true; + extraPackages = with pkgs; [ + intel-media-driver + vpl-gpu-rt + intel-compute-runtime + ]; + }; + + # Set VA-API driver to iHD (modern Intel driver for N100) + environment.sessionVariables = { + LIBVA_DRIVER_NAME = "iHD"; + }; + + networking = { + hostName = "pyload"; + useHostResolvConf = false; + defaultGateway = { + address = "${networkPrefix}.97.1"; + interface = "eth0"; + }; + nameservers = [ "${networkPrefix}.97.1" ]; + firewall.enable = false; + }; + + services.pyload = { + enable = true; + downloadDirectory = "/downloads"; + listenAddress = "0.0.0.0"; + port = 8000; + }; + + services.jellyfin = { + enable = true; + openFirewall = true; + }; + + # Override systemd hardening for GPU access + systemd.services.jellyfin = { + serviceConfig = { + PrivateUsers = lib.mkForce false; # Disable user namespacing - breaks GPU device access + DeviceAllow = [ + "/dev/dri/card0 rw" + "/dev/dri/renderD128 rw" + ]; + SupplementaryGroups = [ "render" "video" ]; # Critical: Explicit group membership for GPU access + }; + environment = { + LIBVA_DRIVER_NAME = "iHD"; # Ensure service sees this variable + }; + }; + + # Disable SSL certificate verification + systemd.services.pyload = { + environment = { + PYLOAD__GENERAL__SSL_VERIFY = "0"; + + # Enable ExtractArchive plugin + PYLOAD__EXTRACTARCHIVE__ENABLED = "1"; + PYLOAD__EXTRACTARCHIVE__DELETE = "1"; + PYLOAD__EXTRACTARCHIVE__DELTOTRASH = "0"; + PYLOAD__EXTRACTARCHIVE__REPAIR = "1"; + PYLOAD__EXTRACTARCHIVE__RECURSIVE = "1"; + PYLOAD__EXTRACTARCHIVE__FULLPATH = "1"; + }; + + # Bind-mount DNS configuration files and system tools into the chroot + serviceConfig = { + BindReadOnlyPaths = [ + "/etc/resolv.conf" + "/etc/nsswitch.conf" + "/etc/hosts" + "/etc/ssl" + "/etc/static/ssl" + # Make all system packages (including unrar and filebot) accessible + "/run/current-system/sw/bin" + ]; + }; + }; + + # FileBot processing service + systemd.services.filebot-process = { + description = "FileBot media file processing"; + serviceConfig = { + Type = "oneshot"; + User = "filebot"; + Group = "filebot"; + ExecStart = "${filebotScript}"; + }; + }; + + # Watch for completed downloads and trigger FileBot + systemd.paths.filebot-watch = { + description = "Watch for completed downloads"; + wantedBy = [ "multi-user.target" ]; + pathConfig = { + PathModified = "/downloads"; + Unit = "filebot-process.service"; + }; + }; + + # Ensure render/video groups exist with consistent GIDs for GPU access + users.groups.render = { gid = 303; }; + users.groups.video = { gid = 26; }; + + users.users.pyload = pyloadUser; + users.groups.pyload = pyloadGroup; + users.users.jellyfin = jellyfinUser; + users.groups.jellyfin = jellyfinGroup; + users.users.filebot = filebotUser; + users.groups.filebot = filebotGroup; + + system.stateVersion = "24.05"; + }; + }; +} diff --git a/hosts/fw/modules/pyload/default.nix b/hosts/fw/modules/pyload/default.nix deleted file mode 100644 index 758e58a..0000000 --- a/hosts/fw/modules/pyload/default.nix +++ /dev/null @@ -1,153 +0,0 @@ -{ config, pkgs, ... }: -let - cids = import ../staticids.nix; - networkPrefix = config.networkPrefix; - filebotScript = pkgs.callPackage ./filebot-process.nix {}; - - pyloadUser = { - isSystemUser = true; - uid = cids.uids.pyload; - group = "pyload"; - home = "/var/lib/pyload"; - createHome = true; - extraGroups = [ "jellyfin" ]; # Access to multimedia directories - }; - pyloadGroup = { - gid = cids.gids.pyload; - }; - - jellyfinUser = { - isSystemUser = true; - uid = cids.uids.jellyfin; - group = "jellyfin"; - home = "/var/lib/jellyfin"; - createHome = true; - extraGroups = [ "render" "video" ]; - }; - jellyfinGroup = { - gid = cids.gids.jellyfin; - }; -in -{ - users.users.pyload = pyloadUser; - users.groups.pyload = pyloadGroup; - users.users.jellyfin = jellyfinUser; - users.groups.jellyfin = jellyfinGroup; - - # Create the directory structure on the host - systemd.tmpfiles.rules = [ - "d /var/lib/downloads 0755 pyload pyload - -" - "d /var/lib/multimedia 0775 root jellyfin - -" - "d /var/lib/multimedia/movies 0775 jellyfin jellyfin - -" - "d /var/lib/multimedia/tv-shows 0775 jellyfin jellyfin - -" - "d /var/lib/multimedia/music 0755 jellyfin jellyfin - -" - "d /var/lib/jellyfin 0755 jellyfin jellyfin - -" - - # PyLoad hook scripts directory - "d /var/lib/pyload/config 0755 pyload pyload - -" - "d /var/lib/pyload/config/scripts 0755 pyload pyload - -" - "d /var/lib/pyload/config/scripts/package_extracted 0755 pyload pyload - -" - "L+ /var/lib/pyload/config/scripts/package_extracted/filebot-process.sh - - - - ${filebotScript}/bin/filebot-process" - ]; - - # FileBot license secret - sops.secrets.filebot-license = { - mode = "0440"; - owner = "pyload"; - group = "pyload"; - }; - - containers.pyload = { - autoStart = true; - ephemeral = false; - privateNetwork = true; - hostBridge = "server"; - hostAddress = "${networkPrefix}.97.1"; - localAddress = "${networkPrefix}.97.11/24"; - - # GPU device passthrough for hardware transcoding - allowedDevices = [ - { - modifier = "rwm"; - node = "/dev/dri/card0"; - } - { - modifier = "rwm"; - node = "/dev/dri/renderD128"; - } - ]; - - bindMounts = { - "/dev/dri" = { - hostPath = "/dev/dri"; - isReadOnly = false; - }; - "/run/opengl-driver" = { - hostPath = "/run/opengl-driver"; - isReadOnly = true; - }; - "/nix/store" = { - hostPath = "/nix/store"; - isReadOnly = true; - }; - "/var/lib/pyload" = { - hostPath = "/var/lib/pyload"; - isReadOnly = false; - }; - "/var/lib/jellyfin" = { - hostPath = "/var/lib/jellyfin"; - isReadOnly = false; - }; - "/downloads" = { - hostPath = "/var/lib/downloads"; - isReadOnly = false; - }; - "/multimedia" = { - hostPath = "/var/lib/multimedia"; - isReadOnly = false; - }; - "/var/lib/pyload/filebot-license.psm" = { - hostPath = config.sops.secrets.filebot-license.path; - isReadOnly = true; - }; - }; - - config = { lib, config, pkgs, ... }: { - nixpkgs.overlays = [ - (import ../../utils/overlays/packages.nix) - ]; - - imports = [ - ./pyload.nix - ./jellyfin.nix - ]; - - nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ - "unrar" - "filebot" - ]; - - networking = { - hostName = "pyload"; - useHostResolvConf = false; - defaultGateway = { - address = "${networkPrefix}.97.1"; - interface = "eth0"; - }; - nameservers = [ "${networkPrefix}.97.1" ]; - firewall.enable = false; - }; - - # Ensure render/video groups exist with consistent GIDs for GPU access - users.groups.render = { gid = 303; }; - users.groups.video = { gid = 26; }; - - users.users.pyload = pyloadUser; - users.groups.pyload = pyloadGroup; - users.users.jellyfin = jellyfinUser; - users.groups.jellyfin = jellyfinGroup; - - system.stateVersion = "24.05"; - }; - }; -} diff --git a/hosts/fw/modules/pyload/filebot-process.nix b/hosts/fw/modules/pyload/filebot-process.nix deleted file mode 100644 index 404a3fb..0000000 --- a/hosts/fw/modules/pyload/filebot-process.nix +++ /dev/null @@ -1,81 +0,0 @@ -{ pkgs }: - -pkgs.writeShellScriptBin "filebot-process" '' - #!/usr/bin/env bash - set -euo pipefail - - # FileBot AMC script for automated media organization - # Called by PyLoad's package_extracted hook with parameters: - # $1 = package_id - # $2 = package_name - # $3 = download_folder (actual path to extracted files) - # $4 = password (optional) - - PACKAGE_ID="''${1:-}" - PACKAGE_NAME="''${2:-unknown}" - DOWNLOAD_DIR="''${3:-/downloads}" - PASSWORD="''${4:-}" - - OUTPUT_DIR="/multimedia" - LOG_FILE="/var/lib/pyload/filebot-amc.log" - EXCLUDE_LIST="/var/lib/pyload/filebot-exclude-list.txt" - - # Ensure FileBot data directory exists - mkdir -p /var/lib/pyload/.local/share/filebot/data - mkdir -p "$(dirname "$LOG_FILE")" - touch "$EXCLUDE_LIST" - - # Install FileBot license if not already installed - if [ ! -f /var/lib/pyload/.local/share/filebot/data/.license ]; then - echo "$(date): Installing FileBot license..." >> "$LOG_FILE" - ${pkgs.filebot}/bin/filebot --license /var/lib/pyload/filebot-license.psm || true - fi - - echo "===========================================" >> "$LOG_FILE" - echo "$(date): PyLoad package extracted hook triggered" >> "$LOG_FILE" - echo "Package ID: $PACKAGE_ID" >> "$LOG_FILE" - echo "Package Name: $PACKAGE_NAME" >> "$LOG_FILE" - echo "Download Directory: $DOWNLOAD_DIR" >> "$LOG_FILE" - echo "===========================================" >> "$LOG_FILE" - - # Check if download directory exists and has media files - if [ ! -d "$DOWNLOAD_DIR" ]; then - echo "$(date): Download directory does not exist: $DOWNLOAD_DIR" >> "$LOG_FILE" - exit 0 - fi - - # Check if there are any video/media files to process - if ! find "$DOWNLOAD_DIR" -type f \( -iname "*.mkv" -o -iname "*.mp4" -o -iname "*.avi" -o -iname "*.m4v" -o -iname "*.mov" \) -print -quit | grep -q .; then - echo "$(date): No media files found in: $DOWNLOAD_DIR" >> "$LOG_FILE" - echo "$(date): Skipping FileBot processing" >> "$LOG_FILE" - exit 0 - fi - - echo "$(date): Starting FileBot processing" >> "$LOG_FILE" - - # Run FileBot AMC script - ${pkgs.filebot}/bin/filebot \ - -script fn:amc \ - --output "$OUTPUT_DIR" \ - --action move \ - --conflict auto \ - -non-strict \ - --log-file "$LOG_FILE" \ - --def \ - excludeList="$EXCLUDE_LIST" \ - movieFormat="$OUTPUT_DIR/movies/{n} ({y})/{n} ({y}) - {vf}" \ - seriesFormat="$OUTPUT_DIR/tv-shows/{n}/Season {s.pad(2)}/{n} - {s00e00} - {t}" \ - ut_dir="$DOWNLOAD_DIR" \ - ut_kind=multi \ - clean=y \ - skipExtract=y || { - echo "$(date): FileBot processing failed with exit code $?" >> "$LOG_FILE" - exit 0 # Don't fail the hook even if FileBot fails - } - - # Clean up empty directories - find "$DOWNLOAD_DIR" -type d -empty -delete 2>/dev/null || true - - echo "$(date): FileBot processing completed successfully" >> "$LOG_FILE" - exit 0 -'' diff --git a/hosts/fw/modules/pyload/jellyfin.nix b/hosts/fw/modules/pyload/jellyfin.nix deleted file mode 100644 index 12bf825..0000000 --- a/hosts/fw/modules/pyload/jellyfin.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ lib, pkgs, ... }: { - # Intel graphics support for hardware transcoding - hardware.graphics = { - enable = true; - extraPackages = with pkgs; [ - intel-media-driver - vpl-gpu-rt - intel-compute-runtime - ]; - }; - - # Set VA-API driver to iHD (modern Intel driver for N100) - environment.sessionVariables = { - LIBVA_DRIVER_NAME = "iHD"; - }; - - services.jellyfin = { - enable = true; - openFirewall = true; - }; - - # Override systemd hardening for GPU access - systemd.services.jellyfin = { - serviceConfig = { - PrivateUsers = lib.mkForce false; # Disable user namespacing - breaks GPU device access - DeviceAllow = [ - "/dev/dri/card0 rw" - "/dev/dri/renderD128 rw" - ]; - SupplementaryGroups = [ "render" "video" ]; # Critical: Explicit group membership for GPU access - }; - environment = { - LIBVA_DRIVER_NAME = "iHD"; # Ensure service sees this variable - }; - }; -} diff --git a/hosts/fw/modules/pyload/pyload.nix b/hosts/fw/modules/pyload/pyload.nix deleted file mode 100644 index c4c9401..0000000 --- a/hosts/fw/modules/pyload/pyload.nix +++ /dev/null @@ -1,57 +0,0 @@ -{ pkgs, ... }: -{ - environment.systemPackages = with pkgs; [ - unrar # Required for RAR archive extraction - p7zip # Required for 7z and other archive formats - ]; - - services.pyload = { - enable = true; - downloadDirectory = "/downloads"; - listenAddress = "0.0.0.0"; - port = 8000; - }; - - # Configure pyload service - systemd.services.pyload = { - # Add extraction tools to service PATH - path = with pkgs; [ - unrar # For RAR extraction - p7zip # For 7z extraction - ]; - - environment = { - # Disable SSL certificate verification - PYLOAD__GENERAL__SSL_VERIFY = "0"; - - # Download speed limiting (150 Mbit/s = 19200 KiB/s) - PYLOAD__DOWNLOAD__LIMIT_SPEED = "1"; - PYLOAD__DOWNLOAD__MAX_SPEED = "19200"; - - # Enable ExtractArchive plugin - PYLOAD__EXTRACTARCHIVE__ENABLED = "1"; - PYLOAD__EXTRACTARCHIVE__DELETE = "1"; - PYLOAD__EXTRACTARCHIVE__DELTOTRASH = "0"; - PYLOAD__EXTRACTARCHIVE__REPAIR = "1"; - PYLOAD__EXTRACTARCHIVE__RECURSIVE = "1"; - PYLOAD__EXTRACTARCHIVE__FULLPATH = "1"; - - # Enable ExternalScripts plugin for hooks - PYLOAD__EXTERNALSCRIPTS__ENABLED = "1"; - PYLOAD__EXTERNALSCRIPTS__UNLOCK = "1"; # Run hooks asynchronously - }; - - # Bind-mount DNS configuration files into the chroot - serviceConfig = { - BindReadOnlyPaths = [ - "/etc/resolv.conf" - "/etc/nsswitch.conf" - "/etc/hosts" - "/etc/ssl" - "/etc/static/ssl" - ]; - # Bind mount multimedia directory as writable for FileBot hook scripts - BindPaths = [ "/multimedia" ]; - }; - }; -} diff --git a/utils/overlays/packages.nix b/utils/overlays/packages.nix index 56e0abf..811ae3b 100644 --- a/utils/overlays/packages.nix +++ b/utils/overlays/packages.nix @@ -14,7 +14,7 @@ self: super: { }; python3Packages = self.python3.pkgs; - pyload-ng = self.callPackage ../pkgs/pyload-ng { pyload-ng = super.pyload-ng; }; + pyload-ng = self.callPackage ../pkgs/pyload-ng-updated.nix { pyload-ng = super.pyload-ng; }; # vscode-insiders = (super.callPackage ../pkgs/vscode-insiders.nix { }); } diff --git a/utils/pkgs/pyload-ng/default.nix b/utils/pkgs/pyload-ng-updated.nix similarity index 90% rename from utils/pkgs/pyload-ng/default.nix rename to utils/pkgs/pyload-ng-updated.nix index 01003bf..195eebf 100644 --- a/utils/pkgs/pyload-ng/default.nix +++ b/utils/pkgs/pyload-ng-updated.nix @@ -10,10 +10,6 @@ pyload-ng.overridePythonAttrs (oldAttrs: rec { hash = "sha256-g1eEeNnr3Axtr+0BJzMcNQomTEX4EsUG1Jxt+huPyoc="; }; - patches = [ - ./patches/declarative-env-config.patch - ]; - # Add new dependencies required in newer versions propagatedBuildInputs = (oldAttrs.propagatedBuildInputs or []) ++ (with python3Packages; [ mini-racer diff --git a/utils/pkgs/pyload-ng/patches/declarative-env-config.patch b/utils/pkgs/pyload-ng/patches/declarative-env-config.patch deleted file mode 100644 index 589a041..0000000 --- a/utils/pkgs/pyload-ng/patches/declarative-env-config.patch +++ /dev/null @@ -1,38 +0,0 @@ -diff --git a/src/pyload/core/__init__.py b/src/pyload/core/__init__.py -index 4324fc700..5d915a85e 100644 ---- a/src/pyload/core/__init__.py -+++ b/src/pyload/core/__init__.py -@@ -130,6 +130,14 @@ class Core: - else: - self._debug = max(0, int(debug)) - -+ # Process core config environment variables (NixOS declarative config) -+ for env, value in os.environ.items(): -+ if not env.startswith("PYLOAD__"): -+ continue -+ parts = env.removeprefix("PYLOAD__").lower().split("__", 1) -+ if len(parts) == 2 and parts[0] in self.config.config: -+ self.config.set(parts[0], parts[1], value) -+ - # If no argument set, read storage dir from config file, - # otherwise save setting to config dir - if storagedir is None: -@@ -227,6 +235,18 @@ class Core: - self.thm = self.thread_manager = ThreadManager(self) - self.cpm = self.captcha_manager = CaptchaManager(self) - self.adm = self.addon_manager = AddonManager(self) -+ -+ # Process plugin config environment variables after plugins are loaded (NixOS declarative config) -+ # Build case-insensitive lookup map for plugin names -+ plugin_name_map = {name.lower(): name for name in self.config.plugin.keys()} -+ -+ for env, value in os.environ.items(): -+ if not env.startswith("PYLOAD__"): -+ continue -+ parts = env.removeprefix("PYLOAD__").lower().split("__", 1) -+ if len(parts) == 2 and parts[0] in plugin_name_map: -+ actual_plugin_name = plugin_name_map[parts[0]] -+ self.config.set_plugin(actual_plugin_name, parts[1], value) - - def _setup_permissions(self): - self.log.debug("Setup permissions...")