<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>HowTo on private Homepage von Rainer Rose</title>
    <link>https://www.rainerrose.de/tags/howto/</link>
    <description>Recent content in HowTo on private Homepage von Rainer Rose</description>
    <generator>Hugo</generator>
    <language>de</language>
    <copyright>Copyright © 1998-2026 Rainer Rose. All Rights Reserved.
</copyright>
    <lastBuildDate>Sun, 22 Feb 2026 18:21:03 +0100</lastBuildDate><atom:link href="https://www.rainerrose.de/tags/howto/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Paperless-ngx: Major-Upgrade oder Wechsel der Datenbank in Docker</title>
      <link>https://www.rainerrose.de/docs/howto/paperless-postgres-mariadb-upgrades/</link>
      <pubDate>Sun, 22 Feb 2026 18:21:03 +0100</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/paperless-postgres-mariadb-upgrades/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Durch Freunde wurde ich auf das Document Management System <code>paperless</code> aufmerksam, dass im derzeit aktuellen Fork <a href="https://tools.rainerrose.de/redirect.php?id=56" target="_blank" rel="noopener noreferrer">paperless-ngx<i class="fas fa-external-link-square-alt ms-1"></i></a> weiterlebt.
In Verbindung mit einem Einzugsscanner ist es ein wahr gewordener Traum, der mich sehr in meinem Alltag entlastet.</p>
<p>Das Aktualisieren von <em>paperless-ngx</em> (im Folgenden nur noch <em>paperless</em> genannt) unter <em>Docker</em> ist zwar in der <a href="https://tools.rainerrose.de/redirect.php?id=57" target="_blank" rel="noopener noreferrer">Dokumentation<i class="fas fa-external-link-square-alt ms-1"></i></a> recht gut beschrieben, aber das Updaten der Datenbank, finde ich zu kompliziert, weil es nur auf die offizielle <em>PostgreSQL</em>-Dokumentation verweist.</p>
<p>Hier müsste ich mit den CLI-Tools wie <code>pg_dumpall</code> und <code>pg_upgrade</code>  hantieren, was arbeiten im <em>Docker</em>-Container notwendig macht und vermutlich auch doppelte <em>Docker</em>-Container-Instanzen nötig macht. Eine automagische Migration unterstützen die <em>postgres</em>-images nach meinem Wissen derzeit nicht. Also habe ich eine andere Möglichkeit gesucht und auch gefunden!</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Durch Freunde wurde ich auf das Document Management System <code>paperless</code> aufmerksam, dass im derzeit aktuellen Fork <a href="https://tools.rainerrose.de/redirect.php?id=56" target="_blank" rel="noopener noreferrer">paperless-ngx<i class="fas fa-external-link-square-alt ms-1"></i></a> weiterlebt.
In Verbindung mit einem Einzugsscanner ist es ein wahr gewordener Traum, der mich sehr in meinem Alltag entlastet.</p>
<p>Das Aktualisieren von <em>paperless-ngx</em> (im Folgenden nur noch <em>paperless</em> genannt) unter <em>Docker</em> ist zwar in der <a href="https://tools.rainerrose.de/redirect.php?id=57" target="_blank" rel="noopener noreferrer">Dokumentation<i class="fas fa-external-link-square-alt ms-1"></i></a> recht gut beschrieben, aber das Updaten der Datenbank, finde ich zu kompliziert, weil es nur auf die offizielle <em>PostgreSQL</em>-Dokumentation verweist.</p>
<p>Hier müsste ich mit den CLI-Tools wie <code>pg_dumpall</code> und <code>pg_upgrade</code>  hantieren, was arbeiten im <em>Docker</em>-Container notwendig macht und vermutlich auch doppelte <em>Docker</em>-Container-Instanzen nötig macht. Eine automagische Migration unterstützen die <em>postgres</em>-images nach meinem Wissen derzeit nicht. Also habe ich eine andere Möglichkeit gesucht und auch gefunden!</p>
<p><code>paperless</code> liefert die beiden Tools <code>document_exporter</code> und <code>document_importer</code> mit, die ein <strong>vollständiges</strong> Backup und Wiederherstellung (Restore) der kompletten Instanz bereitstellt.</p>
<p>Mit diesen beiden Tools lässt sich ein Major-Upgrade bewerkstelligen. Wie ich heute rausfand ist sogar der Wechsel der Datenbank-Engine ohne allzu große Umstände möglich.</p>

<h2 id="überblick" data-numberify>Überblick<a class="anchor ms-1" href="#überblick"></a></h2>
<p>Der grobe Pfad für ein Major-Upgrade ist daher:</p>
<ul>
<li>Zuerst ein vollständiges Backup anfertigen.</li>
<li>Alle Daten inklusive Volumes wegwerfen.</li>
<li>Die <em>PostgreSQL</em>-Version im <code>docker-compose.yml</code> anpassen.</li>
<li>Schlussendlich alle Daten wieder importieren.</li>
</ul>
<p>Das hat bisher anstandslos geklappt.</p>
<p>Der Wechsel auf eine andere Datenbank-Engine wird weiter unten beschrieben.</p>

<h2 id="voraussetzungen" data-numberify>Voraussetzungen<a class="anchor ms-1" href="#voraussetzungen"></a></h2>
<p>Es setzt voraus, dass die Daten außerhalb der Docker-Umgebung landen. Das <code>export</code> Volume ist also als Verzeichnis unterhalb der <code>docker-compose.yml</code> so wie in den <a href="https://tools.rainerrose.de/redirect.php?id=58" target="_blank" rel="noopener noreferrer">Beispielen<i class="fas fa-external-link-square-alt ms-1"></i></a> definiert.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="w">      </span>- <span class="l">./export:/usr/src/paperless/export</span><span class="w">
</span></span></span></code></pre></div><p>Wie ich ein Datenbank-Upgrade im Detail durchführe, beschreibe ich im Folgenden.</p>
<p>Zuerst sollte sichergestellt sein, dass keine Importe ins <em>Paperless</em> stattfinden. Da ich alleine auf dem System arbeite und keinen E-Mail-Import verwende, kann ich das gut sicherstellen.</p>

<h2 id="backup" data-numberify>Backup<a class="anchor ms-1" href="#backup"></a></h2>
<p>Als ersten Schritt fertige ich ein Backup an. Dies geht wunderbar mit dem bereits erwähnten <code>document_exporter</code> folgendermaßen:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">docker compose <span class="nb">exec</span> -T webserver document_exporter ../export -z -zn full-backup
</span></span></code></pre></div><p>Hier sollte dann eine Datei namens <code>full-backup.zip</code> außerhalb des Docker-Umgebung entstanden sein. Dies enthält alle relevanten Informationen für ein Desaster-Recovery.</p>

<blockquote class="alert alert-success" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-lightbulb me-2"></i>Tipp
    </p>
    <p>Der zusätzliche Parameter <code>--no-progress-bar</code> sorgt für weniger Ausgaben in Backup-Scripten.</p>
</blockquote>

<h2 id="zurück-auf-null" data-numberify>Zurück auf Null<a class="anchor ms-1" href="#zurück-auf-null"></a></h2>
<p>Danach fahre ich alle Container herunter und lösche gleichzeitig alle <em>Docker</em> Volumes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">docker compose down --volumes
</span></span></code></pre></div><p>Die Ausgabe sollte ungefähr so aussehen:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">[+] down 8/8
</span></span><span class="line"><span class="ln">2</span><span class="cl"> ✔ Container paperless-webserver-1 Removed   5.6s
</span></span><span class="line"><span class="ln">3</span><span class="cl"> ✔ Container paperless-broker-1    Removed   0.4s
</span></span><span class="line"><span class="ln">4</span><span class="cl"> ✔ Container paperless-db-1        Removed   0.3s
</span></span><span class="line"><span class="ln">5</span><span class="cl"> ✔ Volume paperless_media          Removed   0.2s
</span></span><span class="line"><span class="ln">6</span><span class="cl"> ✔ Volume paperless_pgdata         Removed   0.0s
</span></span><span class="line"><span class="ln">7</span><span class="cl"> ✔ Volume paperless_data           Removed   0.0s
</span></span><span class="line"><span class="ln">8</span><span class="cl"> ✔ Volume paperless_redisdata      Removed   0.0s
</span></span><span class="line"><span class="ln">9</span><span class="cl"> ✔ Network paperless_default       Removed                     
</span></span></code></pre></div>
<h2 id="neue-version-wählen" data-numberify>Neue Version wählen<a class="anchor ms-1" href="#neue-version-wählen"></a></h2>
<p>Nun kann ich in der <code>docker-compose.yml</code>-Datei das Datenbank-Image auf die gewünschte Version der Datenbank hochziehen.</p>
<p>Für PostgreSQL gibt es <a href="https://tools.rainerrose.de/redirect.php?id=59" target="_blank" rel="noopener noreferrer">hier im Repo eine Vorlage<i class="fas fa-external-link-square-alt ms-1"></i></a> an der ich mich orientiert habe. Im Mai 2025 war es die Version 17.
Also änderte ich den Eintrag <code>docker-compose.yml</code> von der Version 16.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/library/postgres:16</span><span class="w">
</span></span></span></code></pre></div><p>auf die Version 17</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/library/postgres:17</span><span class="w">
</span></span></span></code></pre></div><p>Es empfiehlt sich eh ab und zu da mal einzuschauen, ob nicht z.B. eine neue redis-Version unterstützt wird.
Ich selbst stütze mich nur auf die Haupt (Major-Versionen). Bei Minor- oder Patch-Updates pulle ich das jeweilige neue Image und starte alle Docker-Container neu, das reicht mir und hat sich bei mir bewährt.</p>

<blockquote class="alert alert-warning" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-exclamation-circle me-2"></i>Hinweis
    </p>
    <p>Aktuell ist im Repo die <em>PostgreSQL</em>-Version 18 angegeben. Leider wirft der Docker-Container <code>db-1</code> einiges an Fehlermeldungen und startet sich immer wieder neu. Eine Lösung dazu beschreibe ich weiter unten; hier soll es jetzt erst einmal mit dem Upgrade von 16 auf 17 weiter gehen; schließlich hat das Update von 15 auf 16 vor einiger Zeit genauso geklappt.</p>
</blockquote>

