From ddeb64cef53dc0d2a10478d0876a83c2da6fbab8 Mon Sep 17 00:00:00 2001 From: MrRaph_ Date: Wed, 6 Mar 2024 11:20:07 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=A6=20chore(devcontainer.json):=20add?= =?UTF-8?q?=20devcontainer=20configuration=20file=20for=20development=20en?= =?UTF-8?q?vironment=20setup=20=F0=9F=93=A6=20chore(setup.sh):=20add=20set?= =?UTF-8?q?up=20script=20to=20install=20pre-commit=20and=20configure=20it?= =?UTF-8?q?=20=F0=9F=93=84=20docs(README.md):=20update=20repository=20name?= =?UTF-8?q?=20and=20add=20description=20of=20custom=20Home=20Assistant=20a?= =?UTF-8?q?ddons=20=F0=9F=93=84=20docs(kresus/DOCS.md):=20add=20documentat?= =?UTF-8?q?ion=20for=20Kresus=20addon=20=F0=9F=90=B3=20feat(kresus):=20add?= =?UTF-8?q?=20Dockerfile=20and=20build.yaml=20for=20Kresus=20addon=20?= =?UTF-8?q?=F0=9F=94=A7=20chore(kresus):=20add=20configuration=20files=20f?= =?UTF-8?q?or=20Kresus=20addon=20=F0=9F=94=A7=20chore(kresus):=20add=20app?= =?UTF-8?q?armor=20profile=20for=20Kresus=20addon=20=F0=9F=94=A7=20chore(k?= =?UTF-8?q?resus):=20add=20changelog=20and=20docs=20for=20Kresus=20addon?= =?UTF-8?q?=20=F0=9F=94=A7=20chore(kresus):=20add=20icon=20and=20logo=20fo?= =?UTF-8?q?r=20Kresus=20addon=20=F0=9F=94=A7=20chore(kresus):=20add=20lice?= =?UTF-8?q?nse=20file=20for=20Kresus=20addon=20=F0=9F=94=A7=20chore(kresus?= =?UTF-8?q?):=20add=20README=20and=20requirements=20file=20for=20Kresus=20?= =?UTF-8?q?addon=20=F0=9F=94=A7=20chore(kresus):=20add=20service=20scripts?= =?UTF-8?q?=20for=20Kresus=20addon=20=F0=9F=94=A7=20chore(kresus):=20add?= =?UTF-8?q?=20translations=20for=20Kresus=20addon=20=F0=9F=94=A7=20chore(r?= =?UTF-8?q?epository.yaml):=20add=20repository=20information=20for=20MrRap?= =?UTF-8?q?h=5F's=20custom=20Home=20Assistant=20addons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 29 +++ .devcontainer/setup.sh | 5 + LICENSE | 21 ++ README.md | 8 +- kresus/CHANGELOG.md | 37 +++ kresus/DOCS.md | 54 +++++ kresus/Dockerfile | 108 +++++++++ kresus/README.md | 25 ++ kresus/apparmor.txt | 72 ++++++ kresus/build.yaml | 18 ++ kresus/config.yaml | 33 +++ kresus/icon.png | Bin 0 -> 8772 bytes kresus/logo.png | Bin 0 -> 8772 bytes kresus/rootfs/etc/kresus/config.ini | 270 +++++++++++++++++++++ kresus/rootfs/etc/services.d/kresus/finish | 15 ++ kresus/rootfs/etc/services.d/kresus/run | 25 ++ kresus/rootfs/requirements.txt | 1 + kresus/rootfs/usr/libexec/kresus/start.sh | 55 +++++ kresus/translations/en.yaml | 27 +++ repository.yaml | 3 + 20 files changed, 805 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/setup.sh create mode 100644 LICENSE create mode 100644 kresus/CHANGELOG.md create mode 100644 kresus/DOCS.md create mode 100644 kresus/Dockerfile create mode 100644 kresus/README.md create mode 100644 kresus/apparmor.txt create mode 100644 kresus/build.yaml create mode 100644 kresus/config.yaml create mode 100644 kresus/icon.png create mode 100644 kresus/logo.png create mode 100644 kresus/rootfs/etc/kresus/config.ini create mode 100755 kresus/rootfs/etc/services.d/kresus/finish create mode 100755 kresus/rootfs/etc/services.d/kresus/run create mode 100644 kresus/rootfs/requirements.txt create mode 100755 kresus/rootfs/usr/libexec/kresus/start.sh create mode 100644 kresus/translations/en.yaml create mode 100644 repository.yaml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..032f121 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +{ + "containerEnv": { + "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" + }, + "extensions": [ + "timonwong.shellcheck", + "esbenp.prettier-vscode" + ], + "image": "ghcr.io/home-assistant/devcontainer:addons", + "name": "Example Home Assistant add-on repository", + "postStartCommand": ".devcontainer/setup.sh", + "runArgs": [ + "-e", + "GIT_EDITOR=code --wait", + "--privileged" + ], + "settings": { + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true, + "terminal.integrated.defaultProfile.linux": "zsh", + "terminal.integrated.profiles.linux": { + "zsh": { + "path": "/usr/bin/zsh" + } + } + } +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 0000000..3f1eebd --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Install pre-commit +sudo apt install -y pre-commit +pre-commit install diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..421a859 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 MrRaph_ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 577d2cb..6f00410 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# hassio-addons \ No newline at end of file +# MrRaph_'s custom Home Assistant addons + +This repository defines a few custom addons for Home Assistant. Notably: + +- [Kresus](./kresus): sets-up a [Kresus](https://kresus.org/) instance which is a personal finances management software. + +These addons are published under [MIT license](./LICENSE). diff --git a/kresus/CHANGELOG.md b/kresus/CHANGELOG.md new file mode 100644 index 0000000..1e4983e --- /dev/null +++ b/kresus/CHANGELOG.md @@ -0,0 +1,37 @@ +## 0.6.2 + +- Update woob dependencies install method + +## 0.6.1 + +- Remove support for i386 architecture + +## 0.6.0 + +- Bump Kresus to 0.19.0 + +## 0.5.1 + +- Fix AppArmor profile + +## 0.5.0 + +- Attempt using `alpine:edge` as base image + +## 0.4.2 + +- Fix AppArmor profile + +## 0.4.1 + +- Reinstall py3-lxml by default + +## 0.4.0 + +- Remove python dependencies from Dockerfile to prevent + incompatibility with woob requirements + +## 0.3.0 + +- Use `s6-setuidgid` instead of `su-exec` +- Set AppArmor in complain mode diff --git a/kresus/DOCS.md b/kresus/DOCS.md new file mode 100644 index 0000000..a6484b0 --- /dev/null +++ b/kresus/DOCS.md @@ -0,0 +1,54 @@ +# Home Assistant Custom Add-on: Kresus + +[Kresus](https://kresus.org/) is an open-source self-hostable Personal Finance Manager. +It automatically retrieves your daily bank transactions, lets you categorize them and manage your monthly budgets. + +It relies on [woob](https://gitlab.com/woob/woob) to fetch data from your bank website. + +## Installation + +Kresus requires a PostgreSQL database to store data. +If you do not already have a PostgreSQL database installed, you may consider the [PostgreSQL addon](https://github.com/ezlo-picori/hassio-addons/tree/main/postgres). + +The installation of Kresus add-on is quite straightforward and do not differ from the standard installation process for Home-Assistant add-ons: + +1. Click the Home Assistant My button below to open the add-on on your Home + Assistant instance. + + [![Open this add-on in your Home Assistant instance.][addon-badge]][addon] + +1. Click the "Install" button to install the add-on. +1. Start the "Kresus" add-on. +1. Check the logs of the "Kresus" add-on to see if everything + went well. A working installation should indicate `Server is ready, let's start the show!` +1. Click the "OPEN WEB UI" button to open Kresus UI. + +## Configuration + +The only configuration configurations required are the database connection options. +If you used the [PostgreSQL add-on](https://github.com/ezlo-picori/hassio-addons/tree/main/postgres) configured with following options: + +```yaml +databases: + - kresus_db +logins: + - password: CHANGEME_kr3sus-p@ssword_CHANGEME + username: kresus_user +rights: + - database: kresus_db + username: kresus_user +``` + +then you may configure kresus with these options: + +```yaml +postgres_hostname: homeassistant.local +postgres_port: 5432 +postgres_user: kresus_user +postgres_password: CHANGEME_kr3sus-p@ssword_CHANGEME +postgres_database: kresus_db +``` + +## Authors & contributors + +The original setup of this repository is by [Ezlo Picori](https://github.com/ezlo-picori). diff --git a/kresus/Dockerfile b/kresus/Dockerfile new file mode 100644 index 0000000..c9fc360 --- /dev/null +++ b/kresus/Dockerfile @@ -0,0 +1,108 @@ +ARG BUILD_FROM +FROM $BUILD_FROM + +# Default ENV +ENV \ + LANG="C.UTF-8" \ + S6_BEHAVIOUR_IF_STAGE2_FAILS=2 \ + S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \ + S6_CMD_WAIT_FOR_SERVICES=1 \ + S6_SERVICES_READYTIME=50 + +# Set shell +SHELL ["/bin/ash", "-o", "pipefail", "-c"] + +# Build Args +ARG \ + BASHIO_VERSION \ + TEMPIO_VERSION \ + S6_OVERLAY_VERSION \ + JEMALLOC_VERSION \ + QEMU_CPU + +# Base system +WORKDIR /usr/src +ARG BUILD_ARCH + +RUN \ + set -x \ + && apk add --no-cache \ + bash \ + bind-tools \ + ca-certificates \ + curl \ + jq \ + tzdata \ + xz \ + \ + && apk add --no-cache --virtual .build-deps \ + build-base \ + autoconf \ + git \ + \ + && if [ "${BUILD_ARCH}" = "armv7" ]; then \ + export S6_ARCH="arm"; \ + elif [ "${BUILD_ARCH}" = "i386" ]; then \ + export S6_ARCH="i686"; \ + elif [ "${BUILD_ARCH}" = "amd64" ]; then \ + export S6_ARCH="x86_64"; \ + else \ + export S6_ARCH="${BUILD_ARCH}"; \ + fi \ + \ + && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" \ + | tar Jxvf - -C / \ + && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" \ + | tar Jxvf - -C / \ + && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-arch.tar.xz" \ + | tar Jxvf - -C / \ + && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-symlinks-noarch.tar.xz" \ + | tar Jxvf - -C / \ + && mkdir -p /etc/fix-attrs.d \ + && mkdir -p /etc/services.d \ + \ + && git clone "https://github.com/jemalloc/jemalloc" /usr/src/jemalloc \ + && cd /usr/src/jemalloc \ + && git checkout ${JEMALLOC_VERSION} \ + && ./autogen.sh \ + && make -j "$(nproc)" \ + && make install \ + \ + && mkdir -p /usr/src/bashio \ + && curl -L -f -s "https://github.com/hassio-addons/bashio/archive/v${BASHIO_VERSION}.tar.gz" \ + | tar -xzf - --strip 1 -C /usr/src/bashio \ + && mv /usr/src/bashio/lib /usr/lib/bashio \ + && ln -s /usr/lib/bashio/bashio /usr/bin/bashio \ + \ + && curl -L -f -s -o /usr/bin/tempio \ + "https://github.com/home-assistant/tempio/releases/download/${TEMPIO_VERSION}/tempio_${BUILD_ARCH}" \ + && chmod a+x /usr/bin/tempio \ + \ + && apk del .build-deps \ + && rm -rf /usr/src/* + +# S6-Overlay +WORKDIR / +ENTRYPOINT ["/init"] + +# Copy root filesystem +COPY rootfs / + +# Setup base +RUN apk add --no-cache \ + gcc g++ gpgv jpeg-dev pwgen python3-dev py3-lxml py3-pip \ + py3-wheel make nodejs npm zlib-dev && \ + pip install -r /requirements.txt && \ + npm install --omit=dev -g kresus@0.19.0 && \ + addgroup kresus && \ + adduser -G kresus -D -H kresus && \ + mkdir -p /woob && \ + chown kresus:kresus /woob && \ + chown -R kresus:kresus /etc/kresus && \ + chmod 0400 /etc/kresus/config.ini && \ + chmod 0755 /etc/kresus + +ENV KRESUS_DIR="/data/kresus" +ENV KRESUS_USER="kresus" + +WORKDIR / diff --git a/kresus/README.md b/kresus/README.md new file mode 100644 index 0000000..d20aef7 --- /dev/null +++ b/kresus/README.md @@ -0,0 +1,25 @@ +# Home Assistant Add-on: Kresus + +Open source personal accounting solution. + +![Supports aarch64 Architecture][aarch64-shield] +![Supports amd64 Architecture][amd64-shield] +![Supports armhf Architecture][armhf-shield] +![Supports armv7 Architecture][armv7-shield] +![Supports i386 Architecture][i386-shield] + +## About + +[Kresus][kresus] is a free solution for personal accounting management. +It automatically retrieves your daily bank transactions, lets you categorize them +and manage your monthly budgets. + +![Kresus Preview][screenshot] + +[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg +[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg +[armhf-shield]: https://img.shields.io/badge/armhf-yes-green.svg +[armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg +[i386-shield]: https://img.shields.io/badge/i386-no-red.svg +[screenshot]: https://kresus.org/images/pages/view-all-accounts.png +[kresus]: https://kresus.org/ diff --git a/kresus/apparmor.txt b/kresus/apparmor.txt new file mode 100644 index 0000000..44f634b --- /dev/null +++ b/kresus/apparmor.txt @@ -0,0 +1,72 @@ +include + +profile kresus flags=(attach_disconnected,mediate_deleted) { + #include + + # Capabilities + file, + signal (send) set=(kill,term,int,hup,cont), + capability chown, + capability fowner, + capability kill, + + # S6-Overlay + /init ix, + /bin/** ix, + /usr/bin/** ix, + /run/{s6,s6-rc*,service}/** ix, + /package/** ix, + /command/** ix, + /etc/services.d/** rwix, + /etc/cont-init.d/** rwix, + /etc/cont-finish.d/** rwix, + /run/{,**} rwk, + /dev/tty rw, + + # Access to options.json and other files within your addon + /data/options.json r, + /data/kresus/{,**} rw, + + /package/admin/s6-2.11.2.0/command/s6-applyuidgid cx -> s6setuidgid, + profile s6setuidgid flags=(attach_disconnected,mediate_deleted) { + #include + capability setuid, + capability setgid, + + signal (receive) set=("cont","kill","term"), + + # Generic accesses + /package/admin/s6-2.11.2.0/command/s6-applyuidgid rm, + + /bin/{bash,busybox} ix, + /dev/{null,tty} rw, + /etc/{group,hosts,os-release,passwd,resolv.conf,ssl/**} r, + /package/admin/** rmix, + /run/s6/container_environment** r, + /tmp/.bashio/{,**} rw, + /usr/bin/{curl,jq,ssl_client} rix, + /usr/lib/bashio/bashio ix, + /lib/** rmix, + /tmp/pip-install-** rw, + + # Kresus specific accesses + /data/kresus_salt r, + /data/kresus/{,**} rw, + /etc/kresus/config.ini r, + /woob/ r, + /woob/** lrw, + /woob/.py-deps/** lrwix, + + /usr/bin/{,**} r, + /usr/bin/git ix, + /usr/bin/gpgv ix, + /usr/bin/node ix, + /usr/bin/python3.11 ix, + /usr/bin/pip3 rix, + /usr/libexec/git-core/** ix, + /usr/libexec/kresus/** rix, + /usr/local/lib/node_modules/** rm, + /usr/local/lib/node_modules/kresus/bin/kresus.js rix, + /usr/share/** r, + } +} diff --git a/kresus/build.yaml b/kresus/build.yaml new file mode 100644 index 0000000..a953b79 --- /dev/null +++ b/kresus/build.yaml @@ -0,0 +1,18 @@ +build_from: + aarch64: "arm64v8/alpine:edge" + armv7: "arm32v7/alpine:edge" + armhf: "arm32v6/alpine:edge" + amd64: "alpine:edge" +labels: + org.opencontainers.image.title: "Home Assistant Add-on: Kresus" + org.opencontainers.image.description: "Kresus addon" + org.opencontainers.image.source: "https://github.com/ezlo-picori/hassio-addons" + org.opencontainers.image.licenses: "MIT License" + io.hass.base.name: alpine +codenotary: + signer: ezlo@protonmail.com +args: + BASHIO_VERSION: 0.14.3 + TEMPIO_VERSION: 2021.09.0 + S6_OVERLAY_VERSION: 3.1.3.0 + JEMALLOC_VERSION: 5.3.0 diff --git a/kresus/config.yaml b/kresus/config.yaml new file mode 100644 index 0000000..0a9d78a --- /dev/null +++ b/kresus/config.yaml @@ -0,0 +1,33 @@ +name: Kresus +version: "0.6.2" +slug: kresus +codenotary: ezlo@protonmail.com +url: "https://github.com/ezlo-picori/hassio-addons/tree/main/kresus" +description: Open source personal accounting management +arch: + - armhf + - armv7 + - aarch64 + - amd64 +image: "ghcr.io/ezlo-picori/haos-addon-kresus-{arch}" +init: false +options: + postgres_hostname: homeassistant.local + postgres_port: 5432 + postgres_user: kresus + postgres_password: null + postgres_database: kresus +panel_icon: mdi:piggy-bank-outline +ports: + 9876/tcp: 9876 +ports_description: + 9876/tcp: "The port to access kresus web server." +schema: + postgres_hostname: str + postgres_port: int + postgres_user: str + postgres_password: password + postgres_database: str + http_basicauth: str? +startup: system +timeout: 20 diff --git a/kresus/icon.png b/kresus/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..df04d0d3b35c63dc0c90ef2847620ee96ece908a GIT binary patch literal 8772 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:" +; Overriden by the KRESUS_AUTH environment variable, if it's set. +; Example: +; auth=foo:bar +auth= + +[woob] + +; The directory in which Woob core is stored. If empty, indicates +; that woob is already in the PYTHON_PATH (e.g. installed at the global +; level) +; Overriden by the KRESUS_WOOB_DIR environment variable, if it's set. +; Example: +; srcdir=/home/ben/code/woob +srcdir= + +; Path to a file containing a valid Woob's source list directory. +; If empty (the default), indicates that Kresus will generate its own +; source list file and will store it in +; KRESUS_DIR/woob-data/sources.list. +; Overriden by the KRESUS_WOOB_SOURCES_LIST environment variable, if it's set. +; Example: +; sources_list=/home/ben/code/woob/sources.list +sources_list= + +[email] + +; The transport method you want to use. Can be either: +; * "sendmail": relies on sendmail executable to be available on your +; system and only sendmail-specific parameters are used, +; +; * "smtp": you should provide proper SMTP credentials to use, in the +; dedicated configuration entries. +; +; If empty, no emails will be sent by Kresus. +; Overriden by the KRESUS_EMAIL_TRANSPORT environment variable, if it's set. +; Example: +; transport=smtp +transport= + +; The path to the sendmail executable to use. If empty, indicates +; that the default sendmail executable will be used. +; Overriden by the KRESUS_EMAIL_SENDMAIL_BIN environment variable, if it's set. +; Example: +; sendmail_bin=/usr/bin/sendmail +sendmail_bin= + +; The email address from which email alerts will be sent. Make sure +; that your domain DNS is correctly configured and that you've done +; what's needed to prevent email alerts from landing in the spam folder. +; Overriden by the KRESUS_EMAIL_FROM environment variable, if it's set. +; Example: +; from=kresus@domain.tld +from= + +; The network address (ipv4, ipv6 or FQDN) of the SMTP server. +; Overriden by the KRESUS_EMAIL_HOST environment variable, if it's set. +; Example: +; host=mail.domain.tld +host= + +; The port to which the SMTP server listens. Default values tend to +; be: 25 (server to server), or 587 (clients to server), or 465 +; (nonstandard). +; Overriden by the KRESUS_EMAIL_PORT environment variable, if it's set. +; Example: +; port=465 +port= + +; The username used during authentication to the SMTP server. If +; empty, indicates an anonymous connection will be used. +; Overriden by the KRESUS_EMAIL_USER environment variable, if it's set. +; Example: +; user=login +user= + +; The password used during authentication to the SMTP server. If +; empty, indicates no password will be used. +; Overriden by the KRESUS_EMAIL_PASSWORD environment variable, if it's set. +; Example: +; password=hunter2 +password= + +; If set to true, will force using a TLS connection. By default, +; emails are sent with STARTTLS, i.e. using TLS if available. +; Can be removed; defaults to false. +; Overriden by the KRESUS_EMAIL_FORCE_TLS environment variable, if it's set. +; Example: +; force_tls=false +force_tls= + +; If set to false, will allow self-signed TLS certificates. +; Can be removed; defaults to true. +; Overriden by the KRESUS_EMAIL_REJECT_UNAUTHORIZED_TLS environment variable, if it's set. +; Example: +; reject_unauthorized_tls=true +reject_unauthorized_tls= + +[notifications] + +; The baseurl from which apprise-api will be called for +; notifications to be sent. +; See https://github.com/caronc/apprise-api#installation for +; installation +; Overriden by the KRESUS_APPRISE_API_BASE_URL environment variable, if it's set. +; Example: +; appriseApiBaseUrl=http://localhost:8000/ +appriseApiBaseUrl= + +[logs] + +; The path to the log file to use. If empty, defaults to kresus.log +; in datadir. +; Overriden by the KRESUS_LOG_FILE environment variable, if it's set. +; Example: +; log_file=/var/log/kresus.log +log_file= + +[db] + +; Database type supported by Kresus, to choose among: +; - postgres +; - sqlite +; +; It must be set by the user. PostgreSQL is recommended and strongly supported; your experience with other databases might vary. +; +; Note using sqlite is *strongly discouraged* because it can't properly handle certain kinds of database migrations. It is only intended for development purposes. +; Overriden by the KRESUS_DB_TYPE environment variable, if it's set. +; Example: +; type=sqlite +type= + +; Logging level for the SQL queries. Possible values are: +; +; - all: will log every SQL query, including queries causing errors. +; - error (default value): will only log SQL queries resulting in errors. This is useful for debugging purposes. +; - none: nothing will be logged. +; Can be removed; defaults to error. +; Overriden by the KRESUS_DB_LOG environment variable, if it's set. +; Example: +; log=error +log= + +; Path to the sqlite database file. Make sure that the user running +; Kresus has the right permissions to write into this file. Required only for +; sqlite. +; Overriden by the KRESUS_DB_SQLITE_PATH environment variable, if it's set. +; Example: +; sqlite_path=/tmp/dev.sqlite +sqlite_path= + +; Path to a directory containing a Unix socket to connect to the +; database, or host address of the database server. Required for postgres. +; +; If using a Unix socket, the socket file's name will be inferred from the +; standard postgres name and the port number. +; Can be removed; defaults to localhost for postgres. +; Overriden by the KRESUS_DB_HOST environment variable, if it's set. +; Example: +; host=localhost +host= + +; Port of the database server. Required for postgres, even when +; using a Unix socket (the port is used to compute the socket's file name). +; Can be removed; defaults to 5432 for postgres. +; Overriden by the KRESUS_DB_PORT environment variable, if it's set. +; Example: +; port=5432 +port= + +; Username to connect to the database server. Required for postgres. +; Overriden by the KRESUS_DB_USERNAME environment variable, if it's set. +; Example: +; username=benjamin +username= + +; Password to connect to the database server. Required for postgres. +; Overriden by the KRESUS_DB_PASSWORD environment variable, if it's set. +; Example: +; password=hunter2 +password= + +; Database name to use. Required for postgres. +; Can be removed; defaults to kresus. +; Overriden by the KRESUS_DB_NAME environment variable, if it's set. +; Example: +; name=kresus +name= diff --git a/kresus/rootfs/etc/services.d/kresus/finish b/kresus/rootfs/etc/services.d/kresus/finish new file mode 100755 index 0000000..230d179 --- /dev/null +++ b/kresus/rootfs/etc/services.d/kresus/finish @@ -0,0 +1,15 @@ +#!/usr/bin/env bashio +# ============================================================================== +# Take down the S6 supervision tree when example fails +# s6-overlay docs: https://github.com/just-containers/s6-overlay +# ============================================================================== + +declare APP_EXIT_CODE=${1} + +if [[ "${APP_EXIT_CODE}" -ne 0 ]] && [[ "${APP_EXIT_CODE}" -ne 256 ]]; then + bashio::log.warning "Halt add-on with exit code ${APP_EXIT_CODE}" + echo "${APP_EXIT_CODE}" > /run/s6-linux-init-container-results/exitcode + exec /run/s6/basedir/bin/halt +fi + +bashio::log.info "Service restart after closing" diff --git a/kresus/rootfs/etc/services.d/kresus/run b/kresus/rootfs/etc/services.d/kresus/run new file mode 100755 index 0000000..d8bcbce --- /dev/null +++ b/kresus/rootfs/etc/services.d/kresus/run @@ -0,0 +1,25 @@ +#!/usr/bin/with-contenv bashio + +# ============================================================================== +# Init data directory +# ============================================================================== +if ! bashio::fs.directory_exists "${KRESUS_DIR}"; then + bashio::log.info "Create data directory \"${KRESUS_DIR}\"" + mkdir "${KRESUS_DIR}" + chown "${KRESUS_USER}:${KRESUS_USER}" "${KRESUS_DIR}" + chmod 0700 "${KRESUS_DIR}" +fi + +# ============================================================================== +# Generate Kresus salt once +# ============================================================================== +if ! bashio::fs.file_exists "/data/kresus_salt"; then + pwgen 32 1 > /data/kresus_salt + chown "${KRESUS_USER}:${KRESUS_USER}" /data/kresus_salt + chmod 400 /data/kresus_salt +fi + +# ============================================================================== +# Start service as unprivileged user +# ============================================================================== +exec s6-setuidgid "${KRESUS_USER}" /usr/libexec/kresus/start.sh diff --git a/kresus/rootfs/requirements.txt b/kresus/rootfs/requirements.txt new file mode 100644 index 0000000..40ff3e1 --- /dev/null +++ b/kresus/rootfs/requirements.txt @@ -0,0 +1 @@ +python-jose \ No newline at end of file diff --git a/kresus/rootfs/usr/libexec/kresus/start.sh b/kresus/rootfs/usr/libexec/kresus/start.sh new file mode 100755 index 0000000..a03d20d --- /dev/null +++ b/kresus/rootfs/usr/libexec/kresus/start.sh @@ -0,0 +1,55 @@ +#!/usr/bin/with-contenv bashio +WOOB_DIR="/woob" +KRESUS_INI_FILE="/etc/kresus/config.ini" + +# ============================================================================== +# Pull latest Woob version +# ============================================================================== +cd "${WOOB_DIR}" || bashio::exit.nok + +bashio::log.info "Clear woob install" +rm -rf {,.[!.],..?}* + +bashio::log.info "Add clean woob install" +wget -qO- https://gitlab.com/woob/woob/-/archive/master/woob-master.tar.gz | tar xz --strip-components=1 + +bashio::log.info "Updating Woob dependencies..." +pip3 install --no-cache-dir --prefix .py-deps . +PYTHONPATH=$(python3 -c "import sys, os; print(os.sep.join(['$(pwd)', '.py-deps', 'lib', f'python{sys.version_info.major}.{sys.version_info.minor}', 'site-packages']))") +export PYTHONPATH +bashio::log.info "Done updating Woob dependencies." + +# ============================================================================== +# Set-up environment variables +# ============================================================================== + +# Basic Kresus options +export PORT=9876 +export HOST=0.0.0.0 +export KRESUS_PYTHON_EXEC=python3 +export KRESUS_WOOB_DIR="${WOOB_DIR}" + +KRESUS_SALT="$(cat /data/kresus_salt)" +export KRESUS_SALT + +# Kresus Basic auth +if bashio::config.has_value 'http_basicauth'; then + KRESUS_AUTH="$(bashio::config 'http_basicauth')" + export KRESUS_AUTH +fi + +# Kresus database +export KRESUS_DB_TYPE="postgres" + +KRESUS_DB_HOST="$(bashio::config 'postgres_hostname')" +KRESUS_DB_PORT="$(bashio::config 'postgres_port')" +KRESUS_DB_USERNAME="$(bashio::config 'postgres_user')" +KRESUS_DB_PASSWORD="$(bashio::config 'postgres_password')" +KRESUS_DB_NAME="$(bashio::config 'postgres_database')" +export KRESUS_DB_HOST KRESUS_DB_PORT KRESUS_DB_USERNAME \ + KRESUS_DB_PASSWORD KRESUS_DB_NAME + +# ============================================================================== +# Start Kresus +# ============================================================================== +kresus -c ${KRESUS_INI_FILE} diff --git a/kresus/translations/en.yaml b/kresus/translations/en.yaml new file mode 100644 index 0000000..a9aa6a8 --- /dev/null +++ b/kresus/translations/en.yaml @@ -0,0 +1,27 @@ +configuration: + postgres_hostname: + name: "DB hostname" + description: >- + Hostname of the PostgreSQL database. + postgres_port: + name: "DB port" + description: >- + Port of the PostgreSQL database. + postgres_user: + name: "DB user" + description: >- + User to connect to the database. + postgres_password: + name: "DB password" + description: >- + Password of the database user. + postgres_database: + name: "DB database" + description: >- + Name of the kresus database. + http_basicauth: + name: "Authentication" + description: >- + Logins for HTTP basic auth authentication ("login:passwd" format, optional). +network: + 9876/tcp: Port to access kresus web server. diff --git a/repository.yaml b/repository.yaml new file mode 100644 index 0000000..be5d153 --- /dev/null +++ b/repository.yaml @@ -0,0 +1,3 @@ +name: MrRaph_'s custom Home Assistant addons +url: "https://github.com/MrRaph/hassio-addons" +maintainer: MrRaph_