Compare commits

...

16 Commits

Author SHA1 Message Date
800a8dca90 fix delete 2025-09-06 12:01:56 +02:00
c7b09b30bd add delete + fix run_dev 2025-09-06 09:31:20 +02:00
5c3c864a9e some improvement 2025-05-26 22:57:30 +02:00
3aac0116e6 some improvement 2025-05-08 03:37:08 +02:00
b4330c0362 some improvement 2025-05-08 03:23:45 +02:00
25fd30a0c3 some improvement 2025-05-03 10:40:09 +02:00
26d4613dd1 some improvement 2025-05-03 01:22:03 +02:00
42332ac329 largest file size body 2025-04-26 13:00:06 +02:00
c7e52a2382 add multi domain support 2025-04-22 21:45:12 +02:00
cf702fa316 add multi domain support 2025-04-22 20:21:47 +02:00
955ea36433 Fix null to 0 in aggregate 2025-04-20 03:35:04 +02:00
9b9c25806b Fix null to 0 in aggregate 2025-04-20 03:20:34 +02:00
065ac7f250 update debug mode 2025-04-20 03:13:04 +02:00
0a5251ec10 some fix 2025-04-20 03:08:12 +02:00
eb60fc4dd3 fix url to avoid 301 2025-04-20 00:41:21 +02:00
888ed093a1 add title 2025-04-19 23:15:17 +02:00
34 changed files with 174 additions and 55 deletions

View File

@@ -1,7 +1,7 @@
USER_ID=1000 USER_ID=1000
GROUP_ID=1000 GROUP_ID=1000
DOMAIN=127.0.0.1 TRAEFIK_HOST_RULE=Host(`example.com`) || Host(`www.example.com`)
LISTEN_PORT=8000 LISTEN_PORT=8000

View File

@@ -2,6 +2,7 @@ FROM python:3.13-slim
ARG puid=1000 ARG puid=1000
ARG pgid=1000 ARG pgid=1000
ARG debug=false
ENV PYTHONUNBUFFERED=1 \ ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \ PYTHONDONTWRITEBYTECODE=1 \
LANG=C.UTF-8 \ LANG=C.UTF-8 \
@@ -40,8 +41,13 @@ RUN yarn install
WORKDIR /app WORKDIR /app
RUN pip install --upgrade pip RUN pip install --upgrade pip
COPY ./requirements.txt ./requirements.txt COPY requirements*.txt ./
RUN pip install --no-cache-dir -r requirements.txt RUN if [ "$debug" = "true" ] ; then \
pip install --no-cache-dir -r requirements-dev.txt ; \
else \
pip install --no-cache-dir -r requirements-prod.txt ; \
fi
#COPY . . #COPY . .

View File

@@ -58,7 +58,10 @@ INSTALLED_APPS = [
'user', 'user',
'api', 'api',
'torrent', 'torrent',
'watch_party'
] ]
if DEBUG:
INSTALLED_APPS = ["daphne"] + INSTALLED_APPS
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
@@ -139,6 +142,7 @@ USE_TZ = True
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATICFILES_DIRS = [ STATICFILES_DIRS = [
BASE_DIR / "static",
BASE_DIR / "frontend/dist", BASE_DIR / "frontend/dist",
] ]
STATIC_ROOT = BASE_DIR / "static_collected" STATIC_ROOT = BASE_DIR / "static_collected"
@@ -249,4 +253,5 @@ TRANSMISSION = {
"port": getenv("TRANSMISSION_PORT", 9091), "port": getenv("TRANSMISSION_PORT", 9091),
"username": getenv("TRANSMISSION_USERNAME"), "username": getenv("TRANSMISSION_USERNAME"),
"password": getenv("TRANSMISSION_PASSWORD") "password": getenv("TRANSMISSION_PASSWORD")
} }
TORRENT_TTL = int(getenv("TORRENT_TTL", 90*24*60*60)) # 90 jours

View File

