Introduce spotter-appmgr for application, domain and port management
This commit is contained in:
parent
258ece68f5
commit
dd5301a10d
@ -80,9 +80,9 @@ chroot /mnt update-extlinux
|
||||
chroot /mnt setup-timezone -z Europe/Prague
|
||||
|
||||
# Set hostname
|
||||
echo 'vm' >/mnt/etc/hostname
|
||||
sed -i 's/localhost/vm/' /mnt/etc/network/interfaces
|
||||
sed -i 's/localhost /vm localhost /' /mnt/etc/hosts
|
||||
echo 'spottervm' >/mnt/etc/hostname
|
||||
echo '127.0.0.1 spottervm localhost localhost.localdomain' >/mnt/etc/hosts
|
||||
sed -i '/hostname/d' /mnt/etc/network/interfaces
|
||||
|
||||
# Enable services on boot
|
||||
ln -s /etc/init.d/networking /mnt/etc/runlevels/boot
|
||||
|
19
basic.sh
19
basic.sh
@ -16,31 +16,27 @@ cp ${SOURCE_DIR}/root/.config/htop/htoprc /root/.config/htop/htoprc
|
||||
cp ${SOURCE_DIR}/boot/extlinux.conf /boot/extlinux.conf
|
||||
cp ${SOURCE_DIR}/boot/spotter.txt /boot/spotter.txt
|
||||
cp ${SOURCE_DIR}/etc/inittab /etc/inittab
|
||||
>/etc/motd
|
||||
|
||||
# Enable support for Czech characters
|
||||
cp ${SOURCE_DIR}/etc/rc.conf /etc/rc.conf
|
||||
cp ${SOURCE_DIR}/etc/conf.d/consolefont /etc/conf.d/consolefont
|
||||
|
||||
# Set legal banner with URL
|
||||
cp ${SOURCE_DIR}/etc/issue.template /etc/issue.template
|
||||
cp ${SOURCE_DIR}/sbin/issue-gen /sbin/issue-gen
|
||||
>/etc/motd
|
||||
|
||||
# Configure NTP client
|
||||
cp ${SOURCE_DIR}/etc/conf.d/ntpd /etc/conf.d/ntpd
|
||||
|
||||
# Create a self-signed certificate
|
||||
mkdir /etc/ssl/private
|
||||
openssl req -x509 -new -out /etc/ssl/certs/services.pem -keyout /etc/ssl/private/services.key -nodes -days 3654 -subj "/C=CZ/CN=$(hostname -f)"
|
||||
openssl req -x509 -new -out /etc/ssl/certs/services.pem -keyout /etc/ssl/private/services.key -nodes -days 3654 -subj "/CN=$(hostname)"
|
||||
chmod 640 /etc/ssl/private/services.key
|
||||
|
||||
# Configure nginx
|
||||
mkdir /etc/nginx/apps
|
||||
cp ${SOURCE_DIR}/etc/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
cp ${SOURCE_DIR}/etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Copy Portal resources
|
||||
cp ${SOURCE_DIR}/usr/local/bin/portal-app-manager /usr/local/bin/portal-app-manager
|
||||
# Copy Spotter resources
|
||||
mkdir /etc/spotter
|
||||
cp ${SOURCE_DIR}/etc/spotter/apps.json /etc/spotter/apps.json
|
||||
cp ${SOURCE_DIR}/usr/local/bin/spotter-appmgr /usr/local/bin/spotter-appmgr
|
||||
cp -r ${SOURCE_DIR}/srv/portal /srv/portal
|
||||
|
||||
# Configure services
|
||||
@ -53,3 +49,6 @@ done
|
||||
cp ${SOURCE_DIR}/etc/init.d/docker /etc/init.d/docker
|
||||
rc-update add docker
|
||||
service docker start
|
||||
|
||||
# Set dummy domain and generate related files
|
||||
spotter-appmgr update-domain spotter.vm 443
|
||||
|
@ -5,7 +5,6 @@
|
||||
::wait:/sbin/openrc default >/dev/null 2>&1
|
||||
|
||||
# Set up getty
|
||||
::wait:/sbin/issue-gen
|
||||
tty1::respawn:/sbin/getty -l /sbin/nologin 38400 tty1
|
||||
|
||||
# Stuff to do for the 3-finger salute
|
||||
|
@ -1,36 +0,0 @@
|
||||
|
||||
[1;32m _____ _ _ __ ____ __
|
||||
/ ____| | | | | \\ \\ / / \\/ |
|
||||
| (___ _ __ ___ | |_| |_ ___ _ _\\ \\ / /| \\ / |
|
||||
\\___ \\| '_ \\ / _ \\| __| __/ _ \\ '__\\ \\/ / | |\\/| |
|
||||
____) | |_) | (_) | |_| || __/ | \\ / | | | |
|
||||
|_____/| .__/ \\___/ \\__|\\__\\___|_| \\/ |_| |_|
|
||||
| |
|
||||
|_|[0m
|
||||
|
||||
|
||||
|
||||
|
||||
[1;33mUPOZORNĚNÍ:[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.
|
||||
|
||||
|
||||
[1;33mCAUTION:[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 [1mhttps://${URL}/[0m ve Vašem
|
||||
internetovém prohlížeči.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[0;30m
|
@ -1,17 +0,0 @@
|
||||
server {
|
||||
listen [::]:80 default_server ipv6only=off;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen [::]:443 ssl http2 default_server ipv6only=off;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000;";
|
||||
|
||||
root /srv/portal;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
1
basic/etc/spotter/apps.json
Normal file
1
basic/etc/spotter/apps.json
Normal file
@ -0,0 +1 @@
|
||||
{"_": {"domain": "spotter.vm", "port": "443"}, "cluster-spotter": {}}
|
@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
export URL=$(ip route get 1 | awk '{print $NF;exit}')
|
||||
envsubst </etc/issue.template >/etc/issue
|
@ -1 +0,0 @@
|
||||
{"cluster-spotter": {}}
|
@ -1,11 +1,12 @@
|
||||
$(function() {
|
||||
$.getJSON('js/apps.json', function(data) {
|
||||
$.getJSON('apps.json', function(data) {
|
||||
var host = data._.domain + (data._.port != '443' ? ':'+data._.port : '')
|
||||
$.each(data, function(id, props) {
|
||||
var div = $('#'+id).show();
|
||||
if (props.hasOwnProperty('url'))
|
||||
div.find('h2 a').attr('href', props.url.replace('{host}', window.location.hostname));
|
||||
div.find('h2 a').attr('href', props.url.replace('{host}', host));
|
||||
$.each(props, function(key, value) {
|
||||
div.find('.'+key).text(value.replace('{host}', window.location.hostname));
|
||||
div.find('.'+key).text(value.replace('{host}', host));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,33 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
def main(args):
|
||||
jsonfile = '/srv/portal/js/apps.json'
|
||||
|
||||
data = {}
|
||||
with open(jsonfile, 'r') as f:
|
||||
data = json.load(f)
|
||||
if not args.app in data:
|
||||
data[args.app] = {}
|
||||
if args.url:
|
||||
data[args.app]['url'] = args.url
|
||||
if args.login:
|
||||
data[args.app]['login'] = args.login
|
||||
if args.password:
|
||||
data[args.app]['password'] = args.password
|
||||
if args.property:
|
||||
for key, value in args.property:
|
||||
data[args.app][key] = value
|
||||
with open(jsonfile, 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Spotter Cluster portal application manager')
|
||||
parser.add_argument('app', help='Application ID.')
|
||||
parser.add_argument('url', nargs='?', help='URL to the application. Use "{host}" as a host placeholder.')
|
||||
parser.add_argument('login', nargs='?', help='Administrative login.')
|
||||
parser.add_argument('password', nargs='?', help='Administrative password.')
|
||||
parser.add_argument('-p', '--property', nargs=2, action='append', help='Add arbitrary key-value to the application properties')
|
||||
main(parser.parse_args())
|
206
basic/usr/local/bin/spotter-appmgr
Executable file
206
basic/usr/local/bin/spotter-appmgr
Executable file
@ -0,0 +1,206 @@
|
||||
#!/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)
|
Loading…
Reference in New Issue
Block a user