From 621cb675272281400db801200f32a9ea61ae5510 Mon Sep 17 00:00:00 2001 From: Joseph Nuthalapati Date: Mon, 20 Dec 2021 14:17:17 -0800 Subject: [PATCH] diaspora: Drop app that was never finished. Signed-off-by: Joseph Nuthalapati [sunil: Add to configuration file removal in Debian package] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Sunil Mohan Adapa --- actions/diaspora | 146 ------------- debian/copyright | 4 +- debian/freedombox.maintscript | 1 + plinth/modules/diaspora/__init__.py | 194 ------------------ plinth/modules/diaspora/forms.py | 13 -- plinth/modules/diaspora/manifest.py | 33 --- .../templates/diaspora-post-setup.html | 24 --- .../templates/diaspora-pre-setup.html | 47 ----- plinth/modules/diaspora/tests/__init__.py | 1 - .../diaspora/tests/test_apache_config.py | 25 --- plinth/modules/diaspora/urls.py | 14 -- plinth/modules/diaspora/views.py | 80 -------- static/themes/default/icons/diaspora.png | Bin 10630 -> 0 bytes static/themes/default/icons/diaspora.svg | 62 ------ 14 files changed, 2 insertions(+), 642 deletions(-) delete mode 100755 actions/diaspora delete mode 100644 plinth/modules/diaspora/__init__.py delete mode 100644 plinth/modules/diaspora/forms.py delete mode 100644 plinth/modules/diaspora/manifest.py delete mode 100644 plinth/modules/diaspora/templates/diaspora-post-setup.html delete mode 100644 plinth/modules/diaspora/templates/diaspora-pre-setup.html delete mode 100644 plinth/modules/diaspora/tests/__init__.py delete mode 100644 plinth/modules/diaspora/tests/test_apache_config.py delete mode 100644 plinth/modules/diaspora/urls.py delete mode 100644 plinth/modules/diaspora/views.py delete mode 100644 static/themes/default/icons/diaspora.png delete mode 100644 static/themes/default/icons/diaspora.svg diff --git a/actions/diaspora b/actions/diaspora deleted file mode 100755 index ef6ffa52d..000000000 --- a/actions/diaspora +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/python3 -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Configuration helper for diaspora* pod. -""" - -import argparse -import subprocess - -import augeas - -from plinth import action_utils -from plinth.modules import diaspora - -DIASPORA_YAML = "/etc/diaspora/diaspora.yml" - - -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( - 'pre-install', - help='Preseed debconf values before packages are installed.') - subparsers.add_parser( - 'enable-user-registrations', - help='Allow users to sign up to this diaspora* pod without an ' - 'invitation.') - subparsers.add_parser( - 'disable-user-registrations', - help='Allow only users with an invitation to register to this ' - 'diaspora* pod') - subparsers.add_parser('start-diaspora', help='Start diaspora* service') - subparsers.add_parser( - 'disable-ssl', help="Disable SSL on the diaspora* application server") - setup = subparsers.add_parser('setup', - help='Set Domain name for diaspora*') - setup.add_argument('--domain-name', - help='The domain name that will be used by diaspora*') - - return parser.parse_args() - - -def subcommand_setup(arguments): - """Set the domain_name in diaspora configuration files""" - domain_name = arguments.domain_name - with open('/etc/diaspora/domain_name', 'w') as dnf: - dnf.write(domain_name) - set_domain_name(domain_name) - uncomment_user_registrations() - - -def set_domain_name(domain_name): - """Write a new domain name to diaspora configuration files""" - # This did not set the domain_name - # action_utils.dpkg_reconfigure('diaspora-common', - # {'url': domain_name}) - # Manually changing the domain name in the conf files. - conf_file = '/etc/diaspora.conf' - aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + - augeas.Augeas.NO_MODL_AUTOLOAD) - - # lens for shell-script config file - aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns') - aug.set('/augeas/load/Shellvars/incl[last() + 1]', conf_file) - aug.load() - - aug.set('/files/etc/diaspora.conf/SERVERNAME', '"{}"'.format(domain_name)) - aug.set('/files/etc/diaspora.conf/ENVIRONMENT_URL', - 'http://{}:3000'.format(domain_name)) - aug.save() - - diaspora.generate_apache_configuration( - '/etc/apache2/conf-available/diaspora-plinth.conf', domain_name) - - action_utils.service_enable('diaspora') - action_utils.service_start('diaspora') - - -def subcommand_disable_ssl(_): - """ - Disable ssl in the diaspora configuration - as the apache server takes care of ssl - """ - # Using sed because ruamel.yaml has a bug for this kind of files - subprocess.call([ - "sed", "-i", "s/#require_ssl: true/require_ssl: false/g", DIASPORA_YAML - ]) - - -def uncomment_user_registrations(): - """Uncomment the enable_registrations line which is commented by default""" - subprocess.call([ - "sed", "-i", "s/#enable_registrations/enable_registrations/g", - DIASPORA_YAML - ]) - - -def subcommand_enable_user_registrations(_): - """Enable new user registrations on the diaspora* pod """ - subprocess.call([ - "sed", "-i", - "s/enable_registrations: false/enable_registrations: true/g", - DIASPORA_YAML - ]) - - -def subcommand_disable_user_registrations(_): - """Disable new user registrations on the diaspora* pod """ - subprocess.call([ - "sed", "-i", - "s/enable_registrations: true/enable_registrations: false/g", - DIASPORA_YAML - ]) - - -def subcommand_pre_install(_): - """Pre installation configuration for diaspora""" - presets = [ - 'diaspora-common diaspora-common/url string dummy_domain_name', - 'diaspora-common diaspora-common/dbpass note ', - 'diaspora-common diaspora-common/enablessl boolean false', - 'diaspora-common diaspora-common/useletsencrypt string false', - 'diaspora-common diaspora-common/services multiselect ', - 'diaspora-common diaspora-common/ssl boolean false', - 'diaspora-common diaspora-common/pgsql/authmethod-admin string ident', - 'diaspora-common diaspora-common/letsencrypt boolean false', - 'diaspora-common diaspora-common/remote/host string localhost', - 'diaspora-common diaspora-common/database-type string pgsql', - 'diaspora-common diaspora-common/dbconfig-install boolean true' - ] - - action_utils.debconf_set_selections(presets) - - -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 1c2aa540e..bf5b4de20 100644 --- a/debian/copyright +++ b/debian/copyright @@ -60,9 +60,7 @@ Copyright: 2007 Andrew Wedderburn Comment: https://commons.wikimedia.org/wiki/File:Deluge-Logo.svg License: GPL-2+ -Files: static/themes/default/icons/diaspora.png - static/themes/default/icons/diaspora.svg - static/themes/default/icons/ejabberd.png +Files: static/themes/default/icons/ejabberd.png static/themes/default/icons/ejabberd.svg static/themes/default/icons/matrixsynapse.svg static/themes/default/icons/privoxy.png diff --git a/debian/freedombox.maintscript b/debian/freedombox.maintscript index 943a06034..290d29cca 100644 --- a/debian/freedombox.maintscript +++ b/debian/freedombox.maintscript @@ -13,4 +13,5 @@ rm_conffile /etc/apt/preferences.d/50freedombox3.pref 20.5~ rm_conffile /etc/plinth/plinth.config 20.12~ 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~ diff --git a/plinth/modules/diaspora/__init__.py b/plinth/modules/diaspora/__init__.py deleted file mode 100644 index 1dbf13aaa..000000000 --- a/plinth/modules/diaspora/__init__.py +++ /dev/null @@ -1,194 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later - -import os - -import augeas -from django.utils.translation import gettext_lazy as _ - -from plinth import actions -from plinth import app as app_module -from plinth import frontpage, menu -from plinth.daemon import Daemon -from plinth.errors import DomainNotRegisteredError -from plinth.modules.apache.components import Webserver, diagnose_url -from plinth.modules.firewall.components import Firewall -from plinth.package import Packages -from plinth.utils import format_lazy - -domain_name_file = "/etc/diaspora/domain_name" -lazy_domain_name = None # To avoid repeatedly reading from file - - -def is_setup(): - return os.path.exists(domain_name_file) - - -def get_configured_domain_name(): - global lazy_domain_name - if lazy_domain_name: - return lazy_domain_name - - if not is_setup(): - raise DomainNotRegisteredError() - - with open(domain_name_file) as dnf: - lazy_domain_name = dnf.read().rstrip() - return lazy_domain_name - - -_description = [ - _('diaspora* is a decentralized social network where you can store ' - 'and control your own data.'), - format_lazy( - 'When enabled, the diaspora* pod will be available from ' - 'diaspora.{host} path on the ' - 'web server.'.format(host=get_configured_domain_name()) if is_setup() - else 'Please register a domain name for your FreedomBox to be able to' - ' federate with other diaspora* pods.') -] - -app = None - - -class DiasporaApp(app_module.App): - """FreedomBox app for Diaspora.""" - - app_id = 'diaspora' - - _version = 1 - - def __init__(self): - """Create components for the app.""" - super().__init__() - from . import manifest - - info = app_module.Info(app_id=self.app_id, version=self._version, - name=_('diaspora*'), icon_filename='diaspora', - short_description=_('Federated Social Network'), - description=_description, - clients=manifest.clients) - self.add(info) - - menu_item = menu.Menu('menu-diaspora', info.name, - info.short_description, info.icon_filename, - 'diaspora:index', parent_url_name='apps') - self.add(menu_item) - - shortcut = Shortcut('shortcut-diaspora', info.name, - short_description=info.short_description, - icon=info.icon_filename, url=None, - clients=info.clients, login_required=True) - self.add(shortcut) - - packages = Packages('packages-diaspora', ['diaspora']) - self.add(packages) - - firewall = Firewall('firewall-diaspora', info.name, - ports=['http', 'https'], is_external=True) - self.add(firewall) - - webserver = Webserver('webserver-diaspora', 'diaspora-plinth') - self.add(webserver) - - daemon = Daemon('daemon-diaspora', 'diaspora') - self.add(daemon) - - def diagnose(self): - """Run diagnostics and return the results.""" - results = super().diagnose() - - results.append( - diagnose_url('http://diaspora.localhost', kind='4', - check_certificate=False)) - results.append( - diagnose_url('http://diaspora.localhost', kind='6', - check_certificate=False)) - results.append( - diagnose_url( - 'http://diaspora.{}'.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://diaspora.{}'.format(get_configured_domain_name()) - - -def setup(helper, old_version=None): - """Install and configure the module.""" - helper.call('pre', actions.superuser_run, 'diaspora', ['pre-install']) - app.setup(old_version) - helper.call('custom_config', actions.superuser_run, 'diaspora', - ['disable-ssl']) - - -def setup_domain_name(domain_name): - actions.superuser_run('diaspora', ['setup', '--domain-name', domain_name]) - app.enable() - - -def is_user_registrations_enabled(): - """Return whether user registrations are enabled""" - with open('/etc/diaspora/diaspora.yml') as f: - for line in f.readlines(): - if "enable_registrations" in line: - return line.split(":")[1].strip() == "true" - - -def enable_user_registrations(): - """Allow users to register without invitation""" - actions.superuser_run('diaspora', ['enable-user-registrations']) - - -def disable_user_registrations(): - """Disallow users from registering without invitation""" - actions.superuser_run('diaspora', ['disable-user-registrations']) - - -def generate_apache_configuration(conf_file, domain_name): - """Generate Diaspora's apache configuration with the given domain name""" - open(conf_file, 'w').close() - - diaspora_domain_name = ".".join(["diaspora", domain_name]) - - aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + - augeas.Augeas.NO_MODL_AUTOLOAD) - - aug.set('/augeas/load/Httpd/lens', 'Httpd.lns') - aug.set('/augeas/load/Httpd/incl[last() + 1]', conf_file) - aug.load() - - aug.defvar('conf', '/files' + conf_file) - - aug.set('$conf/VirtualHost', None) - aug.defvar('vh', '$conf/VirtualHost') - aug.set('$vh/arg', diaspora_domain_name) - aug.set('$vh/directive[1]', 'ServerName') - aug.set('$vh/directive[1]/arg', diaspora_domain_name) - aug.set('$vh/directive[2]', 'DocumentRoot') - aug.set('$vh/directive[2]/arg', '"/var/lib/diaspora/public/"') - - aug.set('$vh/Location', None) - aug.set('$vh/Location/arg', '"/"') - aug.set('$vh/Location/directive[1]', 'ProxyPass') - aug.set('$vh/Location/directive[1]/arg', - '"unix:/var/run/diaspora/diaspora.sock|http://localhost/"') - - aug.set('$vh/Location[last() + 1]', None) - aug.set('$vh/Location[last()]/arg', '"/assets"') - aug.set('$vh/Location[last()]/directive[1]', 'ProxyPass') - aug.set('$vh/Location[last()]/directive[1]/arg', '!') - - aug.set('$vh/Directory', None) - aug.set('$vh/Directory/arg', '/var/lib/diaspora/public/') - aug.set('$vh/Directory/directive[1]', 'Require') - aug.set('$vh/Directory/directive[1]/arg[1]', 'all') - aug.set('$vh/Directory/directive[1]/arg[2]', 'granted') - - aug.save() diff --git a/plinth/modules/diaspora/forms.py b/plinth/modules/diaspora/forms.py deleted file mode 100644 index 89eb3bc7f..000000000 --- a/plinth/modules/diaspora/forms.py +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Forms for configuring diaspora* -""" - -from django import forms -from django.utils.translation import gettext_lazy as _ - - -class DiasporaAppForm(forms.Form): - """Service Form with additional fields for diaspora*""" - is_user_registrations_enabled = forms.BooleanField( - label=_('Enable new user registrations'), required=False) diff --git a/plinth/modules/diaspora/manifest.py b/plinth/modules/diaspora/manifest.py deleted file mode 100644 index 78b988d0a..000000000 --- a/plinth/modules/diaspora/manifest.py +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later - -from django.utils.translation import gettext_lazy as _ - -from plinth.clients import store_url -from plinth.modules import diaspora -from plinth.utils import format_lazy - -clients = [{ - 'name': - _('dandelion*'), - 'description': - _('It is an unofficial webview based client for the ' - 'community-run, distributed social network diaspora*'), - 'platforms': [{ - 'type': 'store', - 'os': 'android', - 'store_name': 'f-droid', - 'url': store_url('f-droid', 'com.github.dfa.diaspora_android'), - }] -}, { - 'name': - _('diaspora*'), - 'platforms': [{ - 'type': - 'web', - 'url': - format_lazy( - 'https://diaspora.{host}', - host=diaspora.get_configured_domain_name() - if diaspora.is_setup() else "") - }] -}] diff --git a/plinth/modules/diaspora/templates/diaspora-post-setup.html b/plinth/modules/diaspora/templates/diaspora-post-setup.html deleted file mode 100644 index b58f9748d..000000000 --- a/plinth/modules/diaspora/templates/diaspora-post-setup.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "app.html" %} -{% comment %} -# SPDX-License-Identifier: AGPL-3.0-or-later -{% endcomment %} - -{% load i18n %} - -{% block description %} - -{% for paragraph in description %} -

