diff --git a/building_manager.py b/building_manager.py index f8ae6ee46f9bf96d0188139441d6bb7e02013947..32eb9c18275b8fd6d9f20a1dd7f47a4f852cc3e7 100755 --- a/building_manager.py +++ b/building_manager.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """ Python module to assist creating and maintaining docker openHab stacks.""" import crypt +from enum import Enum import logging import os from hashlib import md5 @@ -42,10 +43,11 @@ COMPOSE_NAME = 'docker-stack.yml' SKELETON_NAME = 'docker-skeleton.yml' TEMPLATES_NAME = 'docker-templates.yml' CONFIG_DIRS = ['mosquitto', 'nodered', 'ssh', - 'traefik', 'volumerize', 'postgres'] + 'traefik', 'volumerize', 'postgres', 'pb-framr'] TEMPLATE_FILES = [ 'mosquitto/mosquitto.conf', 'nodered/nodered_package.json', - 'nodered/nodered_settings.js', 'ssh/sshd_config', 'traefik/traefik.toml' + 'pb-framr/logo.svg', 'nodered/nodered_settings.js', + 'ssh/sshd_config', 'traefik/traefik.toml' ] EDIT_FILES = { "mosquitto_passwords": "mosquitto/mosquitto_passwords", @@ -56,7 +58,8 @@ EDIT_FILES = { "known_hosts": "ssh/known_hosts", "backup_config": "volumerize/backup_config.json", "postgres_user": "postgres/user", - "postgres_passwd": "postgres/passwd" + "postgres_passwd": "postgres/passwd", + "pb_framr_pages": "pb-framr/pages.json" } CONSTRAINTS = {"building": "node.labels.building"} SERVICES = { @@ -66,6 +69,10 @@ SERVICES = { "postgres": "postgres_X", "mqtt": "mqtt_X" } +FRONTEND_SERVICES = { + "openhab": "OpenHAB", + "nodered": "Node-RED" +} # Default Swarm port SWARM_PORT = 2377 @@ -73,6 +80,22 @@ SWARM_PORT = 2377 UID = 9001 # Username for admin ADMIN_USER = 'ohadmin' + + +class Service(Enum): + SFTP = ("SFTP", "sftp_X", False) + OPENHAB = ("OpenHAB", "openhab_X", True, '/', 'dashboard') + NODERED = ("Node-RED", "nodered_X", True, 'nodered', 'ballot') + POSTGRES = ("Postgre SQL", "postgres_X", False) + MQTT = ("Mosquitto MQTT Broker", "mqtt_X", False) + + def __init__(self, fullname, compose_entry, frontend, + prefix=None, icon=None): + self.fullname = fullname + self.compose_entry = compose_entry + self.frontend = frontend + self.prefix = prefix + self.icon = icon # >>> @@ -399,6 +422,25 @@ def generate_traefik_user_line(username, password): return line +def generate_pb_framr_entry(host, service): + """Generates a single entry of the framr file + + :host: host this entry is intended for + :service: entry from service enum + :returns: a dict fitting the asked entry + + """ + entry = {} + entry['title'] = service.fullname + if service.prefix == "/": + entry['url'] = f'http://{host}/' + pass + else: + entry['url'] = f'/{service.prefix}_{host}' + entry['icon'] = service.icon + return entry + + def generate_mosquitto_file(base_dir, username, password): """Generates a mosquitto password file using mosquitto_passwd system tool @@ -531,6 +573,26 @@ def generate_volumerize_file(base_dir, hosts): base_dir, EDIT_FILES['backup_config'], configs, json=True) +def generate_pb_framr_file(base_dir, frames): + """Generates config for pb framr landing page + + :base_dir: path that contains custom config folder + :frames: a dict that contains hosts with matching name and services + """ + configs = [] + + for f in frames: + building = { + 'instance': f['building'], + 'entries': [generate_pb_framr_entry(f['host'], s) + for s in f['services'] if s.frontend] + } + configs.append(building) + + create_or_replace_config_file( + base_dir, EDIT_FILES['pb_framr_pages'], configs, json=True) + + def create_or_replace_config_file(base_dir, config_path, content, json=False): """Creates or replaces a config file with new content @@ -545,8 +607,6 @@ def create_or_replace_config_file(base_dir, config_path, content, json=False): json.dump(content, file, indent=2) else: file.write(content) - - # >>> @@ -929,8 +989,15 @@ def init_menu(args): generate_id_rsa_files(base_dir) generate_host_key_files(base_dir, hosts) + frames = [] for i, host in enumerate(hosts): - init_machine_menu(base_dir, host, i) + building, services = init_machine_menu(base_dir, host, i) + frames.append({'host': host, + 'building': building, 'services': services}) + + # When frames is not empty generate frame config + if frames: + generate_pb_framr_file(base_dir, frames) # print(answers) print(f"Configuration files for {stack_name} generated in {base_dir}") @@ -949,25 +1016,25 @@ def init_machine_menu(base_dir, host, increment): :base_dir: Directory of config files :host: docker-machine host :increment: incrementing number to ensure ports are unique + :return: choosen building name and services """ # Prompt for services building = qust.text(f'Choose a name for building on server {host}', default=f'{host}', style=st).ask() services = qust.checkbox(f'What services shall {host} provide?', - choices=generate_cb_choices(SERVICES.keys(), - checked=True), + choices=generate_cb_service_choices(checked=True), style=st).ask() - if 'sftp' in services: + if Service.SFTP in services: add_sftp_service(base_dir, host, increment) - if 'openhab' in services: + if Service.OPENHAB in services: add_openhab_service(base_dir, host) - if 'nodered' in services: + if Service.NODERED in services: add_nodered_service(base_dir, host) - if 'mqtt' in services: + if Service.MQTT in services: add_mqtt_service(base_dir, host, increment) - if 'postgres' in services: + if Service.POSTGRES in services: add_postgres_service(base_dir, host) - print(building) + return building, services # *** Exec Menu Entries *** @@ -1008,6 +1075,17 @@ def generate_cb_choices(list, checked=False): return [{'name': m, 'checked': checked} for m in list] +def generate_cb_service_choices(checked=False): + """Generates checkbox entries for the sevice enum + + :checked: if true, selections will be checked by default + :returns: A list of dicts with name keys + """ + return [ + {'name': s.fullname, 'value': s, 'checked': checked} for s in Service + ] + + def docker_client_prompt(message_details=''): """Show list of docker machines and return selection diff --git a/template_configs/docker-skeleton.yml b/template_configs/docker-skeleton.yml index 8e771c0c10ab5918d6fdf1ccef863d6c1b936b2e..55eeec9b1e202c5fae20866c36462ae058dc1e24 100644 --- a/template_configs/docker-skeleton.yml +++ b/template_configs/docker-skeleton.yml @@ -37,6 +37,10 @@ configs: file: ./postgres/user postgres_passwd: file: ./postgres/passwd + landing_logo: + file: ./pb-framr/logo.svg + landing_pages: + file: ./pb-framr/pages.json volumes: openhab_addons: @@ -71,3 +75,25 @@ services: placement: constraints: - node.role == manager + landing: + image: doblix/pb-framr + networks: + - habnet + configs: + - source: landing_logo + target: /usr/share/nginx/html/logo.svg + - source: landing_pages + target: /usr/share/nginx/html/pages.json + deploy: + labels: + - traefik.docker.network=habnet + - traefik.port=80 + - traefik.backend=landing + - traefik.main.frontend.priority=2 + - traefik.main.frontend.redirect.regex=^(.*)/landing$$ + - traefik.main.frontend.redirect.replacement=$$1/landing/ + - traefik.main.frontend.rule=PathPrefix:/landing;ReplacePathRegex:^/landing/(.*) + /$$1 + placement: + constraints: + - node.role == manager diff --git a/template_configs/docker-templates.yml b/template_configs/docker-templates.yml index cea84f8f9162727f60ff9e53c9df6d123a42207b..e27a44d41c000d628a854f8d62a96094531c7945 100644 --- a/template_configs/docker-templates.yml +++ b/template_configs/docker-templates.yml @@ -37,6 +37,10 @@ configs: file: ./postgres/user postgres_passwd: file: ./postgres/passwd + landing_logo: + file: ./pb-framr/logo.svg + landing_pages: + file: ./pb-framr/pages.json volumes: openhab_addons: diff --git a/template_configs/pb-framr/logo.svg b/template_configs/pb-framr/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..6a04fd05a804594625910d1289859cf4c5b022d7 --- /dev/null +++ b/template_configs/pb-framr/logo.svg @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + width="105.83334mm" + height="105.83334mm" + viewBox="0 0 105.83334 105.83334" + version="1.1" + id="svg8"> + <defs + id="defs2" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + transform="translate(-28.600533,-86.069031)"> + <circle + style="opacity:1;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.12197291;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path881" + cx="81.517204" + cy="138.9857" + r="52.916672" /> + <g + transform="matrix(1.4629556,0,0,1.4629556,-15.525955,-77.21686)" + id="g891" + style="fill:#ffffff"> + <text + id="text859" + y="143.47997" + x="52.784073" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:OpenSans;-inkscape-font-specification:OpenSans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.69999981px;font-family:OpenSans;-inkscape-font-specification:OpenSans;fill:#ffffff;stroke-width:0.26458332" + y="143.47997" + x="52.784073" + id="tspan857">Your</tspan></text> + <text + id="text859-8" + y="158.60701" + x="48.7006" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:Z003;-inkscape-font-specification:Z003;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:OpenSans;-inkscape-font-specification:'OpenSans Bold';fill:#ffffff;stroke-width:0.26458332" + y="158.60701" + x="48.7006" + id="tspan857-9">Logo</tspan></text> + </g> + </g> +</svg>