service 5.34 KB
Newer Older
1 2
#!/usr/bin/python3
#
3
# This file is part of FreedomBox.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
#
# 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/>.
#

"""
Wrapper to list and handle system services
"""

import argparse
from importlib import import_module
import os
import json
import subprocess

from plinth import action_utils, cfg

cfg.read()
module_config_path = os.path.join(cfg.config_dir, 'modules-enabled')


def add_service_action(subparsers, action, help):
    parser = subparsers.add_parser(action, help=help)
    parser.add_argument('service', help='name of the service')

39

40 41 42 43 44 45 46 47 48
def parse_arguments():
    """Return parsed command line arguments as dictionary."""
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')

    add_service_action(subparsers, 'start', 'start a service')
    add_service_action(subparsers, 'stop', 'stop a service')
    add_service_action(subparsers, 'enable', 'enable a service')
    add_service_action(subparsers, 'disable', 'disable a service')
49
    add_service_action(subparsers, 'restart', 'restart a service')
50 51 52
    add_service_action(subparsers, 'reload', 'reload a service')
    add_service_action(subparsers, 'is-running', 'status of a service')
    add_service_action(subparsers, 'is-enabled', 'status a service')
53
    add_service_action(subparsers, 'unmask', 'unmask a service')
54 55 56

    subparsers.add_parser('list', help='List of running system services')

57
    subparsers.required = True
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
    return parser.parse_args()


def subcommand_start(arguments):
    action_utils.service_start(arguments.service)


def subcommand_stop(arguments):
    action_utils.service_stop(arguments.service)


def subcommand_enable(arguments):
    action_utils.service_enable(arguments.service)


def subcommand_disable(arguments):
    action_utils.service_disable(arguments.service)


77 78 79 80
def subcommand_restart(arguments):
    action_utils.service_restart(arguments.service)


81 82 83 84
def subcommand_reload(arguments):
    action_utils.service_reload(arguments.service)


85 86 87 88
def subcommand_unmask(arguments):
    action_utils.service_unmask(arguments.service)


89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
def subcommand_is_enabled(arguments):
    print(action_utils.service_is_enabled(arguments.service))


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 (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:
        module = import_module(modulepath)
    except ImportError:
        return []
    else:
        return getattr(module, 'managed_services', [])


def _get_managed_services():
    """
133
    Get a set of all services managed by FreedomBox.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

    This collects all service-names inside the 'managed_services' variable of
    modules inside 'module_config_path'
    """
    services = set()
    for filename in os.listdir(module_config_path):
        # Omit hidden files
        if filename.startswith('.'):
            continue

        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))

    return services


def _assert_service_is_managed_by_plinth(service_name):
    managed_services = _get_managed_services()
    if service_name not in managed_services:
156
        msg = ("The service '%s' is not managed by FreedomBox. Access is only "
157
               "permitted for services listed in the 'managed_services' "
158
               "variable of any FreedomBox app.") % service_name
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
        raise ValueError(msg)


def main():
    """Parse arguments and perform all duties."""
    arguments = parse_arguments()

    subcommand = arguments.subcommand.replace('-', '_')
    subcommand_method = globals()['subcommand_' + subcommand]
    if hasattr(arguments, 'service'):
        _assert_service_is_managed_by_plinth(arguments.service)
    subcommand_method(arguments)


if __name__ == '__main__':
    main()