Formatted
This commit is contained in:
@@ -1,128 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
mail@rasmuskirk.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -29,7 +29,11 @@
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs @ {flake-parts, vpnconfinement, nixpkgs, ...}:
|
||||
outputs = inputs @ {
|
||||
flake-parts,
|
||||
vpnconfinement,
|
||||
...
|
||||
}:
|
||||
flake-parts.lib.mkFlake {
|
||||
inherit inputs;
|
||||
} {
|
||||
@@ -44,7 +48,7 @@
|
||||
|
||||
flake = {
|
||||
nixosModules = rec {
|
||||
nixarr = (import ./nixarr vpnconfinement);
|
||||
nixarr = import ./nixarr vpnconfinement;
|
||||
default = nixarr;
|
||||
};
|
||||
};
|
||||
@@ -52,7 +56,6 @@
|
||||
perSystem = {
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
treefmt.config = {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.util-nixarr.services.bazarr;
|
||||
in
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.util-nixarr.services.bazarr;
|
||||
in {
|
||||
options = {
|
||||
util-nixarr.services.bazarr = {
|
||||
enable = mkEnableOption ("bazarr, a subtitle manager for Sonarr and Radarr");
|
||||
enable = mkEnableOption "bazarr, a subtitle manager for Sonarr and Radarr";
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
@@ -49,8 +50,8 @@ in
|
||||
|
||||
systemd.services.bazarr = {
|
||||
description = "bazarr";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = ["network.target"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
@@ -68,7 +69,7 @@ in
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.listenPort ];
|
||||
allowedTCPPorts = [cfg.listenPort];
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "bazarr") {
|
||||
|
||||
@@ -10,7 +10,7 @@ in {
|
||||
imports = [
|
||||
./bazarr-module
|
||||
];
|
||||
|
||||
|
||||
options.nixarr.bazarr = {
|
||||
enable = mkEnableOption "the bazarr service.";
|
||||
|
||||
@@ -24,7 +24,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/bazarr
|
||||
```
|
||||
@@ -81,7 +81,12 @@ in {
|
||||
# Port mappings
|
||||
# TODO: openports
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [{ from = config.bazarr.listenPort; to = config.bazarr.listenPort; }];
|
||||
portMappings = [
|
||||
{
|
||||
from = config.bazarr.listenPort;
|
||||
to = config.bazarr.listenPort;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = mkIf cfg.vpn.enable {
|
||||
|
||||
@@ -9,7 +9,7 @@ with lib; let
|
||||
ddns-njalla = pkgs.writeShellApplication {
|
||||
name = "ddns-njalla";
|
||||
|
||||
runtimeInputs = with pkgs; [ curl jq ];
|
||||
runtimeInputs = with pkgs; [curl jq];
|
||||
|
||||
# Thanks chatgpt...
|
||||
text = ''
|
||||
@@ -118,10 +118,13 @@ in {
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = cfg.njalla.vpn.enable -> (
|
||||
cfg.njalla.vpn.keysFile != null &&
|
||||
config.nixarr.vpn.enable
|
||||
);
|
||||
assertion =
|
||||
cfg.njalla.vpn.enable
|
||||
-> (
|
||||
cfg.njalla.vpn.keysFile
|
||||
!= null
|
||||
&& config.nixarr.vpn.enable
|
||||
);
|
||||
message = ''
|
||||
The nixarr.ddns.njalla.enable option requires the
|
||||
nixarr.vpn.enable option to be set, but it was not.
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
vpnconfinement:
|
||||
{ ... }:
|
||||
{
|
||||
vpnconfinement: {...}: {
|
||||
imports = [
|
||||
vpnconfinement.nixosModules.default
|
||||
./nixarr.nix
|
||||
|
||||
+245
-236
@@ -2,273 +2,282 @@
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
}: let
|
||||
cfg = config.nixarr.jellyfin;
|
||||
defaultPort = 8096;
|
||||
nixarr = config.nixarr;
|
||||
in with lib; {
|
||||
options.nixarr.jellyfin = {
|
||||
enable = mkEnableOption "the Jellyfin service.";
|
||||
in
|
||||
with lib; {
|
||||
options.nixarr.jellyfin = {
|
||||
enable = mkEnableOption "the Jellyfin service.";
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${nixarr.stateDir}/jellyfin";
|
||||
defaultText = literalExpression ''"''${nixarr.stateDir}/jellyfin"'';
|
||||
example = "/nixarr/.state/jellyfin";
|
||||
description = ''
|
||||
The location of the state directory for the Jellyfin service.
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${nixarr.stateDir}/jellyfin";
|
||||
defaultText = literalExpression ''"''${nixarr.stateDir}/jellyfin"'';
|
||||
example = "/nixarr/.state/jellyfin";
|
||||
description = ''
|
||||
The location of the state directory for the Jellyfin 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/jellyfin
|
||||
```
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
Is not supported, because `/home/user` is owned by `user`.
|
||||
'';
|
||||
};
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/jellyfin
|
||||
```
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
defaultText = literalExpression ''!cfg.vpn.enable'';
|
||||
default = !cfg.vpn.enable;
|
||||
example = true;
|
||||
description = "Open firewall for Jellyfin";
|
||||
};
|
||||
|
||||
vpn.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
**Required options:** [`nixarr.vpn.enable`](#nixarr.vpn.enable)
|
||||
**Conflicting options:** [`nixarr.jellyfin.expose.https.enable`](#nixarr.jellyfin.expose.https.enable)
|
||||
|
||||
Route Jellyfin traffic through the VPN.
|
||||
'';
|
||||
};
|
||||
|
||||
expose = {
|
||||
vpn = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
**Required options:**
|
||||
|
||||
- [`nixarr.jellyfin.vpn.enable`](#nixarr.jellyfin.vpn.enable)
|
||||
- [`nixarr.jellyfin.expose.vpn.port`](#nixarr.jellyfin.expose.vpn.port)
|
||||
- [`nixarr.jellyfin.expose.vpn.accessibleFrom`](#nixarr.jellyfin.expose.vpn.accessiblefrom)
|
||||
|
||||
Expose the Jellyfin web service to the internet, allowing anyone to
|
||||
access it.
|
||||
|
||||
**Warning:** Do _not_ enable this without setting up Jellyfin
|
||||
authentication through localhost first!
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = with types; nullOr port;
|
||||
default = null;
|
||||
example = 12345;
|
||||
description = ''
|
||||
The port to access jellyfin on. Get this port from your VPN
|
||||
provider.
|
||||
'';
|
||||
};
|
||||
|
||||
accessibleFrom = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "jellyfin.airvpn.org";
|
||||
description = ''
|
||||
The IP or domain that Jellyfin should be able to be accessed from.
|
||||
'';
|
||||
};
|
||||
Is not supported, because `/home/user` is owned by `user`.
|
||||
'';
|
||||
};
|
||||
|
||||
https = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
**Required options:**
|
||||
|
||||
- [`nixarr.jellyfin.expose.https.acmeMail`](#nixarr.jellyfin.expose.https.acmemail)
|
||||
- [`nixarr.jellyfin.expose.https.domainName`](#nixarr.jellyfin.expose.https.domainname)
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
defaultText = literalExpression ''!cfg.vpn.enable'';
|
||||
default = !cfg.vpn.enable;
|
||||
example = true;
|
||||
description = "Open firewall for Jellyfin";
|
||||
};
|
||||
|
||||
**Conflicting options:** [`nixarr.jellyfin.vpn.enable`](#nixarr.jellyfin.vpn.enable)
|
||||
vpn.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
**Required options:** [`nixarr.vpn.enable`](#nixarr.vpn.enable)
|
||||
**Conflicting options:** [`nixarr.jellyfin.expose.https.enable`](#nixarr.jellyfin.expose.https.enable)
|
||||
|
||||
Expose the Jellyfin web service to the internet with https support,
|
||||
allowing anyone to access it.
|
||||
Route Jellyfin traffic through the VPN.
|
||||
'';
|
||||
};
|
||||
|
||||
**Warning:** Do _not_ enable this without setting up Jellyfin
|
||||
authentication through localhost first!
|
||||
'';
|
||||
expose = {
|
||||
vpn = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
**Required options:**
|
||||
|
||||
- [`nixarr.jellyfin.vpn.enable`](#nixarr.jellyfin.vpn.enable)
|
||||
- [`nixarr.jellyfin.expose.vpn.port`](#nixarr.jellyfin.expose.vpn.port)
|
||||
- [`nixarr.jellyfin.expose.vpn.accessibleFrom`](#nixarr.jellyfin.expose.vpn.accessiblefrom)
|
||||
|
||||
Expose the Jellyfin web service to the internet, allowing anyone to
|
||||
access it.
|
||||
|
||||
**Warning:** Do _not_ enable this without setting up Jellyfin
|
||||
authentication through localhost first!
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = with types; nullOr port;
|
||||
default = null;
|
||||
example = 12345;
|
||||
description = ''
|
||||
The port to access jellyfin on. Get this port from your VPN
|
||||
provider.
|
||||
'';
|
||||
};
|
||||
|
||||
accessibleFrom = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "jellyfin.airvpn.org";
|
||||
description = ''
|
||||
The IP or domain that Jellyfin should be able to be accessed from.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
upnp.enable = mkEnableOption "UPNP to try to open ports 80 and 443 on your router.";
|
||||
https = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
**Required options:**
|
||||
|
||||
domainName = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "jellyfin.example.com";
|
||||
description = "The domain name to host Jellyfin on.";
|
||||
};
|
||||
- [`nixarr.jellyfin.expose.https.acmeMail`](#nixarr.jellyfin.expose.https.acmemail)
|
||||
- [`nixarr.jellyfin.expose.https.domainName`](#nixarr.jellyfin.expose.https.domainname)
|
||||
|
||||
acmeMail = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "mail@example.com";
|
||||
description = "The ACME mail required for the letsencrypt bot.";
|
||||
**Conflicting options:** [`nixarr.jellyfin.vpn.enable`](#nixarr.jellyfin.vpn.enable)
|
||||
|
||||
Expose the Jellyfin web service to the internet with https support,
|
||||
allowing anyone to access it.
|
||||
|
||||
**Warning:** Do _not_ enable this without setting up Jellyfin
|
||||
authentication through localhost first!
|
||||
'';
|
||||
};
|
||||
|
||||
upnp.enable = mkEnableOption "UPNP to try to open ports 80 and 443 on your router.";
|
||||
|
||||
domainName = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "jellyfin.example.com";
|
||||
description = "The domain name to host Jellyfin on.";
|
||||
};
|
||||
|
||||
acmeMail = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "mail@example.com";
|
||||
description = "The ACME mail required for the letsencrypt bot.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
mkIf cfg.enable
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.vpn.enable -> nixarr.vpn.enable;
|
||||
message = ''
|
||||
The nixarr.jellyfin.vpn.enable option requires the
|
||||
nixarr.vpn.enable option to be set, but it was not.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.vpn.enable && cfg.expose.https.enable);
|
||||
message = ''
|
||||
The nixarr.jellyfin.vpn.enable option conflicts with the
|
||||
nixarr.jellyfin.expose.https.enable option. You cannot set both.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = cfg.expose.https.enable -> (
|
||||
(cfg.expose.https.domainName != null) &&
|
||||
(cfg.expose.https.acmeMail != null)
|
||||
);
|
||||
message = ''
|
||||
The nixarr.jellyfin.expose.https.enable option requires the
|
||||
following options to be set, but one of them were not:
|
||||
config =
|
||||
mkIf cfg.enable
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.vpn.enable -> nixarr.vpn.enable;
|
||||
message = ''
|
||||
The nixarr.jellyfin.vpn.enable option requires the
|
||||
nixarr.vpn.enable option to be set, but it was not.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !(cfg.vpn.enable && cfg.expose.https.enable);
|
||||
message = ''
|
||||
The nixarr.jellyfin.vpn.enable option conflicts with the
|
||||
nixarr.jellyfin.expose.https.enable option. You cannot set both.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
cfg.expose.https.enable
|
||||
-> (
|
||||
(cfg.expose.https.domainName != null)
|
||||
&& (cfg.expose.https.acmeMail != null)
|
||||
);
|
||||
message = ''
|
||||
The nixarr.jellyfin.expose.https.enable option requires the
|
||||
following options to be set, but one of them were not:
|
||||
|
||||
- nixarr.jellyfin.expose.domainName
|
||||
- nixarr.jellyfin.expose.acmeMail
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = cfg.expose.vpn.enable -> (
|
||||
cfg.vpn.enable &&
|
||||
(cfg.expose.vpn.port != null) &&
|
||||
(cfg.expose.vpn.accessibleFrom != null)
|
||||
);
|
||||
message = ''
|
||||
The nixarr.jellyfin.expose.vpn.enable option requires the
|
||||
following options to be set, but one of them were not:
|
||||
- nixarr.jellyfin.expose.domainName
|
||||
- nixarr.jellyfin.expose.acmeMail
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
cfg.expose.vpn.enable
|
||||
-> (
|
||||
cfg.vpn.enable
|
||||
&& (cfg.expose.vpn.port != null)
|
||||
&& (cfg.expose.vpn.accessibleFrom != null)
|
||||
);
|
||||
message = ''
|
||||
The nixarr.jellyfin.expose.vpn.enable option requires the
|
||||
following options to be set, but one of them were not:
|
||||
|
||||
- nixarr.jellyfin.vpn.enable
|
||||
- nixarr.jellyfin.expose.vpn.port
|
||||
- nixarr.jellyfin.expose.vpn.accessibleFrom
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' 0700 streamer root - -"
|
||||
];
|
||||
- nixarr.jellyfin.vpn.enable
|
||||
- nixarr.jellyfin.expose.vpn.port
|
||||
- nixarr.jellyfin.expose.vpn.accessibleFrom
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
services.jellyfin = {
|
||||
enable = cfg.enable;
|
||||
user = "streamer";
|
||||
group = "streamer";
|
||||
openFirewall = cfg.openFirewall;
|
||||
logDir = "${cfg.stateDir}/log";
|
||||
cacheDir = "${cfg.stateDir}/cache";
|
||||
dataDir = "${cfg.stateDir}/data";
|
||||
configDir = "${cfg.stateDir}/config";
|
||||
};
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' 0700 streamer root - -"
|
||||
];
|
||||
|
||||
networking.firewall = mkIf cfg.expose.https.enable {
|
||||
allowedTCPPorts = [80 443];
|
||||
};
|
||||
services.jellyfin = {
|
||||
enable = cfg.enable;
|
||||
user = "streamer";
|
||||
group = "streamer";
|
||||
openFirewall = cfg.openFirewall;
|
||||
logDir = "${cfg.stateDir}/log";
|
||||
cacheDir = "${cfg.stateDir}/cache";
|
||||
dataDir = "${cfg.stateDir}/data";
|
||||
configDir = "${cfg.stateDir}/config";
|
||||
};
|
||||
|
||||
util-nixarr.upnp = mkIf cfg.expose.https.upnp.enable {
|
||||
enable = true;
|
||||
openTcpPorts = [80 443];
|
||||
};
|
||||
networking.firewall = mkIf cfg.expose.https.enable {
|
||||
allowedTCPPorts = [80 443];
|
||||
};
|
||||
|
||||
services.nginx = mkMerge [
|
||||
(mkIf (cfg.expose.https.enable || cfg.vpn.enable) {
|
||||
util-nixarr.upnp = mkIf cfg.expose.https.upnp.enable {
|
||||
enable = true;
|
||||
openTcpPorts = [80 443];
|
||||
};
|
||||
|
||||
recommendedTlsSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedGzipSettings = true;
|
||||
})
|
||||
(mkIf cfg.expose.https.enable {
|
||||
virtualHosts."${builtins.replaceStrings ["\n"] [""] cfg.expose.https.domainName}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
recommendedProxySettings = true;
|
||||
proxyWebsockets = true;
|
||||
proxyPass = "http://127.0.0.1:${builtins.toString defaultPort}";
|
||||
services.nginx = mkMerge [
|
||||
(mkIf (cfg.expose.https.enable || cfg.vpn.enable) {
|
||||
enable = true;
|
||||
|
||||
recommendedTlsSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedGzipSettings = true;
|
||||
})
|
||||
(mkIf cfg.expose.https.enable {
|
||||
virtualHosts."${builtins.replaceStrings ["\n"] [""] cfg.expose.https.domainName}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
recommendedProxySettings = true;
|
||||
proxyWebsockets = true;
|
||||
proxyPass = "http://127.0.0.1:${builtins.toString defaultPort}";
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
(mkIf cfg.vpn.enable {
|
||||
virtualHosts."127.0.0.1:${builtins.toString defaultPort}" = mkIf cfg.vpn.enable {
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = defaultPort;
|
||||
}
|
||||
];
|
||||
locations."/" = {
|
||||
recommendedProxySettings = true;
|
||||
proxyWebsockets = true;
|
||||
proxyPass = "http://192.168.15.1:${builtins.toString defaultPort}";
|
||||
})
|
||||
(mkIf cfg.vpn.enable {
|
||||
virtualHosts."127.0.0.1:${builtins.toString defaultPort}" = mkIf cfg.vpn.enable {
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = defaultPort;
|
||||
}
|
||||
];
|
||||
locations."/" = {
|
||||
recommendedProxySettings = true;
|
||||
proxyWebsockets = true;
|
||||
proxyPass = "http://192.168.15.1:${builtins.toString defaultPort}";
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
(mkIf cfg.expose.vpn.enable {
|
||||
virtualHosts."${builtins.toString cfg.expose.vpn.accessibleFrom}:${builtins.toString cfg.expose.vpn.port}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
recommendedProxySettings = true;
|
||||
proxyWebsockets = true;
|
||||
proxyPass = "http://192.168.15.1:${builtins.toString defaultPort}";
|
||||
})
|
||||
(mkIf cfg.expose.vpn.enable {
|
||||
virtualHosts."${builtins.toString cfg.expose.vpn.accessibleFrom}:${builtins.toString cfg.expose.vpn.port}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
locations."/" = {
|
||||
recommendedProxySettings = true;
|
||||
proxyWebsockets = true;
|
||||
proxyPass = "http://192.168.15.1:${builtins.toString defaultPort}";
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
security.acme = mkIf cfg.expose.https.enable {
|
||||
acceptTerms = true;
|
||||
defaults.email = cfg.expose.https.acmeMail;
|
||||
};
|
||||
|
||||
# Enable and specify VPN namespace to confine service in.
|
||||
systemd.services.jellyfin.vpnconfinement = mkIf cfg.vpn.enable {
|
||||
enable = true;
|
||||
vpnnamespace = "wg";
|
||||
};
|
||||
|
||||
# Port mappings
|
||||
# TODO: openports if expose.vpn
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [
|
||||
{
|
||||
from = defaultPort;
|
||||
to = defaultPort;
|
||||
}
|
||||
];
|
||||
openVPNPorts = optional cfg.expose.vpn.enable {
|
||||
port = cfg.expose.vpn.port;
|
||||
protocol = "tcp";
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
security.acme = mkIf cfg.expose.https.enable {
|
||||
acceptTerms = true;
|
||||
defaults.email = cfg.expose.https.acmeMail;
|
||||
};
|
||||
|
||||
# Enable and specify VPN namespace to confine service in.
|
||||
systemd.services.jellyfin.vpnconfinement = mkIf cfg.vpn.enable {
|
||||
enable = true;
|
||||
vpnnamespace = "wg";
|
||||
};
|
||||
|
||||
# Port mappings
|
||||
# TODO: openports if expose.vpn
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [{ from = defaultPort; to = defaultPort; }];
|
||||
openVPNPorts = optional cfg.expose.vpn.enable {
|
||||
port = cfg.expose.vpn.port;
|
||||
protocol = "tcp";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/lidarr
|
||||
```
|
||||
@@ -78,7 +78,12 @@ in {
|
||||
# Port mappings
|
||||
# TODO: openports
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [{ from = defaultPort; to = defaultPort; }];
|
||||
portMappings = [
|
||||
{
|
||||
from = defaultPort;
|
||||
to = defaultPort;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = mkIf cfg.vpn.enable {
|
||||
|
||||
+33
-28
@@ -13,7 +13,6 @@ with lib; let
|
||||
find "$1" -type f -links 1 -exec du -h {} + | sort -h
|
||||
'';
|
||||
};
|
||||
|
||||
in {
|
||||
imports = [
|
||||
./jellyfin
|
||||
@@ -46,7 +45,7 @@ in {
|
||||
that they manage is located by default in `/data/.state/nixarr/*`
|
||||
- **Optional Automatic Port Forwarding:** This module has a UPNP support that
|
||||
lets services request ports from your router automatically, if you enable it.
|
||||
|
||||
|
||||
It is possible, _but not recommended_, to run the "*Arrs" behind a VPN,
|
||||
because it can cause rate limiting issues. Generally, you should use
|
||||
VPN on transmission and maybe jellyfin, depending on your setup.
|
||||
@@ -69,7 +68,7 @@ in {
|
||||
mediaUsers = mkOption {
|
||||
type = with types; listOf str;
|
||||
default = [];
|
||||
example = [ "user" ];
|
||||
example = ["user"];
|
||||
description = ''
|
||||
Extra users to add to the media group.
|
||||
'';
|
||||
@@ -84,7 +83,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
mediaDir = /home/user/nixarr
|
||||
```
|
||||
@@ -102,7 +101,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state
|
||||
```
|
||||
@@ -225,9 +224,9 @@ in {
|
||||
# TODO: wtf to do about openports
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
enable = true;
|
||||
openVPNPorts = optional cfg.vpn.vpnTestService.enable {
|
||||
port = cfg.vpn.vpnTestService.port;
|
||||
protocol = "tcp";
|
||||
openVPNPorts = optional cfg.vpn.vpnTestService.enable {
|
||||
port = cfg.vpn.vpnTestService.port;
|
||||
protocol = "tcp";
|
||||
};
|
||||
accessibleFrom = [
|
||||
"192.168.1.0/24"
|
||||
@@ -251,30 +250,36 @@ in {
|
||||
|
||||
runtimeInputs = with pkgs; [util-linux unixtools.ping coreutils curl bash libressl netcat-gnu openresolv dig];
|
||||
|
||||
text = ''
|
||||
cd "$(mktemp -d)"
|
||||
text =
|
||||
''
|
||||
cd "$(mktemp -d)"
|
||||
|
||||
# Print resolv.conf
|
||||
echo "/etc/resolv.conf contains:"
|
||||
cat /etc/resolv.conf
|
||||
# Print resolv.conf
|
||||
echo "/etc/resolv.conf contains:"
|
||||
cat /etc/resolv.conf
|
||||
|
||||
# Query resolvconf
|
||||
echo "resolvconf output:"
|
||||
resolvconf -l
|
||||
echo ""
|
||||
# Query resolvconf
|
||||
echo "resolvconf output:"
|
||||
resolvconf -l
|
||||
echo ""
|
||||
|
||||
# Get ip
|
||||
echo "Getting IP:"
|
||||
curl -s ipinfo.io
|
||||
# Get ip
|
||||
echo "Getting IP:"
|
||||
curl -s ipinfo.io
|
||||
|
||||
echo -ne "DNS leak test:"
|
||||
curl -s https://raw.githubusercontent.com/macvk/dnsleaktest/b03ab54d574adbe322ca48cbcb0523be720ad38d/dnsleaktest.sh -o dnsleaktest.sh
|
||||
chmod +x dnsleaktest.sh
|
||||
./dnsleaktest.sh
|
||||
'' + (if cfg.vpn.vpnTestService.port != null then ''
|
||||
echo "starting netcat on port ${builtins.toString cfg.vpn.vpnTestService.port}:"
|
||||
nc -vnlp ${builtins.toString cfg.vpn.vpnTestService.port}
|
||||
'' else "");
|
||||
echo -ne "DNS leak test:"
|
||||
curl -s https://raw.githubusercontent.com/macvk/dnsleaktest/b03ab54d574adbe322ca48cbcb0523be720ad38d/dnsleaktest.sh -o dnsleaktest.sh
|
||||
chmod +x dnsleaktest.sh
|
||||
./dnsleaktest.sh
|
||||
''
|
||||
+ (
|
||||
if cfg.vpn.vpnTestService.port != null
|
||||
then ''
|
||||
echo "starting netcat on port ${builtins.toString cfg.vpn.vpnTestService.port}:"
|
||||
nc -vnlp ${builtins.toString cfg.vpn.vpnTestService.port}
|
||||
''
|
||||
else ""
|
||||
);
|
||||
};
|
||||
in "${vpn-test}/bin/vpn-test";
|
||||
|
||||
|
||||
+35
-22
@@ -15,7 +15,7 @@ in {
|
||||
**Required options:** [`nixarr.vpn.enable`](#nixarr.vpn.enable)
|
||||
|
||||
Run the openssh service through a vpn, exposing it to the internet.
|
||||
|
||||
|
||||
**Warning:** This lets anyone on the internet connect through SSH,
|
||||
make sure the SSH configuration is secure! Disallowing password
|
||||
authentication and only allowing SSH-keys is considered secure.
|
||||
@@ -53,28 +53,31 @@ in {
|
||||
}
|
||||
];
|
||||
|
||||
warnings = if config.services.openssh.enable then [
|
||||
''
|
||||
nixarr.openssh.expose.vpn.enable is set, but openssh is not enabled
|
||||
on your system, so the openssh server is not running. This is probably
|
||||
not what you wanted. You can add the following lines to enable it:
|
||||
warnings =
|
||||
if config.services.openssh.enable
|
||||
then [
|
||||
''
|
||||
nixarr.openssh.expose.vpn.enable is set, but openssh is not enabled
|
||||
on your system, so the openssh server is not running. This is probably
|
||||
not what you wanted. You can add the following lines to enable it:
|
||||
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings.PasswordAuthentication = false;
|
||||
# Get this port from your VPN provider
|
||||
ports [ 12345 ];
|
||||
};
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings.PasswordAuthentication = false;
|
||||
# Get this port from your VPN provider
|
||||
ports [ 12345 ];
|
||||
};
|
||||
|
||||
users.extraUsers.username.openssh.authorizedKeys.keyFiles = [
|
||||
./path/to/public/key/machine.pub
|
||||
];
|
||||
users.extraUsers.username.openssh.authorizedKeys.keyFiles = [
|
||||
./path/to/public/key/machine.pub
|
||||
];
|
||||
|
||||
Then replace username with your username and the keyFiles path
|
||||
to a ssh public key file from the machine that you want to have
|
||||
access. Don't use password authentication as it is insecure!
|
||||
''
|
||||
] else [];
|
||||
Then replace username with your username and the keyFiles path
|
||||
to a ssh public key file from the machine that you want to have
|
||||
access. Don't use password authentication as it is insecure!
|
||||
''
|
||||
]
|
||||
else [];
|
||||
|
||||
# Enable and specify VPN namespace to confine service in.
|
||||
systemd.services.openssh.vpnconfinement = {
|
||||
@@ -84,8 +87,18 @@ in {
|
||||
|
||||
# Port mappings
|
||||
vpnnamespaces.wg = {
|
||||
portMappings = [{ From = defaultPort; To = defaultPort; }];
|
||||
openVPNPorts = map (x: { port = x; protocol = "both"; }) services.openssh.ports;
|
||||
portMappings = [
|
||||
{
|
||||
From = defaultPort;
|
||||
To = defaultPort;
|
||||
}
|
||||
];
|
||||
openVPNPorts =
|
||||
map (x: {
|
||||
port = x;
|
||||
protocol = "both";
|
||||
})
|
||||
services.openssh.ports;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/prowlarr
|
||||
```
|
||||
@@ -80,7 +80,12 @@ in {
|
||||
|
||||
# Port mappings
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [{ from = defaultPort; to = defaultPort; }];
|
||||
portMappings = [
|
||||
{
|
||||
from = defaultPort;
|
||||
to = defaultPort;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = mkIf cfg.vpn.enable {
|
||||
|
||||
@@ -71,7 +71,7 @@ in {
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "prowlarr") {
|
||||
prowlarr = { };
|
||||
prowlarr = {};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/radarr
|
||||
```
|
||||
@@ -78,7 +78,12 @@ in {
|
||||
|
||||
# Port mappings
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [{ from = defaultPort; to = defaultPort; }];
|
||||
portMappings = [
|
||||
{
|
||||
from = defaultPort;
|
||||
to = defaultPort;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = mkIf cfg.vpn.enable {
|
||||
|
||||
@@ -20,7 +20,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/readarr
|
||||
```
|
||||
@@ -76,7 +76,12 @@ in {
|
||||
|
||||
# Port mappings
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [{ from = defaultPort; to = defaultPort; }];
|
||||
portMappings = [
|
||||
{
|
||||
from = defaultPort;
|
||||
to = defaultPort;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = mkIf cfg.vpn.enable {
|
||||
|
||||
@@ -26,7 +26,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/sonarr
|
||||
```
|
||||
@@ -82,7 +82,12 @@ in {
|
||||
|
||||
# Port mappings
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [{ from = defaultPort; to = defaultPort; }];
|
||||
portMappings = [
|
||||
{
|
||||
from = defaultPort;
|
||||
to = defaultPort;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = mkIf cfg.vpn.enable {
|
||||
|
||||
@@ -82,7 +82,7 @@ in {
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.enable -> cfg.settings.outputDir != null;
|
||||
message = ''
|
||||
@@ -91,14 +91,16 @@ in {
|
||||
}
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"L+ '${cfg.dataDir}'/config.js - - - - ${configJs}"
|
||||
"d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
|
||||
] ++ (
|
||||
if cfg.settings.outputDir != null then
|
||||
[ "d '${cfg.settings.outputDir}' 0755 ${cfg.user} ${cfg.group} - -" ]
|
||||
else []
|
||||
);
|
||||
systemd.tmpfiles.rules =
|
||||
[
|
||||
"L+ '${cfg.dataDir}'/config.js - - - - ${configJs}"
|
||||
"d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
|
||||
]
|
||||
++ (
|
||||
if cfg.settings.outputDir != null
|
||||
then ["d '${cfg.settings.outputDir}' 0755 ${cfg.user} ${cfg.group} - -"]
|
||||
else []
|
||||
);
|
||||
|
||||
systemd.services.cross-seed = {
|
||||
description = "cross-seed";
|
||||
@@ -109,11 +111,15 @@ in {
|
||||
|
||||
serviceConfig = {
|
||||
# Run as root in case that the cfg.credentialsFile is not readable by cross-seed
|
||||
ExecStartPre = [("+" + pkgs.writeShellScript "transmission-prestart" ''
|
||||
${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' |
|
||||
install -D -m 600 -o '${cfg.user}' /dev/stdin '${cfg.dataDir}/config.json'
|
||||
''
|
||||
)];
|
||||
ExecStartPre = [
|
||||
(
|
||||
"+"
|
||||
+ pkgs.writeShellScript "transmission-prestart" ''
|
||||
${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' |
|
||||
install -D -m 600 -o '${cfg.user}' /dev/stdin '${cfg.dataDir}/config.json'
|
||||
''
|
||||
)
|
||||
];
|
||||
Type = "simple";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
@@ -130,7 +136,7 @@ in {
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "cross-seed") {
|
||||
cross-seed = { };
|
||||
cross-seed = {};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,57 +9,60 @@ with lib; let
|
||||
nixarr = config.nixarr;
|
||||
cfg-cross-seed = config.nixarr.transmission.privateTrackers.cross-seed;
|
||||
downloadDir = "${nixarr.mediaDir}/torrents";
|
||||
transmissionCrossSeedScript = with builtins; pkgs.writeShellApplication {
|
||||
name = "transmission-cross-seed-script";
|
||||
transmissionCrossSeedScript = with builtins;
|
||||
pkgs.writeShellApplication {
|
||||
name = "transmission-cross-seed-script";
|
||||
|
||||
runtimeInputs = with pkgs; [ curl ];
|
||||
runtimeInputs = with pkgs; [curl];
|
||||
|
||||
text = ''
|
||||
PROWLARR_API_KEY=$(cat prowlarr-api-key)
|
||||
curl -XPOST http://localhost:2468/api/webhook?apikey="$PROWLARR_API_KEY" --data-urlencode "infoHash=$TR_TORRENT_HASH"
|
||||
'';
|
||||
};
|
||||
importProwlarrApi = with builtins; pkgs.writeShellApplication {
|
||||
name = "import-prowlarr-api";
|
||||
|
||||
runtimeInputs = with pkgs; [ yq ];
|
||||
|
||||
text = ''
|
||||
touch ${cfg.stateDir}/prowlarr-api-key
|
||||
chmod 400 ${cfg.stateDir}/prowlarr-api-key
|
||||
chown torrenter ${cfg.stateDir}/prowlarr-api-key
|
||||
xq -r '.Config.ApiKey' "${nixarr.prowlarr.stateDir}/config.xml" > "${cfg.stateDir}/prowlarr-api-key"
|
||||
'';
|
||||
};
|
||||
mkCrossSeedCredentials = with builtins; pkgs.writeShellApplication {
|
||||
name = "mk-cross-seed-credentials";
|
||||
|
||||
runtimeInputs = with pkgs; [ jq yq ];
|
||||
|
||||
text =
|
||||
"INDEX_LINKS=("
|
||||
+ (strings.concatMapStringsSep " " toString cfg.privateTrackers.cross-seed.indexIds)
|
||||
+ ")"
|
||||
+ "\n"
|
||||
+ ''
|
||||
TMP_JSON=$(mktemp)
|
||||
CRED_FILE="/run/secrets/cross-seed/credentialsFile.json"
|
||||
PROWLARR_API_KEY=$(xq -r '.Config.ApiKey' "${nixarr.prowlarr.stateDir}/config.xml")
|
||||
# shellcheck disable=SC2034
|
||||
CRED_DIR=$(dirname "$CRED_FILE")
|
||||
|
||||
mkdir -p "$CRED_DIR"
|
||||
echo '{}' > "$CRED_FILE"
|
||||
chmod 400 "$CRED_FILE"
|
||||
chown "${config.util-nixarr.services.cross-seed.user}" "$CRED_FILE"
|
||||
|
||||
for i in "''${INDEX_LINKS[@]}"
|
||||
do
|
||||
LINK="http://localhost:9696/$i/api?apikey=$PROWLARR_API_KEY"
|
||||
jq ".torznab += [\"$LINK\"]" "$CRED_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$CRED_FILE"
|
||||
done
|
||||
text = ''
|
||||
PROWLARR_API_KEY=$(cat prowlarr-api-key)
|
||||
curl -XPOST http://localhost:2468/api/webhook?apikey="$PROWLARR_API_KEY" --data-urlencode "infoHash=$TR_TORRENT_HASH"
|
||||
'';
|
||||
};
|
||||
};
|
||||
importProwlarrApi = with builtins;
|
||||
pkgs.writeShellApplication {
|
||||
name = "import-prowlarr-api";
|
||||
|
||||
runtimeInputs = with pkgs; [yq];
|
||||
|
||||
text = ''
|
||||
touch ${cfg.stateDir}/prowlarr-api-key
|
||||
chmod 400 ${cfg.stateDir}/prowlarr-api-key
|
||||
chown torrenter ${cfg.stateDir}/prowlarr-api-key
|
||||
xq -r '.Config.ApiKey' "${nixarr.prowlarr.stateDir}/config.xml" > "${cfg.stateDir}/prowlarr-api-key"
|
||||
'';
|
||||
};
|
||||
mkCrossSeedCredentials = with builtins;
|
||||
pkgs.writeShellApplication {
|
||||
name = "mk-cross-seed-credentials";
|
||||
|
||||
runtimeInputs = with pkgs; [jq yq];
|
||||
|
||||
text =
|
||||
"INDEX_LINKS=("
|
||||
+ (strings.concatMapStringsSep " " toString cfg.privateTrackers.cross-seed.indexIds)
|
||||
+ ")"
|
||||
+ "\n"
|
||||
+ ''
|
||||
TMP_JSON=$(mktemp)
|
||||
CRED_FILE="/run/secrets/cross-seed/credentialsFile.json"
|
||||
PROWLARR_API_KEY=$(xq -r '.Config.ApiKey' "${nixarr.prowlarr.stateDir}/config.xml")
|
||||
# shellcheck disable=SC2034
|
||||
CRED_DIR=$(dirname "$CRED_FILE")
|
||||
|
||||
mkdir -p "$CRED_DIR"
|
||||
echo '{}' > "$CRED_FILE"
|
||||
chmod 400 "$CRED_FILE"
|
||||
chown "${config.util-nixarr.services.cross-seed.user}" "$CRED_FILE"
|
||||
|
||||
for i in "''${INDEX_LINKS[@]}"
|
||||
do
|
||||
LINK="http://localhost:9696/$i/api?apikey=$PROWLARR_API_KEY"
|
||||
jq ".torznab += [\"$LINK\"]" "$CRED_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$CRED_FILE"
|
||||
done
|
||||
'';
|
||||
};
|
||||
in {
|
||||
options.nixarr.transmission = {
|
||||
enable = mkEnableOption "the Transmission service.";
|
||||
@@ -74,7 +77,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/transmission
|
||||
```
|
||||
@@ -142,7 +145,7 @@ in {
|
||||
|
||||
**Warning:** Setting this to any path, where the subpath is not
|
||||
owned by root, will fail! For example:
|
||||
|
||||
|
||||
```nix
|
||||
stateDir = /home/user/nixarr/.state/cross-seed
|
||||
```
|
||||
@@ -154,7 +157,7 @@ in {
|
||||
indexIds = mkOption {
|
||||
type = with types; listOf int;
|
||||
default = [];
|
||||
example = [ 1 3 7 ];
|
||||
example = [1 3 7];
|
||||
description = ''
|
||||
List of indexer-ids, from prowlarr. These are from the RSS links
|
||||
for the indexers, located by the "radio" or "RSS" logo on the
|
||||
@@ -259,31 +262,37 @@ in {
|
||||
enable = true;
|
||||
dataDir = cfg-cross-seed.stateDir;
|
||||
group = "torrenter";
|
||||
settings = {
|
||||
torrentDir = "${nixarr.mediaDir}/torrents";
|
||||
outputDir = "${nixarr.mediaDir}/torrents/.cross-seed";
|
||||
transmissionRpcUrl = "http://localhost:${builtins.toString cfg.uiPort}/transmission/rpc";
|
||||
rssCadence = "20 minutes";
|
||||
settings =
|
||||
{
|
||||
torrentDir = "${nixarr.mediaDir}/torrents";
|
||||
outputDir = "${nixarr.mediaDir}/torrents/.cross-seed";
|
||||
transmissionRpcUrl = "http://localhost:${builtins.toString cfg.uiPort}/transmission/rpc";
|
||||
rssCadence = "20 minutes";
|
||||
|
||||
action = "inject";
|
||||
action = "inject";
|
||||
|
||||
# Enable infrequent periodic searches
|
||||
searchCadence = "1 week";
|
||||
excludeRecentSearch = "1 year";
|
||||
excludeOlder = "1 year";
|
||||
} // cfg-cross-seed.extraSettings;
|
||||
# Enable infrequent periodic searches
|
||||
searchCadence = "1 week";
|
||||
excludeRecentSearch = "1 year";
|
||||
excludeOlder = "1 year";
|
||||
}
|
||||
// cfg-cross-seed.extraSettings;
|
||||
};
|
||||
# Run as root in case that the cfg.credentialsFile is not readable by cross-seed
|
||||
systemd.services.cross-seed.serviceConfig = mkIf cfg-cross-seed.enable {
|
||||
ExecStartPre = mkBefore [(
|
||||
ExecStartPre = mkBefore [
|
||||
(
|
||||
"+" + "${mkCrossSeedCredentials}/bin/mk-cross-seed-credentials"
|
||||
)];
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.transmission.serviceConfig = mkIf cfg-cross-seed.enable {
|
||||
ExecStartPre = mkBefore [(
|
||||
ExecStartPre = mkBefore [
|
||||
(
|
||||
"+" + "${importProwlarrApi}/bin/import-prowlarr-api"
|
||||
)];
|
||||
)
|
||||
];
|
||||
};
|
||||
services.transmission = {
|
||||
enable = true;
|
||||
@@ -305,7 +314,10 @@ in {
|
||||
watch-dir-enabled = true;
|
||||
watch-dir = "${downloadDir}/.watch";
|
||||
|
||||
rpc-bind-address = if cfg.vpn.enable then "192.168.15.1" else "127.0.0.1";
|
||||
rpc-bind-address =
|
||||
if cfg.vpn.enable
|
||||
then "192.168.15.1"
|
||||
else "127.0.0.1";
|
||||
rpc-port = cfg.uiPort;
|
||||
# TODO: fix this for ssh tunneling...
|
||||
rpc-whitelist-enabled = true;
|
||||
@@ -326,9 +338,10 @@ in {
|
||||
anti-brute-force-threshold = 10;
|
||||
|
||||
script-torrent-done-enabled = cfg-cross-seed.enable;
|
||||
script-torrent-done-filename = if cfg-cross-seed.enable then
|
||||
"${transmissionCrossSeedScript}/bin/transmission-cross-seed-script"
|
||||
else null;
|
||||
script-torrent-done-filename =
|
||||
if cfg-cross-seed.enable
|
||||
then "${transmissionCrossSeedScript}/bin/transmission-cross-seed-script"
|
||||
else null;
|
||||
|
||||
message-level =
|
||||
if cfg.messageLevel == "none"
|
||||
@@ -358,9 +371,17 @@ in {
|
||||
|
||||
# Port mappings
|
||||
vpnnamespaces.wg = mkIf cfg.vpn.enable {
|
||||
portMappings = [{ from = cfg.uiPort; to = cfg.uiPort; }];
|
||||
portMappings = [
|
||||
{
|
||||
from = cfg.uiPort;
|
||||
to = cfg.uiPort;
|
||||
}
|
||||
];
|
||||
openVPNPorts = [
|
||||
{ port = cfg.peerPort; protocol = "both"; }
|
||||
{
|
||||
port = cfg.peerPort;
|
||||
protocol = "both";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{ lib, buildNpmPackage, fetchFromGitHub }:
|
||||
|
||||
{
|
||||
lib,
|
||||
buildNpmPackage,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
buildNpmPackage rec {
|
||||
pname = "cross-seed";
|
||||
version = "5.9.2";
|
||||
@@ -17,6 +20,5 @@ buildNpmPackage rec {
|
||||
description = "cross-seed is an app designed to help you download torrents that you can cross seed based on your existing torrents";
|
||||
homepage = "https://www.cross-seed.org";
|
||||
license = licenses.asl20;
|
||||
maintainers = with maintainers; [ rasmus-kirk ];
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user