<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Dirk Holtwick</title>
        <link>https://holtwick.de</link>
        <description>Softwareentwickler für Web, Mobile und andere Plattformen. Starker Fokus auf geschützte Privatsphäre.</description>
        <lastBuildDate>Wed, 15 Apr 2026 08:34:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>de</language>
        <image>
            <title>Dirk Holtwick</title>
            <url>https://holtwick.de/icon-1024.png</url>
            <link>https://holtwick.de</link>
        </image>
        <copyright>Copyright (c) Dirk Holtwick &lt;dirk.holtwick@gmail.com&gt;</copyright>
        <atom:link href="https://holtwick.de/de/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Warum einen neuen statischen Website Builder?]]></title>
            <link>https://holtwick.de/de/blog/birth-of-hostic</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/birth-of-hostic</guid>
            <pubDate>Wed, 02 Sep 2020 06:00:00 GMT</pubDate>
            <description><![CDATA[Ich habe den statischen Website Generator Hostic entwickelt. Hier erkläre ich, warum und wie ich das gemacht habe.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>Ich liebe <a href="https://vuejs.org/" rel="noopener" target="_blank" class="external">Vue.js</a> von <a href="https://evanyou.me/" rel="noopener" target="_blank" class="external">Evan You</a> und ich mag statische Webseiten. Natürlich gibt es bereits Lösungen um diese beiden Neigungen miteinander zu verbinden wie <a href="https://vuepress.vuejs.org/" rel="noopener" target="_blank" class="external">VuePress</a> oder <a href="https://nuxtjs.org/" rel="noopener" target="_blank" class="external">Nuxt</a>. Aber wäre ich ein Programmierer, wenn ich diesen einfachen Weg wählen würde?</p><p>Natürlich wollte ich ran an die <em>bleeding edge</em> und habe mich schnell begeistern lassen von Evans neuestem Coup <a href="https://github.com/vitejs/vite" rel="noopener" target="_blank" class="external">vite</a>. Es wirft den Ballast vom <a href="https://webpack.js.org/" rel="noopener" target="_blank" class="external">webpack</a> über Bord und macht alles richtig. Zunächst versuchte ich damit und mit <a href="https://github.com/vuejs/vitepress" rel="noopener" target="_blank" class="external">vitepress</a> mein Glück, aber das war leider auch nicht ganz das wonach ich suchte.</p><p>Also ging ich noch mal einen Schritt zurück und schaute mir die Klassiker der statischen Website Generierung an: <a href="https://www.gatsbyjs.com/" rel="noopener" target="_blank" class="external">Gatsby</a>, <a href="https://gohugo.io/" rel="noopener" target="_blank" class="external">Hugo</a>, <a href="https://jekyllrb.com" rel="noopener" target="_blank" class="external">Jekyll</a> und <a href="https://www.11ty.dev/" rel="noopener" target="_blank" class="external">11ty</a>. Auch diese machten eigentlich alles richtig, aber alles kam dort auch nicht von der Stange wie ich es gerne hätte. Besonders da ich mich <a href="/en/blog/static-jquery">SeaSite</a> schon eine eigene Lösung gebaut hatte, mit der alle meine Websites generiert wurden.</p><h2 id="was-will-ich-eigentlich%3F" tabindex="-1"><a class="header-anchor" href="#was-will-ich-eigentlich%3F">Was will ich eigentlich?</a></h2><p>Aber was war es also, was ich wollte? Ich habe folgende Punkte für mich herausgefunden:</p><ol><li><strong>Geschwindigkeit:</strong> Ich möchte wie bei Vue.js Änderungen im Code vornehmen und unmittelbar im Browser das Ergebnis sehen.</li><li><strong>Flexibilität:</strong> Ich möchte jeden Aspekt selber beeinflussen und Programmieren können. Am liebsten in Javascript.</li><li><strong>Nachbearbeitung:</strong> Ich möchte Inhalte noch leicht anpassen können, nachdem sie bereits Berechnet wurden. Das war das Kernprinzip von SeaSite, wodurch ich im Nachgang Optimierungen an Bildern und Videos vornehmen konnte, aber auch Übersetzungen von Textpassagen für verschiedene Sprachversionen laufen lassen konnte.</li></ol><h2 id="wie-mache-ich-es%3F" tabindex="-1"><a class="header-anchor" href="#wie-mache-ich-es%3F">Wie mache ich es?</a></h2><p>Gut, bei Punkt 1 hatte ich bei <a href="https://github.com/vitejs/vite" rel="noopener" target="_blank" class="external">vite</a> schon <a href="https://github.com/evanw/esbuild" rel="noopener" target="_blank" class="external">esbuild</a> entdeckt. Es ist so unglaublich schnell, dass ich es gar nicht glauben konnte. Das Ergebnis ist auch zuverlässig und genau so wie es sein soll. <a href="https://github.com/evanw/esbuild" rel="noopener" target="_blank" class="external">Esbuild</a> war also gesetzt als Werkzeug, das ich einsetzen wollte.</p><p>Ich baute also zunächst ein kleines Node.js Skript, dass eine Javascript Datei transpilierte. Dazu baute ich eine kleine Library, um Routen zu registrieren. Die Generierung der Inhalte sollte dann on-demand erfolgen, wenn die Website durch einen einfachen Express.js Webserver angefragt würde. Um die statischen Seiten zu generieren würde ich einfach zu allen registrierten Routen die Inhalte generieren und abspeichern lassen. Das ging super und dauerte nur Millisekunden.</p><p>Schnell wollte ich nun auch den Komfort von <a href="https://github.com/vitejs/vite" rel="noopener" target="_blank" class="external">vite</a> haben, d.h. wenn Dateien sich ändern, lädt auch der Browser unmittelbar neu. Mit Chokidar konnte ich den Ordner mit den JS Dateien beobachten und per <a href="https://github.com/evanw/esbuild" rel="noopener" target="_blank" class="external">esbuild</a> alles neu übersetzen lassen. Durch einen kleinen Trick, ließ sich der Import-Cache von Node.js umgehen und das neue JS einladen und ausführen. Mit socket.io war schnell ein Reload Mechanismus für den Browser zusammengebaut.</p><h2 id="jetzt-soll-alles-sch%C3%B6ner-werden!" tabindex="-1"><a class="header-anchor" href="#jetzt-soll-alles-sch%C3%B6ner-werden!">Jetzt soll alles schöner werden!</a></h2><p>Ich hatte nun endgültig Feuer gefangen und es gab kein zurück mehr. Dann konnte es auch schöner werden :) Es gelang mir leider nicht auf Anhieb Vue.js einzubinden, allerdings zweifelte ich auch, ob das überhaupt sinnvoll sei. In SeaSite hatte ich bereits JSX und JSDOM eingesetzt. Für ein anderes Projekt hatte ich bereits eine DOM Abstraktion geschrieben, die sehr schlank ist. Diese baute ich nun so aus, dass damit per JSX einfach HTML und XML generiert worden konnte.</p><p>Somit war eine Manipulation der Inhalte durch einfache DOM Aktionen möglich. Wie viel schöner wäre es aber, wenn die entsprechenden Nodes per CSS-Selektoren gefunden werden könnten. Also baute ich auch das mit css-parse ein und es funktionierte wunderbar.</p><p>Auch ein Markdown-Parser war aus SeaSite schon vorhanden und wurde nur noch entsprechend ausgebaut, dass unter Beibehaltung der angenehmen Geschwindigkeit die Meta-Daten für die Registrierung von Routen zur Verfügung standen.</p><h2 id="open-source!" tabindex="-1"><a class="header-anchor" href="#open-source!">Open Source!</a></h2><p>Nun war also alles an Bord was benötigt wurde und es wurde Zeit eine einfache einheitliche Struktur zu schaffen, um das Projekt veröffentlichen zu können. Ein erstes Ziel war die Routen durch einfache Datenstrukturen zu beschreiben, um maximale Flexibilität zu erhalten. Für gängige Formate wie HTML, XML, JSON, Text und Assets wurden bequeme Methoden erstellt.</p><p>Da nun schon ohnehin alles die Anmutung eines Webservers hatte, der eben auch statische Seiten ausspucken kann, lag es nahe das smarte Middleware Muster von <a href="https://koajs.com/" rel="noopener" target="_blank" class="external">Koa.js</a> zu übernehmen. Auf diese Weise sind Templates und Plugins leicht zu realisieren. Eine Kopie der genannten Datenstruktur dient dann als Kontext und das Ergebnis wird im Property <code>ctx.body</code> erwartet.</p><p>Hier ist es nun, das fertige Projekt. Ich würde mich sehr über Hilfe und Ideen freuen. Vielleicht ist es nicht das tollste Tool, um statische Websites zu erzeugen, aber eventuell die Basis für eine noch smartere Lösung die darauf aufbaut.</p><p><strong><a href="https://github.com/holtwick/hostic" rel="noopener" target="_blank" class="external">github.com/holtwick/hostic</a></strong></p><p><strong><a href="https://www.npmjs.com/package/hostic" rel="noopener" target="_blank" class="external">https://www.npmjs.com/package/hostic</a></strong></p><p>Im Folgenden werde ich einige Themen weiter beleuchten wollen, die sich aus der Erstellung einer Website ergeben und wie diese mit Hostic gelöst werden können. Die Liste der aktuellen Ideen zu Themen:</p><ul><li>Aufbau einer einfachen statischen Website mit Hostic</li><li>Aufbau eines Blogs mit Markdown</li><li>Aufbau einer Mehrsprachigen Website und Lokalisierung</li><li>Optimierungen für Suchmaschinen und Accessibility</li><li>Hosting: Beaker Browser, sh…</li></ul><p>Diese Webseiten wurden bereits von Hostic erstellt:</p><ul><li><a href="https://pdfify.app" rel="noopener" target="_blank" class="external">https://pdfify.app</a></li><li><a href="https://holtwick.de" rel="noopener" target="_blank" class="external">https://holtwick.de</a></li></ul></div><p><em>Veröffentlicht am 2. September 2020</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[Video Chat Briefing – Mehr Funktionen]]></title>
            <link>https://holtwick.de/de/blog/briefing</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/briefing</guid>
            <pubDate>Fri, 16 Apr 2021 06:00:00 GMT</pubDate>
            <description><![CDATA[Neue Features in der Briefing Video Chat App]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>Letztes Jahr habe ich Video Chat Projekte gestartet. Erst <a href="https://peer-school.apperdeck.com" rel="noopener" target="_blank" class="external">peer.school</a> mit dem ich einen Beitrag zur Beschulung kleinerer Kinder leisten wollte. Dann daraus hervorgehend <a href="https://brie.fi/ng" rel="noopener" target="_blank" class="external">Brie.fi/ng</a>, dass seinen Fokus auf sichere Kommunikation legt.</p><p>Besonders Briefing hat auf <a href="https://github.com/holtwick/briefing/" rel="noopener" target="_blank" class="external">Github</a> viele Freunde gefunden und ist nun in mehreren Sprachen verfügbar, darunter auch Chinesisch und ganz frisch Russisch.</p><p>Das Projekt ist sehr einfach aufgebaut und nutzt das Javascript Framework <a href="https://vuejs.org/" rel="noopener" target="_blank" class="external">Vue</a>. Alle notwendigen Funktionen sind vorhanden und darüber hinaus sogar so spannende Features wie das Ausblenden oder Austauschen des Hintergrundes.</p><p>Einige native Apps sind ebenfalls vorhanden, darunter ist aber besonders die <a href="https://apps.apple.com/app/briefing-video-chat/id1510803601" rel="noopener" target="_blank" class="external">iOS App</a> mit Unterstützung für AppClips (Räume betreten via QR Code oder NFC Sender) erwähnenswert.</p><h2 id="einbetten" tabindex="-1"><a class="header-anchor" href="#einbetten">Einbetten</a></h2><p>Neben dem Teilen einer URL zum betreten eines gemeinsamen Raumes, gibt es auch die Möglichkeit Briefing direkt in die eigene Website einzubetten. Ein einfacher <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" rel="noopener" target="_blank" class="external">IFrame</a> macht es möglich, wie dieser hier:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-html"><span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863a;--shiki-dark:#85E89D">iframe</span></span>
<span class="line"><span style="color:#6f42c1;--shiki-dark:#B392F0">  src</span><span style="color:#24292e;--shiki-dark:#E1E4E8">=</span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;https://brie.fi/ng/cool-call-62?audio=1&amp;video=1&amp;fs=0&amp;invite=0&amp;prefs=0&amp;share=0&quot;</span></span>
<span class="line"><span style="color:#6f42c1;--shiki-dark:#B392F0">  allow</span><span style="color:#24292e;--shiki-dark:#E1E4E8">=</span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;camera; microphone; fullscreen; speaker; display-capture&quot;</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">&gt;&lt;/</span><span style="color:#22863a;--shiki-dark:#85E89D">iframe</span><span style="color:#24292e;--shiki-dark:#E1E4E8">&gt;</span></span></code></pre><p>Zur einfachen Konfiguration gibt es nun ein <a href="https://brie.fi/ng/embed-demo" rel="noopener" target="_blank" class="external">IFrame Tool</a>, in dem jeder seinen Code schnell mit einigen Klicks selber konfigurieren kann:</p><p class="img-wrapper"><a href="https://brie.fi/ng/embed-demo" rel="noopener" target="_blank" class="external"><img src="/assets/e2h6ou5ggjt1r8.png" alt="image-20190521195810826" width="555" height="728" loading="lazy"></a></p><p>Wer die volle kontrolle haben will, kann aber auch alle Komponenten die zum Betrieb notwendig sind selbst installieren. Eine Anleitung befindet sich im <a href="https://github.com/holtwick/briefing/#readme" rel="noopener" target="_blank" class="external">README auf Github</a>.</p><h2 id="kommerzielle-lizenz" tabindex="-1"><a class="header-anchor" href="#kommerzielle-lizenz">Kommerzielle Lizenz</a></h2><p>Briefing is Open Source, aber unter der <a href="https://eupl.eu/" rel="noopener" target="_blank" class="external">EUPL v1.2 Lizenz</a>, diese ist eine europäische Antwort auf die <a href="https://www.gnu.org/licenses/gpl-3.0.en.html" rel="noopener" target="_blank" class="external">GPL</a>. Daraus ergibt sich, das Änderungen wiederum unter der selben Lizenz zu veröffentlichen sind.</p><p>Wenn aber gewünscht ist, Änderungen nicht zu veröffentlichen, so ist es sicherlich fair eine kommerzielle Lizenz zu erwerben, die diese Freiheiten bietet. Alles dazu findet sich ebenfalls im <a href="https://github.com/holtwick/briefing/#commercial-license" rel="noopener" target="_blank" class="external">README</a>.</p><h2 id="future" tabindex="-1"><a class="header-anchor" href="#future">Ausblick</a></h2><p>Aktuell arbeite ich an einem dritten Produkt, dass in die Fußstapfen von Briefing treten soll. Der Fokus liegt auf besserer Skalierbarkeit, Datenschutz und Verschlüsselung, sowie Relatime-Collaboration. Die Entwicklung ist schon weit fortgeschritten. Mehr dazu bald hier…</p></div><p><em>Veröffentlicht am 16. April 2021</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[KI-Coding-Tools in der Sandbox: Warum dein Dateisystem Schutz braucht]]></title>
            <link>https://holtwick.de/de/blog/bx-sandbox</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/bx-sandbox</guid>
            <pubDate>Sun, 29 Mar 2026 06:00:00 GMT</pubDate>
            <description><![CDATA[KI-Coding-Tools wie Claude Code oder Copilot haben vollen Zugriff auf das Dateisystem. bx schützt mit einem einzigen Befehl alles ausser dem aktuellen Projektverzeichnis.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p class="img-wrapper"><img src="/assets/0nwy5x8g1772fo0.jpg" alt="Foto von Kanhaiya Sharma auf Unsplash" width="1684" height="575" loading="lazy"></p><p>Wer heute mit KI-gestützten Coding-Tools arbeitet — sei es Claude Code, Copilot oder Cursor — gewöhnt sich schnell an den Produktivitätsschub. Was dabei gerne übersehen wird: Diese Tools haben vollen Zugriff auf das gesamte Dateisystem. Jeder Befehl, den die KI ausführt, läuft mit denselben Rechten wie der Nutzer selbst.</p><p>Das mag bei einem Hobbyprojekt egal sein. Aber wer Kundendaten verarbeitet, Lizenzschlüssel auf der Platte hat, SSH-Keys für Produktionsserver oder schlicht private Fotos im Home-Verzeichnis — der bekommt irgendwann ein mulmiges Gefühl.</p><h2 id="die-vorhandenen-optionen" tabindex="-1"><a class="header-anchor" href="#die-vorhandenen-optionen">Die vorhandenen Optionen</a></h2><p>Natürlich gibt es Möglichkeiten, sich abzusichern:</p><p><strong>Docker-Container</strong> bieten gute Isolation, aber sie sind für den typischen macOS-Entwicklungsalltag sperrig. Man muss Volumes mounten, IDE-Integration konfigurieren, und der Workflow fühlt sich nie ganz nativ an.</p><p><strong>Claudes eingebaute Beschränkungen</strong> — das Tool fragt brav nach, bevor es Dateien ändert. Aber “nachfragen” ist kein Schutz. Eine halluzinierte Pfadangabe, ein falsch interpretierter Befehl, und schon liest die KI etwas, das sie nicht sehen sollte. Das Vertrauen basiert auf dem Verhalten des Modells, nicht auf einer technischen Barriere.</p><p><strong>VSCode Workspace Trust</strong> schützt vor automatischer Code-Ausführung, aber nicht vor dem Dateisystem-Zugriff eines Terminals oder einer Extension.</p><p>Keine dieser Lösungen hat mich wirklich überzeugt.</p><h2 id="die-idee%3A-macos-sandbox-nutzen" tabindex="-1"><a class="header-anchor" href="#die-idee%3A-macos-sandbox-nutzen">Die Idee: macOS Sandbox nutzen</a></h2><p>macOS bringt mit <code>sandbox-exec</code> eine Kernel-Level-Sandbox mit, die Apple selbst für App-Store-Apps verwendet. Die Idee: Ein Tool, das eine beliebige Anwendung so startet, dass sie nur das aktuelle Projektverzeichnis sehen kann — und sonst nichts.</p><p>Das Ergebnis ist <strong>bx</strong>. Ein CLI-Tool, das man vor den eigentlichen Befehl setzt:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6f42c1;--shiki-dark:#B392F0">bx</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> claude</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> ~/work/my-project</span></span></code></pre><p>Das war’s. Claude Code startet, hat vollen Zugriff auf <code>~/work/my-project</code> — aber <code>~/Documents</code>, <code>~/Desktop</code>, <code>~/.ssh</code>, andere Projekte und alles andere im Home-Verzeichnis ist unsichtbar.</p><h2 id="in-zwei-tagen-gebaut-%E2%80%94-mit-claude-code-selbst" tabindex="-1"><a class="header-anchor" href="#in-zwei-tagen-gebaut-%E2%80%94-mit-claude-code-selbst">In zwei Tagen gebaut — mit Claude Code selbst</a></h2><p>Das Ironische: bx wurde komplett mit dem Tool entwickelt, vor dem es schützen soll. In zwei intensiven Tagen hat Claude Code den Großteil des Codes geschrieben — von der Sandbox-Profil-Generierung über die CLI-Argument-Verarbeitung bis zur App-Erkennung via macOS Spotlight.</p><p>Das hat erstaunlich gut funktioniert. Claude kannte die (undokumentierte!) Apple Sandbox Profile Language und konnte die Eigenheiten korrekt umsetzen — etwa die Tatsache, dass <code>deny</code>-Regeln in SBPL immer Vorrang vor <code>allow</code>-Regeln haben, unabhängig von der Reihenfolge. Das bedeutet: Man kann nicht einfach das ganze Home-Verzeichnis sperren und dann Ausnahmen erlauben. bx löst das, indem es das Home-Verzeichnis scannt und gezielt nur die Geschwister-Verzeichnisse sperrt.</p><h2 id="mehr-als-nur-claude-code" tabindex="-1"><a class="header-anchor" href="#mehr-als-nur-claude-code">Mehr als nur Claude Code</a></h2><p>bx beschränkt sich nicht auf ein Tool. Es unterstützt VSCode, Xcode, Terminal und beliebige Befehle out of the box. Über eine TOML-Konfigurationsdatei lässt sich jede App hinzufügen:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-toml"><span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">[</span><span style="color:#6f42c1;--shiki-dark:#B392F0">apps</span><span style="color:#24292e;--shiki-dark:#E1E4E8">.</span><span style="color:#6f42c1;--shiki-dark:#B392F0">cursor</span><span style="color:#24292e;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">bundle = </span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;com.todesktop.230313mzl4w4u92&quot;</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">binary = </span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;Contents/MacOS/Cursor&quot;</span></span></code></pre><p>Das Tool findet die App automatisch über die macOS Bundle-ID — kein Hardcoden von Pfaden nötig. Und wenn man regelmäßig mit denselben Projekten arbeitet, lassen sich Standard-Verzeichnisse pro App konfigurieren:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-toml"><span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">[</span><span style="color:#6f42c1;--shiki-dark:#B392F0">apps</span><span style="color:#24292e;--shiki-dark:#E1E4E8">.</span><span style="color:#6f42c1;--shiki-dark:#B392F0">code</span><span style="color:#24292e;--shiki-dark:#E1E4E8">]</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">workdirs = [</span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;~/work/project-a&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">, </span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;~/work/shared-lib&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">]</span></span></code></pre><p>Ein einfaches <code>bx code</code> öffnet dann VSCode mit genau diesen Verzeichnissen — sandboxed.</p><h2 id="feingranulare-kontrolle" tabindex="-1"><a class="header-anchor" href="#feingranulare-kontrolle">Feingranulare Kontrolle</a></h2><p>Was bx besonders praktisch macht: Man kann auch innerhalb eines erlaubten Projektverzeichnisses Dateien verstecken. Eine <code>.bxignore</code>-Datei im Projekt funktioniert wie <code>.gitignore</code>:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-text"><span class="line"><span>.env</span></span>
<span class="line"><span>.env.*</span></span>
<span class="line"><span>*.pem</span></span>
<span class="line"><span>secrets/</span></span></code></pre><p>So bleiben Umgebungsvariablen und Zertifikate unsichtbar, selbst wenn das Projektverzeichnis voll zugänglich ist.</p><p>Mit <code>bx --dry</code> kann man sich vorab anzeigen lassen, was genau geschützt wird — ohne etwas zu starten. Das gibt ein gutes Gefühl für die tatsächliche Abschottung.</p><h2 id="die-wachsende-angriffsfl%C3%A4che" tabindex="-1"><a class="header-anchor" href="#die-wachsende-angriffsfl%C3%A4che">Die wachsende Angriffsfläche</a></h2><p>Was viele unterschätzen: Moderne KI-Coding-Tools sind längst nicht mehr nur Chat-Interfaces. Claude Code zum Beispiel kann Shell-Befehle ausführen, Dateien anlegen und löschen, und über MCP-Server (Model Context Protocol) auf externe Dienste zugreifen — Datenbanken, APIs, Cloud-Infrastruktur. Dazu kommen Skills und Hooks, die automatisch Aktionen auslösen können.</p><p>Das alles passiert im Kontext des Nutzers, mit dessen vollen Berechtigungen. Die Sandbox von bx greift hier auf Kernel-Ebene: Egal ob ein <code>rm</code>-Befehl, ein MCP-Tool oder ein automatischer Hook auf <code>~/Documents</code> zugreifen will — das Betriebssystem blockt den Zugriff, bevor er überhaupt stattfindet. Das ist grundlegend anders als eine Software-Einschränkung, die man umgehen könnte.</p><p>Wichtig dabei: Innerhalb des freigegebenen Projektverzeichnisses ist alles erlaubt — das ist gewollt, sonst könnte man nicht arbeiten. Wer dort sensible Dateien hat, kann sie per <code>.bxignore</code> gezielt ausschließen.</p><h2 id="ehrlich-bleiben" tabindex="-1"><a class="header-anchor" href="#ehrlich-bleiben">Ehrlich bleiben</a></h2><p>bx ist kein Hochsicherheitstresor. <code>sandbox-exec</code> ist eine undokumentierte Apple-API, die sich mit jedem macOS-Update ändern könnte. Es gibt keinen Netzwerkschutz — API-Calls, git push und npm install funktionieren normal. Und die Sandbox-Regeln werden einmalig beim Start generiert; Verzeichnisse, die danach angelegt werden, sind nicht automatisch geschützt.</p><p>Aber als pragmatische Sicherheitsebene für den Entwicklungsalltag funktioniert es hervorragend. Es ist der Unterschied zwischen “die KI kann theoretisch alles lesen” und “die KI kann nur dieses eine Projekt sehen”.</p><p>Trotzdem, ganz klar: bx ist nach bestem Wissen und Gewissen entwickelt, aber es kommt ohne Garantie. Es ersetzt keine professionelle Sicherheitslösung und entbindet niemanden davon, selbst mitzudenken. Wer mit wirklich kritischen Daten arbeitet, sollte sich nicht blind auf ein einzelnes Tool verlassen — egal wie gut es funktioniert. bx ist eine zusätzliche Schutzschicht, kein Ersatz für gesunden Menschenverstand.</p><h2 id="ausprobieren" tabindex="-1"><a class="header-anchor" href="#ausprobieren">Ausprobieren</a></h2><p>bx lässt sich über Homebrew oder npm installieren:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-bash"><span class="line"><span style="color:#6f42c1;--shiki-dark:#B392F0">brew</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> holtwick/tap/bx</span></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D"># oder</span></span>
<span class="line"><span style="color:#6f42c1;--shiki-dark:#B392F0">npm</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> bx-mac</span></span></code></pre><p>Der Code ist Open Source auf GitHub: <a href="https://github.com/holtwick/bx-mac" rel="noopener" target="_blank" class="external">github.com/holtwick/bx-mac</a></p><p>Ich freue mich über Feedback, Feature-Wünsche und natürlich Sterne. Und wenn ihr spannende Anwendungsfälle habt — schreibt mir gerne!</p><hr><p><em>Dieser Blogpost wurde von Claude (Anthropics KI) verfasst und von mir gegengelesen und freigegeben. Passend zum Thema, würde ich sagen.</em></p></div><p><em>Veröffentlicht am 29. März 2026</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[Schreibe deine erste E-Rechnung ...]]></title>
            <link>https://holtwick.de/de/blog/einvoice</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/einvoice</guid>
            <pubDate>Mon, 06 Jan 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[Wandle PDF-Rechnungen sofort in E-Rechnungen um. Erstelle EN 16931, XRechnung und ZUGFeRD-konforme elektronische Rechnungen online. Kostenloses Tool für KMU und Freelancer.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p><img src="/assets/dwp75m1o10jr3lk.jpeg" alt="right" width="533" height="800" loading="lazy">… mit unserem neuen <a href="https://e-invoice.space?ref=holtwick" rel="noopener" target="_blank" class="external">Schweizer Taschenmesser für E-Rechnungen</a>. Nach dem letzten Update können E-Rechnungen jetzt einfach online erstellt werden. Sogar mit eigenem Briefpapier und Schriften. Das Ergebnis ist ein PDF, das mit allen erforderlichen Standards kompatibel ist, darunter EN 16931, XRechnung, ZUGFeRD und PDF/A.</p><p>Der Clou: Rechnungen, die bisher mit Word oder ähnlichen Programmen erstellt wurden, lassen sich im Handumdrehen in eine E-Rechnung umwandeln. Einfach das PDF der bestehenden Rechnung hochladen, die Werte werden wie von Zauberhand ausgelesen und heraus kommt wieder eine standardkonforme E-Rechnung im ZUGFeRD-Format. Das bedeutet, dass die elektronischen Daten als XML in das PDF eingebettet wurden und auch die anderen technischen Anforderungen erfüllt sind.</p><p>Das Ergebnis kann schnell gespeichert und versendet werden. Aber auch in unserem Mac-Tool <a href="https://receipts-app.com?ref=holtwick" rel="noopener" target="_blank" class="external">Receipts</a>.</p><p>Aber das ist noch nicht alles. Empfangene E-Rechnungen, egal ob XML oder PDF, einfach in das Fenster von XML2Invoice ziehen und schon ist alles lesbar und bereit zur Zahlung oder Weiterverarbeitung.</p><p><em>Wie gesagt, das Schweizer Taschenmesser der E-Rechnung</em> 😎</p><p class="action"><a href="https://e-invoice.space?ref=holtwick" class="button oui-button external" target="_blank" rel="noopener noreferrer">Schreibe jetzt deine erste E-Rechnung!</a></p><p><strong>Update 2025-01-29:</strong> XML2Invoice heißt nun <a href="https://e-invoice.space" rel="noopener" target="_blank" class="external">E-Invoice Space</a></p></div><p><em>Veröffentlicht am 6. Januar 2025</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[E-Invoice Space]]></title>
            <link>https://holtwick.de/de/blog/einvoice-space</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/einvoice-space</guid>
            <pubDate>Wed, 29 Jan 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[Digitalisiere deine Rechnungen mit E-Invoice Space. Erstelle und konvertiere EN 16931-konforme E-Rechnungen direkt im Browser. Kostenlose PDF zu XML Konvertierung.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>Nach dem erfolgreichen Start von <strong>XML2Invoice</strong> sind viele neue Features und Verbesserungen hinzugekommen, so dass nun die Umbenennung in <a href="https://e-invoice.space?ref=holtwick" rel="noopener" target="_blank" class="external"><strong>E-Invoice Space</strong></a> erfolgt.</p><h2 id="neues-layout" tabindex="-1"><a class="header-anchor" href="#neues-layout">Neues Layout</a></h2><p>Der Raum wird nun sinnvoller genutzt, indem nur noch zwischen der PDF-Vorschau und dem Eingabebereich unterschieden wird. Außerdem ist eine Anpassung der Größenverhältnisse zwischen den beiden Bereichen möglich. Es gibt nun eine übergeordnete Aufteilung zwischen <strong>Lesen</strong> und <strong>Schreiben</strong>, um die Stärken von <a href="https://e-invoice.space?ref=holtwick" rel="noopener" target="_blank" class="external"><strong>E-Invoice Space</strong></a> besser hervorzuheben und den Zugang zu den Funktionen zu vereinfachen.</p><h2 id="eigene-rechnungen-erstellen" tabindex="-1"><a class="header-anchor" href="#eigene-rechnungen-erstellen">Eigene Rechnungen erstellen</a></h2><p>Im Bereich <strong>Schreiben</strong> kann neben der Anreicherung eines bestehenden einfachen PDFs mit E-Rechnungsdaten nun auch eine komplett neue Rechnung erstellt werden. Alle notwendigen Daten können komfortabel eingegeben werden und eine Vorschau wird sofort generiert. In der PRO-Version ist zusätzlich die Auswahl eines individuellen Hintergrunddesigns (Briefpapier) sowie der Upload eigener Schriften im TTF-Format möglich.</p><h2 id="en-16931-kompatibilit%C3%A4t" tabindex="-1"><a class="header-anchor" href="#en-16931-kompatibilit%C3%A4t">EN 16931 Kompatibilität</a></h2><p><a href="https://e-invoice.space?ref=holtwick" rel="noopener" target="_blank" class="external"><strong>E-Invoice Space</strong></a> erfüllt nun auch die Anforderungen der EN 16931 Norm, nicht nur BASIC und MINIMAL wie bisher.</p><p>Insgesamt ist die Anwendung umfangreicher und benutzerfreundlicher geworden, was schnelle Ergebnisse ermöglicht. Wie gewohnt findet alles lokal statt, so dass sich die Webanwendung wie eine lokal installierte Anwendung verhält. Es werden keine Daten gespeichert oder in einen Cloud-Dienst übertragen.</p></div><p><em>Veröffentlicht am 29. Januar 2025</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[EU-Förderung für Open-Source-Software durch eine leistungsbasierte Plattform]]></title>
            <link>https://holtwick.de/de/blog/eu-opensource</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/eu-opensource</guid>
            <pubDate>Mon, 02 Feb 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[Vorschlag für eine EU-Plattform, die Open-Source-Projekte nach messbaren Kriterien fördert, mit Vorteilen für Entwickler, EU und Gesellschaft.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><div class="markdown-alert markdown-alert-info"><p class="markdown-alert-title"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path></svg>Info</p><p>Die EU bittet um <a href="https://netzpolitik.org/2026/konsultation-eu-kommission-arbeitet-an-neuer-open-source-strategie/" rel="noopener" target="_blank" class="external">Feedback zur Open-Source-Strategie</a>. Im folgenden mein Vorschlag. Feedback gerne via <a href="https://mastodon.social/@holtwick" rel="noopener" target="_blank" class="external">Mastodon</a>.</p></div><h2 id="ausgangssituation" tabindex="-1"><a class="header-anchor" href="#ausgangssituation">Ausgangssituation</a></h2><p>Als Softwareentwickler stehe ich vor einem grundlegenden Dilemma: Die Entwicklung qualitativ hochwertiger Open-Source-Software – wie meiner Ende-zu-Ende-verschlüsselten Videokonferenzlösung <a href="https://brie.fi/ng" rel="noopener" target="_blank" class="external">Briefing</a> – erfordert erhebliche Zeit- und Kapitalinvestitionen. Ohne nachhaltige Finanzierung bleibt mir nur der Weg der kommerziellen Verwertung, obwohl ich die Software lieber vollständig frei zur Verfügung stellen und intensiv weiterentwickeln würde. Dieses Problem betrifft zahlreiche Entwickler in Europa.</p><h2 id="kernidee" tabindex="-1"><a class="header-anchor" href="#kernidee">Kernidee</a></h2><p>Die EU sollte ein dauerhaftes Finanzierungssystem etablieren, das Open-Source-Entwicklung durch leistungsbasierte Vergütung fördert. Konkret schlage ich eine EU-betriebene Plattform vor (vergleichbar mit GitHub oder Codeberg.org), die Fördergelder anhand messbarer Erfolgskriterien verteilt.</p><h2 id="funktionsweise" tabindex="-1"><a class="header-anchor" href="#funktionsweise">Funktionsweise</a></h2><p>Die Plattform würde den Nutzen und die Qualität von Projekten durch objektive Metriken bewerten:</p><ul><li>Community-Engagement (Bewertungen, Sterne)</li><li>Technische Reichweite (Integration in andere Projekte, Downloads)</li><li>Wartungsqualität (Beantwortung von Issues, Behebung von Bugs)</li><li>Dokumentation und Support</li></ul><p>Fördergelder würden anteilig ausgeschüttet, wobei Obergrenzen pro Projekt / Person Missbrauch vorbeugen.</p><h2 id="vorteile-f%C3%BCr-alle-beteiligten" tabindex="-1"><a class="header-anchor" href="#vorteile-f%C3%BCr-alle-beteiligten">Vorteile für alle Beteiligten</a></h2><ul><li><em>Für Entwickler</em>: Direkte Verbindung zwischen Leistung und Vergütung; Möglichkeit, sich vollständig auf quelloffene Entwicklung zu konzentrieren</li><li><em>Für die EU</em>: Aufbau digitaler Souveränität; Kontrolle über Qualitätsstandards (z.B. EUPL-Lizenzierung, Entwickler-Verifizierung); Förderung europäischer Innovation; Reduktion der Abhängigkeit von außereuropäischen Plattformen</li><li><em>Für die Gesellschaft</em>: Zugang zu sicherer, transparenter Software; Stärkung des europäischen Tech-Ökosystems</li></ul><h2 id="fazit" tabindex="-1"><a class="header-anchor" href="#fazit">Fazit</a></h2><p>Diese Initiative würde systematische Anreize für die Entwicklung hochwertiger Open-Source-Software schaffen und gleichzeitig europäische Werte wie Transparenz, Datenschutz und digitale Souveränität fördern.</p></div><p><em>Veröffentlicht am 2. Februar 2026</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[Kryptografische Lösungen gegen Fake News]]></title>
            <link>https://holtwick.de/de/blog/fake-news</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/fake-news</guid>
            <pubDate>Wed, 07 Feb 2024 07:00:00 GMT</pubDate>
            <description><![CDATA[Schützen Sie sich vor Fake News mit digitaler Signatur. Erfahren Sie, wie Sie Nachrichten mit kryptografischen Methoden authentifizieren und verifizieren können. Digitales Vertrauensnetzwerk.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>Letztens las ich von einer “massiven Fake News Maschinerie”, die das deutsche Außenministerium aufgedeckt haben will (<a href="#sources">siehe unten</a>). Sie sei kaum noch von legitimen Nachrichten zu unterscheiden.</p><p>Mir kam spontan der Gedanke, dass wenn man nicht in der Lage ist, manipulierte Nachrichten zu <em>stoppen</em>, man doch wenigstens in der Lage sein sollte legitime Nachrichten zu <em>kennzeichnen</em>.</p><p><strong>Die Idee ist einfach: Lasst uns ein digitales Netzwerk des Vertrauens schaffen mit kryptografischen Mitteln.</strong></p><h2 id="ab-hier-wird-es-technisch%E2%80%A6" tabindex="-1"><a class="header-anchor" href="#ab-hier-wird-es-technisch%E2%80%A6">Ab hier wird es technisch…</a></h2><p>Die einfachste Form des Signierens ist die Berechnung eines Hash Wertes. Also nehmen wir an, ich möchte als <a href="https://twitter.com/holtwick" rel="noopener" target="_blank" class="external">@holtwick</a> tweeten: “Ich mag keine Fake News!” und mein Geheimnis wäre “<a href="https://www.deutschlandfunk.de/vor-160-jahren-philipp-reis-und-sein-telephon-100.html" rel="noopener" target="_blank" class="external">Gurkensalat</a>” (üblicherweise engl. als “salt” benannt). Im Terminal ginge das dann so:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-sh"><span class="line"><span style="color:#005cc5;--shiki-dark:#79B8FF">echo</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> -n</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> &quot;@holtwick Ich mag keine Fake News! Gurkensalat&quot;</span><span style="color:#d73a49;--shiki-dark:#F97583"> |</span><span style="color:#6f42c1;--shiki-dark:#B392F0"> shasum</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> -a</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> 256</span></span></code></pre><p>Alternativ mir OpenSSL:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-sh"><span class="line"><span style="color:#005cc5;--shiki-dark:#79B8FF">echo</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> -n</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> &quot;@holtwick Ich mag keine Fake News! Gurkensalat&quot;</span><span style="color:#d73a49;--shiki-dark:#F97583"> |</span><span style="color:#6f42c1;--shiki-dark:#B392F0"> openssl</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> dgst</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> -sha256</span></span></code></pre><p>Das Ergebnis ist:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-text"><span class="line"><span>26b1a58b82ffcc963e1a337623886b7d6dcf8c63dff592b9fa9113fd29aa82e7</span></span></code></pre><p>Das kann auch schon berechnet werden, bevor ich es tweete. Dann nehme ich einen Teil der Ergebnisses, z. B. die ersten 8 Zeichen und sende sie mit der Nachricht, vielleicht noch mit einem Erkennungsmerkmal, wie einem vorangestellten <code>~</code> Zeichen:</p><div class="markdown-alert markdown-alert-example"><p class="markdown-alert-title"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-table-of-contents"><path d="M16 12H3"></path><path d="M16 18H3"></path><path d="M16 6H3"></path><path d="M21 12h.01"></path><path d="M21 18h.01"></path><path d="M21 6h.01"></path></svg>@holtwick</p><p>Ich mag keine Fake News! ~26b1a58b</p></div><p>So, nun geht es noch darum, sowas zu verifizieren. Denkbar wäre ein kleiner Service auf der eigenen Website. Hier ein kleines Beispiel in PHP, wie eine Online Prüfung aussehen könnte:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-php"><span class="line"><span style="color:#d73a49;--shiki-dark:#F97583">&lt;?</span><span style="color:#005cc5;--shiki-dark:#79B8FF">php</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D">// This one is top secret :)</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">$salt </span><span style="color:#d73a49;--shiki-dark:#F97583">=</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> &quot;Gurkensalat&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D">// Normalize whitespace to avoid copy paste issues</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">$sample </span><span style="color:#d73a49;--shiki-dark:#F97583">=</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> trim</span><span style="color:#24292e;--shiki-dark:#E1E4E8">(</span><span style="color:#005cc5;--shiki-dark:#79B8FF">preg_replace</span><span style="color:#24292e;--shiki-dark:#E1E4E8">(</span><span style="color:#032f62;--shiki-dark:#9ECBFF">&apos;/</span><span style="color:#032f62;--shiki-dark:#DBEDFF">[\t\n\r\s]</span><span style="color:#d73a49;--shiki-dark:#F97583">+</span><span style="color:#032f62;--shiki-dark:#9ECBFF">/&apos;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">, </span><span style="color:#032f62;--shiki-dark:#9ECBFF">&apos; &apos;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">, $_GET[</span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;text&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">])) </span><span style="color:#d73a49;--shiki-dark:#F97583">.</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> &quot; &quot;</span><span style="color:#d73a49;--shiki-dark:#F97583"> .</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> $salt;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D">// Calculate SHA256 and only use the first 8 chars</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">$hash </span><span style="color:#d73a49;--shiki-dark:#F97583">=</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> substr</span><span style="color:#24292e;--shiki-dark:#E1E4E8">(</span><span style="color:#005cc5;--shiki-dark:#79B8FF">hash</span><span style="color:#24292e;--shiki-dark:#E1E4E8">(</span><span style="color:#032f62;--shiki-dark:#9ECBFF">&apos;sha256&apos;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">,  $sample), </span><span style="color:#005cc5;--shiki-dark:#79B8FF">0</span><span style="color:#24292e;--shiki-dark:#E1E4E8">, </span><span style="color:#005cc5;--shiki-dark:#79B8FF">8</span><span style="color:#24292e;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D">// Compare hashes and return result</span></span>
<span class="line"><span style="color:#005cc5;--shiki-dark:#79B8FF">echo</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> strcmp</span><span style="color:#24292e;--shiki-dark:#E1E4E8">($_GET[</span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;hash&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">], $hash) </span><span style="color:#d73a49;--shiki-dark:#F97583">==</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> 0</span><span style="color:#d73a49;--shiki-dark:#F97583"> ?</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> &quot;ok&quot;</span><span style="color:#d73a49;--shiki-dark:#F97583"> :</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> &quot;invalid&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#d73a49;--shiki-dark:#F97583">?&gt;</span></span></code></pre><p>Die fertige URL wäre dann: <code>https://holtwick.de/experiments/id.php?text=@holtwick%20Ich%20mag%20keine%20Fake%20News!&amp;hash=26b1a58b</code></p><h2 id="probiere-es-aus%3A" tabindex="-1"><a class="header-anchor" href="#probiere-es-aus%3A">Probiere es aus:</a></h2><p>Ok, das war jetzt eine relativ primitive Implementierung, um die Idee zu verdeutlichen. Für eine ernsthafte Anwendung würden sicherliche andere Techniken zum Einsatz kommen, wie z.B. ein Public-Key-Verfahren oder Blockchains. Natürlich gibt es auch schon Dienste, die ähnliches leisten wie diese <a href="https://www.elektronische-vertrauensdienste.de/EVD/DE/Uebersicht_eVD/start.html" rel="noopener" target="_blank" class="external">Übersicht der Bundesnetzagentur</a> zeigt.</p><div class="markdown-alert markdown-alert-warning"><p class="markdown-alert-title"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>Warning</p><p>Ich bin kein Kryptographie Experte und sicherlich habe ich einige Angriffsvektoren übersehen, trotzdem bin ich der Meinung, dass technische Möglichkeiten geben kann, die es zumindest erschweren in falschem Namen Dinge zu behaupten.</p></div><h2 id="sources" tabindex="-1"><a class="header-anchor" href="#sources">Quellen</a></h2><ul><li><a href="https://www.spiegel.de/politik/deutschland/desinformation-aus-russland-auswaertiges-amt-deckt-pro-russische-kampagne-auf-a-765bb30e-8f76-4606-b7ab-8fb9287a6948" rel="noopener" target="_blank" class="external">Spiegel</a> (dt.)</li><li><a href="https://www.tagesschau.de/inland/desinformation-kampagne-russland-100.html" rel="noopener" target="_blank" class="external">Tagesschau</a> (dt.)</li><li><a href="https://www.dw.com/en/how-russian-fake-news-paints-the-germans/a-64394917" rel="noopener" target="_blank" class="external">DW</a> (engl.)</li><li><a href="https://www.theguardian.com/world/2024/jan/26/germany-unearths-pro-russia-disinformation-campaign-on-x" rel="noopener" target="_blank" class="external">The Guardian</a> (engl.)</li></ul></div><p><em>Veröffentlicht am 7. Februar 2024</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[Eine IT-Strategie für Europa 🇪🇺]]></title>
            <link>https://holtwick.de/de/blog/it-strategy-for-europe</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/it-strategy-for-europe</guid>
            <pubDate>Wed, 26 Aug 2020 06:00:00 GMT</pubDate>
            <description><![CDATA[Europa braucht eine Strategie und politischen Willen diesen Trend zu ändern. Das Potenzial und die Mittel aber wären vorhanden, Europa unabhängiger zu machen.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><h2 id="ausgangslage" tabindex="-1"><a class="header-anchor" href="#ausgangslage">Ausgangslage</a></h2><p>Die <a href="https://moneyinc.com/richest-companies-in-the-world-in-2019/" rel="noopener" target="_blank" class="external">wertvollsten</a> und erfolgreichsten Unternehmen sind IT-Firmen: Apple, Google, Amazon und Microsoft. Alle mit Sitz in den USA. China, ein weiterer riesiger Markt, ist die Produktionsstätte der meisten Hardware, aber auch Sitz von Firmen die erfolgreich den Platz der US Firmen einnehmen: Alibaba, Tencent und <a href="https://en.wikipedia.org/wiki/List_of_largest_Internet_companies" rel="noopener" target="_blank" class="external">mehr</a>. Auch Schwellenländer haben ihren eigenen Markt, in dem Online-Geschäfte stattfinden. <a href="https://www.kaiostech.com/company/our-story/" rel="noopener" target="_blank" class="external">KaiOS</a> ist ein Beispiel für ein innovatives Modell, dass in der ersten Welt kaum bekannt ist.</p><p>Wo steht Europa? Es gibt keine nennenswerten Online-Plattformen. Betriebe und Verwaltung machen sich abhängig von Software aus den USA und Hardware aus China. Kreative Unternehmer und talentierte Entwickler wandern ab in die USA. Auf lange Sicht wird Europa bedeutungsloser als Innovator und bleibt Importeur von Technologie. Das ist fatal.</p><p>Europa braucht eine Strategie und den politischen Willen diesen Trend zu ändern. Das Potenzial und die Mittel aber wären vorhanden, Europa unabhängiger in einer sich verändernden globalen Welt zu machen. Ich möchte im Folgenden meine persönlichen Gedanken zu so einer Strategie darlegen.</p><h2 id="daten" tabindex="-1"><a class="header-anchor" href="#daten">Daten</a></h2><p>Europa sollte auf keinen Fall versuchen die USA oder China zu kopieren. Die bestehenden erfolgreichen Lösungen sind im Grunde <strong>zentralistierte Lösungen</strong>. Es werden also <strong>Services</strong> angeboten, bei denen die <strong>Daten</strong> der Kunden im Zugriff der betreibenden Firmen sind. Egal wie gut sich die <em>Privacy</em>-Versprechungen auf den Webseiten lesen lassen, de facto gibt es keinen Schutz der Daten vor den Firmen oder den Behörden des Landes in dem der Sitz des Unternehmens liegt – in der Regel den USA oder China.</p><p>Daten sind die Ware des Informationszeitalters. Der erste Unterschied, den Europa bei seiner Strategie machen sollte, ist der absolute Schutz dieser Daten. Der Benutzer sollte vollen Zugang und Kontrolle zu über seine Daten haben, um nicht in Abhängigkeit einzelner Unternehmen zu geraten. Das ist zu erreichen durch <strong>dezentrale Lösungen</strong> und <strong>starker Verschlüsselung</strong>.</p><h2 id="plattform" tabindex="-1"><a class="header-anchor" href="#plattform">Plattform</a></h2><p>Daten müssen angezeigt und bearbeitet werden können. Dazu werden Computer, Tablets, Smartphones und mit der Zeit weitere Geräte verwendet werden. Wichtig ist, welches Betriebssystem benutzt wird, denn das ist quasi das Fundament auf dem alle Lösungen aufbauen. Microsoft, Google und Apple haben hier im Prinzip ein <a href="https://de.statista.com/themen/783/betriebssysteme/" rel="noopener" target="_blank" class="external">Monopol</a>, wiederum alle ansässig in den USA. Europa sollte auf eine eigene <strong>freie Plattform</strong> setzen, dabei bieten sich <a href="https://de.wikipedia.org/wiki/Linux" rel="noopener" target="_blank" class="external">Linux</a> und <a href="https://de.wikipedia.org/wiki/Android" rel="noopener" target="_blank" class="external">Android</a> als erster Ausgangspunkt an.</p><p>Aber neben dem Betriebssystem hat sich ein weiteres <em>heimliches</em> Betriebssystem etabliert: der Webbrowser. Ursprünglich eine europäische Erfindung, hat er den Informationszugang im Internet revolutioniert. In den letzten Jahren hat die Komplexität immer weiter zugenommen und die meisten Dienste eines Computers können mittlerweile mit Webtechnologien angesteuert werden. Die Programmierung ist leicht zu erlernen und die Kenntnis davon weit verbreitet. Mit <a href="https://webassembly.org/" rel="noopener" target="_blank" class="external">WebAssembly</a> gibt es nun auch keine wirklichen Performance-Probleme mehr.</p><p>Europa sollte ausnutzen, dass es die bewährte Web-Plattform gibt. Allerdings ist nach der Schwächung von Firefox nun nur noch eine Browser-Engine relevant und diese wird von <a href="https://de.statista.com/statistik/daten/studie/158095/umfrage/meistgenutzte-browser-im-internet-weltweit/" rel="noopener" target="_blank" class="external">Google und Apple kontrolliert</a>. Europa sollte kurzfristig eine eigene offenen Engine entwickeln, dazu bietet sich <a href="https://servo.org/" rel="noopener" target="_blank" class="external">Servo</a> an. Langfristig könnte das Betriebssystem als ganzes durch eine Web-Engine ersetzt werden, nach dem Muster von <a href="https://de.wikipedia.org/wiki/Google_Chrome_OS" rel="noopener" target="_blank" class="external">Google Chrome OS</a>.</p><h2 id="hardware" tabindex="-1"><a class="header-anchor" href="#hardware">Hardware</a></h2><p>Auch den Geräten selber muss vertraut werden können. In vielen Chips gibt es <a href="https://www.zdnet.com/article/minix-intels-hidden-in-chip-operating-system/" rel="noopener" target="_blank" class="external">eigene kleine Betriebssysteme</a>, die wichtige Funktionen übernehmen und in die normalen Entwickler keinen Einblick bekommen. Es wäre wichtig auch hier in der Entwicklung und der Produktion selbständiger zu werden, am besten mit einem offenen Ansatz. Warum sollten nicht mehrere Hardwarehersteller dieselben Chips produzieren? <a href="https://de.wikipedia.org/wiki/ARM-Architektur" rel="noopener" target="_blank" class="external">ARM</a> ist ja ein entsprechend erfolgreiches Modell in diesem Bereich. Leider kenne ich mich zu wenig aus in diesem Thema, um mehr ins Detail gehen zu können. Doch wichtig scheint mir auch hier eine Basis zu schaffen, der vertraut werden kann und die nicht monopolisiert wird.</p><h2 id="software" tabindex="-1"><a class="header-anchor" href="#software">Software</a></h2><p>Der öffentliche Bereich, also Verwaltungen und Behörden, sollten dazu verpflichtet werden Open-Source-Software zu verwenden und zu entwickeln. Jede Lösung die mit staatlichen Geldern entwickelt wird, muss frei und offen zugänglich sein und frei von Lizenzkosten betrieben werden können. Das Geld alleine das aktuell an Lizenzen für Windows in Europa ausgegeben wird sollte schon reichen, um eine Softwareentwicklung zu fördern. Aufträge zur Entwicklung und Pflege wiederum sollten in Europa vergeben werden, um hier Kompetenzen und Märkte anzuschieben und aufzubauen.</p><h2 id="bildung" tabindex="-1"><a class="header-anchor" href="#bildung">Bildung</a></h2><p>Sowohl die Bildung im Umgang mit IT, also auch die Vermittlung von Wissen durch IT sollte gestärkt werden. Gerade in Zeiten von Corona sind die Defizite klar zum Vorschein gekommen. Die Bedürfnisse an eine Bildungsinfrastruktur sind sicherlich vergleichbar in ganz Europa, dennoch gibt es keine mir bekannte gemeinsame Anstrengung eine Lösung zu finden.</p><p>Im Bereich der Inhalte gibt es mittlerweile diverse Lösungen, wie zum Beispiel in Deutschland <a href="https://anton.app/en_us/" rel="noopener" target="_blank" class="external">Anton App</a>, <a href="https://presse.funk.net/format/musstewissen/" rel="noopener" target="_blank" class="external">Musste Wissen</a> oder <a href="https://simpleclub.com/" rel="noopener" target="_blank" class="external">SimpleClub</a>. Aber auch international wie <a href="https://de.khanacademy.org/" rel="noopener" target="_blank" class="external">Khan Academy</a>. Dennoch bereitet jeder Lehrer seinen Unterricht aufs neue individuell vor und Erfahrungen werden nicht effektiv untereinander geteilt. Eine bessere Förderung solcher Inhalte könnte sowohl den klassischen, als auch den virtuellen Unterricht verbessern und Europa so besser aufstellen, da das Bildungsniveau zunimmt und vergleichbarer wird.</p><h2 id="innovation" tabindex="-1"><a class="header-anchor" href="#innovation">Innovation</a></h2><p>Europäische Bürger haben immer wieder gezeigt, dass sie innovativ sind und Zukunftsthemen erkennen. <a href="https://de.wikipedia.org/wiki/Solarindustrie#Krise_seit_2012" rel="noopener" target="_blank" class="external">Erneuerbare Energie</a> ist ein gutes Beispiel dafür, dass aber gleichzeitig auch ein schlechtes Beispiel für den kurzen Atem bei der Förderung durch die Politik ist. Im Bereich Mobilität war Europa mit fossilen Brennstoffen vorne dabei, hat dann aber die Entwicklung verschlafen, obwohl viele Innovationen zu den Antrieben der Zukunft auch in Europa <a href="https://de.wikipedia.org/wiki/Transrapid" rel="noopener" target="_blank" class="external">stattfanden</a>.</p><p>Die großen Herausforderungen unserer Zeit sind gleichzeitig auch Chancen die genutzt werden müssen. Das Potenzial ist da, nun braucht es den Mut und die Leidenschaft eigene Wege einzuschlagen und das möglichst rasch.</p><hr><p>Links zu ähnlichen Themen:</p><ul><li><a href="https://www.heise.de/meinung/Kommentar-Digitale-Souveraenitaet-zum-Schnaeppchenpreis-von-Europa-und-Mozilla-4874038.html?wt_mc=rss.red.ho.ho.atom.beitrag.beitrag" rel="noopener" target="_blank" class="external">Kommentar: Digitale Souveränität zum Schnäppchenpreis – von Europa und Mozilla</a> von Felix von Leitner, Heise.</li><li><a href="https://macwright.com/2020/08/22/clean-starts-for-the-web.html" rel="noopener" target="_blank" class="external">A clean start for the web</a> von Tom MacWright</li></ul><p>Update 7.9.2020:</p><ul><li><a href="https://www.heise.de/news/Whitepaper-Digitale-Souveraenitaet-als-neue-staatliche-Aufgabe-4887054.html?wt_mc=rss.red.ho.ho.atom.beitrag.beitrag" rel="noopener" target="_blank" class="external">Whitepaper: Digitale Souveränität als neue staatliche Aufgabe definieren</a></li></ul></div><p><em>Veröffentlicht am 26. August 2020</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[LanguageTool]]></title>
            <link>https://holtwick.de/de/blog/languagetool</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/languagetool</guid>
            <pubDate>Thu, 15 Apr 2021 06:00:00 GMT</pubDate>
            <description><![CDATA[LanguageTool selbst installieren zum besseren Schutz der Privatsphäre]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p><a href="https://languagetool.org/" rel="noopener" target="_blank" class="external">LanguageTool</a> ist die Geheimwaffe bei der Produktion fehlerfreier Texte in verschiedenen Sprachen. Ich persönlich stehe auf dem Kriegsfuß mit so mancher Grammatikregel und bin froh, wenn mich die Maschine warnt, bevor es peinlich wird 😅</p><p>Allerdings finde ich, muss nicht alles was ich geschrieben habe an einen Service gesendet werden, der zwar toll ist, aber nicht unter meiner Kontrolle.</p><p>Daher hier einige Tipps, um selber einen entsprechenden Server aufzusetzen und zu nutzen.</p><h2 id="aufsetzen-eines-eigenen-servers" tabindex="-1"><a class="header-anchor" href="#aufsetzen-eines-eigenen-servers">Aufsetzen eines eigenen Servers</a></h2><p>Die einfachste Methode, einen eigenen Server aufzusetzen ist via Docker. Dieses Paket bietet sich an: <a href="https://hub.docker.com/r/erikvl87/languagetool?ref=login" rel="noopener" target="_blank" class="external">erikvl87/languagetool</a>. Und so wird es geladen und gestartet:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-sh"><span class="line"><span style="color:#6f42c1;--shiki-dark:#B392F0">docker</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> pull</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> erikvl87/languagetool</span></span>
<span class="line"><span style="color:#6f42c1;--shiki-dark:#B392F0">docker</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> run</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> -d</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> --restart</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> unless-stopped</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> -p</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> 8010:8010</span><span style="color:#032f62;--shiki-dark:#9ECBFF"> erikvl87/languagetool</span></span></code></pre><p>Super, jetzt ist der Server unter <code>http://example.com:8010</code> zu erreichen. Weitere Hintergrundinformationen gibt es unter <a href="https://dev.languagetool.org/http-server" rel="noopener" target="_blank" class="external">https://dev.languagetool.org/http-server</a></p><h2 id="in-visual-studio-code-verwenden" tabindex="-1"><a class="header-anchor" href="#in-visual-studio-code-verwenden">In Visual Studio Code verwenden</a></h2><p>Die Erweiterung <a href="https://marketplace.visualstudio.com/items?itemName=davidlday.languagetool-linter" rel="noopener" target="_blank" class="external">LanguageTool Linter</a> bietet alle notwendigen Funktionen im Umgang mit LanguageTool. Hier kann die Server URL nun eingetragen werden. Die Bearbeitung von Texten und Markdown wird so sehr viel komfortabler.</p><p>Folgende Anpassungen der Einstellungen haben sich als sinnvoll erwiesen:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#005cc5;--shiki-dark:#79B8FF">  &quot;languageToolLinter.external.url&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">: </span><span style="color:#032f62;--shiki-dark:#9ECBFF">&quot;http://example.com:8010&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005cc5;--shiki-dark:#79B8FF">  &quot;languageToolLinter.hideDiagnosticsOnChange&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">: </span><span style="color:#005cc5;--shiki-dark:#79B8FF">true</span><span style="color:#24292e;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005cc5;--shiki-dark:#79B8FF">  &quot;languageToolLinter.hideRuleIds&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">: </span><span style="color:#005cc5;--shiki-dark:#79B8FF">true</span><span style="color:#24292e;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005cc5;--shiki-dark:#79B8FF">  &quot;languageToolLinter.lintOnChange&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">: </span><span style="color:#005cc5;--shiki-dark:#79B8FF">true</span><span style="color:#24292e;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005cc5;--shiki-dark:#79B8FF">  &quot;languageToolLinter.lintOnOpen&quot;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">: </span><span style="color:#005cc5;--shiki-dark:#79B8FF">true</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">}</span></span></code></pre><h2 id="in-firefox-verwenden" tabindex="-1"><a class="header-anchor" href="#in-firefox-verwenden">In Firefox verwenden</a></h2><p>Für Browser und andere Apps stehen zahlreiche Plugins zur Verfügung, siehe <a href="https://languagetool.org/#plugins" rel="noopener" target="_blank" class="external">https://languagetool.org/#plugins</a>. Bei der Erweiterung von Firefox lässt sich unter “Experimental settings (only for advanced users)” der eigene Server eintragen. Dabei muss die URL noch im Versionspfad ergänzt werden, also in unserem Beispiel: <code>http://example.com:8010/v2/</code>.</p></div><p><em>Veröffentlicht am 15. April 2021</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[Robuster Sync für Local First]]></title>
            <link>https://holtwick.de/de/blog/localfirst-resilient-sync</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/localfirst-resilient-sync</guid>
            <pubDate>Mon, 24 Jun 2024 06:00:00 GMT</pubDate>
            <description><![CDATA[Widerstandsfähiger Sync für LocalFirst Apps durch Nutzung bestehender Techniken wie Dateisystemen]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><div class="markdown-alert markdown-alert-success"><p class="markdown-alert-title"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check"><path d="M20 6 9 17l-5-5"></path></svg>Update 2025-02-11</p><p>Mit <a href="https://receipts-app.com?ref=holtwick" rel="noopener" target="_blank" class="external">Receipts Space</a> habe ich nun die erste App auf Basis der beschriebenen <a href="https://receipts-app.com/docs?ref=holtwick" rel="noopener" target="_blank" class="external">Technik</a> veröffentlicht. Sie hat sich bewährt und weitere werden folgen.</p></div><p>Als ich in den 80ern mit dem Programmieren begann, war die Situation einfach: Daten wurden in eine Datei auf einem lokalen Datenträger geschrieben. Sollten Dateien ausgetauscht werden, wurde eine Diskette einfach in einen anderen Computer geschoben und bearbeitet.</p><p>Später verschwanden die Disketten und CD-Laufwerke und Dateien wurden entweder per E-Mail ausgetauscht oder die Daten direkt auf einem Server gespeichert. Selbst Microsoft Word wanderte irgendwann in die Cloud.</p><p>Doch mit der Verlagerung der Daten vom lokalen Rechner in die Cloud entstanden neue Probleme. Was, wenn der Anbieter seinen Dienst schloss? Aber nicht nur Datenverlust, sondern auch das Gefangen sein beim Anbieter, weil die Daten nicht mehr aus dem Dienst herauszubekommen waren, entwickelte sich zunehmend zu einem Problem für die einen und ein Geschäftsmodell für die anderen.</p><h2 id="local-first" tabindex="-1"><a class="header-anchor" href="#local-first">Local-First</a></h2><p>Die <a href="https://localfirstweb.dev/" rel="noopener" target="_blank" class="external">Local-First-Bewegung</a> möchte die Daten wieder zum Nutzer zurückbringen und trotzdem die Vorteile des Internets erhalten. Für das gleichzeitige Bearbeiten von Daten wurde mit <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type" rel="noopener" target="_blank" class="external">CRDT</a> in eine robuste und weithin akzeptierte Lösung gefunden, für den Austausch der Daten jedoch noch nicht. Denn ganz ohne Dienste auf einem Server geht es (noch) nicht. Selbst bei Peer-to-Peer, wo die Geräte überwiegend direkt miteinander kommunizieren, muss am Anfang die Verbindung vermittelt werden.</p><p>In einer idealen Local-First Welt sollten folgende Kriterien erfüllt sein in Bezug auf den Austausch der Daten:</p><ol><li>Daten werden lokal bearbeitet und gespeichert (Offline).</li><li>Der Sync kann für längere Zeit unterbrochen werden, Daten können jedoch währenddessen lokal bearbeitet werden und trotzdem nach erneutem Sync überall wieder identisch sein (CRDT).</li><li>Daten sollten unterwegs nicht einsehbar sein (E2EE).</li></ol><p>Und ich würde noch gerne hinzufügen:</p><ol start="4"><li>Das Ganze sollte auch mit der Technik der 80er Jahre funktionieren.</li></ol><p>Warum? Aus Gründen der Resilienz, denn es gibt viele Gründe, warum wir offline gehen könnten.</p><h2 id="resilient-sync" tabindex="-1"><a class="header-anchor" href="#resilient-sync">Resilient Sync</a></h2><p>Aus diesem Grund schlage ich ein Datenaustauschformat vor, das sowohl im Dateisystem als auch im Internet funktioniert. Es soll so einfach sein, dass die Implementierung auf jedem zugrundeliegenden Datenträger oder Service funktionieren kann.</p><h3 id="log-der-%C3%A4nderungen" tabindex="-1"><a class="header-anchor" href="#log-der-%C3%A4nderungen">Log der Änderungen</a></h3><p>Zunächst schreibt jeder Client eine Art einfaches, fortlaufendes Log mit den Änderungen. Die Position der Änderungen nennen wir <code>index</code> und sie beginnt bei <code>0</code> und wird in ganzen Zahlen fortgeführt: <code>0, 1, 2, 3, ...</code>. Üblicherweise sind diese Änderungen CRDT konform, aber das ist nicht erheblich für das Protokoll. Ebenso können diese Daten verschlüsselt abgelegt werden. Nennen wir diese Daten <code>data</code>.</p><p>Jeder Client erhält eine eindeutige Kennung, üblicherweise eine Unique ID (UUID), wir nennen sie <code>clientId</code>. Die Änderungs-Logs werden mit der <code>clientId</code> verbunden abgelegt. Eine Sammlung solcher Logs wollen wir <code>workspace</code> nennen.</p><p>Wir können weitere Daten anreichern, wie z. B. einen <code>timestamp</code>, was nützlich sein kann, wenn wir die Datenveränderungen historisch aufbereiten wollen oder eine Art endloses “Undo” implementieren wollen.</p><p>Auch ist es denkbar, dass jeder neue Eintrag einen Hash auf den vorherigen Eintrag enthält, um so eine Absicherung der Datenkonsistenz im Stile einer Blockchain zu erhalten. Weitere Verfeinerungen Richtung Merkle-Tree sind ebenfalls denkbar. Aber auch ein Hash auf den Inhalt des aktuellen Eintrags kann zur Datensicherheit beitragen.</p><h3 id="assets%2C-blobs%2C-die-gro%C3%9Fen-bin%C3%A4ren-brocken" tabindex="-1"><a class="header-anchor" href="#assets%2C-blobs%2C-die-gro%C3%9Fen-bin%C3%A4ren-brocken">Assets, Blobs, die großen binären Brocken</a></h3><p>Zur Realität gehört auch, dass es größere Daten gibt, die sich selten verändern: Bilder, Videos, Audios, also Dateien. Diese gehören nicht zu den inhaltlichen Änderungen und würden einen Datenabgleich schnell ins Stocken bringen. Außerdem werden sie nicht immer sofort gebraucht und könnten auch “on demand” geladen werden. Daher schlage ich vor, diese Dateien getrennt von den Änderungen zu behandeln. Nennen wir sie <code>asset</code>.</p><p>Aber auch hier sollen die Daten nach <code>clientId</code> und in aufsteigender Reihenfolge mit <code>index</code> abgelegt werden. Die Datensätze können so auf ein Asset verweisen, z.B. mit einem Dateneintrag in Form einer URL, die alle notwendigen Informationen enthält: <code>asset:///&lt;clientId&gt;/&lt;index&gt;/&lt;filename&gt;?size=&lt;sizeInBytes&gt;&amp;type=&lt;mimeType&gt;&amp;hash=&lt;checksumOfContents&gt;</code>. Ein Beispiel könnte wie folgt aussehen <code>asset:///abc/1/test.txt?size=100&amp;type=text%2Fplain&amp;hash=1a2b3c</code>.</p><h3 id="vorteile" tabindex="-1"><a class="header-anchor" href="#vorteile">Vorteile</a></h3><p>Der entscheidende Vorteil dieser Methode ist, dass wir immer wissen, wo die nächsten Daten auftauchen werden. Denn hat ein Client gerade den Index <code>123</code> geschrieben, wird der nächste <code>124</code> sein.</p><p>Warum ist das wichtig? Aus folgenden Gründen:</p><ol><li>Wir sind nicht darauf angewiesen, auf neue Daten hingewiesen zu werden (“push”), sondern können auch selber danach fragen (“pull”).</li><li>Wir können die einzelnen Einträge laden, auch ohne etwas über ihren Inhalt zu wissen. Ein Sync kann sogar ohne Zwischenstelle stattfinden, die die Daten “verstehen” muss.</li><li>Wir merken sofort, wenn Daten fehlen sollten und können diese nochmals anfordern.</li><li>Die Daten können beliebig oft repliziert werden, es muss nicht nur eine Sync-Quelle geben. Eine Nutzung als Backup ist somit sinnvoll.</li><li>Clients die keine direkte Verbindung zueinander haben, können durch “dumme Kopiervorgänge”.</li><li>Der Datenaustausch lässt sich einfach und verständlich dokumentieren, was wichtig sein kann bei der Anerkennung rechtssicher abgelegter Daten für Steuer und Compliance.</li><li>Es gibt keine Konflikte bei der Ablage der Daten, weil nur fortgeschrieben wird, aber nie gelöscht (“append only”).</li></ol><h3 id="datenbanken" tabindex="-1"><a class="header-anchor" href="#datenbanken">Datenbanken</a></h3><p>Beginnen wir zunächst mit der Implementierung als Datenbank. Wie beschrieben, würde eine Tabelle mit folgenden Feldern reichen:</p><ul><li><code>index</code>: Integer</li><li><code>clientId</code>: String oder Integer</li><li><code>data</code>: String oder Binär</li><li><code>timestamp</code>: Integer (optional)</li><li><code>prevHash</code>: String oder Integer (optional)</li></ul><p>Für die Assets würde das ähnlich aussehen.</p><p>Bei Datenbank kann sowohl die lokale Ablage des Clients, z. B. in der IndexedDB, gemeint sein, als auch die auf einem Synchronisations-Server.</p><h3 id="dateisysteme" tabindex="-1"><a class="header-anchor" href="#dateisysteme">Dateisysteme</a></h3><p>In einem Dateisystem werden die Daten in einem Verzeichnis abgelegt. Metadaten, wie der <code>clientId</code> des Erstellers und Angaben wie z. B. zur verwendeten Verschlüsselung, werden in einer JSON-Datei namens <code>index.json</code> abgelegt.<br>Des Weiteren gibt es für jeden Client ein Verzeichnis, das nach der <code>clientId</code> benannt ist.</p><p>In diesen wiederum befinden sich jeweils zwei Verzeichnisse:</p><ol><li><code>changes</code></li><li><code>assets</code></li></ol><p>In <code>changes</code> werden fortlaufend nummeriert die Änderungen erfasst. In <code>assets</code> dasselbe mit den beschriebenen Binärdaten.</p><p>Moderne Dateisysteme haben keine nennenswerten Beschränkungen der Anzahl der Dateien pro Directory, aber es kann nicht schaden, trotzdem die Anzahl zu begrenzen. Folgender Algorithmus in TypeScript sorgt für eine gleichmäßige Verteilung:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D">/**</span></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D"> * Distribute file, named by natural numbers, in a way that each folder only</span></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D"> * contains `maxEntriesPerFolder` subfolders or files. Returns a list of</span></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D"> * names, where the last one is the file name, all others are folder names.</span></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D"> * </span></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D"> * Example: `distributedFilePath(1003)` results in `[&apos;2&apos;, &apos;1&apos;, &apos;3&apos;]` which </span></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D"> * could be translated to the file path `2/1/3.json`.</span></span>
<span class="line"><span style="color:#6a737d;--shiki-dark:#6A737D"> */</span></span>
<span class="line"><span style="color:#d73a49;--shiki-dark:#F97583">export</span><span style="color:#d73a49;--shiki-dark:#F97583"> function</span><span style="color:#6f42c1;--shiki-dark:#B392F0"> distributedFilePath</span><span style="color:#24292e;--shiki-dark:#E1E4E8">(</span><span style="color:#e36209;--shiki-dark:#FFAB70">index</span><span style="color:#d73a49;--shiki-dark:#F97583">:</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> number</span><span style="color:#24292e;--shiki-dark:#E1E4E8">, </span><span style="color:#e36209;--shiki-dark:#FFAB70">maxEntriesPerFolder</span><span style="color:#d73a49;--shiki-dark:#F97583">:</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> number</span><span style="color:#d73a49;--shiki-dark:#F97583"> =</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> 1000</span><span style="color:#24292e;--shiki-dark:#E1E4E8">)</span><span style="color:#d73a49;--shiki-dark:#F97583">:</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> string</span><span style="color:#24292e;--shiki-dark:#E1E4E8">[] {</span></span>
<span class="line"><span style="color:#d73a49;--shiki-dark:#F97583">  if</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> (index </span><span style="color:#d73a49;--shiki-dark:#F97583">&lt;</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> 0</span><span style="color:#24292e;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#d73a49;--shiki-dark:#F97583">    throw</span><span style="color:#d73a49;--shiki-dark:#F97583"> new</span><span style="color:#6f42c1;--shiki-dark:#B392F0"> Error</span><span style="color:#24292e;--shiki-dark:#E1E4E8">(</span><span style="color:#032f62;--shiki-dark:#9ECBFF">&apos;Only numbers &gt;= 0 supported&apos;</span><span style="color:#24292e;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#d73a49;--shiki-dark:#F97583">  const</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> names</span><span style="color:#d73a49;--shiki-dark:#F97583">:</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> string</span><span style="color:#24292e;--shiki-dark:#E1E4E8">[] </span><span style="color:#d73a49;--shiki-dark:#F97583">=</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> []</span></span>
<span class="line"><span style="color:#d73a49;--shiki-dark:#F97583">  do</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">    names.</span><span style="color:#6f42c1;--shiki-dark:#B392F0">unshift</span><span style="color:#24292e;--shiki-dark:#E1E4E8">((index </span><span style="color:#d73a49;--shiki-dark:#F97583">%</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> maxEntriesPerFolder).</span><span style="color:#6f42c1;--shiki-dark:#B392F0">toString</span><span style="color:#24292e;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">    index </span><span style="color:#d73a49;--shiki-dark:#F97583">=</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> Math.</span><span style="color:#6f42c1;--shiki-dark:#B392F0">floor</span><span style="color:#24292e;--shiki-dark:#E1E4E8">(index </span><span style="color:#d73a49;--shiki-dark:#F97583">/</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> maxEntriesPerFolder)</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">  } </span><span style="color:#d73a49;--shiki-dark:#F97583">while</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> (index </span><span style="color:#d73a49;--shiki-dark:#F97583">&gt;</span><span style="color:#005cc5;--shiki-dark:#79B8FF"> 0</span><span style="color:#24292e;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">  names.</span><span style="color:#6f42c1;--shiki-dark:#B392F0">unshift</span><span style="color:#24292e;--shiki-dark:#E1E4E8">(names.</span><span style="color:#005cc5;--shiki-dark:#79B8FF">length</span><span style="color:#24292e;--shiki-dark:#E1E4E8">.</span><span style="color:#6f42c1;--shiki-dark:#B392F0">toString</span><span style="color:#24292e;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="color:#d73a49;--shiki-dark:#F97583">  return</span><span style="color:#24292e;--shiki-dark:#E1E4E8"> names</span></span>
<span class="line"><span style="color:#24292e;--shiki-dark:#E1E4E8">}</span></span></code></pre><p>Quelle: <a href="https://github.com/holtwick/zeed/blob/master/src/common/data/distributed.ts" rel="noopener" target="_blank" class="external">zeed Framework</a></p><h3 id="online-services-und-peer-to-peer" tabindex="-1"><a class="header-anchor" href="#online-services-und-peer-to-peer">Online Services und Peer-To-Peer</a></h3><p>Für Online Services kann eine passende Mischung aus dem Datenbank- oder dem Dateisystem-Ansatz gewählt werden, je nachdem, was besser zu dem ausgewählten Service passt. Bei Dropbox oder WebDAV wäre das z. B. der Dateisystem-Ansatz. Bei Apple CloudKit eher der Datenbank-Ansatz.</p><p>Aber es ist auch ein einfacher eigener Service denkbar, mit einer REST, WebSocket oder anderen sinnvollen Schnittstellen.</p><p>Es spricht auch nichts gegen einen Abgleich der Daten via Peer-to-Peer oder einem anderen lokalen Kommunikationsweg. Die Daten sind ja identisch und so kann ein Abgleich mit anderen Clients jeweils über den schnelleren Weg durchgeführt werden. Die Redundanz ist also von Vorteil und macht die Sache nicht weiter kompliziert. Theoretisch ist bei CRDT auch die Reihenfolge und die Mehrfachanwendung der Änderungen unproblematisch.</p><h2 id="verfeinerungen" tabindex="-1"><a class="header-anchor" href="#verfeinerungen">Verfeinerungen</a></h2><p>An einigen Stellen gibt es noch Potenzial zu Verbesserungen:</p><ul><li>Steuerung der Datengröße pro Log-Eintrag. Sammeln mehrerer Änderungen für größere Pakete oder Aufteilen einer Änderung in mehrere Pakete, falls der Umfang zu groß wird.</li><li>Komprimierung oder Zusammenfassung der Daten.</li><li>Bekanntmachung neuer Clients, z. B. durch spezielle Logeinträge. Evaluierung durch kryptografische Methoden.</li><li>Durch Hinzufügen einer logischen Uhr, wie der Lamport-Clock, können Einträge logisch sortiert werden und dadurch in die Chronologie eines Eintrages verbessert werden.</li><li>Das Schreiben der Daten in einer einzigen Datei pro Client aus Gründen der Ressourcen-Optimierung.</li><li>Durch geschickten Einsatz von kryptografischen Mitteln lässt sich evtl. sogar ein Rechte-Management implementieren (<a href="https://schmiste.github.io/srds06.pdf" rel="noopener" target="_blank" class="external">Cryptree</a>).</li></ul><h2 id="ausblick" tabindex="-1"><a class="header-anchor" href="#ausblick">Ausblick</a></h2><p>Ich verwende diese Technik seit einigen Jahren in meinen Apps, wie z. B. in dem mittlerweile beendeten Projekt <a href="https://onepile.apperdeck.com/en/help/internal-file-format" rel="noopener" target="_blank" class="external">Onepile</a>. In Kürze sollen neue Projekte mit diesem Ansatz veröffentlicht werden.</p><p>Die Einfachheit und Flexibilität erscheinen mir die größten Pluspunkte bei diesem Ansatz zu sein. Dadurch sollte es auch zukunftssicher sein und sich schnell neuen technischen Gegebenheiten anpassen können.</p><p>Das folgende Diagramm stellt exemplarisch ein Ökosystem für eine Web-App dar:</p><p class="img-wrapper"><img src="/assets/ued9nzrc325q2v.png" alt="Image" width="2976" height="2536" loading="lazy"></p><h2 id="related" tabindex="-1"><a class="header-anchor" href="#related">Related</a></h2><ul><li><strong>Diskussion zum Artikel auf <a href="https://news.ycombinator.com/item?id=40772955" rel="noopener" target="_blank" class="external">HackerNews</a></strong></li><li><a href="https://www.youtube.com/watch?v=NMq0vncHJvU&amp;t=2s" rel="noopener" target="_blank" class="external">Martin Kleppmanns Talk auf der Local-First Conference 2024 in Berlin</a></li><li><a href="https://elk.zone/mastodon.social/@martin@nondeterministic.computer/112639441984059657" rel="noopener" target="_blank" class="external">Mastodon Beitrag von Martin Kleppmann</a></li><li>Liste ähnlicher Ansätze:<ul><li><a href="https://github.com/MichaelMure/git-bug/blob/master/doc/model.md" rel="noopener" target="_blank" class="external">git-bug</a></li><li><a href="https://remotestorage.io" rel="noopener" target="_blank" class="external">remotestorage.io</a></li><li><a href="https://tonsky.me/blog/crdt-filesync/" rel="noopener" target="_blank" class="external">Local, first, forever</a></li><li><a href="https://jack-vanlightly.com/analyses/2024/4/29/understanding-delta-lakes-consistency-model" rel="noopener" target="_blank" class="external">Understanding Delta Lake’s consistency model</a></li><li><a href="http://archagon.net/blog/2018/03/24/data-laced-with-history/" rel="noopener" target="_blank" class="external">Data Laced with History: Causal Trees &amp; Operational CRDTs</a></li></ul></li></ul></div><p><em>Veröffentlicht am 24. Juni 2024</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[Über Javascript Logging]]></title>
            <link>https://holtwick.de/de/blog/logging</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/logging</guid>
            <pubDate>Mon, 01 Jan 2018 07:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>In komplexen Projekten kommt üblicher Weise der Zeitpunkt, an dem es unverzichtbar wird für die Qualitätssicherung weitere Maßnahmen zu ergreifen. In einer losen Serie möchte ich auf einige dieser Aspekte eingehen:</p><ul><li>Logging</li><li>Crash Reporting</li><li>Support und Feedback</li></ul><p>In diesem Artikel beleuchte ich das <strong>Logging</strong>.</p><h4 id="ebenen-%2F-level" tabindex="-1"><a class="header-anchor" href="#ebenen-%2F-level">Ebenen / Level</a></h4><p>Die einfachste Form eines Log Eintrages ist eine <strong>Nachricht</strong>. Doch schnell wird klar, dass das alleine nicht mehr reichen wird und etwas mehr <strong>Kontext</strong> erforderlich ist. Üblicherweise kommt dann schnell der <strong>Zeitstempel</strong> und der <strong>Level</strong> hinzu. Das kann dann z.B. so implementiert werden:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-text"><span class="line"><span>log.error()</span></span>
<span class="line"><span>log.warn()</span></span>
<span class="line"><span>log.info()</span></span>
<span class="line"><span>log.debug()</span></span></code></pre><p>In Umgebungen wie dem Browser kann leicht danach gefiltert werden, aber in einem Logfile sieht das vielleicht schon anders aus. Aus diesem Grunde greife ich gerne auf einen Trick zurück, den ich bei der Arbeit an einem großen Projekt von meinen Kollegen gelernt habe, nämlich diese Ebenen besonders zu kennzeichnen:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-text"><span class="line"><span>E|***</span></span>
<span class="line"><span>W|**</span></span>
<span class="line"><span>I|*</span></span>
<span class="line"><span>D|</span></span></code></pre><p>Ok, das sieht nett und übersichtlich aus, aber was ist daran besser?</p><p>Die Möglichkeit zu filtern ist es, denn ein Filtern nach <code>|*</code> wird alle Nachrichten inklusive und oberhalb von <code>I|*</code> anzeigen, also auch Warnungen und Fehler. <code>|**</code> und <code>|***</code> filtern entsprechend auf höheren Ebenen. Das funktioniert auf macOS z.B. sehr gut in Xcode oder der Console App.</p><h4 id="zeit" tabindex="-1"><a class="header-anchor" href="#zeit">Zeit</a></h4><p>Üblicherweise beginnt ein Log-Eintrag mit einem Zeitstempel. Das ist sinnvoll für ein Programm das in voller Produktivität beim Kunden oder auf dem Server läuft. Während der Entwicklung ist diese Information aber eher überflüssig und verkürzt auch den sichtbaren Bereich der Nachricht. Wenn überhaupt die Zeit interessiert, dann doch eher <strong>wie viel Zeit vergangen ist</strong>, bis dieses oder jenes geschieht. Daher kann es Sinn machen die Zeit seit dem Start des Programmes anzugeben:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-text"><span class="line"><span>I|*       0ms App launch</span></span>
<span class="line"><span>D|      123ms Open file abc.xyz</span></span>
<span class="line"><span>E|***   412ms Could not open file abc.xyz because: Does not exist</span></span></code></pre><h4 id="farben-und-symbole" tabindex="-1"><a class="header-anchor" href="#farben-und-symbole">Farben und Symbole</a></h4><p>Ein anderer Aspekt der Hilfreich ist bei der schnellen Wahrnehmung von Informationen, ist die Farbe des Eintrages. Der Browser erledigt das schon für uns, indem er die Fehler in rot darstellt. Aber in manchen Umgebungen, wie z.B. Xcode, ist die Verwendung von Farben nicht möglich oder nur umständlich zu erreichen, als Alternativer können dort Emojis dienen:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-text"><span class="line"><span>🔷 I|*       0ms App launch</span></span>
<span class="line"><span>◽️ D|      123ms Open file abc.xyz</span></span>
<span class="line"><span>❌ E|***   412ms Could not open file abc.xyz because: Does not exist</span></span></code></pre><p>Das Rot sticht sofort ins Auge und die Fehlermeldung ist so schnell lokalisiert.</p><h4 id="ursprungsort-der-nachricht" tabindex="-1"><a class="header-anchor" href="#ursprungsort-der-nachricht">Ursprungsort der Nachricht</a></h4><p>Ok, wir haben gesehen, es gab einen Fehler, aber wo ist dieser aufgetreten? Der tollste Log-Eintrag nützt leider nichts, wenn wir die Ursache nicht lokalisieren können. Daher kann z.B. der Dateiname und die Zeilennummer mit ausgegeben werden. Viele IDEs erlauben es durch eine bestimmte Formatierung direkt zu einem Ort zu springen. In Xcode z.B. durch <code>CMD + SHIFT + O</code> und dann Angabe der Datei und der Zeilennummer durch Doppelpunkt getrennt. Im Log könnte das dann so aussehen:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-text"><span class="line"><span>🔷 I|*       0ms &lt;main.c:33&gt; App launch</span></span>
<span class="line"><span>◽️ D|      123ms &lt;AppDelegate.m:54&gt; Open file abc.xyz</span></span>
<span class="line"><span>❌ E|***   412ms &lt;AppDelegate.m:62&gt; Could not open file abc.xyz because: Does not exist</span></span></code></pre><p><strong>Aber!</strong> Das sind keine Informationen, die in einer produktiven Umgebung verwendet werden sollten.</p><h4 id="nebenl%C3%A4ufigkeit" tabindex="-1"><a class="header-anchor" href="#nebenl%C3%A4ufigkeit">Nebenläufigkeit</a></h4><p>Und noch ein Merkmal ist wichtig in modernen Programmen, nämlich ob die Meldung aus einem asynchronen Code Block stammt oder dem Haupt-Thread. Auch hier kann eine Visualisierung mit Emojis hilfreich sein, wie hier durch eine Rakete:</p><pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code class="language-text"><span class="line"><span>◽️ 🔷 I|*       0ms &lt;main.c:33&gt; App launch</span></span>
<span class="line"><span>🚀 ❌ E|***   412ms &lt;MyCache.m:12&gt; Expected files were missing</span></span></code></pre><h4 id="abschluss" tabindex="-1"><a class="header-anchor" href="#abschluss">Abschluss</a></h4><p>Auch so ein alltägliches Thema wie das Logging kann also noch Raum für Optimierungen bieten und somit an manchen Stellen Zeit sparen, weil relevante Informationen schnell erfasst werden können und auch dir Ort des Problems einfacher zu lokalisieren ist.</p></div><p><em>Veröffentlicht am 1. Januar 2018</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[PDFify 4.0]]></title>
            <link>https://holtwick.de/de/blog/pdfify4</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/pdfify4</guid>
            <pubDate>Thu, 12 Sep 2024 06:00:00 GMT</pubDate>
            <description><![CDATA[Upgrade auf PDFify 4.0 für PDF/A-Unterstützung, Stapelkonvertierung, schnellere Webanzeige und standardisierte Seitengrößen. Testen Sie es jetzt!]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>Ab sofort kann <a href="https://pdfify.app?ref=holtwick" rel="noopener" target="_blank" class="external">PDFify</a> auf die neueste Version 4.0 aktualisiert werden. Viele <a href="https://pdfify.app?ref=holtwick" rel="noopener" target="_blank" class="external">PDFify</a>-Nutzer freuen sich auf dieses größere Versionsupdate mit Verbesserungen und neuen Funktionen.</p><h2 id="pdf%2Fa-unterst%C3%BCtzung" tabindex="-1"><a class="header-anchor" href="#pdf%2Fa-unterst%C3%BCtzung">PDF/A Unterstützung</a></h2><p>Viele <a href="https://pdfify.app?ref=holtwick" rel="noopener" target="_blank" class="external">PDFify</a>-User haben elektronische Dokumente, die sie langfristig archivieren müssen. <a href="https://en.wikipedia.org/wiki/PDF/A" rel="noopener" target="_blank" class="external">PDF/A</a> - Portable Document Format Archivable - ermöglicht eine standardisierte Langzeitarchivierung von Dokumenten. <a href="https://pdfify.app?ref=holtwick" rel="noopener" target="_blank" class="external">PDFify</a> bietet nun ab macOS 11 in den Einstellungen an, das Dokument standardmäßig als PDF/A abzuspeichern.</p><p class="img-wrapper"><img src="/assets/bv2h2gqc12wxn6z.png" alt="Screenshot" width="596" height="527" loading="lazy"></p><p>Das “normale” PDF, welches Einbettung von Links, Grafiken, Audio- und Videodateien sowie optionale Verschlüsselung unterstützt, bleibt selbstverständlich in <a href="https://pdfify.app?ref=holtwick" rel="noopener" target="_blank" class="external">PDFify</a> erhalten.</p><h2 id="finder-extensions-%2F-quick-actions" tabindex="-1"><a class="header-anchor" href="#finder-extensions-%2F-quick-actions">Finder Extensions / Quick Actions</a></h2><p>Um zügig mehrere bereits bestehende PDFs “im Stapel” in PDF/A umzuwandeln, lassen sich die Schnellaktionen in den Finder Extension um “in PDF/A umwandeln” ergänzen.</p><p>Ebenfalls gibt es eine neue Finder Extension zur Umwandlung von Dokumenten in eine PDF mit PDF/A Format. Aber auch die bestehenden Extensions erzeugen PDF/A, wenn die Option in den Einstellungen gesetzt wurde.</p><div class="markdown-alert markdown-alert-tip"><p class="markdown-alert-title"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lightbulb"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"></path><path d="M9 18h6"></path><path d="M10 22h4"></path></svg>Andere Dateiformate</p><p>Nicht nur PDF lassen sich über die Finder Extensions optimieren, sondern auch Dokumente verschiedener Formate direkt in PDF umwandeln:</p><ul><li>Bilddatein und Scans wie JPG, PNG, TIF oder GIF.</li><li>HTML Dateien</li><li>E-Mails</li></ul></div><h2 id="schnellere-anzeige-der-pdfs-im-web" tabindex="-1"><a class="header-anchor" href="#schnellere-anzeige-der-pdfs-im-web">Schnellere Anzeige der PDFs im Web</a></h2><p>Des Weiteren kann man nun auch, das linearisierte PDF, welches für die zügige Anzeige im Internet optimiert ist, auswählen. Die erste Seite wird dann beim Download bereits angezeigt, während die nachfolgenden Seiten noch geladen werden.</p><h2 id="standardisierte-maximale-seitengr%C3%B6%C3%9Fe" tabindex="-1"><a class="header-anchor" href="#standardisierte-maximale-seitengr%C3%B6%C3%9Fe">Standardisierte maximale Seitengröße</a></h2><p><a href="https://pdfify.app?ref=holtwick" rel="noopener" target="_blank" class="external">PDFify</a> ermöglicht nun eine Begrenzung der maximalen Seitengröße, um im Ergebnis keine unschönen riesigen Seiten zu erhalten. Standardmäßig ist die Seitengröße mit dem Update auf DIN A4 eingestellt. In den Einstellungen kann man den Wert auf die Originalgröße oder US letter anpassen.</p><p class="img-wrapper"><img src="/assets/cyh6zane1m52rot.png" alt="Image" width="596" height="527" loading="lazy"></p><h2 id="reihenfolge-beim-import" tabindex="-1"><a class="header-anchor" href="#reihenfolge-beim-import">Reihenfolge beim Import</a></h2><p>Manchmal liegen die einzelnen Seiten eines Dokuments als separate Bilddateien vor. In der neuen Version von <a href="https://pdfify.app?ref=holtwick" rel="noopener" target="_blank" class="external">PDFify</a> werden diese nun zuverlässig in der vom Apple Finder bekannten Sortierung importiert. So lassen sich schnell einzelne Scans von Seiten zu einem PDF Dokument zusammenführen.</p><p><a href="https://pdfify.app?ref=holtwick" rel="noopener" target="_blank" class="external"><strong>Jetzt kostenlos ausprobieren</strong></a></p></div><p><em>Veröffentlicht am 12. September 2024</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[Receipts Space 3.0]]></title>
            <link>https://holtwick.de/de/blog/receipts3</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/receipts3</guid>
            <pubDate>Wed, 21 Jan 2026 07:00:00 GMT</pubDate>
            <description><![CDATA[Receipts Space 3.0 bringt Revisionskonformität, Ende-zu-Ende-Verschlüsselung, ein neues Dashboard und viele Workflow-Verbesserungen.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>Receipts Space 3.0 ist da – ein großes Update mit vielen Neuerungen. Hier ein kurzer Überblick.</p><a href="https://video.holtwick.de/w/aKxTH1BCVffgCj3gpggiB1" target="_blank">https://video.holtwick.de/w/aKxTH1BCVffgCj3gpggiB1</a><h2 id="revisionskonformit%C3%A4t-(gobd)" tabindex="-1"><a class="header-anchor" href="#revisionskonformit%C3%A4t-(gobd)">Revisionskonformität (GoBD)</a></h2><p>Für alle, die ihre Belege ordnungsgemäß aufbewahren müssen, gibt es jetzt einen dedizierten Modus. Bestätigte Einträge werden schreibgeschützt, alle Änderungen werden protokolliert. Eine Verlaufs-Zeitleiste zeigt auf einen Blick, wer wann was geändert hat.</p><h2 id="ende-zu-ende-verschl%C3%BCsselung" tabindex="-1"><a class="header-anchor" href="#ende-zu-ende-verschl%C3%BCsselung">Ende-zu-Ende-Verschlüsselung</a></h2><p>Wer seine Bibliothek in der Cloud oder auf einem USB-Stick ablegt, kann sie jetzt mit einem Passwort verschlüsseln. Die Synchronisation zwischen mehreren Macs funktioniert weiterhin – solange alle Geräte den Schlüssel kennen.</p><h2 id="neues-dashboard" tabindex="-1"><a class="header-anchor" href="#neues-dashboard">Neues Dashboard</a></h2><p>Das Dashboard wurde komplett überarbeitet. Neben Einnahmen und Ausgaben zeigt es jetzt auch offene Beträge und Trends im Vergleich zum Vorjahr. Ein Klick auf Kontakte, Kategorien oder Tags filtert die Ansicht.</p><p class="img-wrapper"><img src="/assets/mqaruok82ul21u.png" alt="Image" width="1744" height="1132" loading="lazy"></p><h2 id="ordner-statt-package" tabindex="-1"><a class="header-anchor" href="#ordner-statt-package">Ordner statt Package</a></h2><p>Das Dateiformat wurde um eine Option erweitert: Die Bibliothek kann jetzt auch als normaler Ordner gespeichert werden. Das löst Synchronisationsprobleme mit iCloud und anderen Diensten.</p><p>Natürlich gibt es noch viele weitere Änderungen. Alle Details gibt es auf <a href="https://receipts-app.com/de/blog/release-3" rel="noopener" target="_blank" class="external">receipts-app.com</a>.</p></div><p><em>Veröffentlicht am 21. Januar 2026</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[UnplugTrump]]></title>
            <link>https://holtwick.de/de/blog/unplug-trump</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/unplug-trump</guid>
            <pubDate>Tue, 11 Mar 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[UnplugTrump: Wie kann man sich von US-Diensten unabhängig machen, insbesondere in Zeiten von Donald Trump. Zentrale Punkte sind Selfhosting, Dezentralität und OpenSource.]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>Was für verrückte Zeiten, in denen wir leben, und nun wird alles noch absurder mit dem amerikanischen Präsidenten Trump. Nicht nur in der großen Politik sollte das Ziel jetzt heißen, unabhängig von den USA zu werden und die EU zu stärken. Nein, auch im Privaten lässt sich da einiges machen, und ich möchte in einer kleinen Serie nach und nach Alternativen für US-Dienste und Produkte vorstellen.</p><p>Schon 2020 hatte ich in meinem Artikel <a href="it-strategy-for-europe">“Eine IT-Strategie für Europa”</a> versucht zu skizzieren, wie ich mir so einen Schritt in die digitale Souveränität in groben Zügen aussehen könnte. Es passiert in dieser Hinsicht nicht besonders viel.</p><p>Folgende Kriterien möchte ich anbieten, um Unabhängigkeit und Zuverlässigkeit zu erreichen:</p><ul><li><strong>Selfhosting</strong> → Betreibe deine Dienste selbst, dann kann sie niemand abschalten und die Daten gehören dir.</li><li><strong>Dezentral</strong> → Ein verteiltes System ist weniger anfällig für Ausfälle und Zensur.</li><li><strong>Open Source</strong> → Die Software kann so nicht verschwinden und ist überprüfbar.</li></ul><p><img src="/assets/l0xwi0281x0e78m.jpg" alt="Image" width="775" height="434" loading="lazy"><em>Image by <a href="https://chat.mistral.ai/chat/93944700-3a0c-4d90-a1a6-c42c5462064e" rel="noopener" target="_blank" class="external">mistral.ai</a></em></p><h2 id="selfhosting" tabindex="-1"><a class="header-anchor" href="#selfhosting">Selfhosting</a></h2><p>Es ist so schön bequem, die Dienste der großen Anbieter wie Google, Microsoft oder Apple zu nutzen, aber meistens zahlt man mit seinen persönlichen Daten. Auch Dienste wie Apple können schnell ihre datenschutzfreundliche Haltung ändern, Anzeichen gibt es bereits und die Geschichte vergleichbarer Dienste lässt das Vertrauen schrumpfen.</p><h3 id="heimnetz" tabindex="-1"><a class="header-anchor" href="#heimnetz">Heimnetz</a></h3><p>Leider ist das Betreiben eigener Server nichts für jedermann, aber es ist weniger kompliziert als vielleicht befürchtet. Man kann damit zu Hause beginnen mit einer <a href="https://en.wikipedia.org/wiki/Network-attached_storage" rel="noopener" target="_blank" class="external"><strong>NAS</strong></a>. Bei mir steht eine <a href="https://de.wikipedia.org/wiki/Synology" rel="noopener" target="_blank" class="external">Synology</a> (Taiwan). Diese kommt schon mit vielen Diensten in guter Qualität, die das Wesentliche abdecken. Aber eigentlich kann es jeder Rechner sein, Hauptsache <strong><a href="https://de.wikipedia.org/wiki/Docker_(Software)" rel="noopener" target="_blank" class="external">Docker</a></strong> lässt sich darauf betreiben.</p><p><strong>Docker</strong> ist der Schlüssel zum wirklich einfachen und sicheren Betrieb von “self-hosted services”. Es gibt eine schier <a href="https://github.com/awesome-selfhosted/awesome-selfhosted" rel="noopener" target="_blank" class="external">unendliche Menge von Lösungen</a> für jedes Problem.</p><p>Ich betreibe auf meiner Synology z.B. <a href="https://www.home-assistant.io/" rel="noopener" target="_blank" class="external"><strong>Home Assistant</strong></a> (Open Source), das Dashboard für alle Geräte und Sensoren im eigenen Haus, die irgendwie im Netzwerk sind. Für Fotos verwende ich <a href="https://immich.app/" rel="noopener" target="_blank" class="external"><strong>Immich</strong></a> (Open Source), das sich mit seinen Features auch nicht verstecken muss. Über eine <a href="https://de.wikipedia.org/wiki/Virtual_Private_Network" rel="noopener" target="_blank" class="external">VPN-Verbindung</a> - bei mir ist es WireGuard über eine fritz.box - lassen sich alle Dienste auch von unterwegs nutzen.</p><h3 id="internet" tabindex="-1"><a class="header-anchor" href="#internet">Internet</a></h3><p>Aber auch der Betrieb eines Docker Dienstes im Internet ist kein Hexenwerk. Bei <a href="https://hetzner.cloud/?ref=thK9VpOJK5Sg" rel="noopener" target="_blank" class="external">Hetzner</a> (Empfehlungs-Link) lässt sich z. B. für unter 5 EUR ein virtueller Server erstellen und direkt fertig mit Docker aufsetzen. <a href="https://github.com/holtwick/selfhosted?tab=readme-ov-file#selfhosted" rel="noopener" target="_blank" class="external">Nach ein paar Vorbereitungen</a>, um die eigene Domain mit der IP zu verbinden, kann es schon losgehen.</p><p>Ich betreibe viele Dienste dort, privat nutze ich hauptsächlich <a href="https://nextcloud.com/" rel="noopener" target="_blank" class="external"><strong>nextCloud</strong></a>. Das ist an einigen Stellen etwas altbacken, aber erfüllt seinen Zweck für Dateiaustausch und andere Dinge, die man z. B. aus der iCloud kennt.</p><h2 id="dezentral" tabindex="-1"><a class="header-anchor" href="#dezentral">Dezentral</a></h2><p>Den berühmtesten dezentralen Dienst des Internets nutzen alle: E-Mail. Eigentlich ist die ganze Magie des Internets sein dezentraler Aufbau. Fällt irgendwo was aus, sucht sich die Information einen anderen Weg, so wie bei Ameisenstraßen.</p><p>Große Anbieter sind meistens zentral aufgebaut. Klar, im Hintergrund wird auch versucht die Last aufzuteilen, aber trotzdem sind diese Dienste schon rein technisch anfällig für Störungen, Datenverlust und Sicherheitslücken.</p><p>Umso schöner, dass es nun wieder mehr “föderierte” Dienste gibt. In letzter Zeit ist besonders <a href="https://joinmastodon.org" rel="noopener" target="_blank" class="external"><strong>Mastodon</strong></a> als Twitter /X-Alternative bekannt geworden. Aber in diesem sogenannten <a href="https://de.wikipedia.org/wiki/Fediverse" rel="noopener" target="_blank" class="external">Fediverse</a> entstehen auch weitere Lösungen, die dann auch miteinander in Verbindung treten können. Ein gutes Beispiel dafür ist der YouTube-Ersatz <a href="https://joinpeertube.org/de/" rel="noopener" target="_blank" class="external"><strong>PeerTube</strong></a>.</p><p>Aber es gibt noch andere Formen der dezentralen Datenverarbeitung. Eine Bewegung, die mir besonders gefällt, nennt sich <a href="https://www.localfirstconf.com/" rel="noopener" target="_blank" class="external"><strong>Local First</strong></a>, dabei wird wieder Wert darauf gelegt, dass die Daten auch lokal verfügbar sind. Dieselben Daten an verschiedenen physischen Orten zu lagern, ist auch eine Form von dezentraler Struktur. Das Paradebeispiel dafür sind Versionierungssystem wie <a href="https://git-scm.com/" rel="noopener" target="_blank" class="external"><strong>git</strong></a>.</p><h2 id="opensource" tabindex="-1"><a class="header-anchor" href="#opensource">OpenSource</a></h2><p>Freie und offene Software ist der Grundstein für tieferes Vertrauen. Erstens ist offene Software nicht so schnell “aus der Welt”, sie existiert tausendmal kopiert. Zweitens kann man sich mit etwas Sachverstand ansehen, was die Software eigentlich genau macht. Und drittens lässt es sich anpassen, wodurch wiederum zum Projekt beigetragen wird. Quasi eine digitale Form von ehrenamtlicher Arbeit, ohne dafür in einen Verein zu müssen ;)</p><h2 id="weiterf%C3%BChrende-links" tabindex="-1"><a class="header-anchor" href="#weiterf%C3%BChrende-links">Weiterführende Links</a></h2><ul><li>Kuketz Blog zum selben Thema mit interessanten Links und Hinweisen:<br><a href="https://www.kuketz-blog.de/unplugtrump-mach-dich-digital-unabhaengig-von-trump-und-big-tech/" rel="noopener" target="_blank" class="external">https://www.kuketz-blog.de/unplugtrump-mach-dich-digital-unabhaengig-von-trump-und-big-tech/</a></li><li>Verzeichnis europäischer Alternativen:<br><a href="https://european-alternatives.eu" rel="noopener" target="_blank" class="external">https://european-alternatives.eu</a></li><li>Allgemeine Suche nach Alternativen:<br><a href="https://alternativeto.net" rel="noopener" target="_blank" class="external">https://alternativeto.net</a></li><li>Viele vorinstallierte Selfhosted-Projekte, gut zum ausprobieren:<br><a href="https://adminforge.de" rel="noopener" target="_blank" class="external">https://adminforge.de</a></li></ul></div><p><em>Veröffentlicht am 11. März 2025</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[Website SSG]]></title>
            <link>https://holtwick.de/de/blog/website-ssg</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/website-ssg</guid>
            <pubDate>Wed, 06 Dec 2023 07:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p>Nach einigen Jahren war es an der Zeit, die Website zu aktualisieren. Für die letzte Version hatte ich sogar einen eigenen statischen Website-Generator namens <a href="birth-of-hostic">Hostic</a> entwickelt und alles lief wunderbar. Aber obwohl ich es liebe, das Rad neu zu erfinden, habe ich es bei diesem neuen Anlauf nur halb gemacht.</p><p>Grundsätzlich besteht diese Website aus <a href="https://vuejs.org/" rel="noopener" target="_blank" class="external">Vue</a> und <a href="https://en.wikipedia.org/wiki/Markdown" rel="noopener" target="_blank" class="external">Markdown</a>. Davon wird eine statische Version erstellt, so dass für jede URL die entsprechende Seite mit Inhalt vorhanden ist. Dadurch kann die Website von Suchmaschinen gut indiziert werden und die Ladezeit ist sehr kurz. Das Verfahren nennt sich <a href="https://vitejs.dev/guide/ssr" rel="noopener" target="_blank" class="external">SSG</a> (Server-Side Rendering).</p><p>Der Clou ist aber, dass dieser Ansatz es erlaubt, dynamische Elemente in die Website und sogar einzelne Beiträge einzubauen. Ein erstes Beispiel ist die interaktive Anmeldung zu meinem E-Mail-Newsletter, denn ich füge an dieser Stelle einfach <code>&lt;AppNewsletter/&gt;</code> ein:</p><div><a href="https://newsletter.holtwick.de/subscription/form" target="_blank">https://newsletter.holtwick.de/subscription/form</a></div><p>Eine weitere Erleichterung ist die Verwendung von <a href="https://obsidian.md" rel="noopener" target="_blank" class="external">Obsidian</a> als Markdown-Editor für die Inhalte. Ich benutze dieses praktische Tool sowieso jeden Tag und kann so die Inhalte bequem pflegen.</p><div class="markdown-alert markdown-alert-info"><p class="markdown-alert-title"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path></svg>Obsidian Callouts</p><p>Die <a href="https://help.obsidian.md/Editing+and+formatting/Callouts" rel="noopener" target="_blank" class="external">Callouts</a> von Obsidian werden ebenfalls unterstützt. Auch bekannt als <a href="https://github.com/orgs/community/discussions/16925" rel="noopener" target="_blank" class="external">Github Alerts</a>. Ermöglicht wird dies durch das praktische <a href="https://github.com/antfu/markdown-it-github-alerts" rel="noopener" target="_blank" class="external">Markdown Plugin</a>.</p></div><p>Meinen eigenen Ansatz habe ich von dem großartigen Projekt <a href="https://github.com/antfu/vitesse" rel="noopener" target="_blank" class="external">Vitesse</a> abgeleitet. Die verwendete Technik ist fast identisch, allerdings musste ich einige Anpassungen für meine Zwecke vornehmen.</p></div><p><em>Veröffentlicht am 6. Dezember 2023</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
        <item>
            <title><![CDATA[XML2Invoice]]></title>
            <link>https://holtwick.de/de/blog/xml2invoice</link>
            <guid isPermaLink="false">https://holtwick.de/de/blog/xml2invoice</guid>
            <pubDate>Wed, 30 Oct 2024 07:00:00 GMT</pubDate>
            <description><![CDATA[E-Rechnungen:Einfach umwandeln und bezahlen. Existierendes PDF wird maschinenlesbar, XML-Rechnung wird menschenlesbar]]></description>
            <content:encoded><![CDATA[<div class="post-body"><div class="markdown-body"><p><strong>Sorge vor E-Rechungen? Mit XML2invoice kannst du entspannt ins Neues Jahr starten…</strong></p><p class="img-wrapper"><img src="/assets/ld44m5h1twywwl.jpeg" alt="Image" width="2736" height="1824" loading="lazy"></p><p><a href="https://e-invoice.space" rel="noopener" target="_blank" class="external"><strong>XML2Invoice</strong></a> ist eine webbasierte App, die deine “normale” PDF-Rechnung ins XML-Format umwandelt: Entweder in eine reine <strong>XML-Datei</strong> oder in eine <strong>ZUGFeRD</strong>-PDF-Datei, die genau wie deine Rechnung aussieht und in die die vorgeschriebenen XML-Daten eingebettet sind.</p><p>Und auch andersrum funktioniert’s: <a href="https://e-invoice.space" rel="noopener" target="_blank" class="external"><strong>XML2Invoice</strong></a> erzeugt aus von Menschen nicht mehr lesbaren XML-Dateien eine elektronisch lesbare Rechnung im <strong>ZUGFeRD-Format</strong>, die äußerlich wie eine übliche PDF-Rechnung aussieht, aber auch die XML-Daten enthält. Lesbar für Mensch und Maschine. Wenn gewünscht importierst du das PDF mit den eingebetteten XML-Daten per Mausklick <strong>in Receipts</strong>.</p><p class="action"><a href="https://e-invoice.space" class="button oui-button external" target="_blank" rel="noopener noreferrer">Jetzt kostenlos starten auf xml2invoice.com !</a></p><p><strong>Update 2025-01-29:</strong> XML2Invoice heißt nun <a href="https://e-invoice.space" rel="noopener" target="_blank" class="external">E-Invoice Space</a></p></div><p><em>Veröffentlicht am 30. Oktober 2024</em></p></div>]]></content:encoded>
            <author>support@holtwick.de (Dirk Holtwick)</author>
        </item>
    </channel>
</rss>