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
|
chroot /mnt setup-timezone -z Europe/Prague
|
||||||
|
|
||||||
# Set hostname
|
# Set hostname
|
||||||
echo 'vm' >/mnt/etc/hostname
|
echo 'spottervm' >/mnt/etc/hostname
|
||||||
sed -i 's/localhost/vm/' /mnt/etc/network/interfaces
|
echo '127.0.0.1 spottervm localhost localhost.localdomain' >/mnt/etc/hosts
|
||||||
sed -i 's/localhost /vm localhost /' /mnt/etc/hosts
|
sed -i '/hostname/d' /mnt/etc/network/interfaces
|
||||||
|
|
||||||
# Enable services on boot
|
# Enable services on boot
|
||||||
ln -s /etc/init.d/networking /mnt/etc/runlevels/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/extlinux.conf /boot/extlinux.conf
|
||||||
cp ${SOURCE_DIR}/boot/spotter.txt /boot/spotter.txt
|
cp ${SOURCE_DIR}/boot/spotter.txt /boot/spotter.txt
|
||||||
cp ${SOURCE_DIR}/etc/inittab /etc/inittab
|
cp ${SOURCE_DIR}/etc/inittab /etc/inittab
|
||||||
|
>/etc/motd
|
||||||
|
|
||||||
# Enable support for Czech characters
|
# Enable support for Czech characters
|
||||||
cp ${SOURCE_DIR}/etc/rc.conf /etc/rc.conf
|
cp ${SOURCE_DIR}/etc/rc.conf /etc/rc.conf
|
||||||
cp ${SOURCE_DIR}/etc/conf.d/consolefont /etc/conf.d/consolefont
|
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
|
# Configure NTP client
|
||||||
cp ${SOURCE_DIR}/etc/conf.d/ntpd /etc/conf.d/ntpd
|
cp ${SOURCE_DIR}/etc/conf.d/ntpd /etc/conf.d/ntpd
|
||||||
|
|
||||||
# Create a self-signed certificate
|
# Create a self-signed certificate
|
||||||
mkdir /etc/ssl/private
|
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
|
chmod 640 /etc/ssl/private/services.key
|
||||||
|
|
||||||
# Configure nginx
|
# Configure nginx
|
||||||
mkdir /etc/nginx/apps
|
|
||||||
cp ${SOURCE_DIR}/etc/nginx/nginx.conf /etc/nginx/nginx.conf
|
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
|
# Copy Spotter resources
|
||||||
cp ${SOURCE_DIR}/usr/local/bin/portal-app-manager /usr/local/bin/portal-app-manager
|
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
|
cp -r ${SOURCE_DIR}/srv/portal /srv/portal
|
||||||
|
|
||||||
# Configure services
|
# Configure services
|
||||||
@ -53,3 +49,6 @@ done
|
|||||||
cp ${SOURCE_DIR}/etc/init.d/docker /etc/init.d/docker
|
cp ${SOURCE_DIR}/etc/init.d/docker /etc/init.d/docker
|
||||||
rc-update add docker
|
rc-update add docker
|
||||||
service docker start
|
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
|
::wait:/sbin/openrc default >/dev/null 2>&1
|
||||||
|
|
||||||
# Set up getty
|
# Set up getty
|
||||||
::wait:/sbin/issue-gen
|
|
||||||
tty1::respawn:/sbin/getty -l /sbin/nologin 38400 tty1
|
tty1::respawn:/sbin/getty -l /sbin/nologin 38400 tty1
|
||||||
|
|
||||||
# Stuff to do for the 3-finger salute
|
# 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() {
|
$(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) {
|
$.each(data, function(id, props) {
|
||||||
var div = $('#'+id).show();
|
var div = $('#'+id).show();
|
||||||
if (props.hasOwnProperty('url'))
|
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) {
|
$.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