@@ -17,6 +17,7 @@ Including another URLconf
from django.contrib import admin from django.contrib import admin
from django.urls import path, include # Added include for including app URLs from django.urls import path, include # Added include for including app URLs
from django.http import HttpResponse from django.http import HttpResponse
from django.views.generic import RedirectView
from django.contrib.auth.views import ( from django.contrib.auth.views import (
PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteView, PasswordChangeView, PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteView, PasswordChangeView,
PasswordChangeDoneView, LogoutView PasswordChangeDoneView, LogoutView
@@ -28,6 +29,7 @@ urlpatterns = [
path("", include("torrent.urls", "torrent")), path("", include("torrent.urls", "torrent")),
path("user/", include("user.urls", "user")), path("user/", include("user.urls", "user")),
path("api/", include("api.urls", "api")), path("api/", include("api.urls", "api")),
path("home", RedirectView.as_view(url="/", permanent=False), name="home"),
# reset password related # reset password related
path("password_reset/", PasswordResetView.as_view(), name="password_reset"), path("password_reset/", PasswordResetView.as_view(), name="password_reset"),

View File

@@ -1,10 +1,10 @@
#!/bin/bash #!/usr/bin/env bash
CORES=$(nproc) CORES=$(nproc)
WORKERS=$((2 * CORES + 1)) WORKERS=$((2 * CORES + 1))
cd /app/frontend && yarn dev & cd /app/frontend && yarn dev &
cd /app && uvicorn app.asgi:application --workers $WORKERS --host 0.0.0.0 --port 8000 --lifespan off --loop uvloop --ws websockets --reload & cd /app && python manage.py runserver 0.0.0.0:8000 &
wait -n wait -n

View File

@@ -37,6 +37,11 @@
<v-btn v-bind="props">Manage</v-btn> <v-btn v-bind="props">Manage</v-btn>
</template> </template>
<v-list> <v-list>
<v-list-item
v-if="!$qt.is_active"
href="https://gitea.devpanel.fr/oxpanel/oxapp25/releases/download/1.0/OxApp_Setup.exe"
title="Download app"
/>
<v-list-item href="/password_change/" title="Change password"/> <v-list-item href="/password_change/" title="Change password"/>
<v-list-item> <v-list-item>
<v-list-item-action> <v-list-item-action>

View File

@@ -7,6 +7,7 @@
> >
<template v-slot:prepend> <template v-slot:prepend>
<v-btn @click.stop="downloadClicked" :disabled="!is_download_finished" icon="mdi-download" color="green" variant="text"/> <v-btn @click.stop="downloadClicked" :disabled="!is_download_finished" icon="mdi-download" color="green" variant="text"/>
<v-btn v-if="file.is_video" @click.stop="fluxClicked" :disabled="!is_download_finished" icon="mdi-vlc" color="orange" variant="text"></v-btn>
<v-dialog v-if="file.is_stream_video" v-model="video_modal" width="75%" height="100%"> <v-dialog v-if="file.is_stream_video" v-model="video_modal" width="75%" height="100%">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="mdi-play" variant="text"/> <v-btn v-bind="props" icon="mdi-play" variant="text"/>
@@ -45,14 +46,24 @@ export default {
downloadClicked(){ downloadClicked(){
if(!this.is_download_finished) return; if(!this.is_download_finished) return;
if(this.$qt.is_active){ if(this.$qt.is_active){
this.$qt.callMethod("add_files", this.file); this.$qt.callMethod("add_files", [this.file]);
}else{ }else{
let a = document.createElement("a"); let a = document.createElement("a");
a.href = this.file.download_url; a.href = this.file.download_url;
a.setAttribute("download", "download"); a.setAttribute("download", "download");
a.click(); a.click();
} }
},
fluxClicked(){
const full_url = new URL(this.file.flux_url, window.location.href).href
navigator.clipboard.writeText(full_url)
.then(() => {
// Optionnel: Vous pouvez ajouter une notification pour indiquer à l'utilisateur que l'URL a été copiée
console.log('URL copiée dans le presse-papier');
})
.catch(err => {
console.error('Erreur lors de la copie dans le presse-papier:', err);
});
} }
} }
} }

