diff options
-rw-r--r-- | .buildkite/pipeline.yml | 7 | ||||
-rw-r--r-- | Dockerfile | 12 | ||||
-rw-r--r-- | Makefile | 47 | ||||
-rwxr-xr-x | scripts/ask-tracker-for-more-peers.py | 21 | ||||
-rw-r--r-- | scripts/cron.sh | 28 | ||||
-rwxr-xr-x | scripts/delete-based-on-tag.py | 40 | ||||
-rw-r--r-- | scripts/entrypoint.sh | 28 | ||||
-rw-r--r-- | scripts/requirements.txt | 8 | ||||
-rwxr-xr-x | scripts/tag-based-on-dir.py | 30 |
9 files changed, 221 insertions, 0 deletions
diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml new file mode 100644 index 0000000..313fd75 --- /dev/null +++ b/.buildkite/pipeline.yml @@ -0,0 +1,7 @@ +--- +steps: + - name: push + branches: [master] + command: | + make docker-push \ + DOCKER_EXTRA_ARGS="-v ${HOME}/.docker:/root/.docker" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e28354d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:slim-bullseye +COPY scripts /scripts +RUN \ + apt-get update && \ + apt-get install -y --no-install-recommends cron && \ + apt-get clean && \ + pip install --no-cache-dir -r /scripts/requirements.txt && \ + rm -rf /scripts/requirements.txt && \ + rm -rf /var/lib/apt/lists/* && \ + touch crontab /etc/cron.d/crontab && \ + chmod 0644 /etc/cron.d/crontab +CMD ["bash", "/scripts/entrypoint.sh"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..54c1696 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +IMAGENAME := docker.io/chn2guevara/transmission-hacks +VERSION := 1 +BUILDARG_PLATFORM := --platform linux/amd64,linux/arm64/v8 +DOCKER_EXTRA_ARGS := + +ci-deps: + apt-get -qq -y install \ + binfmt-support \ + ca-certificates \ + curl \ + git \ + gnupg \ + lsb-release \ + qemu-user-static \ + wget \ + jq + +ci-deps-docker: + curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \ + echo "deb [arch=$(shell dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(shell lsb_release -cs) stable" |\ + tee /etc/apt/sources.list.d/docker.list > /dev/null && \ + cat /etc/apt/sources.list.d/docker.list && \ + apt-get update && \ + apt-get -qq -y install \ + docker-ce \ + docker-ce-cli \ + containerd.io + +ci-setup-buildx: + docker run --privileged --rm tonistiigi/binfmt --install all + -docker buildx create --name mybuilder + docker buildx use mybuilder + +ci-prepare: ci-deps ci-deps-docker ci-setup-buildx + +push: ci-prepare + docker buildx build -t $(IMAGENAME):latest . --push + docker buildx build -t $(IMAGENAME):$(VERSION) . --push + +docker-%: + docker run \ + --rm \ + --privileged \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $(shell pwd):/data \ + -w /data $(DOCKER_EXTRA_ARGS) \ + debian:stable sh -c "apt-get update && apt-get install make && make $*" diff --git a/scripts/ask-tracker-for-more-peers.py b/scripts/ask-tracker-for-more-peers.py new file mode 100755 index 0000000..8b874f0 --- /dev/null +++ b/scripts/ask-tracker-for-more-peers.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import click +from transmission_rpc import Client + + +@click.command() +@click.option('--port', default=9091) +@click.option('--host', default="localhost") +def main(host, port): + """ Reannounces all torrents that have not received any peers. """ + + c = Client(host=host, port=port) + torrents = c.get_torrents() + for torrent in torrents: + if torrent.progress == 0 and torrent.downloading: + print(f"Reannouncing {torrent.name}") + c.reannounce_torrent(torrent.id) + + +if __name__ == '__main__': + main() diff --git a/scripts/cron.sh b/scripts/cron.sh new file mode 100644 index 0000000..3cde994 --- /dev/null +++ b/scripts/cron.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -euo pipefail + +declare -A tag_age +i=0 +while true; do + key="DELETE_TAG_${i}" + val="DELETE_AGE_${i}" + if [ -z "${!key:-}" ]; then + break + fi + tag_age["${!key}"]="${!val}" + i=$((i+1)) +done + +${PYTHON_PATH} "${SCRIPT_PREFIX}"/tag-based-on-dir.py \ + --host "${TRANSMISSION_HOST}" + +${PYTHON_PATH} "${SCRIPT_PREFIX}"/ask-tracker-for-more-peers.py \ + --host "${TRANSMISSION_HOST}" + +for key in "${!tag_age[@]}" +do + ${PYTHON_PATH} "${SCRIPT_PREFIX}"/delete-based-on-tag.py \ + --host "${TRANSMISSION_HOST}" \ + --tag "${key}" \ + --age "${tag_age[$key]}" +done diff --git a/scripts/delete-based-on-tag.py b/scripts/delete-based-on-tag.py new file mode 100755 index 0000000..a5e2d0b --- /dev/null +++ b/scripts/delete-based-on-tag.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import click +from transmission_rpc import Client +from datetime import timedelta, datetime + + +def convert_to_seconds(s): + units = {"s": "seconds", "m": "minutes", + "h": "hours", "d": "days", "w": "weeks"} + count = int(s[:-1]) + unit = units[s[-1]] + td = timedelta(**{unit: count}) + return td.seconds + 60 * 60 * 24 * td.days + + +@click.command() +@click.option('--port', default=9091) +@click.option('--host', default="localhost") +@click.option('--tag', required=True) +@click.option('--age', default='1w') +def main(host, port, tag, age): + """ Deletes torrents older than the specified age. """ + + c = Client(host=host, port=port) + torrents = c.get_torrents() + for torrent in torrents: + if tag not in torrent.labels: + continue + + specified_age = convert_to_seconds(age) + age_in_seconds = int((datetime.today().timestamp() - + torrent.added_date.timestamp())) + + if age_in_seconds > specified_age: + print(f"Deleting {torrent.name}") + c.remove_torrent(torrent.id, delete_data=True) + + +if __name__ == '__main__': + main() diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh new file mode 100644 index 0000000..4d89be5 --- /dev/null +++ b/scripts/entrypoint.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -euo pipefail + +cat <<EOF >> /etc/cron.d/crontab +PYTHON_PATH=/usr/local/bin/python3 +SCRIPT_PREFIX=/scripts +TRANSMISSION_HOST=${TRANSMISSION_HOST:-localhost} +TRANSMISSION_PORT=${TRANSMISSION_PORT:-9091} +EOF + +i=0 +while true; do + key="DELETE_TAG_${i}_NAME" + val="DELETE_TAG_${i}_AGE" + if [ -z "${!key:-}" ]; then + break + fi + cat <<EOF >> /etc/cron.d/crontab +DELETE_TAG_${i}=${!key} +DELETE_AGE_${i}=${!val} +EOF + i=$((i+1)) +done + +echo "${CRON_EXPRESSION:-* * * * *} /bin/bash /scripts/cron.sh >/proc/1/fd/1 2>/proc/1/fd/2" >> /etc/cron.d/crontab +cat /etc/cron.d/crontab +crontab /etc/cron.d/crontab +cron -f -l "${CRON_LOG_LEVEL:-0}" diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000..66904d3 --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,8 @@ +certifi==2023.5.7 +charset-normalizer==3.1.0 +click==8.1.3 +idna==3.4 +requests==2.31.0 +transmission-rpc==4.3.0 +typing_extensions==4.6.3 +urllib3==2.0.3 diff --git a/scripts/tag-based-on-dir.py b/scripts/tag-based-on-dir.py new file mode 100755 index 0000000..8cec41b --- /dev/null +++ b/scripts/tag-based-on-dir.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import click +from transmission_rpc import Client + + +@click.command() +@click.option('--port', default=9091) +@click.option('--root-dir', default="/downloads/complete") +@click.option('--host', default="localhost") +def main(host, port, root_dir): + """Adds tags to torrents based on their download directory.""" + + c = Client(host=host, port=port) + torrents = c.get_torrents() + for torrent in torrents: + dir = torrent.download_dir.replace(root_dir, '') + label = 'none' + if dir != '': + label = dir.split('/')[1] + + labels = list([label]) + labels.extend(x for x in torrent.labels if x not in labels) + + if set(labels) != set(torrent.labels): + print(f"Tagging {torrent.name}") + c.change_torrent(torrent.id, labels=labels) + + +if __name__ == '__main__': + main() |