From e523a83f1acf490143c5c280ba5703ba483f0a19 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sun, 15 Dec 2024 09:54:36 -0800 Subject: [PATCH] i2p: Drop app as it has not been available in Debian for a while Closes: #1528. Closes: #2041. Closes: #2438. - In Bullseye and Bookworm the app is not available. - i2pd (written is C++) is available in Debian, but it is not a drop-in replacement for i2p. First, it is only a client. Then has a differently philosophy of not integrating apps inside it and let outside apps connect to it. If i2pd is ever added to FreedomBox, it has to be added as a new app with no possibility of migration from an unlikely old setups. - Updated wiki pages to remove references to I2P. Tests: - Looked for all string references to 'i2p'. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- debian/source/lintian-overrides | 1 - plinth/modules/help/tests/test_views.py | 12 +- plinth/modules/i2p/__init__.py | 122 --------- .../services/i2p-http-proxy-freedombox.xml | 6 - .../services/i2p-https-proxy-freedombox.xml | 6 - .../firewalld/services/i2p-irc-freedombox.xml | 6 - .../conf-available/i2p-freedombox.conf | 30 --- .../usr/share/freedombox/modules-enabled/i2p | 1 - plinth/modules/i2p/helpers.py | 232 ------------------ plinth/modules/i2p/manifest.py | 43 ---- plinth/modules/i2p/privileged.py | 24 -- plinth/modules/i2p/resources.py | 111 --------- plinth/modules/i2p/static/icons/i2p.png | Bin 26374 -> 0 bytes plinth/modules/i2p/static/icons/i2p.svg | 201 --------------- plinth/modules/i2p/templates/i2p.html | 37 --- plinth/modules/i2p/tests/__init__.py | 8 - .../modules/i2p/tests/data/i2ptunnel.config | 192 --------------- plinth/modules/i2p/tests/data/router.config | 23 -- plinth/modules/i2p/tests/test_functional.py | 17 -- plinth/modules/i2p/tests/test_helpers.py | 61 ----- .../modules/i2p/tests/test_router_editor.py | 73 ------ plinth/modules/i2p/urls.py | 12 - plinth/modules/i2p/views.py | 35 --- 23 files changed, 6 insertions(+), 1247 deletions(-) delete mode 100644 plinth/modules/i2p/__init__.py delete mode 100644 plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-http-proxy-freedombox.xml delete mode 100644 plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-https-proxy-freedombox.xml delete mode 100644 plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-irc-freedombox.xml delete mode 100644 plinth/modules/i2p/data/usr/share/freedombox/etc/apache2/conf-available/i2p-freedombox.conf delete mode 100644 plinth/modules/i2p/data/usr/share/freedombox/modules-enabled/i2p delete mode 100644 plinth/modules/i2p/helpers.py delete mode 100644 plinth/modules/i2p/manifest.py delete mode 100644 plinth/modules/i2p/privileged.py delete mode 100644 plinth/modules/i2p/resources.py delete mode 100644 plinth/modules/i2p/static/icons/i2p.png delete mode 100644 plinth/modules/i2p/static/icons/i2p.svg delete mode 100644 plinth/modules/i2p/templates/i2p.html delete mode 100644 plinth/modules/i2p/tests/__init__.py delete mode 100644 plinth/modules/i2p/tests/data/i2ptunnel.config delete mode 100644 plinth/modules/i2p/tests/data/router.config delete mode 100644 plinth/modules/i2p/tests/test_functional.py delete mode 100644 plinth/modules/i2p/tests/test_helpers.py delete mode 100644 plinth/modules/i2p/tests/test_router_editor.py delete mode 100644 plinth/modules/i2p/urls.py delete mode 100644 plinth/modules/i2p/views.py diff --git a/debian/source/lintian-overrides b/debian/source/lintian-overrides index eb7a5c201..792a62ca9 100644 --- a/debian/source/lintian-overrides +++ b/debian/source/lintian-overrides @@ -8,5 +8,4 @@ very-long-line-length-in-source-file * [doc/manual/*.raw.wiki:*] very-long-line-length-in-source-file * [plinth/modules/deluge/tests/data/sample.torrent:*] very-long-line-length-in-source-file * [plinth/modules/transmission/tests/data/sample.torrent:*] very-long-line-length-in-source-file * [doc/visual_design/FreedomBox-Logo.7z:*] -very-long-line-length-in-source-file * [plinth/modules/i2p/tests/data/router.config:*] very-long-line-length-in-source-file * [COPYING.md:*] diff --git a/plinth/modules/help/tests/test_views.py b/plinth/modules/help/tests/test_views.py index 6f4b7e620..56e73602a 100644 --- a/plinth/modules/help/tests/test_views.py +++ b/plinth/modules/help/tests/test_views.py @@ -172,12 +172,12 @@ MANUAL_PAGES = ('Apache_userdir', 'APU', 'Backups', 'BananaPro', 'BeagleBone', 'DateTime', 'Debian', 'Deluge', 'Developer', 'Diagnostics', 'Download', 'DynamicDNS', 'ejabberd', 'Firewall', 'freedombox-manual', 'GettingHelp', 'GitWeb', 'Hardware', - 'I2P', 'Ikiwiki', 'Infinoted', 'Introduction', 'JSXC', - 'LetsEncrypt', 'Maker', 'MatrixSynapse', 'MediaWiki', - 'Minetest', 'MiniDLNA', 'Mumble', 'NameServices', 'Networks', - 'OpenVPN', 'OrangePiZero', 'PageKite', 'pcDuino3', - 'Performance', 'PineA64+', 'PioneerEdition', 'Plinth', 'Power', - 'Privoxy', 'Quassel', 'QuickStart', 'Radicale', 'RaspberryPi2', + 'Ikiwiki', 'Infinoted', 'Introduction', 'JSXC', 'LetsEncrypt', + 'Maker', 'MatrixSynapse', 'MediaWiki', 'Minetest', 'MiniDLNA', + 'Mumble', 'NameServices', 'Networks', 'OpenVPN', + 'OrangePiZero', 'PageKite', 'pcDuino3', 'Performance', + 'PineA64+', 'PioneerEdition', 'Plinth', 'Power', 'Privoxy', + 'Quassel', 'QuickStart', 'Radicale', 'RaspberryPi2', 'RaspberryPi3B+', 'RaspberryPi3B', 'RaspberryPi4B', 'ReleaseNotes', 'Rock64', 'RockPro64', 'Roundcube', 'Samba', 'Searx', 'SecureShell', 'Security', 'ServiceDiscovery', diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py deleted file mode 100644 index 4116357f3..000000000 --- a/plinth/modules/i2p/__init__.py +++ /dev/null @@ -1,122 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -"""FreedomBox app to configure I2P.""" - -from django.utils.translation import gettext_lazy as _ - -from plinth import app as app_module -from plinth import frontpage, menu -from plinth.config import DropinConfigs -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.i2p.resources import FAVORITES -from plinth.modules.users.components import UsersAndGroups -from plinth.package import Packages - -from . import manifest, privileged - -_description = [ - _('The Invisible Internet Project is an anonymous network layer intended ' - 'to protect communication from censorship and surveillance. I2P ' - 'provides anonymity by sending encrypted traffic through a ' - 'volunteer-run network distributed around the world.'), - _('Find more information about I2P on their project ' - 'homepage.'), - _('The first visit to the provided web interface will initiate the ' - 'configuration process.') -] - -tunnels_to_manage = { - 'I2P HTTP Proxy': 'i2p-http-proxy-freedombox', - 'I2P HTTPS Proxy': 'i2p-https-proxy-freedombox', - 'Irc2P': 'i2p-irc-freedombox' -} - - -class I2PApp(app_module.App): - """FreedomBox app for I2P.""" - - app_id = 'i2p' - - _version = 3 - - def __init__(self) -> None: - """Create components for the app.""" - super().__init__() - - groups = {'i2p': _('Manage I2P application')} - - info = app_module.Info(app_id=self.app_id, version=self._version, - name=_('I2P'), icon_filename='i2p', - short_description=_('Anonymity Network'), - description=_description, manual_page='I2P', - clients=manifest.clients, tags=manifest.tags) - self.add(info) - - menu_item = menu.Menu('menu-i2p', info.name, info.short_description, - info.icon_filename, 'i2p:index', - parent_url_name='apps') - self.add(menu_item) - - shortcut = frontpage.Shortcut('shortcut-i2p', info.name, - short_description=info.short_description, - icon=info.icon_filename, url='/i2p/', - clients=info.clients, - login_required=True, - allowed_groups=list(groups)) - self.add(shortcut) - - packages = Packages('packages-i2p', ['i2p']) - self.add(packages) - - dropin_configs = DropinConfigs('dropin-configs-i2p', [ - '/etc/apache2/conf-available/i2p-freedombox.conf', - ]) - self.add(dropin_configs) - - firewall = Firewall('firewall-i2p-web', info.name, - ports=['http', 'https'], is_external=True) - self.add(firewall) - - firewall = Firewall('firewall-i2p-proxies', _('I2P Proxy'), - ports=tunnels_to_manage.values(), - is_external=False) - self.add(firewall) - - firewall_local_protection = FirewallLocalProtection( - 'firewall-local-protection-i2p', ['7657']) - self.add(firewall_local_protection) - - webserver = Webserver('webserver-i2p', 'i2p-freedombox', - urls=['https://{host}/i2p/']) - self.add(webserver) - - daemon = Daemon('daemon-i2p', 'i2p', listen_ports=[(7657, 'tcp6')]) - self.add(daemon) - - users_and_groups = UsersAndGroups('users-and-groups-i2p', - groups=groups) - self.add(users_and_groups) - - backup_restore = BackupRestore('backup-restore-i2p', **manifest.backup) - self.add(backup_restore) - - def setup(self, old_version): - """Install and configure the app.""" - super().setup(old_version) - - if not old_version: - self.disable() - # Add favorites to the configuration - for fav in FAVORITES: - privileged.add_favorite(fav['name'], fav['url'], - fav.get('description'), - fav.get('icon')) - - # Tunnels to all interfaces - for tunnel in tunnels_to_manage: - privileged.set_tunnel_property(tunnel, 'interface', '0.0.0.0') - - self.enable() diff --git a/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-http-proxy-freedombox.xml b/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-http-proxy-freedombox.xml deleted file mode 100644 index 7b6cdd383..000000000 --- a/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-http-proxy-freedombox.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - I2P HTTP Proxy - The Invisible Internet Project is an anonymous network layer intended to protect communication from censorship and surveillance. I2P provides anonymity by sending encrypted traffic through a volunteer-run network distributed around the world. Enable this if a running an I2P server with HTTP proxy and wish to connect into the I2P network to browse eepsites. - - diff --git a/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-https-proxy-freedombox.xml b/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-https-proxy-freedombox.xml deleted file mode 100644 index d4c1e8836..000000000 --- a/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-https-proxy-freedombox.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - I2P HTTPS Proxy - The Invisible Internet Project is an anonymous network layer intended to protect communication from censorship and surveillance. I2P provides anonymity by sending encrypted traffic through a volunteer-run network distributed around the world. Enable this if a running an I2P server with HTTPS proxy and wish to connect into the I2P network to browse eepsites. - - diff --git a/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-irc-freedombox.xml b/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-irc-freedombox.xml deleted file mode 100644 index 21aeb2fcc..000000000 --- a/plinth/modules/i2p/data/usr/lib/firewalld/services/i2p-irc-freedombox.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - I2P IRC Tunnel - The Invisible Internet Project is an anonymous network layer intended to protect communication from censorship and surveillance. I2P provides anonymity by sending encrypted traffic through a volunteer-run network distributed around the world. Enable this if a running an I2P server with IRC tunnel and wish to access the Irc2P network. - - diff --git a/plinth/modules/i2p/data/usr/share/freedombox/etc/apache2/conf-available/i2p-freedombox.conf b/plinth/modules/i2p/data/usr/share/freedombox/etc/apache2/conf-available/i2p-freedombox.conf deleted file mode 100644 index 54f3c45f2..000000000 --- a/plinth/modules/i2p/data/usr/share/freedombox/etc/apache2/conf-available/i2p-freedombox.conf +++ /dev/null @@ -1,30 +0,0 @@ -## -## On all sites, provide I2P on a default path: /i2p -## -## Requires the following Apache modules to be enabled: -## mod_headers -## mod_proxy -## mod_proxy_http -## mod_proxy_html -## - - # Disable compression - # As soon as it has to be chunked, it doesn't work - RequestHeader unset Accept-Encoding - - ProxyPass http://localhost:7657 - ProxyPassReverse http://localhost:7657 - - # Rewrite absolute urls from i2p to pass through apache - ProxyHTMLEnable On - ProxyHTMLURLMap / /i2p/ - - Include includes/freedombox-single-sign-on.conf - - TKTAuthToken "admin" "i2p" - - - -# Catch some other root i2p addresses -# These are most likely generated by javascript -RedirectMatch "^/(i2p[^/]+.*)" "/i2p/$1" diff --git a/plinth/modules/i2p/data/usr/share/freedombox/modules-enabled/i2p b/plinth/modules/i2p/data/usr/share/freedombox/modules-enabled/i2p deleted file mode 100644 index 77c98ea19..000000000 --- a/plinth/modules/i2p/data/usr/share/freedombox/modules-enabled/i2p +++ /dev/null @@ -1 +0,0 @@ -plinth.modules.i2p diff --git a/plinth/modules/i2p/helpers.py b/plinth/modules/i2p/helpers.py deleted file mode 100644 index a6b3570da..000000000 --- a/plinth/modules/i2p/helpers.py +++ /dev/null @@ -1,232 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Various helpers for the I2P app. -""" - -import os -import re -from collections import OrderedDict - -import augeas - -I2P_CONF_DIR = '/var/lib/i2p/i2p-config' -FILE_TUNNEL_CONF = os.path.join(I2P_CONF_DIR, 'i2ptunnel.config') -TUNNEL_IDX_REGEX = re.compile(r'tunnel.(\d+).name$') -I2P_ROUTER_CONF = os.path.join(I2P_CONF_DIR, 'router.config') - - -class TunnelEditor(): - """Helper to edit I2P tunnel configuration file using augeas. - - :type aug: augeas.Augeas - - """ - def __init__(self, conf_filename=None, idx=None): - self.conf_filename = conf_filename or FILE_TUNNEL_CONF - self.idx = idx - self.aug = None - - @property - def lines(self): - """Return lines from configuration file.""" - if self.aug: - return self.aug.match('/files{}/*'.format(self.conf_filename)) - - return [] - - def read_conf(self): - """Load an instance of Augeaus for processing APT configuration. - - Chainable method. - - """ - self.aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + - augeas.Augeas.NO_MODL_AUTOLOAD) - self.aug.set('/augeas/load/Properties/lens', 'Properties.lns') - self.aug.set('/augeas/load/Properties/incl[last() + 1]', - self.conf_filename) - self.aug.load() - - return self - - def write_conf(self): - """Write changes to the configuration file to disk. - - Chainable method. - - """ - self.aug.save() - return self - - def set_tunnel_idx(self, name): - """Finds the index of the tunnel with the given name. - - Chainable method. - - :type name: basestring - - """ - for prop in self.aug.match('/files{}/*'.format(self.conf_filename)): - match = TUNNEL_IDX_REGEX.search(prop) - if match and self.aug.get(prop) == name: - self.idx = int(match.group(1)) - return self - - raise ValueError('No tunnel called {}'.format(name)) - - def calc_prop_path(self, tunnel_prop): - """Calculates the property name as found in the properties files. - - :type tunnel_prop: str - :rtype: basestring - - """ - calced_prop_path = \ - '/files{filepath}/tunnel.{idx}.{tunnel_prop}'.format( - idx=self.idx, - tunnel_prop=tunnel_prop, - filepath=self.conf_filename) - return calced_prop_path - - def set_tunnel_prop(self, tunnel_prop, value): - """Updates a tunnel's property. - - The idx has to be set and the property has to exist in the config file - and belong to the tunnel's properties. - - See calc_prop_path. - - Chainable method. - - :param tunnel_prop: - :type tunnel_prop: str - :param value: - :type value: basestring | int - :return: - :rtype: - - """ - if self.idx is None: - raise ValueError( - 'Please init the tunnel index before calling this method') - - calc_prop_path = self.calc_prop_path(tunnel_prop) - self.aug.set(calc_prop_path, value) - return self - - def __getitem__(self, tunnel_prop): - ret = self.aug.get(self.calc_prop_path(tunnel_prop)) - if ret is None: - raise KeyError('Unknown property {}'.format(tunnel_prop)) - - return ret - - def __setitem__(self, tunnel_prop, value): - self.aug.set(self.calc_prop_path(tunnel_prop), value) - - -class RouterEditor(): - """Helper to edit I2P router configuration file using augeas. - - :type aug: augeas.Augeas - - """ - - FAVORITE_PROP = 'routerconsole.favorites' - FAVORITE_TUPLE_SIZE = 4 - - def __init__(self, filename=None): - self.conf_filename = filename or I2P_ROUTER_CONF - self.aug = None - - def read_conf(self): - """Load an instance of Augeaus for processing APT configuration. - - Chainable method. - - """ - self.aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + - augeas.Augeas.NO_MODL_AUTOLOAD) - self.aug.set('/augeas/load/Properties/lens', 'Properties.lns') - self.aug.set('/augeas/load/Properties/incl[last() + 1]', - self.conf_filename) - self.aug.load() - return self - - def write_conf(self): - """Write changes to the configuration file to disk. - - Chainable method. - - """ - self.aug.save() - return self - - @property - def favorite_property(self): - """Return the favourites property from configuration file.""" - return '/files{filename}/{prop}'.format(filename=self.conf_filename, - prop=self.FAVORITE_PROP) - - def add_favorite(self, name, url, description=None, icon=None): - """Add a favorite to the router configuration file. - - Favorites are in a single string and separated by ','. none of the - incoming params can therefore use commas. I2P replaces the commas by - dots. - - That's ok for the name and description, but not for the url and icon. - - :type name: basestring - :type url: basestring - :type description: basestring - :type icon: basestring - - """ - if not description: - description = '' - - if not icon: - icon = '/themes/console/images/eepsite.png' - - if ',' in url: - raise ValueError('URL cannot contain commas') - - if ',' in icon: - raise ValueError('Icon cannot contain commas') - - name = name.replace(',', '.') - description = description.replace(',', '.') - - prop = self.favorite_property - favorites = self.aug.get(prop) or '' - new_favorite = '{name},{description},{url},{icon},'.format( - name=name, description=description, url=url, icon=icon) - self.aug.set(prop, favorites + new_favorite) - return self - - def get_favorites(self): - """Return list of favorites.""" - favs_string = self.aug.get(self.favorite_property) or '' - favs_split = favs_string.split(',') - - # There's a trailing comma --> 1 extra - favs_len = len(favs_split) - if favs_len > 0: - favs_split = favs_split[:-1] - favs_len = len(favs_split) - - if favs_len % self.FAVORITE_TUPLE_SIZE: - raise SyntaxError("Invalid number of fields in favorite line") - - favs = OrderedDict() - for index in range(0, favs_len, self.FAVORITE_TUPLE_SIZE): - next_index = index + self.FAVORITE_TUPLE_SIZE - name, description, url, icon = favs_split[index:next_index] - favs[url] = { - 'name': name, - 'description': description, - 'icon': icon - } - - return favs diff --git a/plinth/modules/i2p/manifest.py b/plinth/modules/i2p/manifest.py deleted file mode 100644 index 8e4cdda96..000000000 --- a/plinth/modules/i2p/manifest.py +++ /dev/null @@ -1,43 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Application manifest for I2P. -""" - -from django.utils.translation import gettext_lazy as _ - -_package_id = 'net.geti2p.i2p' -_download_url = 'https://geti2p.net/download' - -clients = [{ - 'name': - _('I2P'), - 'platforms': [{ - 'type': 'web', - 'url': '/i2p/' - }, { - 'type': 'package', - 'format': 'deb', - 'name': 'i2p', - }, { - 'type': 'download', - 'os': 'gnu-linux', - 'url': _download_url, - }, { - 'type': 'download', - 'os': 'macos', - 'url': _download_url, - }, { - 'type': 'download', - 'os': 'windows', - 'url': _download_url, - }] -}] - -backup = { - 'secrets': { - 'directories': ['/var/lib/i2p/i2p-config'] - }, - 'services': ['i2p'] -} - -tags = [_('Anonymity network'), _('Censorship resistance')] diff --git a/plinth/modules/i2p/privileged.py b/plinth/modules/i2p/privileged.py deleted file mode 100644 index 9220bd698..000000000 --- a/plinth/modules/i2p/privileged.py +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -"""Configure I2P.""" - -from plinth.actions import privileged -from plinth.modules.i2p.helpers import RouterEditor, TunnelEditor - - -@privileged -def set_tunnel_property(name: str, property_: str, value: str): - """Modify the configuration file for a certain tunnel.""" - editor = TunnelEditor() - editor \ - .read_conf() \ - .set_tunnel_idx(name) \ - .set_tunnel_prop(property_, value) \ - .write_conf() - - -@privileged -def add_favorite(name: str, url: str, description: str | None, - icon: str | None): - """Add a favorite to router.config.""" - editor = RouterEditor() - editor.read_conf().add_favorite(name, url, description, icon).write_conf() diff --git a/plinth/modules/i2p/resources.py b/plinth/modules/i2p/resources.py deleted file mode 100644 index 2796be978..000000000 --- a/plinth/modules/i2p/resources.py +++ /dev/null @@ -1,111 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Pre-defined list of favorites for I2P and some additional favorites. -""" - -DEFAULT_FAVORITES = [{ - 'name': 'anoncoin.i2p', - 'description': 'The Anoncoin project', - 'icon': '/themes/console/images/anoncoin_32.png', - 'url': 'http://anoncoin.i2p/' -}, { - 'name': 'Dev Builds', - 'description': 'Development builds of I2P', - 'icon': '/themes/console/images/script_gear.png', - 'url': 'http://bobthebuilder.i2p/' -}, { - 'name': 'Dev Forum', - 'description': 'Development forum', - 'icon': '/themes/console/images/group_gear.png', - 'url': 'http://zzz.i2p/' -}, { - 'name': 'echelon.i2p', - 'description': 'I2P Applications', - 'icon': '/themes/console/images/box_open.png', - 'url': 'http://echelon.i2p/' -}, { - 'name': 'exchanged.i2p', - 'description': 'Anonymous cryptocurrency exchange', - 'icon': '/themes/console/images/exchanged.png', - 'url': 'http://exchanged.i2p/' -}, { - 'name': 'I2P Bug Reports', - 'description': 'Bug tracker', - 'icon': '/themes/console/images/bug.png', - 'url': 'http://trac.i2p2.i2p/report/1' -}, { - 'name': 'I2P FAQ', - 'description': 'Frequently Asked Questions', - 'icon': '/themes/console/images/question.png', - 'url': 'http://i2p-projekt.i2p/faq' -}, { - 'name': 'I2P Forum', - 'description': 'Community forum', - 'icon': '/themes/console/images/group.png', - 'url': 'http://i2pforum.i2p/' -}, { - 'name': 'I2P Plugins', - 'description': 'Add-on directory', - 'icon': '/themes/console/images/info/plugin_link.png', - 'url': 'http://i2pwiki.i2p/index.php?title=Plugins' -}, { - 'name': 'I2P Technical Docs', - 'description': 'Technical documentation', - 'icon': '/themes/console/images/education.png', - 'url': 'http://i2p-projekt.i2p/how' -}, { - 'name': 'I2P Wiki', - 'description': 'Anonymous wiki - share the knowledge', - 'icon': '/themes/console/images/i2pwiki_logo.png', - 'url': 'http://i2pwiki.i2p/' -}, { - 'name': 'Planet I2P', - 'description': 'I2P News', - 'icon': '/themes/console/images/world.png', - 'url': 'http://planet.i2p/' -}, { - 'name': 'PrivateBin', - 'description': 'Encrypted I2P Pastebin', - 'icon': '/themes/console/images/paste_plain.png', - 'url': 'http://paste.crypthost.i2p/' -}, { - 'name': 'Project Website', - 'description': 'I2P home page', - 'icon': '/themes/console/images/info_rhombus.png', - 'url': 'http://i2p-projekt.i2p/' -}, { - 'name': 'stats.i2p', - 'description': 'I2P Network Statistics', - 'icon': '/themes/console/images/chart_line.png', - 'url': 'http://stats.i2p/cgi-bin/dashboard.cgi' -}, { - 'name': 'The Tin Hat', - 'description': 'Privacy guides and tutorials', - 'icon': '/themes/console/images/thetinhat.png', - 'url': 'http://secure.thetinhat.i2p/' -}, { - 'name': 'Trac Wiki', - 'description': '', - 'icon': '/themes/console/images/billiard_marker.png', - 'url': 'http://trac.i2p2.i2p/' -}] -ADDITIONAL_FAVORITES = [ - { - 'name': 'Searx instance', - 'url': 'http://ransack.i2p' - }, - { - 'name': 'Torrent tracker', - 'url': 'http://tracker2.postman.i2p' - }, - { - 'name': 'YaCy Legwork', - 'url': 'http://legwork.i2p' - }, - { - 'name': 'YaCy Seeker', - 'url': 'http://seeker.i2p' - }, -] - -FAVORITES = DEFAULT_FAVORITES + ADDITIONAL_FAVORITES diff --git a/plinth/modules/i2p/static/icons/i2p.png b/plinth/modules/i2p/static/icons/i2p.png deleted file mode 100644 index 63e9aacfb3fdb26c85ebc0409a8b0069893f0369..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26374 zcmd>_^Pwl}!4e){H zEvIUL1^fhIy?FWEQ##C>H4nwS^Ic2ZeV&CJH9ZjO_1_wj`uaKdRGz)9{;o z!`rS6KfNtsef1(+{Zhj(AF@1odw#;AT3*wm|I=c7SARPX)#g3_F!}$lzvKs@VLZe+ zVZzp|^^nXmEsAO_($B6JXFh`%!BsJVFI(qKPS5p@IlES&G#Z&_qL zA~+AhmL^3%U}y(M*zV&kM=4mQMSm#^TQlc>3?axBlUYB8DrH%Q|9}~vF<`lA>UK5O zCHCOB%(@hnE;uedXZ9D_k$rj|r4?MG`}HJ2R!23J!eOU7@v-qx?xhSPKk8JIlEucT5F@pgU-v-YDT))41GO$*1aZ%z`*1n8lvLp+U)q?=dK|JsRS)XJCNcU~W`%1U*|@!a`7DkGs@t8Bli%wO&GSx4zWYDFH9Pdv03N2hV)ebf>5i@E?Pv?S67$#`muX+t_C*Lx#%B6i?pz>ZvZH-q^uS zhRZLVb+wz!#NF5csxt@@W*JkTlUk`Zxw9B!l?)2C8`Q)W;&Zx2DCt&q9lM?Qz(vd~;xE zTHZ8e)HsiDnZ2X*w5&a`DpZ*@yuQh*gSm944iwR{0z2$l^KauZyC*g&UoGsjiMI}4 zK_qHz@E<*BShTK#JUVbP)0P&UHz$D{Y;9|9pmjMBzIa-J)`E;h2e{%(5)`mJ}@1o57G{=4u>YN^kGIX(AAf!ajr7? z6`#<;P0DvD29Cm9G7MpngAAXvd5or!$7@DEe#t2&xw><$tU*+$UqHN-8G@>$&Vvf{^pDg>VP!W{3Wv-n8>^=P@Q0T6cqv3f&$=rCG??J0Ycqc9JR- z6fVE$cq0NATTdz5H4~b;0*^nzK15FaW@(*dAp^;K-yNd(<}1{fLRMRE*S5aX60`?ePGjTxRQYAzRqIkd_s*-ufcP(TAoq_0LY87{k)AzxME1 z7UMS5VdKNI`=u!KFeUBso80(z9}eS}{LWGiP+vYUrQrh3oRX$%>UH z)JTZ3AvWFQ_H1lhk}e0$0>3&|Pt2o@&@e+MR<0BhqR1jtxLO+H27fO{d>vBLBAj?B zUv>sEm$RkS&i1kA+W!ktVEH#A=ut(Q4zqQ=_SHY4fb(;{+ETM)yx{@vV&PNEB9@gB zjgAyNdtIu?Vm2?I&X%rW=sUGhhf^x_x@=F~jjY^q(E}cY=Mzw(QGInR7=1dd#=R4t z->_P+GU~i(VQt&^{7RZnNRsEOczzzVc)Xu}h~Gu$pFTYtU6^38{Z6$I_kpU1pvN$R z_(t;1+}P&nSuG9pf@|ak9)~|*tbUbkdSZnBxs%O%xoW5{i>(Xehe~WX987b20@_Z(YTIxI^<_bAA!*wR%Md7kfQN_|%+lUOMS_8&?%Wz@w@BJjxmbPJHNk(fXBGJ6JM7XPfr=_ZNBaC#+!*~A z-RP$QSGVo~m<~TQqZ#Av2;u#OjyN!ADLw9#m$Am7v;^Pn$BNMG0(?x0=n&h=l$YZx{LGX$N z`^)mVXD6!6EBig?kDD(gI?)-S!Mdn~n>13(7!FeitUd3%A0D&9_~l&`8t`JCAM3m1 zMQS+Ml7Vgd!DO!T__XbOQx1|Kq1LWF=C|I|Mzz)$)wk^vA3y>*f&OVoLKgk=Kaxh8 z>(icDN^Q@fE3+8%qdk>uBeJR-)H@I*)KYz1H*absE)ae^hsMm`+9Mn|gBrh$Qb6e7 zC-<*>e+Kn^KeNEdlmfS^*r=f-zR?aMMj1M&CB?D=hJh@X=lJ%kQ>4+*LjKi zC|TP1$seg-&HYS!syvvSckJIzrj2EAUfQ3atg9at>b5kVkkQ#v|7 zxiA`20xM84iWs!|Snu6Jb6n(!x8LO|pB6e1)km4?TIQ>LHDE^LGd|cupGcTw0?Kmz zdCTeAbF$WTox(57=qnM_)W1MT=Xzh8DDf7|kEz-b0!LA^-gXgBy*nihI9`d@J(PRMD1iOsGsiXM=exyB!voS?{nIrhqv&wN&W(&?bGe zw{@M^-$Nq)LX1lGmh~Z9;pGNICUvy_@^};#v76P%yzB)GkI8q%^6AC-F##|iFn(VJ zt~{~kL!hYUu^5~pRk2HyHXgV!S%MwS7C=`LV3&9itzsT;nnLF4kj?ia-lyA*{m^&V zalgc(;QeG(c>PS>>Y6mtS&`Pl_%Ef>Z`sT8f9*X`C>t5n|?D zqjkU(ZJUF}=&1-oeoHxnzU#q>)L5(&u|XXz2|~%8TDtMPH+0}2 ziu#v^5mJ12lO-e|^^Lex|6VVeLW9#k4fsTzeEl~oViJ20g(`*pMhqGH6=FH65>y#r zB7N?U`)UdrpQ<26=S#zcf*~?{ggEN`1p^VjM7iT{QTcYR4@dc4BJG{uC!~02LH}?T z<-mW(ao(d6_l2KlD9~br-SE>H6ddZ6|5Zq_`#M5!(+!Sdu74)7JkzbIJ^FG?bcI;H zfEfvX7=yn161DbY?yXHRnq{|pqlppY4PsE*q*LMSAcM5`k0aMyV1r~E#c{a)rx+POx z#&hw_|4JfDx1YW;_`F4E>B$Tn@5CD}M!mqNa>K^-=rxBbV@+~yv1Pdd>3hT8{}U$A z#^|xk+j(W!grdW}5`)R5W%Mn<)m5=KU?GflcNhJ@JO67sW4|j*KqIDj)el%d7GqXg z7s(G*wX8nB&SJhzM@-6mS^H8qM7J@vfS%qgMiRB^OY{20!XgEz2MLK~dgNW-9owy?a4y!vgNxP6zaahI1cW6Kqs+9%HM%&U9HsL?9azS5`%*JV zyIyAa2+mRsj8*oM{OM>W54cUNrN7sXkl2JMFA;a+9AB>4#G9`}Q1 zV<%^UA`^CP)ZKm&4@1x-ay&WzwU>{gZYcEA)nU@rKE+X#Xi{5{L4&w@RYB2NU=NEI zeo1uBsmb>D9Y@aoUs?jtB|bRfJ@=z|UENW-9V#2T4^cKpZ6iDm^6;Mv^n{$B4qcpR zQCuwo&FT_>`nD^RN6@TTkBs3AoD69cckZ%>KKV=-lkO{`H{Cp|`(K_Q6}PB?aD<*R zk3)~DWJe^e<0Hg>skSfFS8bK)+2flNsOsa_2=i@-GOm$3>v{Tdi1h%fOyLvJy4N34 z9mS4pftDRT8Vj@pb4!e(i~D1@f7s+B>C;oc5g*F6PeYTO^tP%s?CR}?N$WlcITP1a zIghY=;q55G-G$%#fEKUVdvt5j8Dbg{G+Gh-dN+ zAkdurBx9e$_2j=q$E=MM34c0ZKY}MBzj_1VaZ}gXs^;H|w&;ig@jId~%?R{eBtC-O z$yXBdTFLT;HSe{V9tz)P+MEj6HP$EMqxCAIt?zc2bcD(FLhm&XC!1n~&J*7xT=-&O zecd>AVW@t3=15MvwPlLOlBM-%T=|2e%^0fMOQ!vc+qmLP5+|SsNo{zv$Q#hB^PM2W z9E4R1UO{=#YNZ?+LS4~Pe7nGck^Yu@RVM|0)kd zRJybX2MU8M3FqaHiBr;HjRsT8VmCncS)9{R`bdS^d?qf1H*~4HPEN()hr|g)=|wRe z3w9k?LgUVWkmFUW$@iFrV0g6wdD5AjOk1d#ndWNeBIo~PRoR2vg0cn%Qd!@KTg_s- zNNt-PQKt3we!v>JgT8Lyj0K>K1$e ziS3%<_bZ2aVp_3e4={1q1$FUKhJqOKR3Be=Sw2`=;$ji-R2;{01U2gTqaQsfe!Tp) z)%Sb-P!T#~kezXe55r7?<^wrws5((kNnIkDxxHsmV@g11VZgHiAGnEHxr@PvXN!yR z%@9#Ha}(DEnl{p3)6fhr_9c?Yn zjAbyNOGPN=id@s1dIF4a?I%Iiv{a$*NU^^+#jt@^`*K$Zx-#47 zpc5XLfa6%q64^USj1$jkhCDQRHb8`$#?>m#rFmj7B=|-eaJ%Pw_RoZA~nb=O(HAeiEcl2{i;TTTa)0HJsCJS1Opmnk0c0H@A0G)j3k4ne0?kx_ zBa&emLIfI6c@i3nTE1sj+|wuhu;*IZL_dxn%QmRvGLc%{>ITn^fBp3Q2<;P@2Q(=w zdKaHOmT3t5D87(AGrVgSN@{SZRdI&<>C-5A?0Y-PSF;?u z>XJnpgnA`->=#%^aupu4R`#2_SunX{uLEcIE4n1zSzp^_-A?e+9mU_IAvBjg!5P!T zl4#dx(=OF*?ztBrIOh13ZiNpl@$Kr$f=PD6hw^05HqQRj(*qGycsUlY35zqQ=^JBc zNW;L`yK;UWjD_k>t`Oy>!ml)Xonxy;%U-rOc?Af0_D>*7{jKG0Ec4EGhOQvXH2ADq zK63AmjO>K$7M8UsiC;6y700WqA1)cv2A$whaGH#k{;!Jfoo0AnnBScGoqU0DTD^a5Vy#eO8TK`s$_Xf) zI7`?|%ty(9^orZqk=q@~Cn_6tNp!wvAXm{BTGX%U>Kdv3H^kHS{Cv8_kx$fAUJPUU zzDk2&31ceuBO;@TICN=Y?~wYE`QQ1654U3X_p;wi>a{Och(!lRBc#2*{Lv;^D5n5~ z<-@Ykb;7r;eOz2kzp0PZqjSB^?!PP~TO|cY^Cxz6Sz-c`IE&LuQq;ta-uK&a1q+vG z(`r$@89Ub5EU<3FyGRGF z=})sf#=~G_n-tNZ>=LB($-b(Gt2WrKEx6+IyLhLJ$XMLy7xpi* zm!fCAnP46nrg|_R7B0v+T+3Rl9@iiBDzc9ivjLeiz>U!CqgB{;K}lh`xy_cLe5vd+ z;nmluu>DsR!5{Z9F{qO6Q`9HLS=B8|#@Im^k+18zS$5hYeDmpyYwfg>Tlr0P)KKr6 z7k4I6#fz)O51U^5SXIH;J`D9b(-w*mMh5m^0rCzh&M#Wb`*OAvjAQBRk9B{y8m>=& zBQ8WLrNy}fgkJ=%hRqdF<~K&8nxvQ%Hik9fTF{Z;6i%X!p%>^4tonUhm}nCiF#@q` zU?zc9P`DYLBv_qkj>Hit2>du4us%A@XUr8$YK^%o5seUi2`!(A1B?T*+E-OEa^V(o z5hb##I45@27sSHwguTf4C1vuJ>6M8+wJpKtx@8QjX^=dfWSN&}`(n{QTxGU&73i?A z4qco#j&X(rnS6F?yVK`xW$`hVlPmLcYuUoe7?#p14l}WGxrn_r44;FDFTh$DNsKo! zR(QRbZm2M_ow^yRy%;%64;&BFdgFB8M#?r|uCykrtTKgZxbv1KrO``Zr$>er;&gIE zti+GLikWw6(^T|p)2yo(Z-HY3M~)o zWP$T9E1QRziifM#{9TpWZKWkjp$EMOt;d_@3;y^J1O)LkJe~ibm6d%TCl4H;x*bPFU{yug;B(E%J#5bRdQO9ICzuWHdh9<)YbWy zVD0h6k{S6m`f+Mlgzw1W+qlSGC&jEq|NVzq#Jn;OvlrV~Zw z%VftPoUkILP3J!4Dy_?nHxl4HmLmaxd#IG0J~AMl7WW6K))QT^dVC;pFB#i@n}$(U zqq95$&Bz>;SL6tJ?Zy=f&1a6oLC=wkcn-Elw2aaoC@M}k0wVbzfQLXGW<4n_lK-?% zym%upy7j&3iC1c_*RjzDKz;9j)rxw`)zHoP5;e%YczOau#{r{9TTqxOt^h5G=*kKh zgdE8?DCVb+m441&BKd2Se^(B?{XKu=g~an$$&+WmaJ_P7V0f}gf{ta#ZcO`(H;08zDQ6G_{>i8G5tm;O*$p>sL-m$<7FL1%b@ga%O#x{?ig>9sA!|5f;yQ8b| zL{Al!axbx`%owuK7x}12CVr@{rl&3Ul|BJQ6MG0vJ9t{*f)1axO+pEciC5(3t_~3p zbqZd#j&%LZRZipcdS?i3h-}kE#Pb3yEP~JQe!J?@lW2rm)U3HZ$5&Glv5xL$|70XR z%*Ww$k@y~aIzX`J%8e}elLFRQ@LDM#dXW;e)A(<{u{`{xgkX+4fs;fNfZ-{3D>>8L zC|X2&&U+?)BA`FyGFegR2^F~5M{9Uo!InisUA+$5uBx6h>AI} z6t?*v$O!Q=J~i7%tKAz1s7PRipg&>O24sxb$B|#6usgp3Xfu)%@7nD;lwP@Q;sts& zCtIt1ytEe$Gp~uOgh>h=!UMWgrPn5|yc$S^p&qPh(z~zI`dHV8`5g~Xi^G_ZYrbIv z4GK?KSZsZV4T7{08$edJIOP;V0Pbv?&_kJY;9u&UHzqRpM^*@80~UgeiPKA7WUpJm zA`Q%9g=wosEv|kGKxQ;8&UnfPW!tudv5a-75~UR)y$*DKEkaYA^hD9x3KkshP_$+p z5RN}g8)Gt35K>Mh|3Mm$$-STBUNogSUeO4o(99LM)ZlD5s6eD0Ur&=};Y?f2L%ezV z3Y?cxc3e~T;PFiogfl`IeEV5wEIgRmRI$DsmuL6Z-WrDFCLu+Q#a=pus0jIxY_dgk z!J$cN&)RrrNeh4sS?C`FQaG1BZhP_;m#bVP>htFzmZ;q3c3a2|TQTPJ&!;43-T5<* zvvDEyrS8#`nz3zm-y2dLQG~8K9evy~bpnAi>Rr?{jqEiumJ)3w1ri60qCclqLjcDy zE$W-74v=hyUxD|(c&6Q{G!KV}D3&XOADrrrF$q(UzH?&F9^9Uhg5)pn-5FMzlQ(?# zbqJJs#4ttYpzk>MKt0<4)7U!g@^bj_DW|M>m512r&pbx8N@W!l&GQj+3r6KXo%$6W zNxG_k1w(PqB!Ny7|_)Bpl)vj@GI_F?u5k1Er-?a=)5@kOLhsoV|z{Sd| zbqZA#pZn%BvDV?`M&>vOVq)`_Wdyp!=w2Bmw7kC_@-3zfds3MO7TVO8PaZsB|(*7vE~&NO<-XP$8J3GOt3c+S&WJ!)O3`W?C5s-SVFv(&RkmOnje$q~_0A zlsTTYx!=e_50euQ;W;(K##6YQ2)=j=N1+x~eiefxOfzIJO)3$_WJ=r?x-R&b%oT?^ z-5NT*V3qtjivA-)0?trgekJB)ujW%VG3sD9SC%*;PH4Y&tO&)?GSsdKi0%e`GN#Y1 zZSPYB=t>Od)A*2KC_dZVs+n7L|L#?{+{PyRNZsCXV`h_lu3rfNM_L{5eqA+8KfD5z zuU8QNbHW6*1(YM`AoBN|6|{561P0bn^7FgV2{-PXSc^H>-jEZhE;YF8`74ON>qG8; z-N_Yz(cFm8HX*)d?g(DwGqUkyp3+-ZiStbj5U%iZpf|6poe`x7td4Col*88c$?xaD z*7Be!GGAWal@EIh^A`2#;^crce1EznIRV8rM)S=c%lm6r^WG5Te_2#pzkK|e|1H`1 zFP3(m&nvA!F#D{GmF%7)eW0N_B$$iqX~4NuO$5!8=rcy3jSpqJI!+#I^ermy(=JTh~+)yw|R(txY3)uK}l44=hT#u%itYv?=LH5C)}B)gW!c z=I+mRr!EDGf6>-qB4jGBhg8%&>ye-`N~bf@e-z|2OcD?kv#Ui88>(^S+>_*uMQ-&L zYl<_J?%JhL*#q7IIy|2}ujXgE2QolvT`|}&ynvkB<3)8B2^;NJhbh9qAJqmw)&4By zOfa`+Pj{#vg~ll8_#XZvVm2R(fs{%=bk;?gq&Okw@of_-g6CL4wJXd)mxk__SYR9( z`u^0H>d$}>M&Ywc!gZJB#a-;~o6o=zU)m)~8lT_DgZBH+ji@O~y|IQ#5Mr#_xM%Zy zB-!C=4CCj4l4Bf0Vb=w2(l&fu%Boq8+(rGlG-Q&0YU7R1;5k3}aWc&>m6J7zcJ_uT zXc(GFp;T=&VflU@9#hWUMniy5V_3fVwo1H=(DAm*yXRsXM<*8NW%d>CyL1-97c!oM zzwdW-zrnaVYBM=6YkMDynW)N_kN)!W52JYh6I!i@KrW^J0K3DUa{{J^2H3W0(m>|M zn)*bWPa8fTLxZ=lIX!KjfacqF1^=Iwbn4{^73!VF+D=G&0I~4xfx&w(*ON!4L*EDGw^!|G2JT_s#P16XbIh!lJ6{92q!{C*?Vo-RfKxnG z63BjI722`eGWpxr=fAN}(Rrtqp04>{rSQAONwdnGP<3UoJmjq@zM`msG7;Hwfm9l^;8ycoE9xLf$H5ky&Z6YK+UJZ8lHKmi1vTT zqY)U&=)_`60hGp?5ofYvq$&g0OW&NGvL3$)KgFSZ!j@XLK`el=m}914@gyeaSf}%${t9^D5n6 zm`V_}^`qvQ`70o@vQCBh(exDs26HWd244GvulPYQ9AToHbIqrPb%|^#9c~(3T@#fR z?OdBhU1#nhKYeRyL1F4O_iieO})#28;StXJlgmb18pKvHM1eWX5EIscZ=v0e`A1MLo;;#@y zc`q$jcmst~P-9`syZ63%V;bMOF)8;d-N#lKIVJ#V`N@&*9L@z`c*L>R%^~eyx?yfm z4DAbjcd~M_aA%9ytYUZlP;H)0;K`^OedCXNW(m|Dlr81_2>9Z(!LGp0xo-P;miWxg z4eK9x;1p&w4x^hT<5K+aTwoYNwQ&vt-GTgTPd+?FQXk)a3y^n44f+YNi+c?xYn)Qj z^E7rUU@iaF;lp_cV2EP+8-Aw(;6=;JccZ;ApXcE}O74tMiV{o!Tcadm-P>e=`3nsP*J9JPjB{ zMiD>th`c70>oa&oza~*yrd+5?&tbY^e(K^AS*P6_b_ok=Zd!{p01FgS zeISUOn<@6Aibe#N0qVCB3W)wlAMtf=xcj6c+z%UI(xo}fH!VTr&BtLCj2;nvhvt}7 ztVB(9LK244o#yscDP{t7Yhpm!VaBT8^<;Ac2yUZNXZw%dwR`V+(Uj!`l(n+GIbEzu z6mpkSB>kKrq^=$;Yl036>wDUF8OSz?5{oaCB&3a?K+wqaLo+zoQQ4l{Ndrk3y_}~< zNdeXg6;OGmboiqwhVCQ(9|m}ii`nsQbx=PviDOUc>(^Mj^0}_AS~C-c)$rBsc!p6n zuNkxXQ=RX;00!Y@-NPo&8%NRvQu{Z~DGeFFh{NFMrjVwy?I04>DzxiNQ5JBgMb0;o z5|H?ezwL@EU&?d*=?7mZ?2T)(A4pIT14~z6!f+ywQe0Yk3ZObE4{YFw`~I<^Dq-xr zUo73Wlk;3}2@wv&E9z&PeH8k?wcdwl`aHJ5j3OwtUfCnI`)Yu~KwXh(vD3TUqs^++ z2|vt7l)OD4=j};%#G6d_{6i1#fXVGS=ys#L>OgX{1W3>aDVtGonPYmE$=kQaqWL`m z>U`AW0?@BzD$X7*-&jgi(T4G$9iU#I4Cgy%fpE<6sf{Lqd)fD-FZW%N@{Al$5W;3%IH2k`{KGLcD{54ZfYfQSN?i`jL1fbSjkR33JY+%LxI>9)@1jH zB+$V}q&d4hza&a)G+zBoU&({tZSS1OW5{Z^us+G;w_h zD>Mez0VstK$Naa-s{N;osyYh252!$ok?$x!GW$G_RcK|KAnnEMHB28!Qb?YW{D9L- z$GRIDHj7RRrD3iza2y3x5W=>UH{;?~B_6UZLU*utBrZ1JwrAb-u|I<@eDcvjKRYq? z1O6oOoD0!bQ;R$6BkitI2PiZeM!Mu4a03D|&mLQzQq^3cTNR;*0yJbwclU4RjwQky zHPo{y^bnWCoeccA$vz;^XB>ufwpIP(#6|pzWe(wtHG=AhNhFK{kBwX-6Nl{h)Q28Q z0OjlBPPh{!Yt+_BCFG&_SW=7g!v3F1?gJ|{u zwV!x7#;hReJ`cF$R6nB1k4+zF`}AJF5yC+Q=_%A}$*R~Y4k#DSDMT@M0 zmox?I4nuB-ttccV$2my#AB&&zV!Y)_zYmI~@6|s`>W?{MU8Q@HOFWjo%M(3ER20P) z|6RO>&jz80AjKQVT`+UHd5hlQ^mE=qk2smEi4s0yiLj4Yno>Qy-;n&_Uj4dzyJosI z4II~zOGfmA{#ViXk1i|;EN*mKLIR~*~9+Ohqb+y{jzbN6-_fW6=4JZ#-NMgB zE8&?$hB}?7JPMIHc3`S^lu0PXB)1tTV`w*dQ9@=MO+?G{CV(4d0`5|LJ7qRuQsB@~ zW-G5xkSM>rEJ&8XO`mSczQZfS^nJgmI4Hj_3@4bLi=8wRWvtQUhGqojTw^T2l{JAn zFgGCM1LrFxabUJ?Vb2XiA)1e2$XBU1*N+@q$bAd5gCATfGzz!whaD8v>lA9f5*~F? ztUv+NgHc||Vi%}0j2SijxpoQ}6ZwDETaw1T?zN4>VQp;9P*252l?5J069yr}n){pp z@SssRqWZG&XLZPzsvrAAQB}r-4zZk#?JjN8&fT@B}HF4&7^U)mYs}kfAR8ti?b@xJ%EMmWWikMJ1 zc2q-FI2;DHcrt_C(S?%H&3=y#Z$K_fxQ^ovphicd{-tk}vEzjB$iSn~x>39Pq|q|` zW^SqFb{L&&fil~~L9;N$KG{C1Fh^TAJE0@`dSZVW#5WuzL8eN^cA+k<0c)aaOeQbT zl<6P4`I3`Bh0Lc>1aJNvi4zGMGQcQOCmWxY;>(^{8?HpuQ*8yW$RixBIT#6|auW!A ze5OqDn=q{HZHHNN2n^2X<6s}$(aJO1vw5dtiA^X8rKUK*p*2Bz0_L$33*yYsdin$z z0<dn9*e@UwkmS2>=dX88q|Pup6X<6?Pv!)aNN<6YY15=7H%E*#|5Re>;B8 zejQ7oq^|kS(HDW^9)dRzsz$yv1x$9m=lBLnej-0!Wd*dVwUKYu8C;ywMBL%8VZ;xa zeKEP=YWsUrhGs%}Nsv-PtOZ)&?25HV@8(emNlELe-~w6}(l)bf$vzU5mB!uIUHfr~ zD*fy%35a=R*^T^f%?#^6QSCtRF)`P!1fe0%cXh^}RW#0GmxJw+Rm|JHZ=$ z`9O6R0k{oB%kO{Y)e6&Mc@^}D$5mk7Q-qJ%P5D8E0?;`ho;b{vJ#rufKCY7Jaa^Ko zW8Zz7e&|k{tRT>xPLREX60eCV@{YHVetxEW`87x%n4vx(q5Z--BZgQiM5e0CL;N-U7y`c-u$DoviaPTs73Yl#0yyEK5^nIdA zz(Fz{T*DP^Z0OaQMvN+g@`-e`6F<$MomxB8~q}sC5PcY?99T5YL(lAWz^Z+9)-9~7i;qGM-;A=;rd45X1AE< z9C@-n;*0$E2?G>jyP=g&&1y!`txBeEjX`wPgBL*9dX>^QX@RMl6C`ymOVSDN=mU;7Y^RAQX{GPG7spvZgdVso0$P@=~sDRGE`mvRYy#)Gh7E*hm(DpdZE z?4x>^Y+$XuPjFk<6+olNDNe(=}lxKs-^R{UNyp2ynb(!d7p%_-B3>a_!rJFc z|2w!Ni;z6;%8)7Q<7McdR2_EB(K~Y>?5XIE#20mMm|cB7 zm-xz5qp7TNw&l_# zCT%%}NgEXVq}GFm+3i~5vjO%2y5%2;y2-~x&M$46r?i@Nr`UsR5`{)zW#rF|3@Gk%6*&pB`L zs*iLLdtO$VGNG9GgifK*#{V2mk+H{@Ke`ol4U)dUiT4BefNOQB*Roe9{UK1{^8I73 zfa}_NK-S9dBFzye!CQ6|rr`=XE0r`U>#{S6Lx^f~^X~h6JwXZFt|cHx&_hI8%(Rxy zk$p4-v3Fs%r8(d|njVpRyi<6RJ6AfP1&J^?;v3-lmz_Wv){1Ccfl|{Hr|)DDbewqr zPCyS9SR@5R;df4ZQ-9=%yJJr>4>hYtL>S!<(pPe51e*W74e`S<*DqL|7<19YiuD5y zpKryg!}NqYo_m3tX8}_1*Q|=m8`Ej_C9fGft>9u=+D~)*Pbs0Ut(8ke^35{4#lDUQ5l*sIKZYaoB{LCDT9|kY+{3Tc%H*s=>uLwH(Py z{kgWJna3~>cM5j_8$fkY{;)Ut$`(YKWE>mYU7n3L%+!gXuQj}M(k4HCEPbJaZ_A4t zb;2*d#L0k^1gGELVx+S4XH7OqRKNC-y{T)vT@6~R12!4!r>5B;qNB-w%wk}O!u7(| z)Eo$e0hbC{`-HBd`(EMk*t7eo{~RPIw=WeLS>PH7%5%TD17U+~CBi$*6#@myPXn1= zVu$7X?>SU3ttuN9g-YJK_OMlEyMLsC-2N=I@0`%JokcyPbSfLsRE?gNxya#EP}bWN z_`C4p;LGfWFd^mgVQ(s>BRLzuqHB?a={oFa+n_+e0stNG!QS)&{38sRHbpXZBFscv z5r&fcdywIe9H>=X$L>5Izxm6H|ETD|*Tpu;k;3m<0hEo>qO0ZOVGoTnykn-hk?@7} zT*`9{f38fM0yR9p`{csW#ZCY?dF0zvGpCGw$f1-GF38?}jR?MU7S|l%3II69@pJ;< zq$#aSu*%49t`Z9S+%`c}0&Wzf69VK96Tp=wdD333pay$;C3OSAlG6MFkN+&H+p`Jz zYOv|mCHvtkVDCuxcn@!NFLpQlVrm33DT^Eo*vD(Y65v%H;2O$n$2Q;9BPR zkPdnAuDb~SkGFtbh5A=b)cZ7g7}@U9_d-SzDsfx^QRb0cRfe^li>donaOTw0TW zUfHz>#U~>s^@Cn_Kx}nB!$*W|?*Xka>wU^O8<+wvX0D^uE0tsla%hs#N~JqAbG7X0 zR}}pQ#qitwdo3Z}uB8j3C&^Q|558S3meuR`gvo#8s?mH>x#k%9O!q1UiJQhy@CTLx z7FcLo=4ovR0BO~FWtEC6yt4ntE+!l40BamIK00SVKX9ZxiLPir6u*L)&KR?HLyijV z;)g?tF+As=Qzu-cZDLI?%*EKn-58LY?oapROwmz#mN8Ng{&1qaXUBWwn*pMrtV2kw z{s1!Aj7F#USQqNrG|c=B1ADJ?D#_)thX%kKP#NX(M&^}83rzOdfi;+bXvIy;&l*QR zni7NJ{zahHJt7661E)|En!E4K19Jtyj50#XaGFl4!YP6jIYDeLG(p3h5sYXS7b)OV(rkAy?XHVio@PQ=jW=vR>I z5qZ_B<;PVuU1?ZykNL<(!|jnFit+`ByEJ5*Ie9o?7>z{QYnVx(iShN9s`-bnIp1~W z_>M=I)MtYvD9DJPS;52s{t`a%>w}nC;Gz;K=)CRh(BBtIQ>fs(67#$1Tf*s|pO9xi zSqJ_T-@|@wyO?!$;Oj7oyMJ}D^MLa_N`3)3#EYn{vpo1$refH&O?+VA-FzO)P21J_ zI^)W_Jj!>XW$W89p*Ap#Xcxe$!40w-0SpvRLv{E5YK9E|a^yT7QP91ub>%wV#vPpa z-C~z`{;_>fN3ZY#-I28^dAqwig){Od^a`|Kqu0)R43mi$)%6ivT)n*Q}}4}8Ud!#A}q!G?P2Ds zsh;TewRpt;9I%hqQwMk~{!H3~`F0@6UvOC;x)>~pIXqAP^U2t3jUCtfx{@=HH-cW0 zcxRNF{e{TH1%0DP0Q@({DSX!yHZ0$yY2$r5CQ|q;sR_=R?Sn8nTkhb%8N3C~;D7`0 zjg*X;v#F$<^YC;5`gIiGgAg`nj|(MuGMj<22T}H7e#}F`@&`!A8t%MX_l1i-NX3Ao zE>2R<>)P{!k+jr1eiL56jz6d%$PSPf)+2=fCbLM`d8!*r6o7_^jyDs`xeEe~$h`^a z`!@JvR678zUI*ux*v@SrAPI8)j8ZOC0>GXY-%sD<-4cEGcyS=1l~lcQd$nyWsY*Yl@nTl;*}9_QZAqde{1Q5*?x?8Ps=`<_c&ASU3mqW9ZDUW(l)`HRV#X zVXLRgczoI8GXk^A71g)5A>BdOugfd;a;x_s6*7rE0DwI&|Q zJ0QmoOqp?qrV3{XQxr3d!z~!n8hpKx#FV%ZfJUp$cQwXZ5-eCgx3 zhyDpoxTIjbM2E z)uo(0){lBRYvPdymoL%OMurU9Idwu;!8?Qk4W9M>;B(8YLC>P?Q;Ij2_1E2XUXM-|WOnZeOo9&OX)3{;4s`^fcZf7qG(x zTVc~MrMxEUW%qgayWzQR#ca8_`6$|HiAHnADOu&@1aGFPQf~ao+k>HQ+FSP~cXD=1 zTFBu3Z+m$*EQXV%3OW0%x@!*0Kw&k#0g=Rke!domWk%EHyCm67n9&eT-N4V%WMg|?yJ z^2LIlC?@T*d@{(Oxx(PKUT9q@nUC3J)DscSpQxZC?845ww|dB+$lh3deli}OyPWrl z)>+TC!|@`Iqna0M`ASq=4)O9!s^f>o9|O0)w$H)uj~6ZGKXn@m+cYi41mbpb`y_EX z(Xsxo?!No0sV3?=fE4K}oggYr=|x&-3Q|OhfCLbvNQp=%p?8rYRRe+u0Ric~gR}qw z(n6IcLg=99@t)20EzyKnyoWGjmE0_#K zN+31@q*NI==u!E?ca&s@3@;7uvc%13pxQdTpscjzj6C+0NGRyc69+bxW|8?5F?M?} zH-Z>4^|*JWdh%bnhi&bW;S4#*^D4gekEU+LWQEq^Rzu?iV-^oYyEnte?3MVmUhbBr z8pA9LL_6wem2d{Eg>(ZdaUf$5C{gAN{`yPq=XZ6T9k=5CYNjZR5vAH*b;Lc($f636 zxwH_;XzKXJdQ*8HBxWsh^^oWCF%HZ+b&ct4AXg*R#6{#J62%44*34+Hds>{!dXzA` z{OUE4gd@7~&|7mrv=x0MJ@3u>|GO@rPr= zrZcIjjXwo70SJU8H&1S4s%35gQMOG>=(AUcG4KQn=DXtDZA&%J+tqv@1W!HE_#r)l z7sIo?h9`pL&%gJVhs~2*1x!I8(F~`fC-AtBsuXB$2EpCi89qbllaIkX`qJNWoj!R% zRn?FsEk9s)YKbVSFu-JOyYk%8D>CXu#i`=l%~U@Su&JxAiSL*WY7;ZSY$_-+Zk&t) z`7UA1VsJN$kz3gL+w05$s7i^X6+>HqT#2!h2>x}Xp1!jpH%g_YhoKY6`HW~hbgFxs zw^Ig@pu4mEGD>qC#W+fja>OK@#apgkq)Ai}<_@#0M4nVqorUG*r$$ip`_0>KIBB9v z+T=SRZ+60?f!?rj22{yCcw<%NeDW?NA*KJ}Mi8DYB7Y|rPyNd6UT8mRQhoDoUd_K0$ybZcAP=53KTzk%@jX*ZeENiAHwFgZMq#uNoFZJp& z72wd>0qna1Hj-q#kouFt!4TQG7gC;do~b|Q+DRLtJh}A#A@Zj>F^Q}Mb ztsS0^D64{q+Bng=zhBr_KPrip4V;wU2%MdD-Ng%I`*Z7tk&&HEJye|>KpL6P+?f31 zo9;0huneBFt~*prnOj^8GJ%wmX&Olm?cvTrT!lJn_`t~FnK4*dtI`RTeCPF9DMXqv zr`6a5ILAq*cy1b5DGp`URHMpfhddb=COutv9&GwT`P9%>0v5`ixWv!-@FS}wL()NY zSD6O&)q;^s>wV%$gg|ax>L50ck;)LIuRUVXdE+L}P57feUenkB~v7|*l6189#Sv2gM+E;D~>hqqB`3lF@4c4^N66HK=f0Fn` z1a|fcN^s)>XuI4xy_Lnh-WUmw%7#TeeDXI+2{R%gh!RssR?7N0e|^UEkbwBpK}5$c z2;+;QFw*aG9uQ^?ewo92Uc*EVP6e)6cG7}5Aw&Grka4>g)0agpu3tslyTvl@d3qr2 z-Z+=;snWDfV-;9Qzh1jL-C6?k+PMooDD9i{pu)_x-CMMbx;*}Ky0WerZUIB&Ex&6u$&*@?r9s#~MZRkTt3zvZbQCRPA?( z-7RdSr-f|cHDGHRE1F%ieYa7xAa)WB(^3)}Wcl~c{P|y;tDgbY1|{7}sKx@es6lK! z!j@CIn`dWhh-YsZJ6is4iA-4PZ8x zRqrWipKh&GKP%=Q3h}z#mqeLF5fmEbJL&A{qxZK$SG>!LK#RTs-k_kShyjU2sBBiZ zB%)DhH*`b33#$$#;_sifANlfcc;24H*I38{e6kG#bakA`^0^i?v!Jw7XT%@z{=Dr2 zPJg3qc=^E>AI8ie`b|0cv#qvbVx;2`PQuI}KsU2V%==~-1+dI-*%m3fic^v37pO?> z2GOCiDFb$25fvS~0Z)V5S0!X!Z8nbMu$Q*uCz(2f^{V)o2-&3H zbiJ*weMTs+{ja*_(xMpgS7O%DcWk$CmmXAdWZV(Hk^K~XDOVNllLy#d9Z+<+rDzj^ zri#Rb6CB9*_C(|E_)6zrHGerhhHd#^r!~twnjf~qoV?#w*WuzkxoA+fhhBO0$uZmB zj-^gEZdL(YQ_}qIm{kNo9r>;?p?M-7vA1tz0#=cUZnO!2NF#O^% zmjb%vD)%wB$7g=R9gF|Fd~?n0w)E2i+VGxWEHjn^yYx64JhbeNeQx`)YhK#TL`zYb zSAk7|#UGj>_lt6Yrkvbe_HLSUD>=gN@O~3cCxOq)8uZ9<)^)sQX*twX`MOPeaLaSw z!2wkCe#e4{ml~G>hdH!R|8gYZ;97qffQiK>eBeG z9NUCc=hhpguPD*WoM=vd$}qUV3FSFgGjnra@$SPiM@A)(5c$CsQbBiX5DQQ<5n;_f ztPeBn>3XW%Q=M*YUZgX>rr3p%Y`9uM8)k7?=tNNmyB7pbtf#x z?UzOGtq&hK3`j+{)A`#)`ORGQwHU^q{%$O}Q=!OO0`i|wK7WYSv9%UBmWrZ|7$vun zN93jtrkF_{kQ?xAB^&8yhF4MKNcn#qJj00km6yq%2*NLKK?#}T$7lVaj(tLOBfY$) zK}#0X3{)$nA+|DseDxv;;fDJi@7Z;Ffq?LM{TSyJcK(u z$dm}Eu)owlwT{FfRouU`qr2gda(jD=M)RaZbV}3@v+$yiE}4$rvUu^x?6HRvPWARn zGZE=A7->uh+LB?Po`>IU8bX(LthI@(pSu4RbI1>)>kByDAX=5-gN8)6x15P5pNQAH z&1#z*Lqn8zg&G=4G>-f!ZR)|^>E_0b1p|@Ms3|)NwalI94J$JI*LRm-q|oIQy$E>o z8usoJEhZW)9)dhM__&`bWyyyYil|H6K8n+?>rl;>0&iBzoiC}wGO{ijKZ)F|3BJb^ zWq_pGzmGW4;-ykmcRX_%&YoOnH<)+2Z%TyP@Z&SZfLG76~2B%tM z&k{5Y*T$Dm&wpg)JgqHnM@>fuwD2mBkYg6Q%oMQA%+tifXqV z_G^U9c{hz2SCaoUa8`+ZmxrLU76us~Zn8rt@kS_YIHIossB`Q$y6b5EVRfsLUt4Ba z;(gguLk|(Be)iAtW-*M{yX`}i>ux8x7Y6tAly)Z$(G9IM{an`}6Pg)BR)x2#AW}I4 z$Cf|7kn_LPyYz&zf?_T;q|;g*<-&5v%_#bRPTnPp>E*AGm7qg8rjZ!WTLn-`J3u}< zB8@wTpSwbKwRfXUDJQv;n6 zY#g=UY+om4E!ip9#A}w0sJIp4XH_Z^VZW0rU|XNl#}r`}jgi()2Ay%9^C8~9#_cCA zZW&C|8T#$IRfzR^yk#_I;JHv%64vs2dXZbdzE0{1j_<4yhadzENlUjg#)O8xy$rr| z=N3M_?W}d6ptW?mq;pIc7M>q|s2)>%-%vQfhVLv&Ex|g>ntNs`^;;lZyBThW13TJA z+e`o!)4<*Ocs$D-Rm|~QXBr43>SlaTv@EKE4MuCo_1R}0EHgcgt>#V{rz^PELM{n0 z8rF9>KwUmt+{E+dYCM~5L3;tQ;kfmG07Q4{Yxd3K-V5p4@v9{)it;u;7l+^))Fr76 zm^aZCE!+dMN|(-AQ$M-eB@nT?!efZSWe+-3enPg}TIH{y44c82C!v(sf%i2xoHB)b z8$oAW3#<^G7De!t^VIo!I(`4%cJ*t-+->SmO=DZdiO0aAfvOvq*wrvG77kjTuM6LT z@+2T_N{A#{j(1|#bJJEqaPRtn)p!+7F6+Iy*JaCeC^=&Gmg+NOz<+uoli&@n#j2Z( zl;j0thQJiFOdcjva(a+<&8Zm~t2 z58XzugsW>gYGCSfoAVs`x%h;fl*wuHa*Xfn*rnc4u5%8(ip8I!mD*n2qTk_$ZyAl8 zYzlqp!gMNsdl3H)>AFGe*~$i$q^tubg4L#}cy%M1zcsRiH&6`es!|IZzD_15xPJ z{hX_)d@lOSmFG+yP#p^vv48v)Jp1x8LdSpKuW;UdGPC`}THWm^{Qf|cz5F?2O$8aNnh4>p90|R&vn9*OOiKZkiM}=0oqeqh+IeM)aRcl*} zlKN&b@^0gT=04|v;o2Iy@&NVAASDH@Qr^rglDwqh9SCx4k> z356wQHqcyF2l?{Swu>Jm`{KN9&a>Wy%9zqUaH8v0g!~xTZLO65fOu6de;6*K9`h-2 z(faf7b|Gf~_kPy#n8q$Mq~Fz_1*Pf6ScSsS1qN|T&*?tL_i2Q)M`5l#SX*0ET9y#} zm)k=-(M07fi(-MGMSuSLs%4_sB|~I+!4G*uUye7sJ;W}%VGR&YdPgU937>k@H)(qB zIsGBM@(B(@od0;Ibwv*vse%j)Y-(ticdU@)tf=P!Eczf3U8WK9NvbJdLz`wm6tmD7 zrhsYZEK4+^uUmGn+S(bK{!uOH@tg__2L7ycU$XZ1k7|JV%Q!Pmv*4SvWZmZ2Zk?dw zY;_L`3V;wvx$rCNzCW_=^We-zPy{bIZ<+i=qsfvis@cPYb zw{C3nO@CXv7+deN?(vQK)06MISdSSJY*TgcyZjs@H`0d zCH+d9BirY6_Cj#!Y}A+dwNS0NCG|XF_jc+?U~TNKo6HYYyX$sT1gVw=T5GmXx>y=h zG=Axb0~L6i&S#GSn@%F(uiO27{R`jv-&$`g7LGfcnml~+9Mqvnnow`3hG3GW8G9?T zyzX}OFMACZfDlV;=SRG$_l&Rg7OjPpw~_~E!W;D|(J5P=b!ru_Pj{~YWPUBOj!W80 zwET7x3`g&LodM=}tUIcM5st0(!hrw22pW{@e;G`N7B9XRP_Ac%br6EdDy!V!A$vhN zU=GZB-sJInnKH8H6?xKd2ABZM^uF3iy0YT7Q!wQN!^>auvWW2OTWu-=bQQUnsd2nz z&)VztuWC8gYWslNH0`5{`KDWf|8M}e4Vk&|x~Ow{)M^b)q5!YN#pXze7mmjHr4bc| z6PM*Ca?#Ic(frB?$FcR&Fm$HR3^Wm@UiL)3z*YA$ARe*UxW1(ZtAxuL^nCN#%9d_V zY>axf811zJ(5`+%+E?a$OGiF2)6 zj68Q7C&CtMjj*PP{hSK0Z)!>DB)F5pfQqper>ZmMm!?6B3Gt);9^^(A377+g5{U4v z<{~wFSxuvV$uTB+A$`I_gM=Ttn9^#g^Zg1@KVGCs{KUn`G}VUPpdpO_4) zPHG{pMC~TPypcub!oO-cRob;mAl|TSS$k-x7@qgb0kl8liNjZ@87pTCdYqD!st1l& z5GIKMo!r9=4f^Rv4Wz8NX30>8%w*S&94U=UqHRp?>98$u0<$d?_=s~@F|U^++#0(E z(bipP%KNZCc3ao3c|SS-&I-^L4Zz@P1kTEBy5`ZM_e^z>zB}|c>3Q-Vb@J1VEdH#4 z0L#OCLdjyO;rsR&neIj*JdNYE^KvE7$aoj_V7!bQ1nzX(4KsX3*6j8s=V$&fIA+{$ zvGc#hDfc0FzQZD#wnJCik_Vz*vN0l3snz37cG<$omur&xokNPKiQ~8@Y5a9LsYY$K zlH9iXCNbpaor0Omw~Ue+Gckq6R8F=y>ynd1Zay7b&LbXh60;0RSXH{j%Q%bwaHOLpXPU@99v7Y-{#hR3;| zZ*OFL!wB2%yop{p0ethjQ=;tDDUE!8rwR#{VhXB!n{fZ&S)XhMhpt?7mpytE@TUeJNi8SfLFA)C11vJZ;++CuHkf`ESF49m((OT6}nxaM-v>1U;(I|s>F$=3)~>i*H4`YvaYc|gg~LK#R) zf0=IU!Aby_`i(&R$kg*8D)b1tNCT+gnTE zo+~1fyy2VnK%`Xx43c^)0||qr07fb2qAfl*`Qx7fB%qQdQ&bf;e%8lG5urF{R$s=3 zpC1mZ(AS_nD1TbYP(#-k6Qw#Y#{Nehm`#`beT6axxeam4<7~i^0C(}Q&~KK zt79!Wl^K<`_Ov0 zmF5(7DBe0{MLjja(QCjR9AgjrfEqWE_vku4Xjt^AJ6+{GA>$a$jc^#mCQHW9YDQh~ zC`@3@awES+^*o6X2}c2>!FQU+n(MoP71fC|lRX%z7Ll$KCX}Ohs7MX3$z8lTj^7z; zpkZc+sN_5-qCfei>xq-9yFokB%dHN39REf_6&+hi4t)z~8h1S1g(6}0b)9Nydv6f_ z06Lm|G1xF7?TNG|6>-yF!5|^oEF>FBYi`JpLKxZN^CA&H{r|YJMI4_3?4>9T3YMVn z6rAU1ajuxz!%zbC@maUlUbDIFNEx@oPUClQz)deG0N`?Qb!wVq=FO?`ZbyQ-~DcH$_sFI`~)N-w!Li2ARQlG$t zTAyP&XpT$AEId2y_&1Ao>&pwHY0_T=DaCsq=AA4D-=h53bhL5Y6}bIH(Q+tc>!B1u z6AdTVp1Ts<_@T@2w@smAy~qUrI$jT{i@y4-^NuIe0KZh3sM3Mgo5M9_)T4==^DWcG zi(8L3Ge@}*(14|k>A6g$?^#R{2WA)TXQQzf3+lHIMP5No{p5on<1vy+w*sP`>+?8e zb^5@Am*ZF)WY3%h{^REIwjSYSN~oZm)>3{-o~7{p;;B3HcF)vV(;vNXL z7EGYrYs@XXepakolsf+Im9>^}HCQh|aNsVod?#4U^P>RulffT$en|MSmaW7fyuF;l z9RwxoEKhIYC|w_a_~%eNuV-P;6CZL<+YvqY<`l*J;wLj})r&^Gj}6{|xZA$wQ=fto zErRd6SAp0*I!}gCcNl_gjPBz5|Kng;+q}#o?a@o1W2?c*t0bcycXFtYWObl8&$+JA zk#$M-?x7-D7i$xtqC@*R?H?#t=Y$V7 z)7xN@^D3qdV|^@@41KYdI~bUP8f0 z=f9mL#&bc49=|-|K9bGQVQZGDYD!<<7xjgGu;?*9`4B22_o*hgCFA&82AMeD!oblH zq2A7(Q!3tjv0CypzT&2{mzXF;J%X=;l6W|7G?D4 g|IC&B_mb={6G2saSgQ_rXAS7Frop31b=x=p18!J83;+NC diff --git a/plinth/modules/i2p/static/icons/i2p.svg b/plinth/modules/i2p/static/icons/i2p.svg deleted file mode 100644 index 85b2c446c..000000000 --- a/plinth/modules/i2p/static/icons/i2p.svg +++ /dev/null @@ -1,201 +0,0 @@ - - - - - I2P - - - - - - image/svg+xml - - I2P - - - - Sunil Mohan Adapa <sunil@medhas.org> - - - 2019-04-01 - - https://commons.wikimedia.org/wiki/File:I2P_logo.svg - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plinth/modules/i2p/templates/i2p.html b/plinth/modules/i2p/templates/i2p.html deleted file mode 100644 index f26901881..000000000 --- a/plinth/modules/i2p/templates/i2p.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "app.html" %} -{% comment %} -# SPDX-License-Identifier: AGPL-3.0-or-later -{% endcomment %} - -{% load bootstrap %} -{% load i18n %} - -{% block configuration %} - {{ block.super }} - -

