#!/usr/bin/python2 # -*- mode: python -*- # # This file is part of Plinth. # # 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 . # """ Configuration helper for Plinth PageKite interface Unfortunately there is no python3 package for augeas yet """ import argparse import augeas import json import os import subprocess import util from pagekite_util import SERVICE_PARAMS, convert_service_to_string, \ get_augeas_servicefile_path, load_service, CONF_PATH aug = augeas.Augeas() PATHS = { 'service_on': os.path.join(CONF_PATH, '*', 'service_on', '*'), 'kitename': os.path.join(CONF_PATH, '10_account.rc', 'kitename'), 'kitesecret': os.path.join(CONF_PATH, '10_account.rc', 'kitesecret'), 'abort_not_configured': os.path.join(CONF_PATH, '10_account.rc', 'abort_not_configured'), 'defaults': os.path.join(CONF_PATH, '20_frontends.rc', 'defaults'), 'frontend': os.path.join(CONF_PATH, '20_frontends.rc', 'frontend'), } def parse_arguments(): """Return parsed command line arguments as dictionary""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') # Enable/disable the pagekite service subparsers.add_parser('is-running', help='Get whether PakeKite is running') subparsers.add_parser('start-and-enable', help='Enable PageKite service') subparsers.add_parser('stop-and-disable', help='Disable PageKite service') subparsers.add_parser('restart', help='Restart PageKite service') # Frontend subparsers.add_parser('get-frontend', help='Get pagekite frontend') set_frontend = subparsers.add_parser('set-frontend', help='Set pagekite frontend') set_frontend.add_argument('url', help='frontend url') # Kite details (name + secret) subparsers.add_parser('get-kite', help='Get configured kite name and secret') set_kite = subparsers.add_parser('set-kite', help='Configure kite name and its secret') set_kite.add_argument('--kite-name', help='Name of the kite (eg: mybox.pagekite.me)') set_kite.add_argument('--kite-secret', help='Secret for the kite') # Add/remove pagekite services (service_on entries) subparsers.add_parser('get-services', help='Get list of enabled services') add_service = subparsers.add_parser('add-service', help='Add a pagekite service') add_service.add_argument('--service', help='json service dictionary') remove_service = subparsers.add_parser('remove-service', help='Remove a pagekite service') remove_service.add_argument('--service', help='json service dictionary') return parser.parse_args() def _service(action): """Start/stop/restart the pagekite service""" error = subprocess.call(['service', 'pagekite', action]) if error: raise Exception('Unable to %s PageKite server' % action) def subcommand_is_running(_): """Print whether pagekite is enabled (yes or no)""" print 'yes' if util.service_is_running('pagekite') else 'no' def subcommand_restart(_): """Restart the pagekite service""" _service('restart') print 'restarted' def subcommand_start_and_enable(_): aug.remove(PATHS['abort_not_configured']) aug.save() # 'start' alone sometimes fails, even if the service is not running _service('restart') print 'enabled' def subcommand_stop_and_disable(_): _service('stop') aug.set(PATHS['abort_not_configured'], '') aug.save() print 'disabled' def subcommand_get_frontend(_): """Get pagekite frontend url""" if aug.match(PATHS['defaults']): print "pagekite.net" else: url = aug.get(PATHS['frontend']) print url if url else "" def subcommand_set_frontend(arguments): """Set pagekite frontend url, taking care of defaults and pagekite.net""" if arguments.url in ('pagekite.net', 'defaults', 'default'): enable_pagekitenet_frontend() else: aug.remove(PATHS['defaults']) aug.set(PATHS['frontend'], arguments.url) aug.save() def enable_pagekitenet_frontend(): """Enable using default pageket.net frontend This disables any other frontends. """ aug.set(PATHS['defaults'], '') aug.remove(PATHS['frontend']) aug.save() print "enabled" def subcommand_get_services(arguments): """ lists all available (enabled) services """ for match in aug.match(PATHS['service_on']): service = dict([(param, aug.get(os.path.join(match, param))) for param in SERVICE_PARAMS]) print json.dumps(service) def subcommand_remove_service(arguments): """Searches and removes the service(s) that match all given parameters""" service = load_service(arguments.service) paths = get_existing_service_paths(service) # TODO: theoretically, everything to do here is: # [aug.remove(path) for path in paths] # but augeas won't let you save the changed files and doesn't say why for path in paths: filepath = convert_augeas_path_to_filepath(path) service_found = False with open(filepath, 'r') as file: lines = file.readlines() for i, line in enumerate(lines): if line.startswith('service_on') and \ all(param in line for param in service.values()): lines[i] = "" service_found = True break if service_found: with open(filepath, 'w') as file: file.writelines(lines) # abort to only allow deleting one service break _service('restart') def get_existing_service_paths(service): """Return paths of existing services that match the given service params""" # construct an augeas query path with patterns like: # */service_on/*[protocol='http'] path = PATHS['service_on'] for param, value in service.items(): path += "[%s='%s']" % (param, value) return aug.match(path) def subcommand_add_service(arguments): """Add one service""" service = load_service(arguments.service) if get_existing_service_paths(service): msg = "Service with the parameters %s already exists" raise RuntimeError(msg % service) root = get_new_service_path(service['protocol']) # TODO: after adding a service, augeas fails writing the config; # so add the service_on entry manually instead path = convert_augeas_path_to_filepath(root) with open(path, 'a') as servicefile: line = "\nservice_on = %s\n" % convert_service_to_string(service) servicefile.write(line) _service('restart') def convert_augeas_path_to_filepath(augpath, prefix='/files', suffix='service_on'): """Convert an augeas service_on path to the actual file path""" if augpath.startswith(prefix): augpath = augpath.replace(prefix, "", 1) index = augpath.rfind(suffix) if index: augpath = augpath[:index] return augpath.rstrip('/') def get_new_service_path(protocol): """Get the augeas path of a new service for a protocol This takes care of existing services using a /service_on/*/ query""" root = get_augeas_servicefile_path(protocol) new_index = len(aug.match(root + '/*')) + 1 return os.path.join(root, str(new_index)) def subcommand_get_kite(_): """Print details of the currently configured kite""" kitename = aug.get(PATHS['kitename']) kitesecret = aug.get(PATHS['kitesecret']) print kitename if kitename else '' print kitesecret if kitesecret else '' def subcommand_set_kite(arguments): """Set details of the kite""" aug.set(PATHS['kitename'], arguments.kite_name) aug.set(PATHS['kitesecret'], arguments.kite_secret) aug.save() def main(): """Parse arguments and perform all duties""" arguments = parse_arguments() subcommand = arguments.subcommand.replace('-', '_') subcommand_method = globals()['subcommand_' + subcommand] subcommand_method(arguments) if __name__ == "__main__": main()