View File

@@ -39,7 +39,7 @@ export default {
methods: { methods: {
async fetchFiles(){ async fetchFiles(){
this.loading = true; this.loading = true;
let response = await fetch(`/api/torrent/files?${new URLSearchParams(this.filters)}`); let response = await fetch(`/api/torrent/files/?${new URLSearchParams(this.filters)}`);
this.files = await response.json(); this.files = await response.json();
this.loading = false; this.loading = false;
return this.files; return this.files;

View File

@@ -8,19 +8,32 @@
<!-- indeterminate--> <!-- indeterminate-->
<!-- :color="torrent.transmission_data.progress < 100 ? 'green':'red'"--> <!-- :color="torrent.transmission_data.progress < 100 ? 'green':'red'"-->
<!-- />--> <!-- />-->
<v-icon
v-if="torrent.transmission_data.rateDownload && torrent.transmission_data.rateUpload" <!-- Si téléchargement en cours-->
:color="torrent.transmission_data.rateDownload ? 'green':'red'" <v-chip variant="text" color="white" v-if="progressData.mode === 'download'">
:icon="torrent.transmission_data.rateDownload ? 'mdi-arrow-down-bold':'mdi-arrow-up-bold'" <v-icon
/> v-if="torrent.transmission_data.rateDownload"
<strong style="padding-left: 5px"> color="green"
icon="mdi-arrow-down-bold"
/>
{{progressData.value}}% {{progressData.value}}%
<strong <strong style="padding-left: 5px" v-if="torrent.transmission_data.rateDownload">
v-if="torrent.transmission_data.rateDownload || torrent.transmission_data.rateUpload" ({{fs_speed_format(torrent.transmission_data.rateDownload)}}/s)
>
({{torrent.transmission_data.rateDownload ? fs_speed_format(torrent.transmission_data.rateDownload):fs_speed_format(torrent.transmission_data.rateUpload)}}/s)
</strong> </strong>
</strong> </v-chip>
<!-- Si téléchargement terminé-->
<v-chip variant="text" color="red" v-else>
<v-icon
v-if="torrent.transmission_data.rateUpload"
color="red"
icon="mdi-arrow-up-bold"
/>
{{fs_format(torrent.transmission_data.uploadedEver)}}
<strong style="padding-left: 5px" v-if="torrent.transmission_data.rateUpload">
({{fs_speed_format(torrent.transmission_data.rateUpload)}}/s)
</strong>
</v-chip>
</v-progress-linear> </v-progress-linear>
<v-row no-gutters> <v-row no-gutters>
<!-- ligne du haut --> <!-- ligne du haut -->
@@ -115,9 +128,10 @@ export default {
}, },
computed: { computed: {
progressData(){ progressData(){
let color = "red", value = 0, eta; let color = "red", value = 0, mode, eta;
if(this.torrent.transmission_data.progress < 100){ if(this.torrent.transmission_data.progress < 100){
color = "blue"; color = "blue";
mode = "download";
value = this.torrent.transmission_data.progress; value = this.torrent.transmission_data.progress;
if(this.torrent.transmission_data.eta !== -1){ if(this.torrent.transmission_data.eta !== -1){
eta = toHHMMSS(this.torrent.transmission_data.eta); eta = toHHMMSS(this.torrent.transmission_data.eta);
@@ -126,10 +140,11 @@ export default {
} }
}else{ }else{
color = "green"; color = "green";
mode = "upload";
value = Number((this.torrent.transmission_data.uploadRatio / 5) * 100).toFixed(2) value = Number((this.torrent.transmission_data.uploadRatio / 5) * 100).toFixed(2)
if(value > 100) value = 100; if(value > 100) value = 100;
} }
return {color, value, eta}; return {color, value, mode, eta};
} }
} }
} }

View File

@@ -6,15 +6,9 @@ django-filter
djangorestframework-simplejwt djangorestframework-simplejwt
channels channels
channels_redis channels_redis
celery
pytz pytz
psycopg[binary]
uvicorn
transmission-rpc transmission-rpc
stream-zip stream-zip
anyio anyio
websockets websockets
uvloop uvloop
watchfiles

2
app/requirements-dev.txt Normal file
View File

@@ -0,0 +1,2 @@
-r requirements-common.txt
daphne

View File

@@ -0,0 +1,2 @@
-r requirements-common.txt
uvicorn

BIN
app/static/oxpanel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -1,9 +1,11 @@
{% load django_vite %}<!DOCTYPE html> {% load django_vite %}{% load static %}<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>{% block base_title %}{% block title %}{% endblock %}{% endblock %}</title> <title>{% block base_title %}Oxpanel{% block title %}{% endblock %}{% endblock %}</title>
{% block base_css %} <link rel="icon" type="image/png" href="{% static 'oxpanel.png' %}"/>
{% block base_css %}
{# {% stylesheet_pack 'app' %}#} {# {% stylesheet_pack 'app' %}#}
<link data-n-head="ssr" rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> <link data-n-head="ssr" rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
{% block css %}{% endblock %} {% block css %}{% endblock %}

View File

@@ -41,7 +41,6 @@ class TorrentEventConsumer(AsyncJsonWebsocketConsumer):
await self.channel_layer.group_discard("torrent", self.channel_name) await self.channel_layer.group_discard("torrent", self.channel_name)
async def dispatch(self, message): async def dispatch(self, message):
print("dispatch ws :", message)
return await super().dispatch(message) return await super().dispatch(message)
async def receive_json(self, content, **kwargs): async def receive_json(self, content, **kwargs):

View File

@@ -1,3 +1,5 @@
from django.conf import settings
from django.utils import timezone
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import close_old_connections from django.db import close_old_connections
@@ -5,6 +7,7 @@ import time
import signal import signal
import sys import sys
import traceback import traceback
from datetime import timedelta
from torrent.models import Torrent from torrent.models import Torrent
from torrent.utils import transmission_handler from torrent.utils import transmission_handler
@@ -27,11 +30,22 @@ def update_transmission_data():
}) })
def clean_old_torrents():
expired_date = timezone.now() - timedelta(days=settings.TORRENT_TTL)
torrent = Torrent.objects.filter(date_created__lt=expired_date).first()
if torrent:
torrent.delete()
class Command(BaseCommand): class Command(BaseCommand):
task_schedule = { task_schedule = {
"update_transmission_data": { "update_transmission_data": {
"func": update_transmission_data, "func": update_transmission_data,
"schedule": 5.0 "schedule": 5.0
},
"clean_old_torrents": {
"func": clean_old_torrents,
"schedule": 60.0
} }
} }
histories = {} histories = {}

