#!/usr/bin/python3 # # This file is part of FreedomBox. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # """ Configuration helper for BitTorrent web client. """ import argparse import json import os import shutil import subprocess import time import augeas from plinth import action_utils try: from deluge import config except ImportError: # deluge is not installed or is python2 version config = None DELUGED_DEFAULT_FILE = '/etc/default/deluged' DELUGE_CONF_DIR = '/var/lib/deluged/.config/deluge/' DELUGE_WEB_SYSTEMD_SERVICE_PATH = '/etc/systemd/system/deluge-web.service' DELUGE_WEB_SYSTEMD_SERVICE = ''' # # This file is managed and overwritten by Plinth. If you wish to edit # it, disable Deluge in Plinth, remove this file and manage it manually. # [Unit] Description=Deluge Web Interface Documentation=man:deluge-web(1) After=network.target [Service] ExecStart=bash -c "/usr/bin/deluge-web --base=deluge $(/usr/bin/deluge-web --version | grep deluge-web | cut -f2 -d' ' | grep -q '^1.' && echo '' || echo '--do-not-daemonize')" Restart=on-failure User=debian-deluged Group=debian-deluged LockPersonality=yes NoNewPrivileges=yes PrivateDevices=yes PrivateTmp=yes PrivateUsers=yes ProtectControlGroups=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectSystem=yes RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 RestrictRealtime=yes StateDirectory=deluged SystemCallArchitectures=native [Install] WantedBy=multi-user.target ''' # noqa: E501 def parse_arguments(): """Return parsed command line arguments as dictionary.""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') subparsers.add_parser('setup', help='Setup deluge') subparsers.add_parser('get-configuration', help='Return the current configuration') subparser = subparsers.add_parser('set-configuration', help='Set the configuration parameter') subparser.add_argument('parameter', help='Name of the configuration parameter') subparser.add_argument('value', help='Value of the configuration parameter') subparsers.required = True return parser.parse_args() def _set_configuration(filename, parameter, value): """Set the configuration parameter.""" deluged_is_running = action_utils.service_is_running('deluged') if deluged_is_running: action_utils.service_stop('deluged') deluge_web_is_running = action_utils.service_is_running('deluge-web') if deluge_web_is_running: action_utils.service_stop('deluge-web') filepath = os.path.join(DELUGE_CONF_DIR, filename) if config is None: script = 'from deluge import config;\ conf = config.Config(filename="{0}");\ conf["{1}"] = "{2}";\ conf.save()'.format(filepath, parameter, value) subprocess.check_call(['python2', '-c', script]) else: conf = config.Config(filename=filepath) conf[parameter] = value conf.save() shutil.chown(filepath, 'debian-deluged', 'debian-deluged') if deluged_is_running: action_utils.service_start('deluged') if deluge_web_is_running: action_utils.service_start('deluge-web') def _get_host_id(): """Get default host id.""" if config is None: hosts_conf_file = os.path.join(DELUGE_CONF_DIR, 'hostlist.conf.1.2') script = 'from deluge import config;\ conf = config.Config(filename="{0}");\ print(conf["hosts"][0][0])'.format(hosts_conf_file) output = subprocess.check_output(['python2', '-c', script]).decode() return output.strip() else: hosts_conf_file = os.path.join(DELUGE_CONF_DIR, 'hostlist.conf') conf = config.Config(filename=hosts_conf_file) return conf["hosts"][0][0] def _set_deluged_daemon_options(): """Set deluged daemon options.""" aug = augeas.Augeas( flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD) aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns') aug.set('/augeas/load/Shellvars/incl[last() + 1]', DELUGED_DEFAULT_FILE) aug.load() aug.set('/files' + DELUGED_DEFAULT_FILE + '/ENABLE_DELUGED', '1') # overwrite daemon args, use default config directory (same as deluge-web) aug.set('/files' + DELUGED_DEFAULT_FILE + '/DAEMON_ARGS', '"-d -l /var/log/deluged/daemon.log -L info"') aug.save() def subcommand_get_configuration(_): """Return the current deluged configuration in JSON format.""" deluged_conf_file = os.path.join(DELUGE_CONF_DIR, 'core.conf') if config is None: script = 'from deluge import config;\ conf = config.Config(filename="{0}");\ print(conf["download_location"])'.format(deluged_conf_file) output = subprocess.check_output(['python2', '-c', script]).decode() download_location = output.strip() else: conf = config.Config(filename=deluged_conf_file) download_location = conf["download_location"] print(json.dumps({'download_location': download_location})) def subcommand_set_configuration(arguments): """Set the deluged configuration.""" if arguments.parameter != 'download_location': return _set_configuration('core.conf', arguments.parameter, arguments.value) def subcommand_setup(_): """Perform initial setup for deluge.""" with open(DELUGE_WEB_SYSTEMD_SERVICE_PATH, 'w') as file_handle: file_handle.write(DELUGE_WEB_SYSTEMD_SERVICE) _set_deluged_daemon_options() subprocess.check_call(['systemctl', 'daemon-reload']) # restarting deluge-web service stops also possible deluged process # that was started from the web interface action_utils.service_restart('deluge-web') action_utils.service_restart('deluged') # wait processes to start time.sleep(10) # configure deluge-web to autoconnect to the default deluged daemon, also # restarts deluged and deluge-web services again to create config files host_id = _get_host_id() _set_configuration('web.conf', 'default_daemon', host_id) def main(): """Parse arguments and perform all duties.""" arguments = parse_arguments() subcommand = arguments.subcommand.replace('-', '_') subcommand_method = globals()['subcommand_' + subcommand] subcommand_method(arguments) if __name__ == '__main__': main()