<h2 id="erster-start" data-numberify>Erster Start<a class="anchor ms-1" href="#erster-start"></a></h2>
<p>Nach dem Ändern der Major-Version (bzw. des Tags) vom <em>postgres</em>-Image, können die Docker-Container von <em>paperless</em> schon wieder gestartet werden. Sofern das Image lokal noch nicht vorliegt, wird es automatisch heruntergeladen.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">docker compose up -d
</span></span></code></pre></div><p>Folgendes sollte als Ausgabe zu sehen sein:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">[+] up 22/22
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> ✔ Image docker.io/library/postgres:18 Pulled     15.3s
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> ✔ Network paperless_default           Created    0.0s
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> ✔ Volume paperless_data               Created    0.0s
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> ✔ Volume paperless_media              Created    0.0s
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> ✔ Volume paperless_redisdata          Created    0.0s
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> ✔ Volume paperless_pgdata             Created    0.0s
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> ✔ Container paperless-db-1            Created    0.2s
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> ✔ Container paperless-broker-1        Created    0.2s
</span></span><span class="line"><span class="ln">10</span><span class="cl"> ✔ Container paperless-webserver-1     Created   
</span></span></code></pre></div><p>Nun muss solange gewartet, bis die Login-Maske wieder mit dem Browser besuchbar ist.</p>
<p>Da dies einer Neuinstallation entspricht und daher Datenbanken etc. eingerichtet werden müssen, dauert dies je nach Rechenpower etwas. Daher ist etwas Geduld erforderlich.</p>
<p>Zeigt&rsquo;s im Browser <code>502 Bad Gateway</code> ist noch nicht alles fertig.  Neugierige werfen ein Blick in die Docker-Logs</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">docker compose logs -f
</span></span></code></pre></div><p>Wenn ungefähr folgende Log-Einträge zu sehen sind</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span> <span class="p">[</span><span class="n">tasks</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">bulk_edit</span><span class="o">.</span><span class="n">delete</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">signals</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">send_webhook</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">bulk_update_documents</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">check_scheduled_workflows</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">consume_file</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">empty_trash</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">index_optimize</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">sanity_check</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">train_classifier</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">documents</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">update_document_content_maybe_archive_file</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">paperless_mail</span><span class="o">.</span><span class="n">mail</span><span class="o">.</span><span class="n">apply_mail_action</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">paperless_mail</span><span class="o">.</span><span class="n">mail</span><span class="o">.</span><span class="n">error_callback</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span>   <span class="o">.</span> <span class="n">paperless_mail</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">process_mail_accounts</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span> 
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span> <span class="p">[</span><span class="mi">2025</span><span class="o">-</span><span class="mi">05</span><span class="o">-</span><span class="mi">29</span> <span class="mi">12</span><span class="p">:</span><span class="mi">50</span><span class="p">:</span><span class="mi">03</span><span class="p">,</span><span class="mi">792</span><span class="p">]</span> <span class="p">[</span><span class="n">INFO</span><span class="p">]</span> <span class="p">[</span><span class="n">celery</span><span class="o">.</span><span class="n">worker</span><span class="o">.</span><span class="n">consumer</span><span class="o">.</span><span class="n">connection</span><span class="p">]</span> <span class="n">Connected</span> <span class="n">to</span> <span class="n">redis</span><span class="p">:</span><span class="o">//</span><span class="n">broker</span><span class="p">:</span><span class="mi">6379</span><span class="o">//</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">webserver_1</span>  <span class="o">|</span> <span class="p">[</span><span class="mi">2025</span><span class="o">-</span><span class="mi">05</span><span class="o">-</span><span class="mi">29</span> <span class="mi">12</span><span class="p">:</span><span class="mi">50</span><span class="p">:</span><span class="mi">03</span><span class="p">,</span><span class="mi">982</span><span class="p">]</span> <span class="p">[</span><span class="n">INFO</span><span class="p">]</span> <span class="p">[</span><span class="n">celery</span><span class="o">.</span><span class="n">apps</span><span class="o">.</span><span class="n">worker</span><span class="p">]</span> <span class="n">celery</span><span class="err">@</span><span class="mf">7e94</span><span class="n">fdb4f941</span> <span class="n">ready</span><span class="o">.</span>
</span></span></code></pre></div><p>sollte die Login-Maske zur Einrichtung des ersten Benutzerkontos verfügbar sein und es kann mit dem nächsten Schritt weiter gemacht werden.</p>
<p><picture><img class="img-fluid " alt="Paperless-ngx Anmelde-Maske zur Einrichtung des ersten Benutzerkontos" src="https://www.rainerrose.de/images/docs/paperless-login-initial.png?v=960c44cf92448b0054d81f613282f43c" loading="lazy" width="405" height="716" />
</picture>

</p>

<h2 id="daten-wieder-importieren" data-numberify>Daten wieder importieren<a class="anchor ms-1" href="#daten-wieder-importieren"></a></h2>
<p>Über den <code>document_importer</code> lässt sich nun wieder alles importieren: Alle Dokumente, alle Tags, alle Benutzeraccounts, gespeicherten Ansichten usw.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">docker compose <span class="nb">exec</span> -T webserver document_importer ../export/full-backup.zip
</span></span></code></pre></div><p>Ausgabe:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">Checking the manifest
</span></span><span class="line"><span class="ln">2</span><span class="cl">Installed 4248 object(s) from 1 fixture(s)
</span></span><span class="line"><span class="ln">3</span><span class="cl">Copy files into paperless...
</span></span><span class="line"><span class="ln">4</span><span class="cl">100%|██████████| 1014/1014 [00:04&lt;00:00, 210.59it/s]
</span></span><span class="line"><span class="ln">5</span><span class="cl">Updating search index...
</span></span><span class="line"><span class="ln">6</span><span class="cl">100%|██████████| 1014/1014 [00:10&lt;00:00, 94.50it/s] 
</span></span></code></pre></div><p>Falls im Browser Weiterleitung auf den Unterpfad <code>/accounts/signup/?next=%2F</code> stattfand, diesen Pfad löschen!
Im Browser also wieder die gewohnte URL ansurfen und es sollte wieder die gewohnte Login-Maske erscheinen.</p>

<h2 id="fertig-anmelden" data-numberify>Fertig, anmelden<a class="anchor ms-1" href="#fertig-anmelden"></a></h2>
<p>Schlussendlich stoße ich noch einmal vorsichtshalber mein Backup-Script von Paperless noch an und bin fertig.</p>

<h2 id="postgresql-18" data-numberify>PostgreSQL 18<a class="anchor ms-1" href="#postgresql-18"></a></h2>
<p>Während ich diesen Artikel schrieb, stellte ich fest, dass in den <a href="https://tools.rainerrose.de/redirect.php?id=60" target="_blank" rel="noopener noreferrer">Vorlagen im Repo<i class="fas fa-external-link-square-alt ms-1"></i></a> zu <code>docker-compose.yml</code> schon <em>PostgreSQL</em> 18 unterstützt wird.
Ich bin daher genau nach meiner Anleitung vorgegangen und musste feststellen, dass der Datenbank-Container nicht nach oben kam.</p>
<p>In den Docker-Logs entdeckte ich dann folgende Meldungen:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span> <span class="n">Error</span><span class="p">:</span> <span class="ow">in</span> <span class="mi">18</span><span class="o">+</span><span class="p">,</span> <span class="n">these</span> <span class="n">Docker</span> <span class="n">images</span> <span class="n">are</span> <span class="n">configured</span> <span class="n">to</span> <span class="n">store</span> <span class="n">database</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">a</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">format</span> <span class="n">which</span> <span class="n">is</span> <span class="n">compatible</span> <span class="n">with</span> <span class="s2">&#34;pg_ctlcluster&#34;</span> <span class="p">(</span><span class="n">specifically</span><span class="p">,</span> <span class="n">using</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">major</span><span class="o">-</span><span class="n">version</span><span class="o">-</span><span class="n">specific</span> <span class="n">directory</span> <span class="n">names</span><span class="p">)</span><span class="o">.</span>  <span class="n">This</span> <span class="n">better</span> <span class="n">reflects</span> <span class="n">how</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">PostgreSQL</span> <span class="n">itself</span> <span class="n">works</span><span class="p">,</span> <span class="ow">and</span> <span class="n">how</span> <span class="n">upgrades</span> <span class="n">are</span> <span class="n">to</span> <span class="n">be</span> <span class="n">performed</span><span class="o">.</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span> 
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">See</span> <span class="n">also</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">docker</span><span class="o">-</span><span class="n">library</span><span class="o">/</span><span class="n">postgres</span><span class="o">/</span><span class="n">pull</span><span class="o">/</span><span class="mi">1259</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span> 
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">Counter</span> <span class="n">to</span> <span class="n">that</span><span class="p">,</span> <span class="n">there</span> <span class="n">appears</span> <span class="n">to</span> <span class="n">be</span> <span class="n">PostgreSQL</span> <span class="n">data</span> <span class="ow">in</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>          <span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">postgresql</span><span class="o">/</span><span class="n">data</span> <span class="p">(</span><span class="n">unused</span> <span class="n">mount</span><span class="o">/</span><span class="n">volume</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span> 
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">This</span> <span class="n">is</span> <span class="n">usually</span> <span class="n">the</span> <span class="n">result</span> <span class="n">of</span> <span class="n">upgrading</span> <span class="n">the</span> <span class="n">Docker</span> <span class="n">image</span> <span class="n">without</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">upgrading</span> <span class="n">the</span> <span class="n">underlying</span> <span class="n">database</span> <span class="n">using</span> <span class="s2">&#34;pg_upgrade&#34;</span> <span class="p">(</span><span class="n">which</span> <span class="n">requires</span> <span class="n">both</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">versions</span><span class="p">)</span><span class="o">.</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span> 
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">The</span> <span class="n">suggested</span> <span class="n">container</span> <span class="n">configuration</span> <span class="k">for</span> <span class="mi">18</span><span class="o">+</span> <span class="n">is</span> <span class="n">to</span> <span class="n">place</span> <span class="n">a</span> <span class="n">single</span> <span class="n">mount</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">at</span> <span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">postgresql</span> <span class="n">which</span> <span class="n">will</span> <span class="n">then</span> <span class="n">place</span> <span class="n">PostgreSQL</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">a</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">subdirectory</span><span class="p">,</span> <span class="n">allowing</span> <span class="n">usage</span> <span class="n">of</span> <span class="s2">&#34;pg_upgrade --link&#34;</span> <span class="n">without</span> <span class="n">mount</span> <span class="n">point</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">boundary</span> <span class="n">issues</span><span class="o">.</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span> 
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">See</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">docker</span><span class="o">-</span><span class="n">library</span><span class="o">/</span><span class="n">postgres</span><span class="o">/</span><span class="n">issues</span><span class="o">/</span><span class="mi">37</span> <span class="k">for</span> <span class="n">a</span> <span class="p">(</span><span class="n">long</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span>         <span class="o">|</span>        <span class="n">discussion</span> <span class="n">around</span> <span class="n">this</span> <span class="n">process</span><span class="p">,</span> <span class="ow">and</span> <span class="n">suggestions</span> <span class="k">for</span> <span class="n">how</span> <span class="n">to</span> <span class="k">do</span> <span class="n">so</span><span class="o">.</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">db</span><span class="o">-</span><span class="mi">1</span> <span class="n">exited</span> <span class="n">with</span> <span class="n">code</span> <span class="mi">1</span> <span class="p">(</span><span class="n">restarting</span><span class="p">)</span>
</span></span></code></pre></div><p>Der Downgrade auf 17 war nach der obigen Anleitung recht schnell erledigt und <em>paperless</em> lief wieder zufriedenstellend mit der Version 17 von <em>PostgreSQL</em>.</p>

<h2 id="umstellung-auf-mariadb-12" data-numberify>Umstellung auf MariaDB 12<a class="anchor ms-1" href="#umstellung-auf-mariadb-12"></a></h2>
<p>Ich nahm dies als Anlass, die Datenbank-Engine auf MariaDB umzustellen.
Im Repo des Projektes gibt es dazu ebenfalls <a href="https://tools.rainerrose.de/redirect.php?id=61" target="_blank" rel="noopener noreferrer">eine Vorlage<i class="fas fa-external-link-square-alt ms-1"></i></a>.
Es gab drei Stellen, die ich dafür anpassen musste.</p>
<p>Es mussten im Abschnitt von <code>db</code> ein paar Umgebungsvariablen und natürlich das <code>image</code> umgestellt werden; hier ist 12 gerade aktuell.</p>
<p>Weiter unten im Abschnitt <code>webserver</code> mussten ebenfalls noch ein paar Umgebungsvariablen hinzugefügt werden.</p>
<p>Als letztes musste ich den Volume-Namen im Abschnitt <code>volumes</code> umbenennen.
Danach konnte ich die Container wieder starten und die Daten importieren, wie weiter oben beschrieben.</p>
<p>Nachfolgend ein <code>diff</code> aus meinem <em>ansible</em>-Playbook. (Ja, ich habe <code>restart</code> auch noch gleich umstellt bzw. an die neue Vorlage aus dem Projekt angeglichen. )</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gd">--- a/roles/paperless/files/docker-compose.yml
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gd"></span><span class="gi">+++ b/roles/paperless/files/docker-compose.yml
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gi"></span><span class="gu">@@ -28,26 +28,24 @@
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span> 
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> services:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   broker:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gd">-    image: docker.io/library/redis:8
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gd">-    restart: unless-stopped
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gd"></span><span class="gi">+    image: docker.io/library/redis:7
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gi">+    restart: always
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gi"></span>     volumes:
</span></span><span class="line"><span class="ln">12</span><span class="cl">       - redisdata:/data
</span></span><span class="line"><span class="ln">13</span><span class="cl"> 
</span></span><span class="line"><span class="ln">14</span><span class="cl">   db:
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gd">-    image: docker.io/library/mariadb:12
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gd">-    restart: unless-stopped
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gd"></span><span class="gi">+    image: docker.io/library/postgres:17
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gi">+    restart: always
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gi"></span>     volumes:
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gd">-      - dbdata:/var/lib/mysql
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gd"></span><span class="gi">+      - pgdata:/var/lib/postgresql/data
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gi"></span>     environment:
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gd">-      MARIADB_HOST: paperless
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gd">-      MARIADB_DATABASE: paperless
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gd">-      MARIADB_USER: paperless
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gd">-      MARIADB_PASSWORD: paperless
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gd">-      MARIADB_ROOT_PASSWORD: paperless
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gd"></span><span class="gi">+      POSTGRES_DB: paperless
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gi">+      POSTGRES_USER: paperless
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gi">+      POSTGRES_PASSWORD: paperless
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gi"></span> 
</span></span><span class="line"><span class="ln">32</span><span class="cl">   webserver:
</span></span><span class="line"><span class="ln">33</span><span class="cl">     image: ghcr.io/paperless-ngx/paperless-ngx:latest
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="gd">-    restart: unless-stopped
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gd"></span><span class="gi">+    restart: always
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="gi"></span>     depends_on:
</span></span><span class="line"><span class="ln">37</span><span class="cl">       - db
</span></span><span class="line"><span class="ln">38</span><span class="cl">       - broker
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="gu">@@ -68,13 +66,9 @@ services:
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="gu"></span>       - extra.env
</span></span><span class="line"><span class="ln">41</span><span class="cl">     environment:
</span></span><span class="line"><span class="ln">42</span><span class="cl">       PAPERLESS_REDIS: redis://broker:6379
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="gd">-      PAPERLESS_DBENGINE: mariadb
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="gd"></span>       PAPERLESS_DBHOST: db
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="gd">-      PAPERLESS_DBUSER: paperless # only needed if non-default username
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="gd">-      PAPERLESS_DBPASS: paperless # only needed if non-default password
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="gd">-      PAPERLESS_DBPORT: 3306
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="gd"></span> volumes:
</span></span><span class="line"><span class="ln">49</span><span class="cl">   data:
</span></span><span class="line"><span class="ln">50</span><span class="cl">   media:
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="gd">-  dbdata:
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="gd"></span><span class="gi">+  pgdata:
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="gi"></span>   redisdata:
</span></span></code></pre></div>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/images/docs/paperless.png" length="8086" type="image/.png" />
    </item>
    
    <item>
      <title>Mein Setup: Kontakte, Kalender, EMail, Passwörter</title>
      <link>https://www.rainerrose.de/posts/2025/mein-setup-kontakte-kalender-email-passwoerter/</link>
      <pubDate>Tue, 30 Sep 2025 22:07:22 +0200</pubDate>
      <guid>https://www.rainerrose.de/posts/2025/mein-setup-kontakte-kalender-email-passwoerter/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Ich wurde schon mal des Öfteren gefragt wie ich denn meine Kontakte, Kalender, Passwörter, EMails usw. verwalte.<br>
Persönlich möchte ich diese Datenschätze nicht oder nur möglichst wenig der &ldquo;Cloud&rdquo; überantworten und mache daher vieles selbst.
Das Setup bzw. das Konzept hat eine jahrelange Entwicklung und eine Menge Veränderungen hinter sich. Zu einigen Teilbereichen sind auch schon vereinzelte Blogartikel entstanden.</p>
<p>Ein Gespräch letztens auf Mastodon hat mich dazu veranlasst, dies mal aufzuschreiben und hier zu veröffentlichen. Wer mag, kann sich aus diesem Text Ideen holen und für seine Situation anpassen.</p>

<h2 id="tldr" data-numberify>TL&DR;<a class="anchor ms-1" href="#tldr"></a></h2>
<ul>
<li><em>Nextcloud</em> selfhostet auf eigener Hardware im Homelab</li>
<li>Passwort-Safes <code>KeePass</code>, <code>KeePassXC</code>, <em>Bitwarden</em>, <em>Vaultwarden</em></li>
<li>Backup mit <code>restic</code> und <code>resticprofile</code> verschlüsselt mit Deduplizierung auf S3-Storage und externen Festplatten</li>
</ul>
<p>Jetzt die Langfassung: Wie sieht mein Setup aus?</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Ich wurde schon mal des Öfteren gefragt wie ich denn meine Kontakte, Kalender, Passwörter, EMails usw. verwalte.<br>
Persönlich möchte ich diese Datenschätze nicht oder nur möglichst wenig der &ldquo;Cloud&rdquo; überantworten und mache daher vieles selbst.
Das Setup bzw. das Konzept hat eine jahrelange Entwicklung und eine Menge Veränderungen hinter sich. Zu einigen Teilbereichen sind auch schon vereinzelte Blogartikel entstanden.</p>
<p>Ein Gespräch letztens auf Mastodon hat mich dazu veranlasst, dies mal aufzuschreiben und hier zu veröffentlichen. Wer mag, kann sich aus diesem Text Ideen holen und für seine Situation anpassen.</p>

<h2 id="tldr" data-numberify>TL&DR;<a class="anchor ms-1" href="#tldr"></a></h2>
<ul>
<li><em>Nextcloud</em> selfhostet auf eigener Hardware im Homelab</li>
<li>Passwort-Safes <code>KeePass</code>, <code>KeePassXC</code>, <em>Bitwarden</em>, <em>Vaultwarden</em></li>
<li>Backup mit <code>restic</code> und <code>resticprofile</code> verschlüsselt mit Deduplizierung auf S3-Storage und externen Festplatten</li>
</ul>
<p>Jetzt die Langfassung: Wie sieht mein Setup aus?</p>

<h2 id="die-details" data-numberify>Die Details<a class="anchor ms-1" href="#die-details"></a></h2>
<p>Angefangen habe ich mit <em>Owncloud</em> auf einem <em>BananaPi</em>. Vor einigen Jahren bin dann zu <em>Nextcloud</em> gewechselt. Aktuell läuft die Installation auf meinem Heim-NAS von <em>QNAP</em> in einer virtuellen Maschine wo ein <em>Debian</em> drauf läuft.<br>
Aus-Gründen<sup>(tm)</sup> waren und sind die Installationen nie von außen erreichbar.</p>

<h2 id="nextcloud" data-numberify>Nextcloud<a class="anchor ms-1" href="#nextcloud"></a></h2>
<p>In der <em>Nextcloud</em> nutze ich überwiegend folgende Funkionen:</p>
<ul>
<li>Verwaltung von
<ul>
<li>Kalendern</li>
<li>Kontakt-Daten (VCards)</li>
</ul>
</li>
<li>Dateiaustausch</li>
</ul>
<p>Die gesamte Familie bindet alles in ihre jeweiligen Mail- und Kalender-Clients auf den unterschiedlichsten Rechnern, mobilen Geräten mit verschiedensten Betriebssystemen an. Die Synchronisation funktioniert somit natürlich nur im heimischen WLAN oder über VPN.</p>

<h3 id="backups" data-numberify>Backups<a class="anchor ms-1" href="#backups"></a></h3>
<p>Da mir meine Daten (Kalender und Kontakte) sehr wichtig sind, sichere ich die Daten regelmäßig und fertige ein Backup an.<br>
Aktuell läuft auf dem Server, wo die Nextcloud installiert ist, ein nächtlicher Cronjob:</p>
<ul>
<li>der die <em>Nextcloud</em> in den Wartungsmodus versetzt,
<ul>
<li>um einen konsistenten Zustand zu erreichen,</li>
</ul>
</li>
<li>einen Dump der mysql-Tabellen auf Festplatte zieht,</li>
<li>eine dateibasierte Sicherung anfertigt</li>
<li>und schlussendlich den Wartungsmodus wieder beendet.</li>
</ul>
<p>Der Cronjob läuft mitten in der Nacht, wenn sowieso alle schlafen. Selbst wenn das mal nicht der Fall sein sollte, verhindert der Wartungsmodus schlimmeres.<br>
Dieses Verfahren existiert schon so seit Jahren und hat sich aus meiner Sicht bewährt. Selbst der Umzug von <em>Owncloud</em> nach <em>Nextcloud</em> sowie vom <em>BananaPi</em> in die virtuelle Maschine hat damit wunderbar funktioniert.</p>
<p>Die dateibasierte Sicherung und die Dumps ins Dateisystem sind nur ein erster Schritt. Wenn ich einzelne Kontakte oder ähnliches wiederherstellen möchte, ist das rauspfriemeln einzelner Daten nicht wirklich schön, beispielsweise eine einzelne aus Versehen gelöschte Kontakt-Karte.<br>
Allerdings gibt es die Möglichkeit alle Kalendereinträge in <strong>eine</strong> <code>ics</code> Datei herunterzuladen und alle Kontakte in <strong>eine</strong> <code>.vcf</code>-Datei herunterzuladen. Daraus ist die Wiederherstellung aus meiner Sicht wesentlich einfacher. Wenn alle Dateien gelöscht sind, reicht ggf. sogar ein Doppelklick im Dateiexplorer auf die jeweilige Datei und es wird im Standard-Mail-Programm geöffnet. Von diesem ist dann ein Import wieder möglich. Dadurch, dass das Mail-Programm wieder in die Nextcloud synchronisiert, ist der Kontakt wieder &ldquo;drin&rdquo;. Natürlich war ich faul und habe dies automatisiert; wie ich dies realisiert habe, lässt sich <a href="/posts/export-nextcloud-rolle/">hier nachlesen</a>.</p>
<p>Die ganzen Datenkopien (wie Dumps und Exporte) sichere ich nun mit einem Backup-Programm. Da mir die Daten allgemein wichtig sind und ich auch vor Diebstahl und Feuer gewappnet sein möchte, setze ich hier auf das 3-2-1 Prinzip.</p>

<h4 id="3-2-1-prinzip" data-numberify>3-2-1-Prinzip<a class="anchor ms-1" href="#3-2-1-prinzip"></a></h4>

<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Hinweis
    </p>
    <p>Hinweis: Das 3-2-1-Prinzip bedeutet in kurz:<br>
Du hast <strong>drei</strong> Kopien auf <strong>zwei</strong> unterschiedlichen Medien, wobei <strong>eine</strong> Kopie außerhalb liegt.</p>
</blockquote>
<p>Die bedeutet: Ich habe eine externe Festplatte auf die ich die Backups regelmäßig kopiere, wobei eine Kopie bei meinen Eltern liegt. Das tägliche Backup schiebe ich in die <em>Cloud</em>. <a href="/posts/backupkonzept/#offsite-backup">Details siehe in diesem Blog-Artikel im Abschnitt Offsite-Backup</a>.</p>

<h4 id="cloudspeicher" data-numberify>Cloudspeicher<a class="anchor ms-1" href="#cloudspeicher"></a></h4>
<p>&ldquo;<em>Cloud</em>&rdquo; bedeutet hierbei irgend ein Speicherort, der Dateien aufnehmen kann. Das kann ein <em>OneDrive</em>, eine <em>Dropbox</em>, <em>PDrive</em> oder was auch immer sein.
Bei mir ist dies aktuell ein angemieteter S3-kompatibler-Speicher.<br>
Die Backups verschlüssele ich natürlich, daher ist es egal, wo diese im Endeffekt liegen:</p>
<ul>
<li>ob es nun eine externe Festplatte ist, die geklaut wird</li>
<li>oder irgendein Cloud-Speicher, wo Daten abfließen können.</li>
</ul>
<p>Das gewählte Verschlüsselungspasswort sollte daher möglichst lang und komplex sein.</p>

<h3 id="backup-software" data-numberify>Backup-Software<a class="anchor ms-1" href="#backup-software"></a></h3>
<p>Als Software verwendet ich seit einiger <code>restic</code> in Zusammenarbeit mit <code>resticprofile</code>, da ich mit diesen Tools und die Backup-Jobs gut weg-automatisieren kann. Wenn ich daran denken muss, Backups zu machen, vergesse ich das nur.<br>
Wie die Software funktioniert und bedient wird, haben Andreas und ich mal
<a href="/docs/writings/2025/#backups-mit-restic-und-resticprofile">auf den Chemmnitzer LinuxTagen erzählt</a>.</p>
<p>Aus meiner Sicht ist der Vorteil, dass <code>restic</code></p>
<ul>
<li>die Daten verschlüsselt,</li>
<li>den Platzbedarf mittels Deduplizierung verringert</li>
<li>und ausmisten nach Regeln beherrscht, da kein Speicher unendlich ist.</li>
</ul>
<p><code>resticprofile</code> unterstützt hierbei sogar bei der Erzeugung eines möglichst langen und zufälligem Passwortes.
Dadurch, dass ich Dinge automatisch in die <em>Cloud</em> sichere, aber lokal noch eine Festplatte habe, die ich &ldquo;natürlich&rdquo; (leider) nur unregelmäßig befülle, hat mir dieses Konzept schon mehrfach
<a href="/posts/backupkonzept/#beispiel">wortwörtlich den Hintern gerettet</a>.
Aktuell kostet mich der S3-Storage irgendwas unter 4 Euro pro Monat.
<a href="/posts/backupkonzept/">Bei Interesse nach mehr Details hier klicken</a>.
Die dortige Angabe <em>Backblaze</em> stimmt inzwischen nicht mehr. Wegen <code>#unplugTrump</code> bin ich zu <em>Hetzner</em> <em>StorageShare</em> gewechselt.</p>

<h2 id="passwort-safes" data-numberify>Passwort-Safes<a class="anchor ms-1" href="#passwort-safes"></a></h2>
<p>Ebenfalls seit Jahren nutze ich einen Passwort-Speicher. Bisher reichte mir ein <code>KeePass</code>. Später wechselte ich zu <code>KeePassXC</code>. Die Tresor-Datei lege ich bei mir in meinen <em>Nextcloud</em>-Ordner. Dort synchronisiert sich diese Datei wunderbar zwischen meinen Geräten und ist überall verfügbar, wo ich sie benötige. <br>
Das <code>KeePass</code>-Tersor-Format ist recht verbreitet und es gibt für die unterschiedlichsten Betriebssysteme Software dafür. Die Client-Software für <em>Nextcloud</em> ist ebenso für viele Betriebssysteme verfügbar.</p>
<p>Dank <em>Auto-Fill</em>-Funktion habe ich schon länger kein Passwort mehr getippt und durch die <em>Passwort-Würfeln</em>-Funktion kenne ich auch gar kein Passwort mehr auswendig. Jedem Dienst ein Passwort zu verpassen, ist dadurch für mich recht einfach geworden.
In letzter Zeit musste ich jedoch öfters Passwörter teilen und da hat mir <code>KeePassXC</code> nicht gereicht. <code>KeeShare</code> habe ich erst vor kurzem  <a href="https://tools.rainerrose.de/redirect.php?id=53" target="_blank" rel="noopener noreferrer">in einen Artikel auf gnulinux.ch<i class="fas fa-external-link-square-alt ms-1"></i></a> entdeckt.<br>
Aus diesem Grund habe ich mir einen <em>Vaultwarden</em> installiert. <em>Vaultwarden</em> ist auch perfekt, wenn ich Dateien oder Textschnipsel über unsichere Kanäle verschicken möchte. Die Funktion nennt sich <em>vaultwarden-send</em>. Über diese Funktion kann ich Dateien oder kurze Texte in den Vault hochladen und danach einen Link zum Herunterladen generieren.
Zusätzlich kann ich diesen Link noch mit Passwort-Schutz und sonstigen Parameter versehen. Diesen Link kann ich ruhigen Gewissens unverschlüsselt per E-Mail verschicken. Die Daten liegen verschlüsselt auf der Server-Festplatte und löschen sich  standardmäßig nach sieben Tagen selbst.</p>

<h3 id="tipps-zu-vaultwarden" data-numberify>Tipps zu Vaultwarden<a class="anchor ms-1" href="#tipps-zu-vaultwarden"></a></h3>

<h4 id="standard-einstellungen" data-numberify>Standard-Einstellungen<a class="anchor ms-1" href="#standard-einstellungen"></a></h4>
<p>Ich empfehle zwei Standard-Einstellungen zu ändern:</p>
<ul>
<li><code>Standard-URI Übereinstimmungserkennung</code> auf <code>Beginnt mit</code>
<ul>
<li>das funktioniert bei mir am zuverlässigsten.</li>
<li>gerade auch in Kombination, dass im URL-Feld des jeweiligen Eintrags dann nur die Domain hinterlegt wird.</li>
<li>Das bedeutet ohne Pfade und sonstige Parameter!</li>
<li>Auffindbar derzeit unter: <code>Einstelllungen-&gt;Automatisches Ausfüllen</code></li>
</ul>
</li>
<li><code>Zwischenablage leeren</code> würde ich auf einen kurzen Zeitraum setzen,  z.B. auf 30 Sekunden.
<ul>
<li>soweit ich mich erinnere ist der Standard <code>niemals</code>.</li>
<li>Auffindbar derzeit unter: <code>Einstelllungen-&gt;Sonstiges</code></li>
</ul>
</li>
</ul>
<p>Leider muss die Einstellung in <strong>jedem Client einzeln</strong> gesetzt werden (Browser-Plugin, Smartphone-App usw.)</p>

<h4 id="admin-seite" data-numberify>Admin-Seite<a class="anchor ms-1" href="#admin-seite"></a></h4>
<p>Die Admin-Seite würde ich aus Sicherheitsgründen ausschalten bzw. ausgeschaltet lassen.
Wenn ich die Funktionalität doch einmal benötige, schalte ich sie kurzzeitig ein, auch wenn es zwei kurze Downtimes bedeutet.</p>

<h4 id="verständnis-entwickeln" data-numberify>Verständnis entwickeln<a class="anchor ms-1" href="#verständnis-entwickeln"></a></h4>
<p>Als ich <em>vaultwarden</em> aufgesetzt habe, hatte ich zuerst zwei Test-Accounts angelegt.
Danach habe ich eine &ldquo;Organistation&rdquo; und mehrere &ldquo;Sammlungen&rdquo; angelegt.
Mit diesen zwei Accounts konnte ich dann ein besseres Verständnis der beiden Begriffe und des Berechtigungssystems erlangen.</p>

<h2 id="email" data-numberify>EMail<a class="anchor ms-1" href="#email"></a></h2>
<p>Das ist für mich persönlich so ein großes Unlust-Thema und PITA.<br>
Seit ich online bin, lagere ich diesen Service als Dienstleistung aus. Ich klicke mir irgendwo eine Domain und einen E-Mail-Speicherplatz und lasse das Menschen machen, die mehr Ahnung von dem Thema haben als ich.
Aktuell bin ich seit Jahren sehr glücklich bei <a href="https://tools.rainerrose.de/redirect.php?id=11" target="_blank" rel="noopener noreferrer">uberspace<i class="fas fa-external-link-square-alt ms-1"></i></a>.<br>
Ich habe bei denen mehrere Accounts (<code>asteroid</code> genannt). Dort lassen sich ohne Zusatzkosten Domains &ldquo;aufschalten&rdquo;.
In deren verfügbarer Dokumentation (<code>manual</code>) sind die benötigten Befehle recht gut beschrieben, sofern man sich an die Kommandozeile herantraut.
In deren Dokumentationsbereich namens <code>Uberlab</code> sind weitere Möglichkeiten beschrieben, zusätzliche Software zu installieren; inkl. Update-Schritten. Vielfach ist es nur ein copy&amp;paste von vorgefertigten Befehlen.</p>

<h2 id="idee-und-ausblick" data-numberify>Idee und Ausblick<a class="anchor ms-1" href="#idee-und-ausblick"></a></h2>
<p>Soweit ein Einblick in mein Setup und so wie es für mich funktioniert und bewährt hat. Ich habe versucht es kurz zu halten und bei ausufernden Themen mit Verweisen zu arbeiten.</p>
<p>Wer mag, kann die für sich passenden Informationen herausziehen und die eigenen Verhältnisse und Anforderungen anpassen oder ein Konzept zu entwickeln.</p>
<p>Auf der Wunschliste steht noch die Generierung der TLS-Zertifikate per <em>Let&rsquo;s-Encypt</em>, da muss ich mich nochmal einlesen, wie ich dies umsetze, wenn die Systeme nicht von außen erreichbar sind.</p>

<h2 id="changelog" data-numberify>Changelog<a class="anchor ms-1" href="#changelog"></a></h2>
<table>
  <thead>
      <tr>
          <th>Datum</th>
          <th>Änderung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>16.01.2026</td>
          <td>Vaultwarden: Hinweis auf Admin-Seite und ein paar Unterüberschriften in diesem Absatz hinzugefügt.</td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/wallpaper/posts/hardware.jpg" length="1385855" type="image/.jpg" />
    </item>
    
    <item>
      <title>pdftk - PDF Dateien bearbeiten</title>
      <link>https://www.rainerrose.de/docs/howto/pdftk/</link>
      <pubDate>Wed, 14 May 2025 18:21:53 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/pdftk/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Zur Massenbearbeitung von PDF-Dateien gibt es das schöne Tool <code>pdftk</code> oder auch manchmal <code>pdftk-java</code> genannt.</p>
<p>Mit diesem Tool lassen sich z.B. PDF-Dateien in Einzel-Dateien aufsplitten, mehrere PDF-Dateien zu einer Gesamtdatei zusammenfassen, Seiten rotieren etc.</p>
<p>Um nicht immer Suchmaschinen bedienen zu müssen, erfolgt hier eine Auflistung <strong>meiner</strong> häufig genutzten Parameter.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Zur Massenbearbeitung von PDF-Dateien gibt es das schöne Tool <code>pdftk</code> oder auch manchmal <code>pdftk-java</code> genannt.</p>
<p>Mit diesem Tool lassen sich z.B. PDF-Dateien in Einzel-Dateien aufsplitten, mehrere PDF-Dateien zu einer Gesamtdatei zusammenfassen, Seiten rotieren etc.</p>
<p>Um nicht immer Suchmaschinen bedienen zu müssen, erfolgt hier eine Auflistung <strong>meiner</strong> häufig genutzten Parameter.</p>

<h2 id="aufruf" data-numberify>Aufruf<a class="anchor ms-1" href="#aufruf"></a></h2>
<p>Wenn <code>pdftk</code> ohne Parameter aufgerufen wird, gibt es eine Kurzhilfe mit einer groben Auflistung der Parameter.</p>
<p><code>pdftk --help</code> ist zwar schon etwas geschwätziger, aber ein paar Spezialfälle fehlen mir da; diese folgen nun.</p>

<h2 id="zusammenfügen" data-numberify>Zusammenfügen<a class="anchor ms-1" href="#zusammenfügen"></a></h2>
<p>Kommando: <code>cat</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pdftk dateien-*.pdf cat output gesamtdatei.pdf
</span></span></code></pre></div>
<h2 id="trennen" data-numberify>Trennen<a class="anchor ms-1" href="#trennen"></a></h2>
<p>Kommando: <code>burst</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pdftk gesamtdatei.pdf burst output praefix_
</span></span></code></pre></div><p>Ergibt dann bei einer vierseiten PDF-Datei:</p>
<pre><code>doc_data.txt
praefix_pg_0001.pdf
praefix_pg_0002.pdf
praefix_pg_0003.pdf
praefix_pg_0004.pdf
</code></pre>

<h2 id="seiten-abwechselnd-rotieren" data-numberify>Seiten abwechselnd rotieren<a class="anchor ms-1" href="#seiten-abwechselnd-rotieren"></a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pdftk <span class="nv">A</span><span class="o">=</span>PdfDatei.pdf shuffle AoddWest AevenEast output PdfDatei_Rotiert.pdf
</span></span></code></pre></div>
<h2 id="einzelseite-rotieren" data-numberify>Einzelseite rotieren<a class="anchor ms-1" href="#einzelseite-rotieren"></a></h2>
<p>Kommando: <code>burst</code></p>
<p>Der Parameter setzt sich zusammen aus</p>
<ul>
<li>der Seitenzahl, wo begonnen werden soll.</li>
<li>der Seitenzahl, bis zu welcher Seite gedreht werden soll</li>
<li>der Drehrichtung</li>
</ul>
<p>zusammen.</p>
<table>
  <thead>
      <tr>
          <th>Drehrichtung</th>
          <th style="text-align: right">Grad</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>north</td>
          <td style="text-align: right">0</td>
      </tr>
      <tr>
          <td>east</td>
          <td style="text-align: right">90</td>
      </tr>
      <tr>
          <td>south</td>
          <td style="text-align: right">180</td>
      </tr>
      <tr>
          <td>west</td>
          <td style="text-align: right">270</td>
      </tr>
      <tr>
          <td>alternativ</td>
          <td style="text-align: right"></td>
      </tr>
      <tr>
          <td>left</td>
          <td style="text-align: right">-90</td>
      </tr>
      <tr>
          <td>right</td>
          <td style="text-align: right">+90</td>
      </tr>
      <tr>
          <td>down</td>
          <td style="text-align: right">+180</td>
      </tr>
  </tbody>
</table>
<p><strong>Beispielaufruf</strong></p>
<p>Ab Seite 1 bis zur letzten Seite (zum Ende) um 270 Grad:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pdftk PdfDatei.pdf rotate 1-endwest output rotiert.pdf
</span></span></code></pre></div><p>Alternativen:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pdftk <span class="nv">A</span><span class="o">=</span>out.pdf shuffle Aoddsouth output rotiert.pdf
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pdftk <span class="nv">A</span><span class="o">=</span>out1.pdf <span class="nv">B</span><span class="o">=</span>out12.pdf cat A1-3 B1-7 A8 output rotiert.pdf 
</span></span></code></pre></div>]]></content:encoded>
    </item>
    
    <item>
      <title>Ansible: source not found</title>
      <link>https://www.rainerrose.de/posts/2025/ansible-source-not-found/</link>
      <pubDate>Thu, 08 May 2025 21:26:11 +0200</pubDate>
      <guid>https://www.rainerrose.de/posts/2025/ansible-source-not-found/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Vor ein paar Tagen habe ich mal wieder an einer <code>Ansible</code>-Rolle geschraubt und wurde beim Ausführen von <code>ansible-playbook</code> mit folgender Fehlermeldung begrüßt:</p>
<pre><code>Source /root/.ansible/tmp/ansible-tmp-1746359478.4110813-9236-74562322926004/.source not found
</code></pre>
<p>Das war seltsam, weil ich nur das Modul <code>ansible.builtin.copy</code> aufgerufen hatte, was vorher auf anderen Rechner problemlos funktioniert hatte.</p>
<p>Und jetzt?</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Vor ein paar Tagen habe ich mal wieder an einer <code>Ansible</code>-Rolle geschraubt und wurde beim Ausführen von <code>ansible-playbook</code> mit folgender Fehlermeldung begrüßt:</p>
<pre><code>Source /root/.ansible/tmp/ansible-tmp-1746359478.4110813-9236-74562322926004/.source not found
</code></pre>
<p>Das war seltsam, weil ich nur das Modul <code>ansible.builtin.copy</code> aufgerufen hatte, was vorher auf anderen Rechner problemlos funktioniert hatte.</p>
<p>Und jetzt?</p>

<h2 id="die-fehlermeldung" data-numberify>Die Fehlermeldung<a class="anchor ms-1" href="#die-fehlermeldung"></a></h2>
<pre><code>TASK [meine_rolle : inst | linux_install | Copy Binaries] *************************************************************************************************************************************************************************************************
failed: [t24] (item=/pfad/zur/datei/foo) =&gt; {&quot;ansible_loop_var&quot;: &quot;item&quot;, &quot;changed&quot;: false, &quot;checksum&quot;: &quot;884109935b2a432cb6edb5003c4ac5adc9462cbe&quot;, &quot;item&quot;: &quot;/pfad/zur/datei/foo&quot;, &quot;msg&quot;: &quot;Source /root/.ansible/tmp/ansible-tmp-1746359476.4233594-9236-186673184030632/.source not found&quot;}
failed: [t24] (item=/pfad/zur/datei/bar) =&gt; {&quot;ansible_loop_var&quot;: &quot;item&quot;, &quot;changed&quot;: false, &quot;checksum&quot;: &quot;9e184000b3f2541c07f62dcbf8bf7e8927757bec&quot;, &quot;item&quot;: &quot;/pfad/zur/datei/bar&quot;, &quot;msg&quot;: &quot;Source /root/.ansible/tmp/ansible-tmp-1746359478.4110813-9236-74562322926004/.source not found&quot;}
</code></pre>

<h2 id="die-task" data-numberify>Die Task<a class="anchor ms-1" href="#die-task"></a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">inst | linux_install | Copy Binaries</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.copy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ item }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ meine_rolle_install_path }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">owner</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;root&#39;</span><span class="w"> 
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;root&#39;</span><span class="w"> 
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;0755&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="nt">loop</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span>- <span class="s1">&#39;/pfad/zur/datei/foo&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span>- <span class="s1">&#39;/pfad/zur/datei/bar&#39;</span><span class="w">
</span></span></span></code></pre></div>
<h2 id="das-problem" data-numberify>Das Problem<a class="anchor ms-1" href="#das-problem"></a></h2>
<p>Ich bin per <code>ssh</code> auf die betroffene Maschine gesprungen und musste feststellen, dass das Verzeichnis <code>/root/.ansible/tmp/</code> auf der Remote-Maschine wirklich nicht da war. Auch wurde das <code>/root</code> Verzeichnis (meiner Erinnerung nach) nicht angefasst. Ein manuelles Erstellen des Verzeichnisses brachte keine Besserung.</p>
<p>Zuerst kam mir keine Idee und daher hatte ich diverse Suchmaschinen befragt. Ich erwähnte es eingangs: &ldquo;<em>auf anderen Rechner problemlos</em>&rdquo;.
Nun erinnerte ich mich, dass bei mir eine andere Distribution Einzug gehalten hat, die auf Ubuntu basiert.</p>

<h2 id="die-lösung" data-numberify>Die Lösung<a class="anchor ms-1" href="#die-lösung"></a></h2>
<p>Hier scheint das Modul <code>ansible.builtin.copy:</code> mit dem <code>become: true</code> laut meinen Recherchen das Problem zu sein.</p>
<p>Beheben lies sich dies, in dem ich in der <code>ansible.cfg</code> folgende Option gesetzt habe:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nv">remote_tmp</span> <span class="o">=</span> /tmp/ansible_tmp
</span></span></code></pre></div><p>Ich hab das Playbook dann noch mit einem Host, der unter einem Debian 12 läuft, probiert und auch da lief das Playbook anstandslos durch. Ich lass das jetzt so.</p>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/images/lupe.jpg" length="60818" type="image/.jpg" />
    </item>
    
    <item>
      <title>Ansible Rolle export_nextcloud</title>
      <link>https://www.rainerrose.de/posts/export-nextcloud-rolle/</link>
      <pubDate>Sat, 22 Feb 2025 15:37:52 +0100</pubDate>
      <guid>https://www.rainerrose.de/posts/export-nextcloud-rolle/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Derzeit sichere ich meine Nextcloud-Instanz mit einem dateibasierten Backup-Tool, sowie einem Dump der Datenbank.
Letztens hatte ich in meinem Umfeld jemanden, der ein paar Kontakte aus Versehen gelöscht hat. Nun können die vermissten Kontakte zwar aus dem Dump der Datenbank herausgepfriemelt werden, schön ist das aber nicht.</p>
<p>Nach etwas Recherche bin ich auf einen recht simplen <code>curl</code>-Befehl gestoßen, der eine <code>.ics</code> bzw <code>.vcf</code>-Datei heraus exportiert.</p>
<p>Dies ist dann doch aus meiner Sicht etwas einfacher. Sofern z.B. alle Kontakte gelöscht worden sind, reicht dann sogar ein Doppelklick auf die Datei und alle Kontakte werden wieder importiert. Dies sollte wesentlich stressfreier sein.</p>
<p>Als Familien-Admin möchte ich den Export für alle Nutzenden der Instanz regelmäßig machen; daher habe ich mir eine Ansible-Rolle dafür gestrickt.</p>
<p>Das Grundprinzip und die Konfiguration der Rolle möchte ich in diesem Artikel beschreiben.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Derzeit sichere ich meine Nextcloud-Instanz mit einem dateibasierten Backup-Tool, sowie einem Dump der Datenbank.
Letztens hatte ich in meinem Umfeld jemanden, der ein paar Kontakte aus Versehen gelöscht hat. Nun können die vermissten Kontakte zwar aus dem Dump der Datenbank herausgepfriemelt werden, schön ist das aber nicht.</p>
<p>Nach etwas Recherche bin ich auf einen recht simplen <code>curl</code>-Befehl gestoßen, der eine <code>.ics</code> bzw <code>.vcf</code>-Datei heraus exportiert.</p>
<p>Dies ist dann doch aus meiner Sicht etwas einfacher. Sofern z.B. alle Kontakte gelöscht worden sind, reicht dann sogar ein Doppelklick auf die Datei und alle Kontakte werden wieder importiert. Dies sollte wesentlich stressfreier sein.</p>
<p>Als Familien-Admin möchte ich den Export für alle Nutzenden der Instanz regelmäßig machen; daher habe ich mir eine Ansible-Rolle dafür gestrickt.</p>
<p>Das Grundprinzip und die Konfiguration der Rolle möchte ich in diesem Artikel beschreiben.</p>
<p>Auf die Bedienung und Verwendung von Ansible möchte ich hier nicht eingehen, da setze ich entsprechendes Wissen voraus.</p>
<p>Zum Zeitpunkt des Artikel ist die Nextcloud-Version 31 aktuell. Die Pfade, die in der Ansible-Rolle verwendet werden, orientieren sich an diesem Stand. Sollten sich die Subpfade der URLs ändern, müsste die Rolle angepasst werden.</p>

<h2 id="überblick" data-numberify>Überblick<a class="anchor ms-1" href="#überblick"></a></h2>
<p>Um die Rolle zu verstehen, ist es womöglich am einfachsten, sich das Vorgehen zu verdeutlichen, wenn ein manuelles Backup angefertigt werden soll. Dies soll hier in den nächsten Abschnitten beschrieben werden, um dann im nächsten Schritt zur Automatisierung zu kommen.</p>

<h2 id="händisches-backup" data-numberify>Händisches Backup<a class="anchor ms-1" href="#händisches-backup"></a></h2>
<p>Melde dich als ersten Schritt in Deiner Nextcloud an.</p>

<h3 id="kontakte" data-numberify>Kontakte<a class="anchor ms-1" href="#kontakte"></a></h3>
<ul>
<li>Wähle den Reiter <code>Kontakte</code>.</li>
<li>Gehe nach unten auf der linken Seite und klicke da auf <code>Kontakte-Einstellungen</code>.</li>
</ul>
<p><picture><img class="img-fluid " alt="Weg zu den Kontakte-Einstellungen" src="https://www.rainerrose.de/images/posts/export-nextcloud-rolle/nextcloud-kontakte-weg.jpg?v=59fab2dd88dc536d0263c9c2916f2368" loading="lazy" width="595" height="464" />
</picture>

</p>
<ul>
<li>Navigiere jetzt zu den <code>Allgemeinen Einstellungen</code>.</li>
<li>Beim gewünschten Adressbuch auf die drei Punkte klicken (1)</li>
<li>und auf <code>Herunterladen</code> klicken (2).</li>
</ul>
<p><picture><img class="img-fluid " alt="Weg zu den Export der Kontakte" src="https://www.rainerrose.de/images/posts/export-nextcloud-rolle/nextcloud-kontakte-export.jpg?v=59f012e6d6fa7c320100b4896e27c389" loading="lazy" width="925" height="693" />
</picture>

</p>
<ul>
<li>Voilà! Es wird eine <code>.vcf</code>-Datei mit einem aktuellen Datumsstempel zum Download angeboten.</li>
</ul>

<h3 id="kalender" data-numberify>Kalender<a class="anchor ms-1" href="#kalender"></a></h3>
<ul>
<li>Wähle den Reiter <code>Kontakte</code>.</li>
<li>Wähle Deinen Kalender aus, den du exportieren möchtest.</li>
<li>Fahre Deine Maus etwas nach rechts bis das Stift-Icon erscheint (1) und klicke auf diesen.</li>
</ul>
<p><picture><img class="img-fluid " alt="Weg zu den Kalender-Einstellungen" src="https://www.rainerrose.de/images/posts/export-nextcloud-rolle/kalender-einstellungen-weg.jpg?v=4a778c757475d6d8fdc941d53a6da564" loading="lazy" width="623" height="264" />
</picture>

</p>
<ul>
<li>Im nächsten Dialog <code>Kalender bearbeiten</code> gibt es dann eine Schaltfläche mit der Beschriftung <code>Exportieren</code>.</li>
</ul>
<p><picture><img class="img-fluid " alt="Weg zu den Kalender-Einstellungen" src="https://www.rainerrose.de/images/posts/export-nextcloud-rolle/kalender-export.jpg?v=801d80930c1b8ce33e664c38ade642e3" loading="lazy" width="330" height="454" />
</picture>

</p>
<ul>
<li>Voilà! Es wird eine <code>.ics</code>-Datei mit einem aktuellen Datumsstempel zum Download angeboten.</li>
</ul>

<h2 id="automatisierung" data-numberify>Automatisierung<a class="anchor ms-1" href="#automatisierung"></a></h2>
<p>Jetzt wollen wir das ganze automatisieren; dafür gibt es meine Ansible-Rolle <code>export_nextcloud</code>. Die Rolle findet Du auf meinem <a href="https://tools.rainerrose.de/redirect.php?id=46" target="_blank" rel="noopener noreferrer">Gitlab-Account<i class="fas fa-external-link-square-alt ms-1"></i></a>.</p>
<p>Die Rolle solltest Du auf einem Linux-Host deployen, dem Du vertraust und der Zugriff auf die Nextcloud hat.</p>

<h3 id="minimale-angaben" data-numberify>Minimale Angaben<a class="anchor ms-1" href="#minimale-angaben"></a></h3>

<h4 id="variable-export_nextcloud_url" data-numberify>Variable export_nextcloud_url<a class="anchor ms-1" href="#variable-export_nextcloud_url"></a></h4>
<p>Zunächst benötigt die Rolle die URL, worunter die Nextcloud-Instanz erreichbar ist.
Dazu hinterlegst Du diese in der Variable <code>export_nextcloud_url</code> als Hostvariable, wo das Export-Script später laufen soll.</p>
<p>Beispiel:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">export_nextcloud_url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://meine.nextcloud.instanz&#34;</span><span class="w">
</span></span></span></code></pre></div>
<h4 id="variable-export_nextcloud_user" data-numberify>Variable export_nextcloud_user<a class="anchor ms-1" href="#variable-export_nextcloud_user"></a></h4>
<p>Jetzt brauchst Du eine Liste mit einem oder mehreren Usernamen, die du sichern möchtest. Zusätzlich ein Passwort und einen Zeitpunkt, wann das Export-Script laufen soll.</p>

<blockquote class="alert alert-warning" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-exclamation-circle me-2"></i>Empfehlung
    </p>
    <p>Statt des regulären Passwortes für Deinen User, empfehle ich die Generierung eines Personal Access Token kurz &ldquo;<code>PAT</code>&rdquo;.</p>
</blockquote>
<p>Die Einrichtung eines <code>PAT</code> beschreibe ich <a href="/posts/nextcloud-pat-erstellen/">in einem anderem Blog-Artikel</a>.</p>
<p>Als dritten Parameter wird noch der Zeitstempel benötigt, wo der Export laufen soll.
Mit dieser Angabe wird später eine SystemD-Timer-Unit erstellt.</p>
<p>Die Syntax dieses Attributes folgt dem Parameter <code>on_calendar</code> von <a href="https://www.freedesktop.org/software/systemd/man/systemd.timer#OnCalendar=" target="_blank" rel="noopener noreferrer">systemd.unit(5)<i class="fas fa-external-link-square-alt ms-1"></i></a>.
Aus jeden <code>username</code> wird eine extra SystemD-Timer-Units erstellt, nach folgendem Muster:</p>
<ul>
<li><code>export_nextcloud-user1.timer</code></li>
<li><code>export_nextcloud-user2.timer</code></li>
</ul>
<p>Die Konfiguration sieht am Ende zum Beispiel so aus:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">export_nextcloud_user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span>- <span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;user1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;password1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="nt">on_calendar</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;*-*-* 03:00&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span>- <span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;user2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;password2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span><span class="nt">on_calendar</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;*-*-* 03:01&#39;</span><span class="w">
</span></span></span></code></pre></div>
<h3 id="optionale-angaben" data-numberify>Optionale Angaben<a class="anchor ms-1" href="#optionale-angaben"></a></h3>

<h4 id="variable-export_nextcloud_script_path" data-numberify>Variable export_nextcloud_script_path<a class="anchor ms-1" href="#variable-export_nextcloud_script_path"></a></h4>
<p>Die nächste Variable bestimmt, wohin das Script kopiert werden soll. Der Standardpfad lautet <code>/opt/export_nextcloud</code>.</p>

<h4 id="variable-export_nextcloud_backup_path" data-numberify>Variable export_nextcloud_backup_path<a class="anchor ms-1" href="#variable-export_nextcloud_backup_path"></a></h4>
<p>Über diese Variable kann bestimmt werden, wohin die exportierten Kalender- und Kontakte-Dateien hingeschrieben werden sollen.
<code>/backup/nextcloud_export</code> lautet hier der Standardpfad.</p>
<p>Hier entsteht pro Username eine Kalender- und eine Kontakte-Datei mit dem Username
nach folgendem Muster:</p>
<ul>
<li><code>kalender-${username}.ics</code></li>
<li><code>kontakte-${username}.vcf</code></li>
</ul>
<p>Und ja, ich war zu faul, noch ein extra Aufräum-Mechanismus zu schreiben.
Für die Versionierung ist bei mir mein Backup-Programm zuständig ;-) .
Das kann mein Sicherungsprogramm viel effizienter als ich.
Wenn ich an ältere Versionen herankommen möchte, muss ich die Sicherung bemühen; ich spare mir dadurch zusätzliche Fehlerquellen und Abhängigkeiten.</p>

