Merge pull request #24 from rlad78/sabnzbd
WIP: SABnzbd module for usenet integration
This commit is contained in:
@@ -27,6 +27,7 @@ with lib; let
|
||||
fi
|
||||
|
||||
chown -R torrenter:media "${cfg.mediaDir}/torrents"
|
||||
chown -R usenet:media "${cfg.mediaDir}/usenet"
|
||||
chown -R streamer:media "${cfg.mediaDir}/library"
|
||||
find "${cfg.mediaDir}" \( -type d -exec chmod 0775 {} + -true \) -o \( -exec chmod 0664 {} + \)
|
||||
'' + strings.optionalString cfg.jellyfin.enable ''
|
||||
@@ -35,6 +36,9 @@ with lib; let
|
||||
'' + strings.optionalString cfg.transmission.enable ''
|
||||
chown -R torrenter:cross-seed "${cfg.transmission.stateDir}"
|
||||
find "${cfg.transmission.stateDir}" \( -type d -exec chmod 0750 {} + -true \) -o \( -exec chmod 0640 {} + \)
|
||||
'' + strings.optionalString cfg.sabnzbd.enable ''
|
||||
chown -R usenet:root "${cfg.sabnzbd.stateDir}"
|
||||
find "${cfg.sabnzbd.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
|
||||
'' + strings.optionalString cfg.transmission.privateTrackers.cross-seed.enable ''
|
||||
chown -R cross-seed:root "${cfg.transmission.privateTrackers.cross-seed.stateDir}"
|
||||
find "${cfg.transmission.privateTrackers.cross-seed.stateDir}" \( -type d -exec chmod 0700 {} + -true \) -o \( -exec chmod 0600 {} + \)
|
||||
@@ -70,6 +74,7 @@ in {
|
||||
./openssh
|
||||
./prowlarr
|
||||
./transmission
|
||||
./sabnzbd
|
||||
../util
|
||||
];
|
||||
|
||||
@@ -105,6 +110,7 @@ in {
|
||||
- [Readarr](#nixarr.readarr.enable)
|
||||
- [Sonarr](#nixarr.sonarr.enable)
|
||||
- [Transmission](#nixarr.transmission.enable)
|
||||
- [SABnzbd](#nixarr.sabnzbd.enable)
|
||||
|
||||
Remember to read the options.
|
||||
'';
|
||||
@@ -231,6 +237,7 @@ in {
|
||||
media.members = cfg.mediaUsers;
|
||||
streamer = {};
|
||||
torrenter = {};
|
||||
usenet = {};
|
||||
};
|
||||
users.users = {
|
||||
streamer = {
|
||||
@@ -241,6 +248,10 @@ in {
|
||||
isSystemUser = true;
|
||||
group = "torrenter";
|
||||
};
|
||||
usenet = {
|
||||
isSystemUser = true;
|
||||
group = "usenet";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
@@ -259,6 +270,16 @@ in {
|
||||
"d '${cfg.mediaDir}/torrents/radarr' 0755 torrenter media - -"
|
||||
"d '${cfg.mediaDir}/torrents/sonarr' 0755 torrenter media - -"
|
||||
"d '${cfg.mediaDir}/torrents/readarr' 0755 torrenter media - -"
|
||||
] ++ lists.optionals cfg.sabnzbd.enable [
|
||||
# only create usenet dirs if sabnzbd is enabled
|
||||
"d '${cfg.mediaDir}/usenet' 0755 usenet media - -"
|
||||
"d '${cfg.mediaDir}/usenet/.incomplete' 0755 usenet media - -"
|
||||
"d '${cfg.mediaDir}/usenet/.watch' 0755 usenet media - -"
|
||||
"d '${cfg.mediaDir}/usenet/manual' 0775 usenet media - -"
|
||||
"d '${cfg.mediaDir}/usenet/liadarr' 0775 usenet media - -"
|
||||
"d '${cfg.mediaDir}/usenet/radarr' 0775 usenet media - -"
|
||||
"d '${cfg.mediaDir}/usenet/sonarr' 0775 usenet media - -"
|
||||
"d '${cfg.mediaDir}/usenet/readarr' 0775 usenet media - -"
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
cfg = config.nixarr.sabnzbd;
|
||||
nixarr = config.nixarr;
|
||||
ini-file-target = "${cfg.stateDir}/sabnzbd.ini";
|
||||
concatStringsCommaIfExists = with lib.strings;
|
||||
stringList: (
|
||||
optionalString (builtins.length stringList > 0) (
|
||||
concatStringsSep "," stringList
|
||||
)
|
||||
);
|
||||
|
||||
user-configs = {
|
||||
misc = {
|
||||
host =
|
||||
if cfg.openFirewall
|
||||
then "0.0.0.0"
|
||||
else "127.0.0.1";
|
||||
port = cfg.guiPort;
|
||||
download_dir = "${nixarr.mediaDir}/usenet/.incomplete";
|
||||
complete_dir = "${nixarr.mediaDir}/usenet/manual";
|
||||
dirscan_dir = "${nixarr.mediaDir}/usenet/watch";
|
||||
host_whitelist = concatStringsCommaIfExists cfg.whitelistHostnames;
|
||||
local_ranges = concatStringsCommaIfExists cfg.whitelistRanges;
|
||||
permissions = "775";
|
||||
};
|
||||
};
|
||||
|
||||
ini-base-config-file = pkgs.writeTextFile {
|
||||
name = "base-config.ini";
|
||||
text = lib.generators.toINI {} user-configs;
|
||||
};
|
||||
|
||||
fix-config-permissions-script = pkgs.writeShellApplication {
|
||||
name = "sabnzbd-fix-config-permissions";
|
||||
runtimeInputs = with pkgs; [util-linux];
|
||||
text = ''
|
||||
if [ ! -f ${ini-file-target} ]; then
|
||||
echo 'FAILURE: cannot change permissions of ${ini-file-target}, file does not exist'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod 600 ${ini-file-target}
|
||||
chown usenet:media ${ini-file-target}
|
||||
'';
|
||||
};
|
||||
|
||||
user-configs-to-python-list = with lib;
|
||||
attrsets.collect (f: !builtins.isAttrs f) (
|
||||
attrsets.mapAttrsRecursive (
|
||||
path: value:
|
||||
"sab_config_map['"
|
||||
+ (lib.strings.concatStringsSep "']['" path)
|
||||
+ "'] = '"
|
||||
+ (builtins.toString value)
|
||||
+ "'"
|
||||
)
|
||||
user-configs
|
||||
);
|
||||
apply-user-configs-script = with lib; (pkgs.writers.writePython3Bin
|
||||
"sabnzbd-set-user-values" {libraries = [pkgs.python3Packages.configobj];} ''
|
||||
from pathlib import Path
|
||||
from configobj import ConfigObj
|
||||
|
||||
sab_config_path = Path("${ini-file-target}")
|
||||
if not sab_config_path.is_file() or sab_config_path.suffix != ".ini":
|
||||
raise Exception(f"{sab_config_path} is not a valid config file path.")
|
||||
|
||||
sab_config_map = ConfigObj(str(sab_config_path))
|
||||
|
||||
${lib.strings.concatStringsSep "\n" user-configs-to-python-list}
|
||||
|
||||
sab_config_map.write()
|
||||
'');
|
||||
in {
|
||||
systemd.tmpfiles.rules = ["C ${cfg.stateDir}/sabnzbd.ini - - - - ${ini-base-config-file}"];
|
||||
systemd.services.sabnzbd.serviceConfig.ExecStartPre = lib.mkBefore [
|
||||
("+" + fix-config-permissions-script + "/bin/sabnzbd-fix-config-permissions")
|
||||
(apply-user-configs-script + "/bin/sabnzbd-set-user-values")
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.nixarr.sabnzbd;
|
||||
nixarr = config.nixarr;
|
||||
in {
|
||||
options.nixarr.sabnzbd = {
|
||||
enable = mkEnableOption "Enable the SABnzbd service.";
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${nixarr.stateDir}/sabnzbd";
|
||||
defaultText = literalExpression ''"''${nixarr.stateDir}/sabnzbd"'';
|
||||
example = "/nixarr/.state/sabnzbd";
|
||||
description = ''
|
||||
The location of the state directory for the SABnzbd service.
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/sabnzbd
|
||||
```
|
||||
|
||||
Is not supported, because `/home/user` is owned by `user`.
|
||||
'';
|
||||
};
|
||||
|
||||
guiPort = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
example = 9999;
|
||||
description = ''
|
||||
The port that SABnzbd's GUI will listen on for incomming connections.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
defaultText = literalExpression ''!nixarr.SABnzbd.vpn.enable'';
|
||||
default = !cfg.vpn.enable;
|
||||
example = true;
|
||||
description = "Open firewall for SABnzbd";
|
||||
};
|
||||
|
||||
whitelistHostnames = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [config.networking.hostName];
|
||||
defaultText = "[ config.networking.hostName ]";
|
||||
example = ''[ "mediaserv" "media.example.com" ]'';
|
||||
description = ''
|
||||
A list that specifies what URLs that are allowed to represent your
|
||||
SABnzbd instance. If you see an error message like this when
|
||||
trying to connect to SABnzbd from another device...
|
||||
|
||||
```
|
||||
Refused connection with hostname "your.hostname.com"
|
||||
```
|
||||
|
||||
...then you should add your hostname(s) to this list.
|
||||
|
||||
SABnzbd only allows connections matching these URLs in order to prevent
|
||||
DNS hijacking. See <https://sabnzbd.org/wiki/extra/hostname-check.html>
|
||||
for more info.
|
||||
'';
|
||||
};
|
||||
|
||||
whitelistRanges = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
defaultText = "[ ]";
|
||||
example = ''[ "192.168.1.0/24" "10.0.0.0/23" ]'';
|
||||
description = ''
|
||||
A list of IP ranges that will be allowed to connect to SABnzbd's
|
||||
web GUI. This only needs to be set if SABnzbd needs to be accessed
|
||||
from another machine besides its host.
|
||||
'';
|
||||
};
|
||||
|
||||
vpn.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
**Required options:** [`nixarr.vpn.enable`](#nixarr.vpn.enable)
|
||||
|
||||
Route SABnzbd traffic through the VPN.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
imports = [./config.nix];
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' 0700 usenet root - -"
|
||||
];
|
||||
|
||||
services.sabnzbd = {
|
||||
enable = true;
|
||||
user = "usenet";
|
||||
group = "media";
|
||||
configFile = "${cfg.stateDir}/sabnzbd.ini";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.guiPort];
|
||||
|
||||
systemd.services.sabnzbd.serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
StartLimitBurst = 5;
|
||||
};
|
||||
|
||||
# Enable and specify VPN namespace to confine service in.
|
||||
systemd.services.sabnzbd.vpnconfinement = mkIf cfg.vpn.enable {
|
||||
enable = true;
|
||||
vpnnamespace = "wg";
|
||||
};
|
||||
|
||||
# Port mappings
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [
|
||||
{
|
||||
from = cfg.guiPort;
|
||||
to = cfg.guiPort;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = mkIf cfg.vpn.enable {
|
||||
enable = true;
|
||||
|
||||
recommendedTlsSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedGzipSettings = true;
|
||||
|
||||
virtualHosts."127.0.0.1:${builtins.toString cfg.guiPort}" = {
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = cfg.guiPort;
|
||||
}
|
||||
];
|
||||
locations."/" = {
|
||||
recommendedProxySettings = true;
|
||||
proxyWebsockets = true;
|
||||
proxyPass = "http://192.168.15.1:${builtins.toString cfg.guiPort}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user