207 lines
6.9 KiB
Plaintext
207 lines
6.9 KiB
Plaintext
|
#!/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)
|