<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>goroot — Notizen von der Schnittstelle</title>
    <link>https://blog.goroot.de/de/</link>
    <description>Notizen zwischen Technik und Führung.</description>
    <language>de-DE</language>
    <managingEditor>Michael Kolb</managingEditor>
    <lastBuildDate>Fri, 07 Aug 2026 18:18:32 +0000</lastBuildDate>
    <atom:link href="https://blog.goroot.de/de/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Automatisierte Tasmota-Backups</title>
      <link>https://blog.goroot.de/de/post/automated-tasmota-backup/</link>
      <pubDate>Sat, 04 Dec 2021 14:28:10 +0100</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/automated-tasmota-backup/</guid>
      <description>Ein Smart Home besteht aus Dutzenden smarter Geräte — und deren Backup ist unerlässlich, um sie im Fehlerfall schnell wiederherstellen zu können.</description>
      <content:encoded><![CDATA[<p>Mein Smart Home setzt stark auf <a href="http://tasmota.github.io" rel="noopener">tasmota</a><a class="arch" href="https://web.archive.org/web/20211204/http://tasmota.github.io"
       title="Archiviert am 2021-12-04"
       rel="nofollow noopener">[arch]</a>-basierte Geräte. Während die Tasmota-Software selbst extrem stabil läuft und kaum zu Ausfällen führt, sieht das bei der Hardware, auf der sie läuft, anders aus. Was tust du, wenn du kein Tasmota-Backup hast und das Gerät kaputtgeht? Könntest du die Konfiguration von Dutzenden oder gar Hunderten Geräten einfach aus dem Gedächtnis wiederherstellen?</p>
<p>Wie bei jedem anderen kritischen Teil deiner IT-Infrastruktur ist die einfachste Lösung, dein Tasmota-Gerät zu sichern und es bei Bedarf wiederherzustellen.</p>
<h2 id="tasmota-backup-erstellen">Tasmota-Backup erstellen</h2>
<p>Tasmota stellt eine API bereit, über die sich die Einstellungen des Geräts auslesen lassen. Das Tasmota-Team stellt dafür das Tool <a href="https://github.com/tasmota/decode-config" rel="noopener">decode-config</a><a class="arch" href="https://web.archive.org/web/20211204/https://github.com/tasmota/decode-config"
       title="Archiviert am 2021-12-04"
       rel="nofollow noopener">[arch]</a> zur Verfügung, mit dem sich ein einzelnes Gerät sichern lässt.</p>
<h2 id="tasmota-backups-automatisieren">Tasmota-Backups automatisieren</h2>
<p>Meine Anforderungen an automatisierte Tasmota-Backups waren:</p>
<ul>
<li>Vollautomatisch</li>
<li>Unabhängig von meinem Arbeitsplatzrechner (Backup als Service)</li>
<li>Versioniert (mehrere zeitgestempelte Backups pro Gerät)</li>
</ul>
<p>Also habe ich <strong><a href="https://github.com/gorootde/tasmockup" rel="noopener">tasmockup</a><a class="arch" href="https://web.archive.org/web/20211204/https://github.com/gorootde/tasmockup"
       title="Archiviert am 2021-12-04"
       rel="nofollow noopener">[arch]</a></strong> geschrieben (<strong>Tasmo</strong>ta + Ba<strong>ckup</strong>). <a href="https://github.com/gorootde/tasmockup" rel="noopener">Tasmockup</a><a class="arch" href="https://web.archive.org/web/20211204/https://github.com/gorootde/tasmockup"
       title="Archiviert am 2021-12-04"
       rel="nofollow noopener">[arch]</a> ist ein Wrapper für decode-config, der alle konfigurierten Tasmota-Geräte nach einem festen Zeitplan sichert. Es steht als Docker-Container zur Verfügung, der sich problemlos in jeder Umgebung deployen lässt.</p>
<p>Ich nutze tasmockup, um automatisierte Backups für alle meine Geräte zu erstellen. Der tasmockup-Container läuft auf meinem Server, auf dem ohnehin schon Home Assistant und weitere Smart-Home-Dienste laufen. Tasmockup sichert täglich alle Tasmota-Geräte in meinem Netzwerk und versioniert die Backups mit einem Unix-Zeitstempel.</p>
<p><a href="https://github.com/gorootde/tasmockup" rel="noopener">Hier geht&rsquo;s zu Tasmockup auf GitHub</a><a class="arch" href="https://web.archive.org/web/20211204/https://github.com/gorootde/tasmockup"
       title="Archiviert am 2021-12-04"
       rel="nofollow noopener">[arch]</a></p>
<h2 id="konfiguration">Konfiguration</h2>
<p>Die Konfiguration ist denkbar einfach. Zum Zeitpunkt dieses Artikels gibst du an, welche Geräte gesichert werden sollen, und mountest ein Docker-Volume, in dem die Backups abgelegt werden:</p>
<div class="codeblock" id="c1">
  <div class="code-head">
    <span class="f">bash</span>
    
    <a class="anchor" href="#c1-1">#L1-L1</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln" id="c1-1"><a class="lnlinks" href="#c1-1">1</a></span><span class="cl">docker run -t -i -e <span class="nv">DEVICES</span><span class="o">=</span><span class="s2">&#34;192.168.0.2;superSecret 192.168.0.3;AnotherSecret&#34;</span> -v tasmota-backups:/data gorootde/tasmockup</span></span></code></pre></div>
  
</div>
<p>Eine ausführliche Beschreibung zur Konfiguration von tasmockup findest du in der <a href="https://github.com/gorootde/tasmockup/" rel="noopener">README.md</a><a class="arch" href="https://web.archive.org/web/20211204/https://github.com/gorootde/tasmockup/"
       title="Archiviert am 2021-12-04"
       rel="nofollow noopener">[arch]</a> auf GitHub.</p>
<h2 id="wiederherstellung">Wiederherstellung</h2>
<p>Backups lassen sich einfach über die Tasmota-WebUI wiederherstellen. Navigiere einfach zu Backup Configuration / Restore Configuration und lade dein Backup hoch.</p>
]]></content:encoded>
      <category>smart-home</category>
    </item>
    
    <item>
      <title>Visual Studio Code auf dem iPad nutzen</title>
      <link>https://blog.goroot.de/de/post/Visual-Studio-Code-auf-dem-iPad-nutzen/</link>
      <pubDate>Thu, 29 Jul 2021 13:19:52 +0100</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/Visual-Studio-Code-auf-dem-iPad-nutzen/</guid>
      <description>Ein iPad als Computerersatz zu nutzen ist bei Aufgaben abseits der Entwicklung einfach — aber wie sieht es mit Coding aus? Hier ist ein vollständig portables VSCode-Setup, das sogar offline funktioniert.</description>
      <content:encoded><![CDATA[<p>Kürzlich stand ich vor dem Problem, dass mir im Urlaub mein Hauptcomputer nicht zur Verfügung steht.￼ Ich reise im Urlaub normalerweise nur mit einer leichten IT-Ausstattung, in diesem Fall meinem iPad Pro und ein paar Kabeln und Ladegeräten￼. Das ist eine bewusste Entscheidung, da ich nicht zu viel — im besten Fall gar keine — Zeit damit verbringen möchte, vor meinem Computer zu sitzen￼￼. Es gibt aber Situationen, in denen man schnell etwas reparieren muss: zum Beispiel, wenn eine meiner Webseiten kaputtgeht oder ein anderer Dienst nicht mehr läuft￼. Selbst wenn das theoretisch möglich wäre, ist es schwierig, solche Dinge auf einem Smartphone zu beheben, ohne eine IDE wie <a href="https://code.visualstudio.com" rel="noopener">VSCode</a><a class="arch" href="https://web.archive.org/web/20210729/https://code.visualstudio.com"
       title="Archiviert am 2021-07-29"
       rel="nofollow noopener">[arch]</a> zur Verfügung zu haben.</p>
<p>Ich brauchte also ein Setup, mit dem ich zumindest dringende Dinge reparieren kann, ohne gleichzeitig in Versuchung zu geraten, mehr als das zu tun.</p>
<h2 id="warum-ios-vscode-nicht-nativ-unterstützt">Warum iOS VSCode nicht nativ unterstützt</h2>
<p>Apples iOS ist ein stark reglementiertes Betriebssystem. Das bringt viele Vorteile mit sich, hat aber auch Nachteile. Einer davon ist, dass man keine Apps installieren kann, die auf ein zugrunde liegendes Betriebssystem angewiesen sind. VSCode nutzt intensiv￼ die vom Betriebssystem bereitgestellte Shell. Da iOS keine Shell bereitstellt, ist es schwierig, diese Apps zu portieren. Die App müsste eine komplette Linux-Distribution enthalten, um denselben Funktionsumfang bieten zu können.</p>
<p>Diese Einschränkung führt dazu, dass VSCode auf einem vollwertigen Betriebssystem laufen muss statt direkt auf dem iPad. Aber keine Sorge, es wird sich trotzdem so anfühlen, als würde es direkt auf deinem iPad laufen.</p>
<h2 id="hardware-setup">Hardware-Setup</h2>
<p>Da wir ein portables und leichtes Setup aufbauen wollen, verwenden wir Komponenten, die auch abseits des Stromnetzes laufen können und eine kleine Bauform haben.</p>
<p><strong>Stückliste</strong></p>
<ol>
<li><a href="https://www.raspberrypi.org/products/raspberry-pi-4-model-b/" rel="noopener">Raspberry Pi 4 (8GB)</a><a class="arch" href="https://web.archive.org/web/20210729/https://www.raspberrypi.org/products/raspberry-pi-4-model-b/"
       title="Archiviert am 2021-07-29"
       rel="nofollow noopener">[arch]</a>: du kannst auch die 4GB-Variante wählen, aber man kann nie genug Speicher haben!</li>
<li>USB-C-zu-USB-C-Kabel: Achte darauf, dass es Power Delivery und Datenübertragung unterstützt.</li>
<li>Micro-SD-Karte mit 128GB oder mehr. Ich selbst nutze eine SanDisk Ultra 400GB Class 10 Karte.</li>
<li>Gehäuse für den Raspberry Pi</li>
<li>iPad Pro mit USB-C-Anschluss</li>
<li>(Bluetooth-)Tastatur &amp; Maus für das iPad</li>
</ol>
<h2 id="rpis-usb-c-port-als-netzwerkschnittstelle-einrichten">RPis USB-C-Port als Netzwerkschnittstelle einrichten</h2>
<p>Wir konfigurieren den USB-C-Port des Raspberry so, dass er als Netzwerkkarte für dein iPad fungiert. Dadurch kann das iPad mit deinem Raspberry kommunizieren, während es gleichzeitig in seinem WLAN-Netzwerk bleibt, um internetbasierte Dienste nutzen zu können.</p>
<p>Verbinde dich per SSH mit deinem Pi und führe die folgenden Schritte aus. Alternativ kannst du deine SD-Karte an einem Computer einbinden und die Schritte 1-3 durch Bearbeiten der Dateien dort durchführen.</p>
<ol>
<li>
<p>Bearbeite <code>/boot/config.txt</code> und füge <code>dtoverlay=dwc2</code> hinzu. <em>Dadurch wird der USB-OTG-Modus auf deinem Raspberry aktiviert.</em></p>
</li>
<li>
<p>Füge <code>modules-load=dwc2</code> an das Ende von <code>/boot/cmdline.txt</code> an. <em>Dadurch erhält der Kernel den Befehl, dieses Modul als Parameter zu laden</em></p>
</li>
<li>
<p>Falls SSH noch nicht aktiviert ist, aktiviere es, indem du eine leere Datei namens <code>ssh</code> in <code>/boot/</code> anlegst. <em>Das Anlegen aktiviert den SSH-Server beim nächsten Boot</em></p>
</li>
<li>
<p>Bearbeite <code>/etc/modules</code> und füge <code>libcomposite</code> in einer neuen Zeile am Ende der Datei hinzu. <em>Dadurch wird sichergestellt, dass die USB-Gadget-Treiber geladen werden</em></p>
</li>
<li>
<p>Bearbeite <code>/etc/dhcpd.conf</code> und füge <code>denyinterfaces usb0</code> hinzu. <em>Da wir nicht wollen, dass unsere USB-Netzwerkschnittstelle von DHCP konfiguriert wird, schließen wir das hier explizit aus</em></p>
</li>
<li>
<p>Installiere dnsmasq mit <code>sudo apt-get install dnsmasq</code>. <em>dnsmasq fungiert als DHCP-Server für die USB-basierte Netzwerkschnittstelle. Es stellt sicher, dass die Ethernet-Schnittstelle deines iPads eine IP-Adresse erhält.</em></p>
</li>
<li>
<p>Erstelle eine neue Datei <code>/etc/dnsmasq.d/usbnetwork</code> mit folgendem Inhalt:</p>
<div class="codeblock" id="c1">
  <div class="code-head">
    <span class="f">/etc/dnsmasq.d/usbnetwork</span>
    
    <a class="anchor" href="#c1-1">#L1-L4</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln" id="c1-1"><a class="lnlinks" href="#c1-1">1</a></span><span class="cl"><span class="na">interface</span><span class="o">=</span><span class="s">usb0</span>
</span></span><span class="line"><span class="ln" id="c1-2"><a class="lnlinks" href="#c1-2">2</a></span><span class="cl"><span class="na">dhcp-range</span><span class="o">=</span><span class="s">10.55.0.2,10.55.0.6,255.255.255.248,1h</span>
</span></span><span class="line"><span class="ln" id="c1-3"><a class="lnlinks" href="#c1-3">3</a></span><span class="cl"><span class="na">dhcp-option</span><span class="o">=</span><span class="s">3</span>
</span></span><span class="line"><span class="ln" id="c1-4"><a class="lnlinks" href="#c1-4">4</a></span><span class="cl"><span class="na">leasefile-ro</span></span></span></code></pre></div>
  
</div>
</li>
<li>
<p>Erstelle <code>/etc/network/interfaces.d/usb0</code> mit folgendem Inhalt:</p>
<div class="codeblock" id="c2">
  <div class="code-head">
    <span class="f">/etc/network/interfaces.d/usb0</span>
    
    <a class="anchor" href="#c2-1">#L1-L5</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln" id="c2-1"><a class="lnlinks" href="#c2-1">1</a></span><span class="cl"><span class="na">auto usb0</span>
</span></span><span class="line"><span class="ln" id="c2-2"><a class="lnlinks" href="#c2-2">2</a></span><span class="cl"><span class="na">allow-hotplug usb0</span>
</span></span><span class="line"><span class="ln" id="c2-3"><a class="lnlinks" href="#c2-3">3</a></span><span class="cl"><span class="na">iface usb0 inet static</span>
</span></span><span class="line"><span class="ln" id="c2-4"><a class="lnlinks" href="#c2-4">4</a></span><span class="cl">  <span class="na">address 10.55.0.1</span>
</span></span><span class="line"><span class="ln" id="c2-5"><a class="lnlinks" href="#c2-5">5</a></span><span class="cl">  <span class="na">netmask 255.255.255.248</span></span></span></code></pre></div>
  
</div>
</li>
<li>
<p>Erstelle <code>/root/usb.sh</code> und füge <a href="https://www.hardill.me.uk/wordpress/2019/11/02/pi4-usb-c-gadget/" rel="noopener">Ben Hardills großartigen Code</a><a class="arch" href="https://web.archive.org/web/20210729/https://www.hardill.me.uk/wordpress/2019/11/02/pi4-usb-c-gadget/"
       title="Archiviert am 2021-07-29"
       rel="nofollow noopener">[arch]</a> zur Konfiguration der Hardware-Eigenschaften ein:</p>
<div class="codeblock" id="c3">
  <div class="code-head">
    <span class="f">/root/usb.sh</span>
    
    <a class="anchor" href="#c3-1">#L1-L31</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln" id="c3-1"><a class="lnlinks" href="#c3-1"> 1</a></span><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln" id="c3-2"><a class="lnlinks" href="#c3-2"> 2</a></span><span class="cl"><span class="nb">cd</span> /sys/kernel/config/usb_gadget/
</span></span><span class="line"><span class="ln" id="c3-3"><a class="lnlinks" href="#c3-3"> 3</a></span><span class="cl">mkdir -p pi4
</span></span><span class="line"><span class="ln" id="c3-4"><a class="lnlinks" href="#c3-4"> 4</a></span><span class="cl"><span class="nb">cd</span> pi4
</span></span><span class="line"><span class="ln" id="c3-5"><a class="lnlinks" href="#c3-5"> 5</a></span><span class="cl"><span class="nb">echo</span> 0x1d6b &gt; idVendor <span class="c1"># Linux Foundation</span>
</span></span><span class="line"><span class="ln" id="c3-6"><a class="lnlinks" href="#c3-6"> 6</a></span><span class="cl"><span class="nb">echo</span> 0x0104 &gt; idProduct <span class="c1"># Multifunction Composite Gadget</span>
</span></span><span class="line"><span class="ln" id="c3-7"><a class="lnlinks" href="#c3-7"> 7</a></span><span class="cl"><span class="nb">echo</span> 0x0100 &gt; bcdDevice <span class="c1"># v1.0.0</span>
</span></span><span class="line"><span class="ln" id="c3-8"><a class="lnlinks" href="#c3-8"> 8</a></span><span class="cl"><span class="nb">echo</span> 0x0200 &gt; bcdUSB <span class="c1"># USB2</span>
</span></span><span class="line"><span class="ln" id="c3-9"><a class="lnlinks" href="#c3-9"> 9</a></span><span class="cl"><span class="nb">echo</span> 0xEF &gt; bDeviceClass
</span></span><span class="line"><span class="ln" id="c3-10"><a class="lnlinks" href="#c3-10">10</a></span><span class="cl"><span class="nb">echo</span> 0x02 &gt; bDeviceSubClass
</span></span><span class="line"><span class="ln" id="c3-11"><a class="lnlinks" href="#c3-11">11</a></span><span class="cl"><span class="nb">echo</span> 0x01 &gt; bDeviceProtocol
</span></span><span class="line"><span class="ln" id="c3-12"><a class="lnlinks" href="#c3-12">12</a></span><span class="cl">mkdir -p strings/0x409
</span></span><span class="line"><span class="ln" id="c3-13"><a class="lnlinks" href="#c3-13">13</a></span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;fedcba9876543211&#34;</span> &gt; strings/0x409/serialnumber
</span></span><span class="line"><span class="ln" id="c3-14"><a class="lnlinks" href="#c3-14">14</a></span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Ben Hardill&#34;</span> &gt; strings/0x409/manufacturer
</span></span><span class="line"><span class="ln" id="c3-15"><a class="lnlinks" href="#c3-15">15</a></span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;PI4 USB Device&#34;</span> &gt; strings/0x409/product
</span></span><span class="line"><span class="ln" id="c3-16"><a class="lnlinks" href="#c3-16">16</a></span><span class="cl">mkdir -p configs/c.1/strings/0x409
</span></span><span class="line"><span class="ln" id="c3-17"><a class="lnlinks" href="#c3-17">17</a></span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Config 1: ECM network&#34;</span> &gt; configs/c.1/strings/0x409/configuration
</span></span><span class="line"><span class="ln" id="c3-18"><a class="lnlinks" href="#c3-18">18</a></span><span class="cl"><span class="nb">echo</span> <span class="m">250</span> &gt; configs/c.1/MaxPower
</span></span><span class="line"><span class="ln" id="c3-19"><a class="lnlinks" href="#c3-19">19</a></span><span class="cl"><span class="c1"># Add functions here</span>
</span></span><span class="line"><span class="ln" id="c3-20"><a class="lnlinks" href="#c3-20">20</a></span><span class="cl"><span class="c1"># see gadget configurations below</span>
</span></span><span class="line"><span class="ln" id="c3-21"><a class="lnlinks" href="#c3-21">21</a></span><span class="cl"><span class="c1"># End functions</span>
</span></span><span class="line"><span class="ln" id="c3-22"><a class="lnlinks" href="#c3-22">22</a></span><span class="cl">mkdir -p functions/ecm.usb0
</span></span><span class="line"><span class="ln" id="c3-23"><a class="lnlinks" href="#c3-23">23</a></span><span class="cl"><span class="nv">HOST</span><span class="o">=</span><span class="s2">&#34;00:dc:c8:f7:75:14&#34;</span> <span class="c1"># &#34;HostPC&#34;</span>
</span></span><span class="line"><span class="ln" id="c3-24"><a class="lnlinks" href="#c3-24">24</a></span><span class="cl"><span class="nv">SELF</span><span class="o">=</span><span class="s2">&#34;00:dd:dc:eb:6d:a1&#34;</span> <span class="c1"># &#34;BadUSB&#34;</span>
</span></span><span class="line"><span class="ln" id="c3-25"><a class="lnlinks" href="#c3-25">25</a></span><span class="cl"><span class="nb">echo</span> <span class="nv">$HOST</span> &gt; functions/ecm.usb0/host_addr
</span></span><span class="line"><span class="ln" id="c3-26"><a class="lnlinks" href="#c3-26">26</a></span><span class="cl"><span class="nb">echo</span> <span class="nv">$SELF</span> &gt; functions/ecm.usb0/dev_addr
</span></span><span class="line"><span class="ln" id="c3-27"><a class="lnlinks" href="#c3-27">27</a></span><span class="cl">ln -s functions/ecm.usb0 configs/c.1/
</span></span><span class="line"><span class="ln" id="c3-28"><a class="lnlinks" href="#c3-28">28</a></span><span class="cl">udevadm settle -t <span class="m">5</span> <span class="o">||</span> :
</span></span><span class="line"><span class="ln" id="c3-29"><a class="lnlinks" href="#c3-29">29</a></span><span class="cl">ls /sys/class/udc &gt; UDC
</span></span><span class="line"><span class="ln" id="c3-30"><a class="lnlinks" href="#c3-30">30</a></span><span class="cl">ifup usb0
</span></span><span class="line"><span class="ln" id="c3-31"><a class="lnlinks" href="#c3-31">31</a></span><span class="cl">service dnsmasq restart</span></span></code></pre></div>
  
</div>
</li>
<li>
<p>Mache die Datei mit <code>chmod +x /root/usb.sh</code> ausführbar</p>
</li>
<li>
<p>Füge <code>/root/usb.sh</code> in <code>/etc/rc.local</code> vor der Zeile <code>exit 0</code> hinzu, damit es bei jedem Systemstart automatisch ausgeführt wird.</p>
</li>
</ol>
<p>Nachdem du deinen Pi (neu) gestartet hast, solltest du auf deinem iPad eine neue Netzwerkschnittstelle sehen:
<figure>
  <img src="settings-network-interface.png" alt="iPad Einstellungen Netzwerkschnittstelle" loading="lazy" decoding="async"></figure>
</p>
<h2 id="vscode-einrichten">VSCode einrichten</h2>
<p>Ich gehe davon aus, dass du <a href="https://www.raspbian.org" rel="noopener">raspbian</a><a class="arch" href="https://web.archive.org/web/20210729/https://www.raspbian.org"
       title="Archiviert am 2021-07-29"
       rel="nofollow noopener">[arch]</a> auf deinem Pi installiert hast. Falls nicht, <a href="https://www.raspberrypi.org/software/" rel="noopener">folge den Anweisungen hier</a><a class="arch" href="https://web.archive.org/web/20210729/https://www.raspberrypi.org/software/"
       title="Archiviert am 2021-07-29"
       rel="nofollow noopener">[arch]</a>, um dahin zu gelangen.</p>
<p>Um VSCode auf dem Pi auszuführen und vom iPad aus zugänglich zu machen, verwenden wir <a href="https://github.com/cdr/code-server" rel="noopener">code-server</a><a class="arch" href="https://web.archive.org/web/20210729/https://github.com/cdr/code-server"
       title="Archiviert am 2021-07-29"
       rel="nofollow noopener">[arch]</a>. Code-server verpackt VSCode und seine Abhängigkeiten in einen Dienst, auf den du über deinen Browser zugreifen kannst.</p>
<p><strong>Code-server auf deinem Raspberry Pi installieren</strong></p>
<ol>
<li>
<p>Installiere <code>yarn</code> mit <code>sudo apt-get install yarn</code></p>
</li>
<li>
<p>Installiere code-server global mit <code>yarn global add code-server</code></p>
</li>
<li>
<p>Du solltest code-server jetzt mit <code>yarn run code-server</code> starten können</p>
</li>
<li>
<p>Passe die Code-Server-Konfiguration an deine Netzwerkkonfiguration an. Bearbeite <code>~/.config/code-server/config.yaml</code> und füge folgende Konfiguration ein:</p>
<div class="codeblock" id="c4">
  <div class="code-head">
    <span class="f">~/.config/code-server/config.yaml</span>
    
    <a class="anchor" href="#c4-1">#L1-L5</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln" id="c4-1"><a class="lnlinks" href="#c4-1">1</a></span><span class="cl"><span class="nt">bind-addr</span><span class="p">:</span><span class="w"> </span><span class="m">10.55.0.1</span><span class="p">:</span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c4-2"><a class="lnlinks" href="#c4-2">2</a></span><span class="cl"><span class="nt">auth</span><span class="p">:</span><span class="w"> </span><span class="l">password</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c4-3"><a class="lnlinks" href="#c4-3">3</a></span><span class="cl"><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">changeme</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c4-4"><a class="lnlinks" href="#c4-4">4</a></span><span class="cl"><span class="nt">cert</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c4-5"><a class="lnlinks" href="#c4-5">5</a></span><span class="cl"><span class="nt">cert-host</span><span class="p">:</span><span class="w"> </span><span class="l">raspberrypi.local</span></span></span></code></pre></div>
  
</div>
</li>
<li>
<p>Deine code-server-Instanz sollte jetzt von deinem iPad aus über Safari oder einen anderen Browser unter <code>https://raspberrypi.local:8080</code> erreichbar sein. Falls das nicht funktioniert, versuche, das WLAN auf deinem iPad vorübergehend zu deaktivieren. Du kannst es wieder aktivieren, nachdem VSCode in Safari geladen wurde.</p>
</li>
</ol>
<p>An diesem Punkt solltest du die VSCode-Anmeldeaufforderung sehen, die nach dem Passwort (<code>changeme</code>) fragt, das wir in der YAML-Datei konfiguriert haben. Um VSCode korrekt nutzen zu können, müssen wir auf dem iPad noch weitere Konfigurationen vornehmen. Lies weiter!</p>
<p><em>Detailliertere Anweisungen zur Installation von code-server findest du <a href="https://coder.com/docs/code-server/v3.11.0/install#yarn-npm" rel="noopener">hier</a><a class="arch" href="https://web.archive.org/web/20210729/https://coder.com/docs/code-server/v3.11.0/install#yarn-npm"
       title="Archiviert am 2021-07-29"
       rel="nofollow noopener">[arch]</a>.</em></p>
<h2 id="code-server-beim-systemstart-automatisch-starten">Code-server beim Systemstart automatisch starten</h2>
<p>Damit code-server bei jedem Systemstart automatisch startet, müssen wir es bei systemd registrieren. Erstelle eine neue Datei <code>/lib/systemd/system/code-server.service</code> mit folgendem Inhalt:</p>
<div class="codeblock" id="c5">
  <div class="code-head">
    <span class="f">/lib/systemd/system/code-server.service</span>
    
    <a class="anchor" href="#c5-1">#L1-L13</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln" id="c5-1"><a class="lnlinks" href="#c5-1"> 1</a></span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln" id="c5-2"><a class="lnlinks" href="#c5-2"> 2</a></span><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">code-server</span>
</span></span><span class="line"><span class="ln" id="c5-3"><a class="lnlinks" href="#c5-3"> 3</a></span><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network.target</span>
</span></span><span class="line"><span class="ln" id="c5-4"><a class="lnlinks" href="#c5-4"> 4</a></span><span class="cl">
</span></span><span class="line"><span class="ln" id="c5-5"><a class="lnlinks" href="#c5-5"> 5</a></span><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="ln" id="c5-6"><a class="lnlinks" href="#c5-6"> 6</a></span><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">simple</span>
</span></span><span class="line"><span class="ln" id="c5-7"><a class="lnlinks" href="#c5-7"> 7</a></span><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">yarn exec code-server</span>
</span></span><span class="line"><span class="ln" id="c5-8"><a class="lnlinks" href="#c5-8"> 8</a></span><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">always</span>
</span></span><span class="line"><span class="ln" id="c5-9"><a class="lnlinks" href="#c5-9"> 9</a></span><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">pi</span>
</span></span><span class="line"><span class="ln" id="c5-10"><a class="lnlinks" href="#c5-10">10</a></span><span class="cl"><span class="na">Group</span><span class="o">=</span><span class="s">pi</span>
</span></span><span class="line"><span class="ln" id="c5-11"><a class="lnlinks" href="#c5-11">11</a></span><span class="cl">
</span></span><span class="line"><span class="ln" id="c5-12"><a class="lnlinks" href="#c5-12">12</a></span><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="ln" id="c5-13"><a class="lnlinks" href="#c5-13">13</a></span><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span></span></span></code></pre></div>
  
</div>
<p>Führe dann <code>systemctl daemon-reload</code> aus, um den neuen Dienst zu aktivieren.</p>
<p>Du kannst den Status von code-server nun mit <code>systemctl status code-server</code> prüfen. Er sollte dir mitteilen, dass er <code>active (running)</code> ist. Falls nicht, kannst du versuchen, ihn mit <code>systemctl restart code-server</code> neu zu starten.</p>
<h2 id="vscode-zum-homescreen-deines-ipads-hinzufügen">VSCode zum Homescreen deines iPads hinzufügen</h2>
<p>Der Zugriff auf VSCode über den Safari-Browser des iPads ist nicht ganz einfach, da VSCode Web-Sockets verwendet, um mit dem Backend zu kommunizieren, das auf deinem Raspberry Pi läuft. Damit diese Kommunikation funktioniert, muss die Verbindung verschlüsselt sein (HTTPS oder TLS). Glücklicherweise stellt code-server ein Zertifikat für dich bereit. Diesem Zertifikat muss auf dem iPad vertraut werden.</p>
<p><strong>Zertifikat auf dem iPad importieren</strong></p>
<ol>
<li>Suche auf dem Raspberry nach der Datei <code>~/.local/share/code-server/raspberrypi_local.crt</code> und kopiere sie auf dein iPad.</li>
<li>Öffne <code>raspberrypi_local.crt</code> in der Dateien-App und folge den Dialogen, um dem importierten Zertifikat zu vertrauen.</li>
<li>Öffne die Einstellungen-App und navigiere zu <code>Allgemein -&gt; Info -&gt; Zertifikatsvertrauenseinstellungen</code> (unten in der Liste) und aktiviere den Schalter <code>Vollständiges Vertrauen für Root-Zertifikate aktivieren</code> für das Zertifikat namens <code>raspberrypi.local</code>.
<figure>
  <img src="./certificate-trust-settings.jpg" alt="Zertifikatsvertrauenseinstellungen" loading="lazy" decoding="async"></figure>
</li>
</ol>
<p><strong>VSCode als Progressive Web App zum Homescreen hinzufügen</strong></p>
<ol>
<li>Öffne <code>https://raspberrypi.local:8080</code> in Safari</li>
<li>Klicke auf das Teilen-Symbol</li>
<li>Wähle <code>Zum Home-Bildschirm</code> und vergib einen Namen
<figure>
  <img src="./add-to-homescreen.png" alt="zum Homescreen hinzufügen" loading="lazy" decoding="async"></figure>

Du solltest jetzt VSCode öffnen können, indem du auf das gerade erstellte Symbol tippst.</li>
</ol>
<h2 id="herzlichen-glückwunsch">Herzlichen Glückwunsch!</h2>
<p>Du hast gerade ein vollständig portables Software-Entwicklungs-Setup erstellt, das sogar ohne Internetverbindung genutzt werden kann. Lass mich in den Kommentaren wissen, was du davon hältst!</p>
]]></content:encoded>
      <category>vscode</category><category>development</category><category>raspberry-pi</category>
    </item>
    
    <item>
      <title>Präsenzerkennung: Biene in der Kiste</title>
      <link>https://blog.goroot.de/de/post/presence-detection-bee-in-a-box/</link>
      <pubDate>Thu, 22 Jul 2021 21:31:21 +0100</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/presence-detection-bee-in-a-box/</guid>
      <description>Präsenzerkennung im Smart Home lässt sich ganz einfach umsetzen — ganz ohne GPS-Tracking oder Bewegungsmelder.</description>
      <content:encoded><![CDATA[<p>Der &lsquo;Biene in der Kiste&rsquo;-Algorithmus ermöglicht es dir, Präsenz in einem bestimmten Bereich deines Smart Homes zu erkennen. Er versetzt dein Smart-Home-System in die Lage zu wissen, ob in einem bestimmten Bereich gerade jemand anwesend ist oder nicht. Dafür sind keine speziellen Sensoren oder sonstige zusätzliche Hardware erforderlich.</p>
<h2 id="der-biene-in-der-kiste-algorithmus-die-theorie">Der &lsquo;Biene in der Kiste&rsquo;-Algorithmus: Die Theorie</h2>
<p>Stell dir vor, jemand reicht dir eine kleine Kiste aus Pappe. Auf der Oberseite der Kiste ist ein gelbes Warnsymbol: &ldquo;Achtung: Möglicherweise befindet sich eine Biene darin.&rdquo;</p>
<p>Du kannst nicht hineinschauen. Und öffnen willst du sie auch nicht, da das einen schmerzhaften Stich zur Folge haben könnte. Wie könntest du also herausfinden, ob sich in deiner speziellen Kiste wirklich eine Biene befindet?</p>
<p>Die Antwort ist ziemlich simpel: Du lauschst dem Summen der Biene!</p>
<p>Falls du das Summen gehört hast und die Kiste seitdem nicht geöffnet wurde, kannst du davon ausgehen, dass sich die Biene immer noch darin befindet. Dieses einfache Prinzip lässt sich auch auf dein Smart Home übertragen!</p>
<h2 id="biene-in-der-kiste-person-im-raum-wie-man-den-algorithmus-für-präsenzerkennung-nutzt"><del>Biene in der Kiste</del> Person im Raum: Wie man den Algorithmus für Präsenzerkennung nutzt</h2>
<p>Die Übertragung des &lsquo;Biene in der Kiste&rsquo;-Algorithmus auf ein Smart-Home-Umfeld ist ziemlich einfach:</p>
<ul>
<li>Die Biene entspricht einer beliebigen Person</li>
<li>Die Pappkiste mit der Biene entspricht einem beliebigen Bereich deines Smart Homes. Das kann ein einzelner Raum, deine Wohnung oder sogar das ganze Haus sein</li>
<li>Das Summen entspricht in Wirklichkeit jeder Interaktion mit einem ausgelösten Sensor oder Aktor</li>
</ul>
<p>Nehmen wir ein sehr einfaches Smart-Home-Setup an: Du hast eine Wohnung mit einer einzigen smarten Lampe, die an dein <a href="https://www.homeassistant.io" rel="noopener">Home Assistant</a><a class="arch" href="https://web.archive.org/web/20210722/https://www.homeassistant.io"
       title="Archiviert am 2021-07-22"
       rel="nofollow noopener">[arch]</a> (oder ein anderes) Smart-Home-System angebunden ist. Wenn irgendjemand das Licht ein- oder ausschaltet, weiß dein Smart-Home-Server, dass sich jemand in der Wohnung befindet. Fügt man noch einen Türsensor an der Haupttür hinzu, weiß das System auch, dass jemand die Wohnung möglicherweise verlassen hat.</p>
<p>Mehr braucht es nicht: Präsenzerkennung mit nur einem Türsensor und einem Lichtschalter!</p>
<h2 id="präsenzerkennung-mit-dem-biene-in-der-kiste-algorithmus-zusammengefasst">Präsenzerkennung mit dem &lsquo;Biene in der Kiste&rsquo;-Algorithmus zusammengefasst</h2>
<ol>
<li>Nimm an, dass niemand anwesend ist</li>
<li>Sobald ein beliebiger Sensor im Raum ausgelöst wurde, nimm an, dass jemand anwesend ist</li>
<li>Falls die Zimmertür geöffnet wurde, gehe zurück zu Zustand 1.)</li>
</ol>
<p>Die oben genannten Zustände lassen sich problemlos als Automatisierungsregeln in deinem Smart-Home-Server umsetzen. Für Home Assistant kann ein solches Setup die <a href="https://www.home-assistant.io/integrations/input_boolean" rel="noopener">input_boolean-Integration</a><a class="arch" href="https://web.archive.org/web/20210722/https://www.home-assistant.io/integrations/input_boolean"
       title="Archiviert am 2021-07-22"
       rel="nofollow noopener">[arch]</a> nutzen, um den Zustand zu speichern, ob in einem Raum Präsenz erkannt wurde oder nicht.</p>
<h2 id="erweiterte-präsenzerkennung">Erweiterte Präsenzerkennung</h2>
<p>Der hier beschriebene Algorithmus erlaubt dir zwar zu erkennen, ob <em>irgendjemand</em> in einem bestimmten Raum anwesend ist, kann aber nicht bestimmen, <em>welche</em> Person anwesend ist. Dafür gibt es andere Ansätze, die Thema eines zukünftigen Blogposts sein könnten.</p>
<p>Bis dahin kannst du &lsquo;Biene in der Kiste&rsquo; zumindest für einfache Szenarien nutzen, wie zum Beispiel &lsquo;Licht ausschalten, wenn niemand im Raum ist&rsquo;.</p>
]]></content:encoded>
      <category>iot</category><category>smart-home</category><category>homeassistant</category>
    </item>
    
    <item>
      <title>ESP32 als BLE-Gateway für Xiaomi Mijia BLE Temperatur- &amp; Feuchtigkeitssensor</title>
      <link>https://blog.goroot.de/de/post/esp32-ble-gateway/</link>
      <pubDate>Thu, 01 Jul 2021 14:37:30 +0100</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/esp32-ble-gateway/</guid>
      <description>Der Xiaomi LYWSD03MMC Feuchtigkeitssensor ist einer der beliebtesten BLE-Sensoren auf dem Markt — günstig, batteriebetrieben und mit einem ESP32 als BLE-Gateway leicht zu integrieren.</description>
      <content:encoded><![CDATA[<h2 id="problemstellung">Problemstellung</h2>
<p><del>Zu viele ungenutzte BLE-Sensoren, zu viel Zeit während des COVID-19-Lockdowns — lass uns noch mehr Sensoren hinzufügen! Nur weil wir es können&hellip;</del></p>
<p>Nein, im Ernst. Ich hatte in mehreren Bereichen meines Hauses stark mit zu hoher Luftfeuchtigkeit zu kämpfen. Nach dem heißen Duschen im Bad kondensierte die Feuchtigkeit am Fenster, was das Risiko von Schimmelbildung erhöhte. Im Keller, wo unsere Lebensmittelvorräte lagern, hatten wir Probleme mit feucht werdendem Salz. Und da ich außerdem ein <a href="https://blog.goroot.de/de/post/tasmota-homeassistant-pH-measurement/">Riffbecken besitze, das an mein Smart-Home-System angebunden ist</a> und das Wasser über Verdunstung kühlt, ist die Luftfeuchtigkeit im Wohnzimmer ebenfalls eine sehr interessante Messgröße. Also wa</p>
<h2 id="ble-sensoren-mit-home-assistant-verbinden">BLE-Sensoren mit Home Assistant verbinden</h2>
<p>Home Assistant bietet bereits <a href="https://www.home-assistant.io/integrations/mitemp_bt/" rel="noopener">eine eigene Integration für Xiaomi-Mijia-Sensoren</a><a class="arch" href="https://web.archive.org/web/20210701/https://www.home-assistant.io/integrations/mitemp_bt/"
       title="Archiviert am 2021-07-01"
       rel="nofollow noopener">[arch]</a>, daher läge es nahe, einfach einen BLE-Stick anzuschließen und loszulegen. Aus zwei Gründen habe ich mich gegen diese Option entschieden:</p>
<ol>
<li>BLE ist dafür gemacht, Geräte in direkter Sichtverbindung zum steuernden Gerät (oder zumindest in dessen unmittelbarer Nähe) zu steuern. Da meine Sensoren im ganzen Haus verteilt sein werden, könnte ein einzelner BLE-Stick in meinem Serverschrank möglicherweise nicht mit allen Sensoren kommunizieren.</li>
<li>Ich versuche konsequent, die Verarbeitung / Darstellung meiner Smart-Home-Daten von der Datenerfassung / Hardware-Anbindung zu trennen. Vor ein paar Jahren habe ich mit <a href="https://www.openhab.org" rel="noopener">OpenHAB</a><a class="arch" href="https://web.archive.org/web/20210701/https://www.openhab.org"
       title="Archiviert am 2021-07-01"
       rel="nofollow noopener">[arch]</a> auf einem Raspberry Pi angefangen, der direkt mit jeder einzelnen Hardware verbunden war. Das endete darin, dass Dutzende Kabel durch meine Wohnung liefen, nur um einen DS18S20 auf dem Balkon anzuschließen. Dahin will ich nicht zurück!</li>
</ol>
<h2 id="ein-esp32-board-in-ein-ble-gateway-verwandeln">Ein ESP32-Board in ein BLE-Gateway verwandeln</h2>
<p>Auch hier habe ich mich auf meine bevorzugte ESP-Firmware <a href="http://tasmota.github.io" rel="noopener">Tasmota</a><a class="arch" href="https://web.archive.org/web/20210701/http://tasmota.github.io"
       title="Archiviert am 2021-07-01"
       rel="nofollow noopener">[arch]</a> verlassen. Zum Glück hat Tasmota vor einigen Versionen <a href="https://tasmota.github.io/docs/ESP32/" rel="noopener">ESP32-Unterstützung</a><a class="arch" href="https://web.archive.org/web/20210701/https://tasmota.github.io/docs/ESP32/"
       title="Archiviert am 2021-07-01"
       rel="nofollow noopener">[arch]</a> eingeführt. Auch wenn sie offiziell noch im Beta-Stadium ist, funktioniert zumindest die BLE-Kommunikation absolut zuverlässig.</p>
<ol>
<li>Folge den Anweisungen <a href="https://tasmota.github.io/docs/ESP32/#flashing" rel="noopener">hier</a><a class="arch" href="https://web.archive.org/web/20210701/https://tasmota.github.io/docs/ESP32/#flashing"
       title="Archiviert am 2021-07-01"
       rel="nofollow noopener">[arch]</a>, um deinen ESP mit der erforderlichen Firmware zu flashen. Achte darauf, die ESP32-Binary mit dem Suffix <code>bluetooth</code> zu verwenden, damit alle benötigten Treiber enthalten sind.</li>
<li>Logge dich in die Tasmota-Web-UI ein und navigiere zu <code>Configuration</code> -&gt; <code>Configure BLE</code> und aktiviere <code>Enable Bluetooth</code>.</li>
<li>Sobald du die Einstellungen speicherst, sollten in der darunterliegenden Liste <code>Devices Seen</code> alle Arten von BLE-Geräten aufgeführt werden.
<figure>
  <img src="tasmotablelist.jpg" alt="" loading="lazy" decoding="async"></figure>
</li>
</ol>
<p>Jetzt ist alles bereit, um deinen ESP32 als BLE-Gateway zu nutzen. Konfiguriere ihn einfach so, dass er mit deinem MQTT-Server spricht, genau wie du es bei jedem anderen Tasmota-Gerät auch tun würdest.</p>
<h2 id="individuelle-firmware-für-ble-sensoren">Individuelle Firmware für BLE-Sensoren</h2>
<p>Die LYWSD03MMC-Sensoren von Xiaomi kommen bereits mit einer Werksfirmware, die sich sofort verwenden lässt, um sich mit deinem Gateway zu verbinden. Es gibt jedoch einige Nachteile:</p>
<ol>
<li>Der Batteriestatus wird nicht korrekt gemeldet</li>
<li>Eine aktive Verbindung ist nötig, um Daten abzufragen — das entlädt die Batterie sehr schnell</li>
<li>Vertraue niemals einer Nicht-Open-Source-Firmware ;-)</li>
</ol>
<p>Eine individuelle Firmware auf das Gerät zu bringen ist ziemlich einfach. Folge einfach den großartigen Anleitungen <a href="https://github.com/atc1441/ATC_MiThermometer" rel="noopener">im Repository von atc1441</a><a class="arch" href="https://web.archive.org/web/20210701/https://github.com/atc1441/ATC_MiThermometer"
       title="Archiviert am 2021-07-01"
       rel="nofollow noopener">[arch]</a>, um sie zu flashen. Für den Flash-Vorgang selbst benötigst du keine zusätzlichen Tools. Es gibt ein browserbasiertes Tool, das in allen modernen Browsern funktioniert.</p>
<h2 id="fazit">Fazit</h2>
<p>Ich habe jetzt drei dieser Sensoren mit meinem Home Assistant verbunden. Weitere sind bereits bestellt. Die Verbindung ist absolut zuverlässig, und dank der individuellen Firmware werde ich auch benachrichtigt, falls einem der Geräte bald der Akku ausgeht.
Vorgefertigte Geräte zu verwenden, statt meine eigene Schaltung zu bauen, hat mir eine Menge Zeit gespart. Definitiv empfehlenswert!</p>
]]></content:encoded>
      <category>iot</category><category>sensors</category><category>smart-home</category><category>xiaomi</category><category>homeassistant</category>
    </item>
    
    <item>
      <title>Den pH-Wert deines Riffbeckens mit Tasmota überwachen</title>
      <link>https://blog.goroot.de/de/post/tasmota-homeassistant-pH-measurement/</link>
      <pubDate>Mon, 01 Mar 2021 21:09:03 +0000</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/tasmota-homeassistant-pH-measurement/</guid>
      <description>Die Überwachung der Wasserwerte ist essenziell, besonders für Besitzer von Riffbecken. Ich wollte meinen Tröpfchentest nicht mehr von Hand machen, also habe ich mein Becken stattdessen an Home Assistant angebunden.</description>
      <content:encoded><![CDATA[<p>Die Überwachung der Wasserwerte deines Aquariums ist essenziell. Um dein Riff-Ökosystem am Leben zu erhalten, müssen mehrere Dutzend Wasserwerte gemessen werden. Das ständig manuell zu erledigen, kann ziemlich nervig sein. Also habe ich damit begonnen, die Messung über <a href="http://tasmota.github.io" rel="noopener">Tasmota</a><a class="arch" href="https://web.archive.org/web/20210301/http://tasmota.github.io"
       title="Archiviert am 2021-03-01"
       rel="nofollow noopener">[arch]</a> zu automatisieren. Einer der wichtigsten Leitindikatoren für ein Aquarium ist der pH-Wert des Wassers. Die Messung des pH-Werts gibt dir auch einen guten Hinweis auf mehrere andere Wasserwerte.</p>
<h2 id="tasmota-kompatible-hardware">Tasmota-kompatible Hardware</h2>
<p>Es gibt nicht viele Optionen, um den pH-Wert in einem DIY-Setup zu messen. Genau genommen gibt es nur zwei generelle Optionen: Entweder du entscheidest dich für <a href="https://atlas-scientific.com/embedded-solutions/ezo-ph-circuit/" rel="noopener">Atlas Scientifics EZO-pH-Schaltkreis</a><a class="arch" href="https://web.archive.org/web/20210301/https://atlas-scientific.com/embedded-solutions/ezo-ph-circuit/"
       title="Archiviert am 2021-03-01"
       rel="nofollow noopener">[arch]</a>, oder für eine günstige analoge pH-Sonde. Die erste Option (inklusive pH-Sonde, Schaltkreis, zusätzlichem Trägerboard) hätte etwa 100 US-Dollar plus Versand gekostet. Die analoge Sonden-Option kostet nur etwa 20 US-Dollar. Diese Sonden kannst du praktisch überall kaufen.</p>
<p>Diese analogen Sonden-Kits kommen bereits mit einer Sonde und dem Schaltkreis. Sie haben meist eine 3-polige Stiftleiste angelötet: VCC, GND und Analog Out. Sie an einen <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html" rel="noopener">Wemos D1 Mini</a><a class="arch" href="https://web.archive.org/web/20210301/https://www.wemos.cc/en/latest/d1/d1_mini.html"
       title="Archiviert am 2021-03-01"
       rel="nofollow noopener">[arch]</a> anzuschließen ist eine Sache von Sekunden. Achte nur darauf, die 3.3V-Schiene als VCC zu verwenden, damit der Analogausgang das erlaubte Maximum von 3.3V nicht überschreiten kann.</p>
<h2 id="tasmota-um-unterstützung-für-analoge-ph-sonden-erweitern">Tasmota um Unterstützung für analoge pH-Sonden erweitern</h2>
<p>Da Tasmota keine analogen pH-Schaltkreise unterstützte, habe ich <a href="https://github.com/arendst/Tasmota/pull/10415" rel="noopener">die nötigen Änderungen einfach selbst zum Tasmota-Projekt beigetragen</a><a class="arch" href="https://web.archive.org/web/20210301/https://github.com/arendst/Tasmota/pull/10415"
       title="Archiviert am 2021-03-01"
       rel="nofollow noopener">[arch]</a>. Ab <a href="https://github.com/arendst/Tasmota/releases/tag/v9.3.0" rel="noopener">Version 9.3.0</a><a class="arch" href="https://web.archive.org/web/20210301/https://github.com/arendst/Tasmota/releases/tag/v9.3.0"
       title="Archiviert am 2021-03-01"
       rel="nofollow noopener">[arch]</a> unterstützt Tasmota jetzt eine pH-Sonde an Analogeingängen.</p>
<p>Wie du sehen kannst, lässt sich &ldquo;ADC pH&rdquo; für deinen Analogeingang konfigurieren:
<figure>
  <img src="./tasmota_configuration.png" alt="" loading="lazy" decoding="async"></figure>
</p>
<h2 id="tasmota-ph-messwerte-kalibrieren">Tasmota-pH-Messwerte kalibrieren</h2>
<p>Da es sich um eine analoge Sonde handelt, müssen wir sie kalibrieren. Dafür werden mindestens zwei Kalibrierlösungen mit unterschiedlichem pH-Wert benötigt. Du kannst sie entweder als Pulver im Internet bestellen, oder bereits fertig gemischt bei deinem lokalen Aquarienhändler bekommen.</p>
<p>Kalibrierprozess:</p>
<ol>
<li>Sonde in die Lösung mit dem niedrigeren pH-Wert legen. Der pH-Wert der Lösung ist ANALOG_PH_CALSOLUTION_LOW_PH.</li>
<li>Warten, bis sich der Analogwert / RAW-Wert stabilisiert (~3 Minuten)</li>
<li>Der Analogmesswert ist ANALOG_PH_CALSOLUTION_LOW_ANALOG_VALUE</li>
<li>Sonde reinigen und in die Lösung mit dem höheren pH-Wert legen. Der pH-Wert der Lösung ist ANALOG_PH_CALSOLUTION_HIGH_PH.</li>
<li>Warten, bis sich der Analogwert / RAW-Wert stabilisiert (~3 Minuten)</li>
<li>Der Analogmesswert ist ANALOG_PH_CALSOLUTION_HIGH_ANALOG_VALUE</li>
</ol>
<p>Analogmesswerte lassen sich entweder auslesen, indem du die Konfiguration des Analog-Ports während der Kalibrierung auf &ldquo;Analog Input&rdquo; umstellst, oder indem du Debug-Logs in der Konsole aktivierst und stattdessen den RAW-Wert betrachtest.</p>
<p>Nachdem du die Werte notiert hast, führst du folgenden Befehl in der Tasmota-Konsole aus:
<code>AdcParam 9 &lt;ANALOG_PH_CALSOLUTION_LOW_PH&gt;, &lt;ANALOG_PH_CALSOLUTION_LOW_ANALOG_VALUE&gt;, &lt;ANALOG_PH_CALSOLUTION_HIGH_PH&gt;, &lt;ANALOG_PH_CALSOLUTION_HIGH_ANALOG_VALUE&gt;</code></p>
<p>Deine Sonde ist jetzt kalibriert und sollte dir den korrekten pH-Messwert liefern. Ich habe diese Dokumentation auch zur <a href="https://tasmota.github.io/docs/Commands/#sensors" rel="noopener">offiziellen Tasmota-Dokumentation</a><a class="arch" href="https://web.archive.org/web/20210301/https://tasmota.github.io/docs/Commands/#sensors"
       title="Archiviert am 2021-03-01"
       rel="nofollow noopener">[arch]</a> beigetragen.</p>
<h2 id="tasmota-ph-sensor-mit-home-assistant-verbinden">Tasmota-pH-Sensor mit Home Assistant verbinden</h2>
<p>Dieser Schritt ist ziemlich unkompliziert: Tasmota kommuniziert mit Home Assistant über MQTT. Du kannst einfach der <a href="https://tasmota.github.io/docs/Home-Assistant/" rel="noopener">Standard-Tasmota-Dokumentation</a><a class="arch" href="https://web.archive.org/web/20210301/https://tasmota.github.io/docs/Home-Assistant/"
       title="Archiviert am 2021-03-01"
       rel="nofollow noopener">[arch]</a> folgen und schon bist du fertig.</p>
<p>Nachdem dein Sensor ein paar Stunden gelaufen ist, solltest du deinen pH-Wert sehen können:</p>
<p><figure>
  <img src="phgraph.png" alt="" loading="lazy" decoding="async"></figure>
</p>
<p>Funktioniert wie ein Uhrwerk!</p>
]]></content:encoded>
      <category>tasmota</category><category>iot</category><category>smart-home</category><category>reef-tank</category>
    </item>
    
    <item>
      <title>Netzwerksegmentierung: IoT- vs. NoT-Netzwerke</title>
      <link>https://blog.goroot.de/de/post/network-segregation-IoT-vs-NoT/</link>
      <pubDate>Thu, 18 Feb 2021 17:32:40 +0100</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/network-segregation-IoT-vs-NoT/</guid>
      <description>Dutzende IoT-Geräte im eigenen privaten Netzwerk zu haben, ist eine Sicherheitslücke — Geräte, die nach Hause telefonieren, können still und heimlich eine Route vom Internet in dein lokales Netzwerk öffnen.</description>
      <content:encoded><![CDATA[<p>IoT-Geräte sind in den letzten Jahren sehr populär geworden. Alles ist ständig vernetzt. Was nach einer guten Idee klingt, kann tatsächlich zu einer Sicherheitslücke in deinem Netzwerk führen. Da nur der Hersteller weiß, was ein Gerät tatsächlich tut, könnten deine IoT-Geräte Funktionen mitbringen, die dir nicht gefallen.</p>
<h2 id="was-ist-das-problem-mit-meinem-iot-gerät">Was ist das Problem mit meinem IoT-Gerät?</h2>
<p>Die kurze Antwort lautet: Du besitzt es, aber du kontrollierst es nicht. Aber&hellip;</p>
<p>Nehmen wir als Beispiel ein IoT-Gerät mit Sprachassistent. Die kleinen Boxen, die in jedem Zimmer stehen, reagieren auf deine Spracheingaben. Die aktuelle Wettervorhersage ist nur einen Sprachbefehl entfernt. Aber was passiert eigentlich im Hintergrund? Der smarte Lautsprecher zeichnet deine Stimme auf, sendet die Aufnahme an einen öffentlichen Cloud-Anbieter, der analysiert, was du gesagt hast, und führt einfach aus, was ihm von der Cloud befohlen wird. In unserem Beispiel: die aktuelle Wettervorhersage vorlesen.</p>
<p>Ein weiteres, eher physisches Beispiel wäre ein Bewegungsmelder, der an einen Internetdienst meldet, ob jemand erkannt wurde. In deinem lokalen Netzwerk werden keine Befehle ausgeführt, aber wer sich Zugang zu den Daten im Cloud-Dienst verschafft, könnte leicht herausfinden, ob du zu Hause bist. Wäre ich der Bösewicht, würde ich warten, bis die Cloud mir &rsquo;niemand zu Hause&rsquo; meldet, bevor ich in dein Haus einbreche.</p>
<p>Was ist hier also eigentlich passiert? Dein IoT-Gerät führt Befehle aus oder gibt Informationen preis, die nicht unter deiner Kontrolle stehen. Wie stellst du sicher, dass ein solcher Befehl nicht darin besteht, deine Bankdaten von deinem PC in die Cloud hochzuladen? Würdest du ein großes Schild an deine Haustür hängen, auf dem steht &ldquo;Haus ist leer, niemand zu Hause&rdquo;?</p>
<h2 id="iot--und-not-geräte-in-einem-segmentierten-netzwerk-platzieren">IoT- und NoT-Geräte in einem segmentierten Netzwerk platzieren</h2>
<p>IoT — das <strong>I</strong>nternet <strong>o</strong>f <strong>T</strong>hings — ist ein bekannter Begriff. Er beschreibt Geräte, die mit dem Internet verbunden sein müssen, um richtig zu funktionieren. NoT — <strong>N</strong>etwork <strong>o</strong>f <strong>T</strong>hings — beschreibt Geräte, die mit deinem Netzwerk verbunden sind, aber kein Internet benötigen, um richtig zu funktionieren. Beide Geräteklassen sollten von deinem lokalen Netzwerk isoliert werden. Falls dein Router jedoch keine 3 separaten Netzwerke unterstützt, gibt es auch Alternativen, um das mit weniger als 3 umzusetzen.</p>
<p>Falls dein Router ein Gastnetzwerk unterstützt, aber keine weiteren zusätzlichen Netzwerke, kannst du das Gastnetzwerk als kombinierten Ersatz für die hier beschriebenen IoT- und NoT-Netzwerke nutzen.</p>
<p>Geräte für das <em>IoT</em>-Netzwerk sind Geräte, die ohne Internetverbindung nicht funktionieren. Beispiele dafür sind smarte Lautsprecher, Internetradios oder webbasierte Wetterstationen.</p>
<p>Geräte für das <em>NoT</em>-Netzwerk sind Geräte, die mit deinem Heimnetzwerk kommunizieren müssen, aber nicht mit dem Internet. Zum Beispiel smarte Glühbirnen oder WLAN-Überwachungskameras.</p>
<h2 id="wie-man-iot--und-not-geräte-in-einem-von-unifi-verwalteten-netzwerk-segmentiert">Wie man IoT- und NoT-Geräte in einem von Unifi verwalteten Netzwerk segmentiert</h2>
<p><a href="https://unifi-network.ui.com" rel="noopener">Ubiquiti-Netzwerkkomponenten</a><a class="arch" href="https://web.archive.org/web/20210218/https://unifi-network.ui.com"
       title="Archiviert am 2021-02-18"
       rel="nofollow noopener">[arch]</a> sind erschwingliche Geräte, die viele professionelle Funktionen mitbringen. Eine dieser Funktionen ist die Aufteilung deines Netzwerks in mehrere VLANs. Genau das nutzen wir in unserem Setup.</p>
<p>Gehe in deinem UniFi-Controller zu Settings -&gt; Network. Erstelle ein neues IoT-Netzwerk mit den hier gezeigten Einstellungen:
<figure>
  <img src="IoTNetworkConfiguration.png" alt="" loading="lazy" decoding="async"></figure>

Du kannst auch einen anderen IP-Bereich oder eine andere VLAN-ID verwenden. Ich halte es gerne einfach und nutze dieselbe VLAN-ID wie im dritten Oktett meines IP-Bereichs.</p>
<p>Mach jetzt einfach dasselbe mit dem NoT-Netzwerk:
<figure>
  <img src="NoTNetworkConfiguration.png" alt="" loading="lazy" decoding="async"></figure>
</p>
<p>Da wir jetzt zwei Netzwerke haben, kannst du auch zwei WLANs konfigurieren, damit auch kabellose Clients mit deinem Netzwerk verbunden werden können. Ich zeige hier nur das IoT-WLAN, da die Einstellungen für das NoT-WLAN weitgehend identisch sind:</p>
<p><figure>
  <img src="IoTWirelessNetwork.png" alt="" loading="lazy" decoding="async"></figure>
</p>
<p>Jetzt ist alles eingerichtet: 2 zusätzliche VLANs / Netzwerke stehen zur Verfügung, du musst nur noch deine Geräte damit verbinden.</p>
<h3 id="firewall-konfiguration">Firewall-Konfiguration</h3>
<p>Die Netzwerke können momentan noch mit deinem Hauptnetzwerk und auch untereinander kommunizieren. Um das zu verhindern, müssen wir die Firewall entsprechend konfigurieren.</p>
<p><figure>
  <img src="FirewallConfig.png" alt="" loading="lazy" decoding="async"></figure>
</p>
<p>Gehen wir die Regeln durch:</p>
<table>
	<thead>
			<tr>
					<th>ID</th>
					<th>Name</th>
					<th>Beschreibung</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>2000</td>
					<td>Established und Related erlauben</td>
					<td>Erlaubt Verbindungen, die bereits aufgebaut sind. Wir kontrollieren nur den Verbindungsaufbau. Ist dieser durch eine andere Regel erlaubt, sorgt diese Regel dafür, dass der weitere Datenfluss innerhalb derselben Verbindung nicht blockiert wird.</td>
			</tr>
			<tr>
					<td>2001</td>
					<td>Alle lokalen zu NTP</td>
					<td>Erlaubt NTP-Verkehr (Network Time Protocol) aus allen lokalen Netzwerken. Dadurch können NoT- und IoT-Geräte die aktuelle Uhrzeit von einem Zeitserver beziehen</td>
			</tr>
			<tr>
					<td>2002</td>
					<td>Alle NoT verwerfen</td>
					<td>Verwirft alle Verbindungen, die das NoT-Netzwerk aufzubauen versucht. Verbindungen, die vom LAN zum NoT-Netzwerk initiiert werden, sind davon nicht betroffen — hier sorgt Regel 2000 dafür, dass Daten frei fließen können</td>
			</tr>
			<tr>
					<td>2003</td>
					<td>IoT zu MQTT-Server</td>
					<td>Erlaubt Verbindungen zu meinem Smart-Home-Server für das MQTT-Protokoll. Das ist erforderlich, damit sich meine smarten Geräte mit meinem <a href="https://www.home-assistant.io" rel="noopener">Home-Assistant</a><a class="arch" href="https://web.archive.org/web/20210218/https://www.home-assistant.io"
       title="Archiviert am 2021-02-18"
       rel="nofollow noopener">[arch]</a>-Server verbinden können.</td>
			</tr>
			<tr>
					<td>2005</td>
					<td>IoT zu NoT verwerfen</td>
					<td>Verwirft alle Verbindungsversuche vom IoT- zum NoT-Netzwerk</td>
			</tr>
			<tr>
					<td>4000</td>
					<td>IoT zu LAN verwerfen</td>
					<td>Verwirft alle Verbindungsversuche vom IoT-Netzwerk zum LAN</td>
			</tr>
	</tbody>
</table>
<p>Dieses Setup führt zu folgendem Ergebnis:</p>
<ul>
<li>Das IoT-Netzwerk kann nur Verbindungen zum Internet aufbauen</li>
<li>Das NoT-Netzwerk kann überhaupt keine Verbindungen aufbauen</li>
<li>Das LAN kann Verbindungen zum IoT- und NoT-Netzwerk aufbauen</li>
</ul>
<p>Da keines deiner IoT- oder NoT-Geräte mehr mit deinem Hauptnetzwerk (LAN) kommunizieren kann, sind die vertrauenswürdigen Geräte in diesem Netzwerk jetzt sicher.</p>
<h3 id="mdns-erkennung-aktivieren">mDNS-Erkennung aktivieren</h3>
<p>Mit dem obigen Setup könntest du Probleme bekommen, deine NoT- und IoT-Geräte z. B. von deinem Smartphone im LAN-Netzwerk aus zu finden. Das liegt daran, dass diese Geräte oft <a href="https://en.wikipedia.org/wiki/Multicast_DNS" rel="noopener">mDNS</a><a class="arch" href="https://web.archive.org/web/20210218/https://en.wikipedia.org/wiki/Multicast_DNS"
       title="Archiviert am 2021-02-18"
       rel="nofollow noopener">[arch]</a> verwenden, um die Anwesenheit eines Geräts zu erkennen. Da mDNS keine VLAN-Grenzen überschreiten kann (auch nicht mit einer dedizierten Firewall-Regel!), müssen wir sicherstellen, dass die Geräte ihre Ankündigungen über alle Netzwerke hinweg senden können.</p>
<p>Glücklicherweise erlaubt uns UniFi genau das zu konfigurieren, indem wir den Schalter &ldquo;Enable Multicast DNS&rdquo; aktivieren. Du findest diesen Schalter unter Settings -&gt; Services -&gt; MDNS.</p>
<p>Dadurch können nur die Ankündigungen über VLAN-Grenzen hinweg fließen, aber <em>nicht</em> die eigentlichen Verbindungen. Daher werden dadurch keine der zuvor eingerichteten Sicherheitsmaßnahmen ausgehebelt.</p>
]]></content:encoded>
      <category>iot</category><category>security</category><category>network</category><category>smart-home</category>
    </item>
    
    <item>
      <title>Unbeaufsichtigte Docker-Container-Updates</title>
      <link>https://blog.goroot.de/de/post/unattended-container-updates/</link>
      <pubDate>Sat, 13 Jul 2019 14:32:03 +0000</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/unattended-container-updates/</guid>
      <description>Deine Dienste in Containern laufen zu lassen ist praktisch — aber sie aktuell zu halten ist eine andere Geschichte, und es gibt bequemere Wege als das von Hand zu tun.</description>
      <content:encoded><![CDATA[<p>Um deine Container-Infrastruktur aktuell und damit sicher zu halten, gibt es zwei primäre Ziele, die du erreichen musst:</p>
<ol>
<li>Das Betriebssystem des Hosts aktuell halten</li>
<li>Den Inhalt deiner Container aktuell halten</li>
</ol>
<h2 id="das-betriebssystem-aktualisieren">Das Betriebssystem aktualisieren</h2>
<p>Dein Betriebssystem zu aktualisieren ist ziemlich unkompliziert. Nutze einfach die bekannten Paketmanager, die das für dich erledigen. Für Debian wäre das zum Beispiel so einfach wie regelmäßig <code>apt-get update &amp;&amp; apt-get upgrade</code> auszuführen.</p>
<p>Um die Ausführung dieser beiden Befehle zu automatisieren, gibt es mehrere Ansätze. Entweder du konfigurierst einen Cronjob, der diese Befehle regelmäßig ausführt, oder du entscheidest dich für eine ausgefeiltere Lösung.</p>
<p>Für <code>apt</code> gibt es ein Paket namens <code>unattended-upgrades</code>. Das Paket kümmert sich darum, täglich alle Sicherheitsupdates zu installieren. Es lässt sich vollständig über <code>/etc/apt/apt.conf.d/50-unattended-Upgrades</code> konfigurieren.</p>
<h2 id="container-aktualisieren">Container aktualisieren</h2>
<p>Um Docker-Container automatisch zu aktualisieren, musst du nicht nur den Upgrade-Prozess selbst berücksichtigen.</p>
<h3 id="das-risiko-der-inkompatibilität">Das Risiko der Inkompatibilität</h3>
<p>Es gibt viele Container, die nicht automatisch sicherstellen, dass auch die Daten (z. B. Datenbanken oder Konfigurationen) aktualisiert werden. Es könnte sogar eine neuere Version deines Dienstes geben, die mit der Datenbank in deinem Sidecar-Container nicht kompatibel ist. Wenn du nur einen von beiden aktualisierst, könntest du am Ende mit völlig unbrauchbaren Diensten dastehen.</p>
<p>Um dieses Risiko zu mindern, kannst du mehrere Dinge tun:</p>
<ul>
<li>Nutze <strong>feste Container-Versionen</strong> in deiner <code>docker-compose.yaml</code>. Wenn du keinen Image-Tag angibst, verwendet Docker implizit <code>latest</code> als Image-Tag. Das bedeutet, dass du bei einem unbeaufsichtigten Upgrade immer die neueste Version bekommst — selbst wenn diese Version Änderungen mit sich bringt, die die API zu einem deiner anderen Dienste brechen. Wirf einfach einen Blick auf die verfügbaren Tags für dein Image. Oft gibt es einen Tag, mit dem du dich nur auf die Hauptversion des Images beschränken kannst. Zum Beispiel würde <code>nginx:2</code> nur Upgrades auf andere Versionen erlauben, die mit <code>2</code> beginnen, was hoffentlich keine brechenden API-Änderungen enthält.</li>
<li>Stelle sicher, dass du automatisierte <strong>Daten-Backups</strong> eingerichtet hast. Zum Beispiel, indem du <a href="/using-the-cloud-as-panic-room/">die Cloud als deinen Panic Room nutzt</a>. Wenn du eine neue Version eines Images ziehst, besteht immer das Risiko, dass deine Daten beschädigt werden. Das gilt sogar für Images, die deine Daten migrieren. Migrationsprozesse können scheitern!</li>
<li>Richte <strong>Monitoring mit Alarmierung</strong> ein. Für den seltenen Fall, dass ein Upgrade fehlschlägt, musst du das sofort erfahren. Monitoring-Systeme wie <a href="https://Prometheus.io" rel="noopener">Prometheus</a><a class="arch" href="https://web.archive.org/web/20190713/https://Prometheus.io"
       title="Archiviert am 2019-07-13"
       rel="nofollow noopener">[arch]</a> bieten eine einfache Möglichkeit zu erkennen, wenn ein Container ausgefallen oder ein Webdienst nicht mehr erreichbar ist.</li>
</ul>
<h3 id="unbeaufsichtigte-container-updates-via-cron">Unbeaufsichtigte Container-Updates via Cron</h3>
<p>Um unbeaufsichtigte Container-Upgrades umzusetzen, kannst du die Standard-Docker-Befehle nutzen, die du bereits kennst. Lege einfach das folgende Skript in <code>/etc/cron.daily/</code> ab, und du bist startklar:</p>
<div class="codeblock" id="c1">
  <div class="code-head">
    <span class="f">/etc/cron.daily/update-containers</span>
    
    <a class="anchor" href="#c1-1">#L1-L5</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln" id="c1-1"><a class="lnlinks" href="#c1-1">1</a></span><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln" id="c1-2"><a class="lnlinks" href="#c1-2">2</a></span><span class="cl">
</span></span><span class="line"><span class="ln" id="c1-3"><a class="lnlinks" href="#c1-3">3</a></span><span class="cl"><span class="nb">cd</span> /my-docker-compose-project
</span></span><span class="line"><span class="ln" id="c1-4"><a class="lnlinks" href="#c1-4">4</a></span><span class="cl">docker-compose pull
</span></span><span class="line"><span class="ln" id="c1-5"><a class="lnlinks" href="#c1-5">5</a></span><span class="cl">docker-compose up -d --remove-orphans</span></span></code></pre></div>
  
</div>
<p>Dadurch werden täglich die neuesten, in deinem docker-compose-Projekt referenzierten Image-Versionen heruntergeladen, die Container mit den aktualisierten Images neu gestartet und alte, nicht mehr im Projekt enthaltene Container entfernt.</p>
<h3 id="unbeaufsichtigte-container-updates-via-watchtower">Unbeaufsichtigte Container-Updates via Watchtower</h3>
<p>Es gibt ein hervorragendes Open-Source-Projekt namens <a href="https://blog.goroot.de/using-the-cloud-as-panic-room/">Watchtower</a>, das im Grunde selbst ein Docker-Container ist. Es überwacht die Registries aller Images, die auf deinem Docker-Daemon laufen. Wird ein Image in der Registry aktualisiert, lädt es das Image herunter und startet deinen Dienst einfach mit der alten Konfiguration neu. Genial, oder?</p>
<h4 id="watchtower-in-ein-docker-compose-projekt-einbinden">Watchtower in ein docker-compose-Projekt einbinden</h4>
<p>Füge deiner Docker-Compose-Datei eine weitere Dienstkonfiguration für Watchtower hinzu.</p>
<div class="codeblock" id="c2">
  <div class="code-head">
    <span class="f">docker-compose.yaml</span>
    
    <a class="anchor" href="#c2-1">#L1-L7</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln" id="c2-1"><a class="lnlinks" href="#c2-1">1</a></span><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c2-2"><a class="lnlinks" href="#c2-2">2</a></span><span class="cl"><span class="w">    </span><span class="nt">watchtower</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c2-3"><a class="lnlinks" href="#c2-3">3</a></span><span class="cl"><span class="w">        </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c2-4"><a class="lnlinks" href="#c2-4">4</a></span><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">containrrr/watchtower</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c2-5"><a class="lnlinks" href="#c2-5">5</a></span><span class="cl"><span class="w">        </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">watchtower</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c2-6"><a class="lnlinks" href="#c2-6">6</a></span><span class="cl"><span class="w">        </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c2-7"><a class="lnlinks" href="#c2-7">7</a></span><span class="cl"><span class="w">            </span>- <span class="l">/var/run/docker.sock:/var/run/docker.sock</span></span></span></code></pre></div>
  
</div>
<p>Dadurch läuft Watchtower in seiner Standardkonfiguration, die alle Container im System überwacht. Es gibt viele Konfigurationsmöglichkeiten. Schau einfach in die <a href="https://containrrr.github.io/watchtower/" rel="noopener">Dokumentation</a><a class="arch" href="https://web.archive.org/web/20190713/https://containrrr.github.io/watchtower/"
       title="Archiviert am 2019-07-13"
       rel="nofollow noopener">[arch]</a>.</p>
]]></content:encoded>
      <category>docker</category><category>monitoring</category><category>security</category><category>debian</category><category>linux</category>
    </item>
    
    <item>
      <title>Docker-Image-Review: jwilder/nginx-proxy</title>
      <link>https://blog.goroot.de/de/post/docker-image-review-jwilder-nginx-proxy/</link>
      <pubDate>Mon, 29 Jan 2018 10:18:00 +0000</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/docker-image-review-jwilder-nginx-proxy/</guid>
      <description>Schon mal gefragt, ob es eine gute Alternative für Szenarien gibt, in denen ein vollwertiger Ingress-Proxy Overkill wäre? Hier kommt sie — und die Konfiguration kennst du schon, denn sie basiert auf nginx.</description>
      <content:encoded><![CDATA[<p>Schon mal gefragt, ob es eine gute Alternative für Szenarien gibt, in denen ein vollwertiger Ingress-Proxy Overkill wäre? Hier kommt sie:
<code>jwilder/nginx-proxy</code>. Das Image kann besonders nützlich sein, wenn man zum ersten Mal ein Ingress-Konzept aufsetzt. <a href="https://www.nginx.com" rel="noopener">Nginx</a><a class="arch" href="https://web.archive.org/web/20180129/https://www.nginx.com"
       title="Archiviert am 2018-01-29"
       rel="nofollow noopener">[arch]</a> ist in der Welt der klassischen Serveradministration und des Webhostings bestens bekannt. Warum also nicht den vertrauten Webserver auch in der containerisierten Umgebung nutzen?
Verglichen mit vollwertigen Ingress-Routern wie <a href="https://traefik.io" rel="noopener">Traefik</a><a class="arch" href="https://web.archive.org/web/20180129/https://traefik.io"
       title="Archiviert am 2018-01-29"
       rel="nofollow noopener">[arch]</a> erlaubt dir das Image, dein vorhandenes Know-how und auch einige deiner Konfigurationen weiterzuverwenden. Das Konzept eines Ingress-Routers ähnelt einem Reverse Proxy. Es geht darum, alle anderen Dienste hinter einem einzigen Container zu verstecken, so wie du es mit einem Reverse Proxy tun würdest. Die Vorteile liegen auf der Hand: Du musst dich nicht um Dinge wie Verschlüsselung oder Authentifizierung für jeden einzelnen Dienst separat kümmern. Es gibt dir außerdem die Möglichkeit, Dinge wie Load-Balancing oder das Sammeln von Statistiken über alle deine Dienste hinweg umzusetzen.</p>
<h2 id="anwendungsfall">Anwendungsfall</h2>
<p>Jason Wilder selbst erklärt den Anwendungsfall von <code>jwilder/nginx-proxy</code> sehr ausführlich in seinem <a href="http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/" rel="noopener">Blog</a><a class="arch" href="https://web.archive.org/web/20180129/http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/"
       title="Archiviert am 2018-01-29"
       rel="nofollow noopener">[arch]</a>. Die Nutzung dieses Images beschränkt sich (Stand der Artikelerstellung) auf statische Setups, wie z. B. docker-compose-Dateien. Wenn du dynamischere Setups betreibst — etwa solche, bei denen Container spontan erstellt werden — sind Lösungen wie Traefik besser geeignet. Zum Zeitpunkt der Erstellung dieses Artikels kann das Image Backend-Dienste ohne spezielle <code>jwilder/nginx-proxy</code>-Konfiguration nicht automatisch erkennen.</p>
<h2 id="konfiguration">Konfiguration</h2>
<p>Die Hauptanwendungsfälle lassen sich vollständig über Umgebungsvariablen konfigurieren, die auf den zu proxenden Diensten definiert werden. Leider hat das auch den Nebeneffekt, dass die nginx-proxy-Konfiguration in deinem Backend-Dienst sichtbar wird. Würde man stattdessen Labels zur Konfiguration verwenden, würde das die Umgebung nicht verunreinigen.</p>
<h2 id="aktivität">Aktivität</h2>
<p>Die Community rund um <code>jwilder/nginx-proxy</code> ist sehr aktiv. Das <a href="https://github.com/jwilder/nginx-proxy" rel="noopener">GitHub-Repository</a><a class="arch" href="https://web.archive.org/web/20180129/https://github.com/jwilder/nginx-proxy"
       title="Archiviert am 2018-01-29"
       rel="nofollow noopener">[arch]</a> hat über 60 Contributor. Updates des Images erfolgen sehr häufig, was für einen Dienst, der die erste Verteidigungslinie in deinem Setup darstellt, sehr wichtig ist. Besonders wenn man bedenkt, dass die Nutzung von Docker-Images <a href="/docker-containers-insecure/">zusätzliche potenziell unsichere Schichten hinzufügt</a> zwischen dir und dem Software-Hersteller.</p>
<h2 id="skalierung">Skalierung</h2>
<p>Der Dienst selbst lässt sich nicht so einfach skalieren wie andere Ingress-Router-Lösungen. Um die Instanzen dynamisch zu skalieren, musst du einen dedizierten Load-Balancer betreiben, der die Aufrufe auf deine <code>jwilder/nginx-proxy</code>-Instanzen verteilt.</p>
<h2 id="fazit">Fazit</h2>
<p>Jasons nginx-basiertes Reverse-Proxy-Image ist einer der besten verfügbaren Ingress-Router für statische Setups. Es ist besonders nützlich in statischen Umgebungen, z. B. klassischen Servern, auf denen die Dienste in Containern laufen. Die Fähigkeit, die gesamte notwendige Konfiguration aus wenigen Einstellungen zu generieren, ist hervorragend. Es ist eines der wenigen verfügbaren Images, die überhaupt keine Konfigurationsdateien benötigen. Selbst für Einsteiger ohne jegliches nginx-Wissen senkt das die Hürde, einen Ingress-Router zu konfigurieren, wirklich erheblich.</p>
]]></content:encoded>
      <category>docker</category><category>security</category><category>reverse-proxy</category><category>nginx</category>
    </item>
    
    <item>
      <title>Docker-Container sind unsicher</title>
      <link>https://blog.goroot.de/de/post/docker-containers-are-insecure/</link>
      <pubDate>Sun, 14 Jan 2018 12:12:00 +0000</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/docker-containers-are-insecure/</guid>
      <description>Container bieten Isolation — wenn du sie auf einem vollständig gepatchten Host betreibst, sollte es also gar keine Sicherheitslücken geben. Weit gefehlt.</description>
      <content:encoded><![CDATA[<p>Jeder weiß, wie man eine Linux-Box aktuell hält. Es gilt auch als gesunder Menschenverstand, dass das Ausführen von Dingen in Docker-Containern per Definition sicherer ist. Schließlich isolieren sie Dienste voneinander. Wenn du also Container auf einem vollständig gepatchten Host betreibst, sollte es gar keine Sicherheitslücken geben. Weit gefehlt! Container aktuell zu halten ist eine ganz andere Geschichte. Das wirft die Fragen auf, wie man seine Container aktuell hält und wie man entscheidet, ob sich Containerisierung in deinem Szenario überhaupt lohnt.</p>
<h2 id="was-ist-ein-sicheres-system">Was ist ein sicheres System?</h2>
<p>Die Definition eines sicheren Systems mag je nach Person unterschiedlich ausfallen. Ein System sicher zu halten ist natürlich viel mehr als nur die neuesten Patches und Updates einzuspielen, aber dennoch: Für ein durchschnittliches System ist das Installieren von Updates und Sicherheitspatches <em>der wichtigste</em> Punkt, wenn Systeme dem Internet ausgesetzt sind. Aus Update-Perspektive muss es eine Art &ldquo;Vertrauenskette&rdquo; geben, um sein System sicher aktualisieren zu können. Bricht ein einzelnes Kettenglied, können deine Systeme kompromittiert werden, ohne dass du es überhaupt bemerkst:</p>
<p>Lass uns diese Vertrauenskette definieren:</p>
<ul>
<li><strong>Zeitnah aktualisieren</strong> — Updates so bald wie möglich einspielen</li>
<li><strong>Sichere Quellen</strong> — Alle Updates müssen aus vertrauenswürdigen Quellen bezogen werden. Die Voraussetzungen für eine vertrauenswürdige Quelle sind:</li>
<li><strong>Vertrauenswürdige Repositories</strong> — Die Quellen / Server / Update-Seiten, von denen du deine Updates beziehst, müssen vertrauenswürdig sein</li>
<li><strong>Vertrauenswürdige Beiträge</strong> — Gleiches gilt für die Inhalte dieser Repositories (bzw. den Quellcode, der die Grundlage für die Pakete bildet)</li>
<li><strong>Integrität sicherstellen</strong> — Der Nachweis der Integrität eines Pakets (z. B. durch Signaturen) stellt sicher, dass sich der Paketinhalt auf dem Weg vom Autor zu deinem System nicht verändert hat</li>
</ul>
<p>Prüfen wir also, ob all diese Anforderungen beim Betrieb von Docker-Containern erfüllt sind.</p>
<h2 id="der-klassische-server">Der klassische Server</h2>
<p>Du betreibst Linux auf einer Bare-Metal-Box (oder einer virtuellen Maschine). Um sie aktuell zu halten, führst du von Zeit zu Zeit ein paar Befehle wie</p>
<div class="codeblock" id="c1">
  <div class="code-head">
    <span class="f">bash</span>
    
    <a class="anchor" href="#c1-1">#L1-L2</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln" id="c1-1"><a class="lnlinks" href="#c1-1">1</a></span><span class="cl">apt-get update
</span></span><span class="line"><span class="ln" id="c1-2"><a class="lnlinks" href="#c1-2">2</a></span><span class="cl">apt-get upgrade</span></span></code></pre></div>
  
</div>
<p>aus. Der Paketmanager deiner Distribution kümmert sich dann um den Update-Prozess. Er greift auf die Paket-Repositories zu, lädt die neuesten Sicherheitsupdates herunter und installiert sie schließlich auf deinem System.</p>
<p>Aber warum müssen wir diese Updates eigentlich manuell durchführen? Können wir das nicht einfach automatisch erledigen lassen? Nun, zumindest bei klassischen Serversystemen kann ein automatisches Update dein System jederzeit lahmlegen. Datenbank-Upgrades, überschriebene individuelle Konfigurationen oder einfach unbeaufsichtigte Upgrades, die zu einem ungünstigen Zeitpunkt laufen, bereiten dir Kopfschmerzen.</p>
<h3 id="ping-mich-an">Ping mich an!</h3>
<p>Fortgeschrittenere Setups überwachen irgendwie den aktuellen Zustand des Systems und benachrichtigen den Administrator, dass ausstehende Updates vorliegen. Das kann entweder mit Tools wie <a href="https://wiki.ubuntuusers.de/apticron/" rel="noopener">apticron</a><a class="arch" href="https://web.archive.org/web/20180114/https://wiki.ubuntuusers.de/apticron/"
       title="Archiviert am 2018-01-14"
       rel="nofollow noopener">[arch]</a> geschehen, das dich per Mail benachrichtigt, sobald neue Updates verfügbar sind. Noch professionellere Setups nutzen ein dediziertes Monitoring-System wie <a href="https://www.icinga.com" rel="noopener">icinga2</a><a class="arch" href="https://web.archive.org/web/20180114/https://www.icinga.com"
       title="Archiviert am 2018-01-14"
       rel="nofollow noopener">[arch]</a>, um Alarme auszulösen.</p>
<h3 id="ist-unsere-kette-gebrochen">Ist unsere Kette gebrochen?</h3>
<p>Nein, eigentlich nicht. Wenn du deine Updates regelmäßig ausführst, kümmert sich dein Paketmanager um all die Anforderungen, die wir definiert haben:</p>
<ul>
<li><strong>Zeitnah aktualisieren</strong> — Erledigt! Wir werden benachrichtigt, sobald etwas zu installieren ist, und führen das Update sofort aus</li>
<li><strong>Sichere Quellen</strong> — Erledigt! Alle unten definierten Anforderungen sind erfüllt</li>
<li><strong>Vertrauenswürdige Repositories</strong> — Erledigt! Die Standard-Repositories deiner Distribution werden von denselben Leuten gepflegt, die auch deine Distribution gebaut haben. Wenn du denen nicht vertraust, hättest du die Distribution gar nicht erst genutzt.</li>
<li><strong>Vertrauenswürdige Beiträge</strong> — Erledigt! Code-Änderungen werden von der Open-Source-Community überprüft, das Paketieren der finalen Artefakte übernimmt ein Paketmanager, der für den Paketinhalt verantwortlich ist. Zumindest bei Debian gibt es zusätzlich eine sehr lange Testphase, bevor Dinge in Produktion gehen.</li>
<li><strong>Integrität sicherstellen</strong> — Erledigt! Dein Paketmanager prüft Signaturen und Prüfsummen der zu installierenden Pakete und warnt dich, falls etwas nicht stimmt</li>
</ul>
<h2 id="ab-damit-in-einen-container">Ab damit in einen Container</h2>
<p>Da wir also gehört haben, dass ein containerisierter Dienst sicherer ist, werfen wir alle unsere Dienste in ihre eigenen Container. Wir haben immer noch unsere Bare-Metal-Box mit Linux und einem Docker-Daemon, aber alles andere versteckt sich in mehreren Containern. Um unser Host-System aktuell zu halten, nutzen wir genau dieselbe Strategie, die wir im letzten Kapitel besprochen haben. Da unser Host-System aber aus nicht viel mehr als einem auf einem Kernel laufenden dockerd besteht, gibt es dort auch gar nicht so viele Updates.</p>
<h3 id="frischer-anstrich-für-deine-container">Frischer Anstrich für deine Container</h3>
<p>Wie aktualisiert man einen Container? Man aktualisiert das Docker-Image und startet die Maschine mit der neuen Image-Version neu. Ist das also nur ein weiterer Zweizeiler?</p>
<div class="codeblock" id="c2">
  <div class="code-head">
    <span class="f">bash</span>
    
    <a class="anchor" href="#c2-1">#L1-L2</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln" id="c2-1"><a class="lnlinks" href="#c2-1">1</a></span><span class="cl">docker pull imagename
</span></span><span class="line"><span class="ln" id="c2-2"><a class="lnlinks" href="#c2-2">2</a></span><span class="cl">docker restart myContainer</span></span></code></pre></div>
  
</div>
<p>Auf den ersten Blick: ja. Aber konzentrieren wir uns zunächst darauf, wie man alle seine Container aktuell hält. Vielleicht hast du eine ganze Menge davon laufen — willst du die wirklich alle einzeln von Hand aktualisieren?</p>
<p>Es gibt mehrere Lösungen, um alle Container auf einmal zu aktualisieren. Eine davon ist <a href="https://github.com/v2tec/watchtower" rel="noopener">Watchtower</a><a class="arch" href="https://web.archive.org/web/20180114/https://github.com/v2tec/watchtower"
       title="Archiviert am 2018-01-14"
       rel="nofollow noopener">[arch]</a>. Es betreibt einen zusätzlichen Container, der alle anderen Container inspiziert und deine aktuellen Image-Versionen mit den neuesten Versionen in der Registry vergleicht. Wird ein neueres Image erkannt, lädt es dieses herunter, reißt deinen Container ab und startet ihn mit der neuesten Image-Version neu. Das alles geschieht ganz ohne manuelles Eingreifen.</p>
<h3 id="dem-wal-vertrauen">Dem Wal vertrauen?</h3>
<p>Prüfen wir noch einmal unsere Anforderungen für das Container-Szenario. Unsere Vertrauenskette ist nicht gebrochen, aber angerostet:</p>
<p>Die Quellen unserer Images sind nicht so sicher wie die Paket-Repositories deiner Distribution. Die Bauanleitungen für die Images (auch bekannt als Dockerfiles) sind oft auf GitHub oder anderswo öffentlich verfügbar, aber es gibt auch viele Images, die nicht einmal ihre Dockerfiles veröffentlichen. Zusammengefasst:</p>
<ul>
<li>Wenn das Dockerfile nicht verfügbar ist, nutzt du eine Blackbox ohne einfache Möglichkeit zur Einsicht, was eigentlich vor sich geht. Du kannst dir zwar immer die Historie deines Docker-Images ansehen, um eine Vorstellung davon zu bekommen. Aber ist das wirklich der richtige Weg für jedes einzelne Image-Update?</li>
<li>Wenn das Dockerfile verfügbar ist, hängt es davon ab, ob du überprüfen kannst, dass das veröffentlichte Dockerfile wirklich die Bauanleitung für das von dir verwendete Image ist. Bei Docker Hub wird das implizit durch deren Build-Prozess sichergestellt. Andernfalls gibt es keinen kryptographisch sicheren Weg (wie Signaturen), das wirklich zu belegen.</li>
</ul>
<p>Noch problematischer aus Sicherheitssicht ist der Inhalt des Images. Mit Docker ist der Prozess der Container-Aktualisierung noch stärker automatisiert (was auch ein Vorteil ist). Das bedeutet aber auch, dass in bestimmten Szenarien der Autor des Dockerfiles ganz allein entscheidet, welche Änderungen in Produktion gehen. Besonders bei kleineren Projekten gibt es überhaupt keine Code-Reviews.</p>
<h2 id="fazit">Fazit</h2>
<p>Die Frage, ob man einem Open-Source-Projekt vertrauen soll oder nicht, ist so alt wie Open Source selbst. Das Containerisieren deiner Dienste fügt lediglich eine weitere (potenziell) unsichere Schicht zu denen hinzu, die du in einem klassischen Szenario mit Daemons bereits hast. Immerhin gewinnst du definitiv an Sicherheit, da im Fehlerfall alles auf den bösartigen Container isoliert bleibt. Andererseits erhöht sich auch die Wahrscheinlichkeit, sich etwas Bösartiges einzufangen — besonders bei kleineren Projekten, die vielleicht nicht einmal Open Source sind. Welcher Weg der richtige ist, hängt immer von deinem Wissen über die verwendete Technologie und deiner Bereitschaft ab, Zeit in Sicherheit zu investieren. Sei dir einfach bewusst, dass du mehr Dinge berücksichtigen musst, als wenn du die Repositories deiner Distribution nutzt.</p>
<p>Docker-Container sind also unsicher — aber nicht mehr als deine klassischen Systeme, wenn du es richtig machst ;-)</p>
]]></content:encoded>
      <category>debian</category><category>docker</category><category>icinga</category><category>linux</category><category>monitoring</category><category>patches</category><category>security</category>
    </item>
    
    <item>
      <title>Auf einer leeren Seite beginnen</title>
      <link>https://blog.goroot.de/de/post/starting-on-a-blank-page/</link>
      <pubDate>Thu, 11 Jan 2018 17:48:00 +0000</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/starting-on-a-blank-page/</guid>
      <description>Der alte Blog lief von 2014 bis 2016 und drehte sich um Amateurfunk und IoT. Hier ist, was sich jetzt ändert, da der Blog zurück ist.</description>
      <content:encoded><![CDATA[<h2 id="ein-kleines-stück-geschichte">Ein kleines Stück Geschichte</h2>
<p>Der Blog startete ursprünglich 2014 und war bis Ende 2016 online. Damals lag mein Fokus vor allem auf Amateurfunk- und IoT-Inhalten. Diese Themen wurden von vielen Lesern geschätzt. Trotzdem musste der alte Blog irgendwann enden, da sich auch meine Interessen mit der Zeit verändert haben. Ich war schlicht nicht mehr in der Lage (oder willens :-)), die nötige Zeit zu investieren, um hochwertige Artikel zu Themen zu schreiben, die nicht mehr mein Hauptfokus sind.</p>
<h2 id="was-ist-in-zukunft-zu-erwarten">Was ist in Zukunft zu erwarten?</h2>
<p>Wie der Titel schon sagt: Cloud. Architektur. IoT. Und jede Menge dazwischen!</p>
<p>Ich habe den zukünftigen Fokus dieses Blogs stärker in Richtung IT verschoben, als es in der Vergangenheit der Fall war. Es wird Geschichten aus dem Alltag eines Entwicklers geben, Beiträge, die aktuelle Technologien kontrovers diskutieren, und noch viele andere Dinge, auf die man sich freuen kann.</p>
<p><strong>Bleib also dran und folge mir auf <a href="https://twitter.com/gorootde" rel="noopener">Twitter</a><a class="arch" href="https://web.archive.org/web/20180111/https://twitter.com/gorootde"
       title="Archiviert am 2018-01-11"
       rel="nofollow noopener">[arch]</a>, um keinen der kommenden Beiträge zu verpassen!</strong></p>
]]></content:encoded>
      <category>meta</category><category>blog</category><category>docker</category><category>hamradio</category><category>iot</category>
    </item>
    
  </channel>
</rss>
