feat: split pyload
This commit is contained in:
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
./modules/firefox-sync.nix
|
./modules/firefox-sync.nix
|
||||||
./modules/fivefilters.nix
|
./modules/fivefilters.nix
|
||||||
./modules/pyload.nix
|
./modules/pyload
|
||||||
|
|
||||||
# home assistant
|
# home assistant
|
||||||
./modules/home-assistant
|
./modules/home-assistant
|
||||||
|
|||||||
@@ -1,320 +0,0 @@
|
|||||||
{ config, pkgs, ... }:
|
|
||||||
let
|
|
||||||
cids = import ./staticids.nix;
|
|
||||||
networkPrefix = config.networkPrefix;
|
|
||||||
|
|
||||||
# FileBot post-processing script for PyLoad hooks
|
|
||||||
filebotScript = pkgs.writeShellScript "filebot-process.sh" ''
|
|
||||||
#!/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
|
|
||||||
'';
|
|
||||||
|
|
||||||
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}"
|
|
||||||
];
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
|
||||||
"unrar"
|
|
||||||
"filebot"
|
|
||||||
];
|
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
unrar # Required for RAR archive extraction
|
|
||||||
p7zip # Required for 7z and other archive formats
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# 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" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# 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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
153
hosts/fw/modules/pyload/default.nix
Normal file
153
hosts/fw/modules/pyload/default.nix
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
{ 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
81
hosts/fw/modules/pyload/filebot-process.nix
Normal file
81
hosts/fw/modules/pyload/filebot-process.nix
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{ 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
|
||||||
|
''
|
||||||
36
hosts/fw/modules/pyload/jellyfin.nix
Normal file
36
hosts/fw/modules/pyload/jellyfin.nix
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{ 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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
57
hosts/fw/modules/pyload/pyload.nix
Normal file
57
hosts/fw/modules/pyload/pyload.nix
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{ 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" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user