<?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>Tue, 25 Aug 2026 19:48:34 +0000</lastBuildDate>
    <atom:link href="https://blog.goroot.de/de/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Warum dieser Blog jetzt ein KI-Badge trägt</title>
      <link>https://blog.goroot.de/de/post/idea-badge/</link>
      <pubDate>Mon, 24 Aug 2026 09:30:00 +0200</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/idea-badge/</guid>
      <description>Seit heute markiert ein kleines Badge jeden Post, dessen Fließtext maßgeblich eine KI mitformuliert hat. Der AI Act verlangt das von mir nicht – ich mache es trotzdem.</description>
      <content:encoded><![CDATA[<p>Beim <a href="https://blog.goroot.de/de/post/hybrid-llm-data-sovereignty/">letzten Post über souveräne LLM-Architekturen</a> ist mir aufgefallen, wie viel vom Fließtext eigentlich nicht von mir stammt. Die Gliederung, die Kernthesen, die technischen Entscheidungen – alles meins. Aber ganze Absätze in der finalen Ausformulierung hat eine KI geschrieben, ich habe sie nur freigegeben und geprüft. Das wollte ich nicht länger stillschweigend so lassen.</p>
<h2 id="was-das-badge-zeigt">Was das Badge zeigt</h2>
<p>Jeder Beitrag, in dem ein Modell an der Formulierung mitgeschrieben hat, trägt jetzt ein kleines Badge neben Datum und Tags: „IDEA&quot;. Ausgeschrieben: <em>I Direct, Engine Assembles</em> – Idee, Konzept und fachliche Einordnung stammen von mir, das Modell hat nur den Text zusammengesetzt. Ein Tooltip erklärt das beim Hovern oder Fokussieren:</p>
<blockquote>
<p>I Direct, Engine Assembles — the idea, concept and thinking
are mine; the prose was drafted with AI assistance.</p>
</blockquote>
<p>Technisch ist es ein einzelnes Flag, das meine Blog-engine auswertet – pro Sprachversion, nicht pro Beitrag. Ein Post kann im Original handformuliert sein und nur in der Übersetzung das Badge tragen, oder umgekehrt.</p>
<h2 id="was-der-ai-act-von-mir-verlangt--nämlich-nichts">Was der AI Act von mir verlangt – nämlich nichts</h2>
<p>Der Teil, der mich amüsiert hat, als ich es nachgelesen habe: Artikel 50 Abs. 4 AI Act verpflichtet zur Kennzeichnung von KI-generiertem Text zu Angelegenheiten von öffentlichem Interesse – aber nur, wenn der Inhalt kein menschliches Review durchlaufen hat und keine natürliche oder juristische Person die redaktionelle Verantwortung trägt. Ich lese jeden Satz vor Veröffentlichung, korrigiere, kürze, widerspreche mir selbst und stehe für jede Behauptung im Text mit meinem Namen ein. Damit falle ich aus dem Anwendungsbereich raus, bevor er überhaupt anfängt.</p>
<p>Ich mache das Badge trotzdem. Nicht weil ein Gesetz es verlangt, sondern weil Leser hier ein Recht darauf haben zu wissen, wessen Handschrift sie gerade lesen – auch wenn es rechtlich niemanden interessiert außer mir.</p>
<h2 id="was-sich-dadurch-nicht-ändert">Was sich dadurch nicht ändert</h2>
<p>Jeder Post fängt mit einer Gliederung an, die ich selbst schreibe: Stichpunkte, Kernthesen, Code-Beispiele, die tatsächlich von mir stammen und nicht erfunden sind. Was eine KI übernimmt, ist ausschließlich die letzte Meile – Sätze glattziehen, Wiederholungen raus, bei Übersetzungen den Zieltext produzieren. Vor Veröffentlichung lese ich jeden Beitrag komplett durch, ändere, was falsch klingt oder nicht meine Stimme trifft, und stehe fachlich hinter jeder Zeile. Das Badge ändert daran nichts, es macht nur sichtbar, an welcher Stelle im Prozess die KI mitgeschrieben hat.</p>
<p>Für einen Blog, der öfter über KI-Architekturen schreibt als die durchschnittliche Konkurrenz, wäre es unpassend gewesen, selbst intransparent zu bleiben, wo der eigene Text herkommt. Wer im eigenen Unternehmen irgendwann offenlegen muss, welche Inhalte KI-unterstützt entstanden sind, kann sich das Prinzip gerne kostenlos abschauen.</p>
]]></content:encoded>
      <category>meta</category><category>ai</category><category>transparency</category><category>ai-act</category>
    </item>
    
    <item>
      <title>Warum sich frontier Modelle und Privacy nicht ausschließen</title>
      <link>https://blog.goroot.de/de/post/hybrid-llm-data-sovereignty/</link>
      <pubDate>Wed, 19 Aug 2026 21:00:00 +0200</pubDate>
      <guid isPermaLink="true">https://blog.goroot.de/de/post/hybrid-llm-data-sovereignty/</guid>
      <description>LLMs laufen nicht mehr im eigenen Rechenzentrum - jedenfalls nicht ohne signifikante Investition. Wer sensible Daten trotzdem nicht ungefiltert in die Cloud schicken will, braucht keinen eigenen GPU-Cluster, sondern einen Router davor.</description>
      <content:encoded><![CDATA[<p>Die Frage kommt in praktisch jedem Projekt, das gerade ein LLM-Feature bekommt, früher oder später: Darf das, was hier prozessiert wird, überhaupt in die Cloud? Support-Tickets mit Namen und Vertragsnummern, interne Notizen mit Gehaltsdaten, Code mit Kunden-Secrets im Kontextfenster - sobald ein Feature über den Demo-Status hinaus geht, taucht ein Datensatz auf, den niemand OpenAI, Anthropic oder Google schicken will. Und dann kommt der Reflex: &ldquo;Dann hosten wir das Modell eben selbst.&rdquo; An dieser Stelle funkt die Kostenrealität dazwischen.</p>
<h2 id="warum-einfach-selbst-hosten-an-der-kostenrealität-scheitert">Warum &ldquo;einfach selbst hosten&rdquo; an der Kostenrealität scheitert</h2>
<p>Ein Modell in Frontier-Qualität - GPT-, Claude- oder Gemini-Klasse - selbst zu betreiben, ist keine Option, die für ein Team mit fünf, fünfzig oder hundert Leuten realistisch auf dem Tisch liegt. Diese Modelle laufen auf Clustern mit zwei- bis dreistelliger H100-Anzahl. Selbst wer sich das leisten kann, bekommt nie die gleiche Modellqualität wie die Anbieter selbst, die kontinuierlich nachtrainieren.</p>
<p>Die eigentlich interessante Erkenntnis: Man braucht diese Modellklasse gar nicht für jede Anfrage. Ein Großteil dessen, was in Unternehmen an ein LLM geschickt wird, ist trivial - Zusammenfassen, Umformulieren, Kategorisieren, einfache Extraktion. Dafür reicht ein 8B- oder 14B-Modell auf einer einzelnen Consumer-GPU völlig aus. Die Frage ist also nicht &ldquo;eigenes Rechenzentrum oder Cloud&rdquo;, sondern: welche Anfrage braucht wirklich das teure Modell, und welche nicht.</p>
<h2 id="das-eigentliche-problem-ist-der-kontext-nicht-das-modell">Das eigentliche Problem ist der Kontext, nicht das Modell</h2>
<p>Datensouveränität wird gern als Deployment-Entscheidung behandelt: &ldquo;Wir nutzen jetzt ein On-Premise-Modell.&rdquo; Das greift zu kurz. Die Frage, ob eine Anfrage die eigene Infrastruktur verlassen darf, lässt sich nicht auf Modellebene beantworten, sondern nur pro Anfrage - auf Ebene des Kontexts, der gerade im Prompt steckt.</p>
<p>Zwei Support-Tickets aus derselben Warteschlange können völlig unterschiedliche Anforderungen haben. &ldquo;Wie ändere ich mein Passwort?&rdquo; ist unkritisch für die Cloud. &ldquo;Herr Meier, geboren am 3.4.1981, Kundennummer 88213, beschwert sich über die Abrechnung seiner Berufsunfähigkeitsversicherung&rdquo; ist es nicht. Ein statisches Deployment - alles lokal oder alles Cloud - behandelt beide Fälle gleich und ist damit entweder unnötig teuer oder unnötig riskant.</p>
<h2 id="der-router-ein-sensitivity-aware-gateway-statt-bauchgefühl">Der Router: ein sensitivity-aware Gateway statt Bauchgefühl</h2>
<p>Die Komponente, die diese Entscheidung trifft, bevor irgendein Modell die Anfrage sieht, hat in der Praxis mehrere Namen, je nachdem aus welcher Ecke man kommt. Als <strong>AI Gateway</strong> bzw. <strong>LLM Gateway</strong> bezeichnen es Anbieter, die den Fokus auf PII-Erkennung<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> legen: Der Prompt wird am Gateway gescannt, und bei einem Treffer geht die Anfrage automatisch an ein On-Premise-Modell statt an die Cloud-API. In der Forschung zu Routing-Architekturen taucht der Begriff <strong>Sensitivity-aware Routing</strong> bzw. <strong>Semantic Routing</strong> auf - ein Router vor der Inference-API entscheidet anhand von Datensensitivität, Aufgabenkomplexität und Nutzergruppe, welches Modell die Anfrage bekommt. Am nächsten an dem, was ich mir vorstelle, kommt die <strong>Hybrid Local-Cloud Architecture</strong>: ein vertrauenswürdiger, lokaler &ldquo;Local Controller&rdquo; übernimmt Planung, Routing und einfaches Reasoning selbst und delegiert nur bei Bedarf an ein nicht-vertrauenswürdiges &ldquo;Cloud Language Model&rdquo;.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Wichtig ist das Prinzip, nicht der Name: Klassifizierung passiert, bevor ein Prompt irgendeine Grenze überschreitet, und im Zweifel wird lokal verarbeitet, nie im Zweifel an die Cloud delegiert. Fail closed, nicht fail open. In Code sieht die Entscheidungslogik - bewusst vereinfacht - etwa so aus:</p>
<div class="codeblock" id="c1">
  <div class="code-head">
    <span class="f">go</span>
    
    <a class="anchor" href="#c1-1">#L1-L12</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln" id="c1-1"><a class="lnlinks" href="#c1-1"> 1</a></span><span class="cl"><span class="c1">// Route entscheidet, welche Stufe des Stacks eine Anfrage bearbeitet.</span><span class="w">
</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="c1">// Sensible Kontexte verlassen die eigene Infrastruktur nie - auch dann nicht,</span><span class="w">
</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="c1">// wenn das lokale Modell für die Aufgabe eigentlich zu schwach ist.</span><span class="w">
</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="kd">func</span><span class="w"> </span><span class="nf">Route</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="w"> </span><span class="nx">Prompt</span><span class="p">)</span><span class="w"> </span><span class="nx">Tier</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c1-5"><a class="lnlinks" href="#c1-5"> 5</a></span><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">classifier</span><span class="p">.</span><span class="nf">ContainsSensitiveContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c1-6"><a class="lnlinks" href="#c1-6"> 6</a></span><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">TierDatacenterGPU</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c1-7"><a class="lnlinks" href="#c1-7"> 7</a></span><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c1-8"><a class="lnlinks" href="#c1-8"> 8</a></span><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">Complexity</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="nx">complexityTrivial</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c1-9"><a class="lnlinks" href="#c1-9"> 9</a></span><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">TierLocalOllama</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c1-10"><a class="lnlinks" href="#c1-10">10</a></span><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c1-11"><a class="lnlinks" href="#c1-11">11</a></span><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">TierCloud</span><span class="w">
</span></span></span><span class="line"><span class="ln" id="c1-12"><a class="lnlinks" href="#c1-12">12</a></span><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  
</div>
<p>Die Klassifizierung selbst muss dabei nicht kompliziert sein. Ein Regelwerk aus Regex-Mustern für Kundennummern, IBANs und Namen aus dem eigenen CRM erkennt einen Großteil der Fälle. Für alles, was Muster-basiert nicht sauber zu fassen ist, reicht oft ein sehr kleines lokales Modell - etwa ein 1B- bis 3B-Modell, das nur eine Frage beantwortet: &ldquo;Enthält dieser Text personenbezogene oder vertrauliche Informationen? Ja/Nein.&rdquo; Das läuft in Millisekunden auf jeder halbwegs aktuellen GPU und muss selbst nicht gut im eigentlichen Task sein - nur beim Erkennen, wann Vorsicht angebracht ist.</p>
<h2 id="die-kaskadierte-architektur-workstation--lokale-gpu-im-netz--cloud">Die kaskadierte Architektur: Workstation → lokale GPU im Netz → Cloud</h2>
<p>Aus dem Routing-Prinzip ergibt sich daraus eine dreistufige Kaskade:</p>
<div class="codeblock" id="c2">
  <div class="code-head">
    <span class="f">markdown</span>
    
    <a class="anchor" href="#c2-1">#L1-L10</a>
    <button type="button" data-copied="Kopiert">Kopieren</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln" id="c2-1"><a class="lnlinks" href="#c2-1"> 1</a></span><span class="cl">Anfrage
</span></span><span class="line"><span class="ln" id="c2-2"><a class="lnlinks" href="#c2-2"> 2</a></span><span class="cl">│
</span></span><span class="line"><span class="ln" id="c2-3"><a class="lnlinks" href="#c2-3"> 3</a></span><span class="cl">▼
</span></span><span class="line"><span class="ln" id="c2-4"><a class="lnlinks" href="#c2-4"> 4</a></span><span class="cl">Sensibler Kontext erkannt? ──ja──▶ lokale GPU im Datacenter (RTX 3090, Qwen2.5-14B o.ä.)
</span></span><span class="line"><span class="ln" id="c2-5"><a class="lnlinks" href="#c2-5"> 5</a></span><span class="cl">│ nein
</span></span><span class="line"><span class="ln" id="c2-6"><a class="lnlinks" href="#c2-6"> 6</a></span><span class="cl">▼
</span></span><span class="line"><span class="ln" id="c2-7"><a class="lnlinks" href="#c2-7"> 7</a></span><span class="cl">Trivial genug für die Workstation? ──ja──▶ Ollama lokal auf dem Laptop
</span></span><span class="line"><span class="ln" id="c2-8"><a class="lnlinks" href="#c2-8"> 8</a></span><span class="cl">│ nein
</span></span><span class="line"><span class="ln" id="c2-9"><a class="lnlinks" href="#c2-9"> 9</a></span><span class="cl">▼
</span></span><span class="line"><span class="ln" id="c2-10"><a class="lnlinks" href="#c2-10">10</a></span><span class="cl">Cloud-Modell (Claude, GPT, Gemini, ...)</span></span></code></pre></div>
  
</div>
<p><strong>Stufe 1 - Ollama auf der Workstation.</strong> Autocomplete, kurze Zusammenfassungen, einfache Umformulierungen laufen direkt auf den Rechnern der Mitarbeitenden, ohne dass überhaupt eine Netzwerkanfrage nötig ist. Latenz nahe null, Kosten nahe null.</p>
<p><strong>Stufe 2 - eine GPU im eigenen Netz.</strong> Alles mit sensiblem Kontext, das mehr Leistung braucht als ein Laptop liefert, geht an eine zentrale GPU im Datacenter oder Homelab, bedient über vLLM oder Ollama im Server-Modus. Diese Stufe ist der eigentliche Kern der Datensouveränität: Sie macht &ldquo;lokal&rdquo; praktikabel, ohne dass man gleich einen Cluster braucht. Mit entsprechender Queue ist das auch für Teams mit mehreren gleichzeitigen Nutzern skalierbar.</p>
<p><strong>Stufe 3 - Cloud.</strong> Alles, was weder sensibel noch trivial ist, geht dorthin, wo die beste verfügbare Modellqualität liegt. Das ist der pragmatische Teil: Man muss nicht jede komplexe Aufgabe lokal lösen wollen, nur weil man kann.</p>
<h2 id="was-eine-rtx-3090-für-5-mitarbeitende-leistet">Was eine RTX 3090 für ~5 Mitarbeitende leistet</h2>
<p>Die 24 GB VRAM der RTX 3090 sind der Grund, warum sie in Homelab- und Kleinteam-Setups so oft auftaucht: genug für 8B-Modelle in kaum verlustbehafteter Quantisierung und für 14B-Modelle in Q4, mit noch Luft für Kontext.</p>
<table>
	<thead>
			<tr>
					<th>Modell (Quantisierung)</th>
					<th>VRAM-Bedarf</th>
					<th>Richtwert Durchsatz¹</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Llama 3.1 8B (Q4_K_M)</td>
					<td>~5 GB</td>
					<td>60-90 Tokens/s</td>
			</tr>
			<tr>
					<td>Qwen2.5 14B (Q4_K_M)</td>
					<td>~9 GB</td>
					<td>35-45 Tokens/s</td>
			</tr>
			<tr>
					<td>Qwen2.5 32B (Q4_K_M)</td>
					<td>~19 GB</td>
					<td>15-20 Tokens/s</td>
			</tr>
	</tbody>
</table>
<p>¹ Öffentlich kursierende Richtwerte für Einzel-Anfragen, keine eigenen Messungen - für den produktiven Einsatz mit mehreren gleichzeitigen Nutzern vorher selbst benchmarken, insbesondere bei parallelen Requests.</p>
<p>Für ein Team von fünf Leuten, das die GPU nicht dauerhaft sättigt, reicht eine einzelne 3090 für die zweite Stufe der Kaskade in der Regel aus. Wenn es mehr sein soll, ein DGX Spark für wenige tausend Euro. Immer vorausgesetzt, die wirklich rechenintensiven Aufgaben landen ohnehin in Stufe 3. Die Rechnung dahinter, überschlagen: 350 Watt TDP, sagen wir 6 Stunden aktive Last pro Arbeitstag, macht bei 20 Arbeitstagen im Monat rund 42 kWh - bei 30 Cent/kWh knapp 13 Euro Strom. Dazu die Abschreibung der Karte über zwei bis drei Jahre. Das liegt weit unter dem, was fünf Personen an Cloud-API-Kosten verursachen würden, wenn jede sensible Anfrage an ein Frontier-Modell ginge - vor allem, weil Compliance relevante Anfragen tendenziell lang sind (viel Kontext, viele Dokumente) und damit bei tokenbasierter Abrechnung überproportional teuer.</p>
<h2 id="grenzen-wann-lokale-modelle-für-den-job-einfach-zu-schwach-sind">Grenzen: wann lokale Modelle für den Job einfach zu schwach sind</h2>
<p>Der ehrliche Teil dieser Architektur: Ein Qwen2.5-14B in Q4 ist kein Ersatz für ein Frontier-Modell. Bei komplexem Reasoning, langen Dokumenten mit vielen Querverweisen oder Aufgaben, die echtes Weltwissen jenseits der Trainingsdaten brauchen, stößt man schnell an Grenzen. Wem das nicht bewusst ist, der bekommt am Ende schlechtere Ergebnisse und rechtfertigt diese als Datenschutz-Kompromiss - was auf Dauer niemand mitträgt.</p>
<p>Die Lösung ist nicht, die Grenze zu verschieben, sondern eine dritte Option zwischen &ldquo;lokal, aber schlechter&rdquo; und &ldquo;Cloud, aber riskant&rdquo; einzuziehen: Redaction vor dem hand-off an die Cloud. Namen, Kundennummern und andere Identifikatoren werden vor dem Cloud-Call durch Platzhalter ersetzt und nach der Antwort wieder eingesetzt. Das lokale Modell übernimmt dabei nur die Anonymisierung - eine Aufgabe, für die auch ein kleines Modell gut genug ist -, während die eigentliche, komplexe Aufgabe an ein starkes Cloud-Modell geht, ohne dass Klartext-PII die eigene Infrastruktur verlässt. Das ist mehr Aufwand als reines Routing, aber deutlich weniger als ein eigenes Frontier-Modell zu betreiben.</p>
<h2 id="was-das-für-eine-organisation-bedeutet">Was das für eine Organisation bedeutet</h2>
<p>Das Prinzip dahinter ist dasselbe wie bei der <a href="https://blog.goroot.de/de/post/network-segregation-IoT-vs-NoT/">Netzwerksegmentierung meiner IoT-Geräte</a>: Man vertraut nicht blind dem gesamten System, sondern definiert Grenzen, durch die bestimmte Daten schlicht nicht durch können. Und das unabhängig davon, ob eine Anwendung sich später falsch verhält. Bei IoT-Geräten sind das VLANs und Firewall-Regeln. Bei LLM-Anfragen ist es ein Router vor der Inference-API.</p>
<p>Skaliert man das von einem Fünf-Personen-Team auf eine größere Organisation, wird aus dieser technischen Entscheidung schnell eine Governance-Frage: Wer pflegt die Klassifizierungsregeln, wenn sich die Datenkategorien ändern? Wie weist man gegenüber Kunden oder Aufsichtsbehörden nach, dass ein bestimmter Prompt tatsächlich nie die eigene Infrastruktur verlassen hat? Genau dieser Audit-Trail ist der Punkt, an dem &ldquo;wir nutzen ein lokales Modell&rdquo; zu einer belastbaren Aussage wird statt zu einer Behauptung. Aus einer Homelab-Lösung mit einer einzelnen GPU wird auf einmal eine Lösung, die man auch einem Wirtschaftsprüfer erklären kann.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Personal Identifiable information - die maschinelle Erkennung von personenbezogenen- und personenbeziehbaren Daten in Texten.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Verwandte Ansätze, die tiefer in dieselbe Richtung gehen: &ldquo;Multi-Agent Firewall Architecture for Privacy Protection&rdquo; und &ldquo;PrivScope: Task-scoped Disclosure Control for Hybrid Agentic Systems&rdquo; - beide beschreiben, wie sich Offenlegung von Daten an Cloud-Modelle pro Teilaufgabe statt pro Anfrage steuern lässt.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
      <category>ai</category><category>llm</category><category>data-sovereignty</category><category>self-hosted</category><category>privacy</category>
    </item>
    
    <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" width="2388" height="1167" 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="" width="1018" height="1060" 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="" width="894" height="384" 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="" width="1918" height="1666" 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="" width="1876" height="1758" 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="" width="1910" height="1774" 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="" width="2038" height="862" 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>
