Browse Source

Init commit

Joachim M. Giæver 3 years ago
parent
commit
cc6d672b7b

+ 57 - 0
scripts/bin/certs-daemon

@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+snapctl is-connected certs
+
+if [ $? -eq 0 ]; then
+
+    DOMAIN=`snapctl get domain`
+    if [ $? -ne 0 ]; then
+        logger "Missing domain, use 'snap set ${SNAP_NAME} domain=<domain>'"
+        exit 0
+    fi
+
+    source "${SNAP}/helper/init"
+
+    readarray -d '' DOMAIN_DIR < <(find "${CERTS_DIR}" -type d -name "${DOMAIN}" -print0)
+
+    if [ "${#DOMAIN_DIR[@]}" -ne 1 ]; then
+        logger "Not a unique match for domain ${DOMAIN}"
+        exit 1
+    fi
+
+    DOMAIN_DIR="${DOMAIN_DIR[0]}"
+
+    LAST_EDIT=`stat "${DOMAIN_DIR}/.time" --format="%Y" 2> /dev/null || echo 0`
+    CURR_EDIT=`stat "${SSL_DIR}/.time" --format="%Y" 2> /dev/null || echo 0`
+
+    if [ "${LAST_EDIT}" -le "${CURR_EDIT}" ]; then
+        logger "No new certificate for ${DOMAIN}: `expr ${CURR_EDIT} - ${LAST_EDIT}`"
+        exit 0
+    fi
+
+    readarray -d '' CERTS < <(find "${DOMAIN_DIR}" -type f -name "*.gpg" -print0)
+
+    gpg_start_agent
+
+    for CERT in "${CERTS[@]}"; do
+        DEST="${SSL_DIR}/`basename "${CERT}" ".gpg"`"
+        CURR=""
+
+        if [ -f "${DEST}" ]; then
+            CURR=`cat "${DEST}"`
+        fi
+
+        gpg --batch --yes --output "${DEST}" --decrypt "${CERT}"
+
+        if [ -n "${CURR}" ]; then
+            DIFF=`echo "${CURR}" | diff "${DEST}" -`
+            if [ $? -ne 0 ]; then
+                echo "${ORIG}" > "${DEST}.backup"
+            fi
+        fi
+    done
+    cp -f "${DOMAIN_DIR}/.time" "${SSL_DIR}/.time"
+    logger "Replaced certificate ${DOMAIN} for snap ${SNAP_NAME}"
+    snapctl restart "${SNAP_NAME}.server"
+    gpg_close_agent
+fi

+ 97 - 0
scripts/bin/update-settings

