This repository has no description
9.8 kB
367 lines
1{ config, pkgs, ... }:
2{
3 users.users.download = {
4 isSystemUser = true;
5 uid = 1001;
6 group = "download";
7 };
8 users.groups.download.members = [
9 "transmission"
10 "sabnzbd"
11 ];
12 services.transmission = {
13 enable = true;
14 package = pkgs.transmission_4;
15 home = "/var/state/transmission";
16 group = "download";
17 downloadDirPermissions = "775";
18 settings = {
19 download-dir = "/var/download/transmission";
20 incomplete-dir-enabled = false;
21 rpc-bind-address = "::";
22 message-level = 1;
23 umask = 18;
24 rpc-whitelist-enabled = false;
25 rpc-host-whitelist-enabled = false;
26 # stop seeding after reaching 5.0 ratio
27 ratio-limit = 5;
28 ratio-limit-enabled = true;
29 # limit concurrent seeding to reduce resource usage
30 seed-queue-enabled = true;
31 seed-queue-size = 20;
32 };
33 };
34
35 # garbage collection for old torrents
36 systemd.services.transmission-gc = {
37 description = "Remove old completed torrents from Transmission";
38 after = [ "transmission.service" ];
39 path = [
40 pkgs.transmission_4
41 pkgs.jq
42 pkgs.coreutils
43 pkgs.gawk
44 pkgs.gnugrep
45 pkgs.findutils
46 ];
47 script = ''
48 set -efu
49 TRANSMISSION_HOST="128.0.0.1:9091"
50 MAX_AGE_DAYS=30
51 MIN_RATIO=5.0
52
53 # get list of all torrents as JSON
54 torrents=$(transmission-remote "$TRANSMISSION_HOST" -l 2>/dev/null | tail -n +2 | head -n -1 || true)
55
56 if [ -z "$torrents" ]; then
57 echo "No torrents found"
58 exit 0
59 fi
60
61 # process each torrent
62 echo "$torrents" | while read -r line; do
63 id=$(echo "$line" | awk '{print $1}' | tr -d '*')
64 # skip if not a valid ID
65 [ -z "$id" ] || [ "$id" = "ID" ] && continue
66
67 # get torrent info
68 info=$(transmission-remote "$TRANSMISSION_HOST" -t "$id" -i 2>/dev/null || continue)
69
70 # extract ratio and completion status
71 ratio=$(echo "$info" | grep "Ratio:" | awk '{print $2}')
72 percent=$(echo "$info" | grep "Percent Done:" | awk '{print $3}' | tr -d '%')
73 state=$(echo "$info" | grep "State:" | cut -d: -f2- | xargs)
74
75 # skip if not 100% complete
76 [ "$percent" != "100" ] && continue
77
78 # check if ratio reached
79 ratio_reached=false
80 if [ -n "$ratio" ] && [ "$ratio" != "None" ]; then
81 if awk "BEGIN {exit !($ratio >= $MIN_RATIO)}"; then
82 ratio_reached=true
83 fi
84 fi
85
86 # check age via date added
87 date_added=$(echo "$info" | grep "Date added:" | cut -d: -f2- | xargs)
88 if [ -n "$date_added" ]; then
89 added_epoch=$(date -d "$date_added" +%s 2>/dev/null || echo 0)
90 now_epoch=$(date +%s)
91 age_days=$(( (now_epoch - added_epoch) / 86400 ))
92 else
93 age_days=0
94 fi
95
96 # remove if ratio reached OR older than max age
97 if [ "$ratio_reached" = "true" ]; then
98 echo "Removing torrent $id (ratio: $ratio >= $MIN_RATIO)"
99 transmission-remote "$TRANSMISSION_HOST" -t "$id" --remove-and-delete
100 elif [ "$age_days" -ge "$MAX_AGE_DAYS" ]; then
101 echo "Removing torrent $id (age: $age_days days >= $MAX_AGE_DAYS)"
102 transmission-remote "$TRANSMISSION_HOST" -t "$id" --remove-and-delete
103 fi
104 done
105 '';
106 serviceConfig = {
107 Type = "oneshot";
108 User = "transmission";
109 };
110 };
111
112 systemd.timers.transmission-gc = {
113 wantedBy = [ "timers.target" ];
114 timerConfig = {
115 OnCalendar = "daily";
116 Persistent = true;
117 RandomizedDelaySec = "1h";
118 };
119 };
120 systemd.services.transmission-watcher = {
121 wantedBy = [ "multi-user.target" ];
122 startAt = "*:0/5";
123 path = [
124 pkgs.curl
125 pkgs.systemdMinimal
126 ];
127 script = ''
128 set -efu -o pipefail
129 # don't restart if transmission is currently starting or stopping
130 state=$(systemctl show -p ActiveState --value transmission.service)
131 if [ "$state" = "activating" ] || [ "$state" = "deactivating" ]; then
132 echo "transmission is $state, skipping"
133 exit 0
134 fi
135 # check if transmission responds (with timeout)
136 if ! curl -SsfL --max-time 10 http://transmission.r; then
137 echo "transmission not responding, restarting"
138 systemctl restart transmission.service
139 fi
140 '';
141 };
142
143 security.acme.defaults.email = "spam@krebsco.de";
144 security.acme.acceptTerms = true;
145 security.acme.certs."yellow.r".server = config.krebs.ssl.acmeURL;
146 security.acme.certs."jelly.r".server = config.krebs.ssl.acmeURL;
147 security.acme.certs."radar.r".server = config.krebs.ssl.acmeURL;
148 security.acme.certs."sonar.r".server = config.krebs.ssl.acmeURL;
149 security.acme.certs."transmission.r".server = config.krebs.ssl.acmeURL;
150 services.nginx = {
151 enable = true;
152 package = pkgs.nginx.override {
153 modules = with pkgs.nginxModules; [
154 fancyindex
155 ];
156 };
157 virtualHosts."yellow.r" = {
158 serverAliases = [ "flix.r" ];
159 default = true;
160 enableACME = true;
161 addSSL = true;
162 locations."/" = {
163 root = "/var/download";
164 extraConfig = ''
165 fancyindex on;
166 include ${pkgs.nginx}/conf/mime.types;
167 include ${pkgs.writeText "extrMime" ''
168 types {
169 video/webm mkv;
170 }
171 ''};
172 create_full_put_path on;
173 '';
174 };
175
176 };
177 virtualHosts."jelly.r" = {
178 enableACME = true;
179 addSSL = true;
180 locations."/".extraConfig = ''
181 proxy_pass http://localhost:8096/;
182 proxy_set_header Accept-Encoding "";
183 '';
184 };
185 virtualHosts."transmission.r" = {
186 enableACME = true;
187 addSSL = true;
188 locations."/" = {
189 proxyWebsockets = true;
190 proxyPass = "http://128.0.0.1:9091";
191 };
192 };
193 virtualHosts."radar.r" = {
194 enableACME = true;
195 addSSL = true;
196 locations."/" = {
197 proxyWebsockets = true;
198 proxyPass = "http://localhost:7878";
199 };
200 };
201 virtualHosts."sonar.r" = {
202 enableACME = true;
203 addSSL = true;
204 locations."/" = {
205 proxyWebsockets = true;
206 proxyPass = "http://localhost:8989";
207 # sonar.r now has forced authentication
208 extraConfig = ''
209 proxy_set_header Authorization "Basic a3JlYnM6YWlkc2JhbGxz";
210 '';
211 };
212 };
213 };
214
215 services.samba = {
216 enable = true;
217 enableNmbd = false;
218 settings.global = {
219 "hosts allow" = "42::/16 10.243.0.0/16 10.244.0.0/16 fdcc:c5da:5295:c853:d499::/80";
220 "use sendfile" = "true";
221 "disable netbios" = "true";
222 "mangled names" = "illegal";
223 "socket options" = "TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=65536 SO_SNDBUF=65536";
224 "load printers" = "false";
225 "disable spoolss" = "true";
226 "printcap name" = "/dev/null";
227 "map to guest" = "Bad User";
228 "max log size" = "50";
229 "dns proxy" = "no";
230 "security" = "user";
231 "syslog only" = "yes";
232 };
233 shares.public = {
234 comment = "Warez";
235 path = "/var/download";
236 public = "yes";
237 "only guest" = "yes";
238 "create mask" = "0644";
239 "directory mask" = "2777";
240 writable = "no";
241 printable = "no";
242 };
243 };
244
245 networking.firewall.allowedTCPPorts = [
246 80 # nginx
247 443 # nginx
248 9091 # transmission web
249 8096 # jellyfin
250 8920 # jellyfin
251 51413 # transmission traffic
252 445 # smbd
253 111 # smbd
254 2049 # smbd
255 4000 # smbd
256 4001 # smbd
257 4002 # smbd
258 ];
259 networking.firewall.allowedUDPPorts = [
260 51413 # transmission traffic
261 1900 # jellyfin
262 7359 # jellyfin
263 111 # smbd
264 2049 # smbd
265 4000 # smbd
266 4001 # smbd
267 4002 # smbd
268 ];
269 krebs.iptables = {
270 enable = true;
271 tables.nat.PREROUTING.rules = [
272 # transmission rpc port
273 {
274 predicate = "-i retiolum -p tcp --dport 9091";
275 target = "DNAT --to-destination fdb4:3310:947::2";
276 v4 = false;
277 }
278 ];
279 tables.filter.FORWARD.policy = "ACCEPT"; # we need this so we can forward into the the transmission network namespace
280 };
281
282 systemd.services.flix-index = {
283 wantedBy = [ "multi-user.target" ];
284 path = [
285 pkgs.coreutils
286 pkgs.findutils
287 pkgs.inotify-tools
288 ];
289 startAt = "hourly";
290 serviceConfig = {
291 Type = "oneshot";
292 ExecStart = pkgs.writers.writeDash "flix-index" ''
293 set -efu
294 index(){
295 find . -type f > "$DIR"/index.tmp
296 mv "$DIR"/index.tmp "$DIR"/index
297 }
298
299 DIR=/var/download
300 cd "$DIR"
301 index
302 '';
303 };
304 };
305
306 services.jellyfin = {
307 enable = true;
308 group = "download";
309 };
310
311 # request managment
312 services.jellyseerr = {
313 enable = true;
314 openFirewall = true;
315 };
316
317 # movies
318 services.radarr = {
319 enable = true;
320 openFirewall = true;
321 user = "download";
322 group = "download";
323 };
324
325 # shows
326 services.sonarr = {
327 enable = true;
328 openFirewall = true;
329 user = "download";
330 group = "download";
331 };
332 # sonarr needs unsecure packages
333 nixpkgs.config.permittedInsecurePackages = [
334 "aspnetcore-runtime-wrapped-6.0.36"
335 "aspnetcore-runtime-6.0.36"
336 "dotnet-sdk-wrapped-6.0.428"
337 "dotnet-sdk-6.0.428"
338 "olm-3.2.16"
339 ];
340
341 # indexers
342 services.prowlarr = {
343 enable = true;
344 openFirewall = true;
345 };
346
347 # flaresolverr for bypassing cloudflare on some indexers
348 services.flaresolverr = {
349 enable = true;
350 openFirewall = true;
351 };
352
353 # subtitles
354 services.bazarr = {
355 enable = true;
356 openFirewall = true;
357 user = "download";
358 group = "download";
359 };
360
361 # usenet download client
362 services.sabnzbd = {
363 enable = true;
364 group = "download";
365 openFirewall = true;
366 };
367}