<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://rehsack.de/feed.xml" rel="self" type="application/atom+xml" /><link href="https://rehsack.de/" rel="alternate" type="text/html" /><updated>2026-04-09T19:41:05+00:00</updated><id>https://rehsack.de/feed.xml</id><title type="html">Jens Rehsack</title><subtitle>Persönlicher Blog von Jens Rehsack — Technik, Meinung, Leben</subtitle><author><name>Jens Rehsack</name></author><entry><title type="html">Yocto für Bare-Metal: Cross-Compiler und CI-Toolchain aus einem Guss</title><link href="https://rehsack.de/2026/04/09/yocto-bare-metal-sdk-aus-einem-guss/" rel="alternate" type="text/html" title="Yocto für Bare-Metal: Cross-Compiler und CI-Toolchain aus einem Guss" /><published>2026-04-09T00:00:00+00:00</published><updated>2026-04-09T00:00:00+00:00</updated><id>https://rehsack.de/2026/04/09/yocto-bare-metal-sdk-aus-einem-guss</id><content type="html" xml:base="https://rehsack.de/2026/04/09/yocto-bare-metal-sdk-aus-einem-guss/"><![CDATA[<div class="paragraph">
<p>Wer Firmware für Mikrocontroller baut, braucht einen Cross-Compiler.
Den bekommt man mit <a href="https://crosstool-ng.github.io/">crosstool-ng</a>,
als ARM-Download oder aus dem Paketmanager der Distribution.
Das funktioniert&#8201;&#8212;&#8201;und wenn man dann noch Unit-Tests in der CI
haben will, wird es richtig spannend.</p>
</div>
<div class="paragraph">
<p>Denn dann braucht man <em>zwei</em> Toolchains: eine für das Target
(<code>arm-none-eabi-gcc</code> mit newlib) und eine für den Host (<code>x86_64</code>
oder <code>aarch64</code>), mit der man denselben Code gegen ein Test-Framework
kompiliert.  Auf dem Host läuft natürlich eine andere libc&#8201;&#8212;&#8201;aber
GCC-Generation, Google-Test-Version, gcov und die
Analyse-Werkzeuge müssen auf Punkt und Komma zusammenpassen.</p>
</div>
<div class="paragraph">
<p>Bare-Metal klingt dabei nach wenig Aufwand: Wenn newlib schon das
Maß der Dinge ist, ist der Horizont quasi anfassbar.  Die
Herausforderung steckt nicht in der Menge der Abhängigkeiten,
sondern darin, dass die Werkzeuge drumherum für Target und Host
aus einem Guss sein müssen.  Genau das lässt sich elegant lösen.</p>
</div>
<div class="sect1">
<h2 id="das-problem-zwei-toolchains-eine-wahrheit">Das Problem: Zwei Toolchains, eine Wahrheit</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Nehmen wir an, der Cross-Compiler ist ein GCC 15 mit newlib.
Die Unit-Tests laufen auf dem CI-Host, also brauchen wir dort
ebenfalls einen GCC 15&#8201;&#8212;&#8201;nicht den GCC 14, den Fedora 43 mitbringt,
und schon gar nicht den GCC 13 aus dem Ubuntu-LTS der CI-Runner.</p>
</div>
<div class="paragraph">
<p>Dazu kommen ABI-gekoppelte Werkzeuge:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong>Google Test / GMock</strong>&#8201;&#8212;&#8201;kompiliert gegen die jeweilige Toolchain</p>
</li>
<li>
<p><strong>gcov / lcov</strong>&#8201;&#8212;&#8201;müssen zur GCC-Version passen, sonst stimmt die Coverage nicht</p>
</li>
<li>
<p><strong>valgrind</strong>&#8201;&#8212;&#8201;muss die ABI der getesteten Binaries verstehen</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Wenn man das manuell pflegt, landen Target- und Host-Toolchain
schnell in getrennten Beschaffungsketten.  Das muss nicht sein.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="warum-ich-da-heute-anders-rangehe">Warum ich da heute anders rangehe</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Vor einigen Jahren bei einem Telekommunikationsausrüster: Ein SoM mit
Netzwerk-Beschleunigern, dazu ein Binary-SDK vom Chip-Hersteller.
32-Bit, eine uralte GCC-Version, gelinkt gegen eine
Linux-Distribution, die es nur noch auf dem Schwarzmarkt gegen Kamele
gab.  Lange bevor LLVM/Clang als ernstzunehmende GCC-Konkurrenz die
Stagnation aufmischte.</p>
</div>
<div class="paragraph">
<p>Die IT betrieb dafür einen Multiprozessor-Knoten mit rund 64 VMs&#8201;&#8212;&#8201;je 2&#160;VCPUs, 2&#160;GB RAM, komplett überbucht, kaum wartbar.
Das SDK ließ sich nicht reproduzieren, nicht aktualisieren und nicht
auf eine andere Host-Plattform portieren.  Dabei war der Chip sogar
Linux-fähig&#8201;&#8212;&#8201;da wäre deutlich mehr gegangen.</p>
</div>
<div class="paragraph">
<p>Das Muster habe ich seitdem mehrfach gesehen:
Chip-Hersteller wollen Chips verkaufen, keine Entwicklungsumgebungen.
Das SDK ist Beiwerk&#8201;&#8212;&#8201;es erleichtert den Einstieg, soll aber nicht
langfristig tragen.  Ein Hersteller-BSP auf LTS-Basis, jahrelang gut
genug&#8201;&#8212;&#8201;und wenn der LTS-Zyklus ausläuft, heißt es: Updaten, in
sechs Wochen liefern, nichts darf kosten.</p>
</div>
<div class="paragraph">
<p>Das hat mich motiviert, einen anderen Weg zu gehen.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="die-yocto-lösung-machine-abstraktion-als-ssot">Die Yocto-Lösung: MACHINE-Abstraktion als SSOT</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Das <a href="https://www.yoctoproject.org/">Yocto Project</a> ist primär als
Build-System für Embedded-Linux-Distributionen bekannt.  Aber sein
Rezept-System kann mehr: Es baut <em>beliebige</em> Toolchains aus den
Quellen&#8201;&#8212;&#8201;und zwar für verschiedene Zielarchitekturen aus denselben
Rezepten.</p>
</div>
<div class="paragraph">
<p>Der Schlüssel ist die <code>MACHINE</code>-Variable.  Dieselben Rezepte für GCC,
newlib, Google Test und gcov erzeugen je nach Target:</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 20%;">
<col style="width: 40%;">
<col style="width: 40%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">MACHINE</th>
<th class="tableblock halign-left valign-top">Ergebnis</th>
<th class="tableblock halign-left valign-top">Beispiel</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>aducm360</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>arm-none-eabi-gcc</code> (Cortex-M3, soft-float)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Mess-MCU, ADC-Frontend</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>stm32h7a3</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>arm-none-eabi-gcc</code> (Cortex-M7, hard-float, FPv5-D16)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kommunikations-MCU, DCP-Stack</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>samd21</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>arm-none-eabi-gcc</code> (Cortex-M0+, soft-float)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kamera-Steuerung</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>esp32c6</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>riscv32-none-elf-gcc</code> (RV32IMAC)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">RISC-V IoT, Wi-Fi/BLE</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>gd32vf103</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>riscv32-none-elf-gcc</code> (RV32IMAC)</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">RISC-V mit DSP-Erweiterungen, atomthreads</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>x86-64</code> / <code>aarch64</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Host-nativer GCC derselben Generation</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">CI: Unit-Tests, valgrind, Coverage</p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>Die Liste ließe sich fortsetzen.  Der Punkt ist: Ob ARM Cortex-M, RISC-V oder Host-nativ&#8201;&#8212;&#8201;die Rezepte sind dieselben.  Nur <code>MACHINE</code> und die zugehörige
Tune-Konfiguration unterscheiden sich.</p>
</div>
<div class="paragraph">
<p><strong>Eine</strong> Quelle, <strong>eine</strong> GCC-Version, <strong>eine</strong> ABI-Kopplung&#8201;&#8212;&#8201;DRY und
SSOT statt n&#160;getrennt gepflegte Toolchains.</p>
</div>
<div class="paragraph">
<p>Yocto baut dabei nicht nur den Compiler: Auch ein <strong>QEMU</strong> für das
jeweilige Target lässt sich aus denselben Rezepten erzeugen.  Damit
kann man Firmware-Binaries auf dem CI-Host emulieren&#8201;&#8212;&#8201;ohne echte
Hardware, aber mit der richtigen CPU-Emulation.  Für
Integrationstests auf einem Cortex-M oder RISC-V ist das Gold wert.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="was-gehört-ins-sdkund-was-nicht">Was gehört ins SDK&#8201;&#8212;&#8201;und was nicht?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Die erste Intuition ist: Nur der Compiler und seine unmittelbaren
Abhängigkeiten.  In der Praxis zieht man die Grenze weiter.</p>
</div>
<div class="paragraph">
<p><strong>Aus Yocto&#8201;&#8212;&#8201;eingefroren und versioniert:</strong></p>
</div>
<div class="ulist">
<ul>
<li>
<p>GCC + binutils + newlib (der Compiler-Kern)</p>
</li>
<li>
<p>Google Test / GMock (Test-Framework, gegen die Toolchain kompiliert)</p>
</li>
<li>
<p>gcov / lcov (Coverage&#8201;&#8212;&#8201;muss zur GCC-Version passen)</p>
</li>
<li>
<p>valgrind (Speicheranalyse&#8201;&#8212;&#8201;muss die ABI verstehen)</p>
</li>
<li>
<p><strong>clang-tidy</strong> (statische Analyse&#8201;&#8212;&#8201;braucht die <em>richtigen</em> Header
und Bibliotheken des Targets, nicht die des Host-Systems)</p>
</li>
<li>
<p><strong>clang-format</strong> (eine eingefrorene Version verhindert
Whitespace-Kriege zwischen Entwicklern)</p>
</li>
<li>
<p><strong>CMake, Ninja</strong> (wenn das CMake der Host-Distribution plötzlich
ein Major-Update bekommt, spuckt es unter Umständen in den Build)</p>
</li>
<li>
<p>QEMU (Target-Emulation für Integrationstests)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Der gemeinsame Nenner: Alles, was das Build-Ergebnis oder die
Analyse des Build-Ergebnisses beeinflusst, muss einfrierbar und
reproduzierbar sein.  clang-tidy klingt zunächst orthogonal&#8201;&#8212;&#8201;bis
man merkt, dass es die Target-Header parsen muss, nicht die des
Host-Systems.</p>
</div>
<div class="paragraph">
<p><strong>Aus der Distribution&#8201;&#8212;&#8201;unabhängig aktualisierbar:</strong></p>
</div>
<div class="ulist">
<ul>
<li>
<p>Doxygen (API-Dokumentation)</p>
</li>
<li>
<p>srecord (ELF → HEX-Konvertierung)</p>
</li>
<li>
<p>doctoolchain, asciidoctor (Projektdokumentation)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Diese Werkzeuge beeinflussen weder das Binary noch die Analyse und
können unabhängig aktualisiert werden&#8201;&#8212;&#8201;ohne
Firmware-Requalifizierung.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="bare-metal-hardening-was-geht-ohne-os">Bare-Metal-Hardening: Was geht ohne OS?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Ein Nebeneffekt der Yocto-basierten Toolchain: Man hat volle
Kontrolle über die Compiler-Flags, und zwar in sauberen Schichten.</p>
</div>
<div class="paragraph">
<p>Auf Bare-Metal fehlen die üblichen OS-Schutzmechanismen
(<code><em>_stack_chk_fail</code> ohne libc, kein ASLR ohne Loader, kein PIE ohne
MMU).  Aber GCC 13+ bietet Hardening-Flags, die _ohne</em> OS
funktionieren:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="bash"><span class="nt">-fharden-compares</span>
<span class="nt">-fharden-conditional-branches</span>
<span class="nt">-fharden-control-flow-redundancy</span>
<span class="nt">-ftrivial-auto-var-init</span><span class="o">=</span>zero</code></pre>
</div>
</div>
<div class="paragraph">
<p>Diese Flags landen nicht in der Toolchain selbst (sonst würde newlib
nicht mehr bauen), sondern in einem SDK-Environment-Hook&#8201;&#8212;&#8201;einer
dünnen Shell-Datei, die beim Aktivieren der Toolchain die
<code>CFLAGS</code>/<code>CXXFLAGS</code> setzt.  Drei Schichten:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><strong>Distro-Config</strong> (Yocto): Flags, die auch newlib braucht (<code>-ffunction-sections</code>, <code>-fdata-sections</code>)</p>
</li>
<li>
<p><strong>SDK-Hook</strong>: Hardening und Warnungen für die Firmware-Entwicklung</p>
</li>
<li>
<p><strong>Firmware-Projekt</strong> (CMakeLists.txt): Projektspezifisches (<code>-std=gnu23</code>, Target-Defines)</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect1">
<h2 id="langzeitwirkung-10-jahre-firmware-support">Langzeitwirkung: 10 Jahre Firmware-Support</h2>
<div class="sectionbody">
<div class="paragraph">
<p>In regulierten Branchen liefert man Firmware-Fixes für Produkte,
die vor 10 oder 15 Jahren ausgeliefert wurden.  Mit einer
Yocto-basierten Toolchain ist das ein <code>git checkout</code> auf den
Release-Stand und ein <code>bitbake meta-toolchain</code>&#8201;&#8212;&#8201;fertig.</p>
</div>
<div class="paragraph">
<p>Yocto liefert dafür zwei Mechanismen, die oft als Implementierungs-
detail abgetan werden, aber für das Release-Management essentiell sind:</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>DL_DIR</code></dt>
<dd>
<p>Das Verzeichnis, in das Yocto alle heruntergeladenen
Quellen ablegt&#8201;&#8212;&#8201;Tarballs, Git-Snapshots, Patches.  Das ist kein
Nebenprodukt, das ist ein <strong>Quellenarchiv</strong>.  Wer <code>DL_DIR</code> archiviert,
kann die Toolchain auch dann noch bauen, wenn die Upstream-Server
längst offline sind.</p>
</dd>
<dt class="hdlist1"><code>SSTATE_DIR</code></dt>
<dd>
<p>Der Shared-State-Cache.  Yocto speichert hier die
Zwischenergebnisse jedes Build-Schritts.  Bei einem Release-Fix
Jahre später muss nicht alles von Grund auf neu gebaut werden&#8201;&#8212;&#8201;der Cache beschleunigt den Rebuild auf die tatsächlich geänderten
Komponenten.</p>
</dd>
</dl>
</div>
<div class="paragraph">
<p>Beide Verzeichnisse zusammen bilden das Rückgrat für langfristige
Nachlieferbarkeit: Quellen gesichert, Build-Ergebnisse
nachvollziehbar, Rebuild-Zeiten beherrschbar.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cra-und-sbom-vom-nice-to-have-zur-pflicht">CRA und SBOM: Vom Nice-to-have zur Pflicht</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Mit dem <a href="https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act">Cyber Resilience Act</a>
wird die Frage „Was steckt in meiner Toolchain?" regulatorisch
relevant.  Ein SBOM (Software Bill of Materials) mit voller Provenance
ist keine Kür mehr&#8201;&#8212;&#8201;es wird Pflicht für Produkte mit digitalen
Elementen.</p>
</div>
<div class="paragraph">
<p>Yocto erzeugt SPDX-SBOMs als Nebenprodukt des Builds&#8201;&#8212;&#8201;für jede
Komponente, die aus den Quellen gebaut wurde.  <code>DL_DIR</code> liefert die
Quellenarchivierung, die Rezepte liefern die Abhängigkeitskette,
und <code>INHERIT += "create-spdx"</code> bindet beides zu einem maschinenlesbaren
SBOM zusammen.</p>
</div>
<div class="paragraph">
<p>Aus Yocto gebaut heißt: Die Provenance stimmt per Konstruktion,
nicht per Vertrauen.  Das ist ein echtes Pfund, wenn der Auditor
vor der Tür steht.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="fazit">Fazit</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Cross-Compiler für Bare-Metal gibt es an jeder Ecke.  Der Mehrwert
von Yocto liegt nicht im Cross-Compiler selbst, sondern darin, dass
Target-Toolchain, Host-CI-Toolchain und alle Werkzeuge drumherum
<em>aus derselben Quelle</em> kommen&#8201;&#8212;&#8201;reproduzierbar, einfrierbar,
CRA-ready.</p>
</div>
<div class="paragraph">
<p>Die Einstiegshürde ist real: Yocto hat eine steile Lernkurve.  Aber
wer den Schritt wagt, bekommt eine Toolchain-Infrastruktur, die
mit jedem neuen Target und jedem neuen Projekt mitträgt&#8201;&#8212;&#8201;statt
jedes Mal bei null anzufangen.</p>
</div>
</div>
</div>]]></content><author><name>Jens Rehsack</name></author><category term="yocto" /><category term="embedded" /><category term="bare-metal" /><category term="ci" /><category term="toolchain" /><category term="cross-compilation" /><summary type="html"><![CDATA[Wer Firmware für Mikrocontroller baut, braucht einen Cross-Compiler. Den bekommt man mit crosstool-ng, als ARM-Download oder aus dem Paketmanager der Distribution. Das funktioniert&#8201;&#8212;&#8201;und wenn man dann noch Unit-Tests in der CI haben will, wird es richtig spannend.]]></summary></entry><entry><title type="html">Jekyll + AsciiDoc im Devcontainer — null lokales Setup</title><link href="https://rehsack.de/2026/04/05/devcontainer-asciidoc-jekyll/" rel="alternate" type="text/html" title="Jekyll + AsciiDoc im Devcontainer — null lokales Setup" /><published>2026-04-05T00:00:00+00:00</published><updated>2026-04-05T00:00:00+00:00</updated><id>https://rehsack.de/2026/04/05/devcontainer-asciidoc-jekyll</id><content type="html" xml:base="https://rehsack.de/2026/04/05/devcontainer-asciidoc-jekyll/"><![CDATA[<div class="paragraph">
<p>Eine Jekyll-Site mit AsciiDoc-Unterstützung aufzusetzen bedeutet normalerweise:
Ruby installieren, Bundler, eine Handvoll Gems.  Mit dem
<a href="https://github.com/tpo42/tpo42-asciidoc-container">tpo42/adoc</a>-Container und der
<a href="https://containers.dev/">Dev-Containers</a>-Spezifikation kann man sich das alles
sparen.</p>
</div>
<div class="sect1">
<h2 id="die-idee">Die Idee</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Der Container <code>ghcr.io/tpo42/adoc</code> bringt Ruby 3.x, Bundler 4.x und die
komplette Asciidoctor-Toolchain mit (asciidoctor, asciidoctor-pdf,
asciidoctor-diagram, …).  Jekyll und seine Plugins sind nur ein
<code>bundle install</code> entfernt&#8201;&#8212;&#8201;kein Grund, das Host-System zuzumüllen.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="setup">Setup</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="1-gemfile">1. Gemfile</h3>
<div class="paragraph">
<p>Ein Standard-Jekyll-Gemfile&#8201;&#8212;&#8201;der Container liefert Ruby und Bundler, auf
dem Host wird nichts weiter benötigt:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="ruby"><span class="n">source</span> <span class="s2">"https://rubygems.org"</span>

<span class="n">gem</span> <span class="s2">"jekyll"</span><span class="p">,</span> <span class="s2">"~&gt; 4.4"</span>
<span class="n">gem</span> <span class="s2">"jekyll-remote-theme"</span>
<span class="n">gem</span> <span class="s2">"jekyll-asciidoc"</span>
<span class="n">gem</span> <span class="s2">"asciidoctor"</span>
<span class="n">gem</span> <span class="s2">"jekyll-sitemap"</span>
<span class="n">gem</span> <span class="s2">"jekyll-feed"</span>
<span class="n">gem</span> <span class="s2">"jekyll-include-cache"</span>
<span class="n">gem</span> <span class="s2">"webrick"</span></code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="2-devcontainerdevcontainer-json">2. <code>.devcontainer/devcontainer.json</code></h3>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"meine-site"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"image"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ghcr.io/tpo42/adoc:latest"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"workspaceFolder"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/workspace"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"workspaceMount"</span><span class="p">:</span><span class="w"> </span><span class="s2">"source=${localWorkspaceFolder},target=/workspace,type=bind"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"postCreateCommand"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundle config set --local path vendor/bundle &amp;&amp; bundle install"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"appPort"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">4000</span><span class="p">,</span><span class="w"> </span><span class="mi">35729</span><span class="p">],</span><span class="w">
  </span><span class="nl">"forwardPorts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">4000</span><span class="p">,</span><span class="w"> </span><span class="mi">35729</span><span class="p">],</span><span class="w">
  </span><span class="nl">"portsAttributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"4000"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Jekyll"</span><span class="p">,</span><span class="w"> </span><span class="nl">"onAutoForward"</span><span class="p">:</span><span class="w"> </span><span class="s2">"notify"</span><span class="w"> </span><span class="p">},</span><span class="w">
    </span><span class="nl">"35729"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LiveReload"</span><span class="p">,</span><span class="w"> </span><span class="nl">"onAutoForward"</span><span class="p">:</span><span class="w"> </span><span class="s2">"silent"</span><span class="w"> </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre>
