From ff7b0459bcb4ef121659f6e3f5eaf618d1444a6a Mon Sep 17 00:00:00 2001 From: Bram Date: Fri, 24 Oct 2025 22:18:01 +0200 Subject: [PATCH] port syncer --- .../Dockerfile | 34 +++++ .../gluetun-qbittorrent-port-syncer/main.py | 120 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 Dockers/gluetun-qbittorrent-port-syncer/Dockerfile create mode 100644 Dockers/gluetun-qbittorrent-port-syncer/main.py diff --git a/Dockers/gluetun-qbittorrent-port-syncer/Dockerfile b/Dockers/gluetun-qbittorrent-port-syncer/Dockerfile new file mode 100644 index 0000000..b1862e8 --- /dev/null +++ b/Dockers/gluetun-qbittorrent-port-syncer/Dockerfile @@ -0,0 +1,34 @@ +FROM python:3.11-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies +RUN pip install --no-cache-dir \ + requests \ + watchdog + +# Create app directory +WORKDIR /app + +# Copy the Python script +COPY main.py . + +# Make the script executable +RUN chmod +x main.py + +# Create a non-root user +RUN useradd -m -u 1000 appuser && \ + chown -R appuser:appuser /app + +# Switch to non-root user +USER appuser + +# Set default environment variables +ENV PORT_FORWARDED=/tmp/port_forwarded +ENV HTTP_S=http + +# Run the application +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Dockers/gluetun-qbittorrent-port-syncer/main.py b/Dockers/gluetun-qbittorrent-port-syncer/main.py new file mode 100644 index 0000000..a83bb0c --- /dev/null +++ b/Dockers/gluetun-qbittorrent-port-syncer/main.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +import os +import time +import requests +import tempfile +from pathlib import Path +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +class PortFileHandler(FileSystemEventHandler): + def __init__(self, port_syncer): + self.port_syncer = port_syncer + + def on_modified(self, event): + if not event.is_directory and event.src_path == self.port_syncer.port_file: + self.port_syncer.update_port() + +class QBitTorrentPortSyncer: + def __init__(self): + self.cookies_file = "/tmp/cookies.txt" + self.port_file = os.getenv('PORT_FORWARDED', '/tmp/port_forwarded') + self.qbittorrent_user = os.getenv('QBITTORRENT_USER') + self.qbittorrent_pass = os.getenv('QBITTORRENT_PASS') + self.qbittorrent_server = os.getenv('QBITTORRENT_SERVER') + self.qbittorrent_port = os.getenv('QBITTORRENT_PORT') + self.http_s = os.getenv('HTTP_S', 'http') + + # Validate required environment variables + required_vars = [ + 'PORT_FORWARDED', 'QBITTORRENT_USER', 'QBITTORRENT_PASS', + 'QBITTORRENT_SERVER', 'QBITTORRENT_PORT' + ] + + missing_vars = [var for var in required_vars if not os.getenv(var)] + if missing_vars: + raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}") + + def update_port(self): + """Update qBittorrent port from the port file""" + try: + # Read the port from the file + with open(self.port_file, 'r') as f: + port = f.read().strip() + + if not port: + print(f"Warning: Port file {self.port_file} is empty") + return + + # Remove old cookies file + if os.path.exists(self.cookies_file): + os.remove(self.cookies_file) + + # Login to qBittorrent + login_url = f"{self.http_s}://{self.qbittorrent_server}:{self.qbittorrent_port}/api/v2/auth/login" + login_data = { + 'username': self.qbittorrent_user, + 'password': self.qbittorrent_pass + } + + session = requests.Session() + login_response = session.post(login_url, data=login_data) + + if login_response.status_code != 200: + print(f"Failed to login to qBittorrent: HTTP {login_response.status_code}") + return + + # Update port preferences + prefs_url = f"{self.http_s}://{self.qbittorrent_server}:{self.qbittorrent_port}/api/v2/app/setPreferences" + prefs_data = { + 'json': f'{{"listen_port": "{port}"}}' + } + + prefs_response = session.post(prefs_url, data=prefs_data) + + if prefs_response.status_code == 200: + print(f"Successfully updated qbittorrent to port {port}") + else: + print(f"Failed to update port: HTTP {prefs_response.status_code}") + + except FileNotFoundError: + print(f"Couldn't find file {self.port_file}") + except Exception as e: + print(f"Error updating port: {e}") + + def run(self): + """Main loop to monitor port file changes""" + observer = Observer() + event_handler = PortFileHandler(self) + + while True: + if os.path.exists(self.port_file): + print(f"Found port file {self.port_file}, starting monitoring...") + + # Initial port update + self.update_port() + + # Start file monitoring + observer.schedule(event_handler, os.path.dirname(self.port_file), recursive=False) + observer.start() + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + observer.stop() + break + else: + print(f"Couldn't find file {self.port_file}") + print("Trying again in 10 seconds") + time.sleep(10) + +if __name__ == "__main__": + try: + syncer = QBitTorrentPortSyncer() + syncer.run() + except KeyboardInterrupt: + print("\nShutting down...") + except Exception as e: + print(f"Error: {e}") + exit(1) \ No newline at end of file