Full Code¶
Transmission app is already included in FreedomBox. Here is the full source for the module for reference.
plinth/modules/transmission/__init__.py¶
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app to configure Transmission server.
"""
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from plinth import app as app_module
from plinth import cfg, frontpage, menu
from plinth.daemon import Daemon
from plinth.modules.apache.components import Webserver
from plinth.modules.backups.components import BackupRestore
from plinth.modules.firewall.components import (Firewall,
FirewallLocalProtection)
from plinth.modules.users import add_user_to_share_group
from plinth.modules.users.components import UsersAndGroups
from plinth.package import Packages
from plinth.utils import format_lazy
from . import manifest, privileged
_description = [
_('Transmission is a BitTorrent client with a web interface.'),
_('BitTorrent is a peer-to-peer file sharing protocol. '
'Note that BitTorrent is not anonymous.'),
_('Please do not change the default port of the Transmission daemon.'),
format_lazy(
_('Compared to <a href="{deluge_url}">'
'Deluge</a>, Transmission is simpler and lightweight but is less '
'customizable.'), deluge_url=reverse_lazy('deluge:index')),
format_lazy(
_('It can be accessed by <a href="{users_url}">any user</a> on '
'{box_name} belonging to the bit-torrent group.'),
box_name=_(cfg.box_name), users_url=reverse_lazy('users:index')),
format_lazy(
_('<a href="{samba_url}">Samba</a> shares can be set as the '
'default download directory from the dropdown menu below.'),
samba_url=reverse_lazy('samba:index')),
format_lazy(
_('After a download has completed, you can also access your files '
'using the <a href="{sharing_url}">Sharing</a> app.'),
sharing_url=reverse_lazy('sharing:index'))
]
SYSTEM_USER = 'debian-transmission'
class TransmissionApp(app_module.App):
"""FreedomBox app for Transmission."""
app_id = 'transmission'
_version = 5
DAEMON = 'transmission-daemon'
def __init__(self):
"""Create components for the app."""
super().__init__()
groups = {
'bit-torrent': _('Download files using BitTorrent applications')
}
info = app_module.Info(
app_id=self.app_id, version=self._version, name=_('Transmission'),
icon_filename='transmission',
short_description=_('BitTorrent Web Client'),
description=_description, manual_page='Transmission',
clients=manifest.clients,
donation_url='https://transmissionbt.com/donate/')
self.add(info)
menu_item = menu.Menu('menu-transmission', info.name,
info.short_description, info.icon_filename,
'transmission:index', parent_url_name='apps')
self.add(menu_item)
shortcut = frontpage.Shortcut(
'shortcut-transmission', info.name,
short_description=info.short_description, icon=info.icon_filename,
url='/transmission', clients=info.clients, login_required=True,
allowed_groups=list(groups))
self.add(shortcut)
packages = Packages('packages-transmission', ['transmission-daemon'])
self.add(packages)
firewall = Firewall('firewall-transmission', info.name,
ports=['http', 'https',
'transmission-client'], is_external=True)
self.add(firewall)
firewall_local_protection = FirewallLocalProtection(
'firewall-local-protection-transmission', ['9091'])
self.add(firewall_local_protection)
webserver = Webserver('webserver-transmission', 'transmission-plinth',
urls=['https://{host}/transmission'])
self.add(webserver)
daemon = Daemon(
'daemon-transmission', self.DAEMON, listen_ports=[
(9091, 'tcp4'),
(51413, 'tcp4'),
(51413, 'tcp6'),
(51413, 'udp4'),
])
self.add(daemon)
users_and_groups = UsersAndGroups('users-and-groups-transmission',
reserved_usernames=[SYSTEM_USER],
groups=groups)
self.add(users_and_groups)
backup_restore = BackupRestore('backup-restore-transmission',
**manifest.backup)
self.add(backup_restore)
def setup(self, old_version):
"""Install and configure the app."""
super().setup(old_version)
if old_version and old_version <= 3 and self.is_enabled():
self.get_component('firewall-transmission').enable()
new_configuration = {
'rpc-whitelist-enabled': False,
'rpc-authentication-required': False
}
privileged.merge_configuration(new_configuration)
add_user_to_share_group(SYSTEM_USER, TransmissionApp.DAEMON)
self.enable()
plinth/modules/transmission/forms.py¶
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for configuring Transmission.
"""
from django.utils.translation import gettext_lazy as _
from plinth.modules.storage.forms import (DirectorySelectForm,
DirectoryValidator)
from . import SYSTEM_USER
class TransmissionForm(DirectorySelectForm):
"""Transmission configuration form"""
def __init__(self, *args, **kw):
validator = DirectoryValidator(username=SYSTEM_USER,
check_creatable=True)
super().__init__(title=_('Download directory'),
default='/var/lib/transmission-daemon/downloads',
validator=validator, *args, **kw)
plinth/modules/transmission/manifest.py¶
# SPDX-License-Identifier: AGPL-3.0-or-later
from django.utils.translation import gettext_lazy as _
clients = [{
'name': _('Transmission'),
'platforms': [{
'type': 'web',
'url': '/transmission'
}]
}]
backup = {
'data': {
'directories': ['/var/lib/transmission-daemon/.config']
},
'secrets': {
'files': ['/etc/transmission-daemon/settings.json']
},
'services': ['transmission-daemon']
}
plinth/modules/transmission/privileged.py¶
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configuration helper for Transmission daemon.
"""
import json
import pathlib
from typing import Union
from plinth import action_utils
from plinth.actions import privileged
_transmission_config = pathlib.Path('/etc/transmission-daemon/settings.json')
@privileged
def get_configuration() -> dict[str, str]:
"""Return the current configuration in JSON format."""
return json.loads(_transmission_config.read_text(encoding='utf-8'))
@privileged
def merge_configuration(configuration: dict[str, Union[str, bool]]):
"""Merge given JSON configuration with existing configuration."""
current_configuration = _transmission_config.read_bytes()
current_configuration = json.loads(current_configuration)
new_configuration = current_configuration
new_configuration.update(configuration)
new_configuration = json.dumps(new_configuration, indent=4, sort_keys=True)
_transmission_config.write_text(new_configuration, encoding='utf-8')
action_utils.service_reload('transmission-daemon')
plinth/modules/transmission/urls.py¶
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
URLs for the Transmission module.
"""
from django.urls import re_path
from .views import TransmissionAppView
urlpatterns = [
re_path(r'^apps/transmission/$', TransmissionAppView.as_view(),
name='index'),
]
plinth/modules/transmission/views.py¶
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for configuring Transmission Server.
"""
import logging
import socket
from django.contrib import messages
from django.utils.translation import gettext as _
from plinth import views
from . import privileged
from .forms import TransmissionForm
logger = logging.getLogger(__name__)
class TransmissionAppView(views.AppView):
"""Serve configuration page."""
form_class = TransmissionForm
app_id = 'transmission'
def get_initial(self):
"""Get the current settings from Transmission server."""
status = super().get_initial()
configuration = privileged.get_configuration()
status['storage_path'] = configuration['download-dir']
status['hostname'] = socket.gethostname()
return status
def form_valid(self, form):
"""Apply the changes submitted in the form."""
old_status = form.initial
new_status = form.cleaned_data
if old_status['storage_path'] != new_status['storage_path']:
new_configuration = {
'download-dir': new_status['storage_path'],
}
privileged.merge_configuration(new_configuration)
messages.success(self.request, _('Configuration updated'))
return super().form_valid(form)
plinth/modules/transmission/data/etc/plinth/modules-enabled/transmission¶
plinth.modules.transmission
plinth/modules/transmission/data/etc/apache2/conf-available/transmission-plinth.conf¶
##
## On all sites, provide Transmission on a default path: /transmission
##
## Requires the following Apache modules to be enabled:
## mod_headers
## mod_proxy
## mod_proxy_http
##
<Location /transmission>
ProxyPass http://localhost:9091/transmission
Include includes/freedombox-single-sign-on.conf
<IfModule mod_auth_pubtkt.c>
TKTAuthToken "admin" "bit-torrent"
</IfModule>
## Send the scheme from user's request to enable Transmission to
## redirect URLs, set cookies, set absolute URLs (if any)
## properly.
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
# Make redirects to avoid 409 Conflict errors. See: #2219. Upstream issue:
# https://github.com/transmission/transmission/pull/857 . Drop this
# workaround with Transmission >= 4.0.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/transmission/$
RewriteRule .* /transmission/web/ [R=302,L]
RewriteCond %{REQUEST_URI} ^/transmission/web$
RewriteRule .* /transmission/web/ [R=302,L]
</IfModule>
</Location>
plinth/modules/transmission/tests/__init__.py¶