mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-18 09:10:49 +00:00
Merge remote-tracking branch 'fonfon/pagekite-augeas-new'
This commit is contained in:
commit
44b045fef7
257
actions/pagekite
Executable file
257
actions/pagekite
Executable file
@ -0,0 +1,257 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
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()
|
||||
@ -1,290 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Configuration helper for Plint PageKite inteface
|
||||
|
||||
TODO: Use augeas for manipulating /etc/pagekite.d/* files
|
||||
"""
|
||||
|
||||
# Disable warning about invalid module name # pylint: disable-msg=C0103
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
CONFIG_DIR = '/etc/pagekite.d'
|
||||
|
||||
SERVICE_FILE_MAP = {
|
||||
'http': {
|
||||
'file': '80_httpd.rc',
|
||||
'match': 'http:',
|
||||
'line': 'service_on = http:@kitename : localhost:80 : @kitesecret'},
|
||||
'ssh': {
|
||||
'file': '80_sshd.rc',
|
||||
'match': 'raw/22:',
|
||||
'line': 'service_on = raw/22:@kitename : localhost:22 : @kitesecret'}}
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""Return parsed command line arguments as dictionary"""
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||
|
||||
# Start PageKite
|
||||
subparsers.add_parser('start', help='Start PageKite service')
|
||||
|
||||
# Stop PageKite
|
||||
subparsers.add_parser('stop', help='Stop PageKite service')
|
||||
|
||||
# Get status
|
||||
subparsers.add_parser('get-status', help='Get whether PakeKite is enabled')
|
||||
|
||||
# Set status
|
||||
set_status = subparsers.add_parser('set-status',
|
||||
help='Enable/disable PageKite')
|
||||
set_status.add_argument('enable', choices=['enable', 'disable'])
|
||||
|
||||
# Get kite details
|
||||
subparsers.add_parser('get-kite',
|
||||
help='Get configured kite name and secret')
|
||||
|
||||
# Set kite details
|
||||
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')
|
||||
|
||||
# Get service status
|
||||
get_service = subparsers.add_parser('get-service-status',
|
||||
help='Get whether service is enabled')
|
||||
get_service.add_argument('service', choices=['http', 'ssh'])
|
||||
|
||||
# Set service status
|
||||
set_service = subparsers.add_parser('set-service-status',
|
||||
help='Enable/disable a service')
|
||||
set_service.add_argument('service', choices=['http', 'ssh'])
|
||||
set_service.add_argument('enable', choices=['enable', 'disable'])
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def subcommand_start(_):
|
||||
"""Start PageKite service"""
|
||||
status = subprocess.call(['service', 'pagekite', 'start'])
|
||||
if status:
|
||||
raise Exception('Unable to start PageKite server')
|
||||
|
||||
|
||||
def subcommand_stop(_):
|
||||
"""Stop PageKite service"""
|
||||
status = subprocess.call(['service', 'pagekite', 'stop'])
|
||||
if status:
|
||||
raise Exception('Unable to stop PageKite server')
|
||||
|
||||
|
||||
def subcommand_get_status(_):
|
||||
"""Print status of the pagekite service"""
|
||||
is_enabled = is_pagekite_enabled()
|
||||
print('enabled' if is_enabled else 'disabled')
|
||||
|
||||
|
||||
def is_pagekite_enabled():
|
||||
"""Return if pagekite is enabled"""
|
||||
service_file_path = os.path.join(CONFIG_DIR, '10_account.rc')
|
||||
try:
|
||||
with open(service_file_path, 'r') as file_object:
|
||||
for line in file_object:
|
||||
regex = r'^[ \t]*abort_not_configured'
|
||||
if re.match(regex, line):
|
||||
return False
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def subcommand_set_status(arguments):
|
||||
"""Enable/disable the pagekite service"""
|
||||
enable = arguments.enable == 'enable'
|
||||
is_enabled = is_pagekite_enabled()
|
||||
if enable and is_enabled:
|
||||
print('already enabled')
|
||||
return
|
||||
|
||||
if not enable and not is_enabled:
|
||||
print('already disabled')
|
||||
return
|
||||
|
||||
if enable:
|
||||
pagekite_enable()
|
||||
print('enabled')
|
||||
else:
|
||||
pagekite_disable()
|
||||
print('disabled')
|
||||
|
||||
|
||||
def pagekite_enable():
|
||||
"""Enable the pagekite daemon"""
|
||||
file_path = os.path.join(CONFIG_DIR, '10_account.rc')
|
||||
file_path_new = os.path.join(CONFIG_DIR, '10_account.rc.new')
|
||||
with open(file_path, 'r') as read_file_object, \
|
||||
open(file_path_new, 'w') as write_file_object:
|
||||
for line in read_file_object:
|
||||
if not re.match('^[ \t]*abort_not_configured', line):
|
||||
write_file_object.write(line)
|
||||
|
||||
os.rename(file_path_new, file_path)
|
||||
|
||||
|
||||
def pagekite_disable():
|
||||
"""Disable the pagekite daemon"""
|
||||
file_path = os.path.join(CONFIG_DIR, '10_account.rc')
|
||||
with open(file_path, 'a') as file_object:
|
||||
file_object.write('abort_not_configured\n')
|
||||
|
||||
|
||||
def subcommand_get_kite(_):
|
||||
"""Print details of the currently configure kite"""
|
||||
kite_name = ''
|
||||
kite_secret = ''
|
||||
|
||||
file_path = os.path.join(CONFIG_DIR, '10_account.rc')
|
||||
with open(file_path, 'r') as file_object:
|
||||
for line in file_object:
|
||||
match = re.match(r'[ \t]*kitename[ \t]*=[ \t]*(.*)', line)
|
||||
if match:
|
||||
kite_name = match.group(1)
|
||||
continue
|
||||
|
||||
match = re.match(r'[ \t]*kitesecret[ \t]*=[ \t]*(.*)', line)
|
||||
if match:
|
||||
kite_secret = match.group(1)
|
||||
continue
|
||||
|
||||
print(kite_name)
|
||||
print(kite_secret)
|
||||
|
||||
|
||||
def subcommand_set_kite(arguments):
|
||||
"""Set details of the kite"""
|
||||
kite_name = arguments.kite_name
|
||||
kite_secret = arguments.kite_secret
|
||||
|
||||
file_path = os.path.join(CONFIG_DIR, '10_account.rc')
|
||||
file_path_new = os.path.join(CONFIG_DIR, '10_account.rc.new')
|
||||
with open(file_path, 'r') as read_file_object, \
|
||||
os.fdopen(os.open(file_path_new, os.O_WRONLY | os.O_CREAT,
|
||||
0o400), 'w') as write_file_object:
|
||||
for line in read_file_object:
|
||||
if re.match(r'[ \t]*kitename[ \t]*=.*', line):
|
||||
write_file_object.write(
|
||||
'kitename = {kite_name}\n'.format(kite_name=kite_name))
|
||||
continue
|
||||
|
||||
if re.match(r'[ \t]*kitesecret[ \t]*=.*', line):
|
||||
write_file_object.write('kitesecret = {kite_secret}\n'
|
||||
.format(kite_secret=kite_secret))
|
||||
continue
|
||||
|
||||
write_file_object.write(line)
|
||||
|
||||
os.rename(file_path_new, file_path)
|
||||
|
||||
|
||||
def subcommand_get_service_status(arguments):
|
||||
"""Print status of the pagekite service"""
|
||||
is_enabled = is_service_enabled(arguments.service)
|
||||
print('enabled' if is_enabled else 'disabled')
|
||||
|
||||
|
||||
def is_service_enabled(service):
|
||||
"""Return if a service is enabled"""
|
||||
service = SERVICE_FILE_MAP[service]
|
||||
service_file_path = os.path.join(CONFIG_DIR, service['file'])
|
||||
if not os.path.isfile(service_file_path):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(service_file_path, 'r') as file_object:
|
||||
for line in file_object:
|
||||
regex = '[ \t]*service_on[ \t]*=[ \t]*{match}'
|
||||
regex = regex.format(match=service['match'])
|
||||
if re.match(regex, line):
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def subcommand_set_service_status(arguments):
|
||||
"""Enable/disable a pagekite service"""
|
||||
enable = arguments.enable == 'enable'
|
||||
is_enabled = is_service_enabled(arguments.service)
|
||||
if enable and is_enabled:
|
||||
print('already enabled')
|
||||
return
|
||||
|
||||
if not enable and not is_enabled:
|
||||
print('already disabled')
|
||||
return
|
||||
|
||||
if enable:
|
||||
service_enable(arguments.service)
|
||||
print('enabled')
|
||||
else:
|
||||
service_disable(arguments.service)
|
||||
print('disabled')
|
||||
|
||||
|
||||
def service_enable(service_name):
|
||||
"""Enable a service"""
|
||||
service = SERVICE_FILE_MAP[service_name]
|
||||
service_file_path = os.path.join(CONFIG_DIR, service['file'])
|
||||
with open(service_file_path, 'w') as file_object:
|
||||
file_object.write('''
|
||||
# Expose the local {service_name} daemon
|
||||
# File auto-generated by Plinth
|
||||
|
||||
{line}
|
||||
'''.format(service_name=service_name, line=service['line']))
|
||||
|
||||
|
||||
def service_disable(service_name):
|
||||
"""Disable a service"""
|
||||
service = SERVICE_FILE_MAP[service_name]
|
||||
service_file_path = os.path.join(CONFIG_DIR, service['file'])
|
||||
service_file_path_new = os.path.join(CONFIG_DIR,
|
||||
service['file'] + '.plinthbak')
|
||||
os.rename(service_file_path, service_file_path_new)
|
||||
|
||||
|
||||
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()
|
||||
113
actions/pagekite_util.py
Normal file
113
actions/pagekite_util.py
Normal file
@ -0,0 +1,113 @@
|
||||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Utilities for configuring PageKite.
|
||||
"""
|
||||
# TODO:
|
||||
# Once python-augeas is available for python3 import the following things
|
||||
# from plinth.modules.pagekite.util (instead of having a copy in here):
|
||||
#
|
||||
# SERVICE_PARAMS, convert_service_to_string
|
||||
#
|
||||
# until then, this file is python2 and python3 compatible for the unittests
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
CONF_PATH = '/files/etc/pagekite.d'
|
||||
|
||||
# parameters that get stored in configuration service_on entries
|
||||
SERVICE_PARAMS = ['protocol', 'kitename', 'backend_host', 'backend_port',
|
||||
'secret']
|
||||
|
||||
|
||||
def convert_service_to_string(service):
|
||||
""" Convert service dict into a ":"-separated parameter string
|
||||
|
||||
>>> convert_service_to_string({'kitename': '@kitename', \
|
||||
'backend_host': 'localhost', 'secret': '@kitesecret', \
|
||||
'protocol': 'https/443', 'backend_port': '443'})
|
||||
'https/443:@kitename:localhost:443:@kitesecret'
|
||||
"""
|
||||
try:
|
||||
service_string = ":".join([service[param] for param in SERVICE_PARAMS])
|
||||
except KeyError:
|
||||
raise ValueError("Could not parse params: %s " % service)
|
||||
return service_string
|
||||
|
||||
|
||||
def load_service(json_service):
|
||||
""" create a service out of json command-line argument
|
||||
|
||||
1) parse json
|
||||
2) only use the parameters that we need (SERVICE_PARAMS)
|
||||
3) convert unicode to strings
|
||||
"""
|
||||
service = json.loads(json_service)
|
||||
return dict((str(key), str(service[key])) for key in SERVICE_PARAMS)
|
||||
|
||||
|
||||
def get_augeas_servicefile_path(protocol):
|
||||
"""Get the augeas path where a service for a protocol should be stored
|
||||
|
||||
TODO: Once we use python3 switch from doctests to unittests
|
||||
|
||||
>>> get_augeas_servicefile_path('http')
|
||||
'/files/etc/pagekite.d/80_http.rc/service_on'
|
||||
|
||||
>>> get_augeas_servicefile_path('https')
|
||||
'/files/etc/pagekite.d/443_https.rc/service_on'
|
||||
|
||||
>>> get_augeas_servicefile_path('http/80')
|
||||
'/files/etc/pagekite.d/80_http.rc/service_on'
|
||||
|
||||
>>> get_augeas_servicefile_path('http/8080')
|
||||
'/files/etc/pagekite.d/8080_http.rc/service_on'
|
||||
|
||||
>>> get_augeas_servicefile_path('raw/22')
|
||||
'/files/etc/pagekite.d/22_raw.rc/service_on'
|
||||
|
||||
>>> get_augeas_servicefile_path('xmpp')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Unsupported protocol: xmpp
|
||||
|
||||
"""
|
||||
if not protocol.startswith(("http", "https", "raw")):
|
||||
raise ValueError('Unsupported protocol: %s' % protocol)
|
||||
|
||||
try:
|
||||
_protocol, port = protocol.split('/')
|
||||
except ValueError:
|
||||
if protocol == 'http':
|
||||
relpath = '80_http.rc'
|
||||
elif protocol == 'https':
|
||||
relpath = '443_https.rc'
|
||||
else:
|
||||
raise ValueError('Unsupported protocol: %s' % protocol)
|
||||
else:
|
||||
relpath = '%s_%s.rc' % (port, _protocol)
|
||||
|
||||
return os.path.join(CONF_PATH, relpath, 'service_on')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
17
actions/tor
17
actions/tor
@ -25,6 +25,8 @@ import argparse
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import util
|
||||
|
||||
SERVICE_CONFIG = '/etc/default/tor'
|
||||
TOR_CONFIG = '/etc/tor/torrc'
|
||||
|
||||
@ -57,20 +59,7 @@ def parse_arguments():
|
||||
|
||||
def subcommand_is_running(_):
|
||||
"""Get whether Tor is running"""
|
||||
try:
|
||||
output = subprocess.check_output(['service', 'tor', 'status'])
|
||||
except subprocess.CalledProcessError:
|
||||
# If Tor is not running we get a status code != 0 and a
|
||||
# CalledProcessError
|
||||
print('no')
|
||||
else:
|
||||
running = False
|
||||
for line in output.decode().split('\n'):
|
||||
if 'Active' in line and 'running' in line:
|
||||
running = True
|
||||
break
|
||||
|
||||
print('yes' if running else 'no')
|
||||
print('yes' if util.service_is_running('tor') else 'no')
|
||||
|
||||
|
||||
def subcommand_start(_):
|
||||
|
||||
41
actions/util.py
Normal file
41
actions/util.py
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Python action utility functions
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def service_is_running(servicename):
|
||||
"""Evaluates whether a service is currently running. Returns boolean"""
|
||||
try:
|
||||
output = subprocess.check_output(['service', servicename, 'status'])
|
||||
except subprocess.CalledProcessError:
|
||||
# Usually if a service is not running we get a status code != 0 and
|
||||
# thus a CalledProcessError
|
||||
return False
|
||||
else:
|
||||
running = False # default value
|
||||
for line in output.decode('utf-8').split('\n'):
|
||||
if 'Active' in line and 'running' in line:
|
||||
running = True
|
||||
break
|
||||
return running
|
||||
79
data/usr/share/augeas/lenses/pagekite.aug
Normal file
79
data/usr/share/augeas/lenses/pagekite.aug
Normal file
@ -0,0 +1,79 @@
|
||||
(*
|
||||
Module: Pagekite
|
||||
Parses /etc/pagekite.d/
|
||||
|
||||
Author: Michael Pimmer <blubb@fonfon.at>
|
||||
|
||||
About: License
|
||||
This file is licenced under the LGPL v2+, like the rest of Augeas.
|
||||
*)
|
||||
|
||||
module Pagekite =
|
||||
autoload xfm
|
||||
|
||||
(* View: lns *)
|
||||
|
||||
(* Variables *)
|
||||
let equals = del /[ \t]*=[ \t]*/ "="
|
||||
let neg2 = /[^# \n\t]+/
|
||||
let neg3 = /[^# \:\n\t]+/
|
||||
let eol = del /\n/ "\n"
|
||||
(* Match everything from here to eol, cropping whitespace at both ends *)
|
||||
let to_eol = /[^ \t\n](.*[^ \t\n])?/
|
||||
|
||||
(* A key followed by comma-separated values
|
||||
k: name of the key
|
||||
key_sep: separator between key and values
|
||||
value_sep: separator between values
|
||||
sto: store for values
|
||||
*)
|
||||
let key_csv_line (k:string) (key_sep:lens) (value_sep:lens) (sto:lens) =
|
||||
[ key k . key_sep . [ seq k . sto ] .
|
||||
[ seq k . value_sep . sto ]* . Util.eol ]
|
||||
|
||||
(* entries for pagekite.d/10_account.rc *)
|
||||
let domain = [ key "domain" . equals . store neg2 . Util.comment_or_eol ]
|
||||
let frontend = Build.key_value_line ("frontend" | "frontends")
|
||||
equals (store Rx.neg1)
|
||||
let host = Build.key_value_line "host" equals (store Rx.ip)
|
||||
let ports = key_csv_line "ports" equals Sep.comma (store Rx.integer)
|
||||
let protos = key_csv_line "protos" equals Sep.comma (store Rx.word)
|
||||
|
||||
(* entries for pagekite.d/20_frontends.rc *)
|
||||
let kitesecret = Build.key_value_line "kitesecret" equals (store Rx.space_in)
|
||||
let kv_frontend = Build.key_value_line ( "kitename" | "fe_certname" |
|
||||
"ca_certs" | "tls_endpoint" )
|
||||
equals (store Rx.neg1)
|
||||
|
||||
(* entries for services like 80_httpd.rc *)
|
||||
let service_colon = del /[ \t]*:[ \t]*/ " : "
|
||||
let service_on = [ key "service_on" . [ seq "service_on" . equals .
|
||||
[ label "protocol" . store neg3 ] . service_colon .
|
||||
[ label "kitename" . (store neg3) ] . service_colon .
|
||||
[ label "backend_host" . (store neg3) ] . service_colon .
|
||||
[ label "backend_port" . (store neg3) ] . service_colon . (
|
||||
[ label "secret" . (store Rx.no_spaces) . Util.eol ] | eol
|
||||
) ] ]
|
||||
|
||||
let service_cfg = [ key "service_cfg" . equals . store to_eol . eol ]
|
||||
|
||||
let flags = ( "defaults" | "isfrontend" | "abort_not_configured" | "insecure" )
|
||||
|
||||
let entries = Build.flag_line flags
|
||||
| domain
|
||||
| frontend
|
||||
| host
|
||||
| ports
|
||||
| protos
|
||||
| kv_frontend
|
||||
| kitesecret
|
||||
| service_on
|
||||
| service_cfg
|
||||
|
||||
let lns = ( entries | Util.empty | Util.comment )*
|
||||
|
||||
(* View: filter *)
|
||||
let filter = incl "/etc/pagekite.d/*.rc"
|
||||
. Util.stdexcl
|
||||
|
||||
let xfm = transform lns filter
|
||||
111
data/usr/share/augeas/lenses/tests/test_pagekite.aug
Normal file
111
data/usr/share/augeas/lenses/tests/test_pagekite.aug
Normal file
@ -0,0 +1,111 @@
|
||||
module Test_Pagekite =
|
||||
|
||||
let conf1 = "# Use the pagekite.net service defaults.
|
||||
defaults
|
||||
"
|
||||
test Pagekite.lns get conf1 =
|
||||
{ "#comment" = "Use the pagekite.net service defaults." }
|
||||
{ "defaults" }
|
||||
|
||||
|
||||
let conf2 ="
|
||||
frontends = pagekite.freedombox.me
|
||||
ports=80,81
|
||||
"
|
||||
test Pagekite.lns get conf2 =
|
||||
{ }
|
||||
{ "frontends" = "pagekite.freedombox.me" }
|
||||
{ "ports"
|
||||
{ "1" = "80" }
|
||||
{ "2" = "81" } }
|
||||
|
||||
|
||||
let conf3 = "frontend=pagekite.freedombox.me
|
||||
host=192.168.0.3
|
||||
"
|
||||
test Pagekite.lns get conf3 =
|
||||
{ "frontend" = "pagekite.freedombox.me" }
|
||||
{ "host" = "192.168.0.3" }
|
||||
|
||||
|
||||
let conf4 = "isfrontend
|
||||
ports=80,443
|
||||
protos=http,https
|
||||
domain=http,https:*.your.domain:MakeUpAPasswordHere
|
||||
"
|
||||
test Pagekite.lns get conf4 =
|
||||
{ "isfrontend" }
|
||||
{ "ports"
|
||||
{ "1" = "80" }
|
||||
{ "2" = "443" } }
|
||||
{ "protos"
|
||||
{ "1" = "http" }
|
||||
{ "2" = "https" } }
|
||||
{ "domain" = "http,https:*.your.domain:MakeUpAPasswordHere" }
|
||||
|
||||
let conf_account = "kitename = my.freedombox.me
|
||||
kitesecret = 0420
|
||||
# Delete this line!
|
||||
abort_not_configured
|
||||
"
|
||||
test Pagekite.lns get conf_account =
|
||||
{ "kitename" = "my.freedombox.me" }
|
||||
{ "kitesecret" = "0420" }
|
||||
{ "#comment" = "Delete this line!" }
|
||||
{ "abort_not_configured" }
|
||||
|
||||
|
||||
let conf_service = "
|
||||
service_on = raw/22:@kitename : localhost:22 : @kitesecret
|
||||
service_on=http:192.168.0.1:127.0.0.1:80:
|
||||
service_on=https:yourhostname,fqdn:127.0.0.1:443:
|
||||
"
|
||||
test Pagekite.lns get conf_service =
|
||||
{ }
|
||||
{ "service_on"
|
||||
{ "1"
|
||||
{ "protocol" = "raw/22" }
|
||||
{ "kitename" = "@kitename" }
|
||||
{ "backend_host" = "localhost" }
|
||||
{ "backend_port" = "22" }
|
||||
{ "secret" = "@kitesecret" }
|
||||
}
|
||||
}
|
||||
{ "service_on"
|
||||
{ "2"
|
||||
{ "protocol" = "http" }
|
||||
{ "kitename" = "192.168.0.1" }
|
||||
{ "backend_host" = "127.0.0.1" }
|
||||
{ "backend_port" = "80" }
|
||||
}
|
||||
}
|
||||
{ "service_on"
|
||||
{ "3"
|
||||
{ "protocol" = "https" }
|
||||
{ "kitename" = "yourhostname,fqdn" }
|
||||
{ "backend_host" = "127.0.0.1" }
|
||||
{ "backend_port" = "443" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let conf_encryption = "
|
||||
frontend=frontend.your.domain:443
|
||||
fe_certname=frontend.your/domain
|
||||
ca_certs=/etc/pagekite.d/site-cert.pem
|
||||
tls_endpoint=frontend.your.domain:/path/to/frontend.pem
|
||||
"
|
||||
test Pagekite.lns get conf_encryption =
|
||||
{ }
|
||||
{ "frontend" = "frontend.your.domain:443" }
|
||||
{ "fe_certname" = "frontend.your/domain" }
|
||||
{ "ca_certs" = "/etc/pagekite.d/site-cert.pem" }
|
||||
{ "tls_endpoint" = "frontend.your.domain:/path/to/frontend.pem" }
|
||||
|
||||
|
||||
let conf_service_cfg = "insecure
|
||||
service_cfg = KITENAME.pagekite.me/80 : insecure : True
|
||||
"
|
||||
test Pagekite.lns get conf_service_cfg =
|
||||
{ "insecure" }
|
||||
{ "service_cfg" = "KITENAME.pagekite.me/80 : insecure : True" }
|
||||
0
plinth/modules/__init__.py
Normal file
0
plinth/modules/__init__.py
Normal file
@ -19,9 +19,16 @@
|
||||
Plinth module to configure PageKite
|
||||
"""
|
||||
|
||||
from . import pagekite
|
||||
from .pagekite import init
|
||||
from gettext import gettext as _
|
||||
from plinth import cfg
|
||||
|
||||
__all__ = ['pagekite', 'init']
|
||||
__all__ = ['init']
|
||||
|
||||
depends = ['plinth.modules.apps']
|
||||
|
||||
|
||||
def init():
|
||||
"""Intialize the PageKite module"""
|
||||
menu = cfg.main_menu.get('apps:index')
|
||||
menu.add_urlname(_('Public Visibility (PageKite)'),
|
||||
'glyphicon-flag', 'pagekite:index', 50)
|
||||
|
||||
224
plinth/modules/pagekite/forms.py
Normal file
224
plinth/modules/pagekite/forms.py
Normal file
@ -0,0 +1,224 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import copy
|
||||
from gettext import gettext as _
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.core import validators
|
||||
|
||||
from plinth.errors import ActionError
|
||||
from .util import _run, get_kite_details, BACKEND_HOST, KITE_NAME, \
|
||||
KITE_SECRET, PREDEFINED_SERVICES
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TrimmedCharField(forms.CharField):
|
||||
"""Trim the contents of a CharField"""
|
||||
def clean(self, value):
|
||||
"""Clean and validate the field value"""
|
||||
if value:
|
||||
value = value.strip()
|
||||
|
||||
return super(TrimmedCharField, self).clean(value)
|
||||
|
||||
|
||||
class ConfigurationForm(forms.Form):
|
||||
"""Configure PageKite credentials and frontend"""
|
||||
|
||||
enabled = forms.BooleanField(label=_('Enable PageKite'), required=False)
|
||||
|
||||
server = forms.CharField(
|
||||
label=_('Server'), required=False,
|
||||
help_text=_('Select your pagekite.net server. Set "pagekite.net" to '
|
||||
'use the default pagekite.net server'),
|
||||
widget=forms.TextInput())
|
||||
|
||||
kite_name = TrimmedCharField(
|
||||
label=_('Kite name'),
|
||||
help_text=_('Example: mybox.pagekite.me'),
|
||||
validators=[
|
||||
validators.RegexValidator(r'^[\w-]{1,63}(\.[\w-]{1,63})*$',
|
||||
_('Invalid kite name'))])
|
||||
|
||||
kite_secret = TrimmedCharField(
|
||||
label=_('Kite secret'),
|
||||
help_text=_('A secret associated with the kite or the default secret \
|
||||
for your account if no secret is set on the kite'))
|
||||
|
||||
def save(self, request):
|
||||
old = self.initial
|
||||
new = self.cleaned_data
|
||||
LOGGER.info('New status is - %s', new)
|
||||
|
||||
if old != new:
|
||||
|
||||
config_changed = False
|
||||
|
||||
if old['kite_name'] != new['kite_name'] or \
|
||||
old['kite_secret'] != new['kite_secret']:
|
||||
_run(['set-kite', '--kite-name', new['kite_name'],
|
||||
'--kite-secret', new['kite_secret']])
|
||||
messages.success(request, _('Kite details set'))
|
||||
config_changed = True
|
||||
|
||||
if old['server'] != new['server']:
|
||||
_run(['set-frontend', new['server']])
|
||||
messages.success(request, _('Pagekite server set'))
|
||||
config_changed = True
|
||||
|
||||
if old['enabled'] != new['enabled']:
|
||||
if new['enabled']:
|
||||
_run(['start-and-enable'])
|
||||
messages.success(request, _('PageKite enabled'))
|
||||
else:
|
||||
_run(['stop-and-disable'])
|
||||
messages.success(request, _('PageKite disabled'))
|
||||
|
||||
# Restart the service if the config was changed while the service
|
||||
# was running, so changes take effect immediately.
|
||||
elif config_changed and new['enabled']:
|
||||
_run(['restart'])
|
||||
|
||||
|
||||
class StandardServiceForm(forms.Form):
|
||||
"""Creates a form out of PREDEFINED_SERVICES"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Add the fields from PREDEFINED_SERVICES"""
|
||||
super(StandardServiceForm, self).__init__(*args, **kwargs)
|
||||
kite = get_kite_details()
|
||||
for name, service in PREDEFINED_SERVICES.items():
|
||||
if name in ('http', 'https'):
|
||||
help_text = service['help_text'].format(kite['kite_name'])
|
||||
else:
|
||||
help_text = service['help_text']
|
||||
self.fields[name] = forms.BooleanField(label=service['label'],
|
||||
help_text=help_text,
|
||||
required=False)
|
||||
|
||||
def save(self, request):
|
||||
formdata = self.cleaned_data
|
||||
for service_name in PREDEFINED_SERVICES.keys():
|
||||
if self.initial[service_name] != formdata[service_name]:
|
||||
service = PREDEFINED_SERVICES[service_name]['params']
|
||||
service = json.dumps(service)
|
||||
if formdata[service_name]:
|
||||
_run(['add-service', '--service', service])
|
||||
messages.success(request, _('Service enabled: {name}')
|
||||
.format(name=service_name))
|
||||
else:
|
||||
_run(['remove-service', '--service', service])
|
||||
messages.success(request, _('Service disabled: {name}')
|
||||
.format(name=service_name))
|
||||
|
||||
|
||||
class BaseCustomServiceForm(forms.Form):
|
||||
"""Basic form functionality to handle a custom service"""
|
||||
choices = [("http", "http"), ("https", "https"), ("raw", "raw")]
|
||||
protocol = forms.ChoiceField(choices=choices, label="protocol")
|
||||
frontend_port = forms.IntegerField(min_value=0, max_value=65535,
|
||||
label="external (frontend) port",
|
||||
required=True)
|
||||
backend_port = forms.IntegerField(min_value=0, max_value=65535,
|
||||
label="internal (freedombox) port")
|
||||
subdomains = forms.BooleanField(label="Enable Subdomains", required=False)
|
||||
|
||||
def convert_formdata_to_service(self, formdata):
|
||||
"""Add information to make a service out of the form data"""
|
||||
# convert integers to str (to compare values with DEFAULT_SERVICES)
|
||||
for field in ('frontend_port', 'backend_port'):
|
||||
formdata[field] = str(formdata[field])
|
||||
|
||||
# set kitename and kitesecret if not already set
|
||||
if 'kitename' not in formdata:
|
||||
if 'subdomains' in formdata and formdata['subdomains']:
|
||||
formdata['kitename'] = "*.%s" % KITE_NAME
|
||||
else:
|
||||
formdata['kitename'] = KITE_NAME
|
||||
if 'secret' not in formdata:
|
||||
formdata['secret'] = KITE_SECRET
|
||||
|
||||
# merge protocol and frontend_port back to one entry (protocol)
|
||||
if 'frontend_port' in formdata:
|
||||
if formdata['frontend_port'] not in formdata['protocol']:
|
||||
formdata['protocol'] = "%s/%s" % (formdata['protocol'],
|
||||
formdata['frontend_port'])
|
||||
if 'backend_host' not in formdata:
|
||||
formdata['backend_host'] = BACKEND_HOST
|
||||
|
||||
return formdata
|
||||
|
||||
|
||||
class DeleteCustomServiceForm(BaseCustomServiceForm):
|
||||
|
||||
def delete(self, request):
|
||||
service = self.convert_formdata_to_service(self.cleaned_data)
|
||||
_run(['remove-service', '--service', json.dumps(service)])
|
||||
messages.success(request, _('Deleted custom service'))
|
||||
|
||||
|
||||
class AddCustomServiceForm(BaseCustomServiceForm):
|
||||
"""Adds the save() method and validation to not add predefined services"""
|
||||
|
||||
def matches_predefined_service(self, formdata):
|
||||
"""Returns whether the user input matches a predefined service"""
|
||||
service = self.convert_formdata_to_service(formdata)
|
||||
match_found = False
|
||||
for predefined_service_obj in PREDEFINED_SERVICES.values():
|
||||
# manually add the port to compare predefined with custom services
|
||||
# that's due to the (sometimes) implicit port in the configuration
|
||||
predefined_service = copy.copy(predefined_service_obj['params'])
|
||||
if predefined_service['protocol'] == 'http':
|
||||
predefined_service['protocol'] = 'http/80'
|
||||
elif predefined_service['protocol'] == 'https':
|
||||
predefined_service['protocol'] = 'https/443'
|
||||
|
||||
# The formdata service has additional keys, so we can't compare
|
||||
# the dicts directly.
|
||||
# instead look whether predefined_service is a subset of service
|
||||
if all(service[k] == v for k, v in predefined_service.items()):
|
||||
match_found = True
|
||||
break
|
||||
return match_found
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AddCustomServiceForm, self).clean()
|
||||
try:
|
||||
is_predefined = self.matches_predefined_service(cleaned_data)
|
||||
except KeyError:
|
||||
is_predefined = False
|
||||
if is_predefined:
|
||||
msg = _("""This service is available as a standard service. Please
|
||||
use the 'Standard Services' page to enable it.""")
|
||||
raise forms.ValidationError(msg)
|
||||
return cleaned_data
|
||||
|
||||
def save(self, request):
|
||||
service = self.convert_formdata_to_service(self.cleaned_data)
|
||||
try:
|
||||
_run(['add-service', '--service', json.dumps(service)])
|
||||
messages.success(request, _('Added custom service'))
|
||||
except ActionError as exception:
|
||||
if "already exists" in str(exception):
|
||||
messages.error(request, _('This service already exists'))
|
||||
else:
|
||||
raise
|
||||
@ -1,199 +0,0 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Plinth module for configuring PageKite service
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core import validators
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.template.response import TemplateResponse
|
||||
from gettext import gettext as _
|
||||
import logging
|
||||
|
||||
from plinth import actions
|
||||
from plinth import cfg
|
||||
from plinth import package
|
||||
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
subsubmenu = [{'url': reverse_lazy('pagekite:index'),
|
||||
'text': _('About PageKite')},
|
||||
{'url': reverse_lazy('pagekite:configure'),
|
||||
'text': _('Configure PageKite')}]
|
||||
|
||||
|
||||
def init():
|
||||
"""Intialize the PageKite module"""
|
||||
menu = cfg.main_menu.get('apps:index')
|
||||
menu.add_urlname(_('Public Visibility (PageKite)'),
|
||||
'glyphicon-flag', 'pagekite:index', 50)
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
"""Serve introduction page"""
|
||||
return TemplateResponse(request, 'pagekite_introduction.html',
|
||||
{'title': _('Public Visibility (PageKite)'),
|
||||
'subsubmenu': subsubmenu})
|
||||
|
||||
|
||||
class TrimmedCharField(forms.CharField):
|
||||
"""Trim the contents of a CharField"""
|
||||
def clean(self, value):
|
||||
"""Clean and validate the field value"""
|
||||
if value:
|
||||
value = value.strip()
|
||||
|
||||
return super(TrimmedCharField, self).clean(value)
|
||||
|
||||
|
||||
class ConfigureForm(forms.Form): # pylint: disable-msg=W0232
|
||||
"""Form to configure PageKite"""
|
||||
enabled = forms.BooleanField(label=_('Enable PageKite'),
|
||||
required=False)
|
||||
|
||||
server = forms.CharField(
|
||||
label=_('Server'), required=False,
|
||||
help_text=_('Currently only pagekite.net server is supported'),
|
||||
widget=forms.TextInput(attrs={'placeholder': 'pagekite.net',
|
||||
'disabled': 'disabled'}))
|
||||
|
||||
kite_name = TrimmedCharField(
|
||||
label=_('Kite name'),
|
||||
help_text=_('Example: mybox1-myacc.pagekite.me'),
|
||||
validators=[
|
||||
validators.RegexValidator(r'^[\w-]{1,63}(\.[\w-]{1,63})*$',
|
||||
_('Invalid kite name'))])
|
||||
|
||||
kite_secret = TrimmedCharField(
|
||||
label=_('Kite secret'),
|
||||
help_text=_('A secret associated with the kite or the default secret \
|
||||
for your account if no secret is set on the kite'))
|
||||
|
||||
http_enabled = forms.BooleanField(
|
||||
label=_('Web Server (HTTP)'), required=False,
|
||||
help_text=_('Site will be available at \
|
||||
<a href="http://mybox1-myacc.pagekite.me">http://mybox1-myacc.pagekite.me \
|
||||
</a>'))
|
||||
|
||||
ssh_enabled = forms.BooleanField(
|
||||
label=_('Secure Shell (SSH)'), required=False,
|
||||
help_text=_('See SSH client setup <a href="\
|
||||
https://pagekite.net/wiki/Howto/SshOverPageKite/">instructions</a>'))
|
||||
|
||||
|
||||
@login_required
|
||||
@package.required(['pagekite'])
|
||||
def configure(request):
|
||||
"""Serve the configuration form"""
|
||||
status = get_status()
|
||||
|
||||
form = None
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ConfigureForm(request.POST, prefix='pagekite')
|
||||
# pylint: disable-msg=E1101
|
||||
if form.is_valid():
|
||||
_apply_changes(request, status, form.cleaned_data)
|
||||
status = get_status()
|
||||
form = ConfigureForm(initial=status, prefix='pagekite')
|
||||
else:
|
||||
form = ConfigureForm(initial=status, prefix='pagekite')
|
||||
|
||||
return TemplateResponse(request, 'pagekite_configure.html',
|
||||
{'title': _('Configure PageKite'),
|
||||
'status': status,
|
||||
'form': form,
|
||||
'subsubmenu': subsubmenu})
|
||||
|
||||
|
||||
def get_status():
|
||||
"""
|
||||
Return the current status of PageKite configuration by
|
||||
executing various actions.
|
||||
"""
|
||||
status = {}
|
||||
|
||||
# PageKite service enabled/disabled
|
||||
output = _run(['get-status'])
|
||||
status['enabled'] = (output.split()[0] == 'enabled')
|
||||
|
||||
# PageKite kite details
|
||||
output = _run(['get-kite'])
|
||||
kite_details = output.split()
|
||||
status['kite_name'] = kite_details[0]
|
||||
status['kite_secret'] = kite_details[1]
|
||||
|
||||
# Service status
|
||||
status['service'] = {}
|
||||
for service in ('http', 'ssh'):
|
||||
output = _run(['get-service-status', service])
|
||||
status[service + '_enabled'] = (output.split()[0] == 'enabled')
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def _apply_changes(request, old_status, new_status):
|
||||
"""Apply the changes to PageKite configuration"""
|
||||
LOGGER.info('New status is - %s', new_status)
|
||||
|
||||
if old_status != new_status:
|
||||
_run(['stop'])
|
||||
|
||||
if old_status['enabled'] != new_status['enabled']:
|
||||
if new_status['enabled']:
|
||||
_run(['set-status', 'enable'])
|
||||
messages.success(request, _('PageKite enabled'))
|
||||
else:
|
||||
_run(['set-status', 'disable'])
|
||||
messages.success(request, _('PageKite disabled'))
|
||||
|
||||
if old_status['kite_name'] != new_status['kite_name'] or \
|
||||
old_status['kite_secret'] != new_status['kite_secret']:
|
||||
_run(['set-kite', '--kite-name', new_status['kite_name'],
|
||||
'--kite-secret', new_status['kite_secret']])
|
||||
messages.success(request, _('Kite details set'))
|
||||
|
||||
for service in ['http', 'ssh']:
|
||||
if old_status[service + '_enabled'] != \
|
||||
new_status[service + '_enabled']:
|
||||
if new_status[service + '_enabled']:
|
||||
_run(['set-service-status', service, 'enable'])
|
||||
messages.success(request, _('Service enabled: {service}')
|
||||
.format(service=service))
|
||||
else:
|
||||
_run(['set-service-status', service, 'disable'])
|
||||
messages.success(request, _('Service disabled: {service}')
|
||||
.format(service=service))
|
||||
|
||||
if old_status != new_status:
|
||||
_run(['start'])
|
||||
|
||||
|
||||
def _run(arguments, superuser=True):
|
||||
"""Run a given command and raise exception if there was an error"""
|
||||
command = 'pagekite-configure'
|
||||
|
||||
if superuser:
|
||||
return actions.superuser_run(command, arguments)
|
||||
else:
|
||||
return actions.run(command, arguments)
|
||||
@ -30,17 +30,12 @@
|
||||
<div id='pagekite-post-enabled-form'
|
||||
style='display: {{ form.enabled.value|yesno:'block,none' }};'>
|
||||
<h3>PageKite Account</h3>
|
||||
{% include 'bootstrapform/field.html' with field=form.server %}
|
||||
{% include 'bootstrapform/field.html' with field=form.kite_name %}
|
||||
{% include 'bootstrapform/field.html' with field=form.kite_secret %}
|
||||
{{ form.server|bootstrap_horizontal }}
|
||||
{{ form.kite_name|bootstrap_horizontal }}
|
||||
{{ form.kite_secret|bootstrap_horizontal }}
|
||||
|
||||
<h3>Services</h3>
|
||||
{% include 'bootstrapform/field.html' with field=form.http_enabled %}
|
||||
{% include 'bootstrapform/field.html' with field=form.ssh_enabled %}
|
||||
</div>
|
||||
|
||||
<input type="submit" class="btn btn-primary" value="Update setup"/>
|
||||
|
||||
<input type="submit" class="btn btn-primary" value="Save settings"/>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
104
plinth/modules/pagekite/templates/pagekite_custom_services.html
Normal file
104
plinth/modules/pagekite/templates/pagekite_custom_services.html
Normal file
@ -0,0 +1,104 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% load pagekite_extras %}
|
||||
|
||||
{% block page_head %}
|
||||
<style type="text/css">
|
||||
div.custom-services span.service {
|
||||
display: inline-block;
|
||||
padding-top: 6px;
|
||||
}
|
||||
form.pull-right button {
|
||||
margin: 10px 5px;
|
||||
}
|
||||
.add-service input.btn {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<b>Warning:</b><br>Your PageKite frontend server may not support all the
|
||||
protocol/port combinations that you are able to define here. For example,
|
||||
HTTPS on ports other than 443 is known to cause problems.
|
||||
</div>
|
||||
|
||||
<div class="row custom-services">
|
||||
|
||||
<div class="col-lg-6">
|
||||
<form class="form add-service" method="post">
|
||||
<h4>Create a custom service</h4>
|
||||
{{ form|bootstrap_horizontal:'col-lg-6' }}
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<div class=" col-lg-offset-6 col-lg-6">
|
||||
<input type="submit" class="btn btn-primary" value="Add Service"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-5 col-lg-offset-1">
|
||||
<h4>Existing custom services</h4>
|
||||
{% if not custom_services %}
|
||||
<i>You don't have any Custom Services enabled</i>
|
||||
{% endif %}
|
||||
<div class="list-group">
|
||||
{% for service in custom_services %}
|
||||
{% create_pagekite_service_url service kite_name as service_url %}
|
||||
<div class="list-group-item clearfix">
|
||||
<span class="service">
|
||||
<span title="Connects {{ service_url }} to {{ service.backend_host }}:{{ service.backend_port }}">
|
||||
{% if service_url|slice:":4" == "http" %}
|
||||
<a href="{{ service_url }}">{{ service_url }}</a>
|
||||
{% else %}
|
||||
{{ service_url }}
|
||||
{% endif %}
|
||||
<br>
|
||||
connected to {{ service.backend_host }}:{{ service.backend_port }}
|
||||
</span>
|
||||
</span>
|
||||
<form class="form pull-right" method="post"
|
||||
action="{% url 'pagekite:delete-custom-service' %}">
|
||||
<div style='display:none'>
|
||||
{% csrf_token %}
|
||||
{{ service.form.as_p }}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default"
|
||||
title="Delete this service">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true">
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
<p>PageKite is a system for exposing {{ cfg.box_name }} services when
|
||||
you don't have a direct connection to the Internet. You only need this
|
||||
service if your {{ cfg.box_name }} services are unreachable from the
|
||||
if your {{ cfg.box_name }} services are unreachable from the
|
||||
rest of the Internet. This includes the following situations: </p>
|
||||
|
||||
<ul>
|
||||
@ -41,11 +41,10 @@ rest of the Internet. This includes the following situations: </p>
|
||||
</ul>
|
||||
|
||||
<p>PageKite works around NAT, firewalls and IP-address limitations by
|
||||
using a combination of tunnels and reverse proxies. Currently,
|
||||
exposing web server and SSH server are supported. An intermediary
|
||||
server with direct Internet access is required. Currently, only
|
||||
pagekite.net server is supported and you will need an account
|
||||
there. In future, it might be possible to use your buddy's
|
||||
using a combination of tunnels and reverse proxies. You can use any
|
||||
pagekite service provider, for example
|
||||
<a href="https://pagekite.net">pagekite.net</a>.
|
||||
In future it might be possible to use your buddy's
|
||||
{{ cfg.box_name }} for this.</p>
|
||||
|
||||
<p>
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block page_head %}
|
||||
<style type="text/css">
|
||||
div.checkbox .help-block {
|
||||
display: inline-block;
|
||||
margin: 0px 10px;
|
||||
}
|
||||
input.btn {
|
||||
margin: 10px 15px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<b>Warning:</b><br>
|
||||
<p>Published services are accessible and attackable from the evil internet.<p>
|
||||
<p>Exposing SSH with the default password for 'fbx' is a VERY BAD idea.</p>
|
||||
</div>
|
||||
|
||||
<form class="form predefined" method="post">
|
||||
{{ form.http|bootstrap_horizontal:'col-lg-0' }}
|
||||
{{ form.https|bootstrap_horizontal:'col-lg-0' }}
|
||||
{{ form.ssh|bootstrap_horizontal:'col-lg-0' }}
|
||||
{% csrf_token %}
|
||||
<input type="submit" class="btn btn-primary" value="Save Services"/>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
44
plinth/modules/pagekite/templatetags/pagekite_extras.py
Normal file
44
plinth/modules/pagekite/templatetags/pagekite_extras.py
Normal file
@ -0,0 +1,44 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from django import template
|
||||
from plinth.modules.pagekite.util import prepare_service_for_display
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.assignment_tag
|
||||
def create_pagekite_service_url(service, kite_name):
|
||||
"""Create a URL out of a pagekite service
|
||||
|
||||
Parameters: - service: the service params dictionary
|
||||
- kite_name: kite name from the pagekite configuration, not
|
||||
from the service params
|
||||
"""
|
||||
# add extra information if it's missing
|
||||
if 'subdomains' not in service:
|
||||
service = prepare_service_for_display(service)
|
||||
|
||||
urlparams = {'protocol': service['protocol']}
|
||||
if service['subdomains']:
|
||||
urlparams['kite_name'] = "*.%s" % kite_name
|
||||
else:
|
||||
urlparams['kite_name'] = kite_name
|
||||
url = "{protocol}://{kite_name}".format(**urlparams)
|
||||
if 'frontend_port' in service and service['frontend_port']:
|
||||
url = "%s:%s" % (url, service['frontend_port'])
|
||||
return url
|
||||
@ -20,10 +20,23 @@ URLs for the PageKite module
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from .views import StandardServiceView, CustomServiceView, ConfigurationView, \
|
||||
DeleteServiceView, index
|
||||
|
||||
|
||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
||||
'plinth.modules.pagekite.pagekite',
|
||||
url(r'^apps/pagekite/$', 'index', name='index'),
|
||||
url(r'^apps/pagekite/configure/$', 'configure', name='configure'),
|
||||
'plinth.modules.pagekite.views',
|
||||
url(r'^apps/pagekite/$', login_required(index), name='index'),
|
||||
url(r'^apps/pagekite/configure/$',
|
||||
login_required(ConfigurationView.as_view()), name='configure'),
|
||||
url(r'^apps/pagekite/services/standard$',
|
||||
login_required(StandardServiceView.as_view()),
|
||||
name='standard-services'),
|
||||
url(r'^apps/pagekite/services/custom$',
|
||||
login_required(CustomServiceView.as_view()), name='custom-services'),
|
||||
url(r'^apps/pagekite/services/custom/delete$',
|
||||
login_required(DeleteServiceView.as_view()),
|
||||
name='delete-custom-service'),
|
||||
)
|
||||
|
||||
147
plinth/modules/pagekite/util.py
Normal file
147
plinth/modules/pagekite/util.py
Normal file
@ -0,0 +1,147 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from gettext import gettext as _
|
||||
import json
|
||||
import logging
|
||||
|
||||
from plinth import actions
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# defaults for the credentials; @kitename acts as a placeholder and is
|
||||
# understood (and replaced with the actual kitename) by pagekite.
|
||||
BACKEND_HOST = 'localhost'
|
||||
KITE_NAME = '@kitename'
|
||||
KITE_SECRET = '@kitesecret'
|
||||
|
||||
SERVICE_PARAMS = ['protocol', 'kitename', 'backend_host', 'backend_port',
|
||||
'secret']
|
||||
|
||||
# Predefined services are used to build the PredefinedServiceForm
|
||||
#
|
||||
# ATTENTION: When changing the params, make sure that the AddCustomServiceForm
|
||||
# still recognizes when you try to add a service equal to a predefined one
|
||||
PREDEFINED_SERVICES = {
|
||||
'http': {
|
||||
'params': {'protocol': 'http',
|
||||
'kitename': KITE_NAME,
|
||||
'backend_port': '80',
|
||||
'backend_host': BACKEND_HOST,
|
||||
'secret': KITE_SECRET},
|
||||
'label': _("Web Server (HTTP)"),
|
||||
'help_text': _("Site will be available at "
|
||||
"<a href=\"http://{0}\">http://{0}</a>"),
|
||||
},
|
||||
'https': {
|
||||
'params': {'protocol': 'https',
|
||||
'kitename': KITE_NAME,
|
||||
'backend_port': '443',
|
||||
'backend_host': BACKEND_HOST,
|
||||
'secret': KITE_SECRET},
|
||||
'label': _("Web Server (HTTPS)"),
|
||||
'help_text': _("Site will be available at "
|
||||
"<a href=\"https://{0}\">https://{0}</a>"),
|
||||
},
|
||||
'ssh': {
|
||||
'params': {'protocol': 'raw/22',
|
||||
'kitename': KITE_NAME,
|
||||
'backend_port': '22',
|
||||
'backend_host': BACKEND_HOST,
|
||||
'secret': KITE_SECRET},
|
||||
'label': _("Secure Shell (SSH)"),
|
||||
'help_text': _("See SSH client setup <a href=\""
|
||||
"https://pagekite.net/wiki/Howto/SshOverPageKite/\">"
|
||||
"instructions</a>")
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_kite_details():
|
||||
output = _run(['get-kite'])
|
||||
kite_details = output.split()
|
||||
return {'kite_name': kite_details[0],
|
||||
'kite_secret': kite_details[1]}
|
||||
|
||||
|
||||
def get_pagekite_config():
|
||||
"""
|
||||
Return the current PageKite configuration by executing various actions.
|
||||
"""
|
||||
status = {}
|
||||
|
||||
# PageKite service enabled/disabled
|
||||
# This assumes that if pagekite is running it's also enabled as a service
|
||||
output = _run(['is-running'])
|
||||
status['enabled'] = (output.split()[0] == 'yes')
|
||||
|
||||
# PageKite kite details
|
||||
status.update(get_kite_details())
|
||||
|
||||
# PageKite frontend server
|
||||
server = _run(['get-frontend'])
|
||||
status['server'] = server.replace('\n', '')
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def get_pagekite_services():
|
||||
"""Get enabled services. Returns two values:
|
||||
|
||||
1. predefined services: {'http': False, 'ssh': True, 'https': True}
|
||||
2. custom services: [{'protocol': 'http', 'secret' 'nono', ..}, [..]}
|
||||
"""
|
||||
custom = []
|
||||
predefined = {}
|
||||
# set all predefined services to 'disabled' by default
|
||||
[predefined.update({proto: False}) for proto in PREDEFINED_SERVICES.keys()]
|
||||
# now, search for the enabled ones
|
||||
for serviceline in _run(['get-services']).split('\n'):
|
||||
if not serviceline: # skip empty lines
|
||||
continue
|
||||
|
||||
service = json.loads(serviceline)
|
||||
for name, predefined_service in PREDEFINED_SERVICES.items():
|
||||
if service == predefined_service['params']:
|
||||
predefined[name] = True
|
||||
break
|
||||
else:
|
||||
custom.append(service)
|
||||
return predefined, custom
|
||||
|
||||
|
||||
def prepare_service_for_display(service):
|
||||
""" Add extra information that is used when displaying a service
|
||||
|
||||
- protocol is split into 'protocol' and 'frontend_port'
|
||||
- detect whether 'subdomains' are supported (as boolean)
|
||||
"""
|
||||
protocol = service['protocol']
|
||||
if '/' in protocol:
|
||||
service['protocol'], service['frontend_port'] = protocol.split('/')
|
||||
service['subdomains'] = service['kitename'].startswith('*.')
|
||||
return service
|
||||
|
||||
|
||||
def _run(arguments, superuser=True):
|
||||
"""Run a given command and raise exception if there was an error"""
|
||||
command = 'pagekite'
|
||||
|
||||
if superuser:
|
||||
return actions.superuser_run(command, arguments)
|
||||
else:
|
||||
return actions.run(command, arguments)
|
||||
133
plinth/modules/pagekite/views.py
Normal file
133
plinth/modules/pagekite/views.py
Normal file
@ -0,0 +1,133 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from gettext import gettext as _
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.generic import View, TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from plinth import package
|
||||
from .util import get_pagekite_config, get_pagekite_services, \
|
||||
get_kite_details, prepare_service_for_display
|
||||
from .forms import ConfigurationForm, StandardServiceForm, \
|
||||
AddCustomServiceForm, DeleteCustomServiceForm
|
||||
|
||||
|
||||
required_packages = ('pagekite', 'augeas-tools', 'python-augeas')
|
||||
subsubmenu = [{'url': reverse_lazy('pagekite:index'),
|
||||
'text': _('About PageKite')},
|
||||
{'url': reverse_lazy('pagekite:configure'),
|
||||
'text': _('Configure PageKite')},
|
||||
{'url': reverse_lazy('pagekite:standard-services'),
|
||||
'text': _('Standard Services')},
|
||||
{'url': reverse_lazy('pagekite:custom-services'),
|
||||
'text': _('Custom Services')}]
|
||||
|
||||
|
||||
def index(request):
|
||||
"""Serve introduction page"""
|
||||
return TemplateResponse(request, 'pagekite_introduction.html',
|
||||
{'title': _('Public Visibility (PageKite)'),
|
||||
'subsubmenu': subsubmenu})
|
||||
|
||||
|
||||
class ContextMixin(object):
|
||||
"""Mixin to add 'subsubmenu' and 'title' to the context.
|
||||
|
||||
Also adds the requirement of all necessary packages to be installed
|
||||
"""
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Use self.title and the module-level subsubmenu"""
|
||||
context = super(ContextMixin, self).get_context_data(**kwargs)
|
||||
context['title'] = getattr(self, 'title', '')
|
||||
context['subsubmenu'] = subsubmenu
|
||||
return context
|
||||
|
||||
@method_decorator(package.required(required_packages))
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(ContextMixin, self).dispatch(*args, **kwargs)
|
||||
|
||||
|
||||
class DeleteServiceView(ContextMixin, View):
|
||||
def post(self, request):
|
||||
form = DeleteCustomServiceForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.delete(request)
|
||||
return HttpResponseRedirect(reverse('pagekite:custom-services'))
|
||||
|
||||
|
||||
class CustomServiceView(ContextMixin, TemplateView):
|
||||
template_name = 'pagekite_custom_services.html'
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super(CustomServiceView, self).get_context_data(*args,
|
||||
**kwargs)
|
||||
unused, custom_services = get_pagekite_services()
|
||||
for service in custom_services:
|
||||
service['form'] = AddCustomServiceForm(initial=service)
|
||||
context['custom_services'] = [prepare_service_for_display(service)
|
||||
for service in custom_services]
|
||||
context.update(get_kite_details())
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
context = self.get_context_data(**kwargs)
|
||||
form = AddCustomServiceForm(prefix="custom")
|
||||
context['form'] = form
|
||||
return self.render_to_response(context)
|
||||
|
||||
def post(self, request):
|
||||
form = AddCustomServiceForm(request.POST, prefix="custom")
|
||||
if form.is_valid():
|
||||
form.save(request)
|
||||
form = AddCustomServiceForm(prefix="custom")
|
||||
|
||||
context = self.get_context_data()
|
||||
context['form'] = form
|
||||
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
||||
class StandardServiceView(ContextMixin, FormView):
|
||||
template_name = 'pagekite_standard_services.html'
|
||||
title = 'PageKite Standard Services'
|
||||
form_class = StandardServiceForm
|
||||
success_url = reverse_lazy('pagekite:standard-services')
|
||||
|
||||
def get_initial(self):
|
||||
return get_pagekite_services()[0]
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save(self.request)
|
||||
return super(StandardServiceView, self).form_valid(form)
|
||||
|
||||
|
||||
class ConfigurationView(ContextMixin, FormView):
|
||||
template_name = 'pagekite_configure.html'
|
||||
form_class = ConfigurationForm
|
||||
prefix = 'pagekite'
|
||||
success_url = reverse_lazy('pagekite:configure')
|
||||
|
||||
def get_initial(self):
|
||||
return get_pagekite_config()
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save(self.request)
|
||||
return super(ConfigurationView, self).form_valid(form)
|
||||
55
plinth/tests/test_pagekite.py
Normal file
55
plinth/tests/test_pagekite.py
Normal file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestPagekiteActions(unittest.TestCase):
|
||||
"""Test-cases for the pagekite action utils"""
|
||||
_tests = [
|
||||
{
|
||||
'line': 'https/8080:*.@kitename:localhost:8080:@kitesecret',
|
||||
'params': {'kitename': '*.@kitename', 'backend_host': 'localhost',
|
||||
'secret': '@kitesecret', 'protocol': 'https/8080',
|
||||
'backend_port': '8080'}
|
||||
},
|
||||
{
|
||||
'line': 'https:*.@kitename:localhost:80:@kitesecret',
|
||||
'params': {'protocol': 'https',
|
||||
'kitename': '*.@kitename',
|
||||
'backend_port': '80',
|
||||
'backend_host': 'localhost',
|
||||
'secret': '@kitesecret'}
|
||||
},
|
||||
{
|
||||
'line': 'raw/22:@kitename:localhost:22:@kitesecret',
|
||||
'params': {'protocol': 'raw/22',
|
||||
'kitename': '@kitename',
|
||||
'backend_port': '22',
|
||||
'backend_host': 'localhost',
|
||||
'secret': '@kitesecret'}
|
||||
},
|
||||
]
|
||||
|
||||
@unittest.skip('Use this test once the function is in the pagekite module '
|
||||
'instead of actions/pagekite_util.py')
|
||||
def test_convert_service_to_string(self):
|
||||
""" Test deconstructing parameter dictionaries into strings """
|
||||
for test in self._tests:
|
||||
service_string = convert_service_to_string(test['params'])
|
||||
self.assertEqual(test['line'], service_string)
|
||||
4
setup.py
4
setup.py
@ -140,6 +140,10 @@ setuptools.setup(
|
||||
glob.glob(os.path.join('actions', '*'))),
|
||||
('/usr/share/man/man1', ['doc/plinth.1']),
|
||||
('/etc/plinth', ['data/etc/plinth/plinth.config']),
|
||||
('/usr/share/augeas/lenses',
|
||||
['data/usr/share/augeas/lenses/pagekite.aug']),
|
||||
('/usr/share/augeas/lenses/tests',
|
||||
['data/usr/share/augeas/lenses/tests/test_pagekite.aug']),
|
||||
('/etc/plinth/modules-enabled',
|
||||
glob.glob(os.path.join('data/etc/plinth/modules-enabled',
|
||||
'*')))],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user