View File

@@ -69,7 +69,7 @@ class File(models.Model):
def abs_pathname(self): def abs_pathname(self):
return settings.DOWNLOAD_BASE_DIR / self.pathname return settings.DOWNLOAD_BASE_DIR / self.pathname
@property @cached_property
def mime_types(self): def mime_types(self):
mime = mimetypes.guess_type(self.pathname) mime = mimetypes.guess_type(self.pathname)
if mime: if mime:
@@ -79,11 +79,15 @@ class File(models.Model):
@property @property
def is_stream_video(self): def is_stream_video(self):
return self.pathname.stem in ["mp4", "flv", "webm"] video_extensions = ["mp4", "flv", "webm"]
return self.pathname.suffix.lower() in video_extensions
@property @property
def is_video(self): def is_video(self):
return self.pathname.stem in ["mp4", "flv", "webm", "avi", "mkv"] if self.mime_types.startswith("video/"):
return True
video_extensions = ['.mp4', '.flv', '.webm', '.avi', '.mkv', '.mov', '.wmv']
return self.pathname.suffix.lower() in video_extensions
@property @property
def accel_redirect(self): def accel_redirect(self):

View File

@@ -1,4 +1,5 @@
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify
from rest_framework import serializers from rest_framework import serializers
@@ -21,6 +22,7 @@ class FileSerializer(serializers.ModelSerializer):
is_stream_video = serializers.BooleanField(read_only=True) is_stream_video = serializers.BooleanField(read_only=True)
is_video = serializers.BooleanField(read_only=True) is_video = serializers.BooleanField(read_only=True)
download_url = serializers.SerializerMethodField(read_only=True) download_url = serializers.SerializerMethodField(read_only=True)
flux_url = serializers.SerializerMethodField(read_only=True)
class Meta: class Meta:
model = File model = File
@@ -28,3 +30,6 @@ class FileSerializer(serializers.ModelSerializer):
def get_download_url(self, obj): def get_download_url(self, obj):
return reverse("torrent:download_file", kwargs={"file_id": obj.id}) return reverse("torrent:download_file", kwargs={"file_id": obj.id})
def get_flux_url(self, obj: File):
return f'{reverse("torrent:flux_file", kwargs={"file_id": obj.id})}#{slugify(obj.filename)}'