<h4 id="variable-export_nextcloud_prometheus_path" data-numberify>Variable export_nextcloud_prometheus_path<a class="anchor ms-1" href="#variable-export_nextcloud_prometheus_path"></a></h4>
<p>Diese Variable legt den Pfad fest, wo die Prometheus-Statistiken hingeschrieben werden.</p>

<h4 id="variable-export_nextcloud_extra_curl_options" data-numberify>Variable export_nextcloud_extra_curl_options<a class="anchor ms-1" href="#variable-export_nextcloud_extra_curl_options"></a></h4>
<p>Diese Variable habe ich am 03.10.2025 eingeführt, weil der zugrunde liegende <code>curl</code> sich am selbst-signierten Zertifikat meiner Nextcloud-Instanz störte.
Mit dem Parameter <code>-k</code> oder <code>--insecure</code> lässt sich das aber übersteuern. Dies erforderte allerdings eine Anpassung des Scriptes.</p>
<p>Nun können weitere Parameter über diese Variable optional mitgegeben werden.</p>
<p>Im unteren Beispiel Ansible Playbook fügt ihr an den einen Task folgende zwei Zeilen an.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="w">  </span><span class="nt">vars</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="nt">export_nextcloud_extra_curl_options</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;--insecure&#34;</span><span class="w">
</span></span></span></code></pre></div>
<h3 id="ansible-playbook" data-numberify>Ansible Playbook<a class="anchor ms-1" href="#ansible-playbook"></a></h3>
<p>Jetzt kannst Du die Rolle in ein Ansible-Playbook einbinden.</p>
<p>Ein minimales Beispiel wäre:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup export_nextcloud</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l">export_nextcloud</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span><span class="nt">roles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span>- <span class="l">export_nextcloud</span><span class="w">
</span></span></span></code></pre></div>
<h2 id="backup" data-numberify>Backup<a class="anchor ms-1" href="#backup"></a></h2>
<p>Im Nachgang bzw. nach dem Ausführen der jeweiligen SystemD-Timer-Unit, empfehle ich noch ein Backup des Verzeichnisses wo die exportierten Dateien liegen.
Das Verzeichnis kannst zum Beispiel dateibasiert mittels deiner präferierten Wunsch-Backup-Software gesichert werden.
Ich selbst habe dazu die Pfade in meinem Sicherungsscript aufgenommen; hier verwende ich <code>restic</code> seit längerem erfolgreich.</p>

