ytp-dl 0.6.4__tar.gz → 0.6.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ytp-dl
3
- Version: 0.6.4
3
+ Version: 0.6.6
4
4
  Summary: YouTube video downloader with Mullvad VPN integration and Flask API
5
5
  Home-page: https://github.com/yourusername/ytp-dl
6
6
  Author: dumgum82
@@ -33,39 +33,39 @@ Dynamic: summary
33
33
 
34
34
  # ytp-dl
35
35
 
36
- > A lightweight YouTube downloader with Mullvad VPN integration and HTTP API
36
+ A lightweight YouTube downloader with Mullvad VPN integration and an HTTP API.
37
37
 
38
38
  [![PyPI version](https://img.shields.io/pypi/v/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
39
39
  [![Python Support](https://img.shields.io/pypi/pyversions/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
40
40
  [![License](https://img.shields.io/pypi/l/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
41
41
  [![Downloads](https://img.shields.io/pypi/dm/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
42
42
 
43
- **ytp-dl** is a privacy-focused YouTube downloader that automatically routes downloads through Mullvad VPN via an HTTP API.
43
+ **ytp-dl** is a privacy-focused YouTube downloader that routes downloads through Mullvad VPN via an HTTP API.
44
44
 
45
45
  ---
46
46
 
47
- ## Features
47
+ ## Features
48
48
 
49
- * 🔒 **Privacy First** — Automatically connects/disconnects Mullvad VPN per download
50
- * 🎥 **Smart Quality Selection** Prefers 1080p H.264 + AAC (no transcoding needed)
51
- * 🎵 **Audio Downloads** Extract audio as MP3
52
- * 🚀 **HTTP API** Simple Flask-based API with concurrency controls
53
- * ⚡ **VPS Ready** — Includes automated installer script for Ubuntu
49
+ * Privacy-first: connect/disconnect Mullvad per download
50
+ * Smart quality selection: prefers 1080p H.264 + AAC (no transcoding)
51
+ * Audio downloads: extract audio as MP3
52
+ * HTTP API: simple Flask API with concurrency controls
53
+ * VPS-ready: automated installer script for Ubuntu
54
54
 
55
55
  ---
56
56
 
57
- ## 📦 Installation
57
+ ## Installation
58
58
 
59
59
  ```bash
60
- pip install ytp-dl==0.6.4 yt-dlp[default]
60
+ pip install ytp-dl==0.6.5 yt-dlp[default]
61
61
  ```
62
62
 
63
- **Requirements:**
63
+ Requirements:
64
64
 
65
- * Linux operating system (tested on Ubuntu 24.04/25.04)
66
- * [Mullvad CLI](https://mullvad.net/en/download/vpn/linux) installed and configured
67
- * FFmpeg (for handling audio + video)
68
- * Deno (system-wide, required by yt-dlp for modern YouTube extraction)
65
+ * Linux (tested on Ubuntu 24.04/25.04)
66
+ * Mullvad CLI installed and configured
67
+ * FFmpeg (audio/video handling)
68
+ * Deno (system-wide; required by yt-dlp for modern YouTube extraction)
69
69
  * Python 3.8+
70
70
 
71
71
  Notes:
@@ -74,29 +74,38 @@ Notes:
74
74
 
75
75
  ---
76
76
 
77
- ## 🎯 Using Your VPS
77
+ ## Using your VPS
78
78
 
79
- Once your VPS is running, you can download videos using simple HTTP requests:
79
+ Once your VPS is running, you can download videos using simple HTTP requests.
80
80
 
81
- ### Download a Video (1080p MP4)
81
+ ### Download a video (1080p MP4)
82
82
 
83
83
  ```bash
84
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}' --output video.mp4
84
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
85
+ -H "Content-Type: application/json" \
86
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}' \
87
+ --output video.mp4
85
88
  ```
86
89
 
87
- ### Download Audio Only (MP3)
90
+ ### Download audio only (MP3)
88
91
 
89
92
  ```bash
90
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "extension": "mp3"}' --output audio.mp3
93
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
94
+ -H "Content-Type: application/json" \
95
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "extension": "mp3"}' \
96
+ --output audio.mp3
91
97
  ```
92
98
 
93
- ### Download at Specific Resolution
99
+ ### Download at a specific resolution
94
100
 
95
101
  ```bash
96
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "resolution": 720}' --output video.mp4
102
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
103
+ -H "Content-Type: application/json" \
104
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "resolution": 720}' \
105
+ --output video.mp4
97
106
  ```
98
107
 
99
- ### Check Server Health
108
+ ### Check server health
100
109
 
101
110
  ```bash
102
111
  curl http://YOUR_VPS_IP:5000/healthz
@@ -124,7 +133,7 @@ response = requests.post(
124
133
  "resolution": 1080,
125
134
  "extension": "mp4"
126
135
  },
127
- stream=True
136
+ stream=True,
128
137
  )
129
138
 
130
139
  if response.status_code == 200:
@@ -139,28 +148,29 @@ else:
139
148
 
140
149
  ---
141
150
 
142
- ## ⚙️ Configuration
151
+ ## Configuration
143
152
 
144
- ### Installation Script Variables
153
+ ### Installation script variables
145
154
 
146
- These environment variables configure the VPS installation (they can be overridden when running the script):
155
+ These environment variables configure the VPS installation (you can override them when running the script).
147
156
 
148
157
  | Variable | Description | Default |
149
158
  | ------------------------ | --------------------------------------- | --------------------- |
150
159
  | `PORT` | API server port | `5000` |
151
160
  | `APP_DIR` | Installation directory | `/opt/yt-dlp-mullvad` |
152
- | `MV_ACCOUNT` | Mullvad account number | your mullvad id |
161
+ | `MV_ACCOUNT` | Mullvad account number | (empty) |
153
162
  | `YTPDL_MAX_CONCURRENT` | Max simultaneous downloads (API cap) | `1` |
154
163
  | `YTPDL_MULLVAD_LOCATION` | Mullvad relay location code (e.g. `us`) | `us` |
164
+ | `GUNICORN_THREADS` | Threads per Gunicorn worker | `4` |
155
165
 
156
166
  Notes:
157
167
 
158
168
  * If `MV_ACCOUNT` is set, the installer attempts `mullvad account login <MV_ACCOUNT>` once.
159
169
  * If `MV_ACCOUNT` is left empty, the script skips login and assumes Mullvad is already configured.
160
170
 
161
- ### Runtime Environment Variables
171
+ ### Runtime environment variables
162
172
 
163
- After installation, these variables control the API behavior. They are set in `/etc/default/ytp-dl-api` and can be edited manually:
173
+ After installation, these are set in `/etc/default/ytp-dl-api` and can be edited manually.
164
174
 
165
175
  | Variable | Description | Default |
166
176
  | ------------------------ | ----------------------------- | -------------------------- |
@@ -177,50 +187,33 @@ sudo systemctl restart ytp-dl-api
177
187
 
178
188
  ---
179
189
 
180
- ## 🔧 Managing Your VPS Service
181
-
182
- ### View Service Status
190
+ ## Managing your VPS service
183
191
 
184
192
  ```bash
185
193
  sudo systemctl status ytp-dl-api
186
- ```
187
-
188
- ### View Logs
189
-
190
- ```bash
191
194
  sudo journalctl -u ytp-dl-api -f
192
- ```
193
-
194
- ### Restart Service
195
-
196
- ```bash
197
195
  sudo systemctl restart ytp-dl-api
198
- ```
199
-
200
- ### Stop/Start Service
201
-
202
- ```bash
203
196
  sudo systemctl stop ytp-dl-api
204
197
  sudo systemctl start ytp-dl-api
205
198
  ```
206
199
 
207
200
  ---
208
201
 
209
- ## 📋 API Reference
202
+ ## API reference
210
203
 
211
204
  ### POST `/api/download`
212
205
 
213
- **Request Body:**
206
+ Request body:
214
207
 
215
208
  ```json
216
209
  {
217
210
  "url": "string (required)",
218
211
  "resolution": "integer (optional, default: 1080)",
219
- "extension": "string (optional, 'mp4' or 'mp3')"
212
+ "extension": "string (optional, 'mp4', 'mp3', or 'best')"
220
213
  }
221
214
  ```
222
215
 
223
- **Response:**
216
+ Response:
224
217
 
225
218
  * `200 OK` - File download stream
226
219
  * `400 Bad Request` - Missing or invalid URL
@@ -229,8 +222,6 @@ sudo systemctl start ytp-dl-api
229
222
 
230
223
  ### GET `/healthz`
231
224
 
232
- **Response:**
233
-
234
225
  ```json
235
226
  {
236
227
  "ok": true,
@@ -241,16 +232,22 @@ sudo systemctl start ytp-dl-api
241
232
 
242
233
  ---
243
234
 
244
- ## 🖥️ VPS Deployment
235
+ ## VPS deployment
236
+
237
+ ### Why policy routing is included
238
+
239
+ When Mullvad connects/disconnects, Linux routing can change in a way that breaks inbound TCP handshakes (clients see intermittent connection timeouts). The installer sets up a small policy-routing rule that forces replies sourced from your public VPS IP to use the public interface, keeping the API reachable even while Mullvad is cycling.
245
240
 
246
- **What it does:**
241
+ ### What the installer does
247
242
 
248
- * Installs Python, FFmpeg, and Mullvad CLI
249
- * Installs Deno system-wide (required by yt-dlp for modern YouTube extraction)
250
- * Creates virtualenv at `/opt/yt-dlp-mullvad/venv`
251
- * Installs `ytp-dl==0.6.4` + `yt-dlp[default]` into the virtualenv
252
- * Sets up systemd service on port 5000
253
- * Configures Gunicorn with gthread (threaded) workers
243
+ * Installs Python, FFmpeg, Mullvad CLI, and Deno
244
+ * Creates a virtualenv at `/opt/yt-dlp-mullvad/venv`
245
+ * Installs `ytp-dl==0.6.5` + `yt-dlp[default]` + `gunicorn`
246
+ * Installs a policy-routing oneshot service to keep the public API reachable
247
+ * Sets up a systemd service on port 5000
248
+ * Runs Gunicorn with `gthread` (threaded) workers
249
+
250
+ Note: `gthread` is a built-in Gunicorn worker class (no extra Python dependency).
254
251
 
255
252
  ```bash
256
253
  #!/usr/bin/env bash
@@ -259,9 +256,10 @@ sudo systemctl start ytp-dl-api
259
256
  # What this does:
260
257
  # - Installs Python, ffmpeg, Mullvad CLI
261
258
  # - Installs Deno system-wide (JS runtime required for modern YouTube extraction via yt-dlp)
259
+ # - Configures policy routing so the public API stays reachable while Mullvad toggles
262
260
  # - Creates a virtualenv at /opt/yt-dlp-mullvad/venv
263
- # - Installs ytp-dl==0.6.4 + yt-dlp[default] + gunicorn + gevent in that venv
264
- # - Creates a simple systemd service ytp-dl-api.service on port 5000
261
+ # - Installs ytp-dl==0.6.5 + yt-dlp[default] + gunicorn in that venv
262
+ # - Creates a systemd service ytp-dl-api.service on port 5000
265
263
  #
266
264
  # Mullvad connect/disconnect is handled per-job by downloader.py.
267
265
 
@@ -272,18 +270,33 @@ PORT="${PORT:-5000}" # API listen port
272
270
  APP_DIR="${APP_DIR:-/opt/yt-dlp-mullvad}" # app/venv root
273
271
  VENV_DIR="${VENV_DIR:-${APP_DIR}/venv}" # python venv
274
272
 
275
- MV_ACCOUNT="${MV_ACCOUNT:-}" # Mullvad account (put number after -)
273
+ MV_ACCOUNT="${MV_ACCOUNT:-}" # Mullvad account (optional)
276
274
  YTPDL_MAX_CONCURRENT="${YTPDL_MAX_CONCURRENT:-1}" # API concurrency cap
277
275
  YTPDL_MULLVAD_LOCATION="${YTPDL_MULLVAD_LOCATION:-us}" # default Mullvad relay hint
278
- GUNICORN_THREADS="${GUNICORN_THREADS:-4}" # keep API responsive on tiny VPS
276
+ GUNICORN_THREADS="${GUNICORN_THREADS:-4}" # keep API responsive on small VPS
279
277
  ### -------------------------------------------------------------------------
280
278
 
281
279
  [[ "${EUID}" -eq 0 ]] || { echo "Please run as root"; exit 1; }
282
280
  export DEBIAN_FRONTEND=noninteractive
283
281
 
282
+ echo "==> 0) Capture public routing (pre-VPN)"
283
+ PUB_DEV="$(ip route show default | awk '/default/ {print $5; exit}')"
284
+ PUB_GW="$(ip route show default | awk '/default/ {print $3; exit}')"
285
+ PUB_IP="$(ip -4 addr show dev "${PUB_DEV}" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1)"
286
+
287
+ if [[ -z "${PUB_DEV}" || -z "${PUB_GW}" || -z "${PUB_IP}" ]]; then
288
+ echo "Failed to detect public routing (PUB_DEV/PUB_GW/PUB_IP)."
289
+ echo "PUB_DEV=${PUB_DEV} PUB_GW=${PUB_GW} PUB_IP=${PUB_IP}"
290
+ exit 1
291
+ fi
292
+
293
+ echo "Public dev: ${PUB_DEV} | gw: ${PUB_GW} | ip: ${PUB_IP}"
294
+
284
295
  echo "==> 1) Base packages & Mullvad CLI"
285
296
  apt-get update
286
- apt-get install -yq --no-install-recommends python3-venv python3-pip curl ffmpeg ca-certificates unzip
297
+ apt-get install -yq --no-install-recommends \
298
+ python3-venv python3-pip curl ffmpeg ca-certificates unzip \
299
+ iproute2 iptables
287
300
 
288
301
  if ! command -v mullvad >/dev/null 2>&1; then
289
302
  curl -fsSLo /tmp/mullvad.deb https://mullvad.net/download/app/deb/latest/
@@ -302,6 +315,72 @@ mullvad status || true
302
315
  mullvad lockdown-mode set off || true
303
316
  mullvad lan set allow || true
304
317
 
318
+ echo "==> 1.1) Policy routing: keep replies from ${PUB_IP} on ${PUB_DEV}"
319
+ # Loose reverse-path filtering avoids drops when the default route changes under VPN.
320
+ tee /etc/sysctl.d/99-ytpdl-policy-routing.conf >/dev/null <<EOF
321
+ net.ipv4.conf.all.rp_filter=2
322
+ net.ipv4.conf.default.rp_filter=2
323
+ net.ipv4.conf.${PUB_DEV}.rp_filter=2
324
+ EOF
325
+ sysctl --system >/dev/null
326
+
327
+ # Persist the detected public route info for re-apply at boot.
328
+ tee /etc/default/ytpdl-policy-routing >/dev/null <<EOF
329
+ PUB_DEV=${PUB_DEV}
330
+ PUB_GW=${PUB_GW}
331
+ PUB_IP=${PUB_IP}
332
+ EOF
333
+
334
+ # Add a routing table id if it doesn't already exist.
335
+ grep -qE '^100\s+ytpdl-public$' /etc/iproute2/rt_tables || echo '100 ytpdl-public' >> /etc/iproute2/rt_tables
336
+
337
+ # Idempotent apply script.
338
+ tee /usr/local/sbin/ytpdl-policy-routing.sh >/dev/null <<'EOF'
339
+ #!/usr/bin/env bash
340
+ set -euo pipefail
341
+
342
+ source /etc/default/ytpdl-policy-routing
343
+
344
+ TABLE_ID="100"
345
+ TABLE_NAME="ytpdl-public"
346
+ PRIO="11000"
347
+
348
+ # Ensure table has the public default route.
349
+ ip route replace default via "${PUB_GW}" dev "${PUB_DEV}" table "${TABLE_NAME}"
350
+
351
+ # Ensure rule exists (replace is not supported for rules).
352
+ if ip rule show | grep -qE "^${PRIO}:.*from ${PUB_IP}/32 lookup ${TABLE_NAME}\b"; then
353
+ :
354
+ else
355
+ # remove any stale rule at this priority
356
+ while ip rule show | grep -qE "^${PRIO}:"; do
357
+ ip rule del priority "${PRIO}" || true
358
+ done
359
+ ip rule add priority "${PRIO}" from "${PUB_IP}/32" table "${TABLE_NAME}"
360
+ fi
361
+
362
+ ip route flush cache || true
363
+ EOF
364
+ chmod +x /usr/local/sbin/ytpdl-policy-routing.sh
365
+
366
+ tee /etc/systemd/system/ytpdl-policy-routing.service >/dev/null <<EOF
367
+ [Unit]
368
+ Description=ytp-dl policy routing (keep public API reachable)
369
+ After=network-online.target
370
+ Wants=network-online.target
371
+
372
+ [Service]
373
+ Type=oneshot
374
+ ExecStart=/usr/local/sbin/ytpdl-policy-routing.sh
375
+ RemainAfterExit=yes
376
+
377
+ [Install]
378
+ WantedBy=multi-user.target
379
+ EOF
380
+
381
+ systemctl daemon-reload
382
+ systemctl enable --now ytpdl-policy-routing.service
383
+
305
384
  echo "==> 1.5) Install Deno (system-wide, for yt-dlp YouTube extraction)"
306
385
  # Install into /usr/local/bin/deno so systemd PATH can see it.
307
386
  # Official installer supports system-wide install via DENO_INSTALL=/usr/local.
@@ -315,7 +394,7 @@ mkdir -p "${APP_DIR}"
315
394
  python3 -m venv "${VENV_DIR}"
316
395
  source "${VENV_DIR}/bin/activate"
317
396
  pip install --upgrade pip
318
- pip install "ytp-dl==0.6.4" "yt-dlp[default]" gunicorn
397
+ pip install "ytp-dl==0.6.5" "yt-dlp[default]" gunicorn
319
398
  deactivate
320
399
 
321
400
  echo "==> 3) API environment file (/etc/default/ytp-dl-api)"
@@ -329,8 +408,9 @@ echo "==> 4) Gunicorn systemd service (ytp-dl-api.service on :${PORT})"
329
408
  tee /etc/systemd/system/ytp-dl-api.service >/dev/null <<EOF
330
409
  [Unit]
331
410
  Description=Gunicorn for ytp-dl Mullvad API (minimal)
332
- After=network-online.target
411
+ After=network-online.target ytpdl-policy-routing.service
333
412
  Wants=network-online.target
413
+ Requires=ytpdl-policy-routing.service
334
414
 
335
415
  [Service]
336
416
  User=root
@@ -339,7 +419,9 @@ EnvironmentFile=/etc/default/ytp-dl-api
339
419
  Environment=VIRTUAL_ENV=${VENV_DIR}
340
420
  Environment=PATH=${VENV_DIR}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
341
421
 
342
- ExecStart=${VENV_DIR}/bin/gunicorn -k gthread -w 1 --threads ${GUNICORN_THREADS} --timeout 0 --graceful-timeout 15 --keep-alive 20 --bind 0.0.0.0:${PORT} scripts.api:app
422
+ ExecStart=${VENV_DIR}/bin/gunicorn -k gthread -w 1 --threads ${GUNICORN_THREADS} \
423
+ --timeout 0 --graceful-timeout 15 --keep-alive 20 \
424
+ --bind 0.0.0.0:${PORT} scripts.api:app
343
425
 
344
426
  Restart=always
345
427
  RestartSec=3
@@ -368,5 +450,6 @@ echo "========================================="
368
450
  echo "Installation complete!"
369
451
  echo "API running on port ${PORT}"
370
452
  echo "Test from outside: curl http://YOUR_VPS_IP:${PORT}/healthz"
453
+ echo "If you use UFW: sudo ufw allow ${PORT}/tcp"
371
454
  echo "========================================="
372
455
  ```
@@ -1,38 +1,38 @@
1
1
  # ytp-dl
2
2
 
3
- > A lightweight YouTube downloader with Mullvad VPN integration and HTTP API
3
+ A lightweight YouTube downloader with Mullvad VPN integration and an HTTP API.
4
4
 
5
5
  [![PyPI version](https://img.shields.io/pypi/v/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
6
6
  [![Python Support](https://img.shields.io/pypi/pyversions/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
7
7
  [![License](https://img.shields.io/pypi/l/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
8
8
  [![Downloads](https://img.shields.io/pypi/dm/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
9
9
 
10
- **ytp-dl** is a privacy-focused YouTube downloader that automatically routes downloads through Mullvad VPN via an HTTP API.
10
+ **ytp-dl** is a privacy-focused YouTube downloader that routes downloads through Mullvad VPN via an HTTP API.
11
11
 
12
12
  ---
13
13
 
14
- ## Features
14
+ ## Features
15
15
 
16
- * 🔒 **Privacy First** — Automatically connects/disconnects Mullvad VPN per download
17
- * 🎥 **Smart Quality Selection** Prefers 1080p H.264 + AAC (no transcoding needed)
18
- * 🎵 **Audio Downloads** Extract audio as MP3
19
- * 🚀 **HTTP API** Simple Flask-based API with concurrency controls
20
- * ⚡ **VPS Ready** — Includes automated installer script for Ubuntu
16
+ * Privacy-first: connect/disconnect Mullvad per download
17
+ * Smart quality selection: prefers 1080p H.264 + AAC (no transcoding)
18
+ * Audio downloads: extract audio as MP3
19
+ * HTTP API: simple Flask API with concurrency controls
20
+ * VPS-ready: automated installer script for Ubuntu
21
21
 
22
22
  ---
23
23
 
24
- ## 📦 Installation
24
+ ## Installation
25
25
 
26
26
  ```bash
27
- pip install ytp-dl==0.6.4 yt-dlp[default]
27
+ pip install ytp-dl==0.6.5 yt-dlp[default]
28
28
  ```
29
29
 
30
- **Requirements:**
30
+ Requirements:
31
31
 
32
- * Linux operating system (tested on Ubuntu 24.04/25.04)
33
- * [Mullvad CLI](https://mullvad.net/en/download/vpn/linux) installed and configured
34
- * FFmpeg (for handling audio + video)
35
- * Deno (system-wide, required by yt-dlp for modern YouTube extraction)
32
+ * Linux (tested on Ubuntu 24.04/25.04)
33
+ * Mullvad CLI installed and configured
34
+ * FFmpeg (audio/video handling)
35
+ * Deno (system-wide; required by yt-dlp for modern YouTube extraction)
36
36
  * Python 3.8+
37
37
 
38
38
  Notes:
@@ -41,29 +41,38 @@ Notes:
41
41
 
42
42
  ---
43
43
 
44
- ## 🎯 Using Your VPS
44
+ ## Using your VPS
45
45
 
46
- Once your VPS is running, you can download videos using simple HTTP requests:
46
+ Once your VPS is running, you can download videos using simple HTTP requests.
47
47
 
48
- ### Download a Video (1080p MP4)
48
+ ### Download a video (1080p MP4)
49
49
 
50
50
  ```bash
51
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}' --output video.mp4
51
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
52
+ -H "Content-Type: application/json" \
53
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}' \
54
+ --output video.mp4
52
55
  ```
53
56
 
54
- ### Download Audio Only (MP3)
57
+ ### Download audio only (MP3)
55
58
 
56
59
  ```bash
57
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "extension": "mp3"}' --output audio.mp3
60
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
61
+ -H "Content-Type: application/json" \
62
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "extension": "mp3"}' \
63
+ --output audio.mp3
58
64
  ```
59
65
 
60
- ### Download at Specific Resolution
66
+ ### Download at a specific resolution
61
67
 
62
68
  ```bash
63
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "resolution": 720}' --output video.mp4
69
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
70
+ -H "Content-Type: application/json" \
71
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "resolution": 720}' \
72
+ --output video.mp4
64
73
  ```
65
74
 
66
- ### Check Server Health
75
+ ### Check server health
67
76
 
68
77
  ```bash
69
78
  curl http://YOUR_VPS_IP:5000/healthz
@@ -91,7 +100,7 @@ response = requests.post(
91
100
  "resolution": 1080,
92
101
  "extension": "mp4"
93
102
  },
94
- stream=True
103
+ stream=True,
95
104
  )
96
105
 
97
106
  if response.status_code == 200:
@@ -106,28 +115,29 @@ else:
106
115
 
107
116
  ---
108
117
 
109
- ## ⚙️ Configuration
118
+ ## Configuration
110
119
 
111
- ### Installation Script Variables
120
+ ### Installation script variables
112
121
 
113
- These environment variables configure the VPS installation (they can be overridden when running the script):
122
+ These environment variables configure the VPS installation (you can override them when running the script).
114
123
 
115
124
  | Variable | Description | Default |
116
125
  | ------------------------ | --------------------------------------- | --------------------- |
117
126
  | `PORT` | API server port | `5000` |
118
127
  | `APP_DIR` | Installation directory | `/opt/yt-dlp-mullvad` |
119
- | `MV_ACCOUNT` | Mullvad account number | your mullvad id |
128
+ | `MV_ACCOUNT` | Mullvad account number | (empty) |
120
129
  | `YTPDL_MAX_CONCURRENT` | Max simultaneous downloads (API cap) | `1` |
121
130
  | `YTPDL_MULLVAD_LOCATION` | Mullvad relay location code (e.g. `us`) | `us` |
131
+ | `GUNICORN_THREADS` | Threads per Gunicorn worker | `4` |
122
132
 
123
133
  Notes:
124
134
 
125
135
  * If `MV_ACCOUNT` is set, the installer attempts `mullvad account login <MV_ACCOUNT>` once.
126
136
  * If `MV_ACCOUNT` is left empty, the script skips login and assumes Mullvad is already configured.
127
137
 
128
- ### Runtime Environment Variables
138
+ ### Runtime environment variables
129
139
 
130
- After installation, these variables control the API behavior. They are set in `/etc/default/ytp-dl-api` and can be edited manually:
140
+ After installation, these are set in `/etc/default/ytp-dl-api` and can be edited manually.
131
141
 
132
142
  | Variable | Description | Default |
133
143
  | ------------------------ | ----------------------------- | -------------------------- |
@@ -144,50 +154,33 @@ sudo systemctl restart ytp-dl-api
144
154
 
145
155
  ---
146
156
 
147
- ## 🔧 Managing Your VPS Service
148
-
149
- ### View Service Status
157
+ ## Managing your VPS service
150
158
 
151
159
  ```bash
152
160
  sudo systemctl status ytp-dl-api
153
- ```
154
-
155
- ### View Logs
156
-
157
- ```bash
158
161
  sudo journalctl -u ytp-dl-api -f
159
- ```
160
-
161
- ### Restart Service
162
-
163
- ```bash
164
162
  sudo systemctl restart ytp-dl-api
165
- ```
166
-
167
- ### Stop/Start Service
168
-
169
- ```bash
170
163
  sudo systemctl stop ytp-dl-api
171
164
  sudo systemctl start ytp-dl-api
172
165
  ```
173
166
 
174
167
  ---
175
168
 
176
- ## 📋 API Reference
169
+ ## API reference
177
170
 
178
171
  ### POST `/api/download`
179
172
 
180
- **Request Body:**
173
+ Request body:
181
174
 
182
175
  ```json
183
176
  {
184
177
  "url": "string (required)",
185
178
  "resolution": "integer (optional, default: 1080)",
186
- "extension": "string (optional, 'mp4' or 'mp3')"
179
+ "extension": "string (optional, 'mp4', 'mp3', or 'best')"
187
180
  }
188
181
  ```
189
182
 
190
- **Response:**
183
+ Response:
191
184
 
192
185
  * `200 OK` - File download stream
193
186
  * `400 Bad Request` - Missing or invalid URL
@@ -196,8 +189,6 @@ sudo systemctl start ytp-dl-api
196
189
 
197
190
  ### GET `/healthz`
198
191
 
199
- **Response:**
200
-
201
192
  ```json
202
193
  {
203
194
  "ok": true,
@@ -208,16 +199,22 @@ sudo systemctl start ytp-dl-api
208
199
 
209
200
  ---
210
201
 
211
- ## 🖥️ VPS Deployment
202
+ ## VPS deployment
203
+
204
+ ### Why policy routing is included
205
+
206
+ When Mullvad connects/disconnects, Linux routing can change in a way that breaks inbound TCP handshakes (clients see intermittent connection timeouts). The installer sets up a small policy-routing rule that forces replies sourced from your public VPS IP to use the public interface, keeping the API reachable even while Mullvad is cycling.
212
207
 
213
- **What it does:**
208
+ ### What the installer does
214
209
 
215
- * Installs Python, FFmpeg, and Mullvad CLI
216
- * Installs Deno system-wide (required by yt-dlp for modern YouTube extraction)
217
- * Creates virtualenv at `/opt/yt-dlp-mullvad/venv`
218
- * Installs `ytp-dl==0.6.4` + `yt-dlp[default]` into the virtualenv
219
- * Sets up systemd service on port 5000
220
- * Configures Gunicorn with gthread (threaded) workers
210
+ * Installs Python, FFmpeg, Mullvad CLI, and Deno
211
+ * Creates a virtualenv at `/opt/yt-dlp-mullvad/venv`
212
+ * Installs `ytp-dl==0.6.5` + `yt-dlp[default]` + `gunicorn`
213
+ * Installs a policy-routing oneshot service to keep the public API reachable
214
+ * Sets up a systemd service on port 5000
215
+ * Runs Gunicorn with `gthread` (threaded) workers
216
+
217
+ Note: `gthread` is a built-in Gunicorn worker class (no extra Python dependency).
221
218
 
222
219
  ```bash
223
220
  #!/usr/bin/env bash
@@ -226,9 +223,10 @@ sudo systemctl start ytp-dl-api
226
223
  # What this does:
227
224
  # - Installs Python, ffmpeg, Mullvad CLI
228
225
  # - Installs Deno system-wide (JS runtime required for modern YouTube extraction via yt-dlp)
226
+ # - Configures policy routing so the public API stays reachable while Mullvad toggles
229
227
  # - Creates a virtualenv at /opt/yt-dlp-mullvad/venv
230
- # - Installs ytp-dl==0.6.4 + yt-dlp[default] + gunicorn + gevent in that venv
231
- # - Creates a simple systemd service ytp-dl-api.service on port 5000
228
+ # - Installs ytp-dl==0.6.5 + yt-dlp[default] + gunicorn in that venv
229
+ # - Creates a systemd service ytp-dl-api.service on port 5000
232
230
  #
233
231
  # Mullvad connect/disconnect is handled per-job by downloader.py.
234
232
 
@@ -239,18 +237,33 @@ PORT="${PORT:-5000}" # API listen port
239
237
  APP_DIR="${APP_DIR:-/opt/yt-dlp-mullvad}" # app/venv root
240
238
  VENV_DIR="${VENV_DIR:-${APP_DIR}/venv}" # python venv
241
239
 
242
- MV_ACCOUNT="${MV_ACCOUNT:-}" # Mullvad account (put number after -)
240
+ MV_ACCOUNT="${MV_ACCOUNT:-}" # Mullvad account (optional)
243
241
  YTPDL_MAX_CONCURRENT="${YTPDL_MAX_CONCURRENT:-1}" # API concurrency cap
244
242
  YTPDL_MULLVAD_LOCATION="${YTPDL_MULLVAD_LOCATION:-us}" # default Mullvad relay hint
245
- GUNICORN_THREADS="${GUNICORN_THREADS:-4}" # keep API responsive on tiny VPS
243
+ GUNICORN_THREADS="${GUNICORN_THREADS:-4}" # keep API responsive on small VPS
246
244
  ### -------------------------------------------------------------------------
247
245
 
248
246
  [[ "${EUID}" -eq 0 ]] || { echo "Please run as root"; exit 1; }
249
247
  export DEBIAN_FRONTEND=noninteractive
250
248
 
249
+ echo "==> 0) Capture public routing (pre-VPN)"
250
+ PUB_DEV="$(ip route show default | awk '/default/ {print $5; exit}')"
251
+ PUB_GW="$(ip route show default | awk '/default/ {print $3; exit}')"
252
+ PUB_IP="$(ip -4 addr show dev "${PUB_DEV}" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1)"
253
+
254
+ if [[ -z "${PUB_DEV}" || -z "${PUB_GW}" || -z "${PUB_IP}" ]]; then
255
+ echo "Failed to detect public routing (PUB_DEV/PUB_GW/PUB_IP)."
256
+ echo "PUB_DEV=${PUB_DEV} PUB_GW=${PUB_GW} PUB_IP=${PUB_IP}"
257
+ exit 1
258
+ fi
259
+
260
+ echo "Public dev: ${PUB_DEV} | gw: ${PUB_GW} | ip: ${PUB_IP}"
261
+
251
262
  echo "==> 1) Base packages & Mullvad CLI"
252
263
  apt-get update
253
- apt-get install -yq --no-install-recommends python3-venv python3-pip curl ffmpeg ca-certificates unzip
264
+ apt-get install -yq --no-install-recommends \
265
+ python3-venv python3-pip curl ffmpeg ca-certificates unzip \
266
+ iproute2 iptables
254
267
 
255
268
  if ! command -v mullvad >/dev/null 2>&1; then
256
269
  curl -fsSLo /tmp/mullvad.deb https://mullvad.net/download/app/deb/latest/
@@ -269,6 +282,72 @@ mullvad status || true
269
282
  mullvad lockdown-mode set off || true
270
283
  mullvad lan set allow || true
271
284
 
285
+ echo "==> 1.1) Policy routing: keep replies from ${PUB_IP} on ${PUB_DEV}"
286
+ # Loose reverse-path filtering avoids drops when the default route changes under VPN.
287
+ tee /etc/sysctl.d/99-ytpdl-policy-routing.conf >/dev/null <<EOF
288
+ net.ipv4.conf.all.rp_filter=2
289
+ net.ipv4.conf.default.rp_filter=2
290
+ net.ipv4.conf.${PUB_DEV}.rp_filter=2
291
+ EOF
292
+ sysctl --system >/dev/null
293
+
294
+ # Persist the detected public route info for re-apply at boot.
295
+ tee /etc/default/ytpdl-policy-routing >/dev/null <<EOF
296
+ PUB_DEV=${PUB_DEV}
297
+ PUB_GW=${PUB_GW}
298
+ PUB_IP=${PUB_IP}
299
+ EOF
300
+
301
+ # Add a routing table id if it doesn't already exist.
302
+ grep -qE '^100\s+ytpdl-public$' /etc/iproute2/rt_tables || echo '100 ytpdl-public' >> /etc/iproute2/rt_tables
303
+
304
+ # Idempotent apply script.
305
+ tee /usr/local/sbin/ytpdl-policy-routing.sh >/dev/null <<'EOF'
306
+ #!/usr/bin/env bash
307
+ set -euo pipefail
308
+
309
+ source /etc/default/ytpdl-policy-routing
310
+
311
+ TABLE_ID="100"
312
+ TABLE_NAME="ytpdl-public"
313
+ PRIO="11000"
314
+
315
+ # Ensure table has the public default route.
316
+ ip route replace default via "${PUB_GW}" dev "${PUB_DEV}" table "${TABLE_NAME}"
317
+
318
+ # Ensure rule exists (replace is not supported for rules).
319
+ if ip rule show | grep -qE "^${PRIO}:.*from ${PUB_IP}/32 lookup ${TABLE_NAME}\b"; then
320
+ :
321
+ else
322
+ # remove any stale rule at this priority
323
+ while ip rule show | grep -qE "^${PRIO}:"; do
324
+ ip rule del priority "${PRIO}" || true
325
+ done
326
+ ip rule add priority "${PRIO}" from "${PUB_IP}/32" table "${TABLE_NAME}"
327
+ fi
328
+
329
+ ip route flush cache || true
330
+ EOF
331
+ chmod +x /usr/local/sbin/ytpdl-policy-routing.sh
332
+
333
+ tee /etc/systemd/system/ytpdl-policy-routing.service >/dev/null <<EOF
334
+ [Unit]
335
+ Description=ytp-dl policy routing (keep public API reachable)
336
+ After=network-online.target
337
+ Wants=network-online.target
338
+
339
+ [Service]
340
+ Type=oneshot
341
+ ExecStart=/usr/local/sbin/ytpdl-policy-routing.sh
342
+ RemainAfterExit=yes
343
+
344
+ [Install]
345
+ WantedBy=multi-user.target
346
+ EOF
347
+
348
+ systemctl daemon-reload
349
+ systemctl enable --now ytpdl-policy-routing.service
350
+
272
351
  echo "==> 1.5) Install Deno (system-wide, for yt-dlp YouTube extraction)"
273
352
  # Install into /usr/local/bin/deno so systemd PATH can see it.
274
353
  # Official installer supports system-wide install via DENO_INSTALL=/usr/local.
@@ -282,7 +361,7 @@ mkdir -p "${APP_DIR}"
282
361
  python3 -m venv "${VENV_DIR}"
283
362
  source "${VENV_DIR}/bin/activate"
284
363
  pip install --upgrade pip
285
- pip install "ytp-dl==0.6.4" "yt-dlp[default]" gunicorn
364
+ pip install "ytp-dl==0.6.5" "yt-dlp[default]" gunicorn
286
365
  deactivate
287
366
 
288
367
  echo "==> 3) API environment file (/etc/default/ytp-dl-api)"
@@ -296,8 +375,9 @@ echo "==> 4) Gunicorn systemd service (ytp-dl-api.service on :${PORT})"
296
375
  tee /etc/systemd/system/ytp-dl-api.service >/dev/null <<EOF
297
376
  [Unit]
298
377
  Description=Gunicorn for ytp-dl Mullvad API (minimal)
299
- After=network-online.target
378
+ After=network-online.target ytpdl-policy-routing.service
300
379
  Wants=network-online.target
380
+ Requires=ytpdl-policy-routing.service
301
381
 
302
382
  [Service]
303
383
  User=root
@@ -306,7 +386,9 @@ EnvironmentFile=/etc/default/ytp-dl-api
306
386
  Environment=VIRTUAL_ENV=${VENV_DIR}
307
387
  Environment=PATH=${VENV_DIR}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
308
388
 
309
- ExecStart=${VENV_DIR}/bin/gunicorn -k gthread -w 1 --threads ${GUNICORN_THREADS} --timeout 0 --graceful-timeout 15 --keep-alive 20 --bind 0.0.0.0:${PORT} scripts.api:app
389
+ ExecStart=${VENV_DIR}/bin/gunicorn -k gthread -w 1 --threads ${GUNICORN_THREADS} \
390
+ --timeout 0 --graceful-timeout 15 --keep-alive 20 \
391
+ --bind 0.0.0.0:${PORT} scripts.api:app
310
392
 
311
393
  Restart=always
312
394
  RestartSec=3
@@ -335,5 +417,6 @@ echo "========================================="
335
417
  echo "Installation complete!"
336
418
  echo "API running on port ${PORT}"
337
419
  echo "Test from outside: curl http://YOUR_VPS_IP:${PORT}/healthz"
420
+ echo "If you use UFW: sudo ufw allow ${PORT}/tcp"
338
421
  echo "========================================="
339
- ```
422
+ ```
@@ -8,7 +8,7 @@ with open("requirements.txt", "r", encoding="utf-8") as fh:
8
8
 
9
9
  setup(
10
10
  name="ytp-dl",
11
- version="0.6.4",
11
+ version="0.6.6",
12
12
  author="dumgum82",
13
13
  author_email="dumgum42@gmail.com",
14
14
  description="YouTube video downloader with Mullvad VPN integration and Flask API",
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ytp-dl
3
- Version: 0.6.4
3
+ Version: 0.6.6
4
4
  Summary: YouTube video downloader with Mullvad VPN integration and Flask API
5
5
  Home-page: https://github.com/yourusername/ytp-dl
6
6
  Author: dumgum82
@@ -33,39 +33,39 @@ Dynamic: summary
33
33
 
34
34
  # ytp-dl
35
35
 
36
- > A lightweight YouTube downloader with Mullvad VPN integration and HTTP API
36
+ A lightweight YouTube downloader with Mullvad VPN integration and an HTTP API.
37
37
 
38
38
  [![PyPI version](https://img.shields.io/pypi/v/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
39
39
  [![Python Support](https://img.shields.io/pypi/pyversions/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
40
40
  [![License](https://img.shields.io/pypi/l/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
41
41
  [![Downloads](https://img.shields.io/pypi/dm/ytp-dl.svg)](https://pypi.org/project/ytp-dl/)
42
42
 
43
- **ytp-dl** is a privacy-focused YouTube downloader that automatically routes downloads through Mullvad VPN via an HTTP API.
43
+ **ytp-dl** is a privacy-focused YouTube downloader that routes downloads through Mullvad VPN via an HTTP API.
44
44
 
45
45
  ---
46
46
 
47
- ## Features
47
+ ## Features
48
48
 
49
- * 🔒 **Privacy First** — Automatically connects/disconnects Mullvad VPN per download
50
- * 🎥 **Smart Quality Selection** Prefers 1080p H.264 + AAC (no transcoding needed)
51
- * 🎵 **Audio Downloads** Extract audio as MP3
52
- * 🚀 **HTTP API** Simple Flask-based API with concurrency controls
53
- * ⚡ **VPS Ready** — Includes automated installer script for Ubuntu
49
+ * Privacy-first: connect/disconnect Mullvad per download
50
+ * Smart quality selection: prefers 1080p H.264 + AAC (no transcoding)
51
+ * Audio downloads: extract audio as MP3
52
+ * HTTP API: simple Flask API with concurrency controls
53
+ * VPS-ready: automated installer script for Ubuntu
54
54
 
55
55
  ---
56
56
 
57
- ## 📦 Installation
57
+ ## Installation
58
58
 
59
59
  ```bash
60
- pip install ytp-dl==0.6.4 yt-dlp[default]
60
+ pip install ytp-dl==0.6.5 yt-dlp[default]
61
61
  ```
62
62
 
63
- **Requirements:**
63
+ Requirements:
64
64
 
65
- * Linux operating system (tested on Ubuntu 24.04/25.04)
66
- * [Mullvad CLI](https://mullvad.net/en/download/vpn/linux) installed and configured
67
- * FFmpeg (for handling audio + video)
68
- * Deno (system-wide, required by yt-dlp for modern YouTube extraction)
65
+ * Linux (tested on Ubuntu 24.04/25.04)
66
+ * Mullvad CLI installed and configured
67
+ * FFmpeg (audio/video handling)
68
+ * Deno (system-wide; required by yt-dlp for modern YouTube extraction)
69
69
  * Python 3.8+
70
70
 
71
71
  Notes:
@@ -74,29 +74,38 @@ Notes:
74
74
 
75
75
  ---
76
76
 
77
- ## 🎯 Using Your VPS
77
+ ## Using your VPS
78
78
 
79
- Once your VPS is running, you can download videos using simple HTTP requests:
79
+ Once your VPS is running, you can download videos using simple HTTP requests.
80
80
 
81
- ### Download a Video (1080p MP4)
81
+ ### Download a video (1080p MP4)
82
82
 
83
83
  ```bash
84
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}' --output video.mp4
84
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
85
+ -H "Content-Type: application/json" \
86
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}' \
87
+ --output video.mp4
85
88
  ```
86
89
 
87
- ### Download Audio Only (MP3)
90
+ ### Download audio only (MP3)
88
91
 
89
92
  ```bash
90
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "extension": "mp3"}' --output audio.mp3
93
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
94
+ -H "Content-Type: application/json" \
95
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "extension": "mp3"}' \
96
+ --output audio.mp3
91
97
  ```
92
98
 
93
- ### Download at Specific Resolution
99
+ ### Download at a specific resolution
94
100
 
95
101
  ```bash
96
- curl -X POST http://YOUR_VPS_IP:5000/api/download -H "Content-Type: application/json" -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "resolution": 720}' --output video.mp4
102
+ curl -X POST http://YOUR_VPS_IP:5000/api/download \
103
+ -H "Content-Type: application/json" \
104
+ -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "resolution": 720}' \
105
+ --output video.mp4
97
106
  ```
98
107
 
99
- ### Check Server Health
108
+ ### Check server health
100
109
 
101
110
  ```bash
102
111
  curl http://YOUR_VPS_IP:5000/healthz
@@ -124,7 +133,7 @@ response = requests.post(
124
133
  "resolution": 1080,
125
134
  "extension": "mp4"
126
135
  },
127
- stream=True
136
+ stream=True,
128
137
  )
129
138
 
130
139
  if response.status_code == 200:
@@ -139,28 +148,29 @@ else:
139
148
 
140
149
  ---
141
150
 
142
- ## ⚙️ Configuration
151
+ ## Configuration
143
152
 
144
- ### Installation Script Variables
153
+ ### Installation script variables
145
154
 
146
- These environment variables configure the VPS installation (they can be overridden when running the script):
155
+ These environment variables configure the VPS installation (you can override them when running the script).
147
156
 
148
157
  | Variable | Description | Default |
149
158
  | ------------------------ | --------------------------------------- | --------------------- |
150
159
  | `PORT` | API server port | `5000` |
151
160
  | `APP_DIR` | Installation directory | `/opt/yt-dlp-mullvad` |
152
- | `MV_ACCOUNT` | Mullvad account number | your mullvad id |
161
+ | `MV_ACCOUNT` | Mullvad account number | (empty) |
153
162
  | `YTPDL_MAX_CONCURRENT` | Max simultaneous downloads (API cap) | `1` |
154
163
  | `YTPDL_MULLVAD_LOCATION` | Mullvad relay location code (e.g. `us`) | `us` |
164
+ | `GUNICORN_THREADS` | Threads per Gunicorn worker | `4` |
155
165
 
156
166
  Notes:
157
167
 
158
168
  * If `MV_ACCOUNT` is set, the installer attempts `mullvad account login <MV_ACCOUNT>` once.
159
169
  * If `MV_ACCOUNT` is left empty, the script skips login and assumes Mullvad is already configured.
160
170
 
161
- ### Runtime Environment Variables
171
+ ### Runtime environment variables
162
172
 
163
- After installation, these variables control the API behavior. They are set in `/etc/default/ytp-dl-api` and can be edited manually:
173
+ After installation, these are set in `/etc/default/ytp-dl-api` and can be edited manually.
164
174
 
165
175
  | Variable | Description | Default |
166
176
  | ------------------------ | ----------------------------- | -------------------------- |
@@ -177,50 +187,33 @@ sudo systemctl restart ytp-dl-api
177
187
 
178
188
  ---
179
189
 
180
- ## 🔧 Managing Your VPS Service
181
-
182
- ### View Service Status
190
+ ## Managing your VPS service
183
191
 
184
192
  ```bash
185
193
  sudo systemctl status ytp-dl-api
186
- ```
187
-
188
- ### View Logs
189
-
190
- ```bash
191
194
  sudo journalctl -u ytp-dl-api -f
192
- ```
193
-
194
- ### Restart Service
195
-
196
- ```bash
197
195
  sudo systemctl restart ytp-dl-api
198
- ```
199
-
200
- ### Stop/Start Service
201
-
202
- ```bash
203
196
  sudo systemctl stop ytp-dl-api
204
197
  sudo systemctl start ytp-dl-api
205
198
  ```
206
199
 
207
200
  ---
208
201
 
209
- ## 📋 API Reference
202
+ ## API reference
210
203
 
211
204
  ### POST `/api/download`
212
205
 
213
- **Request Body:**
206
+ Request body:
214
207
 
215
208
  ```json
216
209
  {
217
210
  "url": "string (required)",
218
211
  "resolution": "integer (optional, default: 1080)",
219
- "extension": "string (optional, 'mp4' or 'mp3')"
212
+ "extension": "string (optional, 'mp4', 'mp3', or 'best')"
220
213
  }
221
214
  ```
222
215
 
223
- **Response:**
216
+ Response:
224
217
 
225
218
  * `200 OK` - File download stream
226
219
  * `400 Bad Request` - Missing or invalid URL
@@ -229,8 +222,6 @@ sudo systemctl start ytp-dl-api
229
222
 
230
223
  ### GET `/healthz`
231
224
 
232
- **Response:**
233
-
234
225
  ```json
235
226
  {
236
227
  "ok": true,
@@ -241,16 +232,22 @@ sudo systemctl start ytp-dl-api
241
232
 
242
233
  ---
243
234
 
244
- ## 🖥️ VPS Deployment
235
+ ## VPS deployment
236
+
237
+ ### Why policy routing is included
238
+
239
+ When Mullvad connects/disconnects, Linux routing can change in a way that breaks inbound TCP handshakes (clients see intermittent connection timeouts). The installer sets up a small policy-routing rule that forces replies sourced from your public VPS IP to use the public interface, keeping the API reachable even while Mullvad is cycling.
245
240
 
246
- **What it does:**
241
+ ### What the installer does
247
242
 
248
- * Installs Python, FFmpeg, and Mullvad CLI
249
- * Installs Deno system-wide (required by yt-dlp for modern YouTube extraction)
250
- * Creates virtualenv at `/opt/yt-dlp-mullvad/venv`
251
- * Installs `ytp-dl==0.6.4` + `yt-dlp[default]` into the virtualenv
252
- * Sets up systemd service on port 5000
253
- * Configures Gunicorn with gthread (threaded) workers
243
+ * Installs Python, FFmpeg, Mullvad CLI, and Deno
244
+ * Creates a virtualenv at `/opt/yt-dlp-mullvad/venv`
245
+ * Installs `ytp-dl==0.6.5` + `yt-dlp[default]` + `gunicorn`
246
+ * Installs a policy-routing oneshot service to keep the public API reachable
247
+ * Sets up a systemd service on port 5000
248
+ * Runs Gunicorn with `gthread` (threaded) workers
249
+
250
+ Note: `gthread` is a built-in Gunicorn worker class (no extra Python dependency).
254
251
 
255
252
  ```bash
256
253
  #!/usr/bin/env bash
@@ -259,9 +256,10 @@ sudo systemctl start ytp-dl-api
259
256
  # What this does:
260
257
  # - Installs Python, ffmpeg, Mullvad CLI
261
258
  # - Installs Deno system-wide (JS runtime required for modern YouTube extraction via yt-dlp)
259
+ # - Configures policy routing so the public API stays reachable while Mullvad toggles
262
260
  # - Creates a virtualenv at /opt/yt-dlp-mullvad/venv
263
- # - Installs ytp-dl==0.6.4 + yt-dlp[default] + gunicorn + gevent in that venv
264
- # - Creates a simple systemd service ytp-dl-api.service on port 5000
261
+ # - Installs ytp-dl==0.6.5 + yt-dlp[default] + gunicorn in that venv
262
+ # - Creates a systemd service ytp-dl-api.service on port 5000
265
263
  #
266
264
  # Mullvad connect/disconnect is handled per-job by downloader.py.
267
265
 
@@ -272,18 +270,33 @@ PORT="${PORT:-5000}" # API listen port
272
270
  APP_DIR="${APP_DIR:-/opt/yt-dlp-mullvad}" # app/venv root
273
271
  VENV_DIR="${VENV_DIR:-${APP_DIR}/venv}" # python venv
274
272
 
275
- MV_ACCOUNT="${MV_ACCOUNT:-}" # Mullvad account (put number after -)
273
+ MV_ACCOUNT="${MV_ACCOUNT:-}" # Mullvad account (optional)
276
274
  YTPDL_MAX_CONCURRENT="${YTPDL_MAX_CONCURRENT:-1}" # API concurrency cap
277
275
  YTPDL_MULLVAD_LOCATION="${YTPDL_MULLVAD_LOCATION:-us}" # default Mullvad relay hint
278
- GUNICORN_THREADS="${GUNICORN_THREADS:-4}" # keep API responsive on tiny VPS
276
+ GUNICORN_THREADS="${GUNICORN_THREADS:-4}" # keep API responsive on small VPS
279
277
  ### -------------------------------------------------------------------------
280
278
 
281
279
  [[ "${EUID}" -eq 0 ]] || { echo "Please run as root"; exit 1; }
282
280
  export DEBIAN_FRONTEND=noninteractive
283
281
 
282
+ echo "==> 0) Capture public routing (pre-VPN)"
283
+ PUB_DEV="$(ip route show default | awk '/default/ {print $5; exit}')"
284
+ PUB_GW="$(ip route show default | awk '/default/ {print $3; exit}')"
285
+ PUB_IP="$(ip -4 addr show dev "${PUB_DEV}" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1)"
286
+
287
+ if [[ -z "${PUB_DEV}" || -z "${PUB_GW}" || -z "${PUB_IP}" ]]; then
288
+ echo "Failed to detect public routing (PUB_DEV/PUB_GW/PUB_IP)."
289
+ echo "PUB_DEV=${PUB_DEV} PUB_GW=${PUB_GW} PUB_IP=${PUB_IP}"
290
+ exit 1
291
+ fi
292
+
293
+ echo "Public dev: ${PUB_DEV} | gw: ${PUB_GW} | ip: ${PUB_IP}"
294
+
284
295
  echo "==> 1) Base packages & Mullvad CLI"
285
296
  apt-get update
286
- apt-get install -yq --no-install-recommends python3-venv python3-pip curl ffmpeg ca-certificates unzip
297
+ apt-get install -yq --no-install-recommends \
298
+ python3-venv python3-pip curl ffmpeg ca-certificates unzip \
299
+ iproute2 iptables
287
300
 
288
301
  if ! command -v mullvad >/dev/null 2>&1; then
289
302
  curl -fsSLo /tmp/mullvad.deb https://mullvad.net/download/app/deb/latest/
@@ -302,6 +315,72 @@ mullvad status || true
302
315
  mullvad lockdown-mode set off || true
303
316
  mullvad lan set allow || true
304
317
 
318
+ echo "==> 1.1) Policy routing: keep replies from ${PUB_IP} on ${PUB_DEV}"
319
+ # Loose reverse-path filtering avoids drops when the default route changes under VPN.
320
+ tee /etc/sysctl.d/99-ytpdl-policy-routing.conf >/dev/null <<EOF
321
+ net.ipv4.conf.all.rp_filter=2
322
+ net.ipv4.conf.default.rp_filter=2
323
+ net.ipv4.conf.${PUB_DEV}.rp_filter=2
324
+ EOF
325
+ sysctl --system >/dev/null
326
+
327
+ # Persist the detected public route info for re-apply at boot.
328
+ tee /etc/default/ytpdl-policy-routing >/dev/null <<EOF
329
+ PUB_DEV=${PUB_DEV}
330
+ PUB_GW=${PUB_GW}
331
+ PUB_IP=${PUB_IP}
332
+ EOF
333
+
334
+ # Add a routing table id if it doesn't already exist.
335
+ grep -qE '^100\s+ytpdl-public$' /etc/iproute2/rt_tables || echo '100 ytpdl-public' >> /etc/iproute2/rt_tables
336
+
337
+ # Idempotent apply script.
338
+ tee /usr/local/sbin/ytpdl-policy-routing.sh >/dev/null <<'EOF'
339
+ #!/usr/bin/env bash
340
+ set -euo pipefail
341
+
342
+ source /etc/default/ytpdl-policy-routing
343
+
344
+ TABLE_ID="100"
345
+ TABLE_NAME="ytpdl-public"
346
+ PRIO="11000"
347
+
348
+ # Ensure table has the public default route.
349
+ ip route replace default via "${PUB_GW}" dev "${PUB_DEV}" table "${TABLE_NAME}"
350
+
351
+ # Ensure rule exists (replace is not supported for rules).
352
+ if ip rule show | grep -qE "^${PRIO}:.*from ${PUB_IP}/32 lookup ${TABLE_NAME}\b"; then
353
+ :
354
+ else
355
+ # remove any stale rule at this priority
356
+ while ip rule show | grep -qE "^${PRIO}:"; do
357
+ ip rule del priority "${PRIO}" || true
358
+ done
359
+ ip rule add priority "${PRIO}" from "${PUB_IP}/32" table "${TABLE_NAME}"
360
+ fi
361
+
362
+ ip route flush cache || true
363
+ EOF
364
+ chmod +x /usr/local/sbin/ytpdl-policy-routing.sh
365
+
366
+ tee /etc/systemd/system/ytpdl-policy-routing.service >/dev/null <<EOF
367
+ [Unit]
368
+ Description=ytp-dl policy routing (keep public API reachable)
369
+ After=network-online.target
370
+ Wants=network-online.target
371
+
372
+ [Service]
373
+ Type=oneshot
374
+ ExecStart=/usr/local/sbin/ytpdl-policy-routing.sh
375
+ RemainAfterExit=yes
376
+
377
+ [Install]
378
+ WantedBy=multi-user.target
379
+ EOF
380
+
381
+ systemctl daemon-reload
382
+ systemctl enable --now ytpdl-policy-routing.service
383
+
305
384
  echo "==> 1.5) Install Deno (system-wide, for yt-dlp YouTube extraction)"
306
385
  # Install into /usr/local/bin/deno so systemd PATH can see it.
307
386
  # Official installer supports system-wide install via DENO_INSTALL=/usr/local.
@@ -315,7 +394,7 @@ mkdir -p "${APP_DIR}"
315
394
  python3 -m venv "${VENV_DIR}"
316
395
  source "${VENV_DIR}/bin/activate"
317
396
  pip install --upgrade pip
318
- pip install "ytp-dl==0.6.4" "yt-dlp[default]" gunicorn
397
+ pip install "ytp-dl==0.6.5" "yt-dlp[default]" gunicorn
319
398
  deactivate
320
399
 
321
400
  echo "==> 3) API environment file (/etc/default/ytp-dl-api)"
@@ -329,8 +408,9 @@ echo "==> 4) Gunicorn systemd service (ytp-dl-api.service on :${PORT})"
329
408
  tee /etc/systemd/system/ytp-dl-api.service >/dev/null <<EOF
330
409
  [Unit]
331
410
  Description=Gunicorn for ytp-dl Mullvad API (minimal)
332
- After=network-online.target
411
+ After=network-online.target ytpdl-policy-routing.service
333
412
  Wants=network-online.target
413
+ Requires=ytpdl-policy-routing.service
334
414
 
335
415
  [Service]
336
416
  User=root
@@ -339,7 +419,9 @@ EnvironmentFile=/etc/default/ytp-dl-api
339
419
  Environment=VIRTUAL_ENV=${VENV_DIR}
340
420
  Environment=PATH=${VENV_DIR}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
341
421
 
342
- ExecStart=${VENV_DIR}/bin/gunicorn -k gthread -w 1 --threads ${GUNICORN_THREADS} --timeout 0 --graceful-timeout 15 --keep-alive 20 --bind 0.0.0.0:${PORT} scripts.api:app
422
+ ExecStart=${VENV_DIR}/bin/gunicorn -k gthread -w 1 --threads ${GUNICORN_THREADS} \
423
+ --timeout 0 --graceful-timeout 15 --keep-alive 20 \
424
+ --bind 0.0.0.0:${PORT} scripts.api:app
343
425
 
344
426
  Restart=always
345
427
  RestartSec=3
@@ -368,5 +450,6 @@ echo "========================================="
368
450
  echo "Installation complete!"
369
451
  echo "API running on port ${PORT}"
370
452
  echo "Test from outside: curl http://YOUR_VPS_IP:${PORT}/healthz"
453
+ echo "If you use UFW: sudo ufw allow ${PORT}/tcp"
371
454
  echo "========================================="
372
455
  ```
File without changes
File without changes
File without changes
File without changes