From b0305746c8a741316515eee63a2100f821dca9f2 Mon Sep 17 00:00:00 2001 From: Joseph Nuthalapati Date: Mon, 20 Dec 2021 14:17:27 -0800 Subject: [PATCH] tahoe-lafs: Drop app as it is not being used Signed-off-by: Joseph Nuthalapati [sunil: Add to configuration file removal in Debian package and setup.py] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Sunil Mohan Adapa --- actions/tahoe-lafs | 228 ------------------ debian/copyright | 10 - debian/freedombox.maintscript | 1 + plinth/modules/tahoe/__init__.py | 194 --------------- .../apache2/conf-available/tahoe-plinth.conf | 14 -- .../data/etc/plinth/modules-enabled/tahoe | 1 - .../lib/firewalld/services/tahoe-plinth.xml | 7 - plinth/modules/tahoe/errors.py | 11 - plinth/modules/tahoe/manifest.py | 11 - .../tahoe/templates/tahoe-post-setup.html | 91 ------- .../tahoe/templates/tahoe-pre-setup.html | 47 ---- plinth/modules/tahoe/tests/__init__.py | 0 plinth/modules/tahoe/tests/test_functional.py | 92 ------- plinth/modules/tahoe/urls.py | 18 -- plinth/modules/tahoe/views.py | 64 ----- pyproject.toml | 1 - setup.py | 1 + static/themes/default/icons/tahoe-lafs.png | Bin 38261 -> 0 bytes static/themes/default/icons/tahoe-lafs.svg | 170 ------------- 19 files changed, 2 insertions(+), 959 deletions(-) delete mode 100755 actions/tahoe-lafs delete mode 100644 plinth/modules/tahoe/__init__.py delete mode 100644 plinth/modules/tahoe/data/etc/apache2/conf-available/tahoe-plinth.conf delete mode 100644 plinth/modules/tahoe/data/etc/plinth/modules-enabled/tahoe delete mode 100644 plinth/modules/tahoe/data/usr/lib/firewalld/services/tahoe-plinth.xml delete mode 100644 plinth/modules/tahoe/errors.py delete mode 100644 plinth/modules/tahoe/manifest.py delete mode 100644 plinth/modules/tahoe/templates/tahoe-post-setup.html delete mode 100644 plinth/modules/tahoe/templates/tahoe-pre-setup.html delete mode 100644 plinth/modules/tahoe/tests/__init__.py delete mode 100644 plinth/modules/tahoe/tests/test_functional.py delete mode 100644 plinth/modules/tahoe/urls.py delete mode 100644 plinth/modules/tahoe/views.py delete mode 100644 static/themes/default/icons/tahoe-lafs.png delete mode 100644 static/themes/default/icons/tahoe-lafs.svg diff --git a/actions/tahoe-lafs b/actions/tahoe-lafs deleted file mode 100755 index 23d6d0b9a..000000000 --- a/actions/tahoe-lafs +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/python3 -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Configuration helper for Tahoe-LAFS. -""" - -import argparse -import grp -import json -import os -import pwd -import shutil -import subprocess - -import augeas -import ruamel.yaml - -from plinth.modules.tahoe import (introducer_furl_file, introducer_name, - introducers_file, storage_node_name, - tahoe_home) -from plinth.modules.tahoe.errors import TahoeConfigurationError -from plinth.utils import YAMLFile - -domain_name_file = os.path.join(tahoe_home, 'domain_name') - -DEFAULT_FILE = '/etc/default/tahoe-lafs' - - -def parse_arguments(): - """Return parsed command line arguments as dictionary.""" - parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') - - setup = subparsers.add_parser('setup', - help='Set domain name for Tahoe-LAFS') - setup.add_argument('--domain-name', - help='The domain name to be used by Tahoe-LAFS') - subparsers.add_parser('create-introducer', - help='Create and start the introducer node') - subparsers.add_parser('create-storage-node', - help='Create and start the storage node') - subparsers.add_parser( - 'autostart', help='Automatically start all introducers and ' - 'storage nodes on system startup') - intro_parser_add = subparsers.add_parser( - 'add-introducer', help="Add an introducer to the storage node's list " - 'of introducers.') - intro_parser_add.add_argument( - '--introducer', help="Add an introducer to the storage node's list " - 'of introducers Param introducer must be a tuple ' - 'of (pet_name, furl)') - intro_parser_remove = subparsers.add_parser( - 'remove-introducer', help='Rename the introducer entry in the ' - 'introducers.yaml file specified by the ' - 'param') - intro_parser_remove.add_argument( - '--pet-name', help='The domain name that will be used by ' - 'Tahoe-LAFS') - subparsers.add_parser( - 'get-introducers', help='Return a dictionary of all introducers and ' - 'their furls added to the storage node running ' - 'on this FreedomBox.') - subparsers.add_parser( - 'get-local-introducer', - help='Return the name and furl of the introducer ' - 'created on this FreedomBox') - - return parser.parse_args() - - -def subcommand_setup(arguments): - """Actions to be performed after installing Tahoe-LAFS.""" - # Create tahoe group if needed. - try: - grp.getgrnam('tahoe-lafs') - except KeyError: - subprocess.run(['addgroup', 'tahoe-lafs'], check=True) - - # Create tahoe user if needed. - try: - pwd.getpwnam('tahoe-lafs') - except KeyError: - subprocess.run([ - 'adduser', '--system', '--ingroup', 'tahoe-lafs', '--home', - '/var/lib/tahoe-lafs', '--gecos', - 'Tahoe-LAFS distributed file system', 'tahoe-lafs' - ], check=True) - - if not os.path.exists(tahoe_home): - os.makedirs(tahoe_home, mode=0o755) - - shutil.chown(tahoe_home, user='tahoe-lafs', group='tahoe-lafs') - - if not os.path.exists(domain_name_file): - with open(domain_name_file, 'w') as dnf: - dnf.write(arguments.domain_name) - - -def subcommand_autostart(_): - """Automatically start all introducers and storage nodes on system startup. - """ - 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]', DEFAULT_FILE) - aug.load() - - aug.set('/files' + DEFAULT_FILE + '/AUTOSTART', 'all') - aug.save() - - -def get_configured_domain_name(): - """Extract and return the domain name from the domain name file. - - Throws TahoeConfigurationError if the domain name file is not found. - """ - if not os.path.exists(domain_name_file): - raise TahoeConfigurationError - else: - with open(domain_name_file) as dnf: - return dnf.read().rstrip() - - -def subcommand_create_introducer(_): - """Create a Tahoe-LAFS introducer on this FreedomBox.""" - os.chdir(tahoe_home) - - if not os.path.exists(os.path.join(tahoe_home, introducer_name)): - subprocess.check_call([ - 'tahoe', 'create-introducer', '--port=3456', - '--location=tcp:{}:3456'.format(get_configured_domain_name()), - introducer_name - ]) - - subprocess.call(['tahoe', 'start', introducer_name]) - - -def subcommand_create_storage_node(_): - """Create a Tahoe-LAFS storage node on this FreedomBox.""" - os.chdir(tahoe_home) - - if not os.path.exists(os.path.join(tahoe_home, storage_node_name)): - subprocess.check_call([ - 'tahoe', 'create-node', '--nickname=\"storage_node\"', - '--webport=1234', - '--hostname={}'.format(get_configured_domain_name()), - storage_node_name - ]) - with open( - os.path.join(tahoe_home, introducer_name, 'private', - introducer_name + '.furl'), 'r') as furl_file: - furl = furl_file.read().rstrip() - conf_dict = {'introducers': {introducer_name: {'furl': furl}}} - conf_yaml = ruamel.yaml.dump(conf_dict, - Dumper=ruamel.yaml.RoundTripDumper) - with open( - os.path.join(tahoe_home, storage_node_name, 'private', - 'introducers.yaml'), 'w') as file_handle: - file_handle.write(conf_yaml) - - subprocess.call(['tahoe', 'start', storage_node_name]) - - -def subcommand_add_introducer(arguments): - """Add an introducer to the storage node's list of introducers. - - Param introducer must be a tuple of (pet_name, furl). - """ - with YAMLFile(introducers_file) as conf: - pet_name, furl = arguments.introducer.split(',') - conf['introducers'][pet_name] = {'furl': furl} - - restart_storage_node() - - -def subcommand_remove_introducer(arguments): - """Rename the introducer entry in the introducers.yaml file specified - by the param pet_name - """ - with YAMLFile(introducers_file) as conf: - del conf['introducers'][arguments.pet_name] - - restart_storage_node() - - -def subcommand_get_introducers(_): - """Return a dictionary of all introducers and their furls. - - The ones added to the storage node running on this FreedomBox. - """ - with open(introducers_file, 'r') as intro_conf: - conf = ruamel.yaml.round_trip_load(intro_conf) - - introducers = [] - for pet_name in conf['introducers'].keys(): - introducers.append((pet_name, conf['introducers'][pet_name]['furl'])) - - print(json.dumps(introducers)) - - -def subcommand_get_local_introducer(_): - """Return the name and furl of the introducer created on this FreedomBox - """ - with open(introducer_furl_file, 'r') as furl_file: - furl = furl_file.read().rstrip() - - print(json.dumps((introducer_name, furl))) - - -def restart_storage_node(): - """Called after exiting context of editing introducers file.""" - try: - subprocess.run(['tahoe', 'restart', 'storage_node'], check=True) - except subprocess.CalledProcessError as err: - print('Failed to restart storage_node with new configuration: %s', err) - - -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() diff --git a/debian/copyright b/debian/copyright index bf5b4de20..af2e62b80 100644 --- a/debian/copyright +++ b/debian/copyright @@ -248,16 +248,6 @@ Copyright: Jakob Borg and the Syncthing project Comment: https://commons.wikimedia.org/wiki/File:SyncthingLogoHorizontal.svg License: MPL-2.0 -Files: static/themes/default/icons/tahoe-lafs.png -Copyright: 2017 Kishan Raval -Comment: https://github.com/thekishanraval/Logos -License: GPL-3+ - -Files: static/themes/default/icons/tahoe-lafs.svg -Copyright: 2006-2018 The Tahoe-LAFS Software Foundation -Comment: https://github.com/tahoe-lafs/tahoe-lafs/blob/master/misc/build_helpers/icons/logo.svg -License: GPL-2+ - Files: static/themes/default/icons/tor.png static/themes/default/icons/tor.svg Copyright: The Tor Project, Inc. diff --git a/debian/freedombox.maintscript b/debian/freedombox.maintscript index 290d29cca..128d02ff6 100644 --- a/debian/freedombox.maintscript +++ b/debian/freedombox.maintscript @@ -15,3 +15,4 @@ rm_conffile /etc/plinth/custom-shortcuts.json 20.12~ rm_conffile /etc/plinth/modules-enabled/coquelicot 20.14~ rm_conffile /etc/plinth/modules-enabled/diaspora 21.16~ rm_conffile /etc/plinth/modules-enabled/monkeysphere 21.16~ +rm_conffile /etc/plinth/modules-enabled/tahoe 21.16~ diff --git a/plinth/modules/tahoe/__init__.py b/plinth/modules/tahoe/__init__.py deleted file mode 100644 index 6c4bbcd1e..000000000 --- a/plinth/modules/tahoe/__init__.py +++ /dev/null @@ -1,194 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -FreedomBox app to configure Tahoe-LAFS. -""" - -import json -import os - -from django.urls import reverse_lazy -from django.utils.translation import gettext_lazy as _ - -from plinth import actions -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, diagnose_url -from plinth.modules.backups.components import BackupRestore -from plinth.modules.firewall.components import Firewall -from plinth.package import Packages -from plinth.utils import format_lazy - -from . import manifest -from .errors import TahoeConfigurationError - -_description = [ - _('Tahoe-LAFS is a decentralized secure file storage system. ' - 'It uses provider independent security to store files over a ' - 'distributed network of storage nodes. Even if some of the nodes fail, ' - 'your files can be retrieved from the remaining nodes.'), - format_lazy( - _('This {box_name} hosts a storage node and an introducer by default. ' - 'Additional introducers can be added, which will introduce this ' - 'node to the other storage nodes.'), box_name=_(cfg.box_name)), -] - -tahoe_home = '/var/lib/tahoe-lafs' -introducer_name = 'introducer' -storage_node_name = 'storage_node' -domain_name_file = os.path.join(tahoe_home, 'domain_name') -introducers_file = os.path.join( - tahoe_home, '{}/private/introducers.yaml'.format(storage_node_name)) -introducer_furl_file = os.path.join( - tahoe_home, '{0}/private/{0}.furl'.format(introducer_name)) - -app = None - - -class TahoeApp(app_module.App): - """FreedomBox app for Tahoe LAFS.""" - - app_id = 'tahoe' - - _version = 1 - - def __init__(self): - """Create components for the app.""" - super().__init__() - - info = app_module.Info(app_id=self.app_id, version=self._version, - name=_('Tahoe-LAFS'), - icon_filename='tahoe-lafs', - short_description=_('Distributed File Storage'), - description=_description) - - self.add(info) - - menu_item = menu.Menu('menu-tahoe', info.name, info.short_description, - info.icon_filename, 'tahoe:index', - parent_url_name='apps', advanced=True) - self.add(menu_item) - - shortcut = frontpage.Shortcut( - 'shortcut-tahoe', info.name, - short_description=info.short_description, icon=info.icon_filename, - description=info.description, url=None, - configure_url=reverse_lazy('tahoe:index'), login_required=True) - self.add(shortcut) - - packages = Packages('packages-tahoe', ['tahoe-lafs']) - self.add(packages) - - firewall = Firewall('firewall-tahoe', info.name, - ports=['tahoe-plinth'], is_external=True) - self.add(firewall) - - webserver = Webserver('webserver-tahoe', 'tahoe-plinth') - self.add(webserver) - - daemon = Daemon('daemon-tahoe', 'tahoe-lafs') - self.add(daemon) - - backup_restore = BackupRestore('backup-restore-tahoe', - **manifest.backup) - self.add(backup_restore) - - def is_enabled(self): - """Return whether all the leader components are enabled. - - Return True when there are no leader components and - domain name is setup. - """ - return super().is_enabled() and is_setup() - - def diagnose(self): - """Run diagnostics and return the results.""" - results = super().diagnose() - results.extend([ - diagnose_url('http://localhost:5678', kind='4', - check_certificate=False), - diagnose_url('http://localhost:5678', kind='6', - check_certificate=False), - diagnose_url('http://{}:5678'.format(get_configured_domain_name()), - kind='4', check_certificate=False) - ]) - return results - - -class Shortcut(frontpage.Shortcut): - """Frontpage shortcut to use configured domain name for URL.""" - - def enable(self): - """Set the proper shortcut URL when enabled.""" - super().enable() - self.url = 'https://{}:5678'.format(get_configured_domain_name()) - - -def is_setup(): - """Check whether Tahoe-LAFS is setup""" - return os.path.exists(domain_name_file) - - -def get_configured_domain_name(): - """Extract and return the domain name from the domain name file. - Throws TahoeConfigurationError if the domain name file is not found. - """ - if not os.path.exists(domain_name_file): - raise TahoeConfigurationError - else: - with open(domain_name_file) as dnf: - return dnf.read().rstrip() - - -def setup(helper, old_version=None): - """Install and configure the module.""" - app.setup(old_version) - - -def post_setup(configured_domain_name): - """Actions to be performed after installing tahoe-lafs package.""" - actions.superuser_run('tahoe-lafs', - ['setup', '--domain-name', configured_domain_name]) - actions.run_as_user('tahoe-lafs', ['create-introducer'], - become_user='tahoe-lafs') - actions.run_as_user('tahoe-lafs', ['create-storage-node'], - become_user='tahoe-lafs') - actions.superuser_run('tahoe-lafs', ['autostart']) - app.enable() - - -def add_introducer(introducer): - """Add an introducer to the storage node's list of introducers. - Param introducer must be a tuple of (pet_name, furl) - """ - actions.run_as_user( - 'tahoe-lafs', ['add-introducer', "--introducer", ",".join(introducer)], - become_user='tahoe-lafs') - - -def remove_introducer(pet_name): - """Rename the introducer entry in the introducers.yaml file specified by - the param pet_name. - """ - actions.run_as_user('tahoe-lafs', - ['remove-introducer', '--pet-name', pet_name], - become_user='tahoe-lafs') - - -def get_introducers(): - """Return a dictionary of all introducers and their furls added to the - storage node running on this FreedomBox. - """ - introducers = actions.run_as_user('tahoe-lafs', ['get-introducers'], - become_user='tahoe-lafs') - - return json.loads(introducers) - - -def get_local_introducer(): - """Return the name and furl of the introducer created on this FreedomBox. - """ - introducer = actions.run_as_user('tahoe-lafs', ['get-local-introducer'], - become_user='tahoe-lafs') - - return json.loads(introducer) diff --git a/plinth/modules/tahoe/data/etc/apache2/conf-available/tahoe-plinth.conf b/plinth/modules/tahoe/data/etc/apache2/conf-available/tahoe-plinth.conf deleted file mode 100644 index 80e5c5fdd..000000000 --- a/plinth/modules/tahoe/data/etc/apache2/conf-available/tahoe-plinth.conf +++ /dev/null @@ -1,14 +0,0 @@ -# Tahoe-LAFS Storage Node web interface - -Listen 5678 - -# XXX: SSL is not configured? -# TODO: Use subdomain? - - - Include includes/freedombox-auth-ldap.conf - Require ldap-group cn=admin,ou=groups,dc=thisbox - - ProxyPass http://localhost:1234/ - - diff --git a/plinth/modules/tahoe/data/etc/plinth/modules-enabled/tahoe b/plinth/modules/tahoe/data/etc/plinth/modules-enabled/tahoe deleted file mode 100644 index 2379a52f4..000000000 --- a/plinth/modules/tahoe/data/etc/plinth/modules-enabled/tahoe +++ /dev/null @@ -1 +0,0 @@ -#plinth.modules.tahoe diff --git a/plinth/modules/tahoe/data/usr/lib/firewalld/services/tahoe-plinth.xml b/plinth/modules/tahoe/data/usr/lib/firewalld/services/tahoe-plinth.xml deleted file mode 100644 index e745d9636..000000000 --- a/plinth/modules/tahoe/data/usr/lib/firewalld/services/tahoe-plinth.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - Tahoe-LAFS - Tahoe-LAFS is a distributed file storage system - - - diff --git a/plinth/modules/tahoe/errors.py b/plinth/modules/tahoe/errors.py deleted file mode 100644 index e767d9221..000000000 --- a/plinth/modules/tahoe/errors.py +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Errors for Tahoe-LAFS module -""" - -from plinth.errors import PlinthError - - -class TahoeConfigurationError(PlinthError): - """Tahoe-LAFS has not been configured for domain name.""" - pass diff --git a/plinth/modules/tahoe/manifest.py b/plinth/modules/tahoe/manifest.py deleted file mode 100644 index 22fb0dbeb..000000000 --- a/plinth/modules/tahoe/manifest.py +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Application manfiest for tahoe-lafs. -""" - -backup = { - 'secrets': { - 'directories': ['/var/lib/tahoe-lafs/'] - }, - 'services': ['tahoe-lafs'] -} diff --git a/plinth/modules/tahoe/templates/tahoe-post-setup.html b/plinth/modules/tahoe/templates/tahoe-post-setup.html deleted file mode 100644 index e59011e5d..000000000 --- a/plinth/modules/tahoe/templates/tahoe-post-setup.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends "app.html" %} -{% comment %} -# SPDX-License-Identifier: AGPL-3.0-or-later -{% endcomment %} - -{% load i18n %} -{% load bootstrap %} - -{% block description %} - -{% for paragraph in description %} -

