{ lib, pkgs, config, ... }: # # cp -avl (to create hardlinks instead of copy) # ============================================= # #┌──────────────────────────────────┐ ┌──────────────────────────────────────┐ #│/media/torrent/downloads/music ├─────►│/media/syncthing/music/incomming │ #└──────────────────────────────────┘ └──────────────────────────────────────┘ #┌──────────────────────────────────┐ ┌──────────────────────────────────────┐ #│/media/torrent/downloads/movies ├─────►│/media/syncthing/movies/incoming │ #└──────────────────────────────────┘ └──────────────────────────────────────┘ #┌──────────────────────────────────┐ ┌──────────────────────────────────────┐ #│/media/torrent/downloads ├─────►│/media/torrent/incoming │ #└──────────────────────────────────┘ └──────────────────────────────────────┘ let hostInterface = "enp3s0"; hostAddress = "192.168.100.30"; containerAddress = "192.168.100.31"; uiPort = 9091; ############################################# # These are inherited from Transmission. # # Do not declare these. Just use as needed. # # # # TR_APP_VERSION # # TR_TIME_LOCALTIME # # TR_TORRENT_DIR # # TR_TORRENT_HASH # # TR_TORRENT_ID # # TR_TORRENT_NAME # # # ############################################# completionScript = let copy_map = { "/media/torrent/downloads/movies" = "/media/syncthing/movies/incoming"; "/media/torrent/downloads" = "/media/torrent/incoming"; }; copy_script = lib.concatStringsSep "\n" (lib.mapAttrsToList (source: target: '' if [[ "$TR_TORRENT_DIR" == "${source}" ]] then cp -val "$TR_TORRENT_DIR/$TR_TORRENT_NAME" "${target}/$TR_TORRENT_NAME" fi '') copy_map); in pkgs.writers.writeBash "torrent-finished" '' ${copy_script} ${pkgs.jq}/bin/jq '{ "token": "'"$PUSHOVER_API_KEY"'", "user": "'"$PUSHOVER_USER_KEY"'", "titel": "transmission", "message": "'"$TR_TORRENT_NAME finished"'" }' >> /media/torrent/finish.log ${pkgs.jq}/bin/jq '{ "token": "'"$PUSHOVER_API_KEY"'", "user": "'"$PUSHOVER_USER_KEY"'", "titel": "transmission", "message": "'"$TR_TORRENT_NAME finished"'" }' \ | ${pkgs.curl}/bin/curl \ -sS \ -X POST \ -H 'Content-Type: application/json' \ -d @- \ https://api.pushover.net/1/messages.json ''; in { #users.users.transmission = { # isSystemUser = true; # uid = config.ids.uids.transmission; #}; sops.secrets.nordvpn = { }; sops.secrets.transmissionPushover= { }; containers.torrent = { # mount host folders bindMounts = { #password = { # hostPath = "/run/secrets/transmission_password"; # mountPoint = "/run/secrets/transmission_password"; # isReadOnly = true; #}; transmissionPushover = { hostPath = "/run/secrets/transmissionPushover"; mountPoint = "/run/secrets/transmissionPushover"; isReadOnly = true; }; nordvpnPassword = { hostPath = "/run/secrets/nordvpn"; mountPoint = "/run/secrets/nordvpn"; isReadOnly = true; }; media = { hostPath = "/media"; mountPoint = "/media"; # must be here otherwise transmission can't see the folder isReadOnly = false; }; lib = { hostPath = "/media/torrent/.config"; mountPoint = "/var/lib/transmission/.config"; isReadOnly = false; }; }; # container network setup # see also nating on host system. privateNetwork = true; hostAddress = hostAddress; localAddress = containerAddress; autoStart = true; # needed for openvpn enableTun = true; config = { config, pkgs, lib, ... }: { #services.journalbeat = { # enable = true; # extraConfig = '' # journalbeat.inputs: # - paths: [] # # Position to start reading from journal. Valid values: head, tail, cursor # seek: cursor # # Fallback position if no cursor data is available. # cursor_seek_fallback: tail # output.logstash: # # Boolean flag to enable or disable the output module. # enabled: true # # Graylog host and the beats input # hosts: ["${hostAddress}:5044"] # # If enabled only a subset of events in a batch of events is transferred per # # transaction. The number of events to be sent increases up to `bulk_max_size` # # if no error is encountered. # slow_start: true # # The number of seconds to wait before trying to reconnect to Graylog # # after a network error. After waiting backoff.init seconds, the Beat # # tries to reconnect. If the attempt fails, the backoff timer is increased # # exponentially up to backoff.max. After a successful connection, the backoff # # timer is reset. The default is 1s. # backoff.init: 1s # # The maximum number of seconds to wait before attempting to connect to # # Graylog after a network error. The default is 60s. # backoff.max: 60s # ''; #}; services.journald.extraConfig = "SystemMaxUse=1G"; # allow transmission to write in syncthing folders users.groups.syncthing = { gid = config.ids.gids.syncthing; members = ["transmission"]; }; services.transmission = { enable = true; settings = { download-dir = "/media/torrent/downloads"; incomplete-dir = "/media/torrent/incomplete"; incomplete-dir-enabled = true; message-level = 1; umask = "002"; rpc-whitelist-enabled = false; rpc-host-whitelist-enabled = false; rpc-port = uiPort; rpc-enable = true; rpc-bind-address = "0.0.0.0"; # "normal" speed limits speed-limit-down-enabled = false; speed-limit-down = 800; speed-limit-up-enabled = true; speed-limit-up = 3000; upload-slots-per-torrent = 8; # Queuing # When true, Transmission will only download # download-queue-size non-stalled torrents at once. download-queue-enabled = true; download-queue-size = 3; # When true, torrents that have not shared data for # queue-stalled-minutes are treated as 'stalled' # and are not counted against the queue-download-size # and seed-queue-size limits. queue-stalled-enabled = true; queue-stalled-minutes = 60; # When true. Transmission will only seed seed-queue-size # non-stalled torrents at once. seed-queue-enabled = false; seed-queue-size = 10; # Enable UPnP or NAT-PMP. peer-port = 51413; port-forwarding-enabled = false; # Start torrents as soon as they are added start-added-torrents = true; # notify me when download finished script-torrent-done-enabled = true; script-torrent-done-filename = completionScript; }; }; networking.firewall = { allowedTCPPorts = [ 51413 ]; allowedUDPPorts = [ 51413 ]; # only allow access via nginx (proxy to localhost) interfaces.eth0 = { allowedTCPPorts = [ uiPort ]; allowedUDPPorts = [ uiPort ]; }; }; # bind transmission to openvpn systemd.services.transmission = { bindsTo = [ "openvpn-nordvpn.service" ]; after = [ "openvpn-nordvpn.service" ]; serviceConfig = { Restart = "always"; EnvironmentFile = [ "/run/secrets/transmissionPushover" ]; BindPaths = lib.mkForce [ "/media" "/var/lib/transmission/.config/transmission-daemon" ]; }; }; services.openvpn.servers.nordvpn.updateResolvConf = true; services.openvpn.servers.nordvpn.config = '' client dev tun proto udp remote 152.89.163.99 1194 dhcp-option DNS 8.8.8.8 remote-random nobind tun-mtu 1500 tun-mtu-extra 32 mssfix 1450 persist-key persist-tun ping 15 ping-restart 0 ping-timer-rem reneg-sec 0 comp-lzo no remote-cert-tls server auth-user-pass /run/secrets/nordvpn verb 3 pull resolv-retry infinite fast-io cipher AES-256-CBC auth SHA512 -----BEGIN CERTIFICATE----- MIIFCjCCAvKgAwIBAgIBATANBgkqhkiG9w0BAQ0FADA5MQswCQYDVQQGEwJQQTEQ MA4GA1UEChMHTm9yZFZQTjEYMBYGA1UEAxMPTm9yZFZQTiBSb290IENBMB4XDTE2 MDEwMTAwMDAwMFoXDTM1MTIzMTIzNTk1OVowOTELMAkGA1UEBhMCUEExEDAOBgNV BAoTB05vcmRWUE4xGDAWBgNVBAMTD05vcmRWUE4gUm9vdCBDQTCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBAMkr/BYhyo0F2upsIMXwC6QvkZps3NN2/eQF kfQIS1gql0aejsKsEnmY0Kaon8uZCTXPsRH1gQNgg5D2gixdd1mJUvV3dE3y9FJr XMoDkXdCGBodvKJyU6lcfEVF6/UxHcbBguZK9UtRHS9eJYm3rpL/5huQMCppX7kU eQ8dpCwd3iKITqwd1ZudDqsWaU0vqzC2H55IyaZ/5/TnCk31Q1UP6BksbbuRcwOV skEDsm6YoWDnn/IIzGOYnFJRzQH5jTz3j1QBvRIuQuBuvUkfhx1FEwhwZigrcxXu MP+QgM54kezgziJUaZcOM2zF3lvrwMvXDMfNeIoJABv9ljw969xQ8czQCU5lMVmA 37ltv5Ec9U5hZuwk/9QO1Z+d/r6Jx0mlurS8gnCAKJgwa3kyZw6e4FZ8mYL4vpRR hPdvRTWCMJkeB4yBHyhxUmTRgJHm6YR3D6hcFAc9cQcTEl/I60tMdz33G6m0O42s Qt/+AR3YCY/RusWVBJB/qNS94EtNtj8iaebCQW1jHAhvGmFILVR9lzD0EzWKHkvy WEjmUVRgCDd6Ne3eFRNS73gdv/C3l5boYySeu4exkEYVxVRn8DhCxs0MnkMHWFK6 MyzXCCn+JnWFDYPfDKHvpff/kLDobtPBf+Lbch5wQy9quY27xaj0XwLyjOltpiST LWae/Q4vAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqG SIb3DQEBDQUAA4ICAQC9fUL2sZPxIN2mD32VeNySTgZlCEdVmlq471o/bDMP4B8g nQesFRtXY2ZCjs50Jm73B2LViL9qlREmI6vE5IC8IsRBJSV4ce1WYxyXro5rmVg/ k6a10rlsbK/eg//GHoJxDdXDOokLUSnxt7gk3QKpX6eCdh67p0PuWm/7WUJQxH2S DxsT9vB/iZriTIEe/ILoOQF0Aqp7AgNCcLcLAmbxXQkXYCCSB35Vp06u+eTWjG0/ pyS5V14stGtw+fA0DJp5ZJV4eqJ5LqxMlYvEZ/qKTEdoCeaXv2QEmN6dVqjDoTAo k0t5u4YRXzEVCfXAC3ocplNdtCA72wjFJcSbfif4BSC8bDACTXtnPC7nD0VndZLp +RiNLeiENhk0oTC+UVdSc+n2nJOzkCK0vYu0Ads4JGIB7g8IB3z2t9ICmsWrgnhd NdcOe15BincrGA8avQ1cWXsfIKEjbrnEuEk9b5jel6NfHtPKoHc9mDpRdNPISeVa wDBM1mJChneHt59Nh8Gah74+TM1jBsw4fhJPvoc7Atcg740JErb904mZfkIEmojC VPhBHVQ9LHBAdM8qFI2kRK0IynOmAZhexlP/aT/kpEsEPyaZQlnBn3An1CRz8h0S PApL8PytggYKeQmRhl499+6jLxcZ2IegLfqq41dzIjwHwTMplg+1pKIOVojpWA== -----END CERTIFICATE----- key-direction 1 # # 2048 bit OpenVPN static key # -----BEGIN OpenVPN Static key V1----- e685bdaf659a25a200e2b9e39e51ff03 0fc72cf1ce07232bd8b2be5e6c670143 f51e937e670eee09d4f2ea5a6e4e6996 5db852c275351b86fc4ca892d78ae002 d6f70d029bd79c4d1c26cf14e9588033 cf639f8a74809f29f72b9d58f9b8f5fe fc7938eade40e9fed6cb92184abb2cc1 0eb1a296df243b251df0643d53724cdb 5a92a1d6cb817804c4a9319b57d53be5 80815bcfcb2df55018cc83fc43bc7ff8 2d51f9b88364776ee9d12fc85cc7ea5b 9741c4f598c485316db066d52db4540e 212e1518a9bd4828219e24b20d88f598 a196c9de96012090e333519ae18d3509 9427e7b372d348d352dc4c85e18cd4b9 3f8a56ddb2e64eb67adfc9b337157ff4 -----END OpenVPN Static key V1----- ''; }; }; # give containers internet access networking.nat.enable = true; networking.nat.internalInterfaces = [ "ve-torrent" ]; networking.nat.externalInterface = hostInterface; # open ports for logging #networking.firewall.interfaces."ve-torrent".allowedTCPPorts = # [ 5044 12304 12305 ]; #networking.firewall.interfaces."ve-torrent".allowedUDPPorts = # [ 5044 12304 12305 ]; # host nginx setup # curl transmission.robi.private < will work # curl -H "Host: transmission.robi.private" http://144.76.13.147/ < won't work services.nginx = { enable = true; recommendedProxySettings = true; virtualHosts = { "transmission.${config.networking.hostName}.private" = { locations."/" = { proxyPass = "http://${containerAddress}:${toString uiPort}"; }; }; }; }; }