@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+
+import json, sys, subprocess, ast
+
+if len(sys.argv) < 3:
+    print(f"Error, use {sys.argv[0]} install|update")
+    sys.exit(1)
+
+settings = sys.argv[1]
+
+conv = {
+    "LISTENIP": ("listen.ip", str),
+    "PORT": ("listen.port", int),
+    "GIT": ("git", bool),
+    "BASEPATH": ("basepath.dir", str),
+    "ENFORCE_BASEPATH": ("basepath.enforce", bool),
+    "SSL_CERTIFICATE": ("ssl.certificate", str),
+    "SSL_KEY": ("ssl.key", str),
+    "IGNORE_SSL": ("ssl.ignore", bool),
+    "HASS_API": ("hass.api.url", str),
+    "HASS_WS_API": ("hass.api.ws-url", str),
+    "HASS_API_PASSWORD": ("hass.api.password", str),
+    "USERNAME": ("login.username", str),
+    "PASSWORD": ("login.password", str),
+    "ALLOWED_NETWORKS": ("allowed.networks", list),
+    "ALLOWED_DOMAINS": ("allowed.domains", list),
+    "BANNED_IPS": ("ban.ips", list),
+    "BANLIMIT": ("ban.limit", list),
+    "IGNORE_PATTERN": ("ignore-pattern", list),
+    "DIRSFIRST": ("dirs-first", bool),
+    "SESAME": ("sesame.key", str),
+    "SESAME_TOTP_SECRET": ("sesame.secret", str),
+    "VERIFY_HOSTNAME": ("verify-hostname", bool),
+    "ENV_PREFIX": ("env-prefix",  str),
+    "NOTIFY_SERVICE": ("notify-service", str)
+}
+
+def read(param=None):
+
+    with open(settings, "r") as json_file:
+        obj = json.load(json_file)
+
+        if param is None:
+            return obj
+
+        if param in obj:
+            return obj[param]
+
+    sys.exit(1)
+
+def iterate_obj(obj, orig, i, path=""):
+
+    if not isinstance(obj, dict):
+        raise Exception("Invalid data")
+
+    for k,v in obj.items():
+        if isinstance(v, dict):
+            iterate_obj(v, orig, i, path+k+".")
+        else:
+            key=path+k
+            for origkey, conversion in conv.items():
+                if key == conversion[0]:
+                    if conversion[1] == list:
+                        orig[origkey] = ast.literal_eval(v)
+                    elif conversion[1] == bool:
+                        orig[origkey] = True if v.lower() == "true" else False
+                    else:
+                        if v.lower() in ("none", "null"):
+                            v = "null"
+                        else:
+                            orig[origkey] = (conversion[1])(v)
+                    i[0] = i[0] + 1
+                    break
+
+if sys.argv[2] == "update":
+    try:
+        new = json.loads(sys.stdin.read())
+        orig = read()
+
+        i = [0]
+        iterate_obj(new, orig, i)
+
+        assert i[0] == len(conv), f"Wrong number of entries, {i[0]} != {len(conv)}."
+
+        with open(settings, "w+") as json_file:
+            json.dump(orig, json_file, indent=4)
+
+    except Exception as e:
+        print(e)
+        sys.exit(1)
+    sys.exit(0)
+if sys.argv[2] == "install":
+    obj = read()
+    for k, v in obj.items():
+        print(f"server.{conv[k][0]}=\"{v}\"")
+    sys.exit(0)
+sys.exit(1)

+ 35 - 0
scripts/helper/init

@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+
+CERTS_DIR="${SNAP_DATA}/certs"
+SSL_DIR="${SNAP_DATA}/.ssl"
+UUID_FILE="${SNAP_DATA}/.install-id"
+
+if ! [[ -f "${UUID_FILE}" ]]; then
+    logger "Generate UUID for ${SNAP_NAME}"
+    uuid > "${UUID_FILE}"
+    logger "${SNAP_NAME} (uuid: `cat ${UUID_FILE}`)"
+fi
+
+UUID=`cat ${UUID_FILE}`
+
+function gpg_start_agent {
+    gpg-agent 2> /dev/null
+    if [ $? -ne 0 ]; then
+        logger "GPG-agent not here"
+        gpg-agent --daemon 2> /dev/null
+        if [ $? -eq 0 ]; then
+            logger "GPG-agent started"
+        else
+            logger "Failed starting GPG-agent, trying anyway"
+        fi
+    fi
+    return 0
+}
+
+function gpg_close_agent {
+    PID=`ps -T | grep gpg-agent | head -n 1 | cut -d' ' -f1`
+    if [ -n "${PID}" ]; then
+        logger "Kill GPG-agent: ${PID}"
+        kill -9 "${PID}" >> /dev/null
+    fi
+}

+ 7 - 0
scripts/hooks/configure

@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -e 
+
+snapctl get server | $SNAP/bin/update-settings "${SNAP_DATA}/settings.conf" update
+snapctl restart "${SNAP_NAME}.server"
+exit $?

+ 6 - 0
scripts/hooks/connect-plug-configurations

@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+snapctl set server.basepath.dir="${SNAP_COMMON}/configurations"
+snapctl set server.basepath.enforce=True
+snapctl get server | $SNAP/bin/update-settings "${SNAP_DATA}/settings.conf" update
+snapctl restart "${SNAP_NAME}.server"

+ 9 - 0
scripts/hooks/install

@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -e
+cp "${SNAP}/settings.conf" "${SNAP_DATA}/settings.conf"
+
+$SNAP/bin/update-settings "${SNAP_DATA}/settings.conf" "install" | while read LINE; do
+    logger "${LINE}"
+    snapctl set "${LINE}"
+done

+ 49 - 0
scripts/hooks/prepare-slot-certs

