From 6b306390b3662ab3824fc9c14dbeae06d5421e5e Mon Sep 17 00:00:00 2001 From: Disassembler Date: Thu, 20 Dec 2018 14:56:37 +0100 Subject: [PATCH] Split LXC and package logic to separate modules --- usr/bin/vmmgr | 17 +-- usr/lib/python3.6/vmmgr/__init__.py | 4 + usr/lib/python3.6/vmmgr/appmgr.py | 176 +-------------------------- usr/lib/python3.6/vmmgr/lxcmgr.py | 98 +++++++++++++++ usr/lib/python3.6/vmmgr/pkgmgr.py | 180 ++++++++++++++++++++++++++++ usr/lib/python3.6/vmmgr/vmmgr.py | 97 +-------------- 6 files changed, 301 insertions(+), 271 deletions(-) create mode 100644 usr/lib/python3.6/vmmgr/lxcmgr.py create mode 100644 usr/lib/python3.6/vmmgr/pkgmgr.py diff --git a/usr/bin/vmmgr b/usr/bin/vmmgr index b1bb693..5338964 100755 --- a/usr/bin/vmmgr +++ b/usr/bin/vmmgr @@ -38,25 +38,26 @@ parser_unregister_proxy.add_argument('app', help='Application name') args = parser.parse_args() conf = Config() -mgr = VMMgr(conf) +vmmgr = VMMgr(conf) +lxcmgr = LXCMgr(conf) if args.action == 'register-app': # Used by app install scripts - mgr.register_app(args.app, args.login, args.password) + vmmgr.register_app(args.app, args.login, args.password) elif args.action == 'rebuild-issue': # Used on VM startup - mgr.rebuild_issue() + vmmgr.rebuild_issue() elif args.action == 'prepare-container': # Used with LXC hooks - mgr.prepare_container() + lxcmgr.prepare_container() elif args.action == 'register-container': # Used with LXC hooks - mgr.register_container() + lxcmgr.register_container() elif args.action == 'unregister-container': # Used with LXC hooks - mgr.unregister_container() + lxcmgr.unregister_container() elif args.action == 'register-proxy': # Used in init scripts - mgr.register_proxy(args.app) + lxcmgr.register_proxy(args.app) elif args.action == 'unregister-proxy': # Used in init scripts - mgr.unregister_proxy(args.app) + lxcmgr.unregister_proxy(args.app) diff --git a/usr/lib/python3.6/vmmgr/__init__.py b/usr/lib/python3.6/vmmgr/__init__.py index b688139..38c73f9 100644 --- a/usr/lib/python3.6/vmmgr/__init__.py +++ b/usr/lib/python3.6/vmmgr/__init__.py @@ -2,12 +2,16 @@ from .appmgr import AppMgr from .config import Config +from .lxcmgr import LXCMgr +from .pkgmgr import PkgMgr from .vmmgr import VMMgr from .wsgiapp import WSGIApp __all__ = [ 'AppMgr', 'Config', + 'LXCMgr', + 'PkgMgr', 'VMMgr', 'WSGIApp' ] diff --git a/usr/lib/python3.6/vmmgr/appmgr.py b/usr/lib/python3.6/vmmgr/appmgr.py index a679fb3..e3efffa 100644 --- a/usr/lib/python3.6/vmmgr/appmgr.py +++ b/usr/lib/python3.6/vmmgr/appmgr.py @@ -1,50 +1,16 @@ # -*- coding: utf-8 -*- -import hashlib -import json import os -import requests -import shutil import subprocess -from cryptography.exceptions import InvalidSignature - -from . import crypto +from .pkgmgr import InstallItem, PkgMgr LXC_ROOT = '/var/lib/lxc' -class InstallItem: - def __init__(self, total): - # Stage 0 = download, 1 = deps install, 2 = app install - self.stage = 0 - self.total = total - self.downloaded = 0 - - def __str__(self): - # Limit the displayed percentage to 0 - 99 - return str(min(99, round(self.downloaded / self.total * 100))) - class AppMgr: def __init__(self, conf): self.conf = conf - self.online_packages = {} - - def get_repo_resource(self, url, stream=False): - return requests.get('{}/{}'.format(self.conf['repo']['url'], url), auth=(self.conf['repo']['user'], self.conf['repo']['pwd']), timeout=5, stream=stream) - - def fetch_online_packages(self): - # Fetches and verifies online packages. Can raise InvalidSignature - online_packages = {} - packages = self.get_repo_resource('packages') - if packages.status_code != 200: - return packages.status_code - packages = packages.content - packages_sig = self.get_repo_resource('packages.sig').content - crypto.verify_signature(packages, packages_sig) - online_packages = json.loads(packages) - # Minimze the time when self.online_packages is out of sync - self.online_packages = online_packages - return 200 + self.pkgmgr = PkgMgr(conf) def start_app(self, item): # Start the actual app service @@ -90,24 +56,8 @@ class AppMgr: def install_app(self, item): # Main installation function. Wrapper for download, registration and install script - app = item.key - # Clean packages which previously failed to install - self.clean_pending_packages() - # Get all packages on which the app depends and which have not been installed yet - deps = [d for d in self.get_install_deps(app) if d not in self.conf['packages'] or 'pending' in self.conf['packages'][d]] - item.data = InstallItem(sum(self.online_packages[d]['size'] for d in deps)) - for dep in deps: - self.download_package(dep, item.data) - for dep in deps: - item.data.stage = 2 if dep == deps[-1] else 1 - # Purge old data before unpacking to clean previous failed installation - self.purge_package(dep) - self.unpack_package(dep) - # Run uninstall script before installation to clean previous failed installation - self.run_uninstall_script(dep) - self.register_package(dep) - self.run_install_script(dep) - self.finalize_installation(dep) + item.data = InstallItem() + self.pkgmgr.install_app(item.key, item.data) def uninstall_app(self, item): # Main uninstallation function. Wrapper for uninstall script, filesystem purge and unregistration @@ -115,125 +65,9 @@ class AppMgr: self.stop_app(item) if self.is_service_autostarted(app): self.update_app_autostart(app, False) - deps = self.get_install_deps(app, False)[::-1] - for dep in deps: - if dep not in self.get_uninstall_deps(): - self.run_uninstall_script(dep) - self.purge_package(dep) - self.unregister_package(dep) - - def download_package(self, name, installitem): - tmp_archive = '/tmp/{}.tar.xz'.format(name) - r = self.get_repo_resource('{}.tar.xz'.format(name), True) - with open(tmp_archive, 'wb') as f: - for chunk in r.iter_content(chunk_size=65536): - if chunk: - installitem.downloaded += f.write(chunk) - # Verify hash - if self.online_packages[name]['sha512'] != self.hash_file(tmp_archive): - raise InvalidSignature(name) - - def hash_file(self, file_path): - sha512 = hashlib.sha512() - with open(file_path, 'rb') as f: - while True: - data = f.read(65536) - if not data: - break - sha512.update(data) - return sha512.hexdigest() - - def unpack_package(self, name): - # Unpack archive - tmp_archive = '/tmp/{}.tar.xz'.format(name) - subprocess.run(['tar', 'xJf', tmp_archive], cwd='/', check=True) - os.unlink(tmp_archive) - - def purge_package(self, name): - # Removes package and shared data from filesystem - lxcpath = self.conf['packages'][name]['lxcpath'] if name in self.conf['packages'] else self.online_packages[name]['lxcpath'] - lxc_dir = os.path.join(LXC_ROOT, lxcpath) - if os.path.exists(lxc_dir): - shutil.rmtree(lxc_dir) - srv_dir = os.path.join('/srv/', name) - if os.path.exists(srv_dir): - shutil.rmtree(srv_dir) - lxc_log = '/var/log/lxc/{}.log'.format(name) - if os.path.exists(lxc_log): - os.unlink(lxc_log) - - def register_package(self, name): - # Registers a package in local configuration - metadata = self.online_packages[name].copy() - del metadata['sha512'] - del metadata['size'] - metadata['pending'] = True - self.conf['packages'][name] = metadata - self.conf.save() - - def unregister_package(self, name): - # Removes a package from local configuration if name in self.conf['apps']: del self.conf['apps'][name] - del self.conf['packages'][name] - self.conf.save() - - def finalize_installation(self, name): - # If the install script called vmmgr register-app, perform the app registration - # This can't be done directly from install script due to possible race conditions - cred_file = '/tmp/{}.credentials'.format(name) - if os.path.exists(cred_file): - with open(cred_file, 'r') as f: - cred = f.read().splitlines() - os.unlink(cred_file) - self.conf['apps'][name] = { - 'login': cred[0], - 'password': cred[1], - 'visible': False - } - # Finally, mark the package as fully installed - del self.conf['packages'][name]['pending'] - self.conf.save() - - def clean_pending_packages(self): - # Remove registeres packages with pending flag set from previously failed installation - for name in self.conf['packages'].copy(): - if 'pending' in self.conf['packages'][name]: - self.unregister_package(name) - self.conf.save() - - def run_install_script(self, name): - # Runs install.sh for a package, if the script is present - install_dir = os.path.join('/srv/', name, 'install') - install_script = os.path.join('/srv/', name, 'install.sh') - if os.path.exists(install_script): - subprocess.run(install_script, check=True) - os.unlink(install_script) - if os.path.exists(install_dir): - shutil.rmtree(install_dir) - - def run_uninstall_script(self, name): - # Runs uninstall.sh for a package, if the script is present - uninstall_script = os.path.join('/srv/', name, 'uninstall.sh') - if os.path.exists(uninstall_script): - subprocess.run(uninstall_script, check=True) - - def get_install_deps(self, name, online=True): - # Flatten dependency tree for a package while preserving the dependency order - packages = self.online_packages if online else self.conf['packages'] - deps = packages[name]['deps'].copy() - for dep in deps[::-1]: - deps[:0] = [d for d in self.get_install_deps(dep, online)] - deps = list(dict.fromkeys(deps + [name])) - return deps - - def get_uninstall_deps(self): - # Create reverse dependency tree for all installed packages - deps = {} - for name in self.conf['packages'].copy(): - for d in self.conf['packages'][name]['deps']: - deps.setdefault(d, []).append(name) - return deps + self.pkgmgr.uninstall_app(app) def get_services_deps(self): # Fisrt, build a dictionary of {app: [needs]} diff --git a/usr/lib/python3.6/vmmgr/lxcmgr.py b/usr/lib/python3.6/vmmgr/lxcmgr.py new file mode 100644 index 0000000..1689c1f --- /dev/null +++ b/usr/lib/python3.6/vmmgr/lxcmgr.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +import fcntl +import os +import shutil +import subprocess + +from . import templates + +NGINX_DIR = '/etc/nginx/conf.d' + +class LXCMgr: + def __init__(self, conf): + # Load JSON configuration + self.conf = conf + + def prepare_container(self): + # Extract the variables from values given via lxc.hook.pre-start hook + app = os.environ['LXC_NAME'] + # Remove ephemeral layer data + self.clean_ephemeral_layer(app) + # Configure host and common params used in the app + self.configure_app(app) + + def clean_ephemeral_layer(self, app): + # Cleans containers ephemeral layer. + # This is done early in the container start process, so the inode of the delta0 directory must remain unchanged + layer = os.path.join('/var/lib/lxc', app, 'delta0') + if os.path.exists(layer): + for item in os.scandir(layer): + shutil.rmtree(item.path) if item.is_dir() else os.unlink(item.path) + + def register_container(self): + # Extract the variables from values given via lxc.hook.start-host hook + app = os.environ['LXC_NAME'] + pid = os.environ['LXC_PID'] + # Lease the first unused IP to the container + ip = self.update_hosts_lease(app, True) + # Set IP in container based on PID given via lxc.hook.start-host hook + cmd = 'ip addr add {}/16 broadcast 172.17.255.255 dev eth0 && ip route add default via 172.17.0.1'.format(ip) + subprocess.run(['nsenter', '-a', '-t', pid, '--', '/bin/sh', '-c', cmd]) + + def unregister_container(self): + # Extract the variables from values given via lxc.hook.post-stop hook + app = os.environ['LXC_NAME'] + # Release the container IP + self.update_hosts_lease(app, False) + # Remove ephemeral layer data + self.clean_ephemeral_layer(app) + + def update_hosts_lease(self, app, is_request): + # This is a poor man's DHCP server which uses /etc/hosts as lease database + # Leases the first unused IP from range 172.17.0.0/16 + # Uses file lock as interprocess mutex + ip = None + with open('/var/lock/vmmgr-hosts.lock', 'w') as lock: + fcntl.lockf(lock, fcntl.LOCK_EX) + # Load all existing records + with open('/etc/hosts', 'r') as f: + leases = [l.strip().split(' ', 1) for l in f] + # If this call is a request for lease, find the first unassigned IP + if is_request: + used_ips = [l[0] for l in leases] + for i in range(2, 65534): + ip = '172.17.{}.{}'. format(i // 256, i % 256) + if ip not in used_ips: + leases.append([ip, app]) + break + # Otherwise it is a release in which case we just delete the record + else: + leases = [l for l in leases if l[1] != app] + # Write the contents back to the file + with open('/etc/hosts', 'w') as f: + for lease in leases: + f.write('{} {}\n'.format(lease[0], lease[1])) + return ip + + def configure_app(self, app): + # Supply common configuration for the application. Done as part of container preparation during service startup + script = os.path.join('/srv', app, 'update-conf.sh') + if os.path.exists(script): + setup_env = os.environ.copy() + setup_env['DOMAIN'] = self.conf['host']['domain'] + setup_env['PORT'] = self.conf['host']['port'] + setup_env['EMAIL'] = self.conf['common']['email'] + setup_env['GMAPS_API_KEY'] = self.conf['common']['gmaps-api-key'] + subprocess.run([script], env=setup_env, check=True) + + def register_proxy(self, app): + # Setup proxy configuration and reload nginx + with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f: + f.write(templates.NGINX.format(app=app, host=self.conf['packages'][app]['host'], domain=self.conf['host']['domain'], port=self.conf['host']['port'])) + self.reload_nginx() + + def unregister_proxy(self, app): + # Remove proxy configuration and reload nginx + os.unlink(os.path.join(NGINX_DIR, '{}.conf'.format(app))) + self.reload_nginx() diff --git a/usr/lib/python3.6/vmmgr/pkgmgr.py b/usr/lib/python3.6/vmmgr/pkgmgr.py new file mode 100644 index 0000000..b3d9cf6 --- /dev/null +++ b/usr/lib/python3.6/vmmgr/pkgmgr.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- + +import hashlib +import json +import os +import requests +import shutil +import subprocess + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec + +from . import crypto + +LXC_ROOT = '/var/lib/lxc' + +STAGE_DOWNLOAD = 0 +STAGE_INSTALL_DEPS = 1 +STAGE_INSTALL_APP = 2 + +class InstallItem: + def __init__(self): + self.stage = STAGE_DOWNLOAD + self.bytes_total = 1 + self.bytes_downloaded = 0 + + @property + def percent_downloaded(self): + # Limit the displayed percentage to 0 - 99 + return min(99, round(self.bytes_downloaded / self.bytes_total * 100)) + +class PkgMgr: + def __init__(self, conf): + self.repo_url = repo_url + self.conf = conf + self.online_packages = {} + + def get_repo_resource(self, resource_url, stream=False): + return requests.get('{}/{}'.format(self.repo_url, resource_url), auth=self.repo_auth, timeout=5, stream=stream) + + def fetch_online_packages(self): + # Fetches and verifies online packages. Can raise InvalidSignature + packages = self.get_repo_resource('packages') + if packages.status_code != 200: + return packages.status_code + packages = packages.content + packages_sig = self.get_repo_resource('packages.sig').content + crypto.verify_signature(packages, packages_sig) + self.online_packages = json.loads(packages) + return 200 + + def install_app(self, app, item): + # Main installation function. Wrapper for download, registration and install script + self.fetch_online_packages() + # Clean packages which previously failed to install + self.clean_pending_packages() + # Get all packages on which the app depends and which have not been installed yet + deps = [d for d in self.get_install_deps(app) if d not in self.conf['packages'] or 'pending' in self.conf['packages'][d]] + item.bytes_total = sum(self.online_packages[d]['size'] for d in deps) + for dep in deps: + self.download_package(dep, item) + for dep in deps: + # Set stage to INSTALLING_DEPS or INSTALLING based on which backage in sequence is being installed + item.stage = STAGE_INSTALL_APP if dep == deps[-1] else STAGE_INSTALL_DEPS + # Purge old data before unpacking to clean previous failed installation + self.purge_package(dep) + self.unpack_package(dep) + # Run uninstall script before installation to clean previous failed installation + self.run_uninstall_script(dep) + self.register_package(dep) + self.run_install_script(dep) + + def uninstall_app(self, app): + # Main uninstallation function. Wrapper for uninstall script, filesystem purge and unregistration + deps = self.get_install_deps(app, False)[::-1] + for dep in deps: + if dep not in self.get_uninstall_deps(): + self.run_uninstall_script(dep) + self.purge_package(dep) + self.unregister_package(dep) + + def download_package(self, name, item): + tmp_archive = '/tmp/{}.tar.xz'.format(name) + r = self.get_repo_resource('{}.tar.xz'.format(name), True) + with open(tmp_archive, 'wb') as f: + for chunk in r.iter_content(chunk_size=65536): + if chunk: + item.bytes_downloaded += f.write(chunk) + # Verify hash + if self.online_packages[name]['sha512'] != self.hash_file(tmp_archive): + raise InvalidSignature(name) + + def hash_file(self, file_path): + sha512 = hashlib.sha512() + with open(file_path, 'rb') as f: + while True: + data = f.read(65536) + if not data: + break + sha512.update(data) + return sha512.hexdigest() + + def unpack_package(self, name): + # Unpack archive + tmp_archive = '/tmp/{}.tar.xz'.format(name) + subprocess.run(['tar', 'xJf', tmp_archive], cwd='/', check=True) + os.unlink(tmp_archive) + + def purge_package(self, name): + # Removes package and shared data from filesystem + lxcpath = self.conf['packages'][name]['lxcpath'] if name in self.conf['packages'] else self.online_packages[name]['lxcpath'] + lxc_dir = os.path.join(LXC_ROOT, lxcpath) + if os.path.exists(lxc_dir): + shutil.rmtree(lxc_dir) + srv_dir = os.path.join('/srv/', name) + if os.path.exists(srv_dir): + shutil.rmtree(srv_dir) + lxc_log = '/var/log/lxc/{}.log'.format(name) + if os.path.exists(lxc_log): + os.unlink(lxc_log) + + def register_package(self, name): + # Registers a package in installed packages + metadata = self.online_packages[name].copy() + del metadata['sha512'] + del metadata['size'] + metadata['pending'] = True + self.conf['packages'][name] = metadata + self.conf.save() + + def unregister_package(self, name): + # Removes a package from installed packages + del self.conf['packages'][name] + self.conf.save() + + def clean_pending_packages(self): + # Remove registered packages with pending flag set from previously failed installation + for name in self.conf['packages'].copy(): + if 'pending' in self.conf['packages'][name]: + self.unregister_package(name) + self.conf.save() + + def run_install_script(self, name): + # Runs install.sh for a package, if the script is present + install_dir = os.path.join('/srv/', name, 'install') + install_script = os.path.join('/srv/', name, 'install.sh') + if os.path.exists(install_script): + subprocess.run(install_script, check=True) + os.unlink(install_script) + if os.path.exists(install_dir): + shutil.rmtree(install_dir) + # Reload config to reflect whatever vmmgr register-app from the install script has written in it + self.conf.load() + del self.conf['packages'][name]['pending'] + self.conf.save() + + def run_uninstall_script(self, name): + # Runs uninstall.sh for a package, if the script is present + uninstall_script = os.path.join('/srv/', name, 'uninstall.sh') + if os.path.exists(uninstall_script): + subprocess.run(uninstall_script, check=True) + + def get_install_deps(self, name, online=True): + # Flatten dependency tree for a package while preserving the dependency order + packages = self.online_packages if online else self.conf['packages'] + deps = packages[name]['deps'].copy() + for dep in deps[::-1]: + deps[:0] = [d for d in self.get_install_deps(dep, online)] + deps = list(dict.fromkeys(deps + [name])) + return deps + + def get_uninstall_deps(self): + # Create reverse dependency tree for all installed packages + deps = {} + for name in self.conf['packages'].copy(): + for d in self.conf['packages'][name]['deps']: + deps.setdefault(d, []).append(name) + return deps diff --git a/usr/lib/python3.6/vmmgr/vmmgr.py b/usr/lib/python3.6/vmmgr/vmmgr.py index 55e87d2..c2e573a 100644 --- a/usr/lib/python3.6/vmmgr/vmmgr.py +++ b/usr/lib/python3.6/vmmgr/vmmgr.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -import fcntl import os import shutil import subprocess @@ -9,8 +8,6 @@ from . import crypto from . import templates from . import net -VERSION = '0.0.1' - ISSUE_FILE = '/etc/issue' NGINX_DIR = '/etc/nginx/conf.d' ACME_CRON = '/etc/periodic/daily/acme-sh' @@ -23,12 +20,11 @@ class VMMgr: self.port = conf['host']['port'] def register_app(self, app, login, password): - # Write a file with credentials of a newly installed application which - # will be picked up by thread performing the installation after the install script finishes - login = login if login else 'N/A' - password = password if password else 'N/A' - with open('/tmp/{}.credentials'.format(app), 'w') as f: - f.write('{}\n{}'.format(login, password)) + # Register newly installed application and its credentials + self.conf['apps'][app] = {'login': login if login else 'N/A', + 'password': password if password else 'N/A', + 'visible': False} + self.conf.save() def update_host(self, domain, port): # Update domain and port and rebuild all configuration. Web interface calls restart_nginx() in WSGI close handler @@ -107,86 +103,3 @@ class VMMgr: os.chmod(crypto.CERT_KEY_FILE, 0o640) # Reload nginx self.reload_nginx() - - def prepare_container(self): - # Extract the variables from values given via lxc.hook.pre-start hook - app = os.environ['LXC_NAME'] - # Remove ephemeral layer data - self.clean_ephemeral_layer(app) - # Configure host and common params used in the app - self.configure_app(app) - - def clean_ephemeral_layer(self, app): - # Cleans containers ephemeral layer. - # This is done early in the container start process, so the inode of the delta0 directory must remain unchanged - layer = os.path.join('/var/lib/lxc', app, 'delta0') - if os.path.exists(layer): - for item in os.scandir(layer): - shutil.rmtree(item.path) if item.is_dir() else os.unlink(item.path) - - def register_container(self): - # Extract the variables from values given via lxc.hook.start-host hook - app = os.environ['LXC_NAME'] - pid = os.environ['LXC_PID'] - # Lease the first unused IP to the container - ip = self.update_hosts_lease(app, True) - # Set IP in container based on PID given via lxc.hook.start-host hook - cmd = 'ip addr add {}/16 broadcast 172.17.255.255 dev eth0 && ip route add default via 172.17.0.1'.format(ip) - subprocess.run(['nsenter', '-a', '-t', pid, '--', '/bin/sh', '-c', cmd]) - - def unregister_container(self): - # Extract the variables from values given via lxc.hook.post-stop hook - app = os.environ['LXC_NAME'] - # Release the container IP - self.update_hosts_lease(app, False) - # Remove ephemeral layer data - self.clean_ephemeral_layer(app) - - def update_hosts_lease(self, app, is_request): - # This is a poor man's DHCP server which uses /etc/hosts as lease database - # Leases the first unused IP from range 172.17.0.0/16 - # Uses file lock as interprocess mutex - ip = None - with open('/var/lock/vmmgr-hosts.lock', 'w') as lock: - fcntl.lockf(lock, fcntl.LOCK_EX) - # Load all existing records - with open('/etc/hosts', 'r') as f: - leases = [l.strip().split(' ', 1) for l in f] - # If this call is a request for lease, find the first unassigned IP - if is_request: - used_ips = [l[0] for l in leases] - for i in range(2, 65534): - ip = '172.17.{}.{}'. format(i // 256, i % 256) - if ip not in used_ips: - leases.append([ip, app]) - break - # Otherwise it is a release in which case we just delete the record - else: - leases = [l for l in leases if l[1] != app] - # Write the contents back to the file - with open('/etc/hosts', 'w') as f: - for lease in leases: - f.write('{} {}\n'.format(lease[0], lease[1])) - return ip - - def configure_app(self, app): - # Supply common configuration for the application. Done as part of container preparation during service startup - script = os.path.join('/srv', app, 'update-conf.sh') - if os.path.exists(script): - setup_env = os.environ.copy() - setup_env['DOMAIN'] = self.domain - setup_env['PORT'] = self.port - setup_env['EMAIL'] = self.conf['common']['email'] - setup_env['GMAPS_API_KEY'] = self.conf['common']['gmaps-api-key'] - subprocess.run([script], env=setup_env, check=True) - - def register_proxy(self, app): - # Setup proxy configuration and reload nginx - with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f: - f.write(templates.NGINX.format(app=app, host=self.conf['packages'][app]['host'], domain=self.domain, port=self.port)) - self.reload_nginx() - - def unregister_proxy(self, app): - # Remove proxy configuration and reload nginx - os.unlink(os.path.join(NGINX_DIR, '{}.conf'.format(app))) - self.reload_nginx()