<h2 id="fertig" data-numberify>Fertig.<a class="anchor ms-1" href="#fertig"></a></h2>
<p>Im Grunde war es das auch schon. Viel Spaß beim Benutzen.
Der Source-Code der Rolle liegt in meinem <a href="https://tools.rainerrose.de/redirect.php?id=46" target="_blank" rel="noopener noreferrer">Gitlab-Account<i class="fas fa-external-link-square-alt ms-1"></i></a>.</p>

<h2 id="changelog" data-numberify>Changelog<a class="anchor ms-1" href="#changelog"></a></h2>
<table>
  <thead>
      <tr>
          <th>Datum</th>
          <th>Änderung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>03.10.2025</td>
          <td>Neue optionale Variable <code>export_nextcloud_extra_curl_options</code></td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/wallpaper/posts/nextcloud.png" length="9229" type="image/.png" />
    </item>
    
    <item>
      <title>Nextcloud - Einrichtung eines PAT</title>
      <link>https://www.rainerrose.de/posts/nextcloud-pat-erstellen/</link>
      <pubDate>Sat, 22 Feb 2025 15:37:43 +0100</pubDate>
      <guid>https://www.rainerrose.de/posts/nextcloud-pat-erstellen/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Soll auf Daten in einer Nextcloud zugegriffen werden, erfolgt dies häufig mittels der Kombination aus <code>Username</code> und <code>Passwort</code>.
Erfolg dieser Zugriff jedoch mittels mobilen Geräten oder automatisiert aus anderen Diensten, ist es meist eine schlechte Idee, das Passwort großflächig zu verteilen. Mobile Geräte haben manchmal die Eigenschaft verloren zu gehen oder gestohlen zu werden.
Weiterhin werden Dienste oder Geräte kompromitert oder unterstützen vielfach keine Zweifaktor-Authentisierung (2FA).
Hierbei hilft die
Generierung eines Personal Access Token kurz &ldquo;<code>PAT</code>&rdquo;.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Soll auf Daten in einer Nextcloud zugegriffen werden, erfolgt dies häufig mittels der Kombination aus <code>Username</code> und <code>Passwort</code>.
Erfolg dieser Zugriff jedoch mittels mobilen Geräten oder automatisiert aus anderen Diensten, ist es meist eine schlechte Idee, das Passwort großflächig zu verteilen. Mobile Geräte haben manchmal die Eigenschaft verloren zu gehen oder gestohlen zu werden.
Weiterhin werden Dienste oder Geräte kompromitert oder unterstützen vielfach keine Zweifaktor-Authentisierung (2FA).
Hierbei hilft die
Generierung eines Personal Access Token kurz &ldquo;<code>PAT</code>&rdquo;.</p>
<p>Ein <code>PAT</code> hingegen sollte nur genau einem Dienst oder einem Gerät zugeordnet werden.
Das jeweilige <code>PAT</code> und kann dann bei Verlust oder Kompromittierung über den Hauptzugang gesperrt werden, so dass der Zugriff feingranular entzogen werden kann.
Vorteil: Das &ldquo;Hauptpasswort&rdquo; und die anderen <code>PAT</code> müssen nicht geändert werden und können bestehen bleiben.</p>

<h2 id="erstellung" data-numberify>Erstellung<a class="anchor ms-1" href="#erstellung"></a></h2>

<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Hinweis
    </p>
    <p>Zum Zeitpunkt des Artikel ist die Nextcloud-Version 31 aktuell.</p>
</blockquote>
<p>Zur Erstellung eines <code>PAT</code> melde dich als ersten Schritt in Deiner Nextcloud an.</p>
<p>Rechts oben sollten Deine Initialen oder Dein Profilbild stehen, dort bitte drauf klicken und es erscheint ein weiteres Pull-Down-Menü, wo du auf <code>Einstellungen</code> klickst.</p>
<p><picture><img class="img-fluid " alt="Einstellungen" src="https://www.rainerrose.de/images/posts/nextcloud-pat-erstellen/01-einstellungen.png?v=dd3ee05e268688d07868de57e9488d74" loading="lazy" width="400" height="384" />
</picture>

</p>
<p>Danach gibt es eine Übersicht von Deinem Profil und links eine Menüstruktur.</p>
<p>Mit Klick auf den Menüpunkt <code>Sicherheit</code> geht es jetzt weiter.
Unterhalb des Abschnittes, findest Du schon eine Auflistung der schon aktiven Zugriffe unterhalb von <code>Geräte &amp; Sitzungen</code>.</p>
<p><picture><img class="img-fluid " alt="Sicherheit" src="https://www.rainerrose.de/images/posts/nextcloud-pat-erstellen/02-sicherheit.png?v=f48c07b99bcfad2d35881e666c8b4ab5" loading="lazy" width="836" height="846" />
</picture>

</p>
<p>Am Ende der Tabelle (sofern sie existiert) gibt es eine Eingabemaske mit <code>App-Name</code> und dem Button &ldquo;Neues App-Passwort erstellen&rdquo;.</p>
<p>Trage hier bitte einen sinnvollen Namen ein, so dass Du das erstellte <code>PAT</code> später wieder zuordnen kannst.
Ein sprechender Name erleichtert Dir später die Suche, falls Du einem Gerät oder Dienst den Zugriff entziehen möchtest.</p>
<p><picture><img class="img-fluid " alt="PAT" src="https://www.rainerrose.de/images/posts/nextcloud-pat-erstellen/03-pat.png?v=00e25fcae9fcb172f39da9a4bf45d5c0" loading="lazy" width="431" height="312" />
</picture>

</p>

<blockquote class="alert alert-primary" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-exclamation-circle me-2"></i>Wichtig
    </p>
    <p>Wenn Du auf das kleine Fenster wieder schließt, wird der Dialog <strong>geschlossen</strong>. Es gibt <strong>keine</strong> Möglichkeit, das Passwort nochmal anzuzeigen. Daher ist es wichtig, sich das Passwort an einem sicheren Ort zu notieren.
Hast Du dies vergessen, musst Du ein neues <code>PAT</code> anlegen.</p>
</blockquote>
<p>Das generierte <code>PAT</code> ist im Grunde nichts anderes ist, als ein extra Passwort.
Notiere Dir jetzt das generierte Passwort an einer sicheren Stelle, übertrage es z.B. in Deinen Passwort-Safe.
Das Passwort ist <strong>sofort aktiv</strong> und kann verwendet werden. Der gewohnte Benutzername bliebt hierbei gleich.</p>
<p>Wenn Du z.B. den Nextcloud-APP auf deinem Smartphone einrichten möchtest, hast Du jetzt die Chance mit Klick auf <code>QR-Code für mobile Apps anzeigen</code>
Dir einen QR-Code anzeigen zu lassen, den Du mit der Nextcloup-App abscannen kannst und die Tipparbeit und Tippfehler ersparst.</p>
<p><picture><img class="img-fluid " alt="PAT QR-Code" src="https://www.rainerrose.de/images/posts/nextcloud-pat-erstellen/03-pat-qr.png?v=94fb3686146adc3dd4cb7a809719890f" loading="lazy" width="404" height="466" />
</picture>