View File

@@ -1,6 +1,6 @@
from django.urls import path from django.urls import path
from .views import HomeView, download_file, download_torrent, pping from .views import HomeView, download_file, download_torrent, pping, flux_file
app_name = "torrent" app_name = "torrent"
urlpatterns = [ urlpatterns = [
@@ -8,4 +8,5 @@ urlpatterns = [
path("pping/", pping, name="pping"), path("pping/", pping, name="pping"),
path("download_file/<uuid:file_id>", download_file, name="download_file"), path("download_file/<uuid:file_id>", download_file, name="download_file"),
path("download_torrent/<str:torrent_id>", download_torrent, name="download_torrent"), path("download_torrent/<str:torrent_id>", download_torrent, name="download_torrent"),
path("flux_file/<uuid:file_id>", flux_file, name="flux_file"),
] ]

View File

@@ -15,7 +15,8 @@ from user.models import User
class Transmission: class Transmission:
trpc_args = [ trpc_args = [
"id", "percentDone", "uploadRatio", "rateUpload", "rateDownload", "hashString", "status", "sizeWhenDone", "id", "percentDone", "uploadRatio", "rateUpload", "rateDownload", "hashString", "status", "sizeWhenDone",
"leftUntilDone", "name", "eta", "totalSize" "leftUntilDone", "name", "eta", "totalSize", "uploadedEver", "peersGettingFromUs", "peersSendingToUs",
"tracker", "trackerStats", "activityDate"
] ]
def __init__(self): def __init__(self):
@@ -36,12 +37,13 @@ class Transmission:
return { return {
"progress": data.progress, "progress": data.progress,
"status_str": data.status,
**data.fields **data.fields
} }
def get_all_data(self, hash_strings=None): def get_all_data(self, hash_strings=None):
return { return {
data.hashString: {"progress": data.progress, **data.fields} data.hashString: {"progress": data.progress, "status_str": data.status, **data.fields}
for data in self.client.get_torrents(hash_strings, self.trpc_args) for data in self.client.get_torrents(hash_strings, self.trpc_args)
} }

View File

@@ -62,6 +62,23 @@ async def download_file(request, file_id):
async def flux_file(request, file_id): async def flux_file(request, file_id):
# todo : version non sécurisé, voir pour ajouter un contrôle IP (par ex)
qs = File.objects.filter(pk=file_id)
try:
file = await qs.aget()
except File.DoesNotExist:
raise Http404()
else:
response = HttpResponse()
response["X-Accel-Redirect"] = file.accel_redirect
response["X-Accel-Buffering"] = "no"
response["Content-Type"] = file.mime_types
response["Content-Disposition"] = file.disposition
return response
async def secured_flux_file(request, file_id):
user = await request.auser() user = await request.auser()
qs = File.objects.filter( qs = File.objects.filter(
Q(torrent__user=user) Q(torrent__user=user)
@@ -106,7 +123,7 @@ async def download_torrent(request, torrent_id):
})) }))
response = StreamingZipFileResponse( response = StreamingZipFileResponse(
filename="test.zip", filename=f"{torrent.name}.zip",
file_list=[ file_list=[
(file.abs_pathname, file.rel_name) (file.abs_pathname, file.rel_name)
async for file in torrent.files.all() async for file in torrent.files.all()

View File

@@ -3,6 +3,7 @@ from django.views.generic import CreateView
from django.contrib.auth import login from django.contrib.auth import login
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.db.models import Count, Sum, F, IntegerField from django.db.models import Count, Sum, F, IntegerField
from django.db.models.functions import Coalesce
from rest_framework.viewsets import ModelViewSet, GenericViewSet from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework import mixins from rest_framework import mixins
@@ -96,9 +97,9 @@ class UserViewSet(mixins.RetrieveModelMixin,
@action(methods=["get"], detail=False) @action(methods=["get"], detail=False)
def user_stats(self, request): def user_stats(self, request):
stats = User.objects.filter(id=request.user.id).aggregate( stats = User.objects.filter(id=request.user.id).aggregate(
total_size=Sum("torrents__size"), total_size=Coalesce(Sum("torrents__size"), 0),
total_torrent=Count("torrents"), total_torrent=Coalesce(Count("torrents"), 0),
total_shared_torrent=Count("torrents_shares", distinct=True), total_shared_torrent=Coalesce(Count("torrents_shares", distinct=True), 0),
) )
disk_usage = shutil.disk_usage("/") disk_usage = shutil.disk_usage("/")

View File

3
app/watch_party/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
app/watch_party/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class WatchPartyConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'watch_party'

View File

11
app/watch_party/models.py Normal file
View File

@@ -0,0 +1,11 @@
import uuid
from django.db import models
class Room(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
date_created = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey("user.User", on_delete=models.CASCADE, related_name="rooms_created")
users = models.ManyToManyField("user.User", related_name="rooms")
all_admin = models.BooleanField(default=False)

3
app/watch_party/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
app/watch_party/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -12,6 +12,9 @@ services:
ports: !reset [] ports: !reset []
app: app:
build:
args:
debug: true
restart: "no" restart: "no"
ports: ports:
- "${DEV_SERVER_PORT:-8080}:${DEV_SERVER_PORT:-8080}" - "${DEV_SERVER_PORT:-8080}:${DEV_SERVER_PORT:-8080}"

View File

@@ -14,7 +14,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=web" - "traefik.docker.network=web"
# Routeur HTTP (port 80) pour la redirection vers HTTPS # Routeur HTTP (port 80) pour la redirection vers HTTPS
- "traefik.http.routers.web-http.rule=Host(`${DOMAIN}`)" - "traefik.http.routers.web-http.rule=${TRAEFIK_HOST_RULE}"
- "traefik.http.routers.web-http.entrypoints=web" - "traefik.http.routers.web-http.entrypoints=web"
- "traefik.http.routers.web-http.middlewares=redirect-to-https" - "traefik.http.routers.web-http.middlewares=redirect-to-https"
@@ -22,7 +22,7 @@ services:
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
# Routeur HTTPS # Routeur HTTPS
- "traefik.http.routers.web.rule=Host(`${DOMAIN}`)" - "traefik.http.routers.web.rule=${TRAEFIK_HOST_RULE}"
- "traefik.http.routers.web.entrypoints=websecure" - "traefik.http.routers.web.entrypoints=websecure"
- "traefik.http.routers.web.tls.certresolver=le" - "traefik.http.routers.web.tls.certresolver=le"
@@ -44,8 +44,8 @@ services:
- PUID=${USER_ID} - PUID=${USER_ID}
- PGID=${GROUP_ID} - PGID=${GROUP_ID}
ports: ports:
- "51414:51414" - "51413:51413"
- "51414:51414/udp" - "51413:51413/udp"
volumes: volumes:
- ./transmission/config:/config - ./transmission/config:/config
- ./transmission/downloads:/downloads - ./transmission/downloads:/downloads
@@ -58,6 +58,7 @@ services:
args: args:
puid: ${USER_ID} puid: ${USER_ID}
pgid: ${GROUP_ID} pgid: ${GROUP_ID}
debug: ${DEBUG:-false}
env_file: env_file:
- .env - .env
volumes: volumes:
@@ -73,7 +74,7 @@ services:
&& cd frontend && yarn build && cd .. && cd frontend && yarn build && cd ..
&& python manage.py collectstatic --noinput && python manage.py collectstatic --noinput
&& python manage.py migrate && python manage.py migrate
&& uvicorn app.asgi:application --workers 3 --host 0.0.0.0 --port 8000 --lifespan off --loop uvloop --ws websockets" && uvicorn app.asgi:application --workers 3 --host 0.0.0.0 --port 8000 --lifespan off --loop uvloop --ws websockets --log-level info"
# celery: # celery:
# extends: # extends:

View File

@@ -1,5 +1,6 @@
server { server {
gzip off; gzip off;
client_max_body_size 100M;
location /dl/ { location /dl/ {
internal; internal;

View File

@@ -1,5 +1,6 @@
server { server {
gzip off; gzip off;
client_max_body_size 100M;
location /dl/ { location /dl/ {
internal; internal;

View File

@@ -14,7 +14,7 @@
"bind-address-ipv6": "::", "bind-address-ipv6": "::",
"blocklist-enabled": false, "blocklist-enabled": false,
"blocklist-url": "http://www.example.com/blocklist", "blocklist-url": "http://www.example.com/blocklist",
"cache-size-mb": 4, "cache-size-mb": 256,
"default-trackers": "", "default-trackers": "",
"dht-enabled": true, "dht-enabled": true,
"download-dir": "/downloads/complete", "download-dir": "/downloads/complete",
@@ -29,8 +29,8 @@
"message-level": 2, "message-level": 2,
"peer-congestion-algorithm": "", "peer-congestion-algorithm": "",
"peer-id-ttl-hours": 6, "peer-id-ttl-hours": 6,
"peer-limit-global": 200, "peer-limit-global": 400,
"peer-limit-per-torrent": 50, "peer-limit-per-torrent": 100,
"peer-port": 51413, "peer-port": 51413,
"peer-port-random-high": 65535, "peer-port-random-high": 65535,
"peer-port-random-low": 49152, "peer-port-random-low": 49152,
@@ -42,8 +42,8 @@
"prefetch-enabled": true, "prefetch-enabled": true,
"queue-stalled-enabled": true, "queue-stalled-enabled": true,
"queue-stalled-minutes": 30, "queue-stalled-minutes": 30,
"ratio-limit": 2, "ratio-limit": 20,
"ratio-limit-enabled": false, "ratio-limit-enabled": true,
"rename-partial-files": true, "rename-partial-files": true,
"rpc-authentication-required": false, "rpc-authentication-required": false,
"rpc-bind-address": "0.0.0.0", "rpc-bind-address": "0.0.0.0",
@@ -73,10 +73,10 @@
"start-added-torrents": true, "start-added-torrents": true,
"tcp-enabled": true, "tcp-enabled": true,
"torrent-added-verify-mode": "fast", "torrent-added-verify-mode": "fast",
"trash-original-torrent-files": false, "trash-original-torrent-files": true,
"umask": "002", "umask": "002",
"upload-slots-per-torrent": 14, "upload-slots-per-torrent": 50,
"utp-enabled": false, "utp-enabled": true,
"watch-dir": "/watch", "watch-dir": "/watch",
"watch-dir-enabled": true "watch-dir-enabled": true
} }