{{ paragraph|safe }}

-{% endfor %} - -

- {% url 'config:index' as index_url %} - {% blocktrans trimmed with domain_name=domain_name %} - The Tahoe-LAFS server domain is set to {{ domain_name }}. - Changing the FreedomBox domain name needs a reinstall of - Tahoe-LAFS and you WILL LOSE DATA. You can access Tahoe-LAFS at - https://{{domain_name}}:5678. - {% endblocktrans %} -

-{% endblock %} - -{% block configuration %} - {{ block.super }} - -

{% trans "Local introducer" %}

-
- - - - - - - - - - - -
{% trans "Pet Name" %} furl
{{ local_introducer.0 }}{{ local_introducer.1 }}
-
- -
- {% csrf_token %} -

{% trans "Add new introducer" %}

- -
- - -
-
- - -
- -
- -
- -

{% trans "Connected introducers" %}

-
- - - - - - - - {% for introducer, furl in introducers %} - - - - - - {% endfor %} -
{% trans "Pet Name" %} furl
{{ introducer }}{{ furl }} -
- {% csrf_token %} - -
-
-
- -{% endblock %} diff --git a/plinth/modules/tahoe/templates/tahoe-pre-setup.html b/plinth/modules/tahoe/templates/tahoe-pre-setup.html deleted file mode 100644 index 3249ab6a3..000000000 --- a/plinth/modules/tahoe/templates/tahoe-pre-setup.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends "base.html" %} -{% comment %} -# SPDX-License-Identifier: AGPL-3.0-or-later -{% endcomment %} - -{% load bootstrap %} -{% load i18n %} - -{% block content %} - {% block pagetitle %} -