</p>
<p>Ist das Passwort in der Anwendung hinterlegt, kann der Dialog mit Klick auf das kleine <code>X</code>
geschlossen werden.</p>

<h2 id="löschen" data-numberify>Löschen<a class="anchor ms-1" href="#löschen"></a></h2>
<p>Das Löschen eines <code>PAT</code> wird auf der selben Seite in den Profil-Einstellungen vorgenommen werden.
Über die drei Punkte bekommst Du ein Pulldown-Menü.</p>
<p><picture><img class="img-fluid " alt="PAT löschen" src="https://www.rainerrose.de/images/posts/nextcloud-pat-erstellen/03-pat-loeschen.png?v=870b786167d2329178ac82a03929752c" loading="lazy" width="762" height="219" />
</picture>

</p>
<p>Hier kannst Du das <code>PAT</code> Widerrufen. Sofern der Client dies Unterstützt (z.B. die Smartphone-App) können via Klick auf
<code>Gerät löschen</code>
sogar die lokal vorgehaltenen Daten gelöscht werden.</p>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/images/posts/nextcloud-pat-erstellen/03-pat.png" length="34145" type="image/.png" />
    </item>
    
    <item>
      <title>Starship - Ansible Rolle</title>
      <link>https://www.rainerrose.de/posts/starship-rolle/</link>
      <pubDate>Fri, 27 Dec 2024 15:11:01 +0100</pubDate>
      <guid>https://www.rainerrose.de/posts/starship-rolle/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Das Tool <code>starship</code> hatte ich schon mal in einem <a href="/docs/howto/starship/">Blog-Artikel</a> erwähnt und genauer beschrieben:</p>
<blockquote>
<p>Der Standard-Prompt ist zu langweilig? Die Befüllung der Variablen PS1 ist zu kompliziert und unflexibel? Es sollen mehr Informationen dargestellt werden?</p>
<p>Kein Problem, ich habe jetzt das Tool <code>Starship</code> für mich entdeckt&hellip;</p>
</blockquote>
<p>Vor einiger Zeit habe ich die Installation des Tool inklusive mit meiner Konfiguration in eine Ansible-Rolle gegossen.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Das Tool <code>starship</code> hatte ich schon mal in einem <a href="/docs/howto/starship/">Blog-Artikel</a> erwähnt und genauer beschrieben:</p>
<blockquote>
<p>Der Standard-Prompt ist zu langweilig? Die Befüllung der Variablen PS1 ist zu kompliziert und unflexibel? Es sollen mehr Informationen dargestellt werden?</p>
<p>Kein Problem, ich habe jetzt das Tool <code>Starship</code> für mich entdeckt&hellip;</p>
</blockquote>
<p>Vor einiger Zeit habe ich die Installation des Tool inklusive mit meiner Konfiguration in eine Ansible-Rolle gegossen.</p>

<h2 id="die-rolle" data-numberify>Die Rolle<a class="anchor ms-1" href="#die-rolle"></a></h2>
<p>Diese Ansible-Rolle findet sich auf meinem persönlichen gitlab-Account im Unterordner <a href="https://tools.rainerrose.de/redirect.php?id=45" target="_blank" rel="noopener noreferrer">ansible-roles<i class="fas fa-external-link-square-alt ms-1"></i></a> des Projektes.</p>
<p>Die Rolle muss nur in das <code>roles</code>-Verzeichnis der jeweiligen Ansible-Playbooks kopiert werden.
Danach kann sie in einem Playbook verwendet werden, wobei mindestens die Namen der Accounts angegeben müssen, wo die Rolle installiert werden soll. Ein Beispiel-Playbook findet sich in der Datei <code>README.md</code> des <a href="https://tools.rainerrose.de/redirect.php?id=45" target="_blank" rel="noopener noreferrer">Unterordners<i class="fas fa-external-link-square-alt ms-1"></i></a> der Rolle.</p>

<h2 id="rollen-variable-starship_user" data-numberify>Rollen-Variable: starship_user<a class="anchor ms-1" href="#rollen-variable-starship_user"></a></h2>
<p>Die Namen der Accounts, wo die Rolle installiert werden soll, müssen als Liste angegeben werden.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">starship_user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span>- <span class="l">user1</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="l">user2</span><span class="w">
</span></span></span></code></pre></div><p>Die Beschreibung der Rollen-Variablen findet sich aber ebenfalls noch einmal in der Datei <code>README.md</code> inklusive weiterer Erklärungen.</p>

<h2 id="schwierigkeiten-timeout" data-numberify>Schwierigkeiten: Timeout<a class="anchor ms-1" href="#schwierigkeiten-timeout"></a></h2>
<p>Allerdings stieß ich auf die Schwierigkeit bei einem meiner Rechner, dass das <code>git</code>-Kommando mehr als die vordefinierten 500 Millisekunden brauchte. <br>
Daher habe ich die Variable <code>starship_extra_options</code> eingeführt, wo ich das <code>command_timeout</code> höher setzen konnte:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">starship_extra_options</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;command_timeout = 1000&#39;</span><span class="w">
</span></span></span></code></pre></div><p>Theoretisch lassen sich da noch mehr individuelle Optionen setzen, sofern benötigt.</p>
<p>Beispiel:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">starship_extra_options</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="sd">  command_timeout = 1000
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="sd">  
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="sd">  [container]
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="sd">  format = &#39;[$symbol \[$name\]]($style) &#39;</span><span class="w">
</span></span></span></code></pre></div>
<h2 id="minimales-playbook" data-numberify>Minimales Playbook<a class="anchor ms-1" href="#minimales-playbook"></a></h2>
<p>Ein minimales Playbook sieht beispielsweise so aus:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup starship</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l">linuxhosts</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span><span class="nt">roles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span>- <span class="l">starship</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">  </span><span class="nt">vars</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="w">    </span><span class="nt">starship_user</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="w">      </span>- <span class="l">user1</span><span class="w">
</span></span></span></code></pre></div><p>Vorbei sich die Variable <code>starship_user</code> natürlich noch als Host- oder Gruppenvariable definieren lässt, womit das Playbook noch kleiner wird.</p>
<p>Der alte
<a href="/docs/howto/starship/">Blog-Artikel</a>
findet sich <a href="/docs/howto/starship/">hier</a>, dort wird das Tool noch einmal etwas genauer erklärt.</p>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/wallpaper/docs/linux/starship-screenshot.png" length="27725" type="image/.png" />
    </item>
    
    <item>
      <title>Starship - Shell Prompt mit mehr Infos</title>
      <link>https://www.rainerrose.de/docs/howto/starship/</link>
      <pubDate>Sun, 28 Jul 2024 14:45:16 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/starship/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Der Standard-Prompt ist zu langweilig? Die Befüllung der Variablen <code>PS1</code> ist zu kompliziert und unflexibel? Es sollen mehr Informationen dargestellt werden?</p>
<p>Kein Problem, ich habe jetzt das Tool <code>Starship</code> für mich entdeckt. Es ist schnell installiert und auch recht simpel konfiguriert. Wie das funktioniert, habe ich im Nachfolgenden mal aufgeschrieben.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Der Standard-Prompt ist zu langweilig? Die Befüllung der Variablen <code>PS1</code> ist zu kompliziert und unflexibel? Es sollen mehr Informationen dargestellt werden?</p>
<p>Kein Problem, ich habe jetzt das Tool <code>Starship</code> für mich entdeckt. Es ist schnell installiert und auch recht simpel konfiguriert. Wie das funktioniert, habe ich im Nachfolgenden mal aufgeschrieben.</p>

<h2 id="alte-welt-ps1-variable" data-numberify>Alte Welt: PS1-Variable<a class="anchor ms-1" href="#alte-welt-ps1-variable"></a></h2>
<p>Meine <code>PS1</code>-Variable, die ich in der <code>~./.bashrc</code> setze, sah bisher immer so aus:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nv">PS1</span><span class="o">=</span><span class="s1">&#39;\[\033[01;32m\][\u@\h\[\033[01;37m\]:\w\[\033[01;32m\]]\$\[\033[00m\] &#39;</span>
</span></span></code></pre></div><p>Das sieht dann z.B. so aus:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mag@rainern:~/homepages/rainerrose.de
</span></span></code></pre></div>
<h2 id="das-ziel" data-numberify>Das Ziel<a class="anchor ms-1" href="#das-ziel"></a></h2>
<p><code>Starship</code> bietet die Möglichkeit noch weitere Informationen einblenden zu lassen, z.B.:</p>
<ul>
<li>aktueller Name des git-Branches</li>
<li>virtuelles Enviroment unter Python</li>
<li>aktueller Name des Openstack-Projekt</li>
</ul>
<p>Im folgenden Screenshot sieht man die potentiellen Möglichkeiten.</p>
<p><picture><img class="img-fluid " alt="Screenshot eines bunten Prommpts" src="https://www.rainerrose.de/wallpaper/docs/linux/starship-screenshot.png?v=6961075f81e888ea9f3039d26da236b7" loading="lazy" width="1647" height="116" />
</picture>

</p>
<p>Aus Gründen musste ich ein paar Informationen blurren.</p>

<h2 id="installation" data-numberify>Installation<a class="anchor ms-1" href="#installation"></a></h2>

<h3 id="schritt-1-binary-installieren" data-numberify>Schritt 1: Binary installieren<a class="anchor ms-1" href="#schritt-1-binary-installieren"></a></h3>
<p>Die Installation ist recht einfach und gut auf der <a href="https://tools.rainerrose.de/redirect.php?id=39" target="_blank" rel="noopener noreferrer">Projekt-Homepage<i class="fas fa-external-link-square-alt ms-1"></i></a> beschrieben.</p>

<h3 id="schritt-2-aktivieren" data-numberify>Schritt 2: Aktivieren<a class="anchor ms-1" href="#schritt-2-aktivieren"></a></h3>
<p>Je nach gewählter Shell, muss Starship jetzt noch gestartet werden.</p>
<p>Für die <code>bash</code>, die ich verwendet ist dies ein</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;eval &#34;$(starship init bash)&#34;&#39;</span> &gt;&gt; ~/.bashrc
</span></span></code></pre></div><p>Alle anderen Shells sind gut auf der
<a href="https://tools.rainerrose.de/redirect.php?id=40" target="_blank" rel="noopener noreferrer">Projekt-Webseite<i class="fas fa-external-link-square-alt ms-1"></i></a>
beschrieben.</p>

<h2 id="konfiguration" data-numberify>Konfiguration<a class="anchor ms-1" href="#konfiguration"></a></h2>
<p>Zu erst erstellst Du Dir am besten eine leere Konfigurationsdatei.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir -p ~/.config <span class="o">&amp;&amp;</span> touch ~/.config/starship.toml
</span></span></code></pre></div><p>danach kannst Du meine Konfigurationsdatei weiter unten auf dieser Seite als Vorlage nehmen und sie anpassen.</p>
<p>Auf der <a href="https://tools.rainerrose.de/redirect.php?id=41" target="_blank" rel="noopener noreferrer">Projekt-Webseite<i class="fas fa-external-link-square-alt ms-1"></i></a>
sind es weitere, gute Beispiele aufgeführt.
In der Regel kannst Du die Beispiele aus der Doku 1:1 in Deine Konfigurationsdatei übernehmen und musst sie ggf. nur etwas anpassen.</p>

<h2 id="nacharbeiten" data-numberify>Nacharbeiten<a class="anchor ms-1" href="#nacharbeiten"></a></h2>
<p>Auf meinen <code>Manjaro</code>-Systemen, welche ja auf <code>Arch Linux</code> basieren, musste ich das Fontspaket <code>extra/noto-fonts-emoji</code> nachinstallieren, weil sonst einige Glyphen nicht dargestellt werden konnten.</p>
<p>Um zu testen, ob es Probleme mit einigen Symbolen bzw. Emojis gibt, reichen folgende zwei Befehle:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> -e <span class="s2">&#34;\xf0\x9f\x90\x8d&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">echo</span> -e <span class="s2">&#34;\xee\x82\xa0&#34;</span>
</span></span></code></pre></div><p>Das erste Symbol sollte eine Schlange darstellen, das zweite ein Strich mit einem Pfeil der daneben läuft.</p>
<p>In der
<a href="https://tools.rainerrose.de/redirect.php?id=64" target="_blank" rel="noopener noreferrer">FAQ auf der Projekt-Seite<i class="fas fa-external-link-square-alt ms-1"></i></a>
steht es nochmal genauer.</p>

<h2 id="fertig" data-numberify>Fertig<a class="anchor ms-1" href="#fertig"></a></h2>
<p>Beim nächsten Start einer <code>bash</code> oder das Sourcen der <code>~/.bashrc</code> sollte Dir den Prompt im neuen Gewand präsentieren.</p>

<h2 id="dank" data-numberify>Dank<a class="anchor ms-1" href="#dank"></a></h2>
<p>Mein Dank geht an Kevin für die Tipps und den Hinweis auf dieses Tool.</p>

<h2 id="meine-konfig-datei" data-numberify>Meine Konfig-Datei<a class="anchor ms-1" href="#meine-konfig-datei"></a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># https://starship.rs/config/</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c"># Get editor completions based on the config schema</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">&#34;$schema&#34;</span> <span class="p">=</span> <span class="s1">&#39;https://starship.rs/config-schema.json&#39;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c"># Inserts a blank line between shell prompts</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">add_newline</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">[</span><span class="nx">directory</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">truncate_to_repo</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">truncation_length</span> <span class="p">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">[</span><span class="nx">git_metrics</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">added_style</span> <span class="p">=</span> <span class="s1">&#39;bold blue&#39;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">format</span> <span class="p">=</span> <span class="s1">&#39;[+$added]($added_style)/[-$deleted]($deleted_style) &#39;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">[</span><span class="nx">git_status</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nx">conflicted</span> <span class="p">=</span> <span class="s1">&#39;🏳&#39;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nx">ahead</span> <span class="p">=</span> <span class="s1">&#39;🏎💨&#39;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nx">behind</span> <span class="p">=</span> <span class="s1">&#39;😰&#39;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nx">diverged</span> <span class="p">=</span> <span class="s1">&#39;😵&#39;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nx">up_to_date</span> <span class="p">=</span> <span class="s1">&#39;✓&#39;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nx">untracked</span> <span class="p">=</span> <span class="s1">&#39;🤷&#39;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nx">stashed</span> <span class="p">=</span> <span class="s1">&#39;📦&#39;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nx">modified</span> <span class="p">=</span> <span class="s1">&#39;📝&#39;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nx">staged</span> <span class="p">=</span> <span class="s1">&#39;[++\($count\)](green)&#39;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nx">renamed</span> <span class="p">=</span> <span class="s1">&#39;👅&#39;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="nx">deleted</span> <span class="p">=</span> <span class="s1">&#39;🗑&#39;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="p">[</span><span class="nx">openstack</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="nx">format</span> <span class="p">=</span> <span class="s1">&#39;on [$symbol$cloud(\($project\))]($style) &#39;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="nx">style</span> <span class="p">=</span> <span class="s1">&#39;bold yellow&#39;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="nx">symbol</span> <span class="p">=</span> <span class="s1">&#39;☁ &#39;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="p">[</span><span class="nx">hostname</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="nx">ssh_only</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="nx">format</span> <span class="p">=</span> <span class="s1">&#39;[$ssh_symbol](bold blue)[$hostname](bold red):&#39;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="nx">trim_at</span> <span class="p">=</span> <span class="s1">&#39;.companyname.com&#39;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="nx">disabled</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="p">[</span><span class="nx">username</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="nx">style_user</span> <span class="p">=</span> <span class="s1">&#39;white bold&#39;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="nx">style_root</span> <span class="p">=</span> <span class="s1">&#39;black bold&#39;</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="nx">format</span> <span class="p">=</span> <span class="s1">&#39;[$user]($style)@&#39;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="nx">disabled</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="nx">show_always</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="nx">aliases</span> <span class="p">=</span> <span class="p">{</span> <span class="s2">&#34;corpuser034g&#34;</span> <span class="p">=</span> <span class="s2">&#34;matchai&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="p">[</span><span class="nx">kubernetes</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="nx">disabled</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="nx">format</span> <span class="p">=</span> <span class="s1">&#39;[$symbol$context( \($namespace\))]($style) &#39;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="p">[[</span><span class="nx">kubernetes</span><span class="p">.</span><span class="nx">contexts</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="nx">context_pattern</span> <span class="p">=</span> <span class="s2">&#34;.*_(customers|infrastructure)_(?P&lt;cluster&gt;[\\w-]*)_(tooling|bootstrap).*&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="nx">context_alias</span> <span class="p">=</span> <span class="s2">&#34;$cluster&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="p">[</span><span class="nx">env_var</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="nx">style</span> <span class="p">=</span> <span class="s1">&#39;red bold&#39;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="nx">variable</span> <span class="p">=</span> <span class="s1">&#39;KUBECONFIG&#39;</span>
</span></span></code></pre></div>
<h2 id="ansible-rolle" data-numberify>Ansible-Rolle<a class="anchor ms-1" href="#ansible-rolle"></a></h2>
<p>Update: 27.12.2024</p>
<p>Inzwischen habe ich für die Installation eine Ansible-Rolle geschrieben. Den <a href="/posts/starship-rolle/">dazugehörigen Blog-Artikel</a> findet Du <a href="/posts/starship-rolle/">hier</a>.</p>

<h2 id="kubernetes-context-lange-namen" data-numberify>Kubernetes Context (lange Namen)<a class="anchor ms-1" href="#kubernetes-context-lange-namen"></a></h2>
<p>Update 29.03.2026</p>
<p>Aktuell arbeite ich mit längeren Namen im <code>context</code> was zu sehr langen Zeilen führt, wenn man folgende Format-Syntax wählt:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">format</span> <span class="p">=</span> <span class="s1">&#39;on [☸ $user on $context](dimmed green) &#39;</span>
</span></span></code></pre></div><p>Weiterhin habe ich <code>starship</code> auf die Version <code>1.24.2</code> aktualisiert, dort kann Pattern-Matching verwendet werden.</p>
<p>Ich habe also oben im Abschnitt <code>kubernetes</code> das Format geändert und die beiden Abschnitte <code>kubernetes.contexts</code> und <code>env_var</code> hinzugefügt.</p>
<p>Über den <code>context_alias</code> kann ich nun alles für mich nicht relevante wegschneiden. Im Grunde wird mir jetzt nur noch der Name des Clusters angezeigt.</p>
<p>Beispiele:</p>
<ul>
<li><code>cluster-api</code></li>
<li><code>foo</code></li>
<li><code>bar</code></li>
</ul>