</div>
</div>
<div class="paragraph">
<p>Die wichtigsten Punkte:</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>image</code></dt>
<dd>
<p>Nutzt direkt <code>ghcr.io/tpo42/adoc:latest</code> (der "einfache Fall" aus
ADR-005).  Kein eigenes Dockerfile nötig.</p>
</dd>
<dt class="hdlist1"><code>postCreateCommand</code></dt>
<dd>
<p>Installiert Gems nach <code>vendor/bundle</code> (schreibbar für
den Container-User, von <code>.gitignore</code> ignoriert).</p>
</dd>
<dt class="hdlist1"><code>appPort</code></dt>
<dd>
<p>Mappt Ports auf Docker-Ebene, damit <code>jekyll serve</code> auch ohne
VS Code vom Host aus erreichbar ist.</p>
</dd>
<dt class="hdlist1"><code>forwardPorts</code></dt>
<dd>
<p>Dasselbe für VS Codes eingebautes Port-Forwarding.</p>
</dd>
</dl>
</div>
</div>
<div class="sect2">
<h3 id="3-container-starten">3. Container starten</h3>
<div class="paragraph">
<p>Mit der <a href="https://github.com/devcontainers/cli">devcontainer-CLI</a>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="bash">devcontainer up <span class="nt">--workspace-folder</span> .</code></pre>
</div>
</div>
<div class="paragraph">
<p>Dann die Site starten:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="bash">devcontainer <span class="nb">exec</span> <span class="nt">--workspace-folder</span> <span class="nb">.</span> <span class="se">\</span>
  bundle <span class="nb">exec </span>jekyll serve <span class="nt">--host</span> 0.0.0.0 <span class="nt">--livereload</span></code></pre>
