258 lines
7.7 KiB
Nix
258 lines
7.7 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
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`.
|
|
'';
|
|
};
|
|
|
|
package = mkPackageOption pkgs "sabnzbd" {};
|
|
|
|
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 = literalExpression ''[ config.networking.hostName ]'';
|
|
example = literalExpression ''[ "mediaserv" "media.example.com" ]'';
|
|
description = ''
|
|
A list that specifies what URLs that are allowed to represent your
|
|
SABnzbd instance.
|
|
|
|
> **Note:** 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 ("`hostname.com`" above) 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 = [];
|
|
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.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = let
|
|
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 =
|
|
pkgs.writers.writePython3Bin "sabnzbd-set-user-values" {
|
|
libraries = [pkgs.python3Packages.configobj];
|
|
} ''
|
|
# flake8: noqa
|
|
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
|
|
mkIf cfg.enable {
|
|
users = {
|
|
groups.usenet = {};
|
|
users.usenet = {
|
|
isSystemUser = true;
|
|
group = "usenet";
|
|
};
|
|
};
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d '${cfg.stateDir}' 0700 usenet root - -"
|
|
"C ${cfg.stateDir}/sabnzbd.ini - - - - ${ini-base-config-file}"
|
|
|
|
# Media dirs
|
|
"d '${nixarr.mediaDir}/usenet' 0755 usenet media - -"
|
|
"d '${nixarr.mediaDir}/usenet/.incomplete' 0755 usenet media - -"
|
|
"d '${nixarr.mediaDir}/usenet/.watch' 0755 usenet media - -"
|
|
"d '${nixarr.mediaDir}/usenet/manual' 0775 usenet media - -"
|
|
"d '${nixarr.mediaDir}/usenet/liadarr' 0775 usenet media - -"
|
|
"d '${nixarr.mediaDir}/usenet/radarr' 0775 usenet media - -"
|
|
"d '${nixarr.mediaDir}/usenet/sonarr' 0775 usenet media - -"
|
|
"d '${nixarr.mediaDir}/usenet/readarr' 0775 usenet media - -"
|
|
];
|
|
|
|
services.sabnzbd = {
|
|
enable = true;
|
|
package = cfg.package;
|
|
user = "usenet";
|
|
group = "media";
|
|
configFile = "${cfg.stateDir}/sabnzbd.ini";
|
|
};
|
|
|
|
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.guiPort];
|
|
|
|
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")
|
|
];
|
|
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}";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|