<h2 id="changelog" data-numberify>Changelog<a class="anchor ms-1" href="#changelog"></a></h2>
<table>
  <thead>
      <tr>
          <th>Datum</th>
          <th>Änderung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>25.10.2024</td>
          <td>Abschnitt kubernetes in der Konfig hinzugefügt. Synatx-Highlighting gefixed.</td>
      </tr>
      <tr>
          <td>27.12.2024</td>
          <td>Erwähnung der Ansible-Rolle <code>starship</code>.</td>
      </tr>
      <tr>
          <td>29.03.2026</td>
          <td>Kubernetes Context. Änderungen vom Format. Typos gefixed.</td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/wallpaper/docs/linux/starship-screenshot.png" length="27725" type="image/.png" />
    </item>
    
    <item>
      <title>CACHEDIR.TAG</title>
      <link>https://www.rainerrose.de/posts/cachedirtag/</link>
      <pubDate>Sat, 01 Jun 2024 15:50:36 +0200</pubDate>
      <guid>https://www.rainerrose.de/posts/cachedirtag/</guid>
      <description><![CDATA[<!-- Anreißer --> 
<p>Bei einem dateibasierten Backup wollte ich nicht immer alle Dateien und Unterverzeichnisse in die Sicherung mit aufnehmen.</p>
<p>Viele Tool wie <code>restic</code>, <code>backup</code> oder auch <code>tar</code> unterstützen eine externe Datei, wo diese Ausnahmen gepflegt werden können.</p>
<p>Allerdings vergesse ich manchmal diese Datei zu pflegen oder die Syntax ist etwas umständlich.</p>
<p>Manchmal möchte man auch <em>nur-mal-eben-schnell</em> <sup>(tm)</sup> ein Verzeichnis ausschließen.</p>
<p>Dafür gibt es die Möglichkeit über eine Datei namens <code>CACHEDIR.TAG</code> zu nutzen.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer --> 
<p>Bei einem dateibasierten Backup wollte ich nicht immer alle Dateien und Unterverzeichnisse in die Sicherung mit aufnehmen.</p>
<p>Viele Tool wie <code>restic</code>, <code>backup</code> oder auch <code>tar</code> unterstützen eine externe Datei, wo diese Ausnahmen gepflegt werden können.</p>
<p>Allerdings vergesse ich manchmal diese Datei zu pflegen oder die Syntax ist etwas umständlich.</p>
<p>Manchmal möchte man auch <em>nur-mal-eben-schnell</em> <sup>(tm)</sup> ein Verzeichnis ausschließen.</p>
<p>Dafür gibt es die Möglichkeit über eine Datei namens <code>CACHEDIR.TAG</code> zu nutzen.</p>

<h2 id="voraussetzungen" data-numberify>Voraussetzungen<a class="anchor ms-1" href="#voraussetzungen"></a></h2>
<p>Diese Datei muss nur zwei Voraussetzungen erfüllen:</p>
<ol>
<li>Der Name der Datei muss <code>CACHEDIR.TAG</code> lauten und zwar in genau der Schreibweise.</li>
<li>Der Inhalt der Datei muss einen bestimmten Inhalt haben.</li>
</ol>

<h2 id="name-der-datei" data-numberify>Name der Datei<a class="anchor ms-1" href="#name-der-datei"></a></h2>
<p>Der Name der Datei muss genau <code>CACHEDIR.TAG</code> lauten; hierbei ist auch auf Groß- und Kleinschreibung zu achten.</p>

<h2 id="inhalt-der-datei" data-numberify>Inhalt der Datei<a class="anchor ms-1" href="#inhalt-der-datei"></a></h2>
<p>Am Anfang der Datei muss ein bestimmter Inhalt stehen, er lautet:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">Signature: 8a477f597d28d172789f06886806bc55
</span></span></code></pre></div><p>So eine Datei kann auch über ein Shell erstellt werden, z.B. so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Signature: 8a477f597d28d172789f06886806bc55&#34;</span> &gt; CACHEDIR.TAG
</span></span></code></pre></div><p>Wichtig hierbei sind nur die ersten 43 Octets der Datei. Danach können noch weitere Informationen kommen, wie z.B. eine kleine Notiz:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">Signature: 8a477f597d28d172789f06886806bc55
</span></span><span class="line"><span class="ln">2</span><span class="cl"># Diese Datei bedeutet, dass dieses Unterverzeichnis und seine Inhalte vom Backup ausgeschlossen werden.
</span></span></code></pre></div><p>Aber auch hier muss auf die Groß- und Kleinschreibung beim Inhalt der Datei geachtet werden.
Alternativ kann man diese Datei auch nutzen, um eine solche Datei mit Ausnahmen zu erstellen.
Diese Datei lässt sich dann z.B. mit dem Programm <code>tar</code> auswerten, um ein Archiv zu erstellen, was diese Verzeichnisse auslässt.</p>
<p>Beispiel:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">find . -name CACHEDIR.TAG <span class="p">|</span> sed -e <span class="s1">&#39;s/[/]CACHEDIR.TAG-//g&#39;</span> &gt;/tmp/excludes
</span></span><span class="line"><span class="ln">2</span><span class="cl"> tar cvf /tmp/backup.tar --exclude-from/tmp/excludes .
</span></span></code></pre></div><p>Diese Tagging-Konvention lässt sich somit auch für weitere Tools benutzen, auch wenn diese die Konvention noch nicht direkt unterstützen. Definitionen von Ausnahme-Listen unterstützen meist viele Programme und über diesen kleinen Umweg, lässt sich die Tagging-Technik schon jetzt meist ohne Änderung der Programme nutzen.</p>
<p>Die Tools <code>restic</code> und <code>borg</code> können mit dem Parameter <code>--exclude-caches</code> angewiesen werden, die Datei <code>CACHEDIR.TAG</code> zu berücksichtigen. Hier werden dann automatisch die Verzeichnisse mit ihren Unterinhalten vom Backup ausgenommen, die eine solche Datei enthalten.</p>
<p>Beispiel:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">restic -r /backup/backup_home_dirs backup --exclude-caches /home/
</span></span></code></pre></div><p>Auch Verzeichnisse wie <code>/var/cache</code> lassen sich damit elegant aus dem Backup aussparen.</p>
<p>Ich bin sehr froh dieses entdeckt zu haben, da ich jetzt nicht mehr für jede Ausnahme pflegen muss und dann wieder das Backup-Script irgendwo einchecken oder auf irgendwelche Server verteilen muss. Hier reicht jetzt das triviale Anlegen einer Datei in dem gewünschten Verzeichnis.</p>

<h2 id="credits" data-numberify>Credits<a class="anchor ms-1" href="#credits"></a></h2>
<p>Die ursprüngliche Idee bzw. Vorschlag stammt von Bryan Ford und kann <a href="https://tools.rainerrose.de/redirect.php?id=42" target="_blank" rel="noopener noreferrer">hier nachgelesen<i class="fas fa-external-link-square-alt ms-1"></i></a>  werden.</p>]]></content:encoded>
    </item>
    
    <item>
      <title>SSH-Tricks: Hold on</title>
      <link>https://www.rainerrose.de/posts/ssh-tricks_hold_on/</link>
      <pubDate>Thu, 08 Feb 2024 18:15:02 +0100</pubDate>
      <guid>https://www.rainerrose.de/posts/ssh-tricks_hold_on/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Letztens habe ich ein weiteren Kniff gelernt, der in Zusammenhang mit <code>ssh</code>-Verbindungen hilfreich ist.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Letztens habe ich ein weiteren Kniff gelernt, der in Zusammenhang mit <code>ssh</code>-Verbindungen hilfreich ist.</p>
<p>Führt man mehrere <code>ssh</code>-Kommandos kurz hintereinander aus, muss für jede Verbindung ein erneuter Kontakt zum jeweiligen Server aufgebaut werden.
Das sind zwar immer (je nach Verbindung und Schnelligkeit des Servers) vielleicht nur ein paar Millisekunden, aber zum Beispiel in Zusammenhang mit <code>Ansible</code>-Playbooks kann sich das schon mal addieren und den Lauf durchaus in die Länge ziehen. Zudem ist es nicht sehr ressourcenschonend.</p>
<p>In der <code>ssh</code>-Konfiguration des aktuellen Benutzers
(<code>~/.ssh/config</code>) kann jedoch eine Option gesetzt werden, die ein Socket-File aufmacht und somit die Verbindung offen hält. Mit einem gesetzten Timeout von zum Beispiel 30 Minuten, muss nun nicht mehr jedes Mal eine neue Verbindung initiiert werden. Damit entfällt das ganze Geraffel mit Handshake etc.</p>
<p><code>Ansible</code>-Playbooks laufen jetzt bei mir <strong>wesentlich</strong> schneller!</p>

<h2 id="konfiguration" data-numberify>Konfiguration<a class="anchor ms-1" href="#konfiguration"></a></h2>
<p>Konfiguriert wird dieses Verhalten in der <code>ssh</code>-Konfiguration des jeweiligen Benutzers.</p>
<p>Der entsprechende Ausschnitt aus meiner <code>~/.ssh/config</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">Host *
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ControlPath ~/.ssh/connections/%C
</span></span><span class="line"><span class="ln">3</span><span class="cl">  ControlMaster auto
</span></span><span class="line"><span class="ln">4</span><span class="cl">  ControlPersist 30m
</span></span></code></pre></div><p>Eine paar kleine Erläuterungen und Hinweise zur obigen Datei:</p>

<h3 id="parameter-host" data-numberify>Parameter Host<a class="anchor ms-1" href="#parameter-host"></a></h3>
<p>Das Asterik (<code>*</code>) hinter <code>Host</code> sagt, dass es für jeden Host gilt.</p>

<h3 id="parameter-controlpath" data-numberify>Parameter ControlPath<a class="anchor ms-1" href="#parameter-controlpath"></a></h3>
<p>Das genannte Verzeichnis im <code>ControlPath</code> muss <strong>vorher</strong> angelegt werden:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir ~/.ssh/connections
</span></span></code></pre></div><p>Sofern das Unterverzeichnis <code>~/.ssh</code> noch nicht existiert, muss es mit den richtigen Rechten angelegt werden.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir ~/.ssh
</span></span><span class="line"><span class="ln">2</span><span class="cl">chmod <span class="m">700</span> ~/.ssh
</span></span></code></pre></div>
<h3 id="parameter-controlmaster" data-numberify>Parameter ControlMaster<a class="anchor ms-1" href="#parameter-controlmaster"></a></h3>
<p>Dieser Parameter aktiviert erst die gemeinsame Benutzung von mehreren Sitzungen über eine einzelne Netzwerkverbindung. Erst damit wird diese Funktionalität nutzbar bzw. aktiviert.</p>

<h3 id="parameter-controlpersist" data-numberify>Parameter ControlPersist<a class="anchor ms-1" href="#parameter-controlpersist"></a></h3>
<p>Hier wird die Haltedauer der Verbindung angegeben. In meinem Beispiel 30 Minuten.  Nach Ablauf der definierten Zeitspanne werden die Sockets automatisch gelöscht.</p>

<h2 id="tipps" data-numberify>Tipps<a class="anchor ms-1" href="#tipps"></a></h2>

<h3 id="problem-lösungen" data-numberify>Problem-Lösungen<a class="anchor ms-1" href="#problem-lösungen"></a></h3>
<p>Ändern sich die Hostkeys des Servers einmal, kann das Socket-File einfach gelöscht werden.
Dies passiert zum Beispiel in der Entwicklung-Phase wenn neue VMs neu deployed werden müssen und sich dann der <code>ssh</code>-Hostkey ändert. <code>ssh</code> wertet dies beim erneuten Verbindungsversuch als massives Problem und verweigert den Verbindungsaufbau. In so einem Fall, kann das entsprechende Socket einfach gelöscht werden, statt 30 Minuten zu warten.</p>

<h3 id="warum-c-" data-numberify>Warum %C ?<a class="anchor ms-1" href="#warum-c-"></a></h3>
<p>Ja, es gibt noch andere Parameter, wo der Dateinamen dann unter anderem dem Hostnamen entspricht. Dies würde das gezielte Löschen einzelner Sockets im oben beschrieben Fall vereinfachen.</p>
<p>Allerdings hat die Pfad-Länge von <code>ControlPath</code> eine maximal definierte Länge (den genauen Wert habe ich leider vergessen).
Der Parameter <code>%C</code> macht aus der Pfadangabe einen Hash-Wert, der dieses Problem umgeht.</p>

<h3 id="vpn-und-das-controlpersist" data-numberify>VPN und das ControlPersist<a class="anchor ms-1" href="#vpn-und-das-controlpersist"></a></h3>
<p>Ich muss des öfteren mal VPN-Verbindungen wechseln. Leider hat dies (bisher) den unschönen Nebeneffekt gehabt, dass das Socket noch bestehen bleibt.
Da ich mit jedem neuen Verbindungsaufbau allerdings eine neue Quell-IP etc. bekomme, komme ich nun nicht mehr auf das System. Nun muss ich das entsprechende Socket-File löschen.
Da ich ja Hash-Werte benutze, ist das nicht so einfach, zudem ist es eine händische Arbeit; mag ich nicht.</p>
<p>Da ich faul bin, habe ich nach einer Lösung gesucht und gefunden.</p>
<p>Unterhalb von <code>/etc/NetworkManager/dispatcher.d/</code> habe ich mir folgendes Script hingelegt:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="cp">#!/usr/bin/bash
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="cp"></span><span class="c1"># Connection-Pool aufräumen</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># Ort: /etc/NetworkManager/dispatcher.d</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">find /home/rainerr/.ssh/connections -type f -delete
</span></span></code></pre></div><p>Jetzt wird bei jedem neuen VPN-Verbindungsaufbau einfach alles stumpf weggeworfen. \o/</p>

<h2 id="changelog" data-numberify>Changelog<a class="anchor ms-1" href="#changelog"></a></h2>
<table>
  <thead>
      <tr>
          <th>Datum</th>
          <th>Änderung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>08.02.2024</td>
          <td>neuer Absatz: <em>VPN und das ControlPersist</em></td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    </item>
    
    <item>
      <title>SSH-Tricks: Config-Split</title>
      <link>https://www.rainerrose.de/posts/ssh-tricks_config_split/</link>
      <pubDate>Tue, 23 Jan 2024 17:21:12 +0100</pubDate>
      <guid>https://www.rainerrose.de/posts/ssh-tricks_config_split/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Wenn man wie ich mit mehreren Servern jongiert, wird die <code>ssh</code>-Konfigurationsdatei irgendwann recht voll und unübersichtlich. Mittels eines Paramters, kann ich die Konfigurationsdatei allerdings in unterschiedliche Dateien aufteilen.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Wenn man wie ich mit mehreren Servern jongiert, wird die <code>ssh</code>-Konfigurationsdatei irgendwann recht voll und unübersichtlich. Mittels eines Paramters, kann ich die Konfigurationsdatei allerdings in unterschiedliche Dateien aufteilen.</p>
<p>In der <code>SSH</code>-Konfiguration des aktuellen Benutzers</p>
<p><code>~/.ssh/config</code></p>
<p>kann ich mit dem Parameter <code>Include</code> sagen, wo die anderen Dateien liegen, die ich einbinden möchte:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">Include config.d/*
</span></span></code></pre></div><p>Das genannte Verzeichnis im <code>Include</code>-Parameter muss <strong>vorher</strong> angelegt werden:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir ~/.ssh/connections
</span></span></code></pre></div><p>Sofern das Unterverzeichnis <code>~/.ssh</code> noch nicht existiert muss es mir den richtigen Rechten angelegt werden.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir ~/.ssh
</span></span><span class="line"><span class="ln">2</span><span class="cl">chmod <span class="m">700</span> ~/.ssh
</span></span></code></pre></div><p>In das oben genannte Verzeichnis <code>~/.ssh/connections</code> kann ich jetzt die Hosts strukturiert auslagern.</p>
<p>Mein Unterverzeichnis sieht dann z.B. folgendermaßen aus:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">ls -lR config.d/
</span></span><span class="line"><span class="ln">2</span><span class="cl">config.d/:
</span></span><span class="line"><span class="ln">3</span><span class="cl">insgesamt <span class="m">8</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">-rw-rw-r-- <span class="m">1</span> rainer rainerr <span class="m">239</span> Jan <span class="m">12</span> 17:52 produktionsSysteme
</span></span><span class="line"><span class="ln">5</span><span class="cl">-rw-rw-r-- <span class="m">1</span> rainer rainerr  <span class="m">60</span> Jan <span class="m">12</span> 17:52 testSysteme
</span></span></code></pre></div><p>Die oben genannten Dateien sind genauso aufgebaut wie auch sonst die Datei <code>~/.ssh/config</code>.</p>
<p>Vielleicht bringt es ja auch in Euren Alltag etwas mehr Ordnung.</p>]]></content:encoded>
    </item>
    
    <item>
      <title>Tipps</title>
      <link>https://www.rainerrose.de/docs/howto/mastodon/tipps/</link>
      <pubDate>Sun, 14 Jan 2024 15:37:36 +0100</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/mastodon/tipps/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Kochrezepte, Tipps und Hinweise zu Mastodon als kleine Sammlung.</p>
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Kochrezepte, Tipps und Hinweise zu Mastodon als kleine Sammlung.</p>
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>

<h2 id="lesezeichen" data-numberify>Lesezeichen<a class="anchor ms-1" href="#lesezeichen"></a></h2>
<p>Nutzen! Selbiger Grund wie bei <a href="../posten#hashtags">Hashtags</a> ; sonst wird das Wiederfinden schwer bis unmöglich.</p>

<h2 id="threads-in-einem-stück-lesen-unroll" data-numberify>Threads in einem Stück lesen (unroll)<a class="anchor ms-1" href="#threads-in-einem-stück-lesen-unroll"></a></h2>
<p>Dafür gibt es die schöne Webseite <a href="https://tools.rainerrose.de/redirect.php?id=37" target="_blank" rel="noopener noreferrer">mastoreader.io<i class="fas fa-external-link-square-alt ms-1"></i></a>. Dort kann die URL des Threads als URL-Parameter mitgebenen werden oder nach Besuch der Seite in ein Eingabe-Feld übergeben werden.
Auf der Webseite kann dann der Thread in einem Stück konsumiert werden.</p>
<p>Alternativ erwähnt man das Mastodon-Konto im einem Post <code>@mastoreaderio@mastodon.social</code></p>

<h2 id="changelog" data-numberify>Changelog<a class="anchor ms-1" href="#changelog"></a></h2>
<table>
  <thead>
      <tr>
          <th>Datum</th>
          <th>Änderung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>14.01.2024</td>
          <td>mastoreader.io</td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    </item>
    
    <item>
      <title>Discord-Bot</title>
      <link>https://www.rainerrose.de/posts/discord-bot/</link>
      <pubDate>Thu, 21 Sep 2023 22:28:30 +0200</pubDate>
      <guid>https://www.rainerrose.de/posts/discord-bot/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Ich habe hier einige Backup-Scripte, die auch mal etwas länger laufen, wo ich eine Benachrichtigung haben wollte. Unter anderem muss nach einem Backup-Lauf die externe USB-Festplatte für mein Offsite-Backup wieder abgezogen werden.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Ich habe hier einige Backup-Scripte, die auch mal etwas länger laufen, wo ich eine Benachrichtigung haben wollte. Unter anderem muss nach einem Backup-Lauf die externe USB-Festplatte für mein Offsite-Backup wieder abgezogen werden.</p>
