Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge dev #32

Merged
merged 26 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a839e2f
create usenet user + group
rlad78 Mar 31, 2024
ec83143
usenet mediaDir creation + permissions fixes
rlad78 Mar 31, 2024
3e5a11c
init sabnzbd module
rlad78 Apr 23, 2024
1b7e27d
integrate sabnzbd module
rlad78 Apr 23, 2024
593bb4c
conditionally create usenet dl dirs
rlad78 Apr 23, 2024
77b2a58
add sabnzbd vpn config
rlad78 Apr 23, 2024
a3d0d94
try not to use path type when it may not exist at build
rlad78 Apr 23, 2024
b4e816d
pre-exec script to handle necessary directory and network config
rlad78 Apr 26, 2024
3594605
restart sabnzbd service after init config generation
rlad78 Apr 29, 2024
e966661
run temp instance of sabnzbd to generate config file
rlad78 May 7, 2024
bc85cdc
make sabnzbd gui port a user option
rlad78 May 9, 2024
18bf490
remove commented-out sabnzbd ExecStartPost script
rlad78 May 9, 2024
2e2e3ff
set sabnzbd state files to user-only access permissions
rlad78 May 9, 2024
f835dee
move default config generation to separate module
rlad78 May 11, 2024
90629ff
add ExecStartPre script to fix possible sabnzbd permissions issue
rlad78 May 16, 2024
0344e47
fix language on sabnzbd config edited by nixarr
rlad78 May 16, 2024
f20993e
remove dependency on sabnzbd default config and use sed for editing
rlad78 May 21, 2024
2af829d
use configobj for post-install config changes
rlad78 Jun 4, 2024
41619cd
treefmt-nix formatting
rlad78 Jun 4, 2024
2e791cb
media group needs write permissions for usenet downloads (for arrs to…
rlad78 Jun 7, 2024
e695de9
set usenet arr dirs to 775 so that arrs can remove subdirs on import
rlad78 Jun 9, 2024
88c99d2
Passthrough the credentialsFile transmission option
jsecchiero Jun 23, 2024
37d728f
Merge pull request #29 from jsecchiero/main
rasmus-kirk Jun 26, 2024
d524d09
remove unneeded pkgs import and depricated StartLimitInterval
rlad78 Jul 14, 2024
f9d626b
simplify sabnzbd config file generation
rlad78 Jul 14, 2024
f5a6859
Merge pull request #24 from rlad78/sabnzbd
rasmus-kirk Jul 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions nixarr/nixarr.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 ''
Expand All @@ -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 {} + \)
Expand Down Expand Up @@ -70,6 +74,7 @@ in {
./openssh
./prowlarr
./transmission
./sabnzbd
../util
];

Expand Down Expand Up @@ -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.
'';
Expand Down Expand Up @@ -231,6 +237,7 @@ in {
media.members = cfg.mediaUsers;
streamer = {};
torrenter = {};
usenet = {};
};
users.users = {
streamer = {
Expand All @@ -241,6 +248,10 @@ in {
isSystemUser = true;
group = "torrenter";
};
usenet = {
isSystemUser = true;
group = "usenet";
};
};

systemd.tmpfiles.rules = [
Expand All @@ -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; [
Expand Down
85 changes: 85 additions & 0 deletions nixarr/sabnzbd/config.nix
Original file line number Diff line number Diff line change
@@ -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")
];
}
154 changes: 154 additions & 0 deletions nixarr/sabnzbd/default.nix
Original file line number Diff line number Diff line change
@@ -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}";
};
};
};
};
}
12 changes: 12 additions & 0 deletions nixarr/transmission/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@ in {
description = "Transmission web-UI port.";
};

credentialsFile = mkOption {
type = types.path;
description = ''
Path to a JSON file to be merged with the settings.
Useful to merge a file which is better kept out of the Nix store
to set secret config parameters like `rpc-password`.
'';
default = "/dev/null";
example = "/var/lib/secrets/transmission/settings.json";
};

extraSettings = mkOption {
type = types.attrs;
default = {};
Expand Down Expand Up @@ -339,6 +350,7 @@ in {
package = pkgs.transmission_4;
openRPCPort = cfg.openFirewall;
openPeerPorts = cfg.openFirewall;
credentialsFile = cfg.credentialsFile;
settings =
{
download-dir = downloadDir;
Expand Down
Loading