Sunil Mohan Adapa 3e8c052258
deluge: More reliable initial configuration setup
Closes: #1779.

Wait until configuration files are created the deluge services.

- Deluged service on Debian Stable creates the core configuration file after
first exit. The version on Debian Testing creates the configuration file during
first run.

- deluge-web seems to create web.conf on first run.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Veiko Aasa <veiko17@disroot.org>
2020-02-21 12:42:18 +02:00

195 lines
6.2 KiB
Python
Executable File

#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configuration helper for BitTorrent web client.
"""
import argparse
import json
import pathlib
import subprocess
import time
import augeas
from plinth import action_utils
from plinth.modules.deluge.utils import Config
DELUGED_DEFAULT_FILE = '/etc/default/deluged'
DELUGE_CONF_DIR = pathlib.Path('/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
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')
with Config(DELUGE_CONF_DIR / filename) as config:
config.content[parameter] = value
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."""
try:
with Config(DELUGE_CONF_DIR / 'hostlist.conf') as config:
return config.content['hosts'][0][0]
except FileNotFoundError:
with Config(DELUGE_CONF_DIR / 'hostlist.conf.1.2') as config:
return config.content['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."""
with Config(DELUGE_CONF_DIR / 'core.conf') as config:
download_location = config.content['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 an old deluge-web service stops also possible deluged process
# that was started from the web interface.
action_utils.service_try_restart('deluge-web')
# Wait until core configuration is available so that status of the app can
# be shown properly.
_wait_for_configuration('deluged', 'core.conf')
# Configure deluge-web to autoconnect to the default deluged daemon.
_wait_for_configuration('deluge-web', 'web.conf')
host_id = _get_host_id()
_set_configuration('web.conf', 'default_daemon', host_id)
def _wait_for_configuration(service, file_name):
"""Wait until configuration file has been created."""
conf_file = DELUGE_CONF_DIR / file_name
if conf_file.exists():
return
# deluge-web creates files on first run. deluged on the other than differs
# in version. Older version in Debian Buster creates the files after a
# restart while newer versions create the files on first run. The following
# approach is slightly better for create-on-exit case.
is_running = action_utils.service_is_running(service)
for interval in range(7):
action_utils.service_restart(service)
if conf_file.exists():
break
print('Waiting for {service} configuration')
time.sleep(2**interval) # Exponentially increase the time waited
else:
raise Exception(f'Unable to setup {service}.')
if not is_running:
action_utils.service_stop(service)
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()