<p>Da ich eh schon einen Discord-Account hatte, bot es sich an, per Webhook mir aus dem jeweiligen Script heraus eine Nachricht zu schicken.</p>

<h2 id="channel-anlegen" data-numberify>Channel anlegen<a class="anchor ms-1" href="#channel-anlegen"></a></h2>
<p>Es bietet sich an, einen extra Channel im Discord dafür anzulegen. Meiner heißt &ldquo;<em>bots</em>&rdquo;.</p>
<p><picture><img class="img-fluid " alt="Kanal des Bots bearbeiten" src="https://www.rainerrose.de/images/posts/discord/01-discord-text-channel-bearbeiten.png?v=cd0c707106a8448cba620e7d19b6c959" loading="lazy" width="470" height="236" />
</picture>

</p>

<h2 id="hook-anlegen" data-numberify>Hook anlegen<a class="anchor ms-1" href="#hook-anlegen"></a></h2>
<p>Um einen Webhook anzulegen, wählt man den gewünschten Channel aus, klickt auf das daneben stehende Rädchen, um den Kanal zu bearbeiten. Im folgenden Menü auf den Unterpunkt &ldquo;<em>Integrationen</em>&rdquo; klicken und als nächstes die Schaltfläche &ldquo;<em>Webhook erstellen</em>&rdquo; anklicken.</p>
<p><picture><img class="img-fluid " alt="Webhook erstellen" src="https://www.rainerrose.de/images/posts/discord/02_discord-webhook-erstellen.png?v=cd0c707106a8448cba620e7d19b6c959" loading="lazy" width="1370" height="371" />
</picture>

</p>
<p>Über den Pfeil, die weiteren Optionen aufklappen:</p>
<p><picture><img class="img-fluid " alt="Webhook bearbeiten" src="https://www.rainerrose.de/images/posts/discord/03_discord-webhook-bearbeiten.png?v=cd0c707106a8448cba620e7d19b6c959" loading="lazy" width="1013" height="201" />
</picture>

</p>
<p>Hier den Hook umbenennen oder doch noch einem anderen Channel zuweisen. Der Klick auf die Schaltfläche &ldquo;<em>WebHook-URL kopieren</em>&rdquo; bringt diesen in die Zwischenablage.</p>
<p><picture><img class="img-fluid " alt="Webhook URL" src="https://www.rainerrose.de/images/posts/discord/04_discord-webhook-url-copy.png?v=cd0c707106a8448cba620e7d19b6c959" loading="lazy" width="1018" height="443" />
</picture>

</p>

<h2 id="scripte" data-numberify>Scripte<a class="anchor ms-1" href="#scripte"></a></h2>
<p>Zuerst kam der Wunsch auf, sich bei Ende des Backup-Scriptes eine Meldung zuschicken zu lasssen, dass es fertig ist.
Entweder kann dann das nächste angeworfen werden oder es kann die externe USB-Festplatte abgekoppelt werden.
Später kam dann der Wunsch auf, dass ich auch bei Fehlern eine Benachrichtigung bekomme.
Weil ich keine große Lust hatte, da ein ordentliches Script mit einer Fall-Unterscheidung zu schreiben, habe ich es der Einfachheit halber dupliziert.</p>

<h3 id="ok-meldung" data-numberify>OK-Meldung<a class="anchor ms-1" href="#ok-meldung"></a></h3>
<p>Die Gut-Meldung hat einen grünen Balken an der Seite und meldet ein &ldquo;Backup fertig&rdquo;.</p>
<p><picture><img class="img-fluid " alt="Webhook Info-Meldung" src="https://www.rainerrose.de/images/posts/discord/05-info-meldung.png?v=cd0c707106a8448cba620e7d19b6c959" loading="lazy" width="700" height="270" />
</picture>

</p>
<p>Im Nachfolgenden Script <code>discord-bot.sh</code> muss die Webhook-URL noch durch den Inhalt der Variablen <code>discord_url</code> ersetzt werden.</p>
<div class="highlight" title="discord-bot.sh"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cp"></span><span class="c1"># Discord Webhook</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nv">THE_SCRIPTNAME</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nv">discord_url</span><span class="o">=</span><span class="s2">&#34;https://discord.com/api/webhooks/958845757975330887/wr21BPYs5jECw2OTAo86crAjGrAvbgaexvCAKqCFCgVDr5IFxQtrm5ON4CRAaBmP&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">generate_post_data<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  cat <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s">{
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s">  &#34;content&#34;: &#34;Backup fertig&#34;,
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s">  &#34;embeds&#34;: [{
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s">    &#34;title&#34;: &#34;Backup Script ist fertig!&#34;,
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s">    &#34;description&#34;: &#34;Script ${1}&#34;,
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s">    &#34;color&#34;: &#34;45973&#34;
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s">  }]
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s">}
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># POST request to Discord Webhook</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">curl -H <span class="s2">&#34;Content-Type: application/json&#34;</span> -X POST -d <span class="s2">&#34;</span><span class="k">$(</span>generate_post_data <span class="si">${</span><span class="nv">THE_SCRIPTNAME</span><span class="si">}</span> <span class="k">)</span><span class="s2">&#34;</span> <span class="nv">$discord_url</span>
</span></span></code></pre></div>
<h3 id="fehlermeldung" data-numberify>Fehlermeldung<a class="anchor ms-1" href="#fehlermeldung"></a></h3>
<p>Wenn ein Fehler passiert, gibt es einen roten Balken an der Seite. Zusätzlich habe ich dem Text einen Zeilenumbruch mit <code>\n</code> verpasst.</p>
<p><picture><img class="img-fluid " alt="Webhook Fehler-Meldung" src="https://www.rainerrose.de/images/posts/discord/05-info-error-meldung.png?v=cd0c707106a8448cba620e7d19b6c959" loading="lazy" width="789" height="249" />
</picture>

</p>
<p>Auch hier im nachfolgenden Script <code>discord-bot-error.sh</code> den Variablen-Inhalt <code>discord_url</code> wieder ersetzen.</p>
<div class="highlight" title="discord-bot-error.sh"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cp"></span><span class="c1"># Discord Webhook</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nv">THE_SCRIPTNAME</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nv">FEHLER_MELDUNG</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">2</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nv">discord_url</span><span class="o">=</span><span class="s2">&#34;https://discord.com/api/webhooks/958845757975330887/wr21BPYs5jECw2OTAo86crAjGrAvbgaexvCAKqCFCgVDr5IFxQtrm5ON4CRAaBmP&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">generate_post_data<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  cat <span class="s">&lt;&lt;EOF
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s">{
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s">  &#34;content&#34;: &#34;Backup Error&#34;,
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s">  &#34;embeds&#34;: [{
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s">    &#34;title&#34;: &#34;Backup Script wirft Fehler!&#34;,
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s">    &#34;description&#34;: &#34;Script ${1}\nIrgendwas ist bei diesem Script schiefgelaufen.&#34;,
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s">    &#34;color&#34;: &#34;15548997&#34;
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s">  }]
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s">}
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># POST request to Discord Webhook</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">curl -H <span class="s2">&#34;Content-Type: application/json&#34;</span> -X POST -d <span class="s2">&#34;</span><span class="k">$(</span>generate_post_data <span class="si">${</span><span class="nv">THE_SCRIPTNAME</span><span class="si">}</span> <span class="si">${</span><span class="nv">FEHLER_MELDUNG</span><span class="si">}</span><span class="k">)</span><span class="s2">&#34;</span> <span class="nv">$discord_url</span>
</span></span></code></pre></div>
<h3 id="die-farbe-der-balken" data-numberify>Die Farbe der Balken<a class="anchor ms-1" href="#die-farbe-der-balken"></a></h3>
<p>Die Farbe der Balken lässt sich ändern. Weiter unten ist eine tabellarische Auflistung.</p>

<h2 id="verwendung-bzw-aufruf-im-backup-script" data-numberify>Verwendung bzw. Aufruf im Backup-Script<a class="anchor ms-1" href="#verwendung-bzw-aufruf-im-backup-script"></a></h2>
<p>Der Aufruf im Backup-Script ist dann simpel, hier das <code>discord-bot.sh</code> in der letzten Zeile aufrufen:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">discord-bot.sh <span class="s2">&#34;</span><span class="si">${</span><span class="nv">0</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>Im Fehlerfall an entsprechender Stelle</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">discord-bot-error.sh <span class="s2">&#34;</span><span class="si">${</span><span class="nv">0</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>Die beiden Bash-Dateien sollte natürlich ausführbar im Suchpfad von <code>$PATH</code> sein.</p>

<h2 id="farben-der-balken" data-numberify>Farben der Balken<a class="anchor ms-1" href="#farben-der-balken"></a></h2>
<p>Die erlaubten Farbwerte hatte ich irgendwo mal in diesem Internet gefunden, leider weiß ich nicht mehr wo.</p>
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th style="text-align: right">Wert bei <code>color</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Aqua</code></td>
          <td style="text-align: right">1752220</td>
      </tr>
      <tr>
          <td><code>Blue</code></td>
          <td style="text-align: right">3447003</td>
      </tr>
      <tr>
          <td><code>DarkAqua</code></td>
          <td style="text-align: right">1146986</td>
      </tr>
      <tr>
          <td><code>DarkBlue</code></td>
          <td style="text-align: right">2123412</td>
      </tr>
      <tr>
          <td><code>DarkerGrey</code></td>
          <td style="text-align: right">8359053</td>
      </tr>
      <tr>
          <td><code>DarkGold</code></td>
          <td style="text-align: right">12745742</td>
      </tr>
      <tr>
          <td><code>DarkGreen</code></td>
          <td style="text-align: right">2067276</td>
      </tr>
      <tr>
          <td><code>DarkGrey</code></td>
          <td style="text-align: right">9936031</td>
      </tr>
      <tr>
          <td><code>DarkNavy</code></td>
          <td style="text-align: right">2899536</td>
      </tr>
      <tr>
          <td><code>DarkOrange</code></td>
          <td style="text-align: right">11027200</td>
      </tr>
      <tr>
          <td><code>DarkPurple</code></td>
          <td style="text-align: right">7419530</td>
      </tr>
      <tr>
          <td><code>DarkRed</code></td>
          <td style="text-align: right">10038562</td>
      </tr>
      <tr>
          <td><code>DarkVividPink</code></td>
          <td style="text-align: right">11342935</td>
      </tr>
      <tr>
          <td><code>Gold</code></td>
          <td style="text-align: right">15844367</td>
      </tr>
      <tr>
          <td><code>Green</code></td>
          <td style="text-align: right">5763719</td>
      </tr>
      <tr>
          <td><code>Grey</code></td>
          <td style="text-align: right">9807270</td>
      </tr>
      <tr>
          <td><code>LightGrey</code></td>
          <td style="text-align: right">12370112</td>
      </tr>
      <tr>
          <td><code>LuminousVividPink</code></td>
          <td style="text-align: right">15277667</td>
      </tr>
      <tr>
          <td><code>Navy</code></td>
          <td style="text-align: right">3426654</td>
      </tr>
      <tr>
          <td><code>Orange</code></td>
          <td style="text-align: right">15105570</td>
      </tr>
      <tr>
          <td><code>Purple</code></td>
          <td style="text-align: right">10181046</td>
      </tr>
      <tr>
          <td><code>Red</code></td>
          <td style="text-align: right">15548997</td>
      </tr>
      <tr>
          <td><code>Yellow</code></td>
          <td style="text-align: right">16776960</td>
      </tr>
  </tbody>
</table>

<h2 id="verbesserungsvorschläge" data-numberify>Verbesserungsvorschläge<a class="anchor ms-1" href="#verbesserungsvorschläge"></a></h2>
<p>Ihr könnt gerne auf <a href="https://gitlab.com/rainerrose/cli-tools" target="_blank" rel="noopener noreferrer">GitLab<i class="fas fa-external-link-square-alt ms-1"></i></a> einen Pull-Request einreichen.</p>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/images/posts/discord/05-info-meldung.png" length="27997" type="image/.png" />
    </item>
    
    <item>
      <title>SSL / TLS</title>
      <link>https://www.rainerrose.de/docs/howto/ssl/</link>
      <pubDate>Thu, 08 Jun 2023 17:42:10 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/ssl/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Ein paar Befehle, die ich immer wieder im Umfeld von TLS bzw. SSL benötige.</p>

<h2 id="selbst-signiertes-zertifikat" data-numberify>selbst signiertes Zertifikat<a class="anchor ms-1" href="#selbst-signiertes-zertifikat"></a></h2>
<p>Alles erstellen (CSR, private key, sign) in einem Schritt:</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Ein paar Befehle, die ich immer wieder im Umfeld von TLS bzw. SSL benötige.</p>

<h2 id="selbst-signiertes-zertifikat" data-numberify>selbst signiertes Zertifikat<a class="anchor ms-1" href="#selbst-signiertes-zertifikat"></a></h2>
<p>Alles erstellen (CSR, private key, sign) in einem Schritt:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl req -new -days <span class="m">999</span> -newkey rsa:4096bits -sha512 -x509 -nodes -out server.crt -keyout server.key
</span></span></code></pre></div>
<blockquote class="alert alert-success" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-lightbulb me-2"></i>Hinweis
    </p>
    <p>Die <code>999</code> Tage sollte man ggf. ändern. Dateien werden überschrieben, also Obacht!</p>
</blockquote>

<h2 id="öffentlichen--privaten-schlüssel-extrahieren-pkcs12" data-numberify>öffentlichen / privaten Schlüssel extrahieren (PKCS12)<a class="anchor ms-1" href="#öffentlichen--privaten-schlüssel-extrahieren-pkcs12"></a></h2>

<h3 id="privater-schlüssel" data-numberify>privater Schlüssel<a class="anchor ms-1" href="#privater-schlüssel"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl pkcs12 -in P12Datei.pfx -nocerts -out privateKey.pem
</span></span></code></pre></div>
<h3 id="öffentlicher-schlüssel--zertifikat" data-numberify>öffentlicher Schlüssel / Zertifikat<a class="anchor ms-1" href="#öffentlicher-schlüssel--zertifikat"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl pkcs12 -in P12Datei.pfx -clcerts -nokeys -out publicKey.pem
</span></span></code></pre></div>
<h3 id="zertifikat-als-plain-text-ausgeben--infos-anzeigen" data-numberify>Zertifikat als plain-text ausgeben / Infos anzeigen<a class="anchor ms-1" href="#zertifikat-als-plain-text-ausgeben--infos-anzeigen"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl  x509 -noout -text -purpose -in Zertifikat.pem 
</span></span></code></pre></div>
<h3 id="aufbau-datei" data-numberify>Aufbau Datei<a class="anchor ms-1" href="#aufbau-datei"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">-----BEGIN CERTIFICATE----
</span></span><span class="line"><span class="ln">2</span><span class="cl">... eigentliches Zertifikat ... 
</span></span><span class="line"><span class="ln">3</span><span class="cl">-----END CERTIFICATE-----
</span></span></code></pre></div>
<h2 id="konvertieren" data-numberify>Konvertieren<a class="anchor ms-1" href="#konvertieren"></a></h2>

<h3 id="pem-nach-der" data-numberify>PEM nach DER<a class="anchor ms-1" href="#pem-nach-der"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl x509 -outform der -in Zertfikikat.pem -out Zertfikikat.der
</span></span></code></pre></div>
<h3 id="pem-nach-p7b" data-numberify>PEM nach P7B<a class="anchor ms-1" href="#pem-nach-p7b"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl crl2pkcs7 -nocrl -certfile Zertfikikat.cer -out Zertfikikat.p7b -certfile CAZertifikat.cer
</span></span></code></pre></div>
<h3 id="pem-nach-pfx" data-numberify>PEM nach PFX<a class="anchor ms-1" href="#pem-nach-pfx"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl pkcs12 -export -out Zertfikikat.pfx -inkey privateKey.key -in Zertfikikat.crt -certfile CAZertifikat.crt
</span></span></code></pre></div>
<h3 id="der-nach-pem" data-numberify>DER nach PEM<a class="anchor ms-1" href="#der-nach-pem"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl x509 -inform der -in Zertfikikat.cer -out Zertfikikat.pem
</span></span></code></pre></div>
<h3 id="p7b-nach-pem" data-numberify>P7B nach PEM<a class="anchor ms-1" href="#p7b-nach-pem"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl pkcs7 -print_certs -in Zertfikikat.p7b -out Zertfikikat.cer
</span></span></code></pre></div>
<h3 id="pfxpkcs12-nach-pem" data-numberify>PFX(PKCS#12) nach PEM<a class="anchor ms-1" href="#pfxpkcs12-nach-pem"></a></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl pkcs12 -in Zertfikikat.pfx -nodes -out Zertfikikat.cer
</span></span></code></pre></div>
<h2 id="dhparam-erzeugen" data-numberify>dhparam erzeugen<a class="anchor ms-1" href="#dhparam-erzeugen"></a></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl dhparam -out /etc/ssl/dhparam.pem <span class="m">4096</span>
</span></span></code></pre></div>
<h2 id="changelog" data-numberify>Changelog<a class="anchor ms-1" href="#changelog"></a></h2>
<table>
  <thead>
      <tr>
          <th>Datum</th>
          <th>Änderung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>27.03.2025</td>
          <td>dhparam erzeugen</td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    </item>
    
    <item>
      <title>Profil</title>
      <link>https://www.rainerrose.de/docs/howto/mastodon/profil/</link>
      <pubDate>Mon, 28 Nov 2022 17:43:22 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/mastodon/profil/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>

<h2 id="tabellenfelder" data-numberify>Tabellenfelder<a class="anchor ms-1" href="#tabellenfelder"></a></h2>
<p><code>Einstellungen</code> -&gt; <code>Profil</code> -&gt; <code>Erscheinungsbild</code></p>
<p>Z.B.</p>
<ul>
<li>Featured Hashtags (siehe oben)</li>
<li>Webseiten</li>
</ul>
<ul>
<li>Attribute, die einem wichtig sind oder prominent platziert werden sollen</li>
</ul>
<p>Hier kann auch der &ldquo;Grüne Haken&rdquo; gesetzt werden. Dazu muss auf dem Webseiten-Quellcode jedoch Zugriff bestehen.</p>

<h3 id="featuren-von-profilen" data-numberify>Featuren von Profilen<a class="anchor ms-1" href="#featuren-von-profilen"></a></h3>
<p>Im jeweiligen Profil auf die drei Punkte gehen und im Menü &ldquo;Im Profil hervorheben&rdquo; auswählen.</p>

<blockquote class="alert alert-success" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-lightbulb me-2"></i>Info
    </p>
    <p>Bis zu vier Accounts werden angezeigt. Bei mehr werden daraus bei jedem Aufruf zufällig vier angezeigt.</p>
</blockquote>

<h2 id="featured-hashtags" data-numberify>Featured Hashtags<a class="anchor ms-1" href="#featured-hashtags"></a></h2>
<p>Man kann im Profil wichtige Hashtags anpinnen, unter denen man viel Tröötet.</p>

<h2 id="creds" data-numberify>Creds<a class="anchor ms-1" href="#creds"></a></h2>
<p>Das ist nicht alles auf meinem Mist gewachsen, sondern es ist ein Zusammenschrieb von unterschiedlichen Beiträgen (Toots bzw. Trööts).</p>
<p>Da ich die Loorberen nicht allein ernten will seien folgende Beiträge lobend erwähnt, wo ich schamlos geklaut habe:</p>
<ul>
<li><a href="https://tools.rainerrose.de/redirect.php?id=24" target="_blank" rel="noopener noreferrer">https://chaos.social/@Pertsch/109364783333410682<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=25" target="_blank" rel="noopener noreferrer">https://mastodon.social/@bkastl/109324608994865848<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=26" target="_blank" rel="noopener noreferrer">https://social.tchncs.de/@kaffeeringe/109318992205459828<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=27" target="_blank" rel="noopener noreferrer">https://www.mastodonien.de/einstiegshilfe/<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
</ul>]]></content:encoded>
    </item>
    
    <item>
      <title>Posten / Trööten</title>
      <link>https://www.rainerrose.de/docs/howto/mastodon/posten/</link>
      <pubDate>Mon, 28 Nov 2022 17:42:22 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/mastodon/posten/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Kochrezepte, Tipps und Hinweise zu Mastodon als kleine Sammlung.</p>
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Kochrezepte, Tipps und Hinweise zu Mastodon als kleine Sammlung.</p>
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>

