From dd9fa45784f7eafb3ed30e3333ed0f8077974547 Mon Sep 17 00:00:00 2001 From: trendspotter Date: Sat, 27 Nov 2021 20:13:46 +0000 Subject: [PATCH 01/53] Translated using Weblate (Czech) Currently translated at 64.7% (970 of 1498 strings) --- plinth/locale/cs/LC_MESSAGES/django.po | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plinth/locale/cs/LC_MESSAGES/django.po b/plinth/locale/cs/LC_MESSAGES/django.po index d97f20aa0..934d9a7ac 100644 --- a/plinth/locale/cs/LC_MESSAGES/django.po +++ b/plinth/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-22 18:17-0500\n" -"PO-Revision-Date: 2021-10-25 22:49+0000\n" +"PO-Revision-Date: 2021-11-27 22:05+0000\n" "Last-Translator: trendspotter \n" "Language-Team: Czech \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Weblate 4.9-dev\n" +"X-Generator: Weblate 4.10-dev\n" #: doc/dev/_templates/layout.html:11 msgid "Page source" @@ -75,6 +75,8 @@ msgid "" "Select a domain to use TLS with. If the list is empty, please configure at " "least one domain with certificates." msgstr "" +"Vyberte doménu, se kterou chcete používat TLS. Pokud je seznam prázdný, " +"nakonfigurujte alespoň jednu doménu s certifikáty." #: plinth/forms.py:64 msgid "Language" @@ -185,6 +187,8 @@ msgid "" "A scheduled backup failed. Past {error_count} attempts for backup did not " "succeed. The latest error is: {error_message}" msgstr "" +"Naplánované zálohování se nezdařilo. Minulé pokusy o zálohování s počtem " +"chyb {error_count} nebyly úspěšné. Poslední chyba je: {error_message}" #: plinth/modules/backups/__init__.py:258 msgid "Error During Backup" From b10fc8c107ca646e25f55ebe96adcea0ca3a3556 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sun, 14 Nov 2021 12:03:42 -0500 Subject: [PATCH 02/53] shaarli: Enable app Signed-off-by: James Valleroy Reviewed-by: Joseph Nuthalapati --- plinth/modules/shaarli/data/etc/plinth/modules-enabled/shaarli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plinth/modules/shaarli/data/etc/plinth/modules-enabled/shaarli b/plinth/modules/shaarli/data/etc/plinth/modules-enabled/shaarli index 103c9ec60..ae3ff45d0 100644 --- a/plinth/modules/shaarli/data/etc/plinth/modules-enabled/shaarli +++ b/plinth/modules/shaarli/data/etc/plinth/modules-enabled/shaarli @@ -1 +1 @@ -#plinth.modules.shaarli +plinth.modules.shaarli From 6e6b9795796da287b63b6464bf98298349046d5a Mon Sep 17 00:00:00 2001 From: Petter Reinholdtsen Date: Mon, 29 Nov 2021 09:52:34 +0000 Subject: [PATCH 03/53] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 79.8% (1196 of 1498 strings) --- plinth/locale/nb/LC_MESSAGES/django.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plinth/locale/nb/LC_MESSAGES/django.po b/plinth/locale/nb/LC_MESSAGES/django.po index ee5402170..8a86a7aed 100644 --- a/plinth/locale/nb/LC_MESSAGES/django.po +++ b/plinth/locale/nb/LC_MESSAGES/django.po @@ -16,7 +16,7 @@ msgstr "" "Project-Id-Version: FreedomBox UI\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-11-22 18:17-0500\n" -"PO-Revision-Date: 2021-08-19 13:51+0000\n" +"PO-Revision-Date: 2021-11-29 19:52+0000\n" "Last-Translator: Petter Reinholdtsen \n" "Language-Team: Norwegian Bokmål \n" @@ -25,7 +25,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.8-dev\n" +"X-Generator: Weblate 4.10-dev\n" #: doc/dev/_templates/layout.html:11 msgid "Page source" @@ -200,7 +200,7 @@ msgid "" "succeed. The latest error is: {error_message}" msgstr "" "En sikkerhetskopi på tidsplanen feilet. De {error_count} foregående " -"forsøkene lyktes heller ikke. Den siste feilen er: {error_message}" +"forsøkene lyktes heller ikke. Den siste feilen er: {error_message}" #: plinth/modules/backups/__init__.py:258 #, fuzzy @@ -491,7 +491,7 @@ msgid "" "To restore a backup on a new %(box_name)s you need the ssh credentials and, " "if chosen, the encryption passphrase." msgstr "" -"Innloggingsinfo for dette kodelageret er lagret på din %(box_name)s.
" +"Innloggingsinfo for dette kodelageret er lagret på din %(box_name)s.
" "For å tilbakeføre en sikkerhetskopi på en ny %(box_name)s så trenger du SSH-" "innloggingsinformasjon samt, hvis satt, krypteringspassfrasen." From 9133711bafd580a3ad586b92f0c769c57d7a9847 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 23 Nov 2021 11:59:49 -0800 Subject: [PATCH 04/53] dynamicdns: Update URLs to the new dynamic DNS server By December 15th, all freedomboxes should no longer be pointing to the old server. Hence, we need to have these changes done much before the migration date for the changes to propagate. The new website ddns.freedombox.org does not allow registrations yet. However, the new website has a link to announcement. There is now an advice for new users seeking to create accounts during the migration period. Tests: - In Dynamic DNS app, in the about page, the link is to the new server and it works. - The example link in the configuration form, IP URL field description is to new server and it works. - Functional tests for the Dynamic DNS app work. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/dynamicdns/forms.py | 2 +- plinth/modules/dynamicdns/templates/dynamicdns.html | 4 ++-- plinth/modules/dynamicdns/tests/test_functional.py | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/plinth/modules/dynamicdns/forms.py b/plinth/modules/dynamicdns/forms.py index 3f2bf1b4c..60454a9df 100644 --- a/plinth/modules/dynamicdns/forms.py +++ b/plinth/modules/dynamicdns/forms.py @@ -56,7 +56,7 @@ class ConfigureForm(forms.Form): 'router) this URL is used to determine the real ' 'IP address. The URL should simply return the IP where ' 'the client comes from (example: ' - 'http://myip.datasystems24.de).'), + 'https://ddns.freedombox.org/ip/).'), box_name=gettext_lazy(cfg.box_name)) help_user = \ gettext_lazy('The username that was used when the account was ' diff --git a/plinth/modules/dynamicdns/templates/dynamicdns.html b/plinth/modules/dynamicdns/templates/dynamicdns.html index 1f10b3c5e..0584be5cc 100644 --- a/plinth/modules/dynamicdns/templates/dynamicdns.html +++ b/plinth/modules/dynamicdns/templates/dynamicdns.html @@ -11,8 +11,8 @@

