service.py 4.55 KB
Newer Older
1
#
2
# This file is part of FreedomBox.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

"""
19
Framework for working with servers and their services.
20 21
"""

22
import collections
23

24
from django.utils.translation import ugettext_lazy as _
25

26
from plinth import action_utils, actions, cfg
27
from plinth.signals import service_enabled
28
from plinth.utils import format_lazy
29

30
services = {}
31 32 33 34 35 36 37


class Service(object):
    """
    Representation of an application service provided by the machine
    containing information such as current status and ports required
    for operation.
38 39

    - service_id: unique service name. If possible this should be the name of
40
                  the service's systemd unit file (without the extension).
41
    - name: service name as to be displayed in the GUI
42 43 44 45
    - is_enabled (optional): Boolean or a method returning Boolean
    - enable (optional): method
    - disable (optional): method
    - is_running (optional): Boolean or a method returning Boolean
46
    """
fonfon's avatar
fonfon committed
47 48
    def __init__(self, service_id, name, ports=None, is_external=False,
                 is_enabled=None, enable=None, disable=None, is_running=None):
49
        if ports is None:
50
            ports = []
51

52 53 54
        if is_enabled is None:
            is_enabled = self._default_is_enabled

55 56 57
        self.service_id = service_id
        self.name = name
        self.ports = ports
58
        self.is_external = is_external
59 60 61 62
        self._is_enabled = is_enabled
        self._enable = enable
        self._disable = disable
        self._is_running = is_running
63 64

        # Maintain a complete list of services
65
        assert(service_id not in services)
66
        services[service_id] = self
67

68 69 70 71 72 73 74 75 76 77 78 79 80 81
    def enable(self):
        if self._enable is None:
            actions.superuser_run('service', ['enable', self.service_id])
        else:
            self._call_or_return(self._enable)
        self.notify_enabled(None, True)

    def disable(self):
        if self._disable is None:
            actions.superuser_run('service', ['disable', self.service_id])
        else:
            self._call_or_return(self._disable)
        self.notify_enabled(None, False)

82
    def is_enabled(self):
83
        """Return whether the service is enabled."""
84 85 86
        # TODO: we could cache the service state if we only use this service
        # interface to change service status.
        return self._call_or_return(self._is_enabled)
87

88 89 90 91 92
    def is_running(self):
        if self._is_running is None:
            return action_utils.service_is_running(self.service_id)
        else:
            return self._call_or_return(self._is_running)
93 94

    def notify_enabled(self, sender, enabled):
95
        """Notify observers about change in state of service."""
96
        service_enabled.send_robust(sender=sender, service_id=self.service_id,
fonfon's avatar
fonfon committed
97
                                    enabled=enabled)
98

99 100 101 102 103 104 105 106 107 108 109 110 111 112
    def _call_or_return(self, obj):
        """Calls obj if it's callable, returns it if it's Boolean."""
        if isinstance(obj, collections.Callable):
            return obj()
        elif type(obj) is bool:
            return obj
        else:
            message = 'obj is expected to be callable or a boolean.'
            raise ValueError(message)

    def _default_is_enabled(self):
        """Returns is_enabled relying on a correct service_id"""
        return action_utils.service_is_enabled(self.service_id)

113 114 115 116 117
    def get_internal_interfaces(self):
        """Returns a list of interfaces in a firewall zone."""
        from plinth.modules import firewall
        return firewall.get_interfaces('internal')

118 119

def init():
120
    """Register some misc. services that don't fit elsewhere."""
121 122 123 124 125 126
    Service('http', _('Web Server'), ports=['http'], is_external=True,
            is_enabled=True)
    Service('https', _('Web Server over Secure Socket Layer'), ports=['https'],
            is_external=True, is_enabled=True)
    Service('plinth', format_lazy(_('{box_name} Web Interface (Plinth)'),
                                  box_name=_(cfg.box_name)),
127
            ports=['https'], is_external=True, is_enabled=True)