</div>
</div>
<div class="paragraph">
<p><a href="http://localhost:4000" class="bare">http://localhost:4000</a> öffnen&#8201;&#8212;&#8201;fertig.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="warum-kein-eigenes-jekyll-image">Warum kein eigenes Jekyll-Image?</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Images wie <code>bretfisher/jekyll-serve</code> funktionieren, bringen aber ihr eigenes
Ruby und Gem-Set mit.  Wenn der Content AsciiDoc ist, braucht man die
Asciidoctor-Toolchain <em>und</em> Jekyll&#8201;&#8212;&#8201;zwei getrennte Container oder ein
aufgeblähtes Custom-Image.</p>
</div>
<div class="paragraph">
<p>Mit <code>ghcr.io/tpo42/adoc</code> als Basis bekommt man Asciidoctor für die
Dokumentenverarbeitung (flatten, validate, Diagramme extrahieren via <code>adcw</code>)
<em>und</em> Jekyll zum Servieren&#8201;&#8212;&#8201;aus einem einzigen Image, gesteuert über das
Gemfile des Projekts.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="asciidoc-validierunggeht-auch-ohne-devcontainer">AsciiDoc-Validierung&#8201;&#8212;&#8201;geht auch ohne Devcontainer</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Der <code>adcw</code>-Wrapper braucht keinen Devcontainer.  <code>.adoc</code>-Dateien lassen sich
jederzeit validieren:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="bash">adcw validate <span class="nt">-i</span> _pages/about.adoc</code></pre>
</div>
</div>
<div class="paragraph">
<p>Das startet einen kurzen <code>docker run --rm</code>&#8201;&#8212;&#8201;kein langlebiger Container nötig.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="was-ich-dabei-gelernt-habe">Was ich dabei gelernt habe</h2>
<div class="sectionbody">
<div class="dlist">
<dl>
<dt class="hdlist1"><code>appPort</code> vs <code>forwardPorts</code></dt>
<dd>
<p>Die devcontainer-CLI leitet Ports nicht selbst weiter.  <code>forwardPorts</code> ist ein
VS-Code-Feature.  Für reine CLI-Workflows braucht man <code>appPort</code>.</p>
</dd>
<dt class="hdlist1">Gem-Berechtigungen</dt>
<dd>
<p>Der <code>ghcr.io/tpo42/adoc</code>-Container läuft als unprivilegierter User.
<code>bundle install</code> ohne <code>--path</code> versucht in System-Gem-Verzeichnisse zu
schreiben und scheitert.  <code>bundle config set --local path vendor/bundle</code> löst
das sauber.</p>
</dd>
<dt class="hdlist1">Container-Wiederverwendung</dt>
<dd>
<p><code>devcontainer up</code> nutzt bestehende Container wieder.  Nach Änderungen an
<code>devcontainer.json</code> muss der alte Container erst entfernt werden
(<code>docker rm -f &lt;id&gt;</code>), bevor <code>devcontainer up</code> erneut funktioniert.</p>
</dd>
</dl>
</div>
</div>
</div>]]></content><author><name>Jens Rehsack</name></author><category term="devcontainer" /><category term="jekyll" /><category term="asciidoc" /><category term="docker" /><category term="tpo42" /><summary type="html"><![CDATA[Eine Jekyll-Site mit AsciiDoc-Unterstützung aufzusetzen bedeutet normalerweise: Ruby installieren, Bundler, eine Handvoll Gems. Mit dem tpo42/adoc-Container und der Dev-Containers-Spezifikation kann man sich das alles sparen.]]></summary></entry><entry><title type="html">Offener Brief an Dr. Katrin Vernau — 7 Monate WDR-Intendantin, Zeit für Open Source?</title><link href="https://rehsack.de/2025/08/18/offener-brief-wdr-open-source/" rel="alternate" type="text/html" title="Offener Brief an Dr. Katrin Vernau — 7 Monate WDR-Intendantin, Zeit für Open Source?" /><published>2025-08-18T00:00:00+00:00</published><updated>2025-08-18T00:00:00+00:00</updated><id>https://rehsack.de/2025/08/18/offener-brief-wdr-open-source</id><content type="html" xml:base="https://rehsack.de/2025/08/18/offener-brief-wdr-open-source/"><![CDATA[<div class="paragraph">
<p>Liebe Frau Dr. Vernau,</p>
</div>
<div class="paragraph">
<p>Sie sind seit Januar 2025 WDR-Intendantin&#8201;&#8212;&#8201;7 Monate, um neue Akzente zu setzen.
Haben Sie schon bemerkt, was Ihre Redaktionen seit Jahren übersehen?</p>
</div>
<div class="paragraph">
<p>Lassen Sie mich Ihnen eine Geschichte erzählen, die zeigt, warum dieser Neuanfang
nötig ist:</p>
</div>
<div class="sect1">
<h2 id="2018-deutscher-perl-workshop-in-köln">2018: Deutscher Perl-Workshop in Köln</h2>
<div class="sectionbody">
<div class="paragraph">
<p>2018 schrieb ich WDR-Redaktionen anlässlich des 20. Deutschen Perl-Workshops in
Köln.  Internationale Konferenz, 200+ Teilnehmer, direkt vor der WDR-Haustür.
Die Antwort der Lokalzeit-Redaktion:</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p>Das Thema erscheint uns zu spezialisiert.  Unsere Zuschauer sind nicht
herausragend technisch interessiert.</p>
</div>
</blockquote>
</div>
<div class="paragraph">
<p>Aktuelle Stunde?  Keine Antwort.  Wissenschaftsredaktion?  Schweigen.
Weitere Redaktionen?  Totenstille.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="2025-die-ignoranz-bleibt-aber-die-probleme-werden-dramatischer">2025: Die Ignoranz bleibt, aber die Probleme werden dramatischer</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Gerade diese Woche: External Secrets Operator für Kubernetes vor dem Aus&#8201;&#8212;&#8201;nur
noch ein Maintainer, völlig überarbeitet.  Gernot Starke (arc42, 20 Jahre!)
berichtet: &#8220;Eine Million Downloads jährlich, hunderte Unternehmen nutzen es
täglich&#8201;&#8212;&#8201;aber Contributions?  Nahe Null.&#8221;</p>
</div>
<div class="paragraph">
<p><strong>Das ist die Realität unsichtbarer Communities: Genutzt von allen, unterstützt
von wenigen, berichtet von niemandem.</strong></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="deutsche-entwickler-sichern-globale-standards-ab">Deutsche Entwickler sichern globale Standards ab</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Während Millionen für Fußball-Übertragungen fließen, kostet ein Bericht über
deutsche Tech-Communities offenbar zu viel Aufwand für eine Antwort.</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Chris Badura aus Köln portiert jährlich ~4 ARM-Architekturen für NetBSD&#8201;&#8212;&#8201;weltweit genutzt</p>
</li>
<li>
<p>Gernot Starke feiert mit arc42 das 20-jährige Jubiläum&#8201;&#8212;&#8201;<em>der</em>
Architektur-Standard in DACH</p>
</li>
<li>
<p>unByte GmbH entwickelt Self-Bootstrapping K8s&#8201;&#8212;&#8201;Kubernetes-Innovation aus NRW</p>
</li>
<li>
<p>Yocto Linux mit Contributoren aus Köln, NRW und ganz Deutschland steckt in
Milliarden von Geräten</p>
</li>
<li>
<p><a href="https://coocook.org">Coocook.org</a> hilft tausenden Ferienlagern bei der
Essensplanung</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Und das sind nur die, die mir in 30 Sekunden einfallen.  Was würde echte
Recherche zutage fördern?</p>
</div>
<div class="paragraph">
<p><strong>Aber Sie zeigen lieber die nächste Influencer-Doku.</strong></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="der-wdr-hat-einen-bildungsauftrag">Der WDR hat einen Bildungsauftrag</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Dazu gehört zu zeigen, <em>wer</em> unsere digitale Zukunft gestaltet.  Das sind nicht
nur die üblichen Verdächtigen aus dem Silicon Valley&#8201;&#8212;&#8201;das sind auch Menschen
aus Köln, Düsseldorf, Essen.</p>
</div>
<div class="paragraph">
<p><strong>Und diese Menschen gehören in die Prime-Formate, nicht nur in
Bildungs-Nischen.</strong></p>
</div>
<div class="paragraph">
<p>Stellen Sie sich vor: &#8220;Austausch in der TH Köln: Coocook-Begründer auf
Konferenz&#8221;&#8201;&#8212;&#8201;das wäre in der Lokalzeit eingeschlagen wie eine Bombe.  Echter
lokaler Bezug, echte Innovation, echte Menschen.  Hintergrund-Recherchen gern in
Bildungsformaten&#8201;&#8212;&#8201;aber die Stories müssen raus aus der Nische!</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="mein-vorschlag-für-ihre-verbleibende-amtszeit">Mein Vorschlag für Ihre verbleibende Amtszeit</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Beauftragen Sie eine Redaktion mit einer Serie über deutsche Tech-Communities.
Zeigen Sie die FrOSCon, die Chemnitzer Linux-Tage, den German Perl Workshop.
Zeigen Sie die Menschen dahinter.</p>
</div>
<div class="paragraph">
<p>Nicht als &#8220;Nerd-Spektakel&#8221;, sondern als das, was es ist: Die Arbeit an unserer
gemeinsamen digitalen Zukunft.</p>
</div>
<div class="paragraph">
<p><strong>Werden Sie die Intendantin, die den Unterschied macht?</strong></p>
</div>
<div class="paragraph">
<p>Mit freundlichen Grüßen und konkreten Erwartungen,<br>
Jens Rehsack</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
<div class="paragraph">
<p>Falls Sie Inspiration brauchen&#8201;&#8212;&#8201;die New York Perl Mongers schauten 2018 nach
Köln und sagten: &#8220;I had no idea how to celebrate our anniversary.  Now I know.&#8221;
Vielleicht sollte der WDR auch mal nach Köln schauen.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>]]></content><author><name>Jens Rehsack</name></author><category term="medien" /><category term="open-source" /><category term="wdr" /><category term="digitale-souveränität" /><category term="software-resilienz" /><summary type="html"><![CDATA[Liebe Frau Dr. Vernau,]]></summary></entry></feed>