@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+
+source "${SNAP}/helper/init"
+
+# if ! [[ -d "${CERTS_DIR}" ]]; then
+#     logger "Creating dir ${CERTS_DIR}"
+#     mkdir "${CERTS_DIR}"
+#     chmod 1755 "${CERTS_DIR}"
+# fi
+
+if ! [[ -d "${SSL_DIR}" ]]; then
+    logger "Creating local cert dir ${SSL_DIR}"
+    mkdir "${SSL_DIR}"
+fi
+
+logger "Creating connection from ${SNAP_NAME} (uuid: ${UUID})"
+
+KEY=`gpg --armor --export "${UUID}"`
+
+if [ -z "${KEY}" ]; then
+    gpg_start_agent
+    logger "Create GPG-key"
+    gpg --batch --no-tty --gen-key <<< "
+    %echo Generating a basic OpenPGP key
+    Key-Type: default
+    Subkey-Type: default
+    Name-Real: ${SNAP_NAME}
+    Name-Email: ${UUID}@${SNAP_NAME}.gpg
+    Name-comment: Generated from ${SNAP_NAME} for certs-slot
+    Expire-Date: 0
+    %no-protection
+    %commit
+    %echo done"
+    gpg_close_agent
+fi
+
+KEY=`gpg --armor --export "${UUID}"`
+if [ $? -ne 0 ]; then
+    echo "Failed to create GPG-key"
+    logger "Failed to create GPG-key for ${UUID}"
+    exit 1
+fi
+
+snapctl set :certs snapname="${SNAP_NAME}"
+snapctl set :certs uuid="${UUID}"
+snapctl set :certs pkey="${KEY}"
+
+logger "${SNAP_NAME}:certs (uuid: ${UUID}) prepared"
+logger "Key: ${KEY}"

+ 89 - 0
snap/snapcraft.yaml

@@ -0,0 +1,89 @@
+name: home-assistant-configurator
+base: core20 
+version: '0.4.0'
+summary: Configuration UI for Home Assistant
+description: |
+  The HASS-Configurator is a small webapp (you access it via web browser) that provides a filesystem-browser and text-editor to modify files on the machine the configurator is running on. It has been created to allow easy configuration of Home Assistant. It is powered by Ace editor, which supports syntax highlighting for various code/markup languages. YAML files (the default language for Home Assistant configuration files) will be automatically checked for syntax errors while editing.
+
+  Please configure the snap thout `snap set home-assistant-configurator server.*=`,
+  see `snap get home-assistant-configurator` after installation.
+
+  This snap requires the snap `home-assistant-snap`
+grade: stable
+confinement: strict
+
+apps:
+  server:
+    command: bin/hass-configurator -dH $SNAP_DATA/settings.conf
+    daemon: simple
+    restart-condition: always
+    plugs:
+      - network
+      - network-bind
+      - configurations
+  certs-daemon:
+    command: bin/certs-daemon
+    # daemon: oneshot
+    # restart-condition: always
+    # timer: 00:00-24:00/287
+    slots:
+      - certs
+    plugs:
+      - network
+      - network-bind
+
+slots:
+  certs:
+    interface: content
+    content: certs
+    write:
+      - $SNAP_DATA/certs
+
+plugs:
+  configurations:
+    interface: content
+    target: $SNAP_COMMON/configurations
+    default-provider: home-assistant-snap
+
+hooks:
+  prepare-slot-certs:
+    plugs:
+      - network
+      - network-bind
+
+parts:
+  configurator:
+    plugin: python
+    source: https://github.com/danielperna84/hass-configurator.git
+    source-tag: ${SNAPCRAFT_PROJECT_VERSION}
+    build-environment:
+      - PYTHONPATH: "/usr/lib/python3/dist-packages"
+    build-packages:
+      - python3
+      - python3-dev 
+      - python3-wheel
+      - python3-venv
+      - python3-pip
+    stage:
+      - lib64/python3.8
+      - bin
+      - include
+      - lib
+      - pyvenv.cfg
+      - settings.conf
+      - share
+    override-build: |
+      snapcraftctl build
+      cp ${SNAPCRAFT_PART_BUILD}/settings.conf ${SNAPCRAFT_PART_INSTALL}/settings.conf
+  scripts:
+    plugin: dump
+    source: ./scripts
+    source-type: local
+    organize:
+      hooks: snap/hooks
+    stage-packages:
+      - uuid
+      - libossp-uuid16
+      - libassuan0
+      - gpg
+      - gpg-agent