{{ paragraph|safe }}

-{% endfor %} - -

- {% url 'config:index' as index_url %} - {% blocktrans trimmed with domain_name=domain_name %} - The diaspora* pod domain is set to {{ domain_name }}. User - IDs will look like username@diaspora.{{ domain_name }}
- If the FreedomBox domain name is changed, all data of the users - registered with the previous podname wouldn't be accessible.
- You can access the diaspora* pod at diaspora.{{domain_name}} - {% endblocktrans %} -

-{% endblock %} diff --git a/plinth/modules/diaspora/templates/diaspora-pre-setup.html b/plinth/modules/diaspora/templates/diaspora-pre-setup.html deleted file mode 100644 index cf0892860..000000000 --- a/plinth/modules/diaspora/templates/diaspora-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 - 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/diaspora/tests/__init__.py b/plinth/modules/diaspora/tests/__init__.py deleted file mode 100644 index 1652e34b1..000000000 --- a/plinth/modules/diaspora/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/plinth/modules/diaspora/tests/test_apache_config.py b/plinth/modules/diaspora/tests/test_apache_config.py deleted file mode 100644 index ce9ee2d5f..000000000 --- a/plinth/modules/diaspora/tests/test_apache_config.py +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Test Apache configuration generation for diaspora* -""" - -import os -import tempfile - -from plinth.modules import diaspora - - -def test_generate_apache_configuration(): - """Test that Apache configuration is created properly.""" - with tempfile.NamedTemporaryFile() as conf_file: - diaspora.generate_apache_configuration(conf_file.name, - 'freedombox.rocks') - - assert os.stat(conf_file.name).st_size != 0 - - with open(conf_file.name) as file_handle: - contents = file_handle.read() - - assert all( - word in contents - for word in ['VirtualHost', 'Location', 'Directory', 'assets']) diff --git a/plinth/modules/diaspora/urls.py b/plinth/modules/diaspora/urls.py deleted file mode 100644 index cf5dd66a6..000000000 --- a/plinth/modules/diaspora/urls.py +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -URLs for the diaspora module -""" - -from django.urls import re_path - -from .views import DiasporaAppView, DiasporaSetupView - -urlpatterns = [ - re_path(r'^apps/diaspora/setup$', DiasporaSetupView.as_view(), - name='setup'), - re_path(r'^apps/diaspora/$', DiasporaAppView.as_view(), name='index') -] diff --git a/plinth/modules/diaspora/views.py b/plinth/modules/diaspora/views.py deleted file mode 100644 index 1c449721e..000000000 --- a/plinth/modules/diaspora/views.py +++ /dev/null @@ -1,80 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Views for the diaspora module -""" - -from django.contrib import messages -from django.shortcuts import redirect -from django.urls import reverse_lazy -from django.utils.translation import gettext as _ -from django.views.generic import FormView - -from plinth.forms import DomainSelectionForm -from plinth.modules import diaspora, names -from plinth.views import AppView - -from .forms import DiasporaAppForm - - -class DiasporaSetupView(FormView): - """Show diaspora setup page.""" - template_name = 'diaspora-pre-setup.html' - form_class = DomainSelectionForm - description = diaspora.app.info.description - title = diaspora.app.info.name - success_url = reverse_lazy('diaspora:index') - - def form_valid(self, form): - domain_name = form.cleaned_data['domain_name'] - diaspora.setup_domain_name(domain_name) - - return super().form_valid(form) - - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(**kwargs) - context['description'] = self.description - context['title'] = self.title - context['domain_names'] = names.components.DomainName.list_names( - 'https') - - return context - - -class DiasporaAppView(AppView): - """Show diaspora service page.""" - form_class = DiasporaAppForm - app_id = 'diaspora' - template_name = 'diaspora-post-setup.html' - - def dispatch(self, request, *args, **kwargs): - if not diaspora.is_setup(): - return redirect('diaspora:setup') - return super().dispatch(request, *args, **kwargs) - - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(**kwargs) - context['domain_name'] = diaspora.get_configured_domain_name() - return context - - def get_initial(self): - """Return the status of the service to fill in the form.""" - status = super().get_initial() - status['is_user_registrations_enabled'] = \ - diaspora.is_user_registrations_enabled() - return status - - def form_valid(self, form): - """Enable/disable user registrations""" - old_registration = form.initial['is_user_registrations_enabled'] - new_registration = form.cleaned_data['is_user_registrations_enabled'] - - if old_registration != new_registration: - if new_registration: - diaspora.enable_user_registrations() - messages.success(self.request, _('User registrations enabled')) - else: - diaspora.disable_user_registrations() - messages.success(self.request, - _('User registrations disabled')) - - return super().form_valid(form) diff --git a/static/themes/default/icons/diaspora.png b/static/themes/default/icons/diaspora.png deleted file mode 100644 index ab98a53ad8df674dfc828afb1e9f2b9d26e0e9f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10630 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 - - - - - - - image/svg+xml - - - - - - - - -