{% trans "I2P Proxies and Tunnels" %}

- {% for line in proxies_description %} -

{{ line|safe }}

- {% endfor %} - -

- - {% trans "Launch" %} - -

- -

{% trans "Anonymous Torrents" %}

- {% for line in torrents_description %} -

{{ line|safe }}

- {% endfor %} - -

- - {% trans "Launch" %} - -

-{% endblock %} diff --git a/plinth/modules/i2p/tests/__init__.py b/plinth/modules/i2p/tests/__init__.py deleted file mode 100644 index 2aec89107..000000000 --- a/plinth/modules/i2p/tests/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Common parts for all I2P tests. -""" - -from pathlib import Path - -DATA_DIR = Path(__file__).parent / 'data' diff --git a/plinth/modules/i2p/tests/data/i2ptunnel.config b/plinth/modules/i2p/tests/data/i2ptunnel.config deleted file mode 100644 index 467980059..000000000 --- a/plinth/modules/i2p/tests/data/i2ptunnel.config +++ /dev/null @@ -1,192 +0,0 @@ -# NOTE: This I2P config file must use UTF-8 encoding -tunnel.0.description=HTTP proxy for browsing eepsites and the web -tunnel.0.interface=127.0.0.1 -tunnel.0.listenPort=4444 -tunnel.0.name=I2P HTTP Proxy -tunnel.0.option.i2cp.closeIdleTime=1800000 -tunnel.0.option.i2cp.closeOnIdle=false -tunnel.0.option.i2cp.delayOpen=false -tunnel.0.option.i2cp.destination.sigType=EdDSA_SHA512_Ed25519 -tunnel.0.option.i2cp.newDestOnResume=false -tunnel.0.option.i2cp.reduceIdleTime=900000 -tunnel.0.option.i2cp.reduceOnIdle=true -tunnel.0.option.i2cp.reduceQuantity=1 -tunnel.0.option.i2p.streaming.connectDelay=1000 -tunnel.0.option.i2ptunnel.httpclient.SSLOutproxies=false.i2p -tunnel.0.option.i2ptunnel.httpclient.allowInternalSSL=true -tunnel.0.option.i2ptunnel.httpclient.jumpServers=http://stats.i2p/cgi-bin/jump.cgi?a=,http://no.i2p/jump/,http://i2pjump.i2p/jump/ -tunnel.0.option.i2ptunnel.httpclient.sendAccept=false -tunnel.0.option.i2ptunnel.httpclient.sendReferer=false -tunnel.0.option.i2ptunnel.httpclient.sendUserAgent=false -tunnel.0.option.i2ptunnel.useLocalOutproxy=false -tunnel.0.option.inbound.backupQuantity=0 -tunnel.0.option.inbound.length=3 -tunnel.0.option.inbound.lengthVariance=0 -tunnel.0.option.inbound.nickname=shared clients -tunnel.0.option.inbound.quantity=2 -tunnel.0.option.outbound.backupQuantity=0 -tunnel.0.option.outbound.length=3 -tunnel.0.option.outbound.lengthVariance=0 -tunnel.0.option.outbound.nickname=shared clients -tunnel.0.option.outbound.priority=10 -tunnel.0.option.outbound.quantity=2 -tunnel.0.option.outproxyAuth=false -tunnel.0.option.persistentClientKey=false -tunnel.0.option.sslManuallySet=true -tunnel.0.option.useSSL=false -tunnel.0.proxyList=false.i2p -tunnel.0.sharedClient=true -tunnel.0.startOnLoad=true -tunnel.0.type=httpclient -tunnel.1.description=IRC tunnel to access the Irc2P network -tunnel.1.i2cpHost=127.0.0.1 -tunnel.1.i2cpPort=7654 -tunnel.1.interface=127.0.0.1 -tunnel.1.listenPort=6668 -tunnel.1.name=Irc2P -tunnel.1.option.crypto.lowTagThreshold=14 -tunnel.1.option.crypto.tagsToSend=20 -tunnel.1.option.i2cp.closeIdleTime=1200000 -tunnel.1.option.i2cp.closeOnIdle=true -tunnel.1.option.i2cp.delayOpen=true -tunnel.1.option.i2cp.destination.sigType=ECDSA_SHA256_P256 -tunnel.1.option.i2cp.newDestOnResume=false -tunnel.1.option.i2cp.reduceIdleTime=600000 -tunnel.1.option.i2cp.reduceOnIdle=true -tunnel.1.option.i2cp.reduceQuantity=1 -tunnel.1.option.i2p.streaming.connectDelay=1000 -tunnel.1.option.i2p.streaming.maxWindowSize=16 -tunnel.1.option.inbound.length=3 -tunnel.1.option.inbound.lengthVariance=0 -tunnel.1.option.inbound.nickname=Irc2P -tunnel.1.option.outbound.length=3 -tunnel.1.option.outbound.lengthVariance=0 -tunnel.1.option.outbound.nickname=Irc2P -tunnel.1.option.outbound.priority=15 -tunnel.1.sharedClient=false -tunnel.1.startOnLoad=true -tunnel.1.targetDestination=irc.00.i2p:6667,irc.postman.i2p:6667,irc.echelon.i2p:6667 -tunnel.1.type=ircclient -tunnel.2.description=I2P Monotone Server -tunnel.2.i2cpHost=127.0.0.1 -tunnel.2.i2cpPort=7654 -tunnel.2.interface=127.0.0.1 -tunnel.2.listenPort=8998 -tunnel.2.name=mtn.i2p-projekt.i2p -tunnel.2.option.i2cp.destination.sigType=EdDSA_SHA512_Ed25519 -tunnel.2.option.i2cp.reduceIdleTime=900000 -tunnel.2.option.i2cp.reduceOnIdle=true -tunnel.2.option.i2cp.reduceQuantity=1 -tunnel.2.option.inbound.backupQuantity=0 -tunnel.2.option.inbound.length=3 -tunnel.2.option.inbound.lengthVariance=0 -tunnel.2.option.inbound.nickname=shared clients -tunnel.2.option.inbound.quantity=2 -tunnel.2.option.outbound.backupQuantity=0 -tunnel.2.option.outbound.length=3 -tunnel.2.option.outbound.lengthVariance=0 -tunnel.2.option.outbound.nickname=shared clients -tunnel.2.option.outbound.quantity=2 -tunnel.2.sharedClient=true -tunnel.2.startOnLoad=false -tunnel.2.targetDestination=mtn.i2p-projekt.i2p:4691 -tunnel.2.type=client -tunnel.3.description=My eepsite -tunnel.3.i2cpHost=127.0.0.1 -tunnel.3.i2cpPort=7654 -tunnel.3.name=I2P webserver -tunnel.3.option.i2cp.destination.sigType=7 -tunnel.3.option.i2p.streaming.limitAction=http -tunnel.3.option.i2p.streaming.maxConcurrentStreams=20 -tunnel.3.option.i2p.streaming.maxConnsPerDay=100 -tunnel.3.option.i2p.streaming.maxConnsPerHour=40 -tunnel.3.option.i2p.streaming.maxConnsPerMinute=10 -tunnel.3.option.i2p.streaming.maxTotalConnsPerMinute=25 -tunnel.3.option.inbound.length=3 -tunnel.3.option.inbound.lengthVariance=0 -tunnel.3.option.inbound.nickname=eepsite -tunnel.3.option.maxPosts=3 -tunnel.3.option.maxTotalPosts=10 -tunnel.3.option.outbound.length=3 -tunnel.3.option.outbound.lengthVariance=0 -tunnel.3.option.outbound.nickname=eepsite -tunnel.3.option.shouldBundleReplyInfo=false -tunnel.3.privKeyFile=eepsite/eepPriv.dat -tunnel.3.spoofedHost=mysite.i2p -tunnel.3.startOnLoad=false -tunnel.3.targetHost=127.0.0.1 -tunnel.3.targetPort=7658 -tunnel.3.type=httpserver -tunnel.4.description=smtp server -tunnel.4.i2cpHost=127.0.0.1 -tunnel.4.i2cpPort=7654 -tunnel.4.interface=127.0.0.1 -tunnel.4.listenPort=7659 -tunnel.4.name=smtp.postman.i2p -tunnel.4.option.i2cp.destination.sigType=EdDSA_SHA512_Ed25519 -tunnel.4.option.i2cp.reduceIdleTime=900000 -tunnel.4.option.i2cp.reduceOnIdle=true -tunnel.4.option.i2cp.reduceQuantity=1 -tunnel.4.option.inbound.backupQuantity=0 -tunnel.4.option.inbound.length=3 -tunnel.4.option.inbound.lengthVariance=0 -tunnel.4.option.inbound.nickname=shared clients -tunnel.4.option.inbound.quantity=2 -tunnel.4.option.outbound.backupQuantity=0 -tunnel.4.option.outbound.length=3 -tunnel.4.option.outbound.lengthVariance=0 -tunnel.4.option.outbound.nickname=shared clients -tunnel.4.option.outbound.quantity=2 -tunnel.4.sharedClient=true -tunnel.4.startOnLoad=true -tunnel.4.targetDestination=smtp.postman.i2p:25 -tunnel.4.type=client -tunnel.5.description=pop3 server -tunnel.5.i2cpHost=127.0.0.1 -tunnel.5.i2cpPort=7654 -tunnel.5.interface=127.0.0.1 -tunnel.5.listenPort=7660 -tunnel.5.name=pop3.postman.i2p -tunnel.5.option.i2cp.destination.sigType=EdDSA_SHA512_Ed25519 -tunnel.5.option.i2cp.reduceIdleTime=900000 -tunnel.5.option.i2cp.reduceOnIdle=true -tunnel.5.option.i2cp.reduceQuantity=1 -tunnel.5.option.i2p.streaming.connectDelay=1000 -tunnel.5.option.inbound.backupQuantity=0 -tunnel.5.option.inbound.length=3 -tunnel.5.option.inbound.lengthVariance=0 -tunnel.5.option.inbound.nickname=shared clients -tunnel.5.option.inbound.quantity=2 -tunnel.5.option.outbound.backupQuantity=0 -tunnel.5.option.outbound.length=3 -tunnel.5.option.outbound.lengthVariance=0 -tunnel.5.option.outbound.nickname=shared clients -tunnel.5.option.outbound.quantity=2 -tunnel.5.sharedClient=true -tunnel.5.startOnLoad=true -tunnel.5.targetDestination=pop.postman.i2p:110 -tunnel.5.type=client -tunnel.6.description=HTTPS proxy for browsing eepsites and the web -tunnel.6.i2cpHost=127.0.0.1 -tunnel.6.i2cpPort=7654 -tunnel.6.interface=127.0.0.1 -tunnel.6.listenPort=4445 -tunnel.6.name=I2P HTTPS Proxy -tunnel.6.option.i2cp.reduceIdleTime=900000 -tunnel.6.option.i2cp.reduceOnIdle=true -tunnel.6.option.i2cp.reduceQuantity=1 -tunnel.6.option.i2p.streaming.connectDelay=1000 -tunnel.6.option.inbound.backupQuantity=0 -tunnel.6.option.inbound.length=3 -tunnel.6.option.inbound.lengthVariance=0 -tunnel.6.option.inbound.nickname=shared clients -tunnel.6.option.inbound.quantity=2 -tunnel.6.option.outbound.backupQuantity=0 -tunnel.6.option.outbound.length=3 -tunnel.6.option.outbound.lengthVariance=0 -tunnel.6.option.outbound.nickname=shared clients -tunnel.6.option.outbound.quantity=2 -tunnel.6.proxyList=outproxy-tor.meeh.i2p -tunnel.6.sharedClient=true -tunnel.6.startOnLoad=true -tunnel.6.type=connectclient diff --git a/plinth/modules/i2p/tests/data/router.config b/plinth/modules/i2p/tests/data/router.config deleted file mode 100644 index 8661f7f28..000000000 --- a/plinth/modules/i2p/tests/data/router.config +++ /dev/null @@ -1,23 +0,0 @@ -# NOTE: This I2P config file must use UTF-8 encoding -i2np.lastIPChange=1555091394049 -i2np.ntcp2.iv=i3yLM2tPW6QH5h4YYZ8EWQ== -i2np.ntcp2.sp=j1K18jMDa5SPH23R2cHMJ-dUyPdo~uooZp6Uz06qP0k= -i2np.udp.internalPort=18778 -i2np.udp.port=18778 -jbigi.lastProcessor=Haswell Celeron/Pentium w/ AVX model 60/64 -router.blocklistVersion=1523108115000 -router.firstInstalled=1555091372597 -router.firstVersion=0.9.38 -router.inboundPool.randomKey=c1BGKzCBpxYfsH0AiEMIS39zYWzyBuWO9lYTqeA91dk= -router.outboundPool.randomKey=Of0jkHuGeUAHr~NoIAQbY930fbYacb4NyX3CjbVKKFI= -router.passwordManager.migrated=true -router.previousVersion=0.9.38 -router.startup.jetty9.migrated=true -router.updateDisabled=true -router.updateLastInstalled=1555091372597 -routerconsole.country= -routerconsole.favorites=anoncoin.i2p,The Anoncoin project,http://anoncoin.i2p/,/themes/console/images/anoncoin_32.png,Dev Builds,Development builds of I2P,http://bobthebuilder.i2p/,/themes/console/images/script_gear.png,Dev Forum,Development forum,http://zzz.i2p/,/themes/console/images/group_gear.png,echelon.i2p,I2P Applications,http://echelon.i2p/,/themes/console/images/box_open.png,exchanged.i2p,Anonymous cryptocurrency exchange,http://exchanged.i2p/,/themes/console/images/exchanged.png,I2P Bug Reports,Bug tracker,http://trac.i2p2.i2p/report/1,/themes/console/images/bug.png,I2P FAQ,Frequently Asked Questions,http://i2p-projekt.i2p/faq,/themes/console/images/question.png,I2P Forum,Community forum,http://i2pforum.i2p/,/themes/console/images/group.png,I2P Plugins,Add-on directory,http://i2pwiki.i2p/index.php?title=Plugins,/themes/console/images/info/plugin_link.png,I2P Technical Docs,Technical documentation,http://i2p-projekt.i2p/how,/themes/console/images/education.png,I2P Wiki,Anonymous wiki - share the knowledge,http://i2pwiki.i2p/,/themes/console/images/i2pwiki_logo.png,Planet I2P,I2P News,http://planet.i2p/,/themes/console/images/world.png,PrivateBin,Encrypted I2P Pastebin,http://paste.crypthost.i2p/,/themes/console/images/paste_plain.png,Project Website,I2P home page,http://i2p-projekt.i2p/,/themes/console/images/info_rhombus.png,stats.i2p,I2P Network Statistics,http://stats.i2p/cgi-bin/dashboard.cgi,/themes/console/images/chart_line.png,The Tin Hat,Privacy guides and tutorials,http://secure.thetinhat.i2p/,/themes/console/images/thetinhat.png,Trac Wiki,,http://trac.i2p2.i2p/,/themes/console/images/billiard_marker.png, -routerconsole.lang=en -routerconsole.newsLastChecked=1555093599462 -routerconsole.newsLastUpdated=1553195540000 -routerconsole.welcomeWizardComplete=true diff --git a/plinth/modules/i2p/tests/test_functional.py b/plinth/modules/i2p/tests/test_functional.py deleted file mode 100644 index d71a5f7ef..000000000 --- a/plinth/modules/i2p/tests/test_functional.py +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Functional, browser based tests for i2p app. -""" - -import pytest - -from plinth.tests.functional import BaseAppTests - -pytestmark = [pytest.mark.apps, pytest.mark.i2p] - - -class TestI2pApp(BaseAppTests): - app_name = 'i2p' - has_service = True - has_web = True - diagnostics_delay = 1 diff --git a/plinth/modules/i2p/tests/test_helpers.py b/plinth/modules/i2p/tests/test_helpers.py deleted file mode 100644 index 848621ed1..000000000 --- a/plinth/modules/i2p/tests/test_helpers.py +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Unit tests for helpers of I2P application. -""" - -from pathlib import Path - -import pytest - -from plinth.modules.i2p.helpers import TunnelEditor - -DATA_DIR = Path(__file__).parent / 'data' -TUNNEL_CONF_PATH = DATA_DIR / 'i2ptunnel.config' -TUNNEL_HTTP_NAME = 'I2P HTTP Proxy' - - -@pytest.fixture(name='editor') -def fixture_editor(): - """Setup editor for each test.""" - return TunnelEditor(str(TUNNEL_CONF_PATH)) - - -def test_reading_conf(editor): - """Test reading configuration file.""" - editor.read_conf() - assert len(editor.lines) > 1 - - -def test_setting_idx(editor): - """Test setting index for editing a tunnel.""" - editor.read_conf() - assert editor.idx is None - editor.set_tunnel_idx(TUNNEL_HTTP_NAME) - assert editor.idx == 0 - - -def test_setting_tunnel_props(editor): - """Test setting a tunnel property.""" - editor.read_conf() - editor.set_tunnel_idx('I2P HTTP Proxy') - interface = '0.0.0.0' - editor.set_tunnel_prop('interface', interface) - assert editor['interface'] == interface - - -def test_getting_nonexistent_props(editor): - """Test getting nonexistent property.""" - editor.read_conf() - editor.idx = 0 - with pytest.raises(KeyError): - _ = editor['blabla'] # noqa - - -def test_setting_new_props(editor): - """Test setting new properties.""" - editor.read_conf() - editor.idx = 0 - value = 'lol' - prop = 'blablabla' - editor[prop] = value - assert editor[prop] == value diff --git a/plinth/modules/i2p/tests/test_router_editor.py b/plinth/modules/i2p/tests/test_router_editor.py deleted file mode 100644 index 2f9ae883e..000000000 --- a/plinth/modules/i2p/tests/test_router_editor.py +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Test I2P router configuration editing helper. -""" - -import pytest - -from plinth.modules.i2p.helpers import RouterEditor -from plinth.modules.i2p.tests import DATA_DIR - -ROUTER_CONF_PATH = str(DATA_DIR / 'router.config') - - -@pytest.fixture(name='editor') -def fixture_editor(): - """Return editor instance object for each test.""" - return RouterEditor(ROUTER_CONF_PATH) - - -def test_count_favorites(editor): - """Test counting favorites.""" - editor.read_conf() - favorites = editor.get_favorites() - assert len(favorites.keys()) == 17 - - -def test_add_normal_favorite(editor): - """Test adding a normal favorite.""" - editor.read_conf() - name = 'Somewhere' - url = 'http://somewhere-again.i2p' - description = "Just somewhere else" - editor.add_favorite(name, url, description) - - favorites = editor.get_favorites() - assert url in favorites - favorite = favorites[url] - assert favorite['name'] == name - assert favorite['description'] == description - - assert len(favorites) == 18 - - -def test_add_favorite_with_comma(editor): - """Test adding a favorite with common in its name.""" - editor.read_conf() - name = 'Name,with,comma' - expected_name = name.replace(',', '.') - url = 'http://url-without-comma.i2p' - description = "Another,comma,to,play,with" - expected_description = description.replace(',', '.') - - editor.add_favorite(name, url, description) - - favorites = editor.get_favorites() - assert url in favorites - favorite = favorites[url] - assert favorite['name'] == expected_name - assert favorite['description'] == expected_description - - assert len(favorites) == 18 - - -def test_add_fav_to_empty_config(editor): - """Test adding favorite to empty configuration.""" - editor.conf_filename = '/tmp/inexistent.conf' - editor.read_conf() - assert not editor.get_favorites() - - name = 'Test Favorite' - url = 'http://test-fav.i2p' - editor.add_favorite(name, url) - assert len(editor.get_favorites()) == 1 diff --git a/plinth/modules/i2p/urls.py b/plinth/modules/i2p/urls.py deleted file mode 100644 index e43f0f28e..000000000 --- a/plinth/modules/i2p/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -URLs for the I2P module. -""" - -from django.urls import re_path - -from plinth.modules.i2p import views - -urlpatterns = [ - re_path(r'^apps/i2p/$', views.I2PAppView.as_view(), name='index') -] diff --git a/plinth/modules/i2p/views.py b/plinth/modules/i2p/views.py deleted file mode 100644 index c09806ed7..000000000 --- a/plinth/modules/i2p/views.py +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-License-Identifier: AGPL-3.0-or-later -""" -Views for I2P application. -""" - -from django.utils.translation import gettext_lazy as _ - -from plinth.views import AppView - - -class I2PAppView(AppView): - """Serve configuration page.""" - app_id = 'i2p' - template_name = 'i2p.html' - proxies_description = [ - _('I2P lets you browse the Internet and hidden services (eepsites) ' - 'anonymously. For this, your browser, preferably the Tor Browser, ' - 'needs to be configured with a proxy.'), - _('By default HTTP, HTTPS and IRC proxies are available. Additional ' - 'proxies and tunnels may be configured using the tunnel ' - 'configuration interface.'), - ] - torrents_description = [ - _('I2P provides an application to download files anonymously in a ' - 'peer-to-peer network. Download files by adding torrents or ' - 'create a new torrent to share a file.'), - ] - - def get_context_data(self, **kwargs): - """Return the context data for rendering the template view.""" - context = super().get_context_data(**kwargs) - context['proxies_description'] = self.proxies_description - context['torrents_description'] = self.torrents_description - - return context