{% blocktrans trimmed %} If you are looking for a free dynamic DNS account, you may find - a free GnuDIP service at gnudip.datasystems24.net or you may find + a free GnuDIP service at ddns.freedombox.org or you may find free update URL based services at freedns.afraid.org. diff --git a/plinth/modules/dynamicdns/tests/test_functional.py b/plinth/modules/dynamicdns/tests/test_functional.py index ceee92ea8..a6064e97a 100644 --- a/plinth/modules/dynamicdns/tests/test_functional.py +++ b/plinth/modules/dynamicdns/tests/test_functional.py @@ -6,6 +6,7 @@ Functional, browser based tests for dynamicdns app. import time import pytest + from plinth.tests import functional pytestmark = [ @@ -55,7 +56,7 @@ def _configure(browser): browser.find_by_id('id_dynamicdns_user').fill('tester') browser.find_by_id('id_dynamicdns_secret').fill('testingtesting') browser.find_by_id('id_dynamicdns_ipurl').fill( - 'http://myip.datasystems24.de') + 'https://ddns.freedombox.org/ip/') functional.submit(browser) # After a domain name change, Let's Encrypt will restart the web @@ -76,7 +77,7 @@ def _has_original_config(browser): ipurl = browser.find_by_id('id_dynamicdns_ipurl').value if enabled and service_type == 'GnuDIP' and server == 'example.com' \ and domain == 'freedombox.example.com' and user == 'tester' \ - and ipurl == 'http://myip.datasystems24.de': + and ipurl == 'https://ddns.freedombox.org/ip/': return True else: return False @@ -93,7 +94,7 @@ def _change_config(browser): browser.find_by_id('id_dynamicdns_user').fill('tester2') browser.find_by_id('id_dynamicdns_secret').fill('testingtesting2') browser.find_by_id('id_dynamicdns_ipurl').fill( - 'http://myip2.datasystems24.de') + 'https://ddns2.freedombox.org/ip/') functional.submit(browser) # After a domain name change, Let's Encrypt will restart the web From 18ed1c926720942d56ed3804fba0268ba9c8fece Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sun, 28 Nov 2021 12:24:42 -0800 Subject: [PATCH 05/53] firewall: Allow configuration upgrade to version 1.0.x Closes: #2133. There are two new defaults which need not be altered in FreedomBox: CleanupModulesOnExit=no (removed) AllowZoneDrifting=no Tests: - Without the patch, on a testing system with firewalld 0.9.x installed, run apt update. FreedomBox considers firewalld for force upgrade and then ignores it as shown in the logs. - With the patch, firewalld is upgraded to 1.0.x version by FreedomBox using force upgrade as shown in logs. After upgrade, the default zone is external and backend is nftables. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/firewall/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plinth/modules/firewall/__init__.py b/plinth/modules/firewall/__init__.py index 6bc78f56f..ec3e029cd 100644 --- a/plinth/modules/firewall/__init__.py +++ b/plinth/modules/firewall/__init__.py @@ -107,9 +107,9 @@ def force_upgrade(helper, packages): if 'firewalld' not in packages: return False - # firewalld 0.6.x -> 0.7.x, 0.6.x -> 0.8.x, 0.7.x -> 0.8.x + # firewalld 0.6.x -> 0.7.x, 0.6.x -> 0.8.x, 0.7.x -> 0.8.x, 0.9.x -> 1.0.x package = packages['firewalld'] - if Version(package['current_version']) >= Version('0.9') or \ + if Version(package['current_version']) >= Version('1.0') or \ Version(package['new_version']) < Version('0.7'): return False From 80d0a7638c38ef2bf6d04a547cb4222decb5adae Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 08:03:01 -0800 Subject: [PATCH 06/53] *: Drop unused manual_page at module level Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/avahi/__init__.py | 2 -- plinth/modules/roundcube/__init__.py | 2 -- plinth/modules/searx/__init__.py | 2 -- 3 files changed, 6 deletions(-) diff --git a/plinth/modules/avahi/__init__.py b/plinth/modules/avahi/__init__.py index 243cfd5fd..0e5e61594 100644 --- a/plinth/modules/avahi/__init__.py +++ b/plinth/modules/avahi/__init__.py @@ -42,8 +42,6 @@ _description = [ 'hostile local network.'), box_name=_(cfg.box_name)) ] -manual_page = 'ServiceDiscovery' - app = None diff --git a/plinth/modules/roundcube/__init__.py b/plinth/modules/roundcube/__init__.py index 1a897d082..355610d12 100644 --- a/plinth/modules/roundcube/__init__.py +++ b/plinth/modules/roundcube/__init__.py @@ -39,8 +39,6 @@ _description = [ '>https://www.google.com/settings/security/lesssecureapps).'), ] -manual_page = 'Roundcube' - app = None diff --git a/plinth/modules/searx/__init__.py b/plinth/modules/searx/__init__.py index 5a68055c1..50bff6529 100644 --- a/plinth/modules/searx/__init__.py +++ b/plinth/modules/searx/__init__.py @@ -29,8 +29,6 @@ _description = [ 'It stores no cookies by default.') ] -manual_page = 'Searx' - app = None From 057a1255312dd17a64f59032c0da43657e2559cc Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 08:17:22 -0800 Subject: [PATCH 07/53] app: Introduce API to setup an app Like the earlier API, accept old_version as parameter. A base implementation, simply call setup on all components. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/app.py | 8 ++++++++ plinth/tests/test_app.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/plinth/app.py b/plinth/app.py index b93853b1e..0cc195039 100644 --- a/plinth/app.py +++ b/plinth/app.py @@ -113,6 +113,11 @@ class App: """ return self.get_component(self.app_id + '-info') + def setup(self, old_version): + """Install and configure the app and its components.""" + for component in self.components.values(): + component.setup(old_version=old_version) + def enable(self): """Enable all the components of the app.""" for component in self.components.values(): @@ -221,6 +226,9 @@ class Component: """ return App.get(self.app_id) + def setup(self, old_version): + """Run operations to install and configure the component.""" + def enable(self): """Run operations to enable the component.""" diff --git a/plinth/tests/test_app.py b/plinth/tests/test_app.py index c2a05539d..95e95b092 100644 --- a/plinth/tests/test_app.py +++ b/plinth/tests/test_app.py @@ -4,7 +4,7 @@ Test module for App, base class for all applications. """ import collections -from unittest.mock import patch +from unittest.mock import Mock, call, patch import pytest @@ -114,6 +114,16 @@ def test_get_components_of_type(app_with_components): assert list(components) == leader_components +def test_app_setup(app_with_components): + """Test that running setup on an app run setup on components.""" + for component in app_with_components.components.values(): + component.setup = Mock() + + app_with_components.setup(old_version=2) + for component in app_with_components.components.values(): + component.setup.assert_has_calls([call(old_version=2)]) + + def test_app_enable(app_with_components): """Test that enabling an app enables components.""" app_with_components.disable() @@ -222,6 +232,24 @@ def test_component_app_property(): assert component.app == app +def test_component_setup(): + """Test running setup on component.""" + component = Component('test-component') + assert component.setup(old_version=1) is None + + +def test_component_enable(): + """Test running enable on component.""" + component = Component('test-component') + assert component.enable() is None + + +def test_component_disable(): + """Test running disable on component.""" + component = Component('test-component') + assert component.disable() is None + + def test_component_diagnose(): """Test running diagnostics on component.""" component = Component('test-component') From 2157cb33b78b655bc066235479497d716266d035 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 08:20:13 -0800 Subject: [PATCH 08/53] package: Add parameter to specify skipping package recommendations Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/package.py | 7 ++++++- plinth/tests/test_package.py | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/plinth/package.py b/plinth/package.py index f2e1a1946..7257c5ca7 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -27,18 +27,23 @@ class Packages(app.FollowerComponent): of packages required by an app. """ - def __init__(self, component_id: str, packages: list[str]): + def __init__(self, component_id: str, packages: list[str], + skip_recommends=False): """Initialize a new packages component. 'component_id' should be a unique ID across all components of an app and across all components. 'packages' is the list of Debian packages managed by this component. + + 'skip_recommends' is a boolean specifying whether recommended packages + should be installed along with the listed packages. """ super().__init__(component_id) self.component_id = component_id self._packages = packages + self.skip_recommends = skip_recommends @property def packages(self) -> list[str]: diff --git a/plinth/tests/test_package.py b/plinth/tests/test_package.py index 3339172ca..8ae037f77 100644 --- a/plinth/tests/test_package.py +++ b/plinth/tests/test_package.py @@ -5,8 +5,25 @@ Test module for package module. from unittest.mock import call, patch +import pytest + from plinth.errors import ActionError -from plinth.package import packages_installed, remove +from plinth.package import Packages, packages_installed, remove + + +def test_packages_init(): + """Test initialization of packages component.""" + component = Packages('test-component', ['foo', 'bar']) + assert component.component_id == 'test-component' + assert component.packages == ['foo', 'bar'] + assert not component.skip_recommends + + with pytest.raises(ValueError): + Packages(None, []) + + component = Packages('test-component', [], skip_recommends=True) + assert component.packages == [] + assert component.skip_recommends def test_packages_installed(): From d1b040cdb633c966fffd8d904f0aaf042ebebf13 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 08:21:08 -0800 Subject: [PATCH 09/53] package: Implement installing packages in the component Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/package.py | 9 +++++++++ plinth/tests/test_package.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/plinth/package.py b/plinth/package.py index 7257c5ca7..db9a07428 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -6,6 +6,7 @@ Framework for installing and updating distribution packages import json import logging import subprocess +import sys import threading from typing import Union @@ -50,6 +51,14 @@ class Packages(app.FollowerComponent): """Return the list of packages managed by this component.""" return self._packages + def setup(self, old_version): + """Install the packages.""" + # TODO: Drop the need for setup helper. + module_name = self.app.__module__ + module = sys.modules[module_name] + helper = module.setup_helper + helper.install(self.packages, skip_recommends=self.skip_recommends) + class PackageException(Exception): """A package operation has failed.""" diff --git a/plinth/tests/test_package.py b/plinth/tests/test_package.py index 8ae037f77..8f6db81eb 100644 --- a/plinth/tests/test_package.py +++ b/plinth/tests/test_package.py @@ -3,13 +3,16 @@ Test module for package module. """ -from unittest.mock import call, patch +from unittest.mock import Mock, call, patch import pytest +from plinth.app import App from plinth.errors import ActionError from plinth.package import Packages, packages_installed, remove +setup_helper = Mock() + def test_packages_init(): """Test initialization of packages component.""" @@ -26,6 +29,31 @@ def test_packages_init(): assert component.skip_recommends +def test_packages_setup(): + """Test setting up packages component.""" + + class TestApp(App): + """Test app""" + app_id = 'test-app' + + component = Packages('test-component', ['foo1', 'bar1']) + app = TestApp() + app.add(component) + setup_helper.reset_mock() + app.setup(old_version=3) + setup_helper.install.assert_has_calls( + [call(['foo1', 'bar1'], skip_recommends=False)]) + + component = Packages('test-component', ['foo2', 'bar2'], + skip_recommends=True) + app = TestApp() + app.add(component) + setup_helper.reset_mock() + app.setup(old_version=3) + setup_helper.install.assert_has_calls( + [call(['foo2', 'bar2'], skip_recommends=True)]) + + def test_packages_installed(): """Test packages_installed().""" # list as input From cea023f7b2cc5b44e2ef8f76d07912d20f6576f0 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 08:14:33 -0800 Subject: [PATCH 10/53] actions: Get list of packages from Packages components Instead of getting it from managed_packages module level variable. This is made possible by the ability to instantiate an app without being able to instantiate all apps at once and without even initializing Django. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/packages | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/actions/packages b/actions/packages index 24517a3cd..996450141 100755 --- a/actions/packages +++ b/actions/packages @@ -5,6 +5,7 @@ Wrapper to handle package installation with apt-get. """ import argparse +import inspect import json import logging import os @@ -17,9 +18,11 @@ import apt.cache import apt_inst import apt_pkg +from plinth import app as app_module from plinth import cfg from plinth.action_utils import (apt_hold_freedombox, is_package_manager_busy, run_apt_command) +from plinth.package import Packages logger = logging.getLogger(__name__) @@ -119,8 +122,19 @@ def _assert_managed_packages(module, packages): module_path = file_handle.read().strip() module = import_module(module_path) + module_classes = inspect.getmembers(module, inspect.isclass) + app_classes = [ + cls[1] for cls in module_classes if issubclass(cls[1], app_module.App) + ] + managed_packages = [] + for cls in app_classes: + app = cls() + components = app.get_components_of_type(Packages) + for component in components: + managed_packages += component.packages + for package in packages: - assert package in module.managed_packages + assert package in managed_packages def subcommand_is_package_manager_busy(_): From 6ba98573d551fb2b42577ff4c013a539f6b09c4b Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 08:13:37 -0800 Subject: [PATCH 11/53] security: Get the list of packages from Packages component Instead of from managed_packages module level variable. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/security/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plinth/modules/security/__init__.py b/plinth/modules/security/__init__.py index 4fbad8597..d9467361f 100644 --- a/plinth/modules/security/__init__.py +++ b/plinth/modules/security/__init__.py @@ -131,9 +131,12 @@ def get_apps_report(): } } for module_name, module in module_loader.loaded_modules.items(): - try: - packages = module.managed_packages - except AttributeError: + components = module.app.get_components_of_type(Packages) + packages = [] + for component in components: + packages += component.packages + + if not packages: continue # app has no managed packages try: From 4bf57c5707d16694c954d1e253926e33bec3b810 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 08:11:46 -0800 Subject: [PATCH 12/53] *: Drop use of managed_packages and rely on Packages component - For zoph, drop dependency on php7.4 as it will cause issues for future versions of php. The dependency was a hack and not needed for Bullseye and higher. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/apache/__init__.py | 10 +++----- plinth/modules/avahi/__init__.py | 6 ++--- plinth/modules/backups/__init__.py | 6 ++--- plinth/modules/bepasty/__init__.py | 6 ++--- plinth/modules/bind/__init__.py | 8 +++--- plinth/modules/calibre/__init__.py | 6 ++--- plinth/modules/cockpit/__init__.py | 6 ++--- plinth/modules/config/__init__.py | 6 ++--- plinth/modules/coturn/__init__.py | 6 ++--- plinth/modules/deluge/__init__.py | 6 ++--- plinth/modules/diaspora/__init__.py | 6 ++--- plinth/modules/dynamicdns/__init__.py | 6 ++--- plinth/modules/ejabberd/__init__.py | 6 ++--- plinth/modules/email_server/__init__.py | 30 ++++++++++------------- plinth/modules/firewall/__init__.py | 6 ++--- plinth/modules/gitweb/__init__.py | 6 ++--- plinth/modules/i2p/__init__.py | 6 ++--- plinth/modules/ikiwiki/__init__.py | 12 ++++----- plinth/modules/infinoted/__init__.py | 6 ++--- plinth/modules/jsxc/__init__.py | 6 ++--- plinth/modules/letsencrypt/__init__.py | 6 ++--- plinth/modules/matrixsynapse/__init__.py | 7 +++--- plinth/modules/mediawiki/__init__.py | 7 +++--- plinth/modules/minetest/__init__.py | 8 +++--- plinth/modules/minidlna/__init__.py | 6 ++--- plinth/modules/mldonkey/__init__.py | 6 ++--- plinth/modules/monkeysphere/__init__.py | 6 ++--- plinth/modules/mumble/__init__.py | 6 ++--- plinth/modules/networks/__init__.py | 6 ++--- plinth/modules/openvpn/__init__.py | 6 ++--- plinth/modules/pagekite/__init__.py | 6 ++--- plinth/modules/performance/__init__.py | 6 ++--- plinth/modules/privoxy/__init__.py | 6 ++--- plinth/modules/quassel/__init__.py | 6 ++--- plinth/modules/radicale/__init__.py | 6 ++--- plinth/modules/roundcube/__init__.py | 7 +++--- plinth/modules/samba/__init__.py | 6 ++--- plinth/modules/searx/__init__.py | 6 ++--- plinth/modules/security/__init__.py | 6 ++--- plinth/modules/shaarli/__init__.py | 6 ++--- plinth/modules/shadowsocks/__init__.py | 6 ++--- plinth/modules/snapshot/__init__.py | 6 ++--- plinth/modules/ssh/__init__.py | 5 ++-- plinth/modules/sso/__init__.py | 16 ++++++------ plinth/modules/storage/__init__.py | 8 +++--- plinth/modules/syncthing/__init__.py | 6 ++--- plinth/modules/tahoe/__init__.py | 6 ++--- plinth/modules/tor/__init__.py | 10 +++----- plinth/modules/transmission/__init__.py | 6 ++--- plinth/modules/ttrss/__init__.py | 11 ++++----- plinth/modules/upgrades/__init__.py | 7 +++--- plinth/modules/users/__init__.py | 12 ++++----- plinth/modules/wireguard/__init__.py | 6 ++--- plinth/modules/wordpress/__init__.py | 31 ++++++++++++++---------- plinth/modules/zoph/__init__.py | 10 ++------ 55 files changed, 164 insertions(+), 263 deletions(-) diff --git a/plinth/modules/apache/__init__.py b/plinth/modules/apache/__init__.py index 3ec814c5f..995a9dd2c 100644 --- a/plinth/modules/apache/__init__.py +++ b/plinth/modules/apache/__init__.py @@ -21,10 +21,6 @@ is_essential = True managed_services = ['apache2', 'uwsgi'] -managed_packages = [ - 'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3' -] - app = None @@ -42,7 +38,9 @@ class ApacheApp(app_module.App): name=_('Apache HTTP Server')) self.add(info) - packages = Packages('packages-apache', managed_packages) + packages = Packages('packages-apache', [ + 'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3' + ]) self.add(packages) web_server_ports = Firewall('firewall-web', _('Web Server'), @@ -66,7 +64,7 @@ class ApacheApp(app_module.App): def setup(helper, old_version=None): """Configure the module.""" - helper.install(managed_packages) + app.setup(old_version) actions.superuser_run( 'apache', ['setup', '--old-version', str(old_version)]) diff --git a/plinth/modules/avahi/__init__.py b/plinth/modules/avahi/__init__.py index 0e5e61594..d0dabaec1 100644 --- a/plinth/modules/avahi/__init__.py +++ b/plinth/modules/avahi/__init__.py @@ -29,8 +29,6 @@ depends = ['names'] managed_services = ['avahi-daemon'] -managed_packages = ['avahi-daemon', 'avahi-utils'] - _description = [ format_lazy( _('Service discovery allows other devices on the network to ' @@ -64,7 +62,7 @@ class AvahiApp(app_module.App): 'avahi:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-avahi', managed_packages) + packages = Packages('packages-avahi', ['avahi-daemon', 'avahi-utils']) self.add(packages) domain_type = DomainType('domain-type-local', @@ -96,7 +94,7 @@ class AvahiApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) # Reload avahi-daemon now that first-run does not reboot. After performing # FreedomBox Service (Plinth) package installation, new Avahi files will be # available and require restart. diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py index 0803bdeaa..5df5dd14f 100644 --- a/plinth/modules/backups/__init__.py +++ b/plinth/modules/backups/__init__.py @@ -27,8 +27,6 @@ version = 3 is_essential = True -managed_packages = ['borgbackup', 'sshfs'] - depends = ['storage'] _description = [ @@ -62,7 +60,7 @@ class BackupsApp(app_module.App): 'backups:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-backups', managed_packages) + packages = Packages('packages-backups', ['borgbackup', 'sshfs']) self.add(packages) @staticmethod @@ -76,7 +74,7 @@ class BackupsApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) from . import repository helper.call('post', actions.superuser_run, 'backups', ['setup', '--path', repository.RootBorgRepository.PATH]) diff --git a/plinth/modules/bepasty/__init__.py b/plinth/modules/bepasty/__init__.py index 0170f6780..ed2edc226 100644 --- a/plinth/modules/bepasty/__init__.py +++ b/plinth/modules/bepasty/__init__.py @@ -19,8 +19,6 @@ from . import manifest version = 2 -managed_packages = ['bepasty'] - _description = [ _('bepasty is a web application that allows large files to be uploaded ' 'and shared. Text and code snippets can also be pasted and shared. ' @@ -80,7 +78,7 @@ class BepastyApp(app_module.App): clients=manifest.clients) self.add(shortcut) - packages = Packages('packages-bepasty', managed_packages) + packages = Packages('packages-bepasty', ['bepasty']) self.add(packages) firewall = Firewall('firewall-bepasty', info.name, @@ -101,7 +99,7 @@ class BepastyApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'bepasty', ['setup', '--domain-name', 'freedombox.local']) helper.call('post', app.enable) diff --git a/plinth/modules/bind/__init__.py b/plinth/modules/bind/__init__.py index 0423f2a4e..31591821c 100644 --- a/plinth/modules/bind/__init__.py +++ b/plinth/modules/bind/__init__.py @@ -25,8 +25,6 @@ version = 2 managed_services = ['bind9', 'named'] -managed_packages = ['bind9'] - _description = [ _('BIND enables you to publish your Domain Name System (DNS) information ' 'on the Internet, and to resolve DNS queries for your user devices on ' @@ -87,7 +85,7 @@ class BindApp(app_module.App): parent_url_name='system') self.add(menu_item) - packages = Packages('packages-bind', managed_packages) + packages = Packages('packages-bind', ['bind9']) self.add(packages) firewall = Firewall('firewall-bind', info.name, ports=['dns'], @@ -109,7 +107,7 @@ class BindApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call( 'post', actions.superuser_run, 'bind', ['setup', '--old-version', str(old_version)]) @@ -118,7 +116,7 @@ def setup(helper, old_version=None): def force_upgrade(helper, _packages): """Force upgrade the managed packages to resolve conffile prompt.""" - helper.install(managed_packages, force_configuration='old') + helper.install(['bind9'], force_configuration='old') return True diff --git a/plinth/modules/calibre/__init__.py b/plinth/modules/calibre/__init__.py index 9bbd7f290..22c8a9165 100644 --- a/plinth/modules/calibre/__init__.py +++ b/plinth/modules/calibre/__init__.py @@ -25,8 +25,6 @@ version = 1 managed_services = ['calibre-server-freedombox'] -managed_packages = ['calibre'] - _description = [ format_lazy( _('calibre server provides online access to your e-book collection. ' @@ -79,7 +77,7 @@ class CalibreApp(app_module.App): allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-calibre', managed_packages) + packages = Packages('packages-calibre', ['calibre']) self.add(packages) firewall = Firewall('firewall-calibre', info.name, @@ -106,7 +104,7 @@ class CalibreApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) diff --git a/plinth/modules/cockpit/__init__.py b/plinth/modules/cockpit/__init__.py index 3d07aac20..5fc886b69 100644 --- a/plinth/modules/cockpit/__init__.py +++ b/plinth/modules/cockpit/__init__.py @@ -26,8 +26,6 @@ is_essential = True managed_services = ['cockpit.socket'] -managed_packages = ['cockpit'] - _description = [ format_lazy( _('Cockpit is a server manager that makes it easy to administer ' @@ -83,7 +81,7 @@ class CockpitApp(app_module.App): allowed_groups=['admin']) self.add(shortcut) - packages = Packages('packages-cockpit', managed_packages) + packages = Packages('packages-cockpit', ['cockpit']) self.add(packages) firewall = Firewall('firewall-cockpit', info.name, @@ -110,7 +108,7 @@ class CockpitApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) domains = names.components.DomainName.list_names('https') helper.call('post', actions.superuser_run, 'cockpit', ['setup'] + list(domains)) diff --git a/plinth/modules/config/__init__.py b/plinth/modules/config/__init__.py index 490bd9e22..169bc3fea 100644 --- a/plinth/modules/config/__init__.py +++ b/plinth/modules/config/__init__.py @@ -31,8 +31,6 @@ _description = [ depends = ['apache', 'firewall', 'names'] -managed_packages = ['zram-tools'] - APACHE_CONF_ENABLED_DIR = '/etc/apache2/conf-enabled' APACHE_HOMEPAGE_CONF_FILE_NAME = 'freedombox-apache-homepage.conf' APACHE_HOMEPAGE_CONFIG = os.path.join(APACHE_CONF_ENABLED_DIR, @@ -65,7 +63,7 @@ class ConfigApp(app_module.App): 'config:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-config', managed_packages) + packages = Packages('packages-config', ['zram-tools']) self.add(packages) domain_type = DomainType('domain-type-static', _('Domain Name'), @@ -192,7 +190,7 @@ def set_advanced_mode(advanced_mode): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) _migrate_home_page_config() # systemd-journald is socket activated, it may not be running and it does diff --git a/plinth/modules/coturn/__init__.py b/plinth/modules/coturn/__init__.py index 1deb2f2be..a774049d5 100644 --- a/plinth/modules/coturn/__init__.py +++ b/plinth/modules/coturn/__init__.py @@ -29,8 +29,6 @@ version = 1 managed_services = ['coturn'] -managed_packages = ['coturn'] - managed_paths = [pathlib.Path('/etc/coturn/')] _description = [ @@ -70,7 +68,7 @@ class CoturnApp(app_module.App): parent_url_name='apps') self.add(menu_item) - packages = Packages('packages-coturn', managed_packages) + packages = Packages('packages-coturn', ['coturn']) self.add(packages) firewall = Firewall('firewall-coturn', info.name, @@ -107,7 +105,7 @@ class CoturnApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'coturn', ['setup']) helper.call('post', app.enable) app.get_component('letsencrypt-coturn').setup_certificates() diff --git a/plinth/modules/deluge/__init__.py b/plinth/modules/deluge/__init__.py index 36cfcba62..c563f81d4 100644 --- a/plinth/modules/deluge/__init__.py +++ b/plinth/modules/deluge/__init__.py @@ -22,8 +22,6 @@ version = 6 managed_services = ['deluged', 'deluge-web'] -managed_packages = ['deluged', 'deluge-web'] - _description = [ _('Deluge is a BitTorrent client that features a Web UI.'), _('The default password is \'deluge\', but you should log in and ' @@ -70,7 +68,7 @@ class DelugeApp(app_module.App): allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-deluge', managed_packages) + packages = Packages('packages-deluge', ['deluged', 'deluge-web']) self.add(packages) firewall = Firewall('firewall-deluge', info.name, @@ -101,7 +99,7 @@ class DelugeApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) add_user_to_share_group(SYSTEM_USER) helper.call('post', actions.superuser_run, 'deluge', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/diaspora/__init__.py b/plinth/modules/diaspora/__init__.py index 8261b8e51..53f39f216 100644 --- a/plinth/modules/diaspora/__init__.py +++ b/plinth/modules/diaspora/__init__.py @@ -40,8 +40,6 @@ version = 1 managed_services = ['diaspora'] -managed_packages = ['diaspora'] - _description = [ _('diaspora* is a decentralized social network where you can store ' 'and control your own data.'), @@ -84,7 +82,7 @@ class DiasporaApp(app_module.App): clients=info.clients, login_required=True) self.add(shortcut) - packages = Packages('packages-diaspora', managed_packages) + packages = Packages('packages-diaspora', ['diaspora']) self.add(packages) firewall = Firewall('firewall-diaspora', info.name, @@ -127,7 +125,7 @@ class Shortcut(frontpage.Shortcut): def setup(helper, old_version=None): """Install and configure the module.""" helper.call('pre', actions.superuser_run, 'diaspora', ['pre-install']) - helper.install(managed_packages) + app.setup(old_version) helper.call('custom_config', actions.superuser_run, 'diaspora', ['disable-ssl']) diff --git a/plinth/modules/dynamicdns/__init__.py b/plinth/modules/dynamicdns/__init__.py index 75ae6181c..cf0db265f 100644 --- a/plinth/modules/dynamicdns/__init__.py +++ b/plinth/modules/dynamicdns/__init__.py @@ -23,8 +23,6 @@ is_essential = True depends = ['names'] -managed_packages = ['ez-ipupdate'] - _description = [ format_lazy( _('If your Internet provider changes your IP address periodically ' @@ -64,7 +62,7 @@ class DynamicDNSApp(app_module.App): 'dynamicdns:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-dynamicdns', managed_packages) + packages = Packages('packages-dynamicdns', ['ez-ipupdate']) self.add(packages) domain_type = DomainType('domain-type-dynamic', @@ -101,7 +99,7 @@ class DynamicDNSApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) def get_status(): diff --git a/plinth/modules/ejabberd/__init__.py b/plinth/modules/ejabberd/__init__.py index 8e3c2d052..48ca1a85f 100644 --- a/plinth/modules/ejabberd/__init__.py +++ b/plinth/modules/ejabberd/__init__.py @@ -32,8 +32,6 @@ version = 4 managed_services = ['ejabberd'] -managed_packages = ['ejabberd'] - managed_paths = [pathlib.Path('/etc/ejabberd/')] _description = [ @@ -90,7 +88,7 @@ class EjabberdApp(app_module.App): login_required=True) self.add(shortcut) - packages = Packages('packages-ejabberd', managed_packages) + packages = Packages('packages-ejabberd', ['ejabberd']) self.add(packages) firewall = Firewall('firewall-ejabberd', info.name, @@ -152,7 +150,7 @@ def setup(helper, old_version=None): helper.call('pre', actions.superuser_run, 'ejabberd', ['pre-install', '--domainname', domainname]) # XXX: Configure all other domain names - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.get_component('letsencrypt-ejabberd').setup_certificates, [domainname]) diff --git a/plinth/modules/email_server/__init__.py b/plinth/modules/email_server/__init__.py index 71c4e1ab0..2d21fe87f 100644 --- a/plinth/modules/email_server/__init__.py +++ b/plinth/modules/email_server/__init__.py @@ -14,7 +14,7 @@ from plinth.modules.apache.components import Webserver from plinth.modules.config import get_domainname from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt -from plinth.package import packages_installed, remove +from plinth.package import Packages, packages_installed, remove from . import audit, manifest @@ -27,18 +27,6 @@ version = 1 package_conflicts = ('exim4-base', 'exim4-config', 'exim4-daemon-light') package_conflicts_action = 'ignore' -packages = [ - 'postfix-ldap', - 'postfix-sqlite', - 'dovecot-pop3d', - 'dovecot-imapd', - 'dovecot-ldap', - 'dovecot-lmtpd', - 'dovecot-managesieved', -] - -packages_bloat = ['rspamd'] - clamav_packages = ['clamav', 'clamav-daemon'] clamav_daemons = ['clamav-daemon', 'clamav-freshclam'] @@ -49,8 +37,6 @@ port_info = { managed_services = ['postfix', 'dovecot', 'rspamd'] -managed_packages = packages + packages_bloat - _description = [ _('Roundcube app provides web ' 'interface for users to access email.'), @@ -71,6 +57,17 @@ class EmailServerApp(plinth.app.App): """The app's constructor""" super().__init__() self._add_ui_components() + + packages = Packages('packages-email-server', [ + 'postfix-ldap', 'postfix-sqlite', 'dovecot-pop3d', 'dovecot-imapd', + 'dovecot-ldap', 'dovecot-lmtpd', 'dovecot-managesieved' + ]) + self.add(packages) + + packages = Packages('packages-email-server-skip-rec', ['rspamd'], + skip_recommends=True) + self.add(packages) + self._add_daemons() self._add_firewall_ports() @@ -160,8 +157,7 @@ def setup(helper, old_version=None): # Install helper.call('pre', _clear_conflicts) - helper.install(packages) - helper.install(packages_bloat, skip_recommends=True) + app.setup(old_version) # Setup helper.call('post', audit.home.repair) diff --git a/plinth/modules/firewall/__init__.py b/plinth/modules/firewall/__init__.py index ec3e029cd..c05e1cdac 100644 --- a/plinth/modules/firewall/__init__.py +++ b/plinth/modules/firewall/__init__.py @@ -25,8 +25,6 @@ version = 2 is_essential = True -managed_packages = ['firewalld', 'nftables'] - managed_services = ['firewalld'] _description = [ @@ -74,7 +72,7 @@ class FirewallApp(app_module.App): 'firewall:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-firewall', managed_packages) + packages = Packages('packages-firewall', ['firewalld', 'nftables']) self.add(packages) daemon = Daemon('daemon-firewall', managed_services[0]) @@ -98,7 +96,7 @@ def _run_setup(): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) _run_setup() diff --git a/plinth/modules/gitweb/__init__.py b/plinth/modules/gitweb/__init__.py index 2fdc29ee7..68a8f1de6 100644 --- a/plinth/modules/gitweb/__init__.py +++ b/plinth/modules/gitweb/__init__.py @@ -24,8 +24,6 @@ from .manifest import GIT_REPO_PATH version = 1 -managed_packages = ['gitweb', 'highlight'] - _description = [ _('Git is a distributed version-control system for tracking changes in ' 'source code during software development. Gitweb provides a web ' @@ -74,7 +72,7 @@ class GitwebApp(app_module.App): allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-gitweb', managed_packages) + packages = Packages('packages-gitweb', ['gitweb', 'highlight']) self.add(packages) firewall = Firewall('firewall-gitweb', info.name, @@ -161,7 +159,7 @@ class GitwebBackupRestore(BackupRestore): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'gitweb', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py index c5e190f20..ac60af7da 100644 --- a/plinth/modules/i2p/__init__.py +++ b/plinth/modules/i2p/__init__.py @@ -24,8 +24,6 @@ service_name = 'i2p' managed_services = [service_name] -managed_packages = ['i2p'] - _description = [ _('The Invisible Internet Project is an anonymous network layer intended ' 'to protect communication from censorship and surveillance. I2P ' @@ -78,7 +76,7 @@ class I2PApp(app_module.App): allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-i2p', managed_packages) + packages = Packages('packages-i2p', ['i2p']) self.add(packages) firewall = Firewall('firewall-i2p-web', info.name, @@ -108,7 +106,7 @@ class I2PApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.disable) # Add favorites to the configuration diff --git a/plinth/modules/ikiwiki/__init__.py b/plinth/modules/ikiwiki/__init__.py index 6948b660d..b49ac4c0f 100644 --- a/plinth/modules/ikiwiki/__init__.py +++ b/plinth/modules/ikiwiki/__init__.py @@ -20,11 +20,6 @@ from . import manifest version = 1 -managed_packages = [ - 'ikiwiki', 'libdigest-sha-perl', 'libxml-writer-perl', 'xapian-omega', - 'libsearch-xapian-perl', 'libimage-magick-perl' -] - _description = [ _('ikiwiki is a simple wiki and blog application. It supports ' 'several lightweight markup languages, including Markdown, and ' @@ -65,7 +60,10 @@ class IkiwikiApp(app_module.App): self.refresh_sites() - packages = Packages('packages-ikiwiki', managed_packages) + packages = Packages('packages-ikiwiki', [ + 'ikiwiki', 'libdigest-sha-perl', 'libxml-writer-perl', + 'xapian-omega', 'libsearch-xapian-perl', 'libimage-magick-perl' + ]) self.add(packages) firewall = Firewall('firewall-ikiwiki', info.name, @@ -113,6 +111,6 @@ class IkiwikiApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'ikiwiki', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/infinoted/__init__.py b/plinth/modules/infinoted/__init__.py index be3ef58e8..36a76503d 100644 --- a/plinth/modules/infinoted/__init__.py +++ b/plinth/modules/infinoted/__init__.py @@ -21,8 +21,6 @@ version = 3 managed_services = ['infinoted'] -managed_packages = ['infinoted'] - _description = [ _('infinoted is a server for Gobby, a collaborative text editor.'), format_lazy( @@ -65,7 +63,7 @@ class InfinotedApp(app_module.App): clients=info.clients, login_required=False) self.add(shortcut) - packages = Packages('packages-infinoted', managed_packages) + packages = Packages('packages-infinoted', ['infinoted']) self.add(packages) firewall = Firewall('firewall-infinoted', info.name, @@ -83,6 +81,6 @@ class InfinotedApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'infinoted', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/jsxc/__init__.py b/plinth/modules/jsxc/__init__.py index 25200be7d..0bffe344d 100644 --- a/plinth/modules/jsxc/__init__.py +++ b/plinth/modules/jsxc/__init__.py @@ -19,8 +19,6 @@ from . import manifest version = 1 -managed_packages = ['libjs-jsxc'] - _description = [ _('JSXC is a web client for XMPP. Typically it is used with an XMPP ' 'server running locally.'), @@ -61,7 +59,7 @@ class JSXCApp(app_module.App): clients=info.clients) self.add(shortcut) - packages = Packages('packages-jsxc', managed_packages) + packages = Packages('packages-jsxc', ['libjs-jsxc']) self.add(packages) firewall = Firewall('firewall-jsxc', info.name, @@ -85,5 +83,5 @@ class JSXCApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 2763a5a51..8c1cacd51 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -29,8 +29,6 @@ is_essential = True depends = ['names'] -managed_packages = ['certbot'] - _description = [ format_lazy( _('A digital certificate allows users of a web service to verify the ' @@ -76,7 +74,7 @@ class LetsEncryptApp(app_module.App): 'letsencrypt:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-letsencrypt', managed_packages) + packages = Packages('packages-letsencrypt', ['certbot']) self.add(packages) backup_restore = BackupRestore('backup-restore-letsencrypt', @@ -108,7 +106,7 @@ class LetsEncryptApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) actions.superuser_run( 'letsencrypt', ['setup', '--old-version', str(old_version)]) diff --git a/plinth/modules/matrixsynapse/__init__.py b/plinth/modules/matrixsynapse/__init__.py index 83a7aa952..5736d1ea5 100644 --- a/plinth/modules/matrixsynapse/__init__.py +++ b/plinth/modules/matrixsynapse/__init__.py @@ -30,8 +30,6 @@ version = 7 managed_services = ['matrix-synapse'] -managed_packages = ['matrix-synapse', 'matrix-synapse-ldap3'] - managed_paths = [pathlib.Path('/etc/matrix-synapse/')] _description = [ @@ -94,7 +92,8 @@ class MatrixSynapseApp(app_module.App): clients=info.clients, login_required=True) self.add(shortcut) - packages = Packages('packages-matrixsynapse', managed_packages) + packages = Packages('packages-matrixsynapse', + ['matrix-synapse', 'matrix-synapse-ldap3']) self.add(packages) firewall = Firewall('firewall-matrixsynapse', info.name, @@ -137,7 +136,7 @@ class MatrixSynapseTurnConsumer(TurnConsumer): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) if old_version and old_version < 6: helper.call('post', upgrade, helper) else: diff --git a/plinth/modules/mediawiki/__init__.py b/plinth/modules/mediawiki/__init__.py index 23f1d073a..e78069ba0 100644 --- a/plinth/modules/mediawiki/__init__.py +++ b/plinth/modules/mediawiki/__init__.py @@ -21,8 +21,6 @@ from . import manifest version = 10 -managed_packages = ['mediawiki', 'imagemagick', 'php-sqlite3'] - managed_services = ['mediawiki-jobrunner'] _description = [ @@ -75,7 +73,8 @@ class MediaWikiApp(app_module.App): clients=info.clients, login_required=True) self.add(shortcut) - packages = Packages('packages-mediawiki', managed_packages) + packages = Packages('packages-mediawiki', + ['mediawiki', 'imagemagick', 'php-sqlite3']) self.add(packages) firewall = Firewall('firewall-mediawiki', info.name, @@ -109,7 +108,7 @@ class Shortcut(frontpage.Shortcut): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'mediawiki', ['setup']) helper.call('post', actions.superuser_run, 'mediawiki', ['update']) helper.call('post', app.enable) diff --git a/plinth/modules/minetest/__init__.py b/plinth/modules/minetest/__init__.py index 3ecfc4e9a..3d0027d6e 100644 --- a/plinth/modules/minetest/__init__.py +++ b/plinth/modules/minetest/__init__.py @@ -22,7 +22,7 @@ version = 2 managed_services = ['minetest-server'] -mods = [ +_mods = [ 'minetest-mod-character-creator', 'minetest-mod-craftguide', 'minetest-mod-infinite-chest', 'minetest-mod-lucky-block', 'minetest-mod-maidroid', 'minetest-mod-mesecons', @@ -33,8 +33,6 @@ mods = [ 'minetest-mod-unifieddyes', 'minetest-mod-worldedit' ] -managed_packages = ['minetest-server'] + mods - _description = [ format_lazy( _('Minetest is a multiplayer infinite-world block sandbox. This ' @@ -80,7 +78,7 @@ class MinetestApp(app_module.App): login_required=False) self.add(shortcut) - packages = Packages('packages-minetest', managed_packages) + packages = Packages('packages-minetest', ['minetest-server'] + _mods) self.add(packages) firewall = Firewall('firewall-minetest', info.name, @@ -103,7 +101,7 @@ class MinetestApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) diff --git a/plinth/modules/minidlna/__init__.py b/plinth/modules/minidlna/__init__.py index 2bd9a518b..b256e2144 100644 --- a/plinth/modules/minidlna/__init__.py +++ b/plinth/modules/minidlna/__init__.py @@ -18,8 +18,6 @@ from . import manifest version = 2 -managed_packages = ['minidlna'] - managed_services = ['minidlna'] _description = [ @@ -72,7 +70,7 @@ class MiniDLNAApp(app_module.App): allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-minidlna', managed_packages) + packages = Packages('packages-minidlna', ['minidlna']) self.add(packages) firewall = Firewall('firewall-minidlna', info.name, ports=['minidlna'], @@ -97,7 +95,7 @@ class MiniDLNAApp(app_module.App): def setup(helper, old_version=None): """Install and configure the package""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'minidlna', ['setup']) if not old_version: helper.call('post', app.enable) diff --git a/plinth/modules/mldonkey/__init__.py b/plinth/modules/mldonkey/__init__.py index 088132b63..1a11f6c3a 100644 --- a/plinth/modules/mldonkey/__init__.py +++ b/plinth/modules/mldonkey/__init__.py @@ -23,8 +23,6 @@ version = 2 managed_services = ['mldonkey-server'] -managed_packages = ['mldonkey-server'] - _description = [ _('MLDonkey is a peer-to-peer file sharing application used to exchange ' 'large files. It can participate in multiple peer-to-peer networks ' @@ -74,7 +72,7 @@ class MLDonkeyApp(app_module.App): allowed_groups=list(groups)) self.add(shortcuts) - packages = Packages('packages-mldonkey', managed_packages) + packages = Packages('packages-mldonkey', ['mldonkey-server']) self.add(packages) firewall = Firewall('firewall-mldonkey', info.name, @@ -102,7 +100,7 @@ class MLDonkeyApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" helper.call('pre', actions.superuser_run, 'mldonkey', ['pre-install']) - helper.install(managed_packages) + app.setup(old_version) if not old_version: helper.call('post', app.enable) diff --git a/plinth/modules/monkeysphere/__init__.py b/plinth/modules/monkeysphere/__init__.py index 1e10ddb45..3589578bc 100644 --- a/plinth/modules/monkeysphere/__init__.py +++ b/plinth/modules/monkeysphere/__init__.py @@ -15,8 +15,6 @@ from . import manifest version = 1 -managed_packages = ['monkeysphere'] - _description = [ _('With Monkeysphere, an OpenPGP key can be generated for each configured ' 'domain serving SSH. The OpenPGP public key can then be uploaded to the ' @@ -59,7 +57,7 @@ class MonkeysphereApp(app_module.App): advanced=True) self.add(menu_item) - packages = Packages('packages-monkeysphere', managed_packages) + packages = Packages('packages-monkeysphere', ['monkeysphere']) self.add(packages) users_and_groups = UsersAndGroups('users-and-groups-monkeysphere', @@ -73,4 +71,4 @@ class MonkeysphereApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py index c33912af6..4321c7f87 100644 --- a/plinth/modules/mumble/__init__.py +++ b/plinth/modules/mumble/__init__.py @@ -26,8 +26,6 @@ version = 2 managed_services = ['mumble-server'] -managed_packages = ['mumble-server'] - managed_paths = [pathlib.Path('/var/lib/mumble-server')] _description = [ @@ -69,7 +67,7 @@ class MumbleApp(app_module.App): configure_url=reverse_lazy('mumble:index'), clients=info.clients) self.add(shortcut) - packages = Packages('packages-mumble', managed_packages) + packages = Packages('packages-mumble', ['mumble-server']) self.add(packages) firewall = Firewall('firewall-mumble', info.name, @@ -102,7 +100,7 @@ class MumbleApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'mumble', ['setup']) if not old_version: helper.call('post', app.enable) diff --git a/plinth/modules/networks/__init__.py b/plinth/modules/networks/__init__.py index 32d90097f..6f9be36db 100644 --- a/plinth/modules/networks/__init__.py +++ b/plinth/modules/networks/__init__.py @@ -18,8 +18,6 @@ version = 1 is_essential = True -managed_packages = ['network-manager', 'batctl'] - first_boot_steps = [ { 'id': 'network_topology_wizard', @@ -69,7 +67,7 @@ class NetworksApp(app_module.App): 'networks:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-networks', managed_packages) + packages = Packages('packages-networks', ['network-manager', 'batctl']) self.add(packages) def diagnose(self): @@ -91,7 +89,7 @@ class NetworksApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) actions.superuser_run('networks') helper.call('post', app.enable) diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py index 06d9fd4cf..a3ed018b4 100644 --- a/plinth/modules/openvpn/__init__.py +++ b/plinth/modules/openvpn/__init__.py @@ -24,8 +24,6 @@ version = 4 managed_services = ['openvpn-server@freedombox'] -managed_packages = ['openvpn', 'easy-rsa'] - _description = [ format_lazy( _('Virtual Private Network (VPN) is a technique for securely ' @@ -82,7 +80,7 @@ class OpenVPNApp(app_module.App): allowed_groups=['vpn']) self.add(shortcut) - packages = Packages('packages-openvpn', managed_packages) + packages = Packages('packages-openvpn', ['openvpn', 'easy-rsa']) self.add(packages) firewall = Firewall('firewall-openvpn', info.name, ports=['openvpn'], @@ -112,7 +110,7 @@ class OpenVPNApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'openvpn', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py index 8ae7d04d2..bef97337a 100644 --- a/plinth/modules/pagekite/__init__.py +++ b/plinth/modules/pagekite/__init__.py @@ -22,8 +22,6 @@ depends = ['names'] managed_services = ['pagekite'] -managed_packages = ['pagekite'] - _description = [ format_lazy( _('PageKite is a system for exposing {box_name} services when ' @@ -75,7 +73,7 @@ class PagekiteApp(app_module.App): 'pagekite:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-pagekite', managed_packages) + packages = Packages('packages-pagekite', ['pagekite']) self.add(packages) domain_type = DomainType('domain-type-pagekite', _('PageKite Domain'), @@ -109,7 +107,7 @@ class PagekiteApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) if not old_version: helper.call('post', app.enable) diff --git a/plinth/modules/performance/__init__.py b/plinth/modules/performance/__init__.py index 61f05da17..9bdb5bd4c 100644 --- a/plinth/modules/performance/__init__.py +++ b/plinth/modules/performance/__init__.py @@ -21,8 +21,6 @@ managed_services = [ 'pmcd.service', 'pmie.service', 'pmlogger.service', 'pmproxy.service' ] -managed_packages = ['cockpit-pcp'] - _description = [ _('Performance app allows you to collect, store and view information ' 'about utilization of the hardware. This can give you basic insights ' @@ -57,7 +55,7 @@ class PerformanceApp(app_module.App): 'performance:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-performance', managed_packages) + packages = Packages('packages-performance', ['cockpit-pcp']) self.add(packages) backup_restore = BackupRestore('backup-restore-performance', @@ -83,5 +81,5 @@ class PerformanceApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) diff --git a/plinth/modules/privoxy/__init__.py b/plinth/modules/privoxy/__init__.py index 76bea2307..def21d497 100644 --- a/plinth/modules/privoxy/__init__.py +++ b/plinth/modules/privoxy/__init__.py @@ -25,8 +25,6 @@ is_essential = False managed_services = ['privoxy'] -managed_packages = ['privoxy'] - _description = [ _('Privoxy is a non-caching web proxy with advanced filtering ' 'capabilities for enhancing privacy, modifying web page data and ' @@ -73,7 +71,7 @@ class PrivoxyApp(app_module.App): configure_url=reverse_lazy('privoxy:index'), login_required=True) self.add(shortcut) - packages = Packages('packages-privoxy', managed_packages) + packages = Packages('packages-privoxy', ['privoxy']) self.add(packages) firewall = Firewall('firewall-privoxy', info.name, ports=['privoxy'], @@ -103,7 +101,7 @@ class PrivoxyApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" helper.call('pre', actions.superuser_run, 'privoxy', ['pre-install']) - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index 1f7c03a93..f15f86f56 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -26,8 +26,6 @@ version = 1 managed_services = ['quasselcore'] -managed_packages = ['quassel-core'] - managed_paths = [pathlib.Path('/var/lib/quassel/')] _description = [ @@ -78,7 +76,7 @@ class QuasselApp(app_module.App): login_required=True) self.add(shortcut) - packages = Packages('packages-quassel', managed_packages) + packages = Packages('packages-quassel', ['quassel-core']) self.add(packages) firewall = Firewall('firewall-quassel', info.name, @@ -109,7 +107,7 @@ class QuasselApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) app.get_component('letsencrypt-quassel').setup_certificates() diff --git a/plinth/modules/radicale/__init__.py b/plinth/modules/radicale/__init__.py index dc93e4174..fa87df5cf 100644 --- a/plinth/modules/radicale/__init__.py +++ b/plinth/modules/radicale/__init__.py @@ -22,8 +22,6 @@ from . import manifest version = 2 -managed_packages = ['radicale'] - _description = [ format_lazy( _('Radicale is a CalDAV and CardDAV server. It allows synchronization ' @@ -73,7 +71,7 @@ class RadicaleApp(app_module.App): login_required=True) self.add(shortcut) - packages = Packages('packages-radicale', managed_packages) + packages = Packages('packages-radicale', ['radicale']) self.add(packages) firewall = Firewall('firewall-radicale', info.name, @@ -103,7 +101,7 @@ class RadicaleApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) diff --git a/plinth/modules/roundcube/__init__.py b/plinth/modules/roundcube/__init__.py index 355610d12..a3fa709ed 100644 --- a/plinth/modules/roundcube/__init__.py +++ b/plinth/modules/roundcube/__init__.py @@ -18,8 +18,6 @@ from . import manifest version = 1 -managed_packages = ['sqlite3', 'roundcube', 'roundcube-sqlite3'] - _description = [ _('Roundcube webmail is a browser-based multilingual IMAP ' 'client with an application-like user interface. It provides ' @@ -71,7 +69,8 @@ class RoundcubeApp(app_module.App): login_required=True) self.add(shortcut) - packages = Packages('packages-roundcube', managed_packages) + packages = Packages('packages-roundcube', + ['sqlite3', 'roundcube', 'roundcube-sqlite3']) self.add(packages) firewall = Firewall('firewall-roundcube', info.name, @@ -90,7 +89,7 @@ class RoundcubeApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" helper.call('pre', actions.superuser_run, 'roundcube', ['pre-install']) - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'roundcube', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/samba/__init__.py b/plinth/modules/samba/__init__.py index c9d1584e7..22f131460 100644 --- a/plinth/modules/samba/__init__.py +++ b/plinth/modules/samba/__init__.py @@ -27,8 +27,6 @@ version = 2 managed_services = ['smbd', 'nmbd'] -managed_packages = ['samba', 'acl'] - _description = [ _('Samba allows to share files and folders between FreedomBox and ' 'other computers in your local network.'), @@ -80,7 +78,7 @@ class SambaApp(app_module.App): login_required=True, allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-samba', managed_packages) + packages = Packages('packages-samba', ['samba', 'acl']) self.add(packages) firewall = Firewall('firewall-samba', info.name, ports=['samba']) @@ -122,7 +120,7 @@ class SambaBackupRestore(BackupRestore): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'samba', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/searx/__init__.py b/plinth/modules/searx/__init__.py index 50bff6529..10df828b6 100644 --- a/plinth/modules/searx/__init__.py +++ b/plinth/modules/searx/__init__.py @@ -20,8 +20,6 @@ from . import manifest version = 4 -managed_packages = ['searx'] - _description = [ _('Searx is a privacy-respecting Internet metasearch engine. ' 'It aggregrates and displays results from multiple search engines.'), @@ -64,7 +62,7 @@ class SearxApp(app_module.App): allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-searx', managed_packages) + packages = Packages('packages-searx', ['searx']) self.add(packages) firewall = Firewall('firewall-searx', info.name, @@ -112,7 +110,7 @@ class SearxWebserverAuth(Webserver): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'searx', ['setup']) if not old_version or old_version < 3: helper.call('post', actions.superuser_run, 'searx', diff --git a/plinth/modules/security/__init__.py b/plinth/modules/security/__init__.py index d9467361f..55b02acf8 100644 --- a/plinth/modules/security/__init__.py +++ b/plinth/modules/security/__init__.py @@ -21,8 +21,6 @@ version = 7 is_essential = True -managed_packages = ['fail2ban', 'debsecan'] - managed_services = ['fail2ban'] ACCESS_CONF_FILE = '/etc/security/access.d/50freedombox.conf' @@ -52,7 +50,7 @@ class SecurityApp(app_module.App): 'security:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-security', managed_packages) + packages = Packages('packages-security', ['fail2ban', 'debsecan']) self.add(packages) backup_restore = BackupRestore('backup-restore-security', @@ -62,7 +60,7 @@ class SecurityApp(app_module.App): def setup(helper, old_version=None): """Install the required packages""" - helper.install(managed_packages) + app.setup(old_version) if not old_version: enable_fail2ban() diff --git a/plinth/modules/shaarli/__init__.py b/plinth/modules/shaarli/__init__.py index 2239d2082..9ab2158e7 100644 --- a/plinth/modules/shaarli/__init__.py +++ b/plinth/modules/shaarli/__init__.py @@ -15,8 +15,6 @@ from . import manifest version = 1 -managed_packages = ['shaarli'] - _description = [ _('Shaarli allows you to save and share bookmarks.'), _('Note that Shaarli only supports a single user account, which you will ' @@ -54,7 +52,7 @@ class ShaarliApp(app_module.App): login_required=True) self.add(shortcut) - packages = Packages('packages-shaarli', managed_packages) + packages = Packages('packages-shaarli', ['shaarli']) self.add(packages) firewall = Firewall('firewall-shaarli', info.name, @@ -67,5 +65,5 @@ class ShaarliApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) diff --git a/plinth/modules/shadowsocks/__init__.py b/plinth/modules/shadowsocks/__init__.py index f37f754c8..af4c2b516 100644 --- a/plinth/modules/shadowsocks/__init__.py +++ b/plinth/modules/shadowsocks/__init__.py @@ -21,8 +21,6 @@ version = 3 managed_services = ['shadowsocks-libev-local@freedombox'] -managed_packages = ['shadowsocks-libev'] - _description = [ _('Shadowsocks is a lightweight and secure SOCKS5 proxy, designed to ' 'protect your Internet traffic. It can be used to bypass Internet ' @@ -70,7 +68,7 @@ class ShadowsocksApp(app_module.App): login_required=True) self.add(shortcut) - packages = Packages('packages-shadowsocks', managed_packages) + packages = Packages('packages-shadowsocks', ['shadowsocks-libev']) self.add(packages) firewall = Firewall('firewall-shadowsocks', info.name, @@ -89,6 +87,6 @@ class ShadowsocksApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + helper.install(['shadowsocks-libev']) helper.call('post', actions.superuser_run, 'shadowsocks', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/snapshot/__init__.py b/plinth/modules/snapshot/__init__.py index d92a01897..9a48f2f9f 100644 --- a/plinth/modules/snapshot/__init__.py +++ b/plinth/modules/snapshot/__init__.py @@ -22,8 +22,6 @@ version = 4 is_essential = True -managed_packages = ['snapper'] - _description = [ _('Snapshots allows creating and managing btrfs file system snapshots. ' 'These can be used to roll back the system to a previously known ' @@ -64,7 +62,7 @@ class SnapshotApp(app_module.App): 'snapshot:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-snapshot', managed_packages) + packages = Packages('packages-snapshot', ['snapper']) self.add(packages) backup_restore = SnapshotBackupRestore('backup-restore-snapshot', @@ -92,7 +90,7 @@ def is_supported(): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) if is_supported(): helper.call('post', actions.superuser_run, 'snapshot', ['setup', '--old-version', diff --git a/plinth/modules/ssh/__init__.py b/plinth/modules/ssh/__init__.py index efc3addac..ebe2aa3aa 100644 --- a/plinth/modules/ssh/__init__.py +++ b/plinth/modules/ssh/__init__.py @@ -25,8 +25,6 @@ is_essential = True managed_services = ['ssh'] -managed_packages = ['openssh-server'] - _description = [ _('A Secure Shell server uses the secure shell protocol to accept ' 'connections from remote computers. An authorized remote computer ' @@ -56,7 +54,7 @@ class SSHApp(app_module.App): 'ssh:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-ssh', managed_packages) + packages = Packages('packages-ssh', ['openssh-server']) self.add(packages) firewall = Firewall('firewall-ssh', info.name, ports=['ssh'], @@ -72,6 +70,7 @@ class SSHApp(app_module.App): def setup(helper, old_version=None): """Configure the module.""" + app.setup(old_version) actions.superuser_run('ssh', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/sso/__init__.py b/plinth/modules/sso/__init__.py index a6c2019fb..60286f30b 100644 --- a/plinth/modules/sso/__init__.py +++ b/plinth/modules/sso/__init__.py @@ -15,12 +15,7 @@ is_essential = True depends = ['security', 'apache'] -managed_packages = [ - 'libapache2-mod-auth-pubtkt', - 'openssl', - 'python3-openssl', - 'flite', -] +app = None class SSOApp(app_module.App): @@ -36,11 +31,16 @@ class SSOApp(app_module.App): name=_('Single Sign On')) self.add(info) - packages = Packages('packages-sso', managed_packages) + packages = Packages('packages-sso', [ + 'libapache2-mod-auth-pubtkt', + 'openssl', + 'python3-openssl', + 'flite', + ]) self.add(packages) def setup(helper, old_version=None): """Install the required packages""" - helper.install(managed_packages) + app.setup(old_version) actions.superuser_run('auth-pubtkt', ['create-key-pair']) diff --git a/plinth/modules/storage/__init__.py b/plinth/modules/storage/__init__.py index 9bcea62ba..062ed5b03 100644 --- a/plinth/modules/storage/__init__.py +++ b/plinth/modules/storage/__init__.py @@ -23,8 +23,6 @@ from . import manifest, udisks2 version = 4 -managed_packages = ['parted', 'udisks2', 'gir1.2-udisks-2.0'] - _description = [ format_lazy( _('This module allows you to manage storage media attached to your ' @@ -61,7 +59,9 @@ class StorageApp(app_module.App): 'storage:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-storage', managed_packages) + packages = Packages('packages-storage', + ['parted', 'udisks2', 'gir1.2-udisks-2.0'], + skip_recommends=True) self.add(packages) backup_restore = BackupRestore('backup-restore-storage', @@ -279,7 +279,7 @@ def get_error_message(error): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages, skip_recommends=True) + app.setup(old_version) helper.call('post', actions.superuser_run, 'storage', ['setup']) helper.call('post', app.enable) disks = get_disks() diff --git a/plinth/modules/syncthing/__init__.py b/plinth/modules/syncthing/__init__.py index 776ff9d44..ecacc3a09 100644 --- a/plinth/modules/syncthing/__init__.py +++ b/plinth/modules/syncthing/__init__.py @@ -23,8 +23,6 @@ version = 5 managed_services = ['syncthing@syncthing'] -managed_packages = ['syncthing'] - _description = [ _('Syncthing is an application to synchronize files across multiple ' 'devices, e.g. your desktop computer and mobile phone. Creation, ' @@ -81,7 +79,7 @@ class SyncthingApp(app_module.App): allowed_groups=list(self.groups)) self.add(shortcut) - packages = Packages('packages-syncthing', managed_packages) + packages = Packages('packages-syncthing', ['syncthing']) self.add(packages) firewall = Firewall('firewall-syncthing-web', info.name, @@ -110,7 +108,7 @@ class SyncthingApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'syncthing', ['setup']) add_user_to_share_group(SYSTEM_USER, managed_services[0]) diff --git a/plinth/modules/tahoe/__init__.py b/plinth/modules/tahoe/__init__.py index e9630593c..ec654780d 100644 --- a/plinth/modules/tahoe/__init__.py +++ b/plinth/modules/tahoe/__init__.py @@ -26,8 +26,6 @@ version = 1 managed_services = ['tahoe-lafs'] -managed_packages = ['tahoe-lafs'] - _description = [ _('Tahoe-LAFS is a decentralized secure file storage system. ' 'It uses provider independent security to store files over a ' @@ -80,7 +78,7 @@ class TahoeApp(app_module.App): configure_url=reverse_lazy('tahoe:index'), login_required=True) self.add(shortcut) - packages = Packages('packages-tahoe', managed_packages) + packages = Packages('packages-tahoe', ['tahoe-lafs']) self.add(packages) firewall = Firewall('firewall-tahoe', info.name, @@ -146,7 +144,7 @@ def get_configured_domain_name(): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) def post_setup(configured_domain_name): diff --git a/plinth/modules/tor/__init__.py b/plinth/modules/tor/__init__.py index 42c91d12a..f9fa4e5bb 100644 --- a/plinth/modules/tor/__init__.py +++ b/plinth/modules/tor/__init__.py @@ -26,10 +26,6 @@ version = 5 depends = ['names'] -managed_packages = [ - 'tor', 'tor-geoipdb', 'torsocks', 'obfs4proxy', 'apt-transport-tor' -] - managed_services = ['tor@plinth'] _description = [ @@ -66,7 +62,9 @@ class TorApp(app_module.App): parent_url_name='apps') self.add(menu_item) - packages = Packages('packages-tor', managed_packages) + packages = Packages('packages-tor', [ + 'tor', 'tor-geoipdb', 'torsocks', 'obfs4proxy', 'apt-transport-tor' + ]) self.add(packages) domain_type = DomainType('domain-type-tor', _('Tor Onion Service'), @@ -160,7 +158,7 @@ class TorApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call( 'post', actions.superuser_run, 'tor', ['setup', '--old-version', str(old_version)]) diff --git a/plinth/modules/transmission/__init__.py b/plinth/modules/transmission/__init__.py index e28ccc2e6..2d6191138 100644 --- a/plinth/modules/transmission/__init__.py +++ b/plinth/modules/transmission/__init__.py @@ -24,8 +24,6 @@ version = 4 managed_services = ['transmission-daemon'] -managed_packages = ['transmission-daemon'] - _description = [ _('Transmission is a BitTorrent client with a web interface.'), _('BitTorrent is a peer-to-peer file sharing protocol. ' @@ -72,7 +70,7 @@ class TransmissionApp(app_module.App): allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-transmission', managed_packages) + packages = Packages('packages-transmission', ['transmission-daemon']) self.add(packages) firewall = Firewall('firewall-transmission', info.name, @@ -105,7 +103,7 @@ class TransmissionApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) if old_version and old_version <= 3 and app.is_enabled(): app.get_component('firewall-transmission').enable() diff --git a/plinth/modules/ttrss/__init__.py b/plinth/modules/ttrss/__init__.py index 1ae8a73c0..0983224d5 100644 --- a/plinth/modules/ttrss/__init__.py +++ b/plinth/modules/ttrss/__init__.py @@ -23,10 +23,6 @@ version = 3 managed_services = ['tt-rss'] -managed_packages = [ - 'tt-rss', 'postgresql', 'dbconfig-pgsql', 'php-pgsql', 'python3-psycopg2' -] - _description = [ _('Tiny Tiny RSS is a news feed (RSS/Atom) reader and aggregator, ' 'designed to allow reading news from any location, while feeling as ' @@ -75,7 +71,10 @@ class TTRSSApp(app_module.App): allowed_groups=list(groups)) self.add(shortcut) - packages = Packages('packages-ttrss', managed_packages) + packages = Packages('packages-ttrss', [ + 'tt-rss', 'postgresql', 'dbconfig-pgsql', 'php-pgsql', + 'python3-psycopg2' + ]) self.add(packages) firewall = Firewall('firewall-ttrss', info.name, @@ -125,7 +124,7 @@ class TTRSSBackupRestore(BackupRestore): def setup(helper, old_version=None): """Install and configure the module.""" helper.call('pre', actions.superuser_run, 'ttrss', ['pre-setup']) - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'ttrss', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py index 1ffffd674..a8d539833 100644 --- a/plinth/modules/upgrades/__init__.py +++ b/plinth/modules/upgrades/__init__.py @@ -25,8 +25,6 @@ version = 9 is_essential = True -managed_packages = ['unattended-upgrades', 'needrestart'] - managed_services = ['freedombox-dist-upgrade'] first_boot_steps = [ @@ -85,7 +83,8 @@ class UpgradesApp(app_module.App): 'upgrades:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-upgrades', managed_packages) + packages = Packages('packages-upgrades', + ['unattended-upgrades', 'needrestart']) self.add(packages) backup_restore = BackupRestore('backup-restore-upgrades', @@ -139,7 +138,7 @@ class UpgradesApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) # Enable automatic upgrades but only on first install if not old_version and not cfg.develop: diff --git a/plinth/modules/users/__init__.py b/plinth/modules/users/__init__.py index af9d56363..fcba80e57 100644 --- a/plinth/modules/users/__init__.py +++ b/plinth/modules/users/__init__.py @@ -21,11 +21,6 @@ version = 3 is_essential = True -managed_packages = [ - 'ldapscripts', 'ldap-utils', 'libnss-ldapd', 'libpam-ldapd', 'nscd', - 'nslcd', 'samba-common-bin', 'slapd', 'tdb-tools' -] - managed_services = ['slapd'] first_boot_steps = [ @@ -72,7 +67,10 @@ class UsersApp(app_module.App): 'users:index', parent_url_name='system') self.add(menu_item) - packages = Packages('packages-users', managed_packages) + packages = Packages('packages-users', [ + 'ldapscripts', 'ldap-utils', 'libnss-ldapd', 'libpam-ldapd', + 'nscd', 'nslcd', 'samba-common-bin', 'slapd', 'tdb-tools' + ]) self.add(packages) daemon = Daemon('daemon-users', managed_services[0], @@ -98,7 +96,7 @@ class UsersApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) if not old_version: helper.call('post', actions.superuser_run, 'users', ['first-setup']) helper.call('post', actions.superuser_run, 'users', ['setup']) diff --git a/plinth/modules/wireguard/__init__.py b/plinth/modules/wireguard/__init__.py index 0feae35d8..ca0b3960a 100644 --- a/plinth/modules/wireguard/__init__.py +++ b/plinth/modules/wireguard/__init__.py @@ -18,8 +18,6 @@ nm = import_from_gi('NM', '1.0') version = 1 -managed_packages = ['wireguard'] - _description = [ _('WireGuard is a fast, modern, secure VPN tunnel.'), format_lazy( @@ -69,7 +67,7 @@ class WireguardApp(app_module.App): clients=info.clients) self.add(shortcut) - packages = Packages('packages-wireguard', managed_packages) + packages = Packages('packages-wireguard', ['wireguard']) self.add(packages) firewall = Firewall('firewall-wireguard', info.name, @@ -99,5 +97,5 @@ class WireguardApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', app.enable) diff --git a/plinth/modules/wordpress/__init__.py b/plinth/modules/wordpress/__init__.py index 8c07b7c05..5b825a2d5 100644 --- a/plinth/modules/wordpress/__init__.py +++ b/plinth/modules/wordpress/__init__.py @@ -23,17 +23,6 @@ version = 1 managed_services = ['wordpress-freedombox.timer'] -# Add php to avoid wordpress package bringing in lib-apache2-mod-php. -# WordPress only supports MySQL/MariaDB as database server. -managed_packages = [ - 'wordpress', - 'php', # Avoid WordPress package bringing in libapache2-mod-php - 'php-imagick', # Optional, for performance - 'php-ssh2', # Optional, to upload plugins/themes using SSH connection - 'php-zip', # Optional, for performance - 'default-mysql-server', # WordPress only supports MySQL/MariaDB as DB -] - _description = [ _('WordPress is a popular way to create and manage websites and blogs. ' 'Content can be managed using a visual interface. Layout and ' @@ -86,7 +75,23 @@ class WordPressApp(app_module.App): url='/wordpress/', clients=info.clients) self.add(shortcut) - packages = Packages('packages-wordpress', managed_packages) + # Add php to avoid wordpress package bringing in lib-apache2-mod-php. + # WordPress only supports MySQL/MariaDB as database server. + packages = Packages( + 'packages-wordpress', + [ + 'wordpress', + # Avoid WordPress package bringing in libapache2-mod-php + 'php', + # Optional, for performance + 'php-imagick', + # Optional, to upload plugins/themes using SSH connection + 'php-ssh2', + # Optional, for performance + 'php-zip', + # WordPress only supports MySQL/MariaDB as DB + 'default-mysql-server', + ]) self.add(packages) firewall = Firewall('firewall-wordpress', info.name, @@ -119,6 +124,6 @@ class WordPressBackupRestore(BackupRestore): def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'wordpress', ['setup']) helper.call('post', app.enable) diff --git a/plinth/modules/zoph/__init__.py b/plinth/modules/zoph/__init__.py index 89525c227..7fc055d03 100644 --- a/plinth/modules/zoph/__init__.py +++ b/plinth/modules/zoph/__init__.py @@ -23,12 +23,6 @@ logger = logging.getLogger(__name__) version = 1 -# XXX: This implementation of Zoph does not work with version 0.9.9 in Buster. -# As an easy hack to make the app only available in Bullseye, php7.4 dependency -# has been added. After making the last release for Buster, this can be removed -# to allow compatibility with newer versions of PHP that become available. -managed_packages = ['zoph', 'php7.4'] - _description = [ format_lazy( _('Zoph manages your photo collection. Photos are stored on your ' @@ -79,7 +73,7 @@ class ZophApp(app_module.App): login_required=True) self.add(shortcut) - packages = Packages('packages-zoph', managed_packages) + packages = Packages('packages-zoph', ['zoph']) self.add(packages) firewall = Firewall('firewall-zoph', info.name, @@ -98,7 +92,7 @@ class ZophApp(app_module.App): def setup(helper, old_version=None): """Install and configure the module.""" helper.call('pre', actions.superuser_run, 'zoph', ['pre-install']) - helper.install(managed_packages) + app.setup(old_version) helper.call('post', actions.superuser_run, 'zoph', ['setup']) helper.call('post', app.enable) From e3e3042da481e5cd9d1e90b945ba50f16440858c Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 08:19:13 -0800 Subject: [PATCH 13/53] doc/dev: Update documentation to not refer to managed_packages Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- doc/dev/reference/app_module.rst | 7 ------- doc/dev/tutorial/components.rst | 3 +-- doc/dev/tutorial/setup.rst | 4 +--- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/doc/dev/reference/app_module.rst b/doc/dev/reference/app_module.rst index 001567aaf..1495db396 100644 --- a/doc/dev/reference/app_module.rst +++ b/doc/dev/reference/app_module.rst @@ -32,13 +32,6 @@ triggers the setup() logic allowing the app to run upgrade scripts. This attribute is part of the :class:`~plinth.app.Info` component. Need for this attribute at the module level will be removed in the future. -.managed_packages -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Optional. This must contain the list of all packages that this app deals with. -This is mostly needed to enforce better security. This information may be moved -to a separate component in the future. - .managed_services ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/dev/tutorial/components.rst b/doc/dev/tutorial/components.rst index 9b2d801a1..abbb064ab 100644 --- a/doc/dev/tutorial/components.rst +++ b/doc/dev/tutorial/components.rst @@ -126,7 +126,6 @@ Debian packages to be installed. from plinth.package import Packages - managed_packages = ['transmission-daemon'] class TransmissionApp(app_module.App): ... @@ -134,7 +133,7 @@ Debian packages to be installed. def __init__(self): ... - packages = Packages('packages-transmission', managed_packages) + packages = Packages('packages-transmission', ['transmission-daemon']) self.add(packages) The first argument uniquely identifies this instance of the `Packages` diff --git a/doc/dev/tutorial/setup.rst b/doc/dev/tutorial/setup.rst index a7fddc0ac..63b82c9ca 100644 --- a/doc/dev/tutorial/setup.rst +++ b/doc/dev/tutorial/setup.rst @@ -16,11 +16,9 @@ installation: .. code-block:: python3 :caption: ``__init__.py`` - managed_packages = ['transmission-daemon'] - def setup(helper, old_version=None): """Install and configure the module.""" - helper.install(managed_packages) + app.setup(old_version) new_configuration = { 'rpc-whitelist-enabled': False, From 5b5249873d9e7d0c8dc14d410ea67ac7fe002399 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 14:36:08 -0800 Subject: [PATCH 14/53] actions/service: Drop unused list action Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/service | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/actions/service b/actions/service index a9ea57ac9..7588321db 100755 --- a/actions/service +++ b/actions/service @@ -5,9 +5,7 @@ Wrapper to list and handle system services """ import argparse -import json import os -import subprocess from importlib import import_module from plinth import action_utils, cfg @@ -39,8 +37,6 @@ def parse_arguments(): add_service_action(subparsers, 'mask', 'unmask a service') add_service_action(subparsers, 'unmask', 'unmask a service') - subparsers.add_parser('list', help='List of running system services') - subparsers.required = True return parser.parse_args() @@ -89,36 +85,6 @@ def subcommand_is_running(arguments): print(action_utils.service_is_running(arguments.service)) -def subcommand_list(_): - """Get list of plinth-managed services with their status. - - Status may be either running or not. - - """ - managed_services = _get_managed_services() - services = dict.fromkeys(managed_services, {'running': False}) - - output = subprocess.check_output(['systemctl', 'list-units']) - for line in output.decode().strip().split('\n'): - if line.startswith('UNIT'): - continue - # Stop parsing on empty line after the service list - if not len(line): - break - - try: - unit, load, active, sub = line.split()[:4] - except ValueError: - continue - suffix = '.service' - if unit.endswith(suffix): - name = unit[:-len(suffix)] - if name in services: - services[name] = {'running': (sub == 'running')} - - print(json.dumps(services)) - - def _get_managed_services_of_module(modulepath): """Import a module and return content of its 'managed_services' variable""" try: From 84ae338e666fd830a699ed05e4fae888d1c693ac Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 16:31:36 -0800 Subject: [PATCH 15/53] bind: Drop alias handling unnecessary in >= Bullseye Alias was added to deal with Buster -> Bullseye transition. In Buster the daemon was named bind9 and in Bullseye the daemon is named named with alias to bind9. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/bind | 4 ++-- plinth/modules/bind/__init__.py | 5 ++--- plinth/modules/bind/manifest.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/actions/bind b/actions/bind index 7f0977f50..b1a4703b9 100755 --- a/actions/bind +++ b/actions/bind @@ -39,14 +39,14 @@ def subcommand_setup(arguments): Path(ZONES_DIR).mkdir(exist_ok=True, parents=True) - action_utils.service_restart('bind9') + action_utils.service_restart('named') def subcommand_configure(arguments): """Configure BIND.""" set_forwarders(arguments.forwarders) set_dnssec(arguments.dnssec) - action_utils.service_restart('bind9') + action_utils.service_restart('named') def main(): diff --git a/plinth/modules/bind/__init__.py b/plinth/modules/bind/__init__.py index 31591821c..2de4e1bb8 100644 --- a/plinth/modules/bind/__init__.py +++ b/plinth/modules/bind/__init__.py @@ -23,7 +23,7 @@ from . import manifest version = 2 -managed_services = ['bind9', 'named'] +managed_services = ['named'] _description = [ _('BIND enables you to publish your Domain Name System (DNS) information ' @@ -96,8 +96,7 @@ class BindApp(app_module.App): 'daemon-bind', managed_services[0], listen_ports=[(53, 'tcp6'), (53, 'udp6'), (53, 'tcp4'), - (53, 'udp4')], - alias=managed_services[1]) + (53, 'udp4')]) self.add(daemon) backup_restore = BackupRestore('backup-restore-bind', diff --git a/plinth/modules/bind/manifest.py b/plinth/modules/bind/manifest.py index 616e6f641..b8f345890 100644 --- a/plinth/modules/bind/manifest.py +++ b/plinth/modules/bind/manifest.py @@ -7,5 +7,5 @@ backup = { 'config': { 'files': ['/etc/bind/named.conf.options'] }, - 'services': ['bind9'] + 'services': ['named'] } From 9ec0c5f3db60c126019efd828c49b8947f88541f Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 16:35:23 -0800 Subject: [PATCH 16/53] security: Drop use of managed_services in security report Use Daemon component instead. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/security/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plinth/modules/security/__init__.py b/plinth/modules/security/__init__.py index 55b02acf8..88c1aca8e 100644 --- a/plinth/modules/security/__init__.py +++ b/plinth/modules/security/__init__.py @@ -12,6 +12,7 @@ from django.utils.translation import gettext_lazy as _ from plinth import actions from plinth import app as app_module from plinth import menu, module_loader +from plinth.daemon import Daemon from plinth.modules.backups.components import BackupRestore from plinth.package import Packages @@ -137,10 +138,10 @@ def get_apps_report(): if not packages: continue # app has no managed packages - try: - services = module.managed_services - except AttributeError: - services = None + components = module.app.get_components_of_type(Daemon) + services = [] + for component in components: + services.append(component.unit) # filter out apps not setup yet if module.setup_helper.get_state() == 'needs-setup': From 1c2a5f08250ebdcf333c00babd06e7fa8650c6bd Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Nov 2021 10:44:27 -0800 Subject: [PATCH 17/53] daemon: Add new component to hold information about related daemons Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/daemon.py | 27 +++++++++++++++++++++++++++ plinth/tests/test_daemon.py | 14 ++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/plinth/daemon.py b/plinth/daemon.py index 08a03ca9a..5d1935e83 100644 --- a/plinth/daemon.py +++ b/plinth/daemon.py @@ -107,6 +107,33 @@ class Daemon(app.LeaderComponent): return [testname, result] +class RelatedDaemon(app.FollowerComponent): + """Component to hold information about additional systemd units handled. + + Unlike a daemon described by the Daemon component which is enabled/disabled + when the app is enabled/disabled, the daemon described by this component is + unaffected by the app's enabled/disabled status. The app only has an + indirect interest in this daemon. + + This component primarily holds information about such daemon and does + nothing else. This information is used to check if the app is allowed to + perform operations on the daemon. + """ + + def __init__(self, component_id, unit): + """Initialize a new related daemon component. + + 'component_id' must be a unique string across all apps and components + of a app. Conventionally starts with 'related-daemon-'. + + 'unit' must the name of systemd unit. + + """ + super().__init__(component_id) + + self.unit = unit + + def app_is_running(app_): """Return whether all the daemons in the app are running.""" for component in app_.components.values(): diff --git a/plinth/tests/test_daemon.py b/plinth/tests/test_daemon.py index 8fc1018eb..c28d73db2 100644 --- a/plinth/tests/test_daemon.py +++ b/plinth/tests/test_daemon.py @@ -9,8 +9,8 @@ from unittest.mock import Mock, call, patch import pytest from plinth.app import App, FollowerComponent -from plinth.daemon import (Daemon, app_is_running, diagnose_netcat, - diagnose_port_listening) +from plinth.daemon import (Daemon, RelatedDaemon, app_is_running, + diagnose_netcat, diagnose_port_listening) @pytest.fixture(name='daemon') @@ -228,3 +228,13 @@ def test_diagnose_netcat(popen): result = diagnose_netcat('test-host', 3300, input='test-input', negate=True) assert result == ['Cannot connect to test-host:3300', 'passed'] + + +def test_related_daemon_initialization(): + """Test that initializing related daemon works.""" + component = RelatedDaemon('test-component', 'test-daemon') + assert component.component_id == 'test-component' + assert component.unit == 'test-daemon' + + with pytest.raises(ValueError): + RelatedDaemon(None, 'test-daemon') From ba4b58de78af3fc27984b99d1022b8a0518aefcd Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 14:43:19 -0800 Subject: [PATCH 18/53] actions/service: Drop use of managed_services for Daemon component Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/service | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/actions/service b/actions/service index 7588321db..dab13c73e 100755 --- a/actions/service +++ b/actions/service @@ -6,9 +6,10 @@ Wrapper to list and handle system services import argparse import os -from importlib import import_module -from plinth import action_utils, cfg +from plinth import action_utils, cfg, module_loader +from plinth.app import App +from plinth.daemon import Daemon, RelatedDaemon cfg.read() module_config_path = os.path.join(cfg.config_dir, 'modules-enabled') @@ -85,34 +86,21 @@ def subcommand_is_running(arguments): print(action_utils.service_is_running(arguments.service)) -def _get_managed_services_of_module(modulepath): - """Import a module and return content of its 'managed_services' variable""" - try: - module = import_module(modulepath) - except ImportError: - return [] - else: - return getattr(module, 'managed_services', []) - - def _get_managed_services(): - """ - Get a set of all services managed by FreedomBox. - - This collects all service-names inside the 'managed_services' variable of - modules inside 'module_config_path' - """ + """Get a set of all services managed by FreedomBox.""" services = set() - for filename in os.listdir(module_config_path): - # Omit hidden files - if filename.startswith('.'): - continue + module_loader.load_modules() + module_loader.apps_init() + for app in App.list(): + components = app.get_components_of_type(Daemon) + for component in components: + services.add(component.unit) + if component.alias: + services.add(component.alias) - filepath = os.path.join(module_config_path, filename) - if os.path.isfile(filepath): - with open(filepath, 'r') as f: - modulepath = f.read().strip() - services.update(_get_managed_services_of_module(modulepath)) + components = app.get_components_of_type(RelatedDaemon) + for component in components: + services.add(component.unit) return services From 781d8fa18bba2e6236c28e535eecca0850756001 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 16:20:45 -0800 Subject: [PATCH 19/53] *: Drop use of managed_services, rely on Daemon component Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/shadowsocks | 10 ++++---- plinth/modules/apache/__init__.py | 11 +++++---- plinth/modules/avahi/__init__.py | 4 +--- plinth/modules/bind/__init__.py | 8 ++----- plinth/modules/calibre/__init__.py | 10 ++++---- plinth/modules/cockpit/__init__.py | 10 ++++---- plinth/modules/config/__init__.py | 9 ++++++-- plinth/modules/coturn/__init__.py | 29 +++++++++++++++--------- plinth/modules/datetime/__init__.py | 4 +--- plinth/modules/deluge/__init__.py | 6 ++--- plinth/modules/diaspora/__init__.py | 4 +--- plinth/modules/ejabberd/__init__.py | 11 +++++---- plinth/modules/email_server/__init__.py | 9 ++++---- plinth/modules/firewall/__init__.py | 4 +--- plinth/modules/i2p/__init__.py | 7 +----- plinth/modules/infinoted/__init__.py | 4 +--- plinth/modules/matrixsynapse/__init__.py | 6 ++--- plinth/modules/mediawiki/__init__.py | 4 +--- plinth/modules/minetest/__init__.py | 4 +--- plinth/modules/minidlna/__init__.py | 4 +--- plinth/modules/mldonkey/__init__.py | 8 +++---- plinth/modules/mumble/__init__.py | 11 ++++----- plinth/modules/openvpn/__init__.py | 4 +--- plinth/modules/pagekite/__init__.py | 8 +++---- plinth/modules/performance/__init__.py | 12 ++++------ plinth/modules/privoxy/__init__.py | 4 +--- plinth/modules/quassel/__init__.py | 6 ++--- plinth/modules/samba/__init__.py | 11 ++++----- plinth/modules/security/__init__.py | 7 +++--- plinth/modules/shadowsocks/__init__.py | 6 ++--- plinth/modules/ssh/__init__.py | 4 +--- plinth/modules/syncthing/__init__.py | 8 +++---- plinth/modules/tahoe/__init__.py | 4 +--- plinth/modules/tor/__init__.py | 4 +--- plinth/modules/transmission/__init__.py | 8 +++---- plinth/modules/ttrss/__init__.py | 4 +--- plinth/modules/upgrades/__init__.py | 7 ++++-- plinth/modules/users/__init__.py | 6 ++--- plinth/modules/wordpress/__init__.py | 4 +--- 39 files changed, 123 insertions(+), 161 deletions(-) diff --git a/actions/shadowsocks b/actions/shadowsocks index 61a98bfd5..d5bd93bfe 100755 --- a/actions/shadowsocks +++ b/actions/shadowsocks @@ -14,7 +14,7 @@ import sys from shutil import move from plinth import action_utils -from plinth.modules import shadowsocks +from plinth.modules.shadowsocks import ShadowsocksApp SHADOWSOCKS_CONFIG_SYMLINK = '/etc/shadowsocks-libev/freedombox.json' SHADOWSOCKS_CONFIG_ACTUAL = \ @@ -76,8 +76,8 @@ def subcommand_setup(_): if not wrong_state_dir.is_symlink() and wrong_state_dir.is_dir(): wrong_state_dir.rmdir() - if action_utils.service_is_enabled(shadowsocks.managed_services[0]): - action_utils.service_restart(shadowsocks.managed_services[0]) + if action_utils.service_is_enabled(ShadowsocksApp.DAEMON): + action_utils.service_restart(ShadowsocksApp.DAEMON) def subcommand_get_config(_): @@ -110,8 +110,8 @@ def subcommand_merge_config(_): # Don't try_restart because initial configuration may not be valid so # shadowsocks will not be running even when enabled. - if action_utils.service_is_enabled(shadowsocks.managed_services[0]): - action_utils.service_restart(shadowsocks.managed_services[0]) + if action_utils.service_is_enabled(ShadowsocksApp.DAEMON): + action_utils.service_restart(ShadowsocksApp.DAEMON) def main(): diff --git a/plinth/modules/apache/__init__.py b/plinth/modules/apache/__init__.py index 995a9dd2c..ba49356d7 100644 --- a/plinth/modules/apache/__init__.py +++ b/plinth/modules/apache/__init__.py @@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _ from plinth import actions from plinth import app as app_module from plinth import cfg -from plinth.daemon import Daemon +from plinth.daemon import Daemon, RelatedDaemon from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt from plinth.package import Packages @@ -19,8 +19,6 @@ version = 9 is_essential = True -managed_services = ['apache2', 'uwsgi'] - app = None @@ -55,10 +53,13 @@ class ApacheApp(app_module.App): self.add(freedombox_ports) letsencrypt = LetsEncrypt('letsencrypt-apache', domains='*', - daemons=[managed_services[0]]) + daemons=['apache2']) self.add(letsencrypt) - daemon = Daemon('daemon-apache', managed_services[0]) + daemon = Daemon('daemon-apache', 'apache2') + self.add(daemon) + + daemon = RelatedDaemon('related-daemon-apache', 'uwsgi') self.add(daemon) diff --git a/plinth/modules/avahi/__init__.py b/plinth/modules/avahi/__init__.py index d0dabaec1..32e3e4313 100644 --- a/plinth/modules/avahi/__init__.py +++ b/plinth/modules/avahi/__init__.py @@ -27,8 +27,6 @@ is_essential = True depends = ['names'] -managed_services = ['avahi-daemon'] - _description = [ format_lazy( _('Service discovery allows other devices on the network to ' @@ -74,7 +72,7 @@ class AvahiApp(app_module.App): is_external=False) self.add(firewall) - daemon = Daemon('daemon-avahi', managed_services[0]) + daemon = Daemon('daemon-avahi', 'avahi-daemon') self.add(daemon) backup_restore = BackupRestore('backup-restore-avahi', diff --git a/plinth/modules/bind/__init__.py b/plinth/modules/bind/__init__.py index 2de4e1bb8..3c2bb37c5 100644 --- a/plinth/modules/bind/__init__.py +++ b/plinth/modules/bind/__init__.py @@ -23,8 +23,6 @@ from . import manifest version = 2 -managed_services = ['named'] - _description = [ _('BIND enables you to publish your Domain Name System (DNS) information ' 'on the Internet, and to resolve DNS queries for your user devices on ' @@ -93,10 +91,8 @@ class BindApp(app_module.App): self.add(firewall) daemon = Daemon( - 'daemon-bind', managed_services[0], listen_ports=[(53, 'tcp6'), - (53, 'udp6'), - (53, 'tcp4'), - (53, 'udp4')]) + 'daemon-bind', 'named', listen_ports=[(53, 'tcp6'), (53, 'udp6'), + (53, 'tcp4'), (53, 'udp4')]) self.add(daemon) backup_restore = BackupRestore('backup-restore-bind', diff --git a/plinth/modules/calibre/__init__.py b/plinth/modules/calibre/__init__.py index 22c8a9165..ffee9a87b 100644 --- a/plinth/modules/calibre/__init__.py +++ b/plinth/modules/calibre/__init__.py @@ -23,8 +23,6 @@ from . import manifest version = 1 -managed_services = ['calibre-server-freedombox'] - _description = [ format_lazy( _('calibre server provides online access to your e-book collection. ' @@ -50,6 +48,8 @@ class CalibreApp(app_module.App): app_id = 'calibre' + DAEMON = 'calibre-server-freedombox' + def __init__(self): """Create components for the app.""" super().__init__() @@ -88,7 +88,7 @@ class CalibreApp(app_module.App): urls=['https://{host}/calibre']) self.add(webserver) - daemon = Daemon('daemon-calibre', managed_services[0], + daemon = Daemon('daemon-calibre', self.DAEMON, listen_ports=[(8844, 'tcp4')]) self.add(daemon) @@ -123,10 +123,10 @@ def list_libraries(): def create_library(name): """Create an empty library.""" actions.superuser_run('calibre', ['create-library', name]) - actions.superuser_run('service', ['try-restart', managed_services[0]]) + actions.superuser_run('service', ['try-restart', CalibreApp.DAEMON]) def delete_library(name): """Delete a library and its contents.""" actions.superuser_run('calibre', ['delete-library', name]) - actions.superuser_run('service', ['try-restart', managed_services[0]]) + actions.superuser_run('service', ['try-restart', CalibreApp.DAEMON]) diff --git a/plinth/modules/cockpit/__init__.py b/plinth/modules/cockpit/__init__.py index 5fc886b69..f5817894d 100644 --- a/plinth/modules/cockpit/__init__.py +++ b/plinth/modules/cockpit/__init__.py @@ -24,8 +24,6 @@ version = 1 is_essential = True -managed_services = ['cockpit.socket'] - _description = [ format_lazy( _('Cockpit is a server manager that makes it easy to administer ' @@ -56,6 +54,8 @@ class CockpitApp(app_module.App): app_id = 'cockpit' + DAEMON = 'cockpit.socket' + def __init__(self): """Create components for the app.""" super().__init__() @@ -92,7 +92,7 @@ class CockpitApp(app_module.App): urls=['https://{host}/_cockpit/']) self.add(webserver) - daemon = Daemon('daemon-cockpit', managed_services[0]) + daemon = Daemon('daemon-cockpit', self.DAEMON) self.add(daemon) backup_restore = BackupRestore('backup-restore-cockpit', @@ -123,7 +123,7 @@ def on_domain_added(sender, domain_type, name, description='', services=None, if name not in utils.get_domains(): actions.superuser_run('cockpit', ['add-domain', name]) actions.superuser_run('service', - ['try-restart', managed_services[0]]) + ['try-restart', CockpitApp.DAEMON]) def on_domain_removed(sender, domain_type, name, **kwargs): @@ -133,4 +133,4 @@ def on_domain_removed(sender, domain_type, name, **kwargs): if name in utils.get_domains(): actions.superuser_run('cockpit', ['remove-domain', name]) actions.superuser_run('service', - ['try-restart', managed_services[0]]) + ['try-restart', CockpitApp.DAEMON]) diff --git a/plinth/modules/config/__init__.py b/plinth/modules/config/__init__.py index 169bc3fea..c1c200114 100644 --- a/plinth/modules/config/__init__.py +++ b/plinth/modules/config/__init__.py @@ -12,6 +12,7 @@ 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 RelatedDaemon from plinth.modules.apache import (get_users_with_website, user_of_uws_url, uws_url_of_user) from plinth.modules.names.components import DomainType @@ -22,8 +23,6 @@ version = 3 is_essential = True -managed_services = ['systemd-journald', 'rsyslog'] - _description = [ _('Here you can set some general configuration options ' 'like hostname, domain name, webserver home page etc.') @@ -66,6 +65,12 @@ class ConfigApp(app_module.App): packages = Packages('packages-config', ['zram-tools']) self.add(packages) + daemon1 = RelatedDaemon('related-daemon-config1', 'systemd-journald') + self.add(daemon1) + + daemon2 = RelatedDaemon('related-daemon-config2', 'rsyslog') + self.add(daemon2) + domain_type = DomainType('domain-type-static', _('Domain Name'), 'config:index', can_have_certificate=True) self.add(domain_type) diff --git a/plinth/modules/coturn/__init__.py b/plinth/modules/coturn/__init__.py index a774049d5..dadca2b8c 100644 --- a/plinth/modules/coturn/__init__.py +++ b/plinth/modules/coturn/__init__.py @@ -27,8 +27,6 @@ from . import manifest version = 1 -managed_services = ['coturn'] - managed_paths = [pathlib.Path('/etc/coturn/')] _description = [ @@ -76,8 +74,8 @@ class CoturnApp(app_module.App): self.add(firewall) letsencrypt = LetsEncrypt( - 'letsencrypt-coturn', domains=get_domains, - daemons=managed_services, should_copy_certificates=True, + 'letsencrypt-coturn', domains=get_domains, daemons=['coturn'], + should_copy_certificates=True, private_key_path='/etc/coturn/certs/pkey.pem', certificate_path='/etc/coturn/certs/cert.pem', user_owner='turnserver', group_owner='turnserver', @@ -85,13 +83,22 @@ class CoturnApp(app_module.App): self.add(letsencrypt) daemon = Daemon( - 'daemon-coturn', managed_services[0], - listen_ports=[(3478, 'udp4'), (3478, 'udp6'), (3478, 'tcp4'), - (3478, 'tcp6'), (3479, 'udp4'), (3479, 'udp6'), - (3479, 'tcp4'), (3479, 'tcp6'), (5349, 'udp4'), - (5349, 'udp6'), (5349, 'tcp4'), (5349, 'tcp6'), - (5350, 'udp4'), (5350, 'udp6'), (5350, 'tcp4'), - (5350, 'tcp6')]) + 'daemon-coturn', 'coturn', listen_ports=[(3478, 'udp4'), + (3478, 'udp6'), + (3478, 'tcp4'), + (3478, 'tcp6'), + (3479, 'udp4'), + (3479, 'udp6'), + (3479, 'tcp4'), + (3479, 'tcp6'), + (5349, 'udp4'), + (5349, 'udp6'), + (5349, 'tcp4'), + (5349, 'tcp6'), + (5350, 'udp4'), + (5350, 'udp6'), + (5350, 'tcp4'), + (5350, 'tcp6')]) self.add(daemon) users_and_groups = UsersAndGroups('users-and-groups-coturn', diff --git a/plinth/modules/datetime/__init__.py b/plinth/modules/datetime/__init__.py index 7110aedd5..aefdec394 100644 --- a/plinth/modules/datetime/__init__.py +++ b/plinth/modules/datetime/__init__.py @@ -18,8 +18,6 @@ version = 2 is_essential = True -managed_services = ['systemd-timesyncd'] - _description = [ _('Network time server is a program that maintains the system time ' 'in synchronization with servers on the Internet.') @@ -76,7 +74,7 @@ class DateTimeApp(app_module.App): self.add(menu_item) if self._is_time_managed(): - daemon = Daemon('daemon-datetime', managed_services[0]) + daemon = Daemon('daemon-datetime', 'systemd-timesyncd') self.add(daemon) backup_restore = BackupRestore('backup-restore-datetime', diff --git a/plinth/modules/deluge/__init__.py b/plinth/modules/deluge/__init__.py index c563f81d4..68be81004 100644 --- a/plinth/modules/deluge/__init__.py +++ b/plinth/modules/deluge/__init__.py @@ -20,8 +20,6 @@ from . import manifest version = 6 -managed_services = ['deluged', 'deluge-web'] - _description = [ _('Deluge is a BitTorrent client that features a Web UI.'), _('The default password is \'deluge\', but you should log in and ' @@ -79,11 +77,11 @@ class DelugeApp(app_module.App): urls=['https://{host}/deluge']) self.add(webserver) - daemon = Daemon('daemon-deluged', managed_services[0], + daemon = Daemon('daemon-deluged', 'deluged', listen_ports=[(58846, 'tcp4')]) self.add(daemon) - daemon_web = Daemon('daemon-deluge-web', managed_services[1], + daemon_web = Daemon('daemon-deluge-web', 'deluge-web', listen_ports=[(8112, 'tcp4')]) self.add(daemon_web) diff --git a/plinth/modules/diaspora/__init__.py b/plinth/modules/diaspora/__init__.py index 53f39f216..a144fee75 100644 --- a/plinth/modules/diaspora/__init__.py +++ b/plinth/modules/diaspora/__init__.py @@ -38,8 +38,6 @@ def get_configured_domain_name(): version = 1 -managed_services = ['diaspora'] - _description = [ _('diaspora* is a decentralized social network where you can store ' 'and control your own data.'), @@ -92,7 +90,7 @@ class DiasporaApp(app_module.App): webserver = Webserver('webserver-diaspora', 'diaspora-plinth') self.add(webserver) - daemon = Daemon('daemon-diaspora', managed_services[0]) + daemon = Daemon('daemon-diaspora', 'diaspora') self.add(daemon) def diagnose(self): diff --git a/plinth/modules/ejabberd/__init__.py b/plinth/modules/ejabberd/__init__.py index 48ca1a85f..5926d5441 100644 --- a/plinth/modules/ejabberd/__init__.py +++ b/plinth/modules/ejabberd/__init__.py @@ -30,8 +30,6 @@ from . import manifest version = 4 -managed_services = ['ejabberd'] - managed_paths = [pathlib.Path('/etc/ejabberd/')] _description = [ @@ -110,9 +108,12 @@ class EjabberdApp(app_module.App): self.add(letsencrypt) daemon = Daemon( - 'daemon-ejabberd', managed_services[0], - listen_ports=[(5222, 'tcp4'), (5222, 'tcp6'), (5269, 'tcp4'), - (5269, 'tcp6'), (5443, 'tcp4'), (5443, 'tcp6')]) + 'daemon-ejabberd', 'ejabberd', listen_ports=[(5222, 'tcp4'), + (5222, 'tcp6'), + (5269, 'tcp4'), + (5269, 'tcp6'), + (5443, 'tcp4'), + (5443, 'tcp6')]) self.add(daemon) users_and_groups = UsersAndGroups('users-and-groups-ejabberd', diff --git a/plinth/modules/email_server/__init__.py b/plinth/modules/email_server/__init__.py index 2d21fe87f..83311b4fa 100644 --- a/plinth/modules/email_server/__init__.py +++ b/plinth/modules/email_server/__init__.py @@ -35,8 +35,6 @@ port_info = { 'dovecot': ('imaps', 993, 'pop3s', 995), } -managed_services = ['postfix', 'dovecot', 'rspamd'] - _description = [ _('Roundcube app provides web ' 'interface for users to access email.'), @@ -105,7 +103,7 @@ class EmailServerApp(plinth.app.App): self.add(menu_item) def _add_daemons(self): - for srvname in managed_services: + for srvname in ['postfix', 'dovecot', 'rspamd']: # Construct `listen_ports` parameter for the daemon mixed = port_info.get(srvname, ()) port_numbers = [v for v in mixed if isinstance(v, int)] @@ -168,8 +166,9 @@ def setup(helper, old_version=None): helper.call('post', audit.rcube.repair) # Reload - for srvname in managed_services: - actions.superuser_run('service', ['reload', srvname]) + actions.superuser_run('service', ['reload', 'postfix']) + actions.superuser_run('service', ['reload', 'dovecot']) + actions.superuser_run('service', ['reload', 'rspamd']) # Expose to public internet helper.call('post', app.enable) diff --git a/plinth/modules/firewall/__init__.py b/plinth/modules/firewall/__init__.py index c05e1cdac..343f19be5 100644 --- a/plinth/modules/firewall/__init__.py +++ b/plinth/modules/firewall/__init__.py @@ -25,8 +25,6 @@ version = 2 is_essential = True -managed_services = ['firewalld'] - _description = [ format_lazy( _('Firewall is a security system that controls the incoming and ' @@ -75,7 +73,7 @@ class FirewallApp(app_module.App): packages = Packages('packages-firewall', ['firewalld', 'nftables']) self.add(packages) - daemon = Daemon('daemon-firewall', managed_services[0]) + daemon = Daemon('daemon-firewall', 'firewalld') self.add(daemon) backup_restore = BackupRestore('backup-restore-firewall', diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py index ac60af7da..5a477c355 100644 --- a/plinth/modules/i2p/__init__.py +++ b/plinth/modules/i2p/__init__.py @@ -20,10 +20,6 @@ from . import manifest version = 1 -service_name = 'i2p' - -managed_services = [service_name] - _description = [ _('The Invisible Internet Project is an anonymous network layer intended ' 'to protect communication from censorship and surveillance. I2P ' @@ -92,8 +88,7 @@ class I2PApp(app_module.App): urls=['https://{host}/i2p/']) self.add(webserver) - daemon = Daemon('daemon-i2p', managed_services[0], - listen_ports=[(7657, 'tcp6')]) + daemon = Daemon('daemon-i2p', 'i2p', listen_ports=[(7657, 'tcp6')]) self.add(daemon) users_and_groups = UsersAndGroups('users-and-groups-i2p', diff --git a/plinth/modules/infinoted/__init__.py b/plinth/modules/infinoted/__init__.py index 36a76503d..b02009b36 100644 --- a/plinth/modules/infinoted/__init__.py +++ b/plinth/modules/infinoted/__init__.py @@ -19,8 +19,6 @@ from . import manifest version = 3 -managed_services = ['infinoted'] - _description = [ _('infinoted is a server for Gobby, a collaborative text editor.'), format_lazy( @@ -70,7 +68,7 @@ class InfinotedApp(app_module.App): ports=['infinoted-plinth'], is_external=True) self.add(firewall) - daemon = Daemon('daemon-infinoted', managed_services[0], + daemon = Daemon('daemon-infinoted', 'infinoted', listen_ports=[(6523, 'tcp4'), (6523, 'tcp6')]) self.add(daemon) diff --git a/plinth/modules/matrixsynapse/__init__.py b/plinth/modules/matrixsynapse/__init__.py index 5736d1ea5..a3c9c5481 100644 --- a/plinth/modules/matrixsynapse/__init__.py +++ b/plinth/modules/matrixsynapse/__init__.py @@ -28,8 +28,6 @@ from . import manifest version = 7 -managed_services = ['matrix-synapse'] - managed_paths = [pathlib.Path('/etc/matrix-synapse/')] _description = [ @@ -107,14 +105,14 @@ class MatrixSynapseApp(app_module.App): letsencrypt = LetsEncrypt( 'letsencrypt-matrixsynapse', domains=get_domains, - daemons=[managed_services[0]], should_copy_certificates=True, + daemons=['matrix-synapse'], should_copy_certificates=True, private_key_path='/etc/matrix-synapse/homeserver.tls.key', certificate_path='/etc/matrix-synapse/homeserver.tls.crt', user_owner='matrix-synapse', group_owner='nogroup', managing_app='matrixsynapse') self.add(letsencrypt) - daemon = Daemon('daemon-matrixsynapse', managed_services[0], + daemon = Daemon('daemon-matrixsynapse', 'matrix-synapse', listen_ports=[(8008, 'tcp4'), (8448, 'tcp4')]) self.add(daemon) diff --git a/plinth/modules/mediawiki/__init__.py b/plinth/modules/mediawiki/__init__.py index e78069ba0..387b14d6d 100644 --- a/plinth/modules/mediawiki/__init__.py +++ b/plinth/modules/mediawiki/__init__.py @@ -21,8 +21,6 @@ from . import manifest version = 10 -managed_services = ['mediawiki-jobrunner'] - _description = [ _('MediaWiki is the wiki engine that powers Wikipedia and other WikiMedia ' 'projects. A wiki engine is a program for creating a collaboratively ' @@ -89,7 +87,7 @@ class MediaWikiApp(app_module.App): 'mediawiki-freedombox') self.add(webserver) - daemon = Daemon('daemon-mediawiki', managed_services[0]) + daemon = Daemon('daemon-mediawiki', 'mediawiki-jobrunner') self.add(daemon) backup_restore = BackupRestore('backup-restore-mediawiki', diff --git a/plinth/modules/minetest/__init__.py b/plinth/modules/minetest/__init__.py index 3d0027d6e..f56d48f0d 100644 --- a/plinth/modules/minetest/__init__.py +++ b/plinth/modules/minetest/__init__.py @@ -20,8 +20,6 @@ from . import manifest version = 2 -managed_services = ['minetest-server'] - _mods = [ 'minetest-mod-character-creator', 'minetest-mod-craftguide', 'minetest-mod-infinite-chest', 'minetest-mod-lucky-block', @@ -85,7 +83,7 @@ class MinetestApp(app_module.App): ports=['minetest-plinth'], is_external=True) self.add(firewall) - daemon = Daemon('daemon-minetest', managed_services[0], + daemon = Daemon('daemon-minetest', 'minetest-server', listen_ports=[(30000, 'udp4')]) self.add(daemon) diff --git a/plinth/modules/minidlna/__init__.py b/plinth/modules/minidlna/__init__.py index b256e2144..680c5d089 100644 --- a/plinth/modules/minidlna/__init__.py +++ b/plinth/modules/minidlna/__init__.py @@ -18,8 +18,6 @@ from . import manifest version = 2 -managed_services = ['minidlna'] - _description = [ _('MiniDLNA is a simple media server software, with the aim of being ' 'fully compliant with DLNA/UPnP-AV clients. ' @@ -81,7 +79,7 @@ class MiniDLNAApp(app_module.App): urls=['http://localhost:8200/']) self.add(webserver) - daemon = Daemon('daemon-minidlna', managed_services[0]) + daemon = Daemon('daemon-minidlna', 'minidlna') self.add(daemon) backup_restore = BackupRestore('backup-restore-minidlna', diff --git a/plinth/modules/mldonkey/__init__.py b/plinth/modules/mldonkey/__init__.py index 1a11f6c3a..1b5934230 100644 --- a/plinth/modules/mldonkey/__init__.py +++ b/plinth/modules/mldonkey/__init__.py @@ -21,8 +21,6 @@ from . import manifest version = 2 -managed_services = ['mldonkey-server'] - _description = [ _('MLDonkey is a peer-to-peer file sharing application used to exchange ' 'large files. It can participate in multiple peer-to-peer networks ' @@ -46,6 +44,8 @@ class MLDonkeyApp(app_module.App): app_id = 'mldonkey' + DAEMON = 'mldonkey-server' + def __init__(self): """Create components for the app.""" super().__init__() @@ -83,7 +83,7 @@ class MLDonkeyApp(app_module.App): urls=['https://{host}/mldonkey/']) self.add(webserver) - daemon = Daemon('daemon-mldonkey', managed_services[0], + daemon = Daemon('daemon-mldonkey', self.DAEMON, listen_ports=[(4080, 'tcp4')]) self.add(daemon) @@ -104,4 +104,4 @@ def setup(helper, old_version=None): if not old_version: helper.call('post', app.enable) - add_user_to_share_group(_SYSTEM_USER, managed_services[0]) + add_user_to_share_group(_SYSTEM_USER, MLDonkeyApp.DAEMON) diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py index 4321c7f87..a60ab450d 100644 --- a/plinth/modules/mumble/__init__.py +++ b/plinth/modules/mumble/__init__.py @@ -24,8 +24,6 @@ from . import manifest version = 2 -managed_services = ['mumble-server'] - managed_paths = [pathlib.Path('/var/lib/mumble-server')] _description = [ @@ -76,7 +74,7 @@ class MumbleApp(app_module.App): letsencrypt = LetsEncrypt( 'letsencrypt-mumble', domains=get_domains, - daemons=managed_services, should_copy_certificates=True, + daemons=['mumble-server'], should_copy_certificates=True, private_key_path='/var/lib/mumble-server/privkey.pem', certificate_path='/var/lib/mumble-server/fullchain.pem', user_owner='mumble-server', group_owner='mumble-server', @@ -84,9 +82,10 @@ class MumbleApp(app_module.App): self.add(letsencrypt) daemon = Daemon( - 'daemon-mumble', managed_services[0], - listen_ports=[(64738, 'tcp4'), (64738, 'tcp6'), (64738, 'udp4'), - (64738, 'udp6')]) + 'daemon-mumble', 'mumble-server', listen_ports=[(64738, 'tcp4'), + (64738, 'tcp6'), + (64738, 'udp4'), + (64738, 'udp6')]) self.add(daemon) users_and_groups = UsersAndGroups('users-and-groups-mumble', diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py index a3ed018b4..a156d110d 100644 --- a/plinth/modules/openvpn/__init__.py +++ b/plinth/modules/openvpn/__init__.py @@ -22,8 +22,6 @@ from . import manifest version = 4 -managed_services = ['openvpn-server@freedombox'] - _description = [ format_lazy( _('Virtual Private Network (VPN) is a technique for securely ' @@ -87,7 +85,7 @@ class OpenVPNApp(app_module.App): is_external=True) self.add(firewall) - daemon = Daemon('daemon-openvpn', managed_services[0], + daemon = Daemon('daemon-openvpn', 'openvpn-server@freedombox', listen_ports=[(1194, 'udp4'), (1194, 'udp6')]) self.add(daemon) diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py index bef97337a..531f5393e 100644 --- a/plinth/modules/pagekite/__init__.py +++ b/plinth/modules/pagekite/__init__.py @@ -20,8 +20,6 @@ version = 2 depends = ['names'] -managed_services = ['pagekite'] - _description = [ format_lazy( _('PageKite is a system for exposing {box_name} services when ' @@ -56,6 +54,8 @@ class PagekiteApp(app_module.App): app_id = 'pagekite' + DAEMON = 'pagekite' + def __init__(self): """Create components for the app.""" super().__init__() @@ -80,7 +80,7 @@ class PagekiteApp(app_module.App): 'pagekite:index', can_have_certificate=True) self.add(domain_type) - daemon = Daemon('daemon-pagekite', managed_services[0]) + daemon = Daemon('daemon-pagekite', self.DAEMON) self.add(daemon) backup_restore = BackupRestore('backup-restore-pagekite', @@ -112,4 +112,4 @@ def setup(helper, old_version=None): helper.call('post', app.enable) if old_version == 1: - actions.superuser_run('service', ['try-restart', managed_services[0]]) + actions.superuser_run('service', ['try-restart', PagekiteApp.DAEMON]) diff --git a/plinth/modules/performance/__init__.py b/plinth/modules/performance/__init__.py index 9bdb5bd4c..0901ece69 100644 --- a/plinth/modules/performance/__init__.py +++ b/plinth/modules/performance/__init__.py @@ -17,10 +17,6 @@ version = 1 name = _('Performance') -managed_services = [ - 'pmcd.service', 'pmie.service', 'pmlogger.service', 'pmproxy.service' -] - _description = [ _('Performance app allows you to collect, store and view information ' 'about utilization of the hardware. This can give you basic insights ' @@ -62,19 +58,19 @@ class PerformanceApp(app_module.App): **manifest.backup) self.add(backup_restore) - daemon_0 = Daemon('daemon-performance-0', managed_services[0], + daemon_0 = Daemon('daemon-performance-0', 'pmcd.service', listen_ports=None) self.add(daemon_0) - daemon_1 = Daemon('daemon-performance-1', managed_services[1], + daemon_1 = Daemon('daemon-performance-1', 'pmie.service', listen_ports=None) self.add(daemon_1) - daemon_2 = Daemon('daemon-performance-2', managed_services[2], + daemon_2 = Daemon('daemon-performance-2', 'pmlogger.service', listen_ports=None) self.add(daemon_2) - daemon_3 = Daemon('daemon-performance-3', managed_services[3], + daemon_3 = Daemon('daemon-performance-3', 'pmproxy.service', listen_ports=None) self.add(daemon_3) diff --git a/plinth/modules/privoxy/__init__.py b/plinth/modules/privoxy/__init__.py index def21d497..ee256a55e 100644 --- a/plinth/modules/privoxy/__init__.py +++ b/plinth/modules/privoxy/__init__.py @@ -23,8 +23,6 @@ version = 1 is_essential = False -managed_services = ['privoxy'] - _description = [ _('Privoxy is a non-caching web proxy with advanced filtering ' 'capabilities for enhancing privacy, modifying web page data and ' @@ -78,7 +76,7 @@ class PrivoxyApp(app_module.App): is_external=False) self.add(firewall) - daemon = Daemon('daemon-privoxy', managed_services[0], + daemon = Daemon('daemon-privoxy', 'privoxy', listen_ports=[(8118, 'tcp4'), (8118, 'tcp6')]) self.add(daemon) diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index f15f86f56..a7e0c1764 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -24,8 +24,6 @@ from . import manifest version = 1 -managed_services = ['quasselcore'] - managed_paths = [pathlib.Path('/var/lib/quassel/')] _description = [ @@ -85,14 +83,14 @@ class QuasselApp(app_module.App): letsencrypt = LetsEncrypt( 'letsencrypt-quassel', domains=get_domains, - daemons=managed_services, should_copy_certificates=True, + daemons=['quasselcore'], should_copy_certificates=True, private_key_path='/var/lib/quassel/quasselCert.pem', certificate_path='/var/lib/quassel/quasselCert.pem', user_owner='quasselcore', group_owner='quassel', managing_app='quassel') self.add(letsencrypt) - daemon = Daemon('daemon-quassel', managed_services[0], + daemon = Daemon('daemon-quassel', 'quasselcore', listen_ports=[(4242, 'tcp4'), (4242, 'tcp6')]) self.add(daemon) diff --git a/plinth/modules/samba/__init__.py b/plinth/modules/samba/__init__.py index 22f131460..dc31a9608 100644 --- a/plinth/modules/samba/__init__.py +++ b/plinth/modules/samba/__init__.py @@ -25,8 +25,6 @@ from . import manifest version = 2 -managed_services = ['smbd', 'nmbd'] - _description = [ _('Samba allows to share files and folders between FreedomBox and ' 'other computers in your local network.'), @@ -85,13 +83,12 @@ class SambaApp(app_module.App): self.add(firewall) daemon = Daemon( - 'daemon-samba', managed_services[0], listen_ports=[(139, 'tcp4'), - (139, 'tcp6'), - (445, 'tcp4'), - (445, 'tcp6')]) + 'daemon-samba', 'smbd', listen_ports=[(139, 'tcp4'), (139, 'tcp6'), + (445, 'tcp4'), + (445, 'tcp6')]) self.add(daemon) - daemon_nmbd = Daemon('daemon-samba-nmbd', managed_services[1], + daemon_nmbd = Daemon('daemon-samba-nmbd', 'nmbd', listen_ports=[(137, 'udp4'), (138, 'udp4')]) self.add(daemon_nmbd) diff --git a/plinth/modules/security/__init__.py b/plinth/modules/security/__init__.py index 88c1aca8e..ed32c4b95 100644 --- a/plinth/modules/security/__init__.py +++ b/plinth/modules/security/__init__.py @@ -12,7 +12,7 @@ from django.utils.translation import gettext_lazy as _ from plinth import actions from plinth import app as app_module from plinth import menu, module_loader -from plinth.daemon import Daemon +from plinth.daemon import Daemon, RelatedDaemon from plinth.modules.backups.components import BackupRestore from plinth.package import Packages @@ -22,8 +22,6 @@ version = 7 is_essential = True -managed_services = ['fail2ban'] - ACCESS_CONF_FILE = '/etc/security/access.d/50freedombox.conf' ACCESS_CONF_FILE_OLD = '/etc/security/access.conf' ACCESS_CONF_SNIPPET = '-:ALL EXCEPT root fbx plinth (admin) (sudo):ALL' @@ -54,6 +52,9 @@ class SecurityApp(app_module.App): packages = Packages('packages-security', ['fail2ban', 'debsecan']) self.add(packages) + daemon = RelatedDaemon('related-daemon-fail2ban', 'fail2ban') + self.add(daemon) + backup_restore = BackupRestore('backup-restore-security', **manifest.backup) self.add(backup_restore) diff --git a/plinth/modules/shadowsocks/__init__.py b/plinth/modules/shadowsocks/__init__.py index af4c2b516..a851cb600 100644 --- a/plinth/modules/shadowsocks/__init__.py +++ b/plinth/modules/shadowsocks/__init__.py @@ -19,8 +19,6 @@ from . import manifest version = 3 -managed_services = ['shadowsocks-libev-local@freedombox'] - _description = [ _('Shadowsocks is a lightweight and secure SOCKS5 proxy, designed to ' 'protect your Internet traffic. It can be used to bypass Internet ' @@ -43,6 +41,8 @@ class ShadowsocksApp(app_module.App): app_id = 'shadowsocks' + DAEMON = 'shadowsocks-libev-local@freedombox' + def __init__(self): """Create components for the app.""" super().__init__() @@ -76,7 +76,7 @@ class ShadowsocksApp(app_module.App): is_external=False) self.add(firewall) - daemon = Daemon('daemon-shadowsocks', managed_services[0], + daemon = Daemon('daemon-shadowsocks', self.DAEMON, listen_ports=[(1080, 'tcp4'), (1080, 'tcp6')]) self.add(daemon) diff --git a/plinth/modules/ssh/__init__.py b/plinth/modules/ssh/__init__.py index ebe2aa3aa..7b6bcd272 100644 --- a/plinth/modules/ssh/__init__.py +++ b/plinth/modules/ssh/__init__.py @@ -23,8 +23,6 @@ version = 1 is_essential = True -managed_services = ['ssh'] - _description = [ _('A Secure Shell server uses the secure shell protocol to accept ' 'connections from remote computers. An authorized remote computer ' @@ -61,7 +59,7 @@ class SSHApp(app_module.App): is_external=True) self.add(firewall) - daemon = Daemon('daemon-ssh', managed_services[0]) + daemon = Daemon('daemon-ssh', 'ssh') self.add(daemon) backup_restore = BackupRestore('backup-restore-ssh', **manifest.backup) diff --git a/plinth/modules/syncthing/__init__.py b/plinth/modules/syncthing/__init__.py index ecacc3a09..de89b2b68 100644 --- a/plinth/modules/syncthing/__init__.py +++ b/plinth/modules/syncthing/__init__.py @@ -21,8 +21,6 @@ from . import manifest version = 5 -managed_services = ['syncthing@syncthing'] - _description = [ _('Syncthing is an application to synchronize files across multiple ' 'devices, e.g. your desktop computer and mobile phone. Creation, ' @@ -49,6 +47,8 @@ class SyncthingApp(app_module.App): app_id = 'syncthing' + DAEMON = 'syncthing@syncthing' + def __init__(self): """Create components for the app.""" super().__init__() @@ -94,7 +94,7 @@ class SyncthingApp(app_module.App): urls=['https://{host}/syncthing/']) self.add(webserver) - daemon = Daemon('daemon-syncthing', managed_services[0]) + daemon = Daemon('daemon-syncthing', self.DAEMON) self.add(daemon) users_and_groups = UsersAndGroups('users-and-groups-syncthing', @@ -110,7 +110,7 @@ def setup(helper, old_version=None): """Install and configure the module.""" app.setup(old_version) helper.call('post', actions.superuser_run, 'syncthing', ['setup']) - add_user_to_share_group(SYSTEM_USER, managed_services[0]) + add_user_to_share_group(SYSTEM_USER, SyncthingApp.DAEMON) if not old_version: helper.call('post', app.enable) diff --git a/plinth/modules/tahoe/__init__.py b/plinth/modules/tahoe/__init__.py index ec654780d..98a0e0fcc 100644 --- a/plinth/modules/tahoe/__init__.py +++ b/plinth/modules/tahoe/__init__.py @@ -24,8 +24,6 @@ from .errors import TahoeConfigurationError version = 1 -managed_services = ['tahoe-lafs'] - _description = [ _('Tahoe-LAFS is a decentralized secure file storage system. ' 'It uses provider independent security to store files over a ' @@ -88,7 +86,7 @@ class TahoeApp(app_module.App): webserver = Webserver('webserver-tahoe', 'tahoe-plinth') self.add(webserver) - daemon = Daemon('daemon-tahoe', managed_services[0]) + daemon = Daemon('daemon-tahoe', 'tahoe-lafs') self.add(daemon) backup_restore = BackupRestore('backup-restore-tahoe', diff --git a/plinth/modules/tor/__init__.py b/plinth/modules/tor/__init__.py index f9fa4e5bb..3c58bd758 100644 --- a/plinth/modules/tor/__init__.py +++ b/plinth/modules/tor/__init__.py @@ -26,8 +26,6 @@ version = 5 depends = ['names'] -managed_services = ['tor@plinth'] - _description = [ _('Tor is an anonymous communication system. You can learn more ' 'about it from the Tor ' @@ -81,7 +79,7 @@ class TorApp(app_module.App): self.add(firewall) daemon = Daemon( - 'daemon-tor', managed_services[0], strict_check=True, + 'daemon-tor', 'tor@plinth', strict_check=True, listen_ports=[(9050, 'tcp4'), (9050, 'tcp6'), (9040, 'tcp4'), (9040, 'tcp6'), (9053, 'udp4'), (9053, 'udp6')]) self.add(daemon) diff --git a/plinth/modules/transmission/__init__.py b/plinth/modules/transmission/__init__.py index 2d6191138..7fc8363aa 100644 --- a/plinth/modules/transmission/__init__.py +++ b/plinth/modules/transmission/__init__.py @@ -22,8 +22,6 @@ from . import manifest version = 4 -managed_services = ['transmission-daemon'] - _description = [ _('Transmission is a BitTorrent client with a web interface.'), _('BitTorrent is a peer-to-peer file sharing protocol. ' @@ -41,6 +39,8 @@ class TransmissionApp(app_module.App): app_id = 'transmission' + DAEMON = 'transmission-daemon' + def __init__(self): """Create components for the app.""" super().__init__() @@ -83,7 +83,7 @@ class TransmissionApp(app_module.App): self.add(webserver) daemon = Daemon( - 'daemon-transmission', managed_services[0], listen_ports=[ + 'daemon-transmission', self.DAEMON, listen_ports=[ (9091, 'tcp4'), (51413, 'tcp4'), (51413, 'tcp6'), @@ -115,5 +115,5 @@ def setup(helper, old_version=None): helper.call('post', actions.superuser_run, 'transmission', ['merge-configuration'], input=json.dumps(new_configuration).encode()) - add_user_to_share_group(SYSTEM_USER, managed_services[0]) + add_user_to_share_group(SYSTEM_USER, TransmissionApp.DAEMON) helper.call('post', app.enable) diff --git a/plinth/modules/ttrss/__init__.py b/plinth/modules/ttrss/__init__.py index 0983224d5..6713e44b8 100644 --- a/plinth/modules/ttrss/__init__.py +++ b/plinth/modules/ttrss/__init__.py @@ -21,8 +21,6 @@ from . import manifest version = 3 -managed_services = ['tt-rss'] - _description = [ _('Tiny Tiny RSS is a news feed (RSS/Atom) reader and aggregator, ' 'designed to allow reading news from any location, while feeling as ' @@ -85,7 +83,7 @@ class TTRSSApp(app_module.App): urls=['https://{host}/tt-rss']) self.add(webserver) - daemon = Daemon('daemon-ttrss', managed_services[0]) + daemon = Daemon('daemon-ttrss', 'tt-rss') self.add(daemon) users_and_groups = UsersAndGroups('users-and-groups-ttrss', diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py index a8d539833..34c879297 100644 --- a/plinth/modules/upgrades/__init__.py +++ b/plinth/modules/upgrades/__init__.py @@ -16,6 +16,7 @@ import plinth from plinth import actions from plinth import app as app_module from plinth import cfg, glib, kvstore, menu +from plinth.daemon import RelatedDaemon from plinth.modules.backups.components import BackupRestore from plinth.package import Packages @@ -25,8 +26,6 @@ version = 9 is_essential = True -managed_services = ['freedombox-dist-upgrade'] - first_boot_steps = [ { 'id': 'backports_wizard', @@ -87,6 +86,10 @@ class UpgradesApp(app_module.App): ['unattended-upgrades', 'needrestart']) self.add(packages) + daemon = RelatedDaemon('related-daemon-upgrades', + 'freedombox-dist-upgrade') + self.add(daemon) + backup_restore = BackupRestore('backup-restore-upgrades', **manifest.backup) self.add(backup_restore) diff --git a/plinth/modules/users/__init__.py b/plinth/modules/users/__init__.py index fcba80e57..908cae761 100644 --- a/plinth/modules/users/__init__.py +++ b/plinth/modules/users/__init__.py @@ -21,8 +21,6 @@ version = 3 is_essential = True -managed_services = ['slapd'] - first_boot_steps = [ { 'id': 'users_firstboot', @@ -73,8 +71,8 @@ class UsersApp(app_module.App): ]) self.add(packages) - daemon = Daemon('daemon-users', managed_services[0], - listen_ports=[(389, 'tcp4'), (389, 'tcp6')]) + daemon = Daemon('daemon-users', 'slapd', listen_ports=[(389, 'tcp4'), + (389, 'tcp6')]) self.add(daemon) # Add the admin group diff --git a/plinth/modules/wordpress/__init__.py b/plinth/modules/wordpress/__init__.py index 5b825a2d5..707d2ec5e 100644 --- a/plinth/modules/wordpress/__init__.py +++ b/plinth/modules/wordpress/__init__.py @@ -21,8 +21,6 @@ PUBLIC_ACCESS_FILE = '/etc/wordpress/is_public' version = 1 -managed_services = ['wordpress-freedombox.timer'] - _description = [ _('WordPress is a popular way to create and manage websites and blogs. ' 'Content can be managed using a visual interface. Layout and ' @@ -102,7 +100,7 @@ class WordPressApp(app_module.App): urls=['https://{host}/wordpress/']) self.add(webserver) - daemon = Daemon('daemon-wordpress', managed_services[0]) + daemon = Daemon('daemon-wordpress', 'wordpress-freedombox.timer') self.add(daemon) backup_restore = WordPressBackupRestore('backup-restore-wordpress', From 40830f73f10c91187ad218acd461192f7cae2187 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Nov 2021 16:22:42 -0800 Subject: [PATCH 20/53] doc/dev: Remove mention of managed_services Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- doc/dev/reference/app_module.rst | 8 -------- doc/dev/tutorial/components.rst | 4 +--- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/doc/dev/reference/app_module.rst b/doc/dev/reference/app_module.rst index 1495db396..154a0bcef 100644 --- a/doc/dev/reference/app_module.rst +++ b/doc/dev/reference/app_module.rst @@ -32,14 +32,6 @@ triggers the setup() logic allowing the app to run upgrade scripts. This attribute is part of the :class:`~plinth.app.Info` component. Need for this attribute at the module level will be removed in the future. -.managed_services -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Optional. This must contain the list of all services that this app deals with. -This is mostly needed to enforce better security. This information is part of -the :class:`~plinth.daemon.Daemon` component. Need for this attribute at the -module level will be removed in the future. - .managed_paths ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/dev/tutorial/components.rst b/doc/dev/tutorial/components.rst index abbb064ab..9ba1b25a9 100644 --- a/doc/dev/tutorial/components.rst +++ b/doc/dev/tutorial/components.rst @@ -92,15 +92,13 @@ our app's class. from plinth.daemon import Daemon - managed_services = ['transmission-daemon'] - class TransmissionApp(app_module.App): ... def __init__(self): ... - daemon = Daemon('daemon-transmission', managed_services[0], + daemon = Daemon('daemon-transmission', 'transmission-daemon', listen_ports=[(9091, 'tcp4')]) self.add(daemon) From 5327f65db08cedfe4304917de4074cf1f5b69ab9 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Nov 2021 12:56:55 -0800 Subject: [PATCH 21/53] actions/letsencrypt: Drop use of managed_paths and use LE component Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/letsencrypt | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/actions/letsencrypt b/actions/letsencrypt index 6edf4d4f4..9dec15b97 100755 --- a/actions/letsencrypt +++ b/actions/letsencrypt @@ -8,6 +8,7 @@ import argparse import filecmp import glob import importlib +import inspect import json import os import pathlib @@ -18,8 +19,11 @@ import sys import configobj -from plinth import action_utils, cfg +from plinth import action_utils +from plinth import app as app_module +from plinth import cfg from plinth.modules import letsencrypt as le +from plinth.modules.letsencrypt.components import LetsEncrypt TEST_MODE = False LE_DIRECTORY = '/etc/letsencrypt/' @@ -372,6 +376,14 @@ def _assert_source_directory(path): or str(path).startswith(ETC_SSL_DIRECTORY)) +def _get_managed_path(path): + """Return the managed path given a certificate path.""" + if '{domain}' in path: + return pathlib.Path(path.partition('{domain}')[0]) + + return pathlib.Path(path).parent + + def _assert_managed_path(module, path): """Check that path is in fact managed by module.""" cfg.read() @@ -379,7 +391,24 @@ def _assert_managed_path(module, path): module_path = module_file.read_text().strip() module = importlib.import_module(module_path) - assert set(path.parents).intersection(set(module.managed_paths)) + module_classes = inspect.getmembers(module, inspect.isclass) + app_classes = [ + cls[1] for cls in module_classes if issubclass(cls[1], app_module.App) + ] + + managed_paths = [] + for cls in app_classes: + app = cls() + components = app.get_components_of_type(LetsEncrypt) + for component in components: + if component.private_key_path: + managed_paths.append( + _get_managed_path(component.private_key_path)) + if component.certificate_path: + managed_paths.append( + _get_managed_path(component.certificate_path)) + + assert set(path.parents).intersection(set(managed_paths)) def subcommand_run_pre_hooks(_): From 00182da7510dad26c2807a0e371876427db09aff Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Nov 2021 12:58:22 -0800 Subject: [PATCH 22/53] *: Drop use of unnecessary managed_paths Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/coturn/__init__.py | 2 -- plinth/modules/ejabberd/__init__.py | 2 -- plinth/modules/matrixsynapse/__init__.py | 3 --- plinth/modules/mumble/__init__.py | 2 -- plinth/modules/quassel/__init__.py | 2 -- 5 files changed, 11 deletions(-) diff --git a/plinth/modules/coturn/__init__.py b/plinth/modules/coturn/__init__.py index dadca2b8c..231d2334a 100644 --- a/plinth/modules/coturn/__init__.py +++ b/plinth/modules/coturn/__init__.py @@ -27,8 +27,6 @@ from . import manifest version = 1 -managed_paths = [pathlib.Path('/etc/coturn/')] - _description = [ _('Coturn is a server to facilitate audio/video calls and conferences by ' 'providing an implementation of TURN and STUN protocols. WebRTC, SIP ' diff --git a/plinth/modules/ejabberd/__init__.py b/plinth/modules/ejabberd/__init__.py index 5926d5441..e35cd6367 100644 --- a/plinth/modules/ejabberd/__init__.py +++ b/plinth/modules/ejabberd/__init__.py @@ -30,8 +30,6 @@ from . import manifest version = 4 -managed_paths = [pathlib.Path('/etc/ejabberd/')] - _description = [ _('XMPP is an open and standardized communication protocol. Here ' 'you can run and configure your XMPP server, called ejabberd.'), diff --git a/plinth/modules/matrixsynapse/__init__.py b/plinth/modules/matrixsynapse/__init__.py index a3c9c5481..dda2493ac 100644 --- a/plinth/modules/matrixsynapse/__init__.py +++ b/plinth/modules/matrixsynapse/__init__.py @@ -5,7 +5,6 @@ FreedomBox app to configure matrix-synapse server. import logging import os -import pathlib from typing import List from django.urls import reverse_lazy @@ -28,8 +27,6 @@ from . import manifest version = 7 -managed_paths = [pathlib.Path('/etc/matrix-synapse/')] - _description = [ _('Matrix is an new ' 'ecosystem for open, federated instant messaging and VoIP. Synapse is a ' diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py index a60ab450d..ddc250d07 100644 --- a/plinth/modules/mumble/__init__.py +++ b/plinth/modules/mumble/__init__.py @@ -24,8 +24,6 @@ from . import manifest version = 2 -managed_paths = [pathlib.Path('/var/lib/mumble-server')] - _description = [ _('Mumble is an open source, low-latency, encrypted, high quality ' 'voice chat software.'), diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index a7e0c1764..eff5ee29c 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -24,8 +24,6 @@ from . import manifest version = 1 -managed_paths = [pathlib.Path('/var/lib/quassel/')] - _description = [ format_lazy( _('Quassel is an IRC application that is split into two parts, a ' From 1681aeb2f9ed2741a8795e221dc81ec55be0dfa8 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Nov 2021 12:57:56 -0800 Subject: [PATCH 23/53] doc/dev: Drop discussion on managed_paths Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- doc/dev/reference/app_module.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/doc/dev/reference/app_module.rst b/doc/dev/reference/app_module.rst index 154a0bcef..67600e2ee 100644 --- a/doc/dev/reference/app_module.rst +++ b/doc/dev/reference/app_module.rst @@ -31,11 +31,3 @@ Required. Version number of an app. Increasing the version number of an app triggers the setup() logic allowing the app to run upgrade scripts. This attribute is part of the :class:`~plinth.app.Info` component. Need for this attribute at the module level will be removed in the future. - -.managed_paths -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Optional. This must contain the list of all file system paths that this app -deals with. This is mostly used by the -:class:`~plinth.modules.letsencrypt.components.LetsEncrypt` component to enforce -better security. This requirement may be removed in the future. From 82876a6e23b9a028c86cb5f1a1b47c1dbc26a599 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Nov 2021 19:08:56 -0800 Subject: [PATCH 24/53] package: Introduce component API for package conflicts This is help in eliminating the module level package_conflicts declarations. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/package.py | 29 +++++++++++++++++++++++++++-- plinth/tests/test_package.py | 28 +++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/plinth/package.py b/plinth/package.py index db9a07428..2d41c4806 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -3,12 +3,13 @@ Framework for installing and updating distribution packages """ +import enum import json import logging import subprocess import sys import threading -from typing import Union +from typing import Optional, Union import apt.cache from django.utils.translation import gettext as _ @@ -28,8 +29,15 @@ class Packages(app.FollowerComponent): of packages required by an app. """ + class ConflictsAction(enum.Enum): + """Action to take when a conflicting package is installed.""" + IGNORE = 'ignore' # Proceed as if there are no conflicts + REMOVE = 'remove' # Remove the packages before installing the app + def __init__(self, component_id: str, packages: list[str], - skip_recommends=False): + skip_recommends: bool = False, + conflicts: Optional[list[str]] = None, + conflicts_action: Optional[ConflictsAction] = None): """Initialize a new packages component. 'component_id' should be a unique ID across all components of an app @@ -39,12 +47,22 @@ class Packages(app.FollowerComponent): 'skip_recommends' is a boolean specifying whether recommended packages should be installed along with the listed packages. + + 'conflicts' is the list of Debian packages that can't simultaneously be + installed with packages listed here. None if there are no known + conflicting packages. + + 'conflicts_action' is a string representing the action to take when it + is found that conflicting Debian packages are installed on the system. + None if there are no known conflicting packages. """ super().__init__(component_id) self.component_id = component_id self._packages = packages self.skip_recommends = skip_recommends + self.conflicts = conflicts + self.conflicts_action = conflicts_action @property def packages(self) -> list[str]: @@ -59,6 +77,13 @@ class Packages(app.FollowerComponent): helper = module.setup_helper helper.install(self.packages, skip_recommends=self.skip_recommends) + def find_conflicts(self) -> Optional[list[str]]: + """Return list of conflicting packages installed on the system.""" + if not self.conflicts: + return None + + return packages_installed(self.conflicts) + class PackageException(Exception): """A package operation has failed.""" diff --git a/plinth/tests/test_package.py b/plinth/tests/test_package.py index 8f6db81eb..5435c4ab6 100644 --- a/plinth/tests/test_package.py +++ b/plinth/tests/test_package.py @@ -20,13 +20,19 @@ def test_packages_init(): assert component.component_id == 'test-component' assert component.packages == ['foo', 'bar'] assert not component.skip_recommends + assert component.conflicts is None + assert component.conflicts_action is None with pytest.raises(ValueError): Packages(None, []) - component = Packages('test-component', [], skip_recommends=True) + component = Packages('test-component', [], skip_recommends=True, + conflicts=['conflict1', 'conflict2'], + conflicts_action=Packages.ConflictsAction.IGNORE) assert component.packages == [] assert component.skip_recommends + assert component.conflicts == ['conflict1', 'conflict2'] + assert component.conflicts_action == Packages.ConflictsAction.IGNORE def test_packages_setup(): @@ -54,6 +60,26 @@ def test_packages_setup(): [call(['foo2', 'bar2'], skip_recommends=True)]) +@patch('plinth.package.packages_installed') +def test_packages_find_conflicts(packages_installed_): + """Test finding conflicts.""" + packages_installed_.return_value = [] + component = Packages('test-component', ['package3', 'package4']) + assert component.find_conflicts() is None + + packages_installed_.return_value = [] + component = Packages('test-component', ['package3', 'package4'], + conflicts=['package5', 'package6'], + conflicts_action=Packages.ConflictsAction.IGNORE) + assert component.find_conflicts() == [] + + packages_installed_.return_value = ['package1', 'package2'] + component = Packages('test-component', ['package3', 'package4'], + conflicts=['package1', 'package2'], + conflicts_action=Packages.ConflictsAction.IGNORE) + assert component.find_conflicts() == ['package1', 'package2'] + + def test_packages_installed(): """Test packages_installed().""" # list as input From fb40bb7f42294ecd83efd1a2eaa2ae30b64d46ec Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Nov 2021 19:10:16 -0800 Subject: [PATCH 25/53] *: Drop module level package_conflicts and use component API Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/email_server/__init__.py | 27 +++++++++++++------------ plinth/setup.py | 17 +--------------- plinth/templates/setup.html | 2 +- plinth/views.py | 18 ++++++++++++++++- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/plinth/modules/email_server/__init__.py b/plinth/modules/email_server/__init__.py index 83311b4fa..ec8e14160 100644 --- a/plinth/modules/email_server/__init__.py +++ b/plinth/modules/email_server/__init__.py @@ -14,19 +14,12 @@ from plinth.modules.apache.components import Webserver from plinth.modules.config import get_domainname from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt -from plinth.package import Packages, packages_installed, remove +from plinth.package import Packages, remove from . import audit, manifest version = 1 -# Other likely install conflicts have been discarded: -# - msmtp, nullmailer, sendmail don't cause install faults. -# - qmail and smail are missing in Bullseye (Not tested, -# but less likely due to that). -package_conflicts = ('exim4-base', 'exim4-config', 'exim4-daemon-light') -package_conflicts_action = 'ignore' - clamav_packages = ['clamav', 'clamav-daemon'] clamav_daemons = ['clamav-daemon', 'clamav-freshclam'] @@ -56,10 +49,17 @@ class EmailServerApp(plinth.app.App): super().__init__() self._add_ui_components() - packages = Packages('packages-email-server', [ - 'postfix-ldap', 'postfix-sqlite', 'dovecot-pop3d', 'dovecot-imapd', - 'dovecot-ldap', 'dovecot-lmtpd', 'dovecot-managesieved' - ]) + # Other likely install conflicts have been discarded: + # - msmtp, nullmailer, sendmail don't cause install faults. + # - qmail and smail are missing in Bullseye (Not tested, + # but less likely due to that). + packages = Packages( + 'packages-email-server', [ + 'postfix-ldap', 'postfix-sqlite', 'dovecot-pop3d', + 'dovecot-imapd', 'dovecot-ldap', 'dovecot-lmtpd', + 'dovecot-managesieved' + ], conflicts=['exim4-base', 'exim4-config', 'exim4-daemon-light'], + conflicts_action=Packages.ConflictsAction.IGNORE) self.add(packages) packages = Packages('packages-email-server-skip-rec', ['rspamd'], @@ -147,7 +147,8 @@ def setup(helper, old_version=None): """Installs and configures module""" def _clear_conflicts(): - packages_to_remove = packages_installed(package_conflicts) + component = app.get_component('packages-email-server') + packages_to_remove = component.find_conflicts() if packages_to_remove: logger.info('Removing conflicting packages: %s', packages_to_remove) diff --git a/plinth/setup.py b/plinth/setup.py index 9e3d23dd1..ac436eaf6 100644 --- a/plinth/setup.py +++ b/plinth/setup.py @@ -13,7 +13,7 @@ from collections import defaultdict import apt import plinth -from plinth.package import Packages, packages_installed +from plinth.package import Packages from plinth.signals import post_setup from . import package @@ -187,15 +187,6 @@ class Helper(object): if pkg_name not in cache) return any(unavailable_pkgs) - def get_package_conflicts(self): - """Report list of conflicting packages for the user.""" - package_conflicts, package_conflicts_action = \ - _get_module_package_conflicts(self.module) - if package_conflicts: - package_conflicts = packages_installed(package_conflicts) - - return package_conflicts, package_conflicts_action - def init(module_name, module): """Create a setup helper for a module for later use.""" @@ -317,12 +308,6 @@ def _is_module_essential(module): return getattr(module, 'is_essential', False) -def _get_module_package_conflicts(module): - """Return list of packages that conflict with packages of a module.""" - return (getattr(module, 'package_conflicts', - None), getattr(module, 'package_conflicts_action', None)) - - def _module_state_matches(module, state): """Return if the current setup state of a module matches given state.""" return module.setup_helper.get_state() == state diff --git a/plinth/templates/setup.html b/plinth/templates/setup.html index 3d9d78bbf..0555d04bd 100644 --- a/plinth/templates/setup.html +++ b/plinth/templates/setup.html @@ -50,7 +50,7 @@ {% trans "Check again" %} - {% elif package_conflicts and package_conflicts_action != 'ignore' %} + {% elif package_conflicts and package_conflicts_action.value != 'ignore' %}

- {% elif setup_helper.has_unavailable_packages %} + {% elif has_unavailable_packages %}