<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><title>English on Hyteck</title><link href="https://hyteck.de/tags/english/"/><generator>Hugo -- gohugo.io</generator><language>en-us</language><id>https://hyteck.de/tags/english/</id><updated>2023-07-16T15:10:10+02:00</updated><link href="https://hyteck.de/tags/english/index.xml" rel="self" type="application/rss+xml"/><entry><title>Hosting static sites with Traefik</title><link href="https://hyteck.de/post/static-sites-with-mash/" type="application/octet-stream"/><updated>2023-07-16T15:10:10+02:00</updated><id>https://hyteck.de/post/static-sites-with-mash/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="hosting-static-sites-with-traefik-and-static-web-server">Hosting Static Sites with &lt;a href="https://traefik.io/">Traefik&lt;/a> and &lt;a href="https://static-web-server.net/features/docker/#run-a-container">Static Web Server&lt;/a>&lt;/h1>
&lt;p>Traefik is amazing to host complex services like with containers. On the other hand it&amp;rsquo;s harder than you&amp;rsquo;d think to host a simple static html site. I wanted to share my current approach that is based on &lt;a href="https://static-web-server.net/features/docker/#run-a-container">Static Web Server Project&lt;/a>.&lt;/p>
&lt;h2 id="static-web-server-sws">Static Web Server (SWS)&lt;/h2>
&lt;p>Static Web Server (or SWS abbreviated) is a simple and really fast web server with the goal to serve static web files or assets. The tiny docker image is only 4 MB with a small memory footprint. We can therefore afford to run a container for each static site.&lt;/p>
&lt;h2 id="architecture">Architecture&lt;/h2>
&lt;p>On the server we set up all static sites in one folder called &lt;code>static-sites&lt;/code>. As we run the SWS with &lt;a href="https://docs.docker.com/compose/">docker-compose&lt;/a> we add the &lt;code>docker-compose.yml&lt;/code> to this folder too. The following is an example setup with two static sites on seperate domains.&lt;/p>
&lt;pre tabindex="0">&lt;code>| static-sites
|
| docker-compose.yml
| - domain1.example.org
| - index.html
| - domain2.example.org
| - index.html
| - fonts
| - Open-Dyslexic.odf
&lt;/code>&lt;/pre>&lt;p>For each domain we add a folder. I like to name them by the domain name but this is not necessary (just remember the volume in the &lt;code>docker-compose.yml&lt;/code> too).&lt;/p>
&lt;p>With this done we can now fill the appropriate information in the &lt;code>docker-compose.yml&lt;/code>. Copy the following and replace &lt;code>domain1.example.org&lt;/code>, &lt;code>domain2.example.org&lt;/code>, &lt;code>site-one&lt;/code> and &lt;code>site-two&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">version&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;3.3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">services&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">domain1.example.org&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">image&lt;/span>: &lt;span style="color:#ae81ff">joseluisq/static-web-server:2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">container_name&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;domain1.example.org&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">environment&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Note: those envs are customizable but also optional&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">SERVER_PORT=8080&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">SERVER_ROOT=/public&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">SERVER_LOG_LEVEL=info&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">volumes&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">./domain1.example.org:/public&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">labels&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.enable=true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.docker.network=traefik&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-one.rule=Host(`domain1.example.org`)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-one.service=site-one&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-one.entrypoints=web-secure&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-one.tls=true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-one.tls.certResolver=default&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.services.site-one.loadbalancer.server.port=8080&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">networks&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">traefik&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">domain2.example.org&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">image&lt;/span>: &lt;span style="color:#ae81ff">joseluisq/static-web-server:2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">container_name&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;domain2.example.org&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">environment&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Note: those envs are customizable but also optional&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">SERVER_PORT=8080&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">SERVER_ROOT=/public&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">SERVER_LOG_LEVEL=info&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">volumes&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">./domain2.example.org:/public&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">labels&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.enable=true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.docker.network=traefik&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-two.rule=Host(`domain2.example.org`)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-two.service=site-two&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-two.entrypoints=web-secure&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-two.tls=true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.routers.site-two.tls.certResolver=default&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#e6db74">&amp;#34;traefik.http.services.site-two.loadbalancer.server.port=8080&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">networks&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">traefik&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">networks&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">traefik&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">traefik&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">external&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This assumes traefik runs in a docker-network called traefik. This network must already exist.&lt;/p>
&lt;p>As a last step add at least a &lt;code>index.html&lt;/code> in the appropriate folder. Then you can start the webserver with &lt;code>docker-compose up&lt;/code>. Add &lt;code>-d&lt;/code> to run it in the background.&lt;/p>
&lt;h1 id="deploying-static-sites">Deploying static sites&lt;/h1>
&lt;p>Deploying files manually (via Filezilla, scp or rsync) is not something I like to do. I therefore normally set up a CI job to automatically deploy the site when I push a new commit to GitHub, either via GitHub Actions or my &lt;a href="https://woodpecker-ci.org/docs/usage/intro">Woodpecker CI&lt;/a> instance.&lt;/p>
&lt;p>I order to do that I&lt;/p>
&lt;ul>
&lt;li>create a new user on the server, specifically for that purpose (one per site). The command is &lt;code>useradd USERNAME -m&lt;/code>&lt;/li>
&lt;li>create a SSH key without a password &lt;code>ssh-keygen -t ed25519 -a 100 -C &amp;quot;COMMENT&amp;quot; -f FILENAME&lt;/code>&lt;/li>
&lt;li>copy the public key that was just created at &lt;code>FILENAME.pub&lt;/code> on the server in the textfile &lt;code>/home/USERNAME/.ssh/authorized_keys&lt;/code>&lt;/li>
&lt;li>Add the private key to the secrets of your CI&lt;/li>
&lt;/ul>
&lt;p>A typical CI configuration will look like this with a static site and GitHub Actions&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">Deploy Production Website via SSH&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">on&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">push&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">branches&lt;/span>: [&lt;span style="color:#ae81ff">main]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">jobs&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">build&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">runs-on&lt;/span>: &lt;span style="color:#ae81ff">ubuntu-latest&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">steps&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">uses&lt;/span>: &lt;span style="color:#ae81ff">actions/checkout@v1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">Deploy to Server&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">uses&lt;/span>: &lt;span style="color:#ae81ff">easingthemes/ssh-deploy@main&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">env&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">SSH_PRIVATE_KEY&lt;/span>: &lt;span style="color:#ae81ff">${{ secrets.SSH_PRIVATE_KEY }}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ARGS&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;-rltgoDzvO --delete&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">SOURCE&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">REMOTE_HOST&lt;/span>: &lt;span style="color:#ae81ff">${{ secrets.REMOTE_HOST }}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">REMOTE_USER&lt;/span>: &lt;span style="color:#ae81ff">${{ secrets.REMOTE_USER }}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">TARGET&lt;/span>: &lt;span style="color:#ae81ff">${{ secrets.REMOTE_PRODUCTION_TARGET }}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">EXCLUDE&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;.github/, .gitignore&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>or this, when using &lt;a href="https://gohugo.io/">HUGO&lt;/a> and Woodpecker CI&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">pipeline&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">build&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">image&lt;/span>: &lt;span style="color:#ae81ff">klakegg/hugo &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">commands&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">hugo&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">deploy&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">image&lt;/span>: &lt;span style="color:#ae81ff">appleboy/drone-scp&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">settings&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">strip_components&lt;/span>: &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">host&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">example.org&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">username&lt;/span>: &lt;span style="color:#ae81ff">moanos&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">target&lt;/span>: &lt;span style="color:#ae81ff">/home/USERNAME/static-sites/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">source&lt;/span>: &lt;span style="color:#ae81ff">public/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">key&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">from_secret&lt;/span>: &lt;span style="color:#ae81ff">ssh_key&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>not manually put the files on the server&lt;/p></content></entry><entry><title>Moitoring IoT devices with MASH - Part 1</title><link href="https://hyteck.de/post/monitoring-iot-devices-with-mash/" type="application/octet-stream"/><updated>2023-07-07T15:10:10+02:00</updated><id>https://hyteck.de/post/monitoring-iot-devices-with-mash/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>This article is about monitoring some IoT devices (e.g. a CO2 sensor) with a combination of &lt;a href="https://mosquitto.org/">Mosquitto&lt;/a> (a MQTT broker), &lt;a href="https://www.influxdata.com/time-series-platform/telegraf/">Telegraf&lt;/a> (a metric collector), &lt;a href="https://www.influxdata.com/">InfluxDB&lt;/a> (a time-series database) and &lt;a href="grafana.com/">Grafana&lt;/a> (for displaying everything nicely).
All mentioned services should run on a server that can be reached from the monitoring device(s) and your PC where you want to check the data. We will use MASH (see below) to deploy the services.&lt;/p>
&lt;p>In the end this will enable you to get to something like this:&lt;/p>
&lt;p>&lt;img src="https://hyteck.de/uploads/monitoring-iot-devices-with-MASH/grafana_screenshot1.png" alt="A screenshot of a grafana dashboard. It shows one panel with temperature and humindity values over an hour, one panel showing CO2 data and one showing energy consumption">&lt;/p>
&lt;p>While writing, I decided to split this into two parts. Part 1 will set up our server infrastructure, Part 2 will show how to integrate some real sensors.&lt;/p>
&lt;p>Let&amp;rsquo;s get started with Part 1 and answer:&lt;/p>
&lt;h1 id="what-is-mash">What is MASH?&lt;/h1>
&lt;p>MASH is short for &amp;ldquo;Mother of all self-hosting&amp;rdquo;. It&amp;rsquo;s a collection of &lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/ansible.md">ansible&lt;/a> roles, tied together by a playbook that can help deploy and maintain a large number of services. It was inspired by the &lt;a href="https://github.com/spantaleev/matrix-docker-ansible-deploy/">matrix-docker-ansible-deploy&lt;/a> follows the same philosophy and is maintained partly by the same people.&lt;/p>
&lt;p>MASH includes services like&lt;/p>
&lt;ul>
&lt;li>Nextcloud&lt;/li>
&lt;li>Authentik&lt;/li>
&lt;li>Gitea&lt;/li>
&lt;li>Peertube&lt;/li>
&lt;li>Funkwhale&lt;/li>
&lt;/ul>
&lt;p>and more than 50 others at time of writing this blogpost (&lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/supported-services.md">List of all supported services&lt;/a>).&lt;/p>
&lt;p>The motivation for this playbook is to have all services run in docker, be as configurable as possible, while maintaining easy upgrades and reproducible deployments.
The documentation IMHO is very good, so if people are willing to use ansible this might be a lot easier than writing roles themself.&lt;/p>
&lt;h1 id="architecture">Architecture&lt;/h1>
&lt;p>In the end we will have a IoT device that sends data to a MQTT topic on the MQTT broker Mosquitto. Telegraf will listen to this topic and write this into InfluxDB. When you want to have a look at the data, you can access Grafan which will query InfluxDB.&lt;/p>
&lt;p>&lt;img src="https://hyteck.de/uploads/monitoring-iot-devices-with-MASH/mash-monitoring-mqtt.png" alt="A schema showing a setup where an IoT sensor sends data via MQTT to an MQTT broker. Telegraf subscribes to this broker and writes the data into InfluxDB. Grafana reads from InfluxDB when you want to view the data">&lt;/p>
&lt;h1 id="prerequisits">Prerequisits&lt;/h1>
&lt;p>To have everything it needs to use MASH you should ensure the following things are done and working&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/prerequisites.md">Prerequisits&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/getting-the-playbook.md">Getting the playbook&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Configure DNS&lt;/strong>&lt;/p>
&lt;p>By following this guide we will set up some services which will be reachable from via web interface (Grafana and InfluxDB) while others will not (Telegraf, Mosquitto). We will set a domain name for the server itself, one for Grafana and one for InfluxDB. This allows you to move services more easily. Feel free to adjust the setup to your needs (but don&amp;rsquo;t forget to change tha &lt;code>_hostname&lt;/code> variables accordingly later).&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Service&lt;/th>
&lt;th>Domain&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Target&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Server general&lt;/td>
&lt;td>s0.example.com&lt;/td>
&lt;td>A&lt;/td>
&lt;td>&lt;code>&amp;lt;IPv4-IP&amp;gt;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Server general&lt;/td>
&lt;td>s0.example.com&lt;/td>
&lt;td>AAAA&lt;/td>
&lt;td>&lt;code>&amp;lt;IPv6-IP&amp;gt;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Grafana&lt;/td>
&lt;td>grafana.example.com&lt;/td>
&lt;td>CNAME&lt;/td>
&lt;td>s0.example.com&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>InfluxDB&lt;/td>
&lt;td>influxdb.example.com&lt;/td>
&lt;td>CNAME&lt;/td>
&lt;td>s0.example.com&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h1 id="set-up-the-services">Set up the services&lt;/h1>
&lt;p>Setting up the services will require three steps. The first will set up the general server with Mosquitto and Influxdb. Then we configure mosquitto and influxdb and use this to set up Telegraf and Grafana.&lt;/p>
&lt;h2 id="setting-up-the-basis-mosquitto-and-influxdb">Setting up the basis, Mosquitto and InfluxDB&lt;/h2>
&lt;p>Setting up will be based on one configuration file of the playbook. &lt;code>&amp;lt;your-domain&amp;gt;&lt;/code> is the one you set up as Server General (&lt;code>s0.example.com&lt;/code>).&lt;/p>
&lt;p>Execute the following these steps inside the playbook directory:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>create a directory to hold your configuration (&lt;code>mkdir -p inventory/host_vars/&amp;lt;your-domain&amp;gt;&lt;/code>)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>copy the sample configuration file (&lt;code>cp examples/vars.yml inventory/host_vars/&amp;lt;your-domain&amp;gt;/vars.yml&lt;/code>)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>copy the sample inventory hosts file (&lt;code>cp examples/hosts inventory/hosts&lt;/code>)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>edit the inventory hosts file (&lt;code>inventory/hosts&lt;/code>) to your liking&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Now you are ready to modify the main configuration file at&lt;code>inventory/host_vars/&amp;lt;your-domain&amp;gt;/vars.yml&lt;/code>.&lt;/p>
&lt;p>Add the following to your playbook and replace the default values (&lt;code>IN_CAPS&lt;/code>)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## Basics ##&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Put a strong secret below, generated with `pwgen -s 64 1` or in another way&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Various other secrets will be derived from this secret automatically.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">mash_playbook_generic_secret_key&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">mash_playbook_docker_installation_enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">devture_docker_sdk_for_python_installation_enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># To ensure the server&amp;#39;s clock is synchronized (using systemd-timesyncd/ntpd),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># we enable the timesync service.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">devture_timesync_installation_enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## traefik ##&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Traefik will be our revers proxy that makes grafana and influxdb accessible from the outside. It will automatically obtain SSL certificates for us&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">mash_playbook_reverse_proxy_type&lt;/span>: &lt;span style="color:#ae81ff">playbook-managed-traefik&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># The E-Mail address that traefik will use to obtain certificates with&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">devture_traefik_config_certificatesResolvers_acme_email&lt;/span>: &lt;span style="color:#ae81ff">certs@example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">##############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## influxdb ##&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">##############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">influxdb_enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">influxdb_hostname&lt;/span>: &lt;span style="color:#ae81ff">influxdb.example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">influxdb_init&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">influxdb_init_username&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;USERNAME&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">influxdb_init_password&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;SECURE-PASSWORD&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">influxdb_init_org&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;YOUR-ORG&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">influxdb_init_bucket&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;monitoring&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#################&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## Mosquitto ##&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#################&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">mosquitto_enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Installing&lt;/strong>&lt;/p>
&lt;p>Before installing and each time you update the playbook in the future, you will need to update the Ansible roles in this playbook by running &lt;code>just roles&lt;/code>. &lt;code>just roles&lt;/code> is a shortcut to download the latest Ansible roles. If you don&amp;rsquo;t have &lt;code>just&lt;/code>, you can also manually run the &lt;code>roles&lt;/code> commands seen in the &lt;code>justfile&lt;/code> (this gets tedious fast).&lt;/p>
&lt;p>To install you should can &lt;code>just install-all&lt;/code>. That&amp;rsquo;s it.&lt;/p>
&lt;p>Congratulations! You installed your first services and can now visit influxdb.example.com and login with the credentials you set above!&lt;/p>
&lt;h2 id="configuring-mosquitto-and-influxdb">Configuring Mosquitto and InfluxDB&lt;/h2>
&lt;p>&lt;strong>Mosquitto&lt;/strong>&lt;/p>
&lt;p>To configure mosquitto you only need to set up users. I would recommend to set up seperate users for telegraf and your IoT devices as you might want to restrict the IoT users permissions (not covered here).&lt;/p>
&lt;p>Setting up mosquitto users can be done &lt;code>just run-tags mosquitto-add-user --extra-vars=username=&amp;lt;username&amp;gt; --extra-vars=password=&amp;lt;password&amp;gt;&lt;/code>. For the setting to take effect, you must restart the container. To do that you can use &lt;code>just start-group mosquitto&lt;/code>.&lt;/p>
&lt;p>&lt;strong>InfluxDB&lt;/strong>&lt;/p>
&lt;p>Log in on influxdb.example.com with the credentials you configured in your &lt;code>vars.yml&lt;/code>.&lt;/p>
&lt;p>We will now create a configuration that will automatically be read by telegraf.&lt;/p>
&lt;p>Got to &lt;code>Load Data -&amp;gt; Telegraf&lt;/code> and press &lt;code>Create configuration&lt;/code>. Choose the bucket monitoring and search for the MQTT consumer as data source.&lt;/p>
&lt;p>Add the following replace the &lt;code>[[inputs.mqtt_consumer]]&lt;/code> with the following configuration (and make sure that you replace the example values).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-toml" data-lang="toml">&lt;span style="display:flex;">&lt;span>[[&lt;span style="color:#a6e22e">inputs&lt;/span>.&lt;span style="color:#a6e22e">mqtt_consumer&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">servers&lt;/span> = [&lt;span style="color:#e6db74">&amp;#34;tcp://s0.example.com:1883&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">## Topics that will be subscribed to.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">topics&lt;/span> = [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;sensors/#&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">data_format&lt;/span> = &lt;span style="color:#e6db74">&amp;#34;value&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">data_type&lt;/span> = &lt;span style="color:#e6db74">&amp;#34;float&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">username&lt;/span> = &lt;span style="color:#e6db74">&amp;#34;USERNAME_SET_WHEN_CONFIGURING_MOSQUITTO&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">password&lt;/span> = &lt;span style="color:#e6db74">&amp;#34;PASSWORD_SET_WHEN_CONFIGURING_MOSQUITTO&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now got to &lt;code>Setup Instructions&lt;/code> and copy the &lt;code>INFLUX_TOKEN&lt;/code>. It will only show once! Also copy the config URL. You will need both in the following step.&lt;/p>
&lt;h2 id="setting-up-telegraf-and-grafana">Setting up Telegraf and Grafana&lt;/h2>
&lt;p>You now have everything for the last step: Setting up Telegraf and grafana. We did not do this in the first step as wee needed the access tokes/configuration link which we have now. Therefore you should now add two new sections to your &lt;code>vars.yml&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">##############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## telegraf ##&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">##############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">telegraf_enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">telegraf_influx_token&lt;/span>: &lt;span style="color:#ae81ff">TOKEN-YOU-GOT-FROM-INFLUX&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">telegraf_config_link&lt;/span>: &lt;span style="color:#ae81ff">https://influxdb.example.com/api/v2/telegrafs/0b6d11ca6bb1b000&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## grafana ##&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#############&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">grafana_enabled&lt;/span>: &lt;span style="color:#66d9ef">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">grafana_hostname&lt;/span>: &lt;span style="color:#ae81ff">grafana.example.com&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">grafana_default_admin_user&lt;/span>: &lt;span style="color:#ae81ff">USERNAME&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">grafana_default_admin_password&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;SECURE-PASSWORD&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">grafana_provisioning_datasources&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#f92672">name&lt;/span>: &lt;span style="color:#ae81ff">InfluxDBs3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">type&lt;/span>: &lt;span style="color:#ae81ff">influxdb&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">access&lt;/span>: &lt;span style="color:#ae81ff">proxy&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">url&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;https://{{ influxdb_hostname }}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">jsonData&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">version&lt;/span>: &lt;span style="color:#ae81ff">Flux&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">organization&lt;/span>: &lt;span style="color:#ae81ff">YOUR-ORG&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">defaultBucket&lt;/span>: &lt;span style="color:#ae81ff">monitoring&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">secureJsonData&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">token&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;TOKEN-YOU-GOT-FROM-INFLUXDB&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After that, once again do &lt;code>just install-all&lt;/code>. You should now have a working setup of all services. Now&lt;/p>
&lt;h1 id="lets-put-some-data-in-and-display-it">Let&amp;rsquo;s put some data in and display it&lt;/h1>
&lt;p>To send some data that you can display you can use the following python script. Save it as &lt;code>cli.py&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> argparse
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> paho.mqtt.client &lt;span style="color:#66d9ef">as&lt;/span> mqtt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> random
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> numpy &lt;span style="color:#66d9ef">as&lt;/span> np
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> time
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">gen_time&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> time&lt;span style="color:#f92672">.&lt;/span>strftime(&lt;span style="color:#e6db74">&amp;#34;%H:%M:%S&amp;#34;&lt;/span>, time&lt;span style="color:#f92672">.&lt;/span>localtime())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">gen_temp&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> amplitude: amplitude of temperature changes
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> mean: mean of the simulated temperature
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> offset: offset of the signal in seconds
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> period: periond time in seconds
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> amplitude &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">5&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mean &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> offset &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> period &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">900&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> temp &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>sin((time&lt;span style="color:#f92672">.&lt;/span>time() &lt;span style="color:#f92672">+&lt;/span> offset) &lt;span style="color:#f92672">%&lt;/span> (period) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#f92672">*&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>pi) &lt;span style="color:#f92672">*&lt;/span> amplitude &lt;span style="color:#f92672">+&lt;/span> mean
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> temp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">fake&lt;/span>(client, data_type&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;temperature&amp;#34;&lt;/span>, topic&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;sensors/temperature&amp;#34;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> data_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;temperature&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data_generator &lt;span style="color:#f92672">=&lt;/span> gen_temp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> data_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;time&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data_generator &lt;span style="color:#f92672">=&lt;/span> gen_time
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Publishing&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> data_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;temperature&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>publish(topic, gen_temp())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> data_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;time&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>publish(topic, gen_time())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> time&lt;span style="color:#f92672">.&lt;/span>sleep(&lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">on_message&lt;/span>(client, userdata, msg):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(msg&lt;span style="color:#f92672">.&lt;/span>topic &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(msg&lt;span style="color:#f92672">.&lt;/span>payload))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">on_connect&lt;/span>(client, userdata, flags, rc):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Connected with result code &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(rc))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> rc &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">5&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">raise&lt;/span> &lt;span style="color:#a6e22e">ConnectionError&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;MQTT server refused connection&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">connect_broker&lt;/span>(server, port, username&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>, password&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#39;python-mqtt-&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>random&lt;span style="color:#f92672">.&lt;/span>randint(&lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">1000&lt;/span>)&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client &lt;span style="color:#f92672">=&lt;/span> mqtt&lt;span style="color:#f92672">.&lt;/span>Client(client_id)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>on_connect &lt;span style="color:#f92672">=&lt;/span> on_connect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>username_pw_set(username&lt;span style="color:#f92672">=&lt;/span>username,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> password&lt;span style="color:#f92672">=&lt;/span>password)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>connect(server, port, &lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> client
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">cli&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser &lt;span style="color:#f92672">=&lt;/span> argparse&lt;span style="color:#f92672">.&lt;/span>ArgumentParser(description&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;Do basic MQTT operations&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;action&amp;#39;&lt;/span>, choices&lt;span style="color:#f92672">=&lt;/span>[&lt;span style="color:#e6db74">&amp;#39;sub&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;pub&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;fake&amp;#39;&lt;/span>], help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;-t&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;--topic&amp;#39;&lt;/span>, help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;The MQTT topic&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--type&amp;#39;&lt;/span>, help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;The type of data to fake&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;-b&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;--broker&amp;#39;&lt;/span>, help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Hostname of the MQTT broker&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;--port&amp;#39;&lt;/span>, type&lt;span style="color:#f92672">=&lt;/span>int, default&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1883&lt;/span>, help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;The MQTT brokers port (default: 1883)&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;-u&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;--user&amp;#39;&lt;/span>, help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;User for the MQTT broker&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;-p&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;--password&amp;#39;&lt;/span>, help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Password of the MQTT broker&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parser&lt;span style="color:#f92672">.&lt;/span>add_argument(&lt;span style="color:#e6db74">&amp;#39;-d&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;--payload&amp;#39;&lt;/span>, help&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;The payload to send with a set command&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> args &lt;span style="color:#f92672">=&lt;/span> parser&lt;span style="color:#f92672">.&lt;/span>parse_args()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client &lt;span style="color:#f92672">=&lt;/span> connect_broker(args&lt;span style="color:#f92672">.&lt;/span>broker, args&lt;span style="color:#f92672">.&lt;/span>port, args&lt;span style="color:#f92672">.&lt;/span>user, args&lt;span style="color:#f92672">.&lt;/span>password)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> args&lt;span style="color:#f92672">.&lt;/span>action &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;sub&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>subscribe(args&lt;span style="color:#f92672">.&lt;/span>topic)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>on_message &lt;span style="color:#f92672">=&lt;/span> on_message
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>loop_forever()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> args&lt;span style="color:#f92672">.&lt;/span>action &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;pub&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> r &lt;span style="color:#f92672">=&lt;/span> client&lt;span style="color:#f92672">.&lt;/span>publish(args&lt;span style="color:#f92672">.&lt;/span>topic, payload&lt;span style="color:#f92672">=&lt;/span>args&lt;span style="color:#f92672">.&lt;/span>payload)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(r)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> args&lt;span style="color:#f92672">.&lt;/span>action &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;fake&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fake(client, args&lt;span style="color:#f92672">.&lt;/span>topic)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cli()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Start the script with&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sh" data-lang="sh">&lt;span style="display:flex;">&lt;span>python cli.py fake -t sensors/temperature -b s0.example.com -u USERNAME -p SECURE_PASSWORD&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will fake a temperature sensor and publish the results to the MQTT topic sensors/temperature. To make sure the MQTT broker works correctly you can subscribe with&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>python cli.py sub -t sensors/temperature -b stats.hyteck.de -u qzt -p fisch-salz-hof
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now let&amp;rsquo;s display this data in Grafana. Add a new dashboard and then use &lt;code>Add-&amp;gt;Visualization&lt;/code>.&lt;/p>
&lt;p>&lt;img src="https://hyteck.de/uploads/monitoring-iot-devices-with-MASH/grafana_new_query.jpeg" alt="Screenshot of the grafana interface with numbers indicating where to click for the following sentence">
In the panel select InfluxDB as datasource (1+2) put in the folowing query (3).&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-flux" data-lang="flux">from(bucket: &amp;#34;monitoring&amp;#34;)
|&amp;gt; range(start: v.timeRangeStart, stop: v.timeRangeStop)
|&amp;gt; filter(fn: (r) =&amp;gt; r[&amp;#34;_measurement&amp;#34;] == &amp;#34;mqtt_consumer&amp;#34;)
|&amp;gt; filter(fn: (r) =&amp;gt; r[&amp;#34;_field&amp;#34;] == &amp;#34;value&amp;#34;)
|&amp;gt; filter(fn: (r) =&amp;gt; r[&amp;#34;topic&amp;#34;] == &amp;#34;sensors/temperature&amp;#34;)
|&amp;gt; group(columns: [&amp;#34;topic&amp;#34;])
|&amp;gt; aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|&amp;gt; yield(name: &amp;#34;mean&amp;#34;)
&lt;/code>&lt;/pre>&lt;p>add a nice title (4) and apply to see tha data (5). You should now have a nice display of your fake sensor!&lt;/p>
&lt;h1 id="some-last-words">Some last words&lt;/h1>
&lt;p>Please let me know when you follow this guide - wheter sucessful or not! I would love to improve it.&lt;/p>
&lt;p>I know this seems like a lot of effort to get a few numbers to display a few numbers nicely. On the other hands, once set up this setup can do a lot of stuff, from monitoring, to alerting. Upgrades should be taken care of by MASH, check out how to do that &lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/maintenance-upgrading-services.md">here&lt;/a>. Also setting up more services is a breeze. Either check the &lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/supported-services.md">list of existing services&lt;/a>, or add a new role to the playbook. If you don&amp;rsquo;t know how, I am happy to help!&lt;/p>
&lt;p>So make sure to follow my &lt;a href="https://hyteck.de/index.xml">RSS feed&lt;/a> or any of my socials to get notified when Part 2 comes around.&lt;/p>
&lt;h1 id="troubleshooting">Troubleshooting&lt;/h1>
&lt;p>When something doesn&amp;rsquo;t work feel free to&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Consult the docs&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/services/mosquitto.md">Mosquitto&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/services/telegraf.md">Telegraf&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/services/influxdb.md">InfluxDB&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/services/grafana.md">Grafana&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://hyteck.de/about/#contact">Message me&lt;/a>&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ul></content></entry><entry><title>Raspberry Pi as Offsite Backup</title><link href="https://hyteck.de/post/raspi-backup/" type="application/octet-stream"/><updated>2022-10-23T10:12:54+02:00</updated><id>https://hyteck.de/post/raspi-backup/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="use-case">Use Case&lt;/h1>
&lt;p>You have one (or more) servers at a hosting provider and a raspberry pi at home. You want to have an offsite backup of the websites, apps and databases at home.&lt;/p>
&lt;h1 id="prerequesits">Prerequesits&lt;/h1>
&lt;p>You configure your raspberry pi to be reachable from &lt;em>the internet&lt;/em> using DynDNS. In the following we assume that it is reachable at offsite.example.com.&lt;/p>
&lt;h1 id="preparing-your-backup-raspberry-pi">Preparing your backup raspberry pi&lt;/h1>
&lt;p>We want to make sure that backups on the raspberry pi can come from multiple sources and one source can not delete another.&lt;/p>
&lt;h2 id="create-an-additional-user">Create an additional user&lt;/h2>
&lt;p>and change to that user afterwards. You can change service1 to the name of the service that this user should backup.&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo useradd service1_backup
sudo su service1_backup
cd ~
&lt;/code>&lt;/pre>&lt;h2 id="create-an-ssh-key-for-the-user">Create an SSH key for the user&lt;/h2>
&lt;p>This SSH key will later be used by your server to push backups automatically. Therefore you should not set a passphrase for the key (just press enter until the key is generated)&lt;/p>
&lt;pre tabindex="0">&lt;code>$ ssh-keygen -t ed25519
&lt;/code>&lt;/pre>&lt;h2 id="create-your-backup-directory">Create your backup directory&lt;/h2>
&lt;pre tabindex="0">&lt;code>mkdir backup &amp;amp;&amp;amp; cd backup
&lt;/code>&lt;/pre>&lt;p>If you want to use an external drive you can mount it to this users home directory.&lt;/p>
&lt;h2 id="initialzie-the-borg-repository">Initialzie the borg repository&lt;/h2>
&lt;pre tabindex="0">&lt;code>borg init --encryption=repokey ./
&lt;/code>&lt;/pre>&lt;p>Make sure to set a strong passphrase and note it down somewhere safe. Without it you will not be able to access you backup!&lt;/p>
&lt;h2 id="make-sure-the-user-can-only-access-the-backup-directory">Make sure the user can only access the backup directory&lt;/h2>
&lt;p>Put the following in &lt;code>~/.ssh/authorized_keys&lt;/code> and make sure everything is in one line. The last values are simply your public key that can be found in &lt;code>~/.ssh/id_ed25519.pub&lt;/code>&lt;/p>
&lt;pre tabindex="0">&lt;code>command=&amp;#34;borg serve --restrict-to-repository /home/&amp;lt;user&amp;gt;/backup&amp;#34;,restrict &amp;lt;key type&amp;gt; &amp;lt;key&amp;gt; &amp;lt;key host&amp;gt;
&lt;/code>&lt;/pre>&lt;p>&lt;strong>Done with the raspberry pi&lt;/strong>&lt;/p>
&lt;h1 id="configure-your-server">Configure your server&lt;/h1>
&lt;p>In this guide we will use &lt;a href="https://torsion.org/borgmatic/">borgmatic&lt;/a> to configure and automatically run the backup in the server.&lt;/p>
&lt;h2 id="install-borgmatic">Install borgmatic&lt;/h2>
&lt;pre tabindex="0">&lt;code>sudo pip3 install --user --upgrade borgmatic
&lt;/code>&lt;/pre>&lt;h2 id="configure-borgmatic">Configure borgmatic&lt;/h2>
&lt;p>The following is a small configuration example. Place it in &lt;code>/etc/borgmatic.d/servic1.yaml&lt;/code>. If you need more options check out the &lt;a href="https://torsion.org/borgmatic/docs/reference/configuration/">full configuration file reference&lt;/a>&lt;/p>
&lt;pre tabindex="0">&lt;code>location:
source_directories:
- /home/service1/static
repositories:
- ssh://service1_backup@offsite1.example.com/./backup
storage:
encryption_passphrase: &amp;#34;ThePassphraseouUsedOnYourRaspi&amp;#34;
ssh_command: ssh -i /etc/borgmatic.d/service1_backup_key
retention:
# Number of daily archives to keep.
keep_daily: 7
hooks:
# List of one or more shell commands or scripts to execute
# before creating a backup, run once per configuration file.
before_backup:
- echo &amp;#34;Starting a backup.&amp;#34;
# List of one or more shell commands or scripts to execute
# after creating a backup, run once per configuration file.
after_backup:
- echo &amp;#34;Finished a backup.&amp;#34;
after_everything:
- echo &amp;#34;Completed actions.&amp;#34;
postgresql_databases:
- name: service1
# mysql_databases:
# - name: users
&lt;/code>&lt;/pre>&lt;h2 id="place-the-private-ssh-key">Place the private SSH key&lt;/h2>
&lt;p>The server will need the private SSH key so connect to your raspberry pi&lt;/p>
&lt;p>On the raspberry pi use&lt;/p>
&lt;pre tabindex="0">&lt;code>cat ~/.ssh/id_ed25519
&lt;/code>&lt;/pre>&lt;p>to get the private key and place it on your server in the file &lt;code>/etc/borgmatic.d/service1_backup_key&lt;/code>.
As this is a private SSH key it must only be readable by the user. Ro change its permissions correctly use&lt;/p>
&lt;pre tabindex="0">&lt;code>chown 600 service1_backup_key
&lt;/code>&lt;/pre>&lt;h2 id="check-if-the-backup-works">Check if the backup works&lt;/h2>
&lt;p>Create your backup with&lt;/p>
&lt;pre tabindex="0">&lt;code>sudo borgmatic create --verbosity 1 --list --stats
&lt;/code>&lt;/pre>&lt;p>Now check out the &lt;a href="https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot">borgmatic configuration&lt;/a> on how to properly set up automated backups&lt;/p>
&lt;h1 id="done">Done&lt;/h1>
&lt;p>Congrats, you should now have a fully functioning backup configuration!&lt;/p>
&lt;script type="text/javascript" src="https://latest.cactus.chat/cactus.js">&lt;/script>
&lt;link rel="stylesheet" href="https://latest.cactus.chat/style.css" type="text/css">
&lt;div id="comment-section">&lt;/div>
&lt;script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "https://matrix.cactus.chat:8448",
serverName: "cactus.chat",
siteName: "hyteck",
commentSectionId: "raspi-backup"
})
&lt;/script></content></entry><entry><title>Set up and secure an MQTT broker on Ubuntu</title><link href="https://hyteck.de/post/mqtt-telegraf-influx/" type="application/octet-stream"/><updated>2021-01-01T18:18:10+02:00</updated><id>https://hyteck.de/post/mqtt-telegraf-influx/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>I had some IoT devices that I wanted to integrate in my monitoring. For this I set up a MQTT broker as the MQTT protocol is a simple solution to send data from IoT devices to a server. This tutorial is focusing on setting up the server, but I also introduce a Python based MQTT client to test our installation.&lt;/p>
&lt;p>On your server, first install mosquitto, our MQTT server/broker.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo apt-get install mosquitto
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Allow standard mqtt port in firewall (if you have ufw installed)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo ufw allow &lt;span style="color:#ae81ff">1883&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now on the client side connect to the server and publish some fake sensor values.
First install the mqtt client&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo pip install phao-mqtt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and then use the following python code on your client side to send fake values to your server. You only need to change &lt;code>mqtt.example.com&lt;/code> to your servers IP/domain.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> time
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> paho.mqtt.client &lt;span style="color:#66d9ef">as&lt;/span> mqtt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> numpy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> numpy &lt;span style="color:#66d9ef">as&lt;/span> np
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">calc_temp&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> temp &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>sin(time&lt;span style="color:#f92672">.&lt;/span>time()&lt;span style="color:#f92672">%&lt;/span>(&lt;span style="color:#ae81ff">3600&lt;/span>)&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#f92672">*&lt;/span>np&lt;span style="color:#f92672">.&lt;/span>pi)&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">5&lt;/span>&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> temp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">on_connect&lt;/span>(client, userdata, flags, rc):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Connected with result code &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(rc))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client &lt;span style="color:#f92672">=&lt;/span> mqtt&lt;span style="color:#f92672">.&lt;/span>Client()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#client.username_pw_set(username=&amp;#34;username&amp;#34;,password=&amp;#34;my_super_secret_pw&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>on_connect &lt;span style="color:#f92672">=&lt;/span> on_connect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>connect(&lt;span style="color:#e6db74">&amp;#34;mqtt.example.com&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1883&lt;/span>, &lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>loop_start()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">while&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> time&lt;span style="color:#f92672">.&lt;/span>sleep(&lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>publish(&lt;span style="color:#e6db74">&amp;#34;test/temperature&amp;#34;&lt;/span>, calc_temp())
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can check if the broker accepts the values by subscribing to the topic:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/env python&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> paho.mqtt.client &lt;span style="color:#66d9ef">as&lt;/span> mqtt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">on_connect&lt;/span>(client, userdata, flags, rc):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Connected with result code &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(rc))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>subscribe(&lt;span style="color:#e6db74">&amp;#34;test/#&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">on_message&lt;/span>(client, userdata, msg):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(msg&lt;span style="color:#f92672">.&lt;/span>topic &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(msg&lt;span style="color:#f92672">.&lt;/span>payload))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client &lt;span style="color:#f92672">=&lt;/span> mqtt&lt;span style="color:#f92672">.&lt;/span>Client()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#client.username_pw_set(username=&amp;#34;username&amp;#34;,password=&amp;#34;my_super_secret_pw&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>on_connect &lt;span style="color:#f92672">=&lt;/span> on_connect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>on_message &lt;span style="color:#f92672">=&lt;/span> on_message
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>connect(&lt;span style="color:#e6db74">&amp;#34;mqtt.example.com&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1883&lt;/span>, &lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>loop_forever()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now secure your broker by creating a user with a password&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo mosquitto_passwd -c /etc/mosquitto/passwd &amp;lt;username&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and configure mosquitto to use it in &lt;code>/etc/mosquitto/conf.d/default.conf&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code>allow_anonymous false
password_file /etc/mosquitto/passwd
&lt;/code>&lt;/pre>&lt;p>Now restart mosquitto to enable the protection&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo systemctl restart mosquitto
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Test the installation by uncommenting &lt;code>client.username_pw_set(username=&amp;quot;username&amp;quot;,password=&amp;quot;my_super_secret_pw&amp;quot;)&lt;/code> and filling in your credentials.
The result code &lt;code>0&lt;/code> indicates a valid connection. &lt;code>5&lt;/code> indicates a authentication error.&lt;/p>
&lt;p>I hope this helps setting up a MQTT broker. Hopefully I will have the time to write how to connect such a broker to Grafana via Telegraf and Influx DB.&lt;/p>
&lt;script type="text/javascript" src="https://latest.cactus.chat/cactus.js">&lt;/script>
&lt;link rel="stylesheet" href="https://latest.cactus.chat/style.css" type="text/css">
&lt;div id="comment-section">&lt;/div>
&lt;script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "https://matrix.cactus.chat:8448",
serverName: "cactus.chat",
siteName: "hyteck",
commentSectionId: "monitoring"
})
&lt;/script></content></entry><entry><title>Translation for video conferences</title><link href="https://hyteck.de/post/bbb_translation/" type="application/octet-stream"/><updated>2020-07-23T07:30:00+02:00</updated><id>https://hyteck.de/post/bbb_translation/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>Although physical meetings are not possible, many groups and organizations are dependent on multilingual meetings.
In videoconferencing, concepts such as a whispered translation are not intuitive. Therefore a friend and I have developed a concept for translation in videoconferencing.
The concept was planned for a conference with 80 participants who will discuss and make decisions over two days either in large groups or in sub-groups.
The videoconferencing software that was used was BigBlueButton (BBB).&lt;/p>
&lt;h1 id="translation-via-subtitles">Translation via subtitles&lt;/h1>
&lt;p>BBB offers the possibility to write subtitles simultaneously to video conferencing.
This feature is especially aimed at hearing impaired people.
For translation, one person would translate and type in one language at a time.
Due to the limited typing speed, this procedure is associated with large losses of content.
However, it is a great option for hearing-impaired people and can be combined with a simultaneous oral translation.&lt;/p>
&lt;h1 id="simultaneous-translation">Simultaneous translation&lt;/h1>
&lt;p>The concept of simultaneous translation is based on the fact that there is a seperate BBB-room for each language. These are communicated to the participants in advance.&lt;/p>
&lt;p>The room whose language most of the participants speak will be the main room. People who do not speak the language of the main room will still join it, as moderation tasks and the presentation will be only in this room.&lt;/p>
&lt;p>All statements in the main room will be translated by overseers in the other rooms. The translators should be present in the main room with the microphone switched off. If a person from a secondary room wants to speak they indicate this in the chat of the main room, - preferably with &amp;ldquo;* (language)&amp;rdquo;. They will then be allowed to speak by the moderator and speak in their respective room. The translator will unmute their microphone in the main room and translates to the primar language.&lt;/p>
&lt;h2 id="breakout-rooms">Breakout Rooms&lt;/h2>
&lt;p>Breakput rooms are often formed to debate in smaller groups.
To ensure a reasonable amount of translation effort, all participants and translators of one language are assigned to one breakout room (in the main room).
If the Breakout Rooms become too full, Breakout Rooms can also be formed in the secondary rooms, but translators are then required for each room.&lt;/p>
&lt;h1 id="tips-and-tricks">Tips and tricks&lt;/h1>
&lt;ul>
&lt;li>Use two translators per language. One person cannot do this even at a moderate speed of discussion. It also allows for better reproduction of conversations by alternating the translators.&lt;/li>
&lt;li>Ask participants to set their names according to the &amp;ldquo;Name [pronouns] (language)&amp;rdquo; scheme. This makes it easier for moderation and translators.&lt;/li>
&lt;li>Participants do not need to join the audio in the main room, but they can, so that their contributions can be heard in the original. However, they should mute the browser window of the main room.&lt;/li>
&lt;/ul>
&lt;h1 id="conclusion">Conclusion&lt;/h1>
&lt;p>Whispered translation is possible in BBB; in order to use it, the possibility must be announced in good time by the moderator and enough translators must be found. The concept of oral translation has already proven itself at a large meeting.&lt;/p>
&lt;p>If you are interested in implementing the concept you are welcome to contact me at &lt;a href="mailto:fluesteruebersetzung@hyteck.de">fluesteruebersetzung@hyteck.de&lt;/a> or in the comments below.&lt;/p>
&lt;script type="text/javascript" src="https://latest.cactus.chat/cactus.js">&lt;/script>
&lt;link rel="stylesheet" href="https://latest.cactus.chat/style.css" type="text/css">
&lt;div id="comment-section">&lt;/div>
&lt;script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "https://matrix.cactus.chat:8448",
serverName: "cactus.chat",
siteName: "hyteck",
commentSectionId: "bbb-translation"
})
&lt;/script></content></entry></feed>