diff --git a/packages/default.nix b/packages/default.nix index e4cc0a5..13352ff 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -36,6 +36,7 @@ docker-shlink-web-client = import ./docker/shlink-web-client { inherit pkgs inputs system; }; docker-sish = import ./docker/sish { inherit pkgs; }; docker-traefik = import ./docker/traefik { inherit pkgs; }; + docker-transmission-protonvpn = import ./docker/transmission-protonvpn { inherit pkgs; }; docker-whoami = import ./docker/whoami { inherit pkgs; }; docker-yq = import ./docker/yq { inherit pkgs; }; diff --git a/packages/docker/mariadb/entrypoint.sh b/packages/docker/mariadb/entrypoint.sh index 0b692ed..c9d00c0 100644 --- a/packages/docker/mariadb/entrypoint.sh +++ b/packages/docker/mariadb/entrypoint.sh @@ -32,7 +32,7 @@ if [ ! -f "$DATADIR/mysql_upgrade_info" ]; then wait "$PID" || true fi -trap 'kill -QUIT "$PID"' INT +trap 'kill -QUIT "$PID"' INT TERM mariadbd --user=root --datadir="$DATADIR" "$@" & PID=$! wait "$PID" diff --git a/packages/docker/nextcloud/entrypoint.sh b/packages/docker/nextcloud/entrypoint.sh index a13a4ff..1c31e6c 100644 --- a/packages/docker/nextcloud/entrypoint.sh +++ b/packages/docker/nextcloud/entrypoint.sh @@ -79,6 +79,6 @@ until [ -s "$PIDFILE" ]; do sleep 0.01; done PID=$(cat "$PIDFILE") rm "$PIDFILE" -trap 'kill -INT "$PID"' INT +trap 'kill -INT "$PID"' INT TERM wait "$PID" exit $? diff --git a/packages/docker/transmission-protonvpn/default.nix b/packages/docker/transmission-protonvpn/default.nix new file mode 100644 index 0000000..56587d0 --- /dev/null +++ b/packages/docker/transmission-protonvpn/default.nix @@ -0,0 +1,49 @@ +{ pkgs, ... }: +let + entrypoint = pkgs.writeTextFile { + name = "entrypoint"; + executable = true; + destination = "/bin/entrypoint"; + text = builtins.readFile ./entrypoint.sh; + }; +in +pkgs.dockerTools.buildImage { + name = "transmission-protonvpn"; + fromImage = import ../base { inherit pkgs; }; + + copyToRoot = pkgs.buildEnv { + name = "root"; + paths = with pkgs; [ + entrypoint + transmission_4 + flood-for-transmission + wireguard-tools + libnatpmp + curl + jq + ]; + pathsToLink = [ + "/bin" + "/lib" + "/share" + ]; + }; + + runAsRoot = '' + mkdir -p /tmp + ''; + + config = { + Entrypoint = [ "entrypoint" ]; + ExposedPorts = { + "9091/tcp" = { }; + }; + Volumes = { + "/etc/transmission" = { }; + "/var/lib/transmission" = { }; + }; + Env = [ + "TRANSMISSION_WEB_HOME=${pkgs.flood-for-transmission}" + ]; + }; +} diff --git a/packages/docker/transmission-protonvpn/entrypoint.sh b/packages/docker/transmission-protonvpn/entrypoint.sh new file mode 100644 index 0000000..5ba5325 --- /dev/null +++ b/packages/docker/transmission-protonvpn/entrypoint.sh @@ -0,0 +1,122 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +WIREGUARD_PRIVATE_KEY_PATH="${WIREGUARD_PRIVATE_KEY_PATH:-/etc/wireguard/privatekey}" +WIREGUARD_ALLOWED_IPS="${WIREGUARD_ALLOWED_IPS:-0.0.0.0/0}" +WIREGUARD_ADDRESS="${WIREGUARD_ADDRESS:-10.2.0.2/32}" +WIREGUARD_DNS="${WIREGUARD_DNS:-10.2.0.1}" + +WIREGUARD_PEER_IP="${WIREGUARD_ENDPOINT%%:*}" +DEFAULT_IFACE=$(ip route show default | awk '/default/ {print $5; exit}') + +ip link add dev wg0 type wireguard + +ip address add "$WIREGUARD_ADDRESS" dev wg0 +wg set wg0 private-key "$WIREGUARD_PRIVATE_KEY_PATH" +wg set wg0 peer "$WIREGUARD_PUBLIC_KEY" allowed-ips "$WIREGUARD_ALLOWED_IPS" endpoint "$WIREGUARD_ENDPOINT" + +ip link set up dev wg0 + +ip route add "$WIREGUARD_PEER_IP/32" dev "$DEFAULT_IFACE" +ip route add 0.0.0.0/0 dev wg0 + +echo "nameserver $WIREGUARD_DNS" > /etc/resolv.conf + +PIPE=$(mktemp -u) +mkfifo "$PIPE" + +BIND_IP="${WIREGUARD_ADDRESS%%/*}" + +transmission-daemon -d \ + --no-portmap \ + --bind-address-ipv4 "$BIND_IP" \ + --bind-address-ipv6 "::1" \ + "$@" 2> /etc/transmission/settings.json + +tmpfile="$(mktemp)" +jq '. + { + "rpc-whitelist-enabled": false, + "rpc-host-whitelist-enabled": false, + "rpc-url": "/", + "download-dir": "/var/lib/transmission", + "incomplete-dir": "/var/lib/transmission/incomplete", + "rename-partial-files": true +}' /etc/transmission/settings.json > "$tmpfile" +mv "$tmpfile" /etc/transmission/settings.json + +if [ -f /etc/transmission/settings.override.json ]; then + tmpfile="$(mktemp)" + jq -s \ + '.[0] * .[1]' \ + /etc/transmission/settings.json \ + /etc/transmission/settings.override.json \ + > "$tmpfile" + mv "$tmpfile" /etc/transmission/settings.json +fi + +transmission-daemon -f \ + --config-dir /etc/transmission \ + --no-portmap \ + --bind-address-ipv4 "$BIND_IP" \ + --bind-address-ipv6 "::1" \ + "$@" > "$PIPE" 2>&1 & +PID=$! + +CAT_PIPE=$(mktemp -u) +GREP_PIPE=$(mktemp -u) +mkfifo "$CAT_PIPE" "$GREP_PIPE" + +tee "$CAT_PIPE" "$GREP_PIPE" < "$PIPE" > /dev/null & +cat "$CAT_PIPE" & +grep -qm1 "Serving RPC and Web requests on 0.0.0.0:9091" < "$GREP_PIPE" + +rpc_path="$(jq -r '.["rpc-url"]' /etc/transmission/settings.json)" +rpc_url="http://127.0.0.1:9091${rpc_path}rpc/" + +( + set +o errexit + + while true; do + natpmp_output="$(natpmpc -a 1 0 udp 60 -g 10.2.0.1)" + echo "$natpmp_output" + + natpmp_output="$(natpmpc -a 1 0 tcp 60 -g 10.2.0.1)" + echo "$natpmp_output" + + natpmp_port="$(echo "$natpmp_output" | awk '/Mapped public port/ { print $4 }')" + + output_headers=$(curl -s -D - -o /dev/null -X POST "$rpc_url" \ + -H "Content-Type: application/json" \ + -d '{"method": "session-get", "arguments": {"fields": ["session-id"]}}') + + session_id="$(echo "$output_headers" | awk '/X-Transmission-Session-Id:/ { print $2 }' | tr -d '\r')" + + curl -s -X POST "$rpc_url" \ + -H "X-Transmission-Session-Id: $session_id" \ + -H "Content-Type: application/json" \ + -d "{\"method\": \"session-set\", \"arguments\": {\"peer-port\": $natpmp_port}}" \ + > /dev/null + + sleep 45 + done +) & +NATPMP_PID=$! + +# shellcheck disable=SC2317 +cleanup() { + kill -INT "$PID" "$NATPMP_PID" || true + + ip route del 0.0.0.0/0 dev wg0 + ip route del "$WIREGUARD_PEER_IP/32" dev "$DEFAULT_IFACE" + + ip link set down dev wg0 + ip link delete dev wg0 + + rm -f "$PIPE" "$CAT_PIPE" "$GREP_PIPE" +} + +trap cleanup INT TERM +wait "$PID" +exit $?