Add rar2fs mount service

This commit is contained in:
Jakob Lechner 2025-10-06 20:31:08 +02:00
parent 50dedefe79
commit ab489e3bd8
5 changed files with 181 additions and 1 deletions

View file

@ -6,7 +6,7 @@
./dyndns.nix
./esphome
./home-assistant.nix
./jellyfin.nix
./jellyfin
./mail.nix
./matrix.nix
./navidrome.nix

View file

@ -3,6 +3,10 @@ let
inherit (config.networking) ports;
in
{
imports = [
./rar2fs.nix
];
services.jellyfin = {
enable = true;
};

View 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";
};
};
}

View 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())

View file

@ -3,7 +3,9 @@
{
nixpkgs.config.allowUnfreePredicate = pkg: lib.elem (lib.getName pkg) [
"mongodb"
"rar2fs"
"roomeqwizard"
"unifi-controller"
"unrar"
];
}