Spotter-VM/basic/usr/local/bin/spotter-appmgr

207 lines
6.9 KiB
Plaintext
Raw Normal View History

#!/usr/bin/python
# -*- coding: utf-8 -*-
import argparse
import json
import os
import subprocess
CONF_FILE = '/etc/spotter/apps.json'
HOSTS_FILE = '/etc/hosts'
ISSUE_FILE = '/etc/issue'
NGINX_DIR = '/etc/nginx/conf.d'
NGINX_TEMPLATE = '''server {{
listen [::]:{port} ssl http2;
server_name {app}.{domain};
access_log /var/log/nginx/{app}.access.log;
error_log /var/log/nginx/{app}.error.log;
location / {{
proxy_pass http://{app}:8080;
}}
}}
'''
NGINX_DEFAULT_TEMPLATE = '''server {{
listen [::]:80 default_server ipv6only=off;
location / {{
return 301 https://$host:{port}$request_uri;
}}
location /.well-known/acme-challenge/ {{
root /etc/acme.sh.d;
}}
}}
server {{
listen [::]:{port} ssl http2 default_server ipv6only=off;
root /srv/portal;
index index.html;
location / {{
try_files $uri $uri/ =404;
}}
location /apps.json {{
alias /etc/spotter/apps.json;
}}
}}
'''
ISSUE_TEMPLATE = '''
\x1b[1;32m _____ _ _ __ ____ __
/ ____| | | | | \\\\ \\\\ / / \\\\/ |
| (___ _ __ ___ | |_| |_ ___ _ _\\\\ \\\\ / /| \\\\ / |
\\\\___ \\\\| '_ \\\\ / _ \\\\| __| __/ _ \\\\ '__\\\\ \\\\/ / | |\\\\/| |
____) | |_) | (_) | |_| || __/ | \\\\ / | | | |
|_____/| .__/ \\\\___/ \\\\__|\\\\__\\\\___|_| \\\\/ |_| |_|
| |
|_|\x1b[0m
\x1b[1;33mUPOZORNĚNÍ:\x1b[0m Neoprávněný přístup k tomuto zařízení je zakázán.
Musíte mít výslovné oprávnění k přístupu nebo konfiguraci tohoto zařízení.
Neoprávněné pokusy a kroky k přístupu nebo používání tohoto systému mohou mít
za následek občanské nebo trestní sankce.
\x1b[1;33mCAUTION:\x1b[0m Unauthozired access to this device is prohibited.
You must have explicit, authorized permission to access or configure this
device. Unauthorized attempts and actions to access or use this system may
result in civil or criminal penalties.
Pro přístup k aplikacím otevřete URL \x1b[1mhttps://{host}/\x1b[0m ve Vašem
internetovém prohlížeči.
\x1b[0;30m
'''
class SpotterManager:
def __init__(self):
self.conf = {}
with open(CONF_FILE, 'r') as f:
self.conf = json.load(f)
self.domain = self.conf["_"]["domain"]
self.port = self.conf["_"]["port"]
def save_conf(self):
with open(CONF_FILE, 'w') as f:
json.dump(self.conf, f)
def add_app(self, app, args):
self.add_app_to_conf(app, args)
if args.url:
self.update_app_conf(app)
self.add_app_to_nginx(app)
self.reload_nginx()
def add_app_to_conf(self, app, args):
self.conf[app] = {}
for key in ('url', 'login', 'password'):
value = getattr(args, key)
if value:
self.conf[app][key] = value
if args.property:
for key, value in args.property:
self.conf[app][key] = value
self.save_conf()
def update_app_conf(self, app):
script_path = os.path.join('/srv', app, 'update-url.sh')
if os.path.exists(script_path) and os.access(script_path, os.X_OK):
host = '{}.{}'.format(app, self.domain)
subprocess.call([script_path, host, self.port])
def add_app_to_nginx(self, app):
with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f:
f.write(NGINX_TEMPLATE.format(app=app, domain=self.domain, port=self.port))
def reload_nginx(self):
subprocess.call(['service', 'nginx', 'reload'])
def update_hosts(self, app):
with open(HOSTS_FILE, 'r') as f:
lines = f.readlines()
with open(HOSTS_FILE, 'w') as f:
for line in lines:
if not line.strip().endswith(' {}'.format(app)):
f.write(line)
f.write('{} {}\n'.format(get_container_ip(app), app))
def update_domain(self, domain, port):
self.domain = self.conf["_"]["domain"] = domain
self.port = self.conf["_"]["port"] = port
self.save_conf()
self.update_app_confs()
self.rebuild_nginx()
self.rebuild_issue()
self.reload_nginx()
def update_app_confs(self):
for app in self.conf.iteritems():
if 'url' in app[1]:
self.update_app_conf(app[0])
def rebuild_nginx(self):
for f in os.listdir(NGINX_DIR):
os.unlink(os.path.join(NGINX_DIR, f))
with open(os.path.join(NGINX_DIR, 'default.conf'), 'w') as f:
f.write(NGINX_DEFAULT_TEMPLATE.format(port=self.port))
for app in self.conf.iteritems():
if 'url' in app[1]:
self.add_app_to_nginx(app[0])
def rebuild_issue(self):
host = self.domain
if self.port != '443':
host = '{}:{}'.format(host, self.port)
with open(ISSUE_FILE, 'w') as f:
f.write(ISSUE_TEMPLATE.format(host=host))
def get_container_ip(app):
try:
return subprocess.check_output(['docker', 'inspect', '-f', '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}', app]).strip()
except:
return '127.0.0.1'
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Spotter VM application manager')
subparsers = parser.add_subparsers()
parser_add_app = subparsers.add_parser('add-app', help='Registers a new application and creates hosts and nginx definition for it')
parser_add_app.set_defaults(action='add-app')
parser_add_app.add_argument('app', help='Application name')
parser_add_app.add_argument('url', nargs='?', help='URL to the application. Use "{host}" as a host placeholder')
parser_add_app.add_argument('login', nargs='?', help='Administrative login')
parser_add_app.add_argument('password', nargs='?', help='Administrative password')
parser_add_app.add_argument('-p', '--property', nargs=2, action='append', help='Add arbitrary key-value to the application properties')
parser_update_app = subparsers.add_parser('update-hosts', help='Updates hosts definition for application container')
parser_update_app.set_defaults(action='update-hosts')
parser_update_app.add_argument('app', help='Application name')
parser_update_domain = subparsers.add_parser('update-domain', help='Rebuilds domain structure of VM with new domain name and new HTTPS port')
parser_update_domain.set_defaults(action='update-domain')
parser_update_domain.add_argument('domain', help='Domain name')
parser_update_domain.add_argument('port', help='HTTPS port')
args = parser.parse_args()
sm = SpotterManager()
if args.action == 'add-app':
sm.add_app(args.app, args)
elif args.action == 'update-hosts':
sm.update_hosts(args.app)
elif args.action == 'update-domain':
sm.update_domain(args.domain, args.port)