{{ title }}

- {% endblock %} - - {% block description %} - {% for paragraph in description %} -

{{ paragraph|safe }}

- {% endfor %} - {% endblock %} - -

- {% url 'config:index' as index_url %} - {% if domain_names|length == 0 %} - No domain(s) are set. You can setup your domain on the system at - {% trans "Configure" %} page. - {% endif %} -

- - {% block status %} - {% endblock %} - - {% block diagnostics %} - {% endblock %} - - {% block configuration %} - {% if domain_names|length > 0 %} -

{% trans Configuration %}

-
- {% csrf_token %} - - {{ form|bootstrap }} - - -
- {% endif %} - {% endblock %} -{% endblock %} diff --git a/plinth/modules/tahoe/tests/__init__.py b/plinth/modules/tahoe/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/plinth/modules/tahoe/tests/test_functional.py b/plinth/modules/tahoe/tests/test_functional.py deleted file mode 100644 index 3adf10e85..000000000 --- a/plinth/modules/tahoe/tests/test_functional.py +++ /dev/null @@ -1,92 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Functional, browser based tests for tahoe app. -""" - -import pytest -from plinth.tests import functional - -pytestmark = [pytest.mark.apps, pytest.mark.tahoe, pytest.mark.skip] - -# TODO: When tahoe-lafs is restarted, it leaves a .gnupg folder in -# /var/lib/tahoe-lafs and failes to start in the next run. Enable tests after -# this is fixed. - - -class TestTahoeApp(functional.BaseAppTests): - app_name = 'tahoe' - has_service = True - has_web = True - - @pytest.fixture(scope='class', autouse=True) - def fixture_setup(self, session_browser): - """Setup the app.""" - functional.login(session_browser) - functional.set_advanced_mode(session_browser, True) - functional.set_domain_name(session_browser, 'mydomain.example') - functional.install(session_browser, self.app_name) - functional.app_select_domain_name(session_browser, self.app_name, - 'mydomain.example') - - def test_default_introducers(self, session_browser): - """Test default introducers.""" - assert _get_introducer(session_browser, 'mydomain.example', 'local') - assert _get_introducer(session_browser, 'mydomain.example', - 'connected') - - def test_add_remove_introducers(self, session_browser): - """Test add and remove introducers.""" - if _get_introducer(session_browser, 'anotherdomain.example', - 'connected'): - _remove_introducer(session_browser, 'anotherdomain.example') - - _add_introducer(session_browser, 'anotherdomain.example') - assert _get_introducer(session_browser, 'anotherdomain.example', - 'connected') - - _remove_introducer(session_browser, 'anotherdomain.example') - assert not _get_introducer(session_browser, 'anotherdomain.example', - 'connected') - - @pytest.mark.backups - def test_backup_restore(self, session_browser): - """Test backup and restore of app data.""" - if not _get_introducer(session_browser, 'backupdomain.example', - 'connected'): - _add_introducer(session_browser, 'backupdomain.example') - functional.backup_create(session_browser, self.app_name, 'test_tahoe') - - _remove_introducer(session_browser, 'backupdomain.example') - functional.backup_restore(session_browser, self.app_name, 'test_tahoe') - - assert functional.service_is_running(session_browser, self.app_name) - assert _get_introducer(session_browser, 'backupdomain.example', - 'connected') - - -def _get_introducer(browser, domain, introducer_type): - """Return an introducer element with a given type from tahoe-lafs.""" - functional.nav_to_module(browser, 'tahoe') - css_class = '.{}-introducers .introducer-furl'.format(introducer_type) - for furl in browser.find_by_css(css_class): - if domain in furl.text: - return furl.parent - - return None - - -def _add_introducer(browser, domain): - """Add a new introducer into tahoe-lafs.""" - functional.nav_to_module(browser, 'tahoe') - - furl = 'pb://ewe4zdz6kxn7xhuvc7izj2da2gpbgeir@tcp:{}:3456/' \ - 'fko4ivfwgqvybppwar3uehkx6spaaou7'.format(domain) - browser.fill('pet_name', 'testintroducer') - browser.fill('furl', furl) - functional.submit(browser, form_class='form-add-introducer') - - -def _remove_introducer(browser, domain): - """Remove an introducer from tahoe-lafs.""" - introducer = _get_introducer(browser, domain, 'connected') - functional.submit(browser, element=introducer.find_by_css('.form-remove')) diff --git a/plinth/modules/tahoe/urls.py b/plinth/modules/tahoe/urls.py deleted file mode 100644 index e076a5a95..000000000 --- a/plinth/modules/tahoe/urls.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -URLs for the Tahoe-LAFS module. -""" - -from django.urls import re_path - -from . import views -from .views import TahoeAppView, TahoeSetupView - -urlpatterns = [ - re_path(r'^apps/tahoe/setup/$', TahoeSetupView.as_view(), name='setup'), - re_path(r'^apps/tahoe/add_introducer/$', views.add_introducer, - name='add-introducer'), - re_path(r'^apps/tahoe/remove_introducer/(?P[0-9a-zA-Z_]+)/$', - views.remove_introducer, name='remove-introducer'), - re_path(r'^apps/tahoe/$', TahoeAppView.as_view(), name='index') -] diff --git a/plinth/modules/tahoe/views.py b/plinth/modules/tahoe/views.py deleted file mode 100644 index 82bfd6510..000000000 --- a/plinth/modules/tahoe/views.py +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Views for the Tahoe-LAFS module -""" -from django.shortcuts import redirect -from django.urls import reverse_lazy -from django.views.generic import FormView - -from plinth.forms import DomainSelectionForm -from plinth.modules import names, tahoe -from plinth.views import AppView - - -class TahoeSetupView(FormView): - """Show tahoe-lafs setup page.""" - template_name = 'tahoe-pre-setup.html' - form_class = DomainSelectionForm - success_url = reverse_lazy('tahoe:index') - - def form_valid(self, form): - domain_name = form.cleaned_data['domain_name'] - tahoe.post_setup(domain_name) - return super().form_valid(form) - - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(**kwargs) - context['description'] = tahoe.app.info.description - context['title'] = tahoe.app.info.name - context['domain_names'] = names.components.DomainName.list_names( - 'tahoe-plinth') - - return context - - -class TahoeAppView(AppView): - """Show tahoe-lafs service page.""" - app_id = 'tahoe' - template_name = 'tahoe-post-setup.html' - - def dispatch(self, request, *args, **kwargs): - if not tahoe.is_setup(): - return redirect('tahoe:setup') - - return super().dispatch(request, *args, **kwargs) - - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(**kwargs) - context['domain_name'] = tahoe.get_configured_domain_name() - context['introducers'] = tahoe.get_introducers() - context['local_introducer'] = tahoe.get_local_introducer() - - return context - - -def add_introducer(request): - if request.method == 'POST': - tahoe.add_introducer((request.POST['pet_name'], request.POST['furl'])) - return redirect('tahoe:index') - - -def remove_introducer(request, introducer): - if request.method == 'POST': - tahoe.remove_introducer(introducer) - return redirect('tahoe:index') diff --git a/pyproject.toml b/pyproject.toml index d0d36c748..0ad41f90b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,6 @@ markers = [ "storage", "syncthing", "system", - "tahoe", "tor", "transmission", "ttrss", diff --git a/setup.py b/setup.py index 2d82edf1d..9855f177a 100755 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ DISABLED_APPS_TO_REMOVE = [ 'udiskie', 'restore', 'repro', + 'tahoe', ] REMOVED_FILES = [ diff --git a/static/themes/default/icons/tahoe-lafs.png b/static/themes/default/icons/tahoe-lafs.png deleted file mode 100644 index bdac72e3ab9ed7751cb2b0400ac9be3846b06f30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38261 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 - - - - - image/svg+xml - - Tahoe-LAFS logo - - - - - - Tahoe-LAFS logo - - A proposed logo for the Tahoe-LAFS Project. - - - - -Tahoe-LAFS Logo - - by Kevin Reid - - is licensed under a Creative Commons Attribution 3.0 Unported License - -. - - - - - - - - - - - - - - - -