move encrypted secrets near relevant files

This commit is contained in:
Noah Masur
2025-03-09 17:09:33 +00:00
parent f59ac536a2
commit 37d1d7724a
60 changed files with 27 additions and 94 deletions

View File

@ -0,0 +1,289 @@
{
config,
pkgs,
lib,
...
}:
let
inherit (config.nmasur.settings) hostnames;
cfg = config.nmasur.presets.services.arrs;
# This config specifies ports for Prometheus to scrape information
arrConfig = {
radarr = {
exportarrPort = "9707";
url = "localhost:7878";
apiKey = config.secrets.radarrApiKey.dest;
};
readarr = {
exportarrPort = "9711";
url = "localhost:8787";
apiKey = config.secrets.readarrApiKey.dest;
};
sonarr = {
exportarrPort = "9708";
url = "localhost:8989";
apiKey = config.secrets.sonarrApiKey.dest;
};
prowlarr = {
exportarrPort = "9709";
url = "localhost:9696";
apiKey = config.secrets.prowlarrApiKey.dest;
};
sabnzbd = {
exportarrPort = "9710";
url = "localhost:8085";
apiKey = config.secrets.sabnzbdApiKey.dest;
};
};
in
{
options.nmasur.presets.services.arrs.enable = lib.mkEnableOption "Arr services";
config = lib.mkIf cfg.enable {
# Required
nmasur.profiles.shared-media.enable = true; # Shared user for multiple services
# # Broken on 2024-12-07
# # https://discourse.nixos.org/t/solved-sonarr-is-broken-in-24-11-unstable-aka-how-the-hell-do-i-use-nixpkgs-config-permittedinsecurepackages/
# insecurePackages = [
# "aspnetcore-runtime-wrapped-6.0.36"
# "aspnetcore-runtime-6.0.36"
# "dotnet-sdk-wrapped-6.0.428"
# "dotnet-sdk-6.0.428"
# ];
services = {
bazarr = {
enable = true;
group = lib.mkIf config.nmasur.profiles.shared-media.enable "shared";
};
jellyseerr.enable = true;
prowlarr.enable = true;
sabnzbd = {
enable = true;
group = lib.mkIf config.nmasur.profiles.shared-media.enable "shared";
# The config file must be editable within the application
# It contains server configs and credentials
configFile = "/data/downloads/sabnzbd/sabnzbd.ini";
};
sonarr = {
enable = true;
group = lib.mkIf config.nmasur.profiles.shared-media.enable "shared";
};
radarr = {
enable = true;
group = lib.mkIf config.nmasur.profiles.shared-media.enable "shared";
};
readarr = {
enable = true;
group = lib.mkIf config.nmasur.profiles.shared-media.enable "shared";
};
};
# Allows shared group to read/write the sabnzbd directory
users.users.sabnzbd.homeMode = "0770";
allowUnfreePackages = [ "unrar" ]; # Required as a dependency for sabnzbd
# Requires updating the base_url config value in each service
# If you try to rewrite the URL, the service won't redirect properly
nmasur.presets.services.caddy.routes = [
{
# Group means that routes with the same name are mutually exclusive,
# so they are split between the appropriate services.
group = "download";
match = [
{
host = [ hostnames.download ];
path = [ "/sonarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
# We're able to reference the url and port of the service dynamically
upstreams = [ { dial = arrConfig.sonarr.url; } ];
}
];
}
{
group = "download";
match = [
{
host = [ hostnames.download ];
path = [ "/radarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = arrConfig.radarr.url; } ];
}
];
}
{
group = "download";
match = [
{
host = [ hostnames.download ];
path = [ "/readarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = arrConfig.readarr.url; } ];
}
];
}
{
group = "download";
match = [
{
host = [ hostnames.download ];
path = [ "/prowlarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
# Prowlarr doesn't offer a dynamic config, so we have to hardcode it
upstreams = [ { dial = "localhost:9696"; } ];
}
];
}
{
group = "download";
match = [
{
host = [ hostnames.download ];
path = [ "/bazarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{
# Bazarr only dynamically sets the port, not the host
dial = "localhost:${builtins.toString config.services.bazarr.listenPort}";
}
];
}
];
}
{
group = "download";
match = [
{
host = [ hostnames.download ];
path = [ "/sabnzbd*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = arrConfig.sabnzbd.url; } ];
}
];
}
{
group = "download";
match = [ { host = [ hostnames.download ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.jellyseerr.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ hostnames.download ];
# Enable Prometheus exporters
systemd.services = lib.mapAttrs' (name: attrs: {
name = "prometheus-${name}-exporter";
value = {
description = "Export Prometheus metrics for ${name}";
after = [ "network.target" ];
wantedBy = [ "${name}.service" ];
serviceConfig = {
Type = "simple";
DynamicUser = true;
ExecStart =
let
# Sabnzbd doesn't accept the URI path, unlike the others
url = if name != "sabnzbd" then "http://${attrs.url}/${name}" else "http://${attrs.url}";
in
# Exportarr is trained to pull from the arr services
''
${pkgs.exportarr}/bin/exportarr ${name} \
--url ${url} \
--port ${attrs.exportarrPort}'';
EnvironmentFile = lib.mkIf (builtins.hasAttr "apiKey" attrs) attrs.apiKey;
Restart = "on-failure";
ProtectHome = true;
ProtectSystem = "strict";
PrivateTmp = true;
PrivateDevices = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
NoNewPrivileges = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RemoveIPC = true;
PrivateMounts = true;
};
};
}) arrConfig;
# Secrets for Prometheus exporters
secrets.radarrApiKey = {
source = ./radarr-api-key.age;
dest = "/var/private/radarr-api";
prefix = "API_KEY=";
};
secrets.readarrApiKey = {
source = ./radarr-api-key.age;
dest = "/var/private/readarr-api";
prefix = "API_KEY=";
};
secrets.sonarrApiKey = {
source = ./sonarr-api-key.age;
dest = "/var/private/sonarr-api";
prefix = "API_KEY=";
};
secrets.prowlarrApiKey = {
source = ./prowlarr-api-key.age;
dest = "/var/private/prowlarr-api";
prefix = "API_KEY=";
};
secrets.sabnzbdApiKey = {
source = ./sabnzbd-api-key.age;
dest = "/var/private/sabnzbd-api";
prefix = "API_KEY=";
};
# Prometheus scrape targets (expose Exportarr to Prometheus)
nmasur.presets.services.prometheus-exporters.scrapeTargets = map (
key:
"127.0.0.1:${
lib.attrsets.getAttrFromPath [
key
"exportarrPort"
] arrConfig
}"
) (builtins.attrNames arrConfig);
};
}

View File

@ -0,0 +1,17 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyAzMUNW
QnV3SGtMcmVOUG05YnloWWNnUUE2aXMxZXF4S21yTm5ZY3hsVjNRCjlhckw2MXoy
OE9YV0xGNDhpQVlQV2k0UVQ4eEFLZXhwSHBxRXpDRnozVGsKLT4gc3NoLWVkMjU1
MTkgWXlTVU1RIEE0MEhRMExPR2tralRvZ2hDNjRLUzB3VG1HMkNEemV2ZTlrbWdW
SGY3WGcKZ0J2MUtHdnduNFJ1YVpCd01OT1ZhRVJyd3dWRmNGQWN4VmUvdS94R2dX
OAotPiBzc2gtZWQyNTUxOSBuanZYNUEgU3RhNjBleFJHQUhnUjNZYVo2dmdISm5B
M2x6UkVRa0xMUTZybGlER2xCZwphaWFOOFowaUhXeVcyamlCclBhM3JZZlh2ZmRI
RlB6Q2tySkwwM0lyL3dJCi0+IHNzaC1lZDI1NTE5IENxSU9VQSBnODdnZXVCSHl4
Y2JkdXJidmtqWVdQdWJTZEZjS2puMmM2UzFtcmdTZHlvCmdPSkFDWTFwdmNyc2xH
R0hxRXZkNTZ5b1dvaFgxdjVUWFc1aFpuWTYza2MKLT4gc3NoLWVkMjU1MTkgejFP
Y1p3IFB2WVlaZ0YxenR5SDIvOEhza2c5THVldUw2dXplR0t3N1h2ZmY3NlJYZzQK
Q0RwRlFGTVRuSzE1QVlSRWg5cHRYMElLZVlDM3NaOUVQTEl0U0VuM1VCSQotLS0g
a05hV3haWlE5QkFnMHQvWEowcEU1RExIMGdUWjQvUkFmaHFVdnJnT0g1RQrkp3VL
7YRcvkng6dO+swKzNUhPbJtYJqGtAtxo6I2v9nQl7Zc8X1vcJiaic4xaYNYfRFPS
oAd3/SnRi+sghHuTWw==
-----END AGE ENCRYPTED FILE-----

View File

@ -0,0 +1,17 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyBzV2Jx
ZG5JL3oxc3Vqcmo1VE9GQ21yeGpINWJNZTYxcFpNTXJQQ0hDZFNnCk4zMkNNdmFs
LzFPUzFQUTYzQ2lEcUpkSlBsUFpvQUdOSmZocnVZeFlKbDgKLT4gc3NoLWVkMjU1
MTkgWXlTVU1RIFJHWGpmNTNFbkdDNEE1YVpBRWowOUIwYmRIVFYxSFFaUDVBblY2
NWxMMlkKOFh6WXVwM2lRaU5CZm5JMDN4eThoRUNkWUExcnpmRnpUa0pmUTcxWUo5
UQotPiBzc2gtZWQyNTUxOSBuanZYNUEgRFhsWklkVmpXWTl1ZS9XVHN2dStDTXZw
bzlpUktIdW5rLzdnOFM0N0pBNApvNmxUK3d2aWt5ZXdQeUhWdHFpclQrVENOaDdx
RTdBdHVDSnFPVkh2ellRCi0+IHNzaC1lZDI1NTE5IENxSU9VQSA4M2J4cTVnVWtq
RzBsRlR1YWhwVkF6L2wrcC9vNzdaYzVoQk8wTXVmRXowCnJkc3dyNnN3UTZyZG1q
Z2xtUzhRT3ozZHhCeXJPVUdETkhoa3lpY1QweTAKLT4gc3NoLWVkMjU1MTkgejFP
Y1p3IG1VdTd3aDkwclpZY0hBOVdiY3pIai9MSjc2ZStjb0NMaWx5RWpxR2F3elUK
RE1sRFBJZ0ZNSE5QN293anNXWldiZm9jV2w5SVpxU2VuTXMwTndJU0Z1RQotLS0g
dzBMKzZRYU9BQ0YvWGpLMHZLQ0YrZGJZRGZOcGNmcnJ2RGJmMit4c0NHZwrMord1
EhzFVq3Hrt+3l2vfY90fSkP+X0yQqC5m5A7F/jA7xBzBr3WGQ/DKYsxT1e/bZv2g
cTLB74cM0mVm5iGHDQ==
-----END AGE ENCRYPTED FILE-----

View File

@ -0,0 +1,17 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyBITUVj
MjN5MGljc0ZEdEdPSGdNSWZZS1VDdnp6d0dIci9GeXI0TXhUZ3lrCm5ZbjZtN1BO
ZUkzQy9ZbGp2S3Nxd1ljanBQemlwdVdDMXZOYkp0UFBVTDAKLT4gc3NoLWVkMjU1
MTkgWXlTVU1RIG9VSStaM2RTdUF4Q2VkQ3h5S0JHeWYwdndiQmlyNytWTXplMkdE
MXpqeWsKaWhMOE5DS2RnS0VneUJHWUc3Ylhpb2ZFazJGdkJIdkxxalhVdU4wR2N4
awotPiBzc2gtZWQyNTUxOSBuanZYNUEgaHMwWWZQVDdRai90S3U0SE95TW5JODcv
Y2Jqb0hZZC91dExCSWFCT3AxbwpzbTk3SW43OVVmY1ExemkwcXMxdUx4UWRtek1O
cVJuQ3JmWHNFSUxwRXo0Ci0+IHNzaC1lZDI1NTE5IENxSU9VQSBBUVRZeHZnNm1a
OGsyK0JXbWF1RXBxUkQzRkZ1R0s1bzRNa1BHTWhOblc0Clo0VngzYTg1elNRY3Fj
UEJ4cnp2MkV3ckM4R0loU3VuTGtuSEgzamNsY2sKLT4gc3NoLWVkMjU1MTkgejFP
Y1p3IHZIOUNFME1TbDhsUDNobUdwV2RRMXQ3cDFCeEg2WTArQTlSMmYxN1liZ1EK
SXVWOGI3ejRwNUtDeUE4SDh6SGhzYWNHdiswaUFQZGd4L0VPZStQaFA4TQotLS0g
R09RR0hEeWJmb1N3SjBUemhnOGQyeTN4K3ppaFJ2MDFGR2VqNmYraHZaVQrsfqps
BUEbuwbRJOooVEMyJmUjNavhK09fgxHyyaiDVpEsTdg3dQ6sQYIV/Envmad8An+a
Cn8oklYFq/UxoMHHbA==
-----END AGE ENCRYPTED FILE-----

View File

@ -0,0 +1,17 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyA0Wkc3
cFMyN1lLanQzNm9WR0JPS2RCYjJpbndER3BmUTZOcmVOem1DWkZzCnY5S1Yyamt6
ZkFUem5vT1JQYWRmTHZMREVTb3hHQ20xdUpQK2dqcjJtRmMKLT4gc3NoLWVkMjU1
MTkgWXlTVU1RIFRrUWorNVRaYkhHaWpheXZZSmRaM0d6U0tDNjgvcE5IMWk5SEhX
MzhZa0kKVEkxWlZhNytJMEdKampWY0l3NzQ2aUdNOU5rV1Zjb3FHTVovdXRpdlB4
dwotPiBzc2gtZWQyNTUxOSBuanZYNUEgVkxtNFFoWUlaM25GVDJGMzBiWEt6Lzlj
NFdka1dUd0ZJNzMwTTl4emdqZwo3Y0FTbzZxcUNpOTl0R09JOUVkWXVIMkFndFg0
NXFKR0lZQVhCZFRBL0lJCi0+IHNzaC1lZDI1NTE5IENxSU9VQSBJWDZ0aFBtRUxr
dXNRaVVxOWFacmE0allBcmJleFNFOXhNNlFZL1FDUVJJCmkzeStQSGN4STdTSFJG
eWVnRFhEL3UzNHJzWngwWWZIV25NbzZOK3pOeDgKLT4gc3NoLWVkMjU1MTkgejFP
Y1p3IG13N1RWYzFzcUxkSnJCb3duSEQ0UnpnK2U2VmZlZlBFMjFqa2Y3Q2sySHcK
a3RURU51bGRzZllyOVpCT1RQSVpFSnB3K3RFZGJQMDNKN0wySko0OVBPcwotLS0g
eEluUUZwanRWWS8rTk1jcEpSU3Q1L05ja0lLNklhM2hHNUdBeG0rSjU5YwqEOyo/
YSIlWzZ13Vm64tqg6ksRnEuaSwUSQro0R8zRy8MNlPcX0IKtZV3H4wnvZBRIScWd
Mng3c8Wq4p3/ip+/mw==
-----END AGE ENCRYPTED FILE-----