<h2 id="hashtags" data-numberify>Hashtags<a class="anchor ms-1" href="#hashtags"></a></h2>
<p>Unbedingt nutzen, da es (noch?) keine Volltext-Suche gibt und auch keine Algorithmen die Interessen auswerten und vorschlagen.
Beiträge die Tags verwenden lassen sich verfolgen und leichter finden im Fediverse.</p>
<p>Beispiel <tt style='color:blue;'>#hashtag</tt><tt>​s</tt></p>

<h3 id="mehrzahl--einzahl--grundform" data-numberify>Mehrzahl / Einzahl / Grundform<a class="anchor ms-1" href="#mehrzahl--einzahl--grundform"></a></h3>
<p>Wenn nur einen Teil eines Wortes als <code>#Hashtag</code> gesetzt werden soll  (z. B. für die Grundform), dann das <a href="https://tools.rainerrose.de/redirect.php?id=23" target="_blank" rel="noopener noreferrer">breitenlose Leerzeichen<i class="fas fa-external-link-square-alt ms-1"></i></a> einfügen für einen nahtlosen Übergang ein.</p>

<h2 id="creds" data-numberify>Creds<a class="anchor ms-1" href="#creds"></a></h2>
<p>Das ist nicht alles auf meinem Mist gewachsen, sondern es ist ein Zusammenschrieb von unterschiedlichen Beiträgen (Toots bzw. Trööts).</p>
<p>Da ich die Lorbeeren nicht allein ernten will seien folgende Beiträge lobend erwähnt, wo ich schamlos geklaut habe:</p>
<ul>
<li><a href="https://tools.rainerrose.de/redirect.php?id=24" target="_blank" rel="noopener noreferrer">https://chaos.social/@Pertsch/109364783333410682<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=25" target="_blank" rel="noopener noreferrer">https://mastodon.social/@bkastl/109324608994865848<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=26" target="_blank" rel="noopener noreferrer">https://social.tchncs.de/@kaffeeringe/109318992205459828<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=27" target="_blank" rel="noopener noreferrer">https://www.mastodonien.de/einstiegshilfe/<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
</ul>]]></content:encoded>
    </item>
    
    <item>
      <title>Entdecken</title>
      <link>https://www.rainerrose.de/docs/howto/mastodon/entdecken/</link>
      <pubDate>Mon, 28 Nov 2022 17:41:22 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/mastodon/entdecken/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>

<h2 id="entdecken-ab-version-4" data-numberify>Entdecken ab Version 4<a class="anchor ms-1" href="#entdecken-ab-version-4"></a></h2>

<h3 id="profilverzeichnis" data-numberify>Profilverzeichnis<a class="anchor ms-1" href="#profilverzeichnis"></a></h3>
<p>Jede Instanz kann ein Profilverzeichnis haben, sonfern angegeben wurde, dass man gelistet werden möchte.</p>
<p>Beispiel: <a href="https://tools.rainerrose.de/redirect.php?id=21" target="_blank" rel="noopener noreferrer">https://norden.social/directory<i class="fas fa-external-link-square-alt ms-1"></i></a></p>
<p><picture><img class="img-fluid " alt="Auswahlmenü mit mehreren Optionen zum Profil durchsuchen. Kürzlich aktiv. Neue Profile. Nur von der Domain norden.social . Oder: Aus dem Fediverse" src="https://www.rainerrose.de/images/docs/profile-durchsuchen.png?v=c2f90ae1e77c30a722cf029b03dd4346" loading="lazy" width="450" height="278" />
</picture>

</p>
<p>Eingestellt wird dies unter den <code>Einstellungen</code> -&gt; <code>Profil</code> und dann dort den Haken setzen bei <code>Dieses Profil im Profilverzeichnis zeigen</code>, wenn man gelistet werden möchte.</p>

<h3 id="tags" data-numberify>Tags<a class="anchor ms-1" href="#tags"></a></h3>
<p>Trends in den Tags via <code>#Entdecken</code> -&gt; <code>Hashtags</code></p>
<p>Beispiel <a href="https://tools.rainerrose.de/redirect.php?id=22" target="_blank" rel="noopener noreferrer">https://norden.social/explore/tags<i class="fas fa-external-link-square-alt ms-1"></i></a></p>
<p><picture><img class="img-fluid " alt="tag liste mit Beschreibungen und einem kleinem Chart" src="https://www.rainerrose.de/images/docs/trends-hastags.png?v=13edcf3c7249e10210df15414843fc3c" loading="lazy" width="456" height="503" />
</picture>

</p>

<h2 id="rss" data-numberify>RSS<a class="anchor ms-1" href="#rss"></a></h2>
<p>Die Beiträge eines Kontos lassen sich als RSS-Feed abbonnieren. Es muss nur ein <code>.rss</code> an die URL des Profils rangehängt werden.</p>
<p><code>https://norden.social/@Kaffee_Junkie42</code> wird zu <code>https://norden.social/@Kaffee_Junkie42.rss</code></p>

<h2 id="eigene-beiträge" data-numberify>Eigene Beiträge<a class="anchor ms-1" href="#eigene-beiträge"></a></h2>
<p>Die eigenen Beiträge lassen sich anzeigen, in dem <code>from:me</code> in die Suchleiste angegeben wird.</p>

<h2 id="creds" data-numberify>Creds<a class="anchor ms-1" href="#creds"></a></h2>
<p>Das ist nicht alles auf meinem Mist gewachsen, sondern es ist ein Zusammenschrieb von unterschiedlichen Beiträgen (Toots bzw. Trööts).</p>
<p>Da ich die Lorbeeren nicht allein ernten will seien folgende Beiträge lobend erwähnt, wo ich schamlos geklaut habe:</p>
<ul>
<li><a href="https://tools.rainerrose.de/redirect.php?id=24" target="_blank" rel="noopener noreferrer">https://chaos.social/@Pertsch/109364783333410682<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=25" target="_blank" rel="noopener noreferrer">https://mastodon.social/@bkastl/109324608994865848<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=26" target="_blank" rel="noopener noreferrer">https://social.tchncs.de/@kaffeeringe/109318992205459828<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=27" target="_blank" rel="noopener noreferrer">https://www.mastodonien.de/einstiegshilfe/<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://tools.rainerrose.de/redirect.php?id=52" target="_blank" rel="noopener noreferrer">https://social.tchncs.de/@cark/114796294158937637<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
</ul>]]></content:encoded>
    </item>
    
    <item>
      <title>Backup</title>
      <link>https://www.rainerrose.de/docs/howto/mastodon/backup/</link>
      <pubDate>Mon, 28 Nov 2022 17:40:22 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/mastodon/backup/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Kochrezepte, Tipps und Hinweise zu Mastodon als kleine Sammlung.</p>
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Kochrezepte, Tipps und Hinweise zu Mastodon als kleine Sammlung.</p>
<p>Die Liste wird immer mal wieder erweitert; dabei wird dann hier das Datum einfach aktualisiert.</p>
<p>Unter <code>Einstel­lungen</code> -&gt; <code>Import und Export</code>  -&gt; <code>Export</code> können auch die anderen Daten­kate­gorien (außer Medien) im CSV-Format herun­terge­laden werden, wie Blockierte Accounts, Listen oder Lese­zei­chen. Und das nicht nur, um sie bei einem geplanten Umzug ins neue Konto zu impor­tieren.</p>
<p>Regel­mäßige Backups sind auch wichtig für den Fall, dass die Instanz, auf der man ange­meldet ist, einmal über­raschend abge­schaltet werden sollte. Alle Daten­kate­gorien inklu­sive Medi­enspei­cher können <strong>einmal wöchent­lich</strong> als Archiv herun­ter­geladen werden. Die Zusammenstellung dauert einen Moment und der Download-Link wird per E-Mail zugesendet.</p>]]></content:encoded>
    </item>
    
    <item>
      <title>Anwendungen</title>
      <link>https://www.rainerrose.de/docs/howto/</link>
      <pubDate>Mon, 24 Oct 2022 16:06:43 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/</guid>
      <description><![CDATA[<p>Sammlung an Anleitungen (HowTo) für verschiedene Dinge.</p>]]></description>
      <content:encoded><![CDATA[<p>Sammlung an Anleitungen (HowTo) für verschiedene Dinge.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Linux Ecke</title>
      <link>https://www.rainerrose.de/docs/linux/</link>
      <pubDate>Mon, 20 Dec 2021 23:03:55 +0100</pubDate>
      <guid>https://www.rainerrose.de/docs/linux/</guid>
      <description><![CDATA[<p>LINUX Ecke: Links, Tips &amp; Tricks zum Thema LINUX.</p>
<div class="alert alert-Info" role="alert"><i class="alert-icon fas fa-fw fa-info-circle me-1"></i>

  Linux is like a wigwam: no windows, no gates, apache inside.
</div>]]></description>
      <content:encoded><![CDATA[<p>LINUX Ecke: Links, Tips &amp; Tricks zum Thema LINUX.</p>
<div class="alert alert-Info" role="alert"><i class="alert-icon fas fa-fw fa-info-circle me-1"></i>

  Linux is like a wigwam: no windows, no gates, apache inside.
</div>

]]></content:encoded>
    </item>
    
    <item>
      <title>Dokuwiki</title>
      <link>https://www.rainerrose.de/docs/howto/dokuwiki/</link>
      <pubDate>Tue, 05 Oct 2021 16:41:57 +0100</pubDate>
      <guid>https://www.rainerrose.de/docs/howto/dokuwiki/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Notizen zur Webanwendung Dokuwiki.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Notizen zur Webanwendung Dokuwiki.</p>

<h2 id="urls" data-numberify>URLs<a class="anchor ms-1" href="#urls"></a></h2>
<ul>
<li><a href="https://tools.rainerrose.de/redirect.php?id=4" target="_blank" rel="noopener noreferrer">https://www.dokuwiki.org/de:rewrite<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
</ul>

<h2 id="plugins" data-numberify>Plugins<a class="anchor ms-1" href="#plugins"></a></h2>
<ul>
<li><a href="https://tools.rainerrose.de/redirect.php?id=5" target="_blank" rel="noopener noreferrer">https://www.dokuwiki.org/plugin:ifauthex<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li>Backlinks</li>
<li>Bureaucracy Plugin</li>
<li><a href="https:*tools.rainerrose.de/redirect.php?id=6">catlist</a></li>
<li>ckgedit</li>
<li>Cloud Plugin</li>
<li>Color Plugin</li>
<li>Color Picker</li>
<li>Flowchartjs Plugin</li>
<li>include plugin</li>
<li>Indexmenu Plugin</li>
<li>keyboard</li>
<li>Lightbox for images</li>
<li>Move plugin</li>
<li>Note Plugin</li>
<li>Page Index</li>
<li>Pagelist Plugin</li>
<li>Inline Page Modifier</li>
<li>PageQuery Plugin</li>
<li>sortablejs</li>
<li>Tag Plugin</li>
<li>ToDo</li>
<li>widearea plugin</li>
<li>Wrap Plugin</li>
</ul>

<h2 id="sidebar" data-numberify>sidebar<a class="anchor ms-1" href="#sidebar"></a></h2>
<pre><code>{{indexmenu&gt;:#1|js#thread navbar context tsort}}
</code></pre>

<h2 id="edit-button" data-numberify>edit-Button<a class="anchor ms-1" href="#edit-button"></a></h2>
<pre><code>&lt;ifauth @user&gt;
[[:dokuwiki_doku.php?id=sidebar&amp;do=edit|{{:edit.png?nolink&amp;16}}]] [[:sidebar|Edit Sidebar]]
&lt;/ifauth&gt;
</code></pre>

<h2 id="tags-verwenden" data-numberify>TAGs verwenden<a class="anchor ms-1" href="#tags-verwenden"></a></h2>
<pre><code>{{tag&gt;[list of tags]}}
</code></pre>

<h2 id="auflistung-verwendeter-tags" data-numberify>Auflistung verwendeter TAGs<a class="anchor ms-1" href="#auflistung-verwendeter-tags"></a></h2>
<pre><code>{{searchtags}}
</code></pre>

<h2 id="tagcloud" data-numberify>TAGCLOUD<a class="anchor ms-1" href="#tagcloud"></a></h2>
<pre><code>~~TAGCLOUD~~
</code></pre>

<h2 id="sequenzdiagramme" data-numberify>Sequenzdiagramme<a class="anchor ms-1" href="#sequenzdiagramme"></a></h2>
<p>Basieren auf der Bibliothek
<a href="https://tools.rainerrose.de/redirect.php?id=7" target="_blank" rel="noopener noreferrer">js-sequence-diagrams<i class="fas fa-external-link-square-alt ms-1"></i></a>.</p>

<h2 id="code" data-numberify>Code<a class="anchor ms-1" href="#code"></a></h2>
<pre><code>&lt;sequencediagram&gt;
  Title: Der Titel
  A-&gt;B: Normale Linie
  B--&gt;C: Gestrichelte Linie
  C-&gt;&gt;D: Pfeilspitze offen
  Note right of D: Und wieder zurück
  D--&gt;&gt;A: Gestrichelte Linie Pfeilspitze offen
&lt;/sequencediagram&gt;
</code></pre>

<h2 id="tastenkürzel--tastenkombi" data-numberify>Tastenkürzel / Tastenkombi<a class="anchor ms-1" href="#tastenkürzel--tastenkombi"></a></h2>
<p>This displays the keys <code>ALT</code> + <code>H</code> .</p>
<pre><code>This displays the keys &lt;kbd&gt;ALT&lt;/kbd&gt; + &lt;kbd&gt;H&lt;/kbd&gt;.
</code></pre>
<p>Kürzer:</p>
<pre><code>This displays the keys &lt;kbd&gt;ALT+H&lt;/kbd&gt;.
</code></pre>
<p>Tipp: <code>ALT+-</code></p>
<pre><code>&lt;kbd&gt;ALT+-&lt;/kbd&gt;
</code></pre>

<h2 id="hinweisboxen-anlegen" data-numberify>Hinweisboxen anlegen<a class="anchor ms-1" href="#hinweisboxen-anlegen"></a></h2>

<h3 id="beispiele" data-numberify>Beispiele<a class="anchor ms-1" href="#beispiele"></a></h3>
<p>Classic</p>
<pre><code>&lt;note&gt; Classic &lt;/note&gt;
</code></pre>
<p>Important</p>
<pre><code>&lt;note important&gt; Important &lt;/note&gt;
</code></pre>
<p>Warning</p>
<pre><code>&lt;note warning&gt; Warning &lt;/note&gt;
</code></pre>
<p>Tip</p>
<pre><code>&lt;note tip&gt; Tip &lt;/note&gt;
</code></pre>

<h2 id="farben" data-numberify>Farben<a class="anchor ms-1" href="#farben"></a></h2>
<table>
  <thead>
      <tr>
          <th>Im Editor einzugeben</th>
          <th>Erläuterung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>&lt;color green&gt;text&lt;/color&gt;</code></td>
          <td>Nur Textfarbe ändern</td>
      </tr>
      <tr>
          <td><code>&lt;color blue/lightgrey&gt;text&lt;/color&gt;</code></td>
          <td>Textfarbe und Texthintergrund ändern</td>
      </tr>
      <tr>
          <td><code>&lt;color /lightgrey&gt;text&lt;/color&gt;</code></td>
          <td>nur Texthintergrund ändern</td>
      </tr>
      <tr>
          <td><code>&lt;color #FF0000&gt;text&lt;/color&gt;</code></td>
          <td>Angabe in RGB-Notation</td>
      </tr>
      <tr>
          <td><code>&lt;color /#FFff00&gt;text&lt;/color&gt;</code></td>
          <td>Texthintergrund ändern in RGB-Notation</td>
      </tr>
  </tbody>
</table>

<h2 id="flowcharts" data-numberify>Flowcharts<a class="anchor ms-1" href="#flowcharts"></a></h2>
<p>Die Flowcharts basieren auf der Bibliothek
<a href="https://tools.rainerrose.de/redirect.php?id=8" target="_blank" rel="noopener noreferrer">http://flowchart.js.org<i class="fas fa-external-link-square-alt ms-1"></i></a></p>

<h3 id="code-1" data-numberify>Code<a class="anchor ms-1" href="#code-1"></a></h3>
<pre><code>&lt;flowchartjs default&gt;
  st=&gt;start: Start
  e=&gt;end: Ende
  con1=&gt;condition: Kennste 
  Flowcharts?
  con2=&gt;condition: Weißte wie
  es geht?
  op1=&gt;operation: Informieren (Klick):&gt;https://de.wikipedia.org/wiki/Programmablaufplan[blank]
  op2=&gt;operation: Informieren (Klick):&gt;http://flowchart.js.org[blank]
  io1=&gt;inputoutput: Flowchart tippen
  op3=&gt;operation: Freuen
  
  st-&gt;con1
  con1(no)-&gt;op1-&gt;con2
  con1(yes)-&gt;con2
  con2(no)-&gt;op2-&gt;io1-&gt;op3-&gt;e
  con2(yes)-&gt;io1
&lt;/flowchartjs&gt;
</code></pre>]]></content:encoded>
    </item>
    
  </channel>
</rss>

