From ff4e35e2d4f2f4164b75ad081d17979c01414042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 09:23:50 +0100 Subject: [PATCH 01/20] add testing for `core.settings.Settings` --- api/ovdashboard_api/core/settings.py | 75 +++++---- api/poetry.lock | 46 ++++++ api/pyproject.toml | 1 + api/test/test_settings.py | 224 +++++++++++++++++++++++++++ 4 files changed, 307 insertions(+), 39 deletions(-) create mode 100644 api/test/test_settings.py diff --git a/api/ovdashboard_api/core/settings.py b/api/ovdashboard_api/core/settings.py index 1cc17e8..e1801af 100644 --- a/api/ovdashboard_api/core/settings.py +++ b/api/ovdashboard_api/core/settings.py @@ -18,12 +18,11 @@ class DAVSettings(BaseModel): Connection to a DAV server. """ - protocol: str | None = None - host: str | None = None - path: str | None = None + protocol: str = "https" + host: str = "example.com" - username: str | None = None - password: str | None = None + username: str = "ovd_user" + password: str = "password" cache_ttl: int = 60 * 10 @@ -33,21 +32,34 @@ class DAVSettings(BaseModel): Combined DAV URL. """ - return f"{self.protocol}://{self.host}{self.path}" + return f"{self.protocol}://{self.host}" -class WebDAVSettings(DAVSettings): +_DAV_DEFAULT = DAVSettings().model_dump() + + +class CalDAVSettings(DAVSettings): + """ + Connection to a CalDAV server. + """ + + path: str = "/remote.php/dav" + + @property + def url(self) -> str: + """ + Combined DAV URL. + """ + + return f"{super().url}{self.path}" + + +class WebDAVSettings(CalDAVSettings): """ Connection to a WebDAV server. """ - protocol: str = "https" - host: str = "example.com" - path: str = "/remote.php/dav" - prefix: str = "/ovdashboard" - - username: str = "ovd_user" - password: str = "password" + path: str = "/remote.php/webdav" config_filename: str = "config.txt" @@ -62,7 +74,10 @@ class WebDAVSettings(DAVSettings): Combined DAV URL. """ - return f"{self.protocol}://{self.host}{self.path}{self.prefix}" + return f"{super().url}{self.prefix}" + + +_WEBDAV_DEFAULT = WebDAVSettings().model_dump() class RedisSettings(BaseModel): @@ -132,7 +147,7 @@ class Settings(BaseSettings): # caldav settings ##### - caldav: DAVSettings = DAVSettings() + caldav: CalDAVSettings = CalDAVSettings() ##### # redis settings @@ -141,37 +156,19 @@ class Settings(BaseSettings): redis: RedisSettings = RedisSettings() @model_validator(mode="before") - @classmethod def validate_dav_settings(cls, data) -> dict[str, Any]: assert isinstance(data, dict) # ensure both settings dicts are created for key in ("webdav", "caldav"): - if key not in data: - data[key] = {} + data[key] = data.get(key, {}) - default_dav = DAVSettings( - protocol="https", - host="example.com", - username="ovdashboard", - password="secret", - ).model_dump() - - for key in default_dav: - # if "webdav" value is not specified, use default - if key not in data["webdav"] or data["webdav"][key] is None: - data["webdav"][key] = default_dav[key] + for key in _DAV_DEFAULT: + # if "webdav" value is not specified, use default value + data["webdav"][key] = data["webdav"].get(key, _WEBDAV_DEFAULT[key]) # if "caldav" value is not specified, use "webdav" value - if key not in data["caldav"] or data["caldav"][key] is None: - data["caldav"][key] = data["webdav"][key] - - # add default "path"s if None - if data["webdav"]["path"] is None: - data["webdav"]["path"] = "/remote.php/webdav" - - if data["caldav"]["path"] is None: - data["caldav"]["path"] = "/remote.php/dav" + data["caldav"][key] = data["caldav"].get(key, data["webdav"][key]) return data diff --git a/api/poetry.lock b/api/poetry.lock index f401e97..eaabfce 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -522,6 +522,17 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "isort" version = "5.12.0" @@ -787,6 +798,21 @@ files = [ docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "pycodestyle" version = "2.11.1" @@ -961,6 +987,26 @@ files = [ {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, ] +[[package]] +name = "pytest" +version = "7.4.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.8.2" diff --git a/api/pyproject.toml b/api/pyproject.toml index c899f68..f18ce19 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -26,6 +26,7 @@ black = "^23.10.1" flake8 = "^6.1.0" flake8-isort = "^6.1.0" types-cachetools = "^5.3.0.6" +pytest = "^7.4.3" [build-system] build-backend = "poetry.core.masonry.api" diff --git a/api/test/test_settings.py b/api/test/test_settings.py new file mode 100644 index 0000000..d94e167 --- /dev/null +++ b/api/test/test_settings.py @@ -0,0 +1,224 @@ +import os + +import pytest + +from ovdashboard_api.core.settings import Settings + + +@pytest.fixture(autouse=True, scope="function") +def patch_settings_env_file(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(os, "environ", {}) + monkeypatch.delitem(Settings.model_config, "env_file") + + +def test_empty(): + s = Settings.model_validate({}) + + assert s.log_level == "INFO" + assert s.production_mode is False + assert s.ui_directory == "/usr/local/share/ovdashboard_ui/html" + + assert s.ping_host == "1.0.0.0" + assert s.ping_port == 1 + + assert s.openapi_url == "/api/openapi.json" + assert s.docs_url == "/api/docs" + assert s.redoc_url == "/api/redoc" + + # webdav + + assert s.webdav.protocol == "https" + assert s.webdav.host == "example.com" + + assert s.webdav.username == "ovd_user" + assert s.webdav.password == "password" + + assert s.webdav.cache_ttl == 600 + + assert s.webdav.path == "/remote.php/webdav" + + assert s.webdav.config_filename == "config.txt" + + assert s.webdav.disable_check is False + assert s.webdav.retries == 20 + assert s.webdav.retry_delay == 30 + assert s.webdav.prefix == "/ovdashboard" + + # caldav + + assert s.caldav.protocol == "https" + assert s.caldav.host == "example.com" + + assert s.caldav.username == "ovd_user" + assert s.caldav.password == "password" + + assert s.caldav.cache_ttl == 600 + + assert s.caldav.path == "/remote.php/dav" + + +def test_prod(): + s = Settings.model_validate({"production_mode": True}) + + assert s.log_level == "INFO" + assert s.production_mode is True + + assert s.openapi_url is None + assert s.docs_url is None + assert s.redoc_url is None + + +def test_set_caldav(): + s = Settings.model_validate( + { + "caldav": { + "protocol": "cd_protocol", + "host": "cd_host", + "username": "cd_username", + "password": "cd_password", + "cache_ttl": "0", + "path": "cd_path", + } + } + ) + + # webdav + + assert s.webdav.protocol == "https" + assert s.webdav.host == "example.com" + + assert s.webdav.username == "ovd_user" + assert s.webdav.password == "password" + + assert s.webdav.cache_ttl == 600 + + assert s.webdav.path == "/remote.php/webdav" + + assert s.webdav.config_filename == "config.txt" + + assert s.webdav.disable_check is False + assert s.webdav.retries == 20 + assert s.webdav.retry_delay == 30 + assert s.webdav.prefix == "/ovdashboard" + + # caldav + + assert s.caldav.protocol == "cd_protocol" + assert s.caldav.host == "cd_host" + + assert s.caldav.username == "cd_username" + assert s.caldav.password == "cd_password" + + assert s.caldav.cache_ttl == 0 + + assert s.caldav.path == "cd_path" + + +def test_set_webdav(): + s = Settings.model_validate( + { + "webdav": { + "protocol": "wd_protocol", + "host": "wd_host", + "username": "wd_username", + "password": "wd_password", + "cache_ttl": "99", + "path": "wd_path", + "config_filename": "wd_config_filename", + "disable_check": "true", + "retries": "99", + "retry_delay": "99", + "prefix": "wd_prefix", + } + } + ) + + # webdav + + assert s.webdav.protocol == "wd_protocol" + assert s.webdav.host == "wd_host" + + assert s.webdav.username == "wd_username" + assert s.webdav.password == "wd_password" + + assert s.webdav.cache_ttl == 99 + + assert s.webdav.path == "wd_path" + + assert s.webdav.config_filename == "wd_config_filename" + + assert s.webdav.disable_check is True + assert s.webdav.retries == 99 + assert s.webdav.retry_delay == 99 + assert s.webdav.prefix == "wd_prefix" + + # caldav + + assert s.caldav.protocol == "wd_protocol" + assert s.caldav.host == "wd_host" + + assert s.caldav.username == "wd_username" + assert s.caldav.password == "wd_password" + + assert s.caldav.cache_ttl == 99 + + assert s.caldav.path == "/remote.php/dav" + + +def test_set_caldav_webdav(): + s = Settings.model_validate( + { + "webdav": { + "protocol": "wd_protocol", + "host": "wd_host", + "username": "wd_username", + "password": "wd_password", + "cache_ttl": "99", + "path": "wd_path", + "config_filename": "wd_config_filename", + "disable_check": "true", + "retries": "99", + "retry_delay": "99", + "prefix": "wd_prefix", + }, + "caldav": { + "protocol": "cd_protocol", + "host": "cd_host", + "username": "cd_username", + "password": "cd_password", + "cache_ttl": "0", + "path": "cd_path", + }, + } + ) + + # webdav + + assert s.webdav.protocol == "wd_protocol" + assert s.webdav.host == "wd_host" + + assert s.webdav.username == "wd_username" + assert s.webdav.password == "wd_password" + + assert s.webdav.cache_ttl == 99 + + assert s.webdav.path == "wd_path" + + assert s.webdav.config_filename == "wd_config_filename" + + assert s.webdav.disable_check is True + assert s.webdav.retries == 99 + assert s.webdav.retry_delay == 99 + assert s.webdav.prefix == "wd_prefix" + + # caldav + + assert s.caldav.protocol == "cd_protocol" + assert s.caldav.host == "cd_host" + + assert s.caldav.username == "cd_username" + assert s.caldav.password == "cd_password" + + assert s.caldav.cache_ttl == 0 + + assert s.caldav.path == "cd_path" From 9faaee714a2ecd037527d71f37611b8bf58658ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 09:26:18 +0100 Subject: [PATCH 02/20] add "mini-tiangolo" to Dockerfile - tiangolo/uvicorn-gunicorn is not available for ARM arch --- Dockerfile | 28 ++++++++--- install/mini-tiangolo/gunicorn_conf.py | 67 ++++++++++++++++++++++++++ install/mini-tiangolo/start.sh | 20 ++++++++ 3 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 install/mini-tiangolo/gunicorn_conf.py create mode 100644 install/mini-tiangolo/start.sh diff --git a/Dockerfile b/Dockerfile index f708a7b..1dac049 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,12 +17,31 @@ RUN yarn install --production false COPY ui ./ RUN yarn build --dest /tmp/ovdashboard_ui/html +###################### +# python preparation # +###################### + +ARG PYTHON_VERSION +FROM python:${PYTHON_VERSION} as uvicorn-gunicorn + +# where credit is due ... +LABEL maintainer="Sebastian Ramirez " +WORKDIR /usr/local/share/uvicorn-gunicorn + +# install uvicorn-gunicorn +COPY "./install/mini-tiangolo/" "." + +RUN set -ex; \ + chmod +x start.sh; \ + python3 -m pip --no-cache-dir install gunicorn; + +CMD ["/usr/local/share/uvicorn-gunicorn/start.sh"] + ########### # web app # ########### -ARG PYTHON_VERSION -FROM tiangolo/uvicorn-gunicorn:python${PYTHON_VERSION} AS production +FROM uvicorn-gunicorn AS production # env setup WORKDIR /usr/local/src/ovdashboard_api @@ -40,11 +59,8 @@ RUN set -ex; \ libmagic1 \ ; rm -rf /var/lib/apt/lists/*; \ \ - # remove example app - rm -rf /app; \ - \ # install ovdashboard_api - python -m pip --no-cache-dir install ./ + python3 -m pip --no-cache-dir install ./ # add prepared ovdashboard_ui COPY --from=build-ui /tmp/ovdashboard_ui /usr/local/share/ovdashboard_ui diff --git a/install/mini-tiangolo/gunicorn_conf.py b/install/mini-tiangolo/gunicorn_conf.py new file mode 100644 index 0000000..7dd141d --- /dev/null +++ b/install/mini-tiangolo/gunicorn_conf.py @@ -0,0 +1,67 @@ +import json +import multiprocessing +import os + +workers_per_core_str = os.getenv("WORKERS_PER_CORE", "1") +max_workers_str = os.getenv("MAX_WORKERS") +use_max_workers = None +if max_workers_str: + use_max_workers = int(max_workers_str) +web_concurrency_str = os.getenv("WEB_CONCURRENCY", None) + +host = os.getenv("HOST", "0.0.0.0") +port = os.getenv("PORT", "80") +bind_env = os.getenv("BIND", None) +use_loglevel = os.getenv("LOG_LEVEL", "info") +if bind_env: + use_bind = bind_env +else: + use_bind = f"{host}:{port}" + +cores = multiprocessing.cpu_count() +workers_per_core = float(workers_per_core_str) +default_web_concurrency = workers_per_core * cores +if web_concurrency_str: + web_concurrency = int(web_concurrency_str) + assert web_concurrency > 0 +else: + web_concurrency = max(int(default_web_concurrency), 2) + if use_max_workers: + web_concurrency = min(web_concurrency, use_max_workers) +accesslog_var = os.getenv("ACCESS_LOG", "-") +use_accesslog = accesslog_var or None +errorlog_var = os.getenv("ERROR_LOG", "-") +use_errorlog = errorlog_var or None +graceful_timeout_str = os.getenv("GRACEFUL_TIMEOUT", "120") +timeout_str = os.getenv("TIMEOUT", "120") +keepalive_str = os.getenv("KEEP_ALIVE", "5") + +# Gunicorn config variables +loglevel = use_loglevel +workers = web_concurrency +bind = use_bind +errorlog = use_errorlog +worker_tmp_dir = "/dev/shm" +accesslog = use_accesslog +graceful_timeout = int(graceful_timeout_str) +timeout = int(timeout_str) +keepalive = int(keepalive_str) + + +# For debugging and testing +log_data = { + "loglevel": loglevel, + "workers": workers, + "bind": bind, + "graceful_timeout": graceful_timeout, + "timeout": timeout, + "keepalive": keepalive, + "errorlog": errorlog, + "accesslog": accesslog, + # Additional, non-gunicorn variables + "workers_per_core": workers_per_core, + "use_max_workers": use_max_workers, + "host": host, + "port": port, +} +print(json.dumps(log_data)) diff --git a/install/mini-tiangolo/start.sh b/install/mini-tiangolo/start.sh new file mode 100644 index 0000000..b72737b --- /dev/null +++ b/install/mini-tiangolo/start.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -e + +MODULE_NAME=${MODULE_NAME:-"app.main"} +VARIABLE_NAME=${VARIABLE_NAME:-"app"} +export APP_MODULE="${APP_MODULE:-"$MODULE_NAME:$VARIABLE_NAME"}" +export GUNICORN_CONF="${GUNICORN_CONF:-"/usr/local/share/uvicorn-gunicorn/gunicorn_conf.py"}" +export WORKER_CLASS="${WORKER_CLASS:-"uvicorn.workers.UvicornWorker"}" + +if [ -f "${PRE_START_PATH}" ] ; then + echo "Running script ${PRE_START_PATH}" + # shellcheck disable=SC1090 + . "${PRE_START_PATH}" +fi + +# Start Gunicorn +exec gunicorn \ + -k "${WORKER_CLASS}" \ + -c "${GUNICORN_CONF}" \ + "${APP_MODULE}" From 72ae9e222c39536575550a764fd3ce3e1840cb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:19:12 +0100 Subject: [PATCH 03/20] compose file --- install/docker-compose.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 install/docker-compose.yml diff --git a/install/docker-compose.yml b/install/docker-compose.yml new file mode 100644 index 0000000..9a78aa0 --- /dev/null +++ b/install/docker-compose.yml @@ -0,0 +1,38 @@ +services: + # shared cache + redis: + image: redis:7-alpine + restart: always + pull_policy: always + + # necessary for "host" network deployment, + # but only listen on localhost + ports: + - "127.0.0.1:6379:6379" + + app: + image: code.yavook.de/oekzident.de/ovdashboard:0.1.0 + restart: always + pull_policy: always + depends_on: + - redis + + # "app" container needs host ip + network_mode: host + user: root + + environment: + # necessary for "host" network deployment + PORT: "80" + REDIS__HOST: "localhost" + + # >>>>> USER VARIABLES <<<<< + # you will want to adjust these! + TZ: "Europe/Berlin" + + WEBDAV__HOST: "example.com" + WEBDAV__PATH: "/remote.php/webdav" + WEBDAV__PREFIX: "/ovdashboard" + + WEBDAV__USERNAME: "ovd_user" + WEBDAV__PASSWORD: "password" From a93f56ee65fb14e7733a4a7f14f7a91d243e61af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:27:09 +0100 Subject: [PATCH 04/20] README overhaul --- README.md | 80 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index f1520f0..c07c8de 100644 --- a/README.md +++ b/README.md @@ -36,19 +36,53 @@ The Dashboard UI is created using [Vue](https://vuejs.org/) and the [Vuetify](ht ## Quick Start +### Prerequisites + Make sure you have a WebDAV and CalDAV account available. For an all-in-one solution, consider setting up an account on a [Nextcloud](https://nextcloud.com/) instance!
-On your WebvDAV account, create a resource (directory) named `ovdashboard`. +On your WebDAV account, create a resource (directory) named `ovdashboard`[^1]. -The intended installation method is as follows: -1. Have a Raspberry Pi (3 or later) connected to a HDMI screen -1. Boot up "Raspberry Pi OS" (64 bit) and connect the Pi to your local network -1. Download (and review) the [OVDashboard installer](TODO), then run it from a terminal - > If you feel adventurous and want to skip the script review, run `curl --proto '=https' --tlsv1.2 -sSf 'TODO' | sh` instead. -1. Reboot the Raspberry Pi +Your target device should be a Raspberry Pi Model 3 or later[^2]. You will need some accessories: + - microSD card, class 10 or UHS (min. 8 GB) + - network connectivity (bring WiFi credentials if applicable) + - connection to a HDMI screen -For a better understanding of your newly created OVDashboard, refer to the [about section](TODO). +It is also heavily advisable that you log into your device using SSH, so you should get another device (PC, tablet or smartphone) onto the same network as your OVDashboard. +[^1]: if named differently, you will need to adjust your compose file later on +[^2]: other devices will also work, but might require extra installation steps + +### Install Base System + +`OVDashboard` is designed to run on a `DietPi` installation. Full installation documentation is available [at dietpi.com](https://dietpi.com/docs/install/). To quickly get up and running: + +1. Download Image from [dietpi.com/#download](https://dietpi.com/#download) +1. Uncompress Image and flash onto SD card – you might need "7zip", "balenaEtcher" and/or other tools +1. Check the SD card, open "dietpi.txt" and change some options (full documentation [here](https://dietpi.com/docs/usage/#options-within-the-file)): + - For WiFi, use `AUTO_SETUP_NET_WIFI_ENABLED=1` and `AUTO_SETUP_NET_WIFI_COUNTRY_CODE=DE`, and also check "dietpi-wifi.txt" + - System options, e.g. `AUTO_SETUP_AUTOMATED=1`, `AUTO_SETUP_NET_HOSTNAME=ovdashboard`, `AUTO_SETUP_GLOBAL_PASSWORD=dietpi`, `AUTO_SETUP_LOCALE=de_DE.UTF-8`, `AUTO_SETUP_KEYBOARD_LAYOUT=de`, `AUTO_SETUP_TIMEZONE=Europe/Berlin`, `CONFIG_SERIAL_CONSOLE_ENABLE=0` +1. Be sure to at least change the password (and remember it 🙂️), then put the SD card into your device and boot it. Let the first time setup finish, it will take a bit. It will let you know when it is done! +1. Log into your device using SSH. By default, that's user name `root` and password `dietpi` + +### Install OVDashboard + +Download (and review) the [OVDashboard install script](//code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/install/install.sh), then run it from a terminal. + +This can all be done after logging into your prepared device: +- The safe way: + 1. `mkdir /tmp/ovdashboard && cd /tmp/ovdashboard` + 1. `wget 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/install/install.sh'` + 1. `less install.sh` and/or edit with `nano install.sh` + 1. `sh install.sh` +- If you feel adventurous and do not want to review the script, just run `sh <( curl --proto '=https' --tlsv1.2 -sSf 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/install/install.sh' )` + +Afterwards, reboot your device (`reboot` in the terminal). Your OVDashboard should be working now. + +For a better understanding of your newly created OVDashboard, refer to the [about section](#about-the-default-ovdashboard-deployment). + +## Updates, upgrades + +`/opt/ovdashboard` ## Configuration @@ -60,31 +94,17 @@ Refer to the specific README files for [the API](./api/README.md) and [the UI](. ## About the "default" OVDashboard deployment -The default deployment is made up of three parts: The **installer** which runs once, prepares your device for OVDashboard usage and deploys the **client** and the **service**. +The default deployment is made up of three parts: The **installer** runs once, prepares your device for OVDashboard usage and deploys the **server**. -The **client** will connect to and display your OVDashboard. +The **server** will then run continuously and make OVDashboard available as a web application in your network. -The **service** will run continuously and makes sure your OVDashboard is up to date. +Also, a browser is installed to display the OVDashboard using your device. ### The OVDashboard installer will: -- install some general-purpose packages -- make you set a new password, if you haven't yet -- install and set up Docker Engine and Mozilla Firefox -- (optionally) enable SSH and/or VNC access to your device -- create the OVDashboard configuration directory `/usr/local/etc/ovdashboard` on your device -- set up the OVDashboard service -- (optionally) set up your device to auto-run the OVDashboard client - - -### The OVDashboard service will: - -- regularly pull the latest `docker` image from [`TODO`](TODO), containing both the OVDashboard API and UI -- ensure a container of that image is always running, using your OVDashboard configuration directory - - -### The OVDashboard client will: - -- open Mozilla Firefox in full-screen mode and browse to the local OVDashboard UI -- enable auto-hiding your mouse cursor +- install and set up Docker, Docker Compose and Chromium-Browser +- create the OVDashboard Compose project at `/opt/ovdashboard` +- get a `docker` image from [`code.yavook.de`](https://code.yavook.de/OEKZident.de/-/packages/container/ovdashboard) containing the OVDashboard server +- set up your device to auto-run Chromium in "kiosk" mode to display the local OVDashboard +- auto-hide your mouse cursor From caa30660dc76a3edfa6428880a8bef7b87bb895e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:31:49 +0100 Subject: [PATCH 05/20] TODOs in readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c07c8de..8bc190d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ All that matters, at one glance!
*Date and Time – Upcoming Events – Public Announcements – News – Pictures* - **Easy Install**
-Set up a RaspberryPi, run the [installer script](TODO), done! +Set up a RaspberryPi, run the [installer script](./install/install.sh), done! - **DAV Server Interface**
Update your content anytime, from anywhere! @@ -82,10 +82,12 @@ For a better understanding of your newly created OVDashboard, refer to the [abou ## Updates, upgrades + `/opt/ovdashboard` ## Configuration + ## Setup for development and contribution From 976506489cb89d2e15d7c94e3dd29dbbc86b8d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:55:10 +0100 Subject: [PATCH 06/20] "dietpi" installer suspected working --- install/install.sh | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 install/install.sh diff --git a/install/install.sh b/install/install.sh new file mode 100644 index 0000000..a590ea0 --- /dev/null +++ b/install/install.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +######### +# start # +######### + +# env setup +ovd_version="0.1.0" +export DEBIAN_FRONTEND="noninteractive" +set -e + +# banner +echo "Installer for OVDashboard ${ovd_version}" +echo "Waiting 10 seconds, press Ctrl+C to cancel installation ..." +sleep 10 + +################# +# prerequisites # +################# + +# 134: docker with compose +# 113: chromium browser +/boot/dietpi/dietpi-software install 134 113 + +# htpdate (timesync in restricted networks) +# unclutter (hides mouse cursor) +apt-get update && apt-get install --yes --no-install-recommends \ + htpdate unclutter + +# activate unclutter +echo '/usr/bin/unclutter -idle 0.1 &' > /etc/chromium.d/dietpi-unclutter + +# chromium window size +echo "Please enter your screen resolution!" + +screen_x="$( cut -d ',' -f 1 '/sys/class/graphics/fb0/virtual_size' )" +printf "Width [default: %d]: " "${screen_x}" +read -r screen_x_in +screen_x="${screen_x_in:-$screen_x}" +sed -ri "s/^(SOFTWARE_CHROMIUM_RES_X=)[0-9]+$/\1${screen_x}/" '/boot/dietpi.txt' + +screen_y="$( cut -d ',' -f 2 '/sys/class/graphics/fb0/virtual_size' )" +printf "Height [default: %d]: " "${screen_y}" +read -r screen_y_in +screen_y="${screen_y_in:-$screen_y}" +sed -ri "s/^(SOFTWARE_CHROMIUM_RES_Y=)[0-9]+$/\1${screen_y}/" '/boot/dietpi.txt' + +# chromium autostart +echo '11' > '/boot/dietpi/.dietpi-autostart_index' # magic number +sed -ri "s/^(AUTO_SETUP_AUTOSTART_LOGIN_USER=).+$/\1dietpi/" '/boot/dietpi.txt' # run as "dietpi" +sed -ri "s/^(SOFTWARE_CHROMIUM_AUTOSTART_URL=).+$/\1http:\/\/localhost\//" '/boot/dietpi.txt' # open "localhost" + +# chromium language +display_lang="en-US,en" +printf "Enter display language(s) [default: %s]: " "${display_lang}" +read -r display_lang_in +display_lang="${display_lang_in:-${display_lang}}" + +sudo -u dietpi mkdir -p '/home/dietpi/.config/chromium/Default' +echo '{"intl":{"selected_languages":"'"${display_lang}"'"}}' \ + | sudo -u dietpi tee '/home/dietpi/.config/chromium/Default/Preferences' \ + > /dev/null + +####### +# app # +####### + +mkdir -p /opt/ovdashboard + +# compose file +curl \ + --proto "=https" --tlsv1.2 -sSf \ + --output "/opt/ovdashboard/docker-compose.yml" \ + "https://code.yavook.de/OEKZident.de/ovdashboard/raw/tag/v${ovd_version}/install/docker-compose.yml" +echo "Please review the Docker Compose file before continuing! [press Enter]" +read -r _ENTER +nano "/opt/ovdashboard/docker-compose.yml" + +# start server +docker compose \ + --file "/opt/ovdashboard/docker-compose.yml" \ + --project-directory "/opt/ovdashboard" \ + --project-name "ovdashboard" \ + up --detach From f00f95edd3b18fee8d853698275e390420ec087a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:55:40 +0100 Subject: [PATCH 07/20] adds to README --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8bc190d..96ba2f1 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ It is also heavily advisable that you log into your device using SSH, so you sho 1. Uncompress Image and flash onto SD card – you might need "7zip", "balenaEtcher" and/or other tools 1. Check the SD card, open "dietpi.txt" and change some options (full documentation [here](https://dietpi.com/docs/usage/#options-within-the-file)): - For WiFi, use `AUTO_SETUP_NET_WIFI_ENABLED=1` and `AUTO_SETUP_NET_WIFI_COUNTRY_CODE=DE`, and also check "dietpi-wifi.txt" - - System options, e.g. `AUTO_SETUP_AUTOMATED=1`, `AUTO_SETUP_NET_HOSTNAME=ovdashboard`, `AUTO_SETUP_GLOBAL_PASSWORD=dietpi`, `AUTO_SETUP_LOCALE=de_DE.UTF-8`, `AUTO_SETUP_KEYBOARD_LAYOUT=de`, `AUTO_SETUP_TIMEZONE=Europe/Berlin`, `CONFIG_SERIAL_CONSOLE_ENABLE=0` + - System options, e.g. `AUTO_SETUP_AUTOMATED=1`, `AUTO_SETUP_NET_HOSTNAME=OVDashboard`, `AUTO_SETUP_GLOBAL_PASSWORD=dietpi`, `AUTO_SETUP_LOCALE=de_DE.UTF-8`, `AUTO_SETUP_KEYBOARD_LAYOUT=de`, `AUTO_SETUP_TIMEZONE=Europe/Berlin`, `CONFIG_SERIAL_CONSOLE_ENABLE=0` 1. Be sure to at least change the password (and remember it 🙂️), then put the SD card into your device and boot it. Let the first time setup finish, it will take a bit. It will let you know when it is done! 1. Log into your device using SSH. By default, that's user name `root` and password `dietpi` @@ -76,14 +76,20 @@ This can all be done after logging into your prepared device: 1. `sh install.sh` - If you feel adventurous and do not want to review the script, just run `sh <( curl --proto '=https' --tlsv1.2 -sSf 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/install/install.sh' )` +> There will be some prompts during installation. +> +> - DietPi might ask if you want to change your GPU split. Choose **Yes**. +> - DietPi will ask if you want to configure autostart options. **Cancel** that, this choice does not matter as it is changed by the installer. +> - The installer will ask for the connected display's resolution. The default values should be fine, you can likely just hit **Return** here. +> - The installer will ask for "display languages". This determines internationalization on the kiosk (connected display). For German, enter `de-DE,de,en-US,en`. + Afterwards, reboot your device (`reboot` in the terminal). Your OVDashboard should be working now. For a better understanding of your newly created OVDashboard, refer to the [about section](#about-the-default-ovdashboard-deployment). ## Updates, upgrades - -`/opt/ovdashboard` + ## Configuration From 6e803739799b80f533583355ff8aae4356b85d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:09:11 +0100 Subject: [PATCH 08/20] use non-slim python image (hiredis compilation) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1dac049..84ccc4d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ############ ARG NODE_VERSION=lts -ARG PYTHON_VERSION=3.12-slim +ARG PYTHON_VERSION=3.12 FROM node:${NODE_VERSION} AS build-ui # env setup From b0a1c78384fb4384d256c8c1fad923a7d4032b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:45:04 +0100 Subject: [PATCH 09/20] script to check version --- install/chores/check_version | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 install/chores/check_version diff --git a/install/chores/check_version b/install/chores/check_version new file mode 100755 index 0000000..8cc8d8a --- /dev/null +++ b/install/chores/check_version @@ -0,0 +1,34 @@ +#!/bin/sh + +script="$( readlink -f "${0}" )" +script_dir="$( dirname "${script}" )" + +git_version="$( \ + git rev-parse --abbrev-ref HEAD \ + | cut -d '/' -f 2 +)" + +api_version="$( \ + grep '^version' "${script_dir}/../../api/pyproject.toml" \ + | sed -E 's/^version[^0-9]*((0|[1-9][0-9]*)[0-9\.]*[0-9]).*$/\1/' +)" + +ui_version="$( \ + python3 -c 'import sys, json; print(json.load(sys.stdin)["version"])' \ + < "${script_dir}/../../ui/package.json" \ +)" + +install_version="$( \ + grep '^ovd_version' "${script_dir}/../install.sh" \ + | sed -E 's/^ovd_version[^0-9]*((0|[1-9][0-9]*)[0-9\.]*[0-9]).*$/\1/' +)" + +if [ "${git_version}" = "${api_version}" ] \ +&& [ "${git_version}" = "${ui_version}" ] \ +&& [ "${git_version}" = "${install_version}" ]; then + mark="✅️" +else + mark="❌️" +fi + +echo "git: ${git_version}, api: ${api_version}, ui: ${ui_version}, installer: ${install_version} ${mark}" \ No newline at end of file From eea1e7880a656a676aaa4482ad0e6e4794e9119d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:15:31 +0100 Subject: [PATCH 10/20] rename "install" -> "deploy" --- Dockerfile | 2 +- README.md | 10 +++++----- {install => deploy}/chores/check_version | 4 +++- {install => deploy}/docker-compose.yml | 0 {install => deploy}/install.sh | 2 +- {install => deploy}/mini-tiangolo/gunicorn_conf.py | 0 {install => deploy}/mini-tiangolo/start.sh | 0 7 files changed, 10 insertions(+), 8 deletions(-) rename {install => deploy}/chores/check_version (91%) rename {install => deploy}/docker-compose.yml (100%) rename {install => deploy}/install.sh (98%) rename {install => deploy}/mini-tiangolo/gunicorn_conf.py (100%) rename {install => deploy}/mini-tiangolo/start.sh (100%) diff --git a/Dockerfile b/Dockerfile index 84ccc4d..7e340fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ LABEL maintainer="Sebastian Ramirez " WORKDIR /usr/local/share/uvicorn-gunicorn # install uvicorn-gunicorn -COPY "./install/mini-tiangolo/" "." +COPY "./deploy/mini-tiangolo/" "." RUN set -ex; \ chmod +x start.sh; \ diff --git a/README.md b/README.md index 96ba2f1..4470818 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ All that matters, at one glance!
*Date and Time – Upcoming Events – Public Announcements – News – Pictures* - **Easy Install**
-Set up a RaspberryPi, run the [installer script](./install/install.sh), done! +Set up a RaspberryPi, run the [installer script](./deploy/install.sh), done! - **DAV Server Interface**
Update your content anytime, from anywhere! @@ -54,7 +54,7 @@ It is also heavily advisable that you log into your device using SSH, so you sho ### Install Base System -`OVDashboard` is designed to run on a `DietPi` installation. Full installation documentation is available [at dietpi.com](https://dietpi.com/docs/install/). To quickly get up and running: +`OVDashboard` is designed to run on a `DietPi` installation. Full installation documentation is available [at dietpi.com](https://dietpi.com/docs/deploy/). To quickly get up and running: 1. Download Image from [dietpi.com/#download](https://dietpi.com/#download) 1. Uncompress Image and flash onto SD card – you might need "7zip", "balenaEtcher" and/or other tools @@ -66,15 +66,15 @@ It is also heavily advisable that you log into your device using SSH, so you sho ### Install OVDashboard -Download (and review) the [OVDashboard install script](//code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/install/install.sh), then run it from a terminal. +Download (and review) the [OVDashboard install script](//code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/deploy/install.sh), then run it from a terminal. This can all be done after logging into your prepared device: - The safe way: 1. `mkdir /tmp/ovdashboard && cd /tmp/ovdashboard` - 1. `wget 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/install/install.sh'` + 1. `wget 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/deploy/install.sh'` 1. `less install.sh` and/or edit with `nano install.sh` 1. `sh install.sh` -- If you feel adventurous and do not want to review the script, just run `sh <( curl --proto '=https' --tlsv1.2 -sSf 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/install/install.sh' )` +- If you feel adventurous and do not want to review the script, just run `sh <( curl --proto '=https' --tlsv1.2 -sSf 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/deploy/install.sh' )` > There will be some prompts during installation. > diff --git a/install/chores/check_version b/deploy/chores/check_version similarity index 91% rename from install/chores/check_version rename to deploy/chores/check_version index 8cc8d8a..2bca03b 100755 --- a/install/chores/check_version +++ b/deploy/chores/check_version @@ -31,4 +31,6 @@ else mark="❌️" fi -echo "git: ${git_version}, api: ${api_version}, ui: ${ui_version}, installer: ${install_version} ${mark}" \ No newline at end of file +echo "git: ${git_version}, api: ${api_version}, ui: ${ui_version}, installer: ${install_version} ${mark}" + +[ "${mark}" = "✅️" ] || exit 1 diff --git a/install/docker-compose.yml b/deploy/docker-compose.yml similarity index 100% rename from install/docker-compose.yml rename to deploy/docker-compose.yml diff --git a/install/install.sh b/deploy/install.sh similarity index 98% rename from install/install.sh rename to deploy/install.sh index a590ea0..25c8925 100644 --- a/install/install.sh +++ b/deploy/install.sh @@ -71,7 +71,7 @@ mkdir -p /opt/ovdashboard curl \ --proto "=https" --tlsv1.2 -sSf \ --output "/opt/ovdashboard/docker-compose.yml" \ - "https://code.yavook.de/OEKZident.de/ovdashboard/raw/tag/v${ovd_version}/install/docker-compose.yml" + "https://code.yavook.de/OEKZident.de/ovdashboard/raw/tag/v${ovd_version}/deploy/docker-compose.yml" echo "Please review the Docker Compose file before continuing! [press Enter]" read -r _ENTER nano "/opt/ovdashboard/docker-compose.yml" diff --git a/install/mini-tiangolo/gunicorn_conf.py b/deploy/mini-tiangolo/gunicorn_conf.py similarity index 100% rename from install/mini-tiangolo/gunicorn_conf.py rename to deploy/mini-tiangolo/gunicorn_conf.py diff --git a/install/mini-tiangolo/start.sh b/deploy/mini-tiangolo/start.sh similarity index 100% rename from install/mini-tiangolo/start.sh rename to deploy/mini-tiangolo/start.sh From 02bede84463c2f6f544a6c24cf508fbc5cc7739f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:17:02 +0100 Subject: [PATCH 11/20] "chore" script for docker buildx --- deploy/chores/docker_buildx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 deploy/chores/docker_buildx diff --git a/deploy/chores/docker_buildx b/deploy/chores/docker_buildx new file mode 100755 index 0000000..c4ff9a0 --- /dev/null +++ b/deploy/chores/docker_buildx @@ -0,0 +1,18 @@ +#!/bin/sh + +script="$( readlink -f "${0}" )" +script_dir="$( dirname "${script}" )" + +# shellcheck disable=SC1091 +. "${script_dir}/check_version" + +install_version="$( \ + grep '^ovd_version' "${script_dir}/../install.sh" \ + | sed -E 's/^ovd_version[^0-9]*((0|[1-9][0-9]*)[0-9\.]*[0-9]).*$/\1/' +)" + +docker buildx build \ + --pull --push \ + --tag "code.yavook.de/oekzident.de/ovdashboard:${install_version}" \ + --platform "linux/amd64,linux/arm64" \ + "${script_dir}/../.." From f27aa8b284ee8a9090571b1afcbb716bf4fa9a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:28:03 +0100 Subject: [PATCH 12/20] add compose_version to `check_version` --- deploy/chores/check_version | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/deploy/chores/check_version b/deploy/chores/check_version index 2bca03b..4560974 100755 --- a/deploy/chores/check_version +++ b/deploy/chores/check_version @@ -23,14 +23,21 @@ install_version="$( \ | sed -E 's/^ovd_version[^0-9]*((0|[1-9][0-9]*)[0-9\.]*[0-9]).*$/\1/' )" +compose_version="$( \ + grep 'image: code\.yavook\.de/oekzident\.de/ovdashboard' "${script_dir}/../docker-compose.yml" \ + | sed -E 's/.*code\.yavook\.de\/oekzident\.de\/ovdashboard[^0-9]*((0|[1-9][0-9]*)[0-9\.]*[0-9]).*$/\1/' +)" + if [ "${git_version}" = "${api_version}" ] \ && [ "${git_version}" = "${ui_version}" ] \ -&& [ "${git_version}" = "${install_version}" ]; then +&& [ "${git_version}" = "${install_version}" ] \ +&& [ "${git_version}" = "${compose_version}" ]; then mark="✅️" else mark="❌️" fi -echo "git: ${git_version}, api: ${api_version}, ui: ${ui_version}, installer: ${install_version} ${mark}" +echo "git: ${git_version}, api: ${api_version}, ui: ${ui_version}" +echo "installer: ${install_version}, compose: ${compose_version} => ${mark}" [ "${mark}" = "✅️" ] || exit 1 From 6d041e691811a55a6882c391ce9875b5c1ff26a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 16:17:32 +0100 Subject: [PATCH 13/20] amendments after integration test #2 --- README.md | 17 +++++++++-------- deploy/install.sh | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4470818..def0208 100644 --- a/README.md +++ b/README.md @@ -70,18 +70,19 @@ Download (and review) the [OVDashboard install script](//code.yavook.de/OEKZiden This can all be done after logging into your prepared device: - The safe way: - 1. `mkdir /tmp/ovdashboard && cd /tmp/ovdashboard` - 1. `wget 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/deploy/install.sh'` - 1. `less install.sh` and/or edit with `nano install.sh` - 1. `sh install.sh` + 1. download: `wget 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/deploy/install.sh'` + 1. read/edit: `nano install.sh` + 1. execute: `sh install.sh` - If you feel adventurous and do not want to review the script, just run `sh <( curl --proto '=https' --tlsv1.2 -sSf 'https://code.yavook.de/OEKZident.de/ovdashboard/raw/branch/master/deploy/install.sh' )` > There will be some prompts during installation. > -> - DietPi might ask if you want to change your GPU split. Choose **Yes**. -> - DietPi will ask if you want to configure autostart options. **Cancel** that, this choice does not matter as it is changed by the installer. -> - The installer will ask for the connected display's resolution. The default values should be fine, you can likely just hit **Return** here. -> - The installer will ask for "display languages". This determines internationalization on the kiosk (connected display). For German, enter `de-DE,de,en-US,en`. +> - DietPi might ask: "Would you like DietPi to apply the recommended GPU memory split?". Choose **Yes**. +> - DietPi will ask: "Would you like to configure the DietPi-AutoStart option?". **Cancel** that, this choice does not matter as it is changed by the installer. +> - DietPi will ask: "Would you like to join DietPi-Survey?". It's up to you, but I'd suggest to **opt OUT**. +> - The installer will ask for the connected screen's resolution. The default values should be fine, you can likely just hit **Return** here. +> - The installer will ask for "display languages". This will affect some details on the connected screen.
+ For German, enter `de-DE,de,en-US,en`. Afterwards, reboot your device (`reboot` in the terminal). Your OVDashboard should be working now. diff --git a/deploy/install.sh b/deploy/install.sh index 25c8925..c0f1bab 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -1,5 +1,7 @@ #!/bin/sh +# issue: autologin doesn't work ... + ######### # start # ######### From 525b711005052041275fbfdd5c3bbfd91fd070b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 23:17:56 +0100 Subject: [PATCH 14/20] amendments after integration test #3 --- deploy/install.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index c0f1bab..385fdbf 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -1,7 +1,5 @@ #!/bin/sh -# issue: autologin doesn't work ... - ######### # start # ######### @@ -48,9 +46,9 @@ screen_y="${screen_y_in:-$screen_y}" sed -ri "s/^(SOFTWARE_CHROMIUM_RES_Y=)[0-9]+$/\1${screen_y}/" '/boot/dietpi.txt' # chromium autostart -echo '11' > '/boot/dietpi/.dietpi-autostart_index' # magic number sed -ri "s/^(AUTO_SETUP_AUTOSTART_LOGIN_USER=).+$/\1dietpi/" '/boot/dietpi.txt' # run as "dietpi" sed -ri "s/^(SOFTWARE_CHROMIUM_AUTOSTART_URL=).+$/\1http:\/\/localhost\//" '/boot/dietpi.txt' # open "localhost" +/boot/dietpi/dietpi-autostart 11 # 11: magic number for chromium autostart # chromium language display_lang="en-US,en" From daa959bd147faeb22bc6b5891f54fb0178dec6a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Wed, 15 Nov 2023 23:25:32 +0100 Subject: [PATCH 15/20] check_version output format --- deploy/chores/check_version | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/chores/check_version b/deploy/chores/check_version index 4560974..19417c9 100755 --- a/deploy/chores/check_version +++ b/deploy/chores/check_version @@ -38,6 +38,7 @@ else fi echo "git: ${git_version}, api: ${api_version}, ui: ${ui_version}" -echo "installer: ${install_version}, compose: ${compose_version} => ${mark}" +echo "installer: ${install_version}, compose: ${compose_version}" +echo ">>>>> RESULT: ${mark} <<<<<" [ "${mark}" = "✅️" ] || exit 1 From 0db0e92b132f4d1d24771e9de929ca8309f20bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:55:02 +0100 Subject: [PATCH 16/20] optimization: image size --- api/ovdashboard_api/routers/v1/image.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/ovdashboard_api/routers/v1/image.py b/api/ovdashboard_api/routers/v1/image.py index e05f9d8..bbb6484 100644 --- a/api/ovdashboard_api/routers/v1/image.py +++ b/api/ovdashboard_api/routers/v1/image.py @@ -63,13 +63,23 @@ async def find_images_by_prefix( response_class=StreamingResponse, ) async def get_image_by_prefix( + cfg: Config = Depends(get_config), remote_path: str = Depends(RP_IMAGE), name: str = Depends(LM_IMAGE.getter.func), ) -> StreamingResponse: - cfg = await get_config() img = Image.open(BytesIO(await WebDAV.read_bytes(f"{remote_path}/{name}"))) img_buffer = BytesIO() + + width, height = img.size + target_height = cfg.image.height + target_width = int(width * target_height / height) + + img = img.resize( + size=(target_width, target_height), + resample=Image.LANCZOS, + ) + img.save(img_buffer, **cfg.image.save_params) img_buffer.seek(0) From 6b1fda1ffe07500c9a7e3a65beb898c0d4eb9567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:55:32 +0100 Subject: [PATCH 17/20] amendments after final integration test --- README.md | 2 +- deploy/install.sh | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index def0208..7730d66 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ It is also heavily advisable that you log into your device using SSH, so you sho 1. Download Image from [dietpi.com/#download](https://dietpi.com/#download) 1. Uncompress Image and flash onto SD card – you might need "7zip", "balenaEtcher" and/or other tools 1. Check the SD card, open "dietpi.txt" and change some options (full documentation [here](https://dietpi.com/docs/usage/#options-within-the-file)): - - For WiFi, use `AUTO_SETUP_NET_WIFI_ENABLED=1` and `AUTO_SETUP_NET_WIFI_COUNTRY_CODE=DE`, and also check "dietpi-wifi.txt" + - For WiFi, use `AUTO_SETUP_NET_WIFI_ENABLED=1` and `AUTO_SETUP_NET_WIFI_COUNTRY_CODE=DE`, and also edit "dietpi-wifi.txt" like `aWIFI_SSID[0]='OV WLAN'` and `aWIFI_KEY[0]='Strong_pa55w0rd'` - System options, e.g. `AUTO_SETUP_AUTOMATED=1`, `AUTO_SETUP_NET_HOSTNAME=OVDashboard`, `AUTO_SETUP_GLOBAL_PASSWORD=dietpi`, `AUTO_SETUP_LOCALE=de_DE.UTF-8`, `AUTO_SETUP_KEYBOARD_LAYOUT=de`, `AUTO_SETUP_TIMEZONE=Europe/Berlin`, `CONFIG_SERIAL_CONSOLE_ENABLE=0` 1. Be sure to at least change the password (and remember it 🙂️), then put the SD card into your device and boot it. Let the first time setup finish, it will take a bit. It will let you know when it is done! 1. Log into your device using SSH. By default, that's user name `root` and password `dietpi` diff --git a/deploy/install.sh b/deploy/install.sh index 385fdbf..95c2f63 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -82,3 +82,14 @@ docker compose \ --project-directory "/opt/ovdashboard" \ --project-name "ovdashboard" \ up --detach + +############ +# finalize # +############ + +echo "" +echo "#########################" +echo "# OVDashboard Installed #" +echo "#########################" +echo "" +echo "You can now reboot your device." From afbd9f4db8baf1e5fa8aa539a151dc2ebf88a053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:31:07 +0100 Subject: [PATCH 18/20] README overhaul --- README.md | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7730d66..bb5c0bd 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ It is also heavily advisable that you log into your device using SSH, so you sho ### Install Base System -`OVDashboard` is designed to run on a `DietPi` installation. Full installation documentation is available [at dietpi.com](https://dietpi.com/docs/deploy/). To quickly get up and running: +OVDashboard is designed to run on a `DietPi` installation. Full installation documentation is available [at dietpi.com](https://dietpi.com/docs/deploy/). To quickly get up and running: 1. Download Image from [dietpi.com/#download](https://dietpi.com/#download) 1. Uncompress Image and flash onto SD card – you might need "7zip", "balenaEtcher" and/or other tools @@ -83,19 +83,32 @@ This can all be done after logging into your prepared device: > - The installer will ask for the connected screen's resolution. The default values should be fine, you can likely just hit **Return** here. > - The installer will ask for "display languages". This will affect some details on the connected screen.
For German, enter `de-DE,de,en-US,en`. +> - The installer will ask you to "review the Docker Compose file" before starting the services. You will want to edit `WEBDAV__HOST`, `WEBDAV__USERNAME` and `WEBDAV__PASSWORD` at least.
+ Refer to [the "Settings"](TODO) for the full list of options. -Afterwards, reboot your device (`reboot` in the terminal). Your OVDashboard should be working now. +Afterwards, reboot your device (`reboot` in the terminal). + +If the install was successful, your OVDashboard should be showing up on the connected screen after rebooting. + +> You will also be able to view your OVDashboard on any webbrowser in your network using `http://`.
+> The device IP is displayed in the lower right region of the OVDashboard. For a better understanding of your newly created OVDashboard, refer to the [about section](#about-the-default-ovdashboard-deployment). -## Updates, upgrades - - - ## Configuration +### "Config" in your WebDAV share: `config.txt` + +### "Settings" on your Device: `/opt/ovdashboard/docker-compose.yml` + + + +## Updating your Device + + + ## Setup for development and contribution Refer to the specific README files for [the API](./api/README.md) and [the UI](./ui/README.md) to contribute to one of those sub-projects. @@ -103,17 +116,9 @@ Refer to the specific README files for [the API](./api/README.md) and [the UI](. ## About the "default" OVDashboard deployment -The default deployment is made up of three parts: The **installer** runs once, prepares your device for OVDashboard usage and deploys the **server**. +Running the installer script carries out the following actions: -The **server** will then run continuously and make OVDashboard available as a web application in your network. - -Also, a browser is installed to display the OVDashboard using your device. - - -### The OVDashboard installer will: - -- install and set up Docker, Docker Compose and Chromium-Browser -- create the OVDashboard Compose project at `/opt/ovdashboard` -- get a `docker` image from [`code.yavook.de`](https://code.yavook.de/OEKZident.de/-/packages/container/ovdashboard) containing the OVDashboard server -- set up your device to auto-run Chromium in "kiosk" mode to display the local OVDashboard -- auto-hide your mouse cursor +- install Chromium-Browser, Docker and Docker Compose +- create the OVDashboard project at `/opt/ovdashboard` +- start the OVDashboard project, this deploys the `ovdashboard` service from [`code.yavook.de`](https://code.yavook.de/OEKZident.de/-/packages/container/ovdashboard) and a [`redis` instance](https://redis.io/) to your device +- set up your device to auto-boot into Chromium "kiosk" mode to display the local OVDashboard From 30566fe449cc431175d1701df8923d9163032e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:32:02 +0100 Subject: [PATCH 19/20] deploy scripts QoL changes --- deploy/chores/docker_buildx | 9 ++++----- deploy/install.sh | 13 ++++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/deploy/chores/docker_buildx b/deploy/chores/docker_buildx index c4ff9a0..2f37620 100755 --- a/deploy/chores/docker_buildx +++ b/deploy/chores/docker_buildx @@ -6,13 +6,12 @@ script_dir="$( dirname "${script}" )" # shellcheck disable=SC1091 . "${script_dir}/check_version" -install_version="$( \ - grep '^ovd_version' "${script_dir}/../install.sh" \ - | sed -E 's/^ovd_version[^0-9]*((0|[1-9][0-9]*)[0-9\.]*[0-9]).*$/\1/' -)" +# defined in `check_version` script +# shellcheck disable=SC2154 +echo "${git_version}" >/dev/null docker buildx build \ --pull --push \ - --tag "code.yavook.de/oekzident.de/ovdashboard:${install_version}" \ + --tag "code.yavook.de/oekzident.de/ovdashboard:${git_version}" \ --platform "linux/amd64,linux/arm64" \ "${script_dir}/../.." diff --git a/deploy/install.sh b/deploy/install.sh index 95c2f63..0d5531b 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -67,20 +67,23 @@ echo '{"intl":{"selected_languages":"'"${display_lang}"'"}}' \ mkdir -p /opt/ovdashboard -# compose file +# prepare compose project curl \ --proto "=https" --tlsv1.2 -sSf \ --output "/opt/ovdashboard/docker-compose.yml" \ "https://code.yavook.de/OEKZident.de/ovdashboard/raw/tag/v${ovd_version}/deploy/docker-compose.yml" -echo "Please review the Docker Compose file before continuing! [press Enter]" -read -r _ENTER +docker compose \ + --project-directory "/opt/ovdashboard" \ + pull + +# review compose file +echo "Please review the Docker Compose file before continuing! [hit Return]" +read -r _RETURN nano "/opt/ovdashboard/docker-compose.yml" # start server docker compose \ - --file "/opt/ovdashboard/docker-compose.yml" \ --project-directory "/opt/ovdashboard" \ - --project-name "ovdashboard" \ up --detach ############ From 16215611958271eb33aab8f82f77b13f9b546ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn-Michael=20Miehe?= <40151420+ldericher@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:44:02 +0100 Subject: [PATCH 20/20] optimization: use slim python image variant - hiredis compilation fixed --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7e340fe..81daee6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ############ ARG NODE_VERSION=lts -ARG PYTHON_VERSION=3.12 +ARG PYTHON_VERSION=3.12-slim FROM node:${NODE_VERSION} AS build-ui # env setup @@ -57,6 +57,9 @@ RUN set -ex; \ export DEBIAN_FRONTEND=noninteractive; \ apt-get update; apt-get install --yes --no-install-recommends \ libmagic1 \ + # need to build hiredis + gcc \ + libc-dev \ ; rm -rf /var/lib/apt/lists/*; \ \ # install ovdashboard_api