Add rar2fs mount service
This commit is contained in:
parent
50dedefe79
commit
ab489e3bd8
5 changed files with 181 additions and 1 deletions
|
|
@ -6,7 +6,7 @@
|
|||
./dyndns.nix
|
||||
./esphome
|
||||
./home-assistant.nix
|
||||
./jellyfin.nix
|
||||
./jellyfin
|
||||
./mail.nix
|
||||
./matrix.nix
|
||||
./navidrome.nix
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ let
|
|||
inherit (config.networking) ports;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./rar2fs.nix
|
||||
];
|
||||
|
||||
services.jellyfin = {
|
||||
enable = true;
|
||||
};
|
||||
62
hosts/iron/services/jellyfin/rar2fs.nix
Normal file
62
hosts/iron/services/jellyfin/rar2fs.nix
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
rar2fs = pkgs.rar2fs.override { unrar = pkgs.unrar_6; };
|
||||
rar2fs_mounts = pkgs.writeScriptBin "rar2fs_mounts" (lib.strings.concatLines [
|
||||
"#!${pkgs.python3}/bin/python"
|
||||
(builtins.readFile ./rar2fs_mounts.py)
|
||||
]);
|
||||
rar_path = "/var/lib/qBittorrent/downloads";
|
||||
mount_path = "/run/jellyfin/rar2fs";
|
||||
in
|
||||
{
|
||||
programs.fuse = {
|
||||
userAllowOther = true;
|
||||
mountMax = 1000;
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
rar2fs
|
||||
];
|
||||
|
||||
systemd.services.jellyfin-rar2fs = {
|
||||
after = [ "jellyfin.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ rar2fs "/run/wrappers/bin" ];
|
||||
environment.USER = "jellyfin";
|
||||
serviceConfig = {
|
||||
AmbientCapabilities = "CAP_SYS_ADMIN CAP_SETUID CAP_SETGID";
|
||||
CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_SETUID CAP_SETGID";
|
||||
DeviceAllow = "/dev/fuse rw";
|
||||
ExecStart = "${rar2fs_mounts}/bin/rar2fs_mounts ${rar_path} ${mount_path}";
|
||||
Group = "jellyfin";
|
||||
IPAddressDeny = "any";
|
||||
LockPersonality = true;
|
||||
NoNewPrivileges = "no";
|
||||
PrivateDevices = false;
|
||||
PrivateMounts = false;
|
||||
PrivateTmp = false;
|
||||
PrivateUsers = false;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = false; # implies MountAPIVFS
|
||||
ProtectHome = false;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = false;
|
||||
ProtectKernelModules = false;
|
||||
ProtectKernelTunables = false; # implies MountAPIVFS
|
||||
#ProtectProc = "noaccess"; # implies MountAPIVFS
|
||||
ProtectSystem = false;
|
||||
RestrictAddressFamilies = "none";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"@mount"
|
||||
"@setuid"
|
||||
"umount2"
|
||||
];
|
||||
User = "jellyfin";
|
||||
};
|
||||
};
|
||||
}
|
||||
112
hosts/iron/services/jellyfin/rar2fs_mounts.py
Normal file
112
hosts/iron/services/jellyfin/rar2fs_mounts.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
from pathlib import Path
|
||||
import argparse
|
||||
import errno
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
mounts = {}
|
||||
|
||||
|
||||
class RarMount:
|
||||
process = None
|
||||
|
||||
@property
|
||||
def mountpoint(self):
|
||||
result = self.mount_root / self.rar_file.relative_to(self.rar_root).parent
|
||||
return result
|
||||
|
||||
def __init__(self, mount_root: str, rar_file: Path, rar_root: Path):
|
||||
self.mount_root = mount_root
|
||||
self.rar_file = rar_file
|
||||
self.rar_root = rar_root
|
||||
|
||||
os.makedirs(self.mountpoint, exist_ok=True)
|
||||
|
||||
print(f"Mounting '{self.rar_file}' at '{self.mountpoint}'")
|
||||
self.process = subprocess.Popen(
|
||||
[
|
||||
"rar2fs",
|
||||
"-f",
|
||||
"-o",
|
||||
"auto_unmount",
|
||||
"-o",
|
||||
"allow_other",
|
||||
"--no-inherit-perm",
|
||||
self.rar_file,
|
||||
self.mountpoint,
|
||||
]
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
self.process.communicate()
|
||||
|
||||
for i in range(10):
|
||||
try:
|
||||
os.rmdir(self.mountpoint)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except OSError as ex:
|
||||
# if ex.errno == errno.ENOEMPTY:
|
||||
# break
|
||||
if ex.errno == errno.EBUSY:
|
||||
time.sleep(1)
|
||||
raise
|
||||
else:
|
||||
break
|
||||
|
||||
for dir in self.mountpoint.relative_to(self.mount_root).parents:
|
||||
try:
|
||||
os.rmdir(self.mount_root.joinpath(dir))
|
||||
except OSError as ex:
|
||||
pass
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
for rar_file, mount in mounts.items():
|
||||
del mount
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Recursively globs a path containing rar files and mounts them under a given mount path."
|
||||
)
|
||||
|
||||
parser.add_argument("rar_path", type=Path, help="Path to the RAR directory")
|
||||
|
||||
parser.add_argument("mount_path", type=Path, help="Path to the mount directory")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
if not args.rar_path.is_dir():
|
||||
parser.error(f"RAR path '{args.rar_path}' is not a valid directory.")
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
for rar_file in args.rar_path.rglob("*.rar"):
|
||||
if rar_file in mounts:
|
||||
continue
|
||||
if len(rar_file.parts) >= 2 and rar_file.parts[-2].lower() in ["subs", "proof"]:
|
||||
continue
|
||||
|
||||
mounts[rar_file] = RarMount(args.mount_path, rar_file, args.rar_path)
|
||||
|
||||
while True:
|
||||
time.sleep(600)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
{
|
||||
nixpkgs.config.allowUnfreePredicate = pkg: lib.elem (lib.getName pkg) [
|
||||
"mongodb"
|
||||
"rar2fs"
|
||||
"roomeqwizard"
|
||||
"unifi-controller"
|
||||
"unrar"
|
||||
];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue