<?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>Backup on private Homepage von Rainer Rose</title>
    <link>https://www.rainerrose.de/tags/backup/</link>
    <description>Recent content in Backup 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/backup/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>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>Systemd</title>
      <link>https://www.rainerrose.de/docs/linux/systemd/</link>
      <pubDate>Sat, 28 Dec 2024 12:48:58 +0100</pubDate>
      <guid>https://www.rainerrose.de/docs/linux/systemd/</guid>
      <description><![CDATA[<!-- Anreißer -->

<h2 id="notizensammlung" data-numberify>Notizensammlung<a class="anchor ms-1" href="#notizensammlung"></a></h2>
<p>Dinge, die ich mir bei <code>systemd</code> nicht merken kann. Und bevor ich wieder Suchmaschinen oder man-Pages bemühen muss, schreibe ich sie hier lieber nieder.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->

<h2 id="notizensammlung" data-numberify>Notizensammlung<a class="anchor ms-1" href="#notizensammlung"></a></h2>
<p>Dinge, die ich mir bei <code>systemd</code> nicht merken kann. Und bevor ich wieder Suchmaschinen oder man-Pages bemühen muss, schreibe ich sie hier lieber nieder.</p>

<h2 id="im-hintergrund-starten" data-numberify>Im Hintergrund starten<a class="anchor ms-1" href="#im-hintergrund-starten"></a></h2>
<p>Parameter <code>--no-block</code> verwenden, damit der Prompt gleich wieder kommt und nicht auf Beendigung gewartet wird; praktisch für Backup-Jobs außerhalb der Reihe.</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">systemctl start resticprofile_full-backup.service --no-block
</span></span></code></pre></div>
<h2 id="timer-in-systemd" data-numberify>Timer in SystemD<a class="anchor ms-1" href="#timer-in-systemd"></a></h2>
<p>wird gerne von snap-Paketen genutzt, die keinen cronjob installieren.</p>
<p>Auflistung</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">systemctl list-timers <span class="o">[</span>--all<span class="o">]</span>
</span></span></code></pre></div><p>In der Auflistung steht dann auch der timer, wo die Zeitdefinition drin
steht und welche start-Unit anzieht.</p>
<p>Verzeichnis vielfach: <code>/etc/systemd/system/foo.timer</code></p>

<h2 id="als-ersatz-für-at" data-numberify>Als Ersatz für at<a class="anchor ms-1" href="#als-ersatz-für-at"></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">systemd-run --user --on-calendar<span class="o">=</span><span class="s2">&#34;2024-12-28 10:31:19&#34;</span> Befehl Parameter....
</span></span></code></pre></div><p>ggf. <code>sudo</code> davor für <code>root</code>.</p>

<h2 id="failed-units" data-numberify>failed Units<a class="anchor ms-1" href="#failed-units"></a></h2>

<h3 id="alle-auflisten" data-numberify>Alle auflisten<a class="anchor ms-1" href="#alle-auflisten"></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">systemctl status --failed
</span></span></code></pre></div>
<h3 id="resetten" data-numberify>Resetten<a class="anchor ms-1" href="#resetten"></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">systemctl reset-failed &lt;unit-name&gt;
</span></span></code></pre></div>
<h2 id="auskunft-über-pids" data-numberify>Auskunft über pids<a class="anchor ms-1" href="#auskunft-über-pids"></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">systemctl status &lt;pid&gt;
</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>05.11.2025</td>
          <td>failed-Kommandos hinzugefügt</td>
      </tr>
      <tr>
          <td>28.03.2026</td>
          <td>Auskunft über pids</td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    </item>
    
    <item>
      <title>Docker</title>
      <link>https://www.rainerrose.de/docs/linux/docker/</link>
      <pubDate>Sat, 01 Jun 2024 16:28:13 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/linux/docker/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Ein paar kleine Tricks und Kniffe zum Thema Docker.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Ein paar kleine Tricks und Kniffe zum Thema Docker.</p>

<h2 id="mysqldump-ohne-passwort-in-prozessliste" data-numberify>mysqldump ohne Passwort in Prozessliste<a class="anchor ms-1" href="#mysqldump-ohne-passwort-in-prozessliste"></a></h2>
<p>Unter docker werden die Zugangsdaten meist über Umgebungsvariablen
weitergegeben.</p>
<p>Arbeitet man jedoch bei <code>mysqldump</code> mit dem Parameter <code>-p</code> gibt es
folgende Warnung</p>
<pre><code>mysqldump: [Warning] Using a password on the command line interface can be insecure.
</code></pre>
<p>Um jetzt nicht alles nach <code>/dev/null</code> umzuleiten, um noch echte Fehler
mitzubekommen, kann man mittels des Parameters <code>--defaults-file</code> das
Passwort in eine Datei kopieren. Da sich Zugangsdaten ändern können,
empfiehlt es sich, das dynamisch per Script zu erzeugen und das Passwort
aus der Umgebungsvariable des Containers auszulesen.</p>
<p>Das Script legt man entweder in ein lokales Volume oder kopiert es via
<code>docker cp</code> in den Container.</p>
<p>Das Beispiel-Script zum dynamischen Erzeugen der Credentials-Datei:</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">#!/bin/bash
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="cp"></span><span class="nv">CRED_FILE</span><span class="o">=</span><span class="s2">&#34;/docker-entrypoint-initdb.d/credentials-root.conf&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">#loeschen</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">&gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CRED_FILE</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">chmod <span class="m">600</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CRED_FILE</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;[client]&#34;</span> &gt;&gt;  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CRED_FILE</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;password=</span><span class="si">${</span><span class="nv">MYSQL_ROOT_PASSWORD</span><span class="si">}</span><span class="s2">&#34;</span> &gt;&gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CRED_FILE</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>Und noch ein Auszug aus einem Backup-Script:</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 <span class="nb">exec</span> -i  onlyoffice-mysql-server  /bin/bash /docker-entrypoint-initdb.d/gen_cred_config.sh
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="o">(</span>docker <span class="nb">exec</span> -i onlyoffice-mysql-server mysqldump  --defaults-file<span class="o">=</span>/docker-entrypoint-initdb.d/credentials-root.conf &lt;weitere Dump-Parameter&gt;  -u root -h&lt;hostname&gt;  <span class="p">|</span> xz -c &gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BACKUPDIR</span><span class="si">}</span><span class="s2">/dump.sql.xz&#34;</span>
</span></span></code></pre></div>
<h2 id="umgebungsvariable-aus-docker-container-auslesen" data-numberify>Umgebungsvariable aus Docker-Container auslesen<a class="anchor ms-1" href="#umgebungsvariable-aus-docker-container-auslesen"></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">docker <span class="nb">exec</span> -i onlyoffice-community-server /bin/bash -c <span class="s1">&#39;/bin/echo ${MYSQL_SERVER_PASS}&#39;</span>
</span></span></code></pre></div>
<h2 id="unterordner-in-docker-volume-ignorieren" data-numberify>Unterordner in Docker-Volume ignorieren<a class="anchor ms-1" href="#unterordner-in-docker-volume-ignorieren"></a></h2>
<p>Dem <code>docker-compose</code> ein Verzeichnis der lokalen Maschine übergeben,
aber einzelne Unterverszeichnisse davon ausnehmen:</p>
<p>Das funktioniert, indem für die auszunehmenden Unterordner ein zweites
Volume angeben wird. Volume darf keine Zuordnung mit einem Doppelpunkt
enhalten:</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">volumes</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">./webseite/html:/var/www/html</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">/var/www/html/static/</span><span class="w">
</span></span></span></code></pre></div><p>Im Beispiel bekommt ein Webserver die ganze Webseite übergeben, der
Ordner <code>static</code> wird aber aus dem Container benutzt.</p>

<h2 id="backups--recover-via-volume-mounten" data-numberify>Backups & Recover via Volume mounten<a class="anchor ms-1" href="#backups--recover-via-volume-mounten"></a></h2>

<h3 id="reingucken" data-numberify>reingucken<a class="anchor ms-1" href="#reingucken"></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">docker run -it --rm -v portainer_data:/data:ro -v <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>:/backup alpine /bin/sh
</span></span></code></pre></div>
<h3 id="sichern" data-numberify>sichern<a class="anchor ms-1" href="#sichern"></a></h3>

<h4 id="variante-1" data-numberify>Variante 1<a class="anchor ms-1" href="#variante-1"></a></h4>
<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 run --rm -v portainer_data:/data:ro -v <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>:/backup alpine tar cjvf /backup/data.tbz2 -C /data ./
</span></span></code></pre></div>
<h4 id="variante-2" data-numberify>Variante 2<a class="anchor ms-1" href="#variante-2"></a></h4>
<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 run --rm -v portainer_data:/data alpine tar cf - -C /data ./ <span class="p">|</span> xz &gt; ./portainer_data_volume.txz
</span></span></code></pre></div>
<h3 id="recover" data-numberify>Recover<a class="anchor ms-1" href="#recover"></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">docker run --rm -v portainer_data:/data -v ./portainer_data_volume.txz:/backup.tgz alpine tar tJf -C /data /backup.tgz
</span></span></code></pre></div>
<h2 id="volumes" data-numberify>Volumes<a class="anchor ms-1" href="#volumes"></a></h2>
<p>Wie unterscheiden sich nochmal die Bezeichnungen bei den Volumes?</p>
<ul>
<li>bind volume</li>
<li>named volume</li>
<li>volume</li>
</ul>

<h2 id="logfiles-auf-die-konsole" data-numberify>Logfiles auf die Konsole<a class="anchor ms-1" href="#logfiles-auf-die-konsole"></a></h2>
<p>Logfile-Ausgaben können über STDOUT einfach an das &quot;Terminal&quot; geblasen
werden, der dann via <code>docker log &lt;container-name&gt;</code> bzw. Portainer
auslesbar ist.</p>
<p>Eine Apache-Konfig <code>conf-enabled/other-vhosts-access-log.conf</code> kann z.B. so aussehen:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># Define an access log for VirtualHosts that don&#39;t define their own logfile</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">CustomLog</span> <span class="sx">/proc/self/fd/1</span> vhost_combined
</span></span></code></pre></div><p>Im Container selbst ist dann der Grund zu sehen, warum das funktioniert:</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="c1"># ls -l /proc/self/fd/1</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">lrwx------. <span class="m">1</span> root root <span class="m">64</span> Jul  <span class="m">8</span> 16:19 /proc/self/fd/1 -&gt; /dev/pts/0
</span></span></code></pre></div><p>Habe ich mal in einem ubuntu-basierten Container gesehen, ziemlich
genial.</p>

<h2 id="docker-entrypoint" data-numberify>docker ENTRYPOINT<a class="anchor ms-1" href="#docker-entrypoint"></a></h2>
<p>Weil ich mir nie merken kann, wie wann was gestartet wird, hier mal eine Tabelle:</p>
<table>
  <thead>
      <tr>
          <th>ENTRYPOINT</th>
          <th>CMD</th>
          <th>run-Parameter</th>
          <th>ausgeführt wird</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>[&quot;script.sh&quot;]</code></td>
          <td></td>
          <td></td>
          <td><code>script.sh</code></td>
      </tr>
      <tr>
          <td><code>[&quot;script.sh&quot;]</code></td>
          <td></td>
          <td><code>/bin/bash</code></td>
          <td><code>script.sh /bin/bash</code></td>
      </tr>
      <tr>
          <td><code>[&quot;script.sh&quot;]</code></td>
          <td><code>[&quot;mysqld&quot;]</code></td>
          <td></td>
          <td><code>script.sh mysqld</code></td>
      </tr>
      <tr>
          <td><code>[&quot;script.sh&quot;]</code></td>
          <td><code>[&quot;mysqld&quot;]</code></td>
          <td><code>/bin/bash</code></td>
          <td><code>script.sh /bin/bash</code></td>
      </tr>
      <tr>
          <td></td>
          <td><code>[&quot;/bin/sh&quot;]</code></td>
          <td></td>
          <td><code>/bin/sh</code></td>
      </tr>
      <tr>
          <td></td>
          <td><code>[&quot;/bin/sh&quot;]</code></td>
          <td><code>/bin/bash</code></td>
          <td><code>/bin/bash</code></td>
      </tr>
  </tbody>
</table>

<h2 id="zeitzonen" data-numberify>Zeitzonen<a class="anchor ms-1" href="#zeitzonen"></a></h2>
<p>Die lokale Zeitzone lässt sich per Volume-Mount an den Container durchreichen, sofern die Applikation das unterstützt bzw. beachtet.</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">volumes</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">/etc/timezone:/etc/timezone:ro</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">/etc/localtime:/etc/localtime:ro</span><span class="w">
</span></span></span></code></pre></div>
<h2 id="pull-und-restart" data-numberify>Pull und Restart<a class="anchor ms-1" href="#pull-und-restart"></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">docker compose up -d --pull always
</span></span></code></pre></div><p>bzw.</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 --build --pull always
</span></span></code></pre></div>
<h2 id="erstellungsdatum-created-eines-images-abfragen" data-numberify>Erstellungsdatum (Created) eines Images abfragen<a class="anchor ms-1" href="#erstellungsdatum-created-eines-images-abfragen"></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">docker inspect docker.io/photoprism/photoprism:latest -f <span class="s2">&#34;{{ .Created}}&#34;</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>08.11.2023</td>
          <td>Backup via Volume weitere Variante sowie Recover-Möglichkeit.</td>
      </tr>
      <tr>
          <td>01.06.2024</td>
          <td>docker ENTRYPOINT</td>
      </tr>
      <tr>
          <td>25.11.2024</td>
          <td>Zeitzonen-Volumes</td>
      </tr>
      <tr>
          <td>02.05.2025</td>
          <td>Pull und Restart</td>
      </tr>
      <tr>
          <td>24.03.2026</td>
          <td>Erstellungsdatum (Created) eines Images abfragen</td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    </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>Backupkonzept</title>
      <link>https://www.rainerrose.de/posts/backupkonzept/</link>
      <pubDate>Sat, 16 Dec 2023 17:46:08 +0200</pubDate>
      <guid>https://www.rainerrose.de/posts/backupkonzept/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Heute möchte ich über das Thema Datensicherung aka Backups schreiben. Hier und da wurde ich gefragt, wie ich das löse bzw. umgesetzt habe.</p>
<p>Mariannes <a href="https://tools.rainerrose.de/redirect.php?id=28" target="_blank" rel="noopener noreferrer">Blog-Artikel<i class="fas fa-external-link-square-alt ms-1"></i></a>
gab den Ausschlag dieses nun endlich mal aufzuschreiben.</p>
<p>Es wird eine kleine Reise werden. Ich werde über die grundsätzlichen Gedanken berichten, die ich mir gemacht habe, als auch etwas in technische Details gehen.
Ebenso werde ich Dinge niederschreiben, die sich <strong>nicht</strong> bewährt haben, da dies evtl. für andere ebenfalls hilfreich erweisen sein.</p>
<p>Dieser Artikel wird länger ;-)</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Heute möchte ich über das Thema Datensicherung aka Backups schreiben. Hier und da wurde ich gefragt, wie ich das löse bzw. umgesetzt habe.</p>
<p>Mariannes <a href="https://tools.rainerrose.de/redirect.php?id=28" target="_blank" rel="noopener noreferrer">Blog-Artikel<i class="fas fa-external-link-square-alt ms-1"></i></a>
gab den Ausschlag dieses nun endlich mal aufzuschreiben.</p>
<p>Es wird eine kleine Reise werden. Ich werde über die grundsätzlichen Gedanken berichten, die ich mir gemacht habe, als auch etwas in technische Details gehen.
Ebenso werde ich Dinge niederschreiben, die sich <strong>nicht</strong> bewährt haben, da dies evtl. für andere ebenfalls hilfreich erweisen sein.</p>
<p>Dieser Artikel wird länger ;-)</p>

<h2 id="start" data-numberify>Start<a class="anchor ms-1" href="#start"></a></h2>
<p>Grundsätzlich wollte ich das Backup am Ende so gestalten, dass es für mich relativ einfach ist anzuwenden. Sind beim Anfertigen eines Backups mehrere Vorbereitungen notwendig oder muss ich minutenlang einem Script zuschauen, neige ich dazu, das Backup zu vergessen oder &ldquo;gerade keine Zeit&rdquo; zu haben.
Und ein Backup sollte natürlich möglichst aktuell sein.
<strong>Ein</strong> Ziel von mehreren muss also <em>die Cloud</em><sup>(tm)</sup> sein.
Damit entfällt das Gestöpsel von einer externe Festplatte und es gibt eine Ausrede weniger. Weiterhin bietet dies die Möglichkeit, das Backup weiter zu automatisieren, z.B. beim Runterfahren des PCs. Dazu jedoch später mehr.</p>

<h2 id="was-will-ich-sichern-und-warum" data-numberify>Was will ich sichern und warum?<a class="anchor ms-1" href="#was-will-ich-sichern-und-warum"></a></h2>
<p>Der erste Schritt, den ich getan habe, war mir zu überlegen, <strong>was</strong> ich alles sichern möchte und ich habe mir folgende Fragen gestellt:</p>

<h3 id="was-ist-mir-wichtig" data-numberify>Was ist mir wichtig?<a class="anchor ms-1" href="#was-ist-mir-wichtig"></a></h3>
<p>Habe ich Dinge, an denen mein Herz hängt? Die Antwort lautet: <em>&ldquo;Ja&rdquo;</em> und zwar z.B.</p>
<ul>
<li>Fotos, Videos</li>
<li>bestimmte E-Mails</li>
<li>Systemeinstellungen</li>
<li>Programm-Einstellungen</li>
<li>selbst geschriebene Scripte</li>
<li>Dokumente</li>
</ul>

<h3 id="gibt-es-nicht-ganz-so-wichtige-daten" data-numberify>Gibt es nicht ganz so wichtige Daten?<a class="anchor ms-1" href="#gibt-es-nicht-ganz-so-wichtige-daten"></a></h3>
<p>Gibt es Dinge, die ich zwar sichern möchte, aber es vielleicht nicht so schlimm ist, wenn ich sie doch verliere? Dies wird später wichtig, in welche Sicherungs-Archive ich Dateien zuordne. Zum Beispiel:</p>
<ul>
<li>ISO-Dateien von gekauften CDs</li>
<li>alte Installations-CDs von Steuer- oder Finanzsoftware</li>
</ul>

<h3 id="habe-ich-unwichtige-dinge" data-numberify>Habe ich unwichtige Dinge?<a class="anchor ms-1" href="#habe-ich-unwichtige-dinge"></a></h3>
<p>Kann ich Daten sparen bzw. das Backup kleiner machen? Kann ich Dinge ausschließen? Beispiele:</p>
<ul>
<li>temporäre Dateien</li>
<li>Download-Ordner</li>
<li><code>thumbnails</code>-Ordner</li>
<li><code>Cache</code>-Ordner</li>
</ul>
<p>All das verringert unter anderem die Größe eines Archivs, was wiederum Kosten spart. Eine größere externe Festplatte oder mehr Cloud-Speicherplatz kosten nun mal Geld.
Dateien, die beim Backup übergangen werden können, sparen zudem Zeit, weil sie nicht gelesen werden müssen. Das hat den Vorteil, dass auch der einzelne Lauf schneller geht, was wiederum mehr motiviert, ein Backup durchzuführen.</p>
<p>Je nach Art des Backup-Programms gibt es unterschiedliche Möglichkeiten, entweder bestimmte Dateien oder
Pfade zu definieren, die ich ausschließen möchte. Zum Teil lassen sich sogar generische Muster von Dateinamen oder Verzeichnispfaden
definieren. Diese Muster sind flexibler als starre Listen, weil ich dann nicht mehr absolut und gewissenhaft jeden einzelnen
<code>thumbnails</code>-Unterordner definieren muss.</p>
<p>Einige Backup-Programme unterstützen auch eine Datei namens <a href="/posts/cachedirtag/">CACHEDIR.TAG</a> .<br>
Dies ist eine simple Text-Datei, die einen bestimmten Inhalt haben muss. Befindet sich diese Datei in einem Ordner (zum Beispiel der Download-Ordner) so wird dieser Inhalt vom Backup-Lauf ausgenommen. Wie die Datei genau aussehen muss, habe ich in <a href="/posts/cachedirtag/">diesem Blog-Artikel</a> beschrieben.</p>

<h3 id="vertraulichkeit" data-numberify>Vertraulichkeit<a class="anchor ms-1" href="#vertraulichkeit"></a></h3>
<p>Zudem wollte ich ein Backup haben, das für andere Personen wertlos ist, wenn es ihnen in die Hände fällt. Hier bietet es sich an das Archiv zu verschlüsseln.</p>

<h2 id="wie-will-ich-sichern" data-numberify>Wie will ich sichern?<a class="anchor ms-1" href="#wie-will-ich-sichern"></a></h2>
<p>Ich habe mich für eine <strong>dateibasierte Sicherung</strong> entschieden. Vielfach musste ich in der Vergangenheit bisher nur einzelne Dateien oder Ordner wiederherstellen. Im allerschlimmsten Fall, musste ich meinen Rechner komplett neu aufsetzen. In diesem Fall habe ich die Installationsmedien des Betriebssystems und der jeweiligen installierten Programme zu Hause vorrätig.</p>
<p>Alternativ müsste ich mir die Medien aus diesem Internet neu herunterladen. Es gibt zwar Tools, mit denen man die komplette Festplatte inklusive ihrer Konfigurationen sichern kann, aber dies erfordert meist mehrere Handgriffe und eine Downtime des Rechners. Die Erfahrung zeigt, dass man dies nur selten macht, eben weil es so umständlich ist.
Als initiale Sicherung mag dies jedoch eine gute Idee sein.</p>
<p>Insofern wollte ich eine bequeme Lösung haben, wo ich nicht viel tun muss. Ich wollte nicht allzu oft meine externe Festplatte anschließen, denn das bietet sonst nur Ausreden Nahrung.</p>

<h3 id="und-was-ist-mit-dem-betriebssystem" data-numberify>Und was ist mit dem Betriebssystem?<a class="anchor ms-1" href="#und-was-ist-mit-dem-betriebssystem"></a></h3>
<p>Ich habe vom jeweiligen Betriebssystem ein Installationsmedium; das reicht mir. Im schlechtesten Fall muss ich den kompletten Rechner neu aufsetzen; egal ob Windows oder Linux. Im schlimmsten Fall sind die Installationsmedien auch noch aus dem Internet herunterladbar.</p>

<h2 id="3-2-1" data-numberify>3-2-1<a class="anchor ms-1" href="#3-2-1"></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>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>

<h3 id="schnelle-sicherung-lokal" data-numberify>Schnelle Sicherung: lokal<a class="anchor ms-1" href="#schnelle-sicherung-lokal"></a></h3>
<p>Was im Grunde übrig bleibt, sind viele Dateien unterschiedlicher Größe und Wichtigkeit. Sehr lange habe ich dafür
<a href="https://tools.rainerrose.de/redirect.php?id=29" target="_blank" rel="noopener noreferrer">BorgBackup<i class="fas fa-external-link-square-alt ms-1"></i></a> benutzt, einfach auch weil es bei meiner Distribution dabei war und ich mich dann nicht um Programm-Updates kümmern muss(te).
Inzwischen verwende ich <a href="https://tools.rainerrose.de/redirect.php?id=30" target="_blank" rel="noopener noreferrer">restic<i class="fas fa-external-link-square-alt ms-1"></i></a> - zu den Gründen komme ich später.</p>
<p>Die allererste Sicherung geht erst einmal lokal auf die Festplatte in einem extra Bereich; unter Linux ist das bei mir der Pfad <code>/backup</code>. Die Dateisystem-Rechte sind so gesetzt, dass hier nur ein Benutzer mit administrativen Rechten herankommt. Das schützt unter anderem vor versehentlichen Löschen.</p>
<p>Die eigentliche Arbeit erledigt ein selbst geschriebenes <code>bash</code>-Script. Dieses Script kann z.B. beim Runterfahren des PCs verwendet werden. Der Vorteil: Ich muss dem Script nicht zugucken und alle Programme sind geschlossen. Dies ermöglicht ein konsistentes Backup der Dateien, weil sich keine Dateien mehr ändern. Ggf. kann ich dies sogar remote per ssh vom Handy aus aufrufen, weil ich für ein <code>bash</code>-Script keine Oberfläche benötige.</p>
<p>Wenn das Script fertig ist, fährt es den PC komplett runter und schickt mir eine Discord-Nachricht aufs Handy mit einer Statusmeldung.
Für dieses Script habe ich mich schon vor einiger Zeit in einen <a href="../discord-bot/">eigenen Blog-Artikel</a>
verfasst.</p>
<p>Wenn das Script fehler<strong>frei</strong> durchgelaufen ist, kann ich die Steckerleiste ausschalten und habe keinen Standby-Stromverbrauch mehr. Im <strong>Fehlerfall</strong> weiß ich Bescheid, dass irgendwo ein Fehler passiert ist und ich evtl. kein aktuelles Backup habe.</p>
<p>Auf Servern starte ich dieses (ggf. abgewandelte und angepasste Script) automatisch per <code>crontab</code>. Im Fehlerfall weiß ich Bescheid, dass ich bei nächster Gelegenheit mal auf das System gucken sollte, was da genau schief gelaufen ist. Im einfachsten Fall ist es eine Datei, die sich dann im laufenden Betrieb geändert hat, aber auch schon volle Backup-Partitionen hatte ich. Ja, Monitoring auf den privaten Systemen ist Punkt 3.520.230 auf meiner TODO-Liste.</p>

<h3 id="aufräumen" data-numberify>Aufräumen<a class="anchor ms-1" href="#aufräumen"></a></h3>
<p>Am besten räumt man vorher auf, hat also etwas Datendisziplin. Mehrere Versionen einen Schnappschusses bereinigt man um die Version, die man aufheben möchte. Eigene Filmaufnahme oder Filmschnipsel kürzen und verwackelte, unscharfe Handyvideos fressen auch nur unnötig Platz.</p>
<p>Die Programme <code>borg</code> bzw. <code>restic</code> können Sicherungen zudem automatisch ausdünnen. Über Parameter kann ein Backup anhand definierter Regeln ausgedünnt werden. Beispielweise bleiben die letzten 7 täglichen Backups erhalten und für den Rest des Monats wird ein wöchentlicher Schnappschuss aufbewahrt. Danach wird nur der neueste monatliche Schnappschuss aufbewahrt. Durch die fest definierten Regeln, wird ein manuelles und damit meist fehlerträchtiges Heraussuchen vermieden.</p>

<h3 id="sicherung-cloud" data-numberify>Sicherung Cloud<a class="anchor ms-1" href="#sicherung-cloud"></a></h3>
<p>Das selbe Script hat einen optionalen Schalter, wo diese Sicherung &ldquo;in die Cloud&rdquo; angeschaltet werden kann. Dieser Schritt kommt zwischen dem Ende der lokalen Sicherung und dem letztendlichen Herunterfahren des Rechners.</p>

<h3 id="offsite-backup" data-numberify>Offsite-Backup<a class="anchor ms-1" href="#offsite-backup"></a></h3>
<p>Um eine weitere Kopie meiner Daten zu haben, habe ich mir zwei externe Festplatten beschafft. Eine liegt immer neben dem Rechner, ist aber nicht angeschlossen, die andere liegt bei meinen Eltern. Die externen Platten sind außen beschriftet und haben auch ein Software-Label, so dass ich sie beim Einhängen in das Dateisystem unterscheiden kann.
Alle paar Tage stöpsle ich dann die externe Festplatte an und synchronisiere die lokale Sicherung vom dem PC auf diese. Nach der Sicherung stöpsle ich sie wieder ab und die Festplatte ist dann vor versehentlichen Löschen sicher.</p>
<p>Diese Festplatte bringe ich dann alle paar Wochen zu meinen Eltern und tausche sie mit dort liegenden Festplatte aus.
So habe ich im Fall der Fälle (Hausbrand, Einbruch usw.) eine weitere Kopie an einem anderen Ort. Der Stand der Sicherung ist dann möglicherweise ein paar Wochen alt, aber immerhin besser als ein Totalverlust.</p>
<p>In der Summe sind die Daten jetzt <strong>fünfmal</strong> vorhanden.</p>

<h2 id="aber-" data-numberify>Aber !?!<a class="anchor ms-1" href="#aber-"></a></h2>

<h3 id="die-kosten" data-numberify>Die Kosten<a class="anchor ms-1" href="#die-kosten"></a></h3>

<h4 id="usb-festplatten" data-numberify>USB-Festplatten<a class="anchor ms-1" href="#usb-festplatten"></a></h4>
<p>Die externen Festplatten tausche ich alle paar Jahre aus. Nicht nur weil sie irgendwann zu klein werden, sondern ich möchte Bitfäule und mechanischem Ausfall vorbeugen. Die Variante als drehenden Rost ist derzeit noch die billigste Variante pro Gigabyte.</p>

<h4 id="cloud" data-numberify>Cloud<a class="anchor ms-1" href="#cloud"></a></h4>
<p>Ja auch Cloud-Storage-Anbieter möchten Geld verdienen; schließlich müssen Server und Personal irgendwie bezahlt werden.</p>

<h4 id="alternative" data-numberify>Alternative?<a class="anchor ms-1" href="#alternative"></a></h4>
<p>In der Summe sind mir meine Daten lieb und diesen Preis wert. Die Alternative wäre ein Totalverlust und das möchte ich nicht.</p>

<h3 id="sync" data-numberify>Sync<a class="anchor ms-1" href="#sync"></a></h3>
<p>Das führende Backup ist die Sicherung auf dem lokalen PC. Zum Synchronisieren auf die externe Platte nutze ich <code>rsync</code>, in die Cloud verwende ich <code>rclone</code>. Beide Tools haben mehrere Vorteile. Das lokale Backup hat einen konsistenten Stand. Die Synchronisation kann im Hintergrund laufen. Weil der neueste Stand immer lokal vorhanden ist, müssen nur die Differenzen in die Cloud oder die externe Festplatte übertragen werden, d.h. es geht sehr schnell.
Zudem muss ich mir keine Gedanken machen, wo wie welcher Stand ist und ob und wo ich irgendwelche Voll- oder inkrementelle Backups gemacht habe oder noch machen muss.
Auch das automatische Ausdünnen bzw. aufräumen der Backups wird dann auf allen Zielen automatisch mitgezogen bzw. vollzogen.
Da ich nur stumpf Dateien hin und her kopiere, ist die Wahl des Backup-Programms (<code>borg</code> oder <code>restic</code>) irrelevant.</p>

<h3 id="online-speicher-aka-cloud" data-numberify>Online-Speicher aka Cloud<a class="anchor ms-1" href="#online-speicher-aka-cloud"></a></h3>
<p>Cloud !!!111EinsElf Oh! Nein! Doch!</p>
<p>Die Programme <code>borg</code> bzw. <code>restic</code> können Out-of-the-box stark verschlüsseln. Die Archive können mit einer Datei oder einen starken Passwort gesichert werden. Insofern ist Wahl des Anbieters für den Cloud-Storage unabhängig von dessen Vertrauenswürdigkeit. Mit den verschlüsselten Dateien kann ohne das Passwort niemand etwas anfangen. Somit können Parameter wie Zuverlässigkeit, Geschwindigkeit und der Preis entscheiden.</p>
<p>Ich persönlich habe mich aufgrund
<a href="https://tools.rainerrose.de/redirect.php?id=31" target="_blank" rel="noopener noreferrer">Mariannes restic-Artikel<i class="fas fa-external-link-square-alt ms-1"></i></a>
ebenfalls für <del><em>Backblaze</em></del><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> entschieden. Das Tool <code>rclone</code> synchronisiert wie gefordert die dateibasierte Sicherung und unterstützt deren Produkt <em>B2 Cloud</em>.</p>

<h3 id="download-kostet-aber" data-numberify>Download kostet aber<a class="anchor ms-1" href="#download-kostet-aber"></a></h3>
<p><del><em>Backblaze</em></del><sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> verlangte für den Download der Daten Geld; inzwischen gibt es ein bestimmtes Freikontingent. Andere Anbieter verlangen allerdings auch zum Teil Download-Gebühren.</p>
<p>Hier stellte ich mir die Frage, wann ich jemals Download-Traffic in Anspruch nehmen werde. Ja genau, beim Recover. Und diesen Fall wollte ich erstens so selten wie möglich und wenn, dann ist der Cloud-Speicher ja <strong>einer</strong> meiner Standbeine.
Und im Fall einer Wiederherstellung sind mir die Daten so wichtig, dass ich das Geld auch gerne ausgebe, um die wiederzubekommen. Schließlich war genau dies der Zweck, den der Anbieter erfüllen sollte.</p>
<p>Im Bestfall kann der Großteil der Sicherung auch von der externen Festplatte gezogen werden und es müssen nur noch die Differenz aus der Cloud heruntergeladen werden. Dadurch minimiert sich dann die Größe der Dateien, die heruntergeladen werden müssen. Und damit sinken die Download-Kosten und natürlich auch die benötigte Zeit.</p>

<h4 id="beispiel" data-numberify>Beispiel<a class="anchor ms-1" href="#beispiel"></a></h4>
<p>Genau so einen Fall hatte ich vor einiger Zeit schon einmal. Ich wollte eine alte, externe Festplatte verschenken und daher diese sicher löschen.
Ich hatte leider nicht gut genug aufgepasst und mit <code>dc3dd</code> die falsche Partition geschreddert; inklusive der lokalen Backup-Kopie unter <code>/backup</code>.
Die letzte Sicherung auf der externen Festplatte war drei Wochen alt, weil ich wohl zu diesem Zeitpunt längere Zeit zu faul war sie anzustöpseln. Der Stand im Cloud-Speicher war aber vom Vortag. <em>Yay!</em> Das war meine Rettung. Einige E-Mails lagen eh noch auf dem IMAP-Server und ein paar Downloads wie Rechnungen, Kontoauszüge, musste ich dann eben neu herunterladen. Das Delta aus der Cloud war damit gering und ging schnell von statten. Im Grunde hatte ich kaum etwas verloren.
Ohne Cloud-Backup wären mir drei Wochen Daten flöten gegangen, es hat mir wortwörtlich den Hintern gerettet.</p>

<h3 id="jeder-ort-ein-repo" data-numberify>Jeder Ort ein Repo<a class="anchor ms-1" href="#jeder-ort-ein-repo"></a></h3>
<p>Hier gehe ich einen anderen Weg, als allgemein empfohlen. Viele Backup-Konzepte raten dazu für jedes Ziel (lokale Festplatte, externe Festplatte oder Cloud) einen komplett neuen Lauf zu starten.
Aus meiner Sicht laufen die Stände dann auseinander. Man hat überall unterschiedliche Stände. Wenn ich eine Datei wiederherstellen möchte, muss ich gucken welcher meiner Sicherungsorte die gewünschte Datei zu einem vorhandenen Sicherungszeitpunkt noch beinhaltet.
Außerdem dauern die Sicherungen länger, weil wieder alle Quell-Dateien neu eingelesen werden müssen, um zu schauen, welche Dateien sich geändert haben.
Dieser Schritt fällt bei mir weg, weil die Anzahl der Dateien in meinem Backup prinzipbedingt schon geringer ist und die Daten schon komprimiert wurden. Das Übertragen auf einen weiteren Ort, geht also wesentlich schneller.
Zudem müsste ich so mehrere Scripte vorhalten, die nur unterschiedliche Zielpfade haben, oder das Script so generisch schreiben, dass wiederum die Komplexität steigt und Wartbarkeit leidet.</p>
<p>In meinem Fall ist es ein einfacher <code>rsync</code> oder <code>rclone</code>-Aufruf.</p>

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

<h3 id="linux" data-numberify>Linux<a class="anchor ms-1" href="#linux"></a></h3>
<p>Ich habe mich bewusst gegen einen Automatismus an meinem persönlichen Desktop-PC entschieden. Es erfordert etwas Disziplin, hat sich bisher aber für mich bewährt.</p>
<p>Aktuell kann ich spontan entscheiden, ob ich den PC nur schnell runterfahren möchte oder ob ich ein Backup fahren möchte.
Wenn ich mich entscheide, kein Backup zu machen, weil ich z.B. nur schnell ein paar E-Mails geschrieben habe, kann ich den PC schneller wieder runterfahren. Das Ausschalten über die Steckerleiste hilft mir dann wieder Strom sparen.</p>
<p>Habe ich viele Änderungen an den Daten vorgenommen und entscheide ich mich <strong>für</strong> das Backup, wird eine lokale Kopie auf dem Rechner angelegt. Dies kann auch durchaus mehrmals am Tag sein. Am Ende des Tages setze ich dann per Script-Parameter, dass im Anschluss eine Synchronisation in die Cloud stattfinden soll. Ich kann dann den Rechner verlassen und mich anderen Dingen widmen. Etwas später, z.B. kurz vor dem ins Bett gehen, kann ich dann die Steckerleiste ausschalten. Die automatische Discord-Benachrichtigung zum Schluss des Scriptes erinnert mich daran.
Da ich das Script kurz vor dem Runterfahren des Rechners ausführe, ist auch egal wie lange es dauert.
Solange ich außerhalb jeglichen Glasfaser-Ausbau-Gebieten wohne, muss ich nun mal mit der langsamen Upload-Leitung leben.</p>

<h4 id="was" data-numberify>Was<a class="anchor ms-1" href="#was"></a></h4>
<p>Unter Linux sichere ich z.B. den <code>/etc</code>-Baum, wo viele Einstellungen und Konfigurationen liegen. Der größere Teil liegt dann im Benutzer-Verzeichnis (bei mir unterhalb von <code>/home</code>). Genauere Details dazu wird es mal in einem späteren Blog-Artikel geben.</p>

<h4 id="wiederherstellung" data-numberify>Wiederherstellung<a class="anchor ms-1" href="#wiederherstellung"></a></h4>
<p>Das letzte Mal, als ich mein System wiederherstellen musste, hat es gereicht <code>/etc</code> als auch <code>/home</code> neu einzuspielen und ich war überwiegend wieder arbeitsfähig. Die Programm-Installation unter Linux gestaltet sich relativ einfach und unkompliziert, zumindest bei den Dingen, die von der jeweiligen Distribution mitgeliefert werden.</p>

<h3 id="windows" data-numberify>Windows<a class="anchor ms-1" href="#windows"></a></h3>
<p>Bei Familienmitgliedern, die Windows verwenden, läuft das Backup-Script wiederum beim Anmelden am System. Ich habe noch keine Möglichkeit gefunden, dass es beim Herunterfahren ausgeführt wird.
Ein extra Klick mit einer Verknüpfung hat sich nicht bewährt, weil es gerne vergessen wird und der Rechner einfach nur stumpf via Windows-Menü heruntergefahren wird.
Durch den automatisierten Aufruf während des Logins, entfällt ein manueller Eingriff und es läuft jedes Mal.</p>

<h4 id="was-1" data-numberify>Was<a class="anchor ms-1" href="#was-1"></a></h4>
<p>Bei Windows im Enduser-Bereich sichere ich ebenfalls hauptsächlich den Benutzerordner, da dort vieles wichtiges abgelegt ist.
Mir ist klar, dass auch durchaus einige Einstellungen in der Registry stehen, aber für die weitere Recherche reicht meine Motivation nicht. Wahrscheinlich ist es eh besser, im K-Fall<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> das System komplett neu aufzusetzen und dann nur die Benutzerdaten wiederherzustellen.</p>

<h2 id="server" data-numberify>Server<a class="anchor ms-1" href="#server"></a></h2>
<p>Zu Hause läuft noch ein NAS-System mit virtuellen Servern. Dort wird mittels Crontab fast dasselbe Backup-Script ausgeführt. Der Sync in die Cloud findet allerdings täglich statt.
Da ich noch Webanwendungen betreibe, ist das Backup-Script etwas aufgebohrt. Je nach Dienst dreht es noch ein paar Schleifen.
Bei neueren Systemen habe ich sogar schon auf <code>resticprofile</code> umgestellt.</p>
<p>Beispielsweise hat das interne Kanboard eine Datenbank-Anbindung. Vor dem Start der dateibasierten Sicherung wird noch ein logischer Datenbank-Dump in ein Verzeichnis geschrieben, dass dann ebenfalls mitgesichert wird.
Ich weiß, dass es da z.T. bessere Möglichkeiten gibt, als ein schnöder <code>mysqldump</code>. Da ich schon mehrfach die Webanwendung <em>Kanboard</em> und andere Dienste wiederherstellen konnte, hat es für mich bisher so gereicht.</p>
<p>Anwendungen in Docker-Containern stoppe ich vorher, um einen konsistenten Stand zu sichern oder fahre, wie bei <em>paperless</em>, einen automatisierten Export der Daten in das Dateisystem.</p>

<h2 id="recover-testen" data-numberify>Recover testen<a class="anchor ms-1" href="#recover-testen"></a></h2>
<p>Auch das Thema Recover, also der Wiederherstellen der Daten, sollte man nicht unterschätzen. <strong>Testet Eure Backups</strong> und versucht die Daten wieder herzustellen!</p>
<p>Nichts ist schlimmer, als wenn man meint immer ein Backup gemacht zu haben und man dann feststellen muss, dass es kaputt ist oder wichtige Dinge fehlen.
Der Test schützt vor dem Verlust wichtiger Dateien bzw. Daten. Wenn Ihr Software wie zum Beispiel <em>Tandoor Recipes</em> aufsetzt, befüllt sie zu Anfang mit nur ein paar wenigen Daten. Dann macht Ihr ein Backup und löscht die komplette Installation. Im nächsten Schritt versucht Ihr die Anwendung oder auch die Daten aus dem Backup wieder herzustellen.</p>
<p>Pro-Tipp am Rande: <strong>Dokumentiert</strong> in diesem Fall gleich, wie ihr vorgegangen seid, die Daten wieder herzustellen.
Wenn Ihr Daten verloren habt, befindet Ihr Euch meist sowieso in einer Stress-Situation. Wenn ihr dann noch rausfinden müsst, wie ihr wieder an Eure Daten kommt und wie der alten Zustand wieder hergestellt werden kann, bedeutet das zusätzlichen Stress, den ihr mit diesem Vorgehen vermeiden könnt.
Eine gute Anleitung erleichtert später die Arbeit und trägt wesentlich zur Entspannung bei.</p>

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

<h3 id="sortierung-nach-wichtigkeit" data-numberify>Sortierung nach Wichtigkeit<a class="anchor ms-1" href="#sortierung-nach-wichtigkeit"></a></h3>
<p>Ich sortiere meine Backups in unterschiedliche Container bzw. Repositorys.</p>
<p>Es gibt persönlich mir wichtige Sachen, wie E-Mails und Bilder, selbst geschriebene Scripte etc.
Dann gibt es
nicht so wichtige Dinge, wie ISO-Dateien von gekauften CDs oder alter Installations-CDs von Steuer- oder Finanzsoftware.</p>
<p>Einige Dateien ziehe ich auch über das Netz, wie das Backup-Archiv vom NAS. Dies dauert dann länger und daher möchte ich das nicht immer machen.</p>
<p>Es gibt ein extra Repo und Script für die Sicherung meiner digitalen Bilder. Dies erfordert durchaus Disziplin, um da den Überblick zu behalten, aber die Bilder-Ordner erfahren aber auch nicht so oft eine Änderung, als dass das täglich nötig wäre.</p>
<p>Ebenso gibt es Backups-Repos, die nur auf der externen Festplatte landen und nicht in der Cloud.</p>

<h3 id="meta-daten" data-numberify>Meta-Daten<a class="anchor ms-1" href="#meta-daten"></a></h3>
<p>Ein paar wichtige Meta-Daten, wie die Aufteilung der Festplatte, schreibe ich ebenso automatisiert während des Backup-Laufs in einen speziellen Unterordner. Diesen sichere ich gleich mit.</p>
<p>Auch Infos zum LVM, Filesysteme oder installierte Pakete speichere ich in Textdateien und nehme diese Pfade in zu sichernden auf.</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="c1"># Partitionen</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">parted -l &gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">METAINFOSDIR</span><span class="si">}</span><span class="s2">/parted-l.txt&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Filesysteme</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">mount &gt;&gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">METAINFOSDIR</span><span class="si">}</span><span class="s2">/parted-l.txt&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># Software-Pakete</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">pacman -Qqet &gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">METAINFOSDIR</span><span class="si">}</span><span class="s2">/manjrao_installierte_pakete&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">pacman -Qqm &gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">METAINFOSDIR</span><span class="si">}</span><span class="s2">/manuell-installierte-pakete.txt&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># LVM-Infos</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">vgdisplay &gt;  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">METAINFOSDIR</span><span class="si">}</span><span class="s2">/lvm_infos.txt&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">pvdisplay &gt;&gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">METAINFOSDIR</span><span class="si">}</span><span class="s2">/lvm_infos.txt&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">lvdisplay &gt;&gt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">METAINFOSDIR</span><span class="si">}</span><span class="s2">/lvm_infos.txt&#34;</span>
</span></span></code></pre></div>
<h2 id="probleme-borg" data-numberify>Probleme: Borg<a class="anchor ms-1" href="#probleme-borg"></a></h2>
<p>Lange Zeit habe ich <code>borg</code> für meine Sicherungen benutzt. In der Vergangenheit hatte ich ein paar Unschönheiten, die ich aber lösen könnte.
Irgendwann hatte ich Probleme meine Sicherung in die Cloud zu schieben; doch der Reihe nach.</p>

<h3 id="anderer-ort" data-numberify>Anderer Ort<a class="anchor ms-1" href="#anderer-ort"></a></h3>
<p>Dadurch, dass ich meine Repos immer nur per <code>rsync</code> übertrage, nörgelt <code>borg</code>, wenn ich mal einen Blick in das Archiv werfen wollte, was auf einer externen Festplatte liegt.</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">borg list bilder
</span></span><span class="line"><span class="ln">2</span><span class="cl">Warning: The repository at location /run/media/rainer/backup_hdd_1/backup_nuc/bilder was previously located at /backup/borgbackup/bilder
</span></span><span class="line"><span class="ln">3</span><span class="cl">Do you want to continue? [yN]
</span></span></code></pre></div><p>Vielleicht war es ein Bug oder es gab ein anderes Problem. Es gab mal eine Zeit, wo genau dieser der Zugriff außerhalb des Original-Ortes meiner Erinnerung nach verweigert wurde. Eine Lösung hatte ich damals gefunden. Durch das oben beschriebene Konzept mit <code>rsync</code> konnte ich es z.B. wieder an den ursprünglichen Ort kopieren. Danach war der Zugriff wieder möglich. Ich kann es derzeit nicht mehr nachstellen; aktuell scheint es wieder zu gehen, wenn die Frage einfach bejaht wird.
Es ist mir negativ im Gedächtnis geblieben und es schwirrte schon lange die Idee in meinem Kopf herum, dass ich mich um eine Alternative zu <code>borg</code> kümmern müsse.</p>

<h3 id="große-dateien" data-numberify>Große Dateien<a class="anchor ms-1" href="#große-dateien"></a></h3>
<p>Ausschlaggebend für den endgültigen Wechsel war ein Problem mit <del><em>Backblaze</em></del><sup id="fnref2:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> vor einiger Zeit.</p>
<p>Wie weiter oben beschrieben habe ich mehrere Repos. Eines der größeren Archive mit ca. 66 GB machte beim Upload nach B2 Probleme:</p>
<p>Das Logfile war sehr hilfreich - nicht:</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"> data/0/31: Error sending chunk 0 (retry=true): incident id ec43eb366fa6-744fae19214412f6 (500 internal_error): &amp;api.Error{Status:500, Code:&#34;internal_error&#34;, Mes 
</span></span></code></pre></div><p>Ich lernte, dass es bei größeren Repos, wie dem 66 GB in meinem Fall,  irgendwann recht große Dateien (ca. 500MB pro Stück) mit <code>borg</code> entstehen.</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">ls -hl data/0/ |head
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">insgesamt 46G
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">-rw------- 1 root root 503M 29. Sep 2022  10
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">-rw------- 1 root root 504M 11. Okt 2022  100
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">-rw------- 1 root root 503M 11. Okt 2022  101
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">-rw------- 1 root root 501M 11. Okt 2022  102
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">-rw------- 1 root root 503M 11. Okt 2022  103
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">-rw------- 1 root root 503M 11. Okt 2022  104
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">-rw------- 1 root root 502M 11. Okt 2022  105
</span></span><span class="line"><span class="ln">10</span><span class="cl">-rw------- 1 root root 504M 11. Okt 2022  106
</span></span><span class="line"><span class="ln">11</span><span class="cl">-rw------- 1 root root 501M 11. Okt 2022  107
</span></span></code></pre></div><p>Dieses führte beim Upload zu <del><em>Backblaze</em></del><sup id="fnref3:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Problemen und warf daher mehrere Fehler beim Ausführen von <code>rclone</code>.</p>
<p>Bei meinen Recherchen in diesem Internet fand ich folgenden Blog-Artikel mit dem Titel
<a href="https://tools.rainerrose.de/redirect.php?id=32" target="_blank" rel="noopener noreferrer">What To Do When You Get a B2 503 (or 500) Server Error<i class="fas fa-external-link-square-alt ms-1"></i></a>.</p>
<p>Im Grunde steht da nur stumpf, dass man es nach einer gewissen Zeit nochmal probieren soll. Nach ein paar Tagen mit dem selben Fehlerbild war ich genervt und las den Artikel nochmal genauer. Dort wird auf
<a href="https://tools.rainerrose.de/redirect.php?id=33" target="_blank" rel="noopener noreferrer">B2 error handling protocols<i class="fas fa-external-link-square-alt ms-1"></i></a>
verwiesen, wo unter dem Absatz &ldquo;<em>Multithreading Uploads</em>&rdquo;
Hinweise in Bezug auf Dateien größer als 200 MB gegeben werden. Diese Dateien sollen beim Upload gesplittet werden und dann parallel hochgeladen werden. Ich vermute mal, dass das <code>rclone</code> unter der Haube macht; nachgeschaut habe ich nicht.</p>
<p>Bisher war nur ein Repo betroffen und andere Repos konnte ich problemlos nach <del><em>Backblaze</em></del><sup id="fnref4:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> hochladen bzw. synchronisieren mittels <code>rclone</code>.</p>
<p>Große Lust dem <del><em>Backblaze</em></del>-Support<sup id="fnref5:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> hinterherzulaufen oder debugging im <code>rclone</code>-Umfeld zu betreiben, hatte ich nicht.</p>

<h2 id="umstieg-auf-restic" data-numberify>Umstieg auf restic<a class="anchor ms-1" href="#umstieg-auf-restic"></a></h2>
<p>Damit war die Entscheidung gefallen; ich würde alle meine Backup-Scripte auf <code>restic</code> umstellen. Ein kleiner Test ergab, dass ein <code>restic</code>-Lauf zwar wesentlich mehr Dateien der selben Quell-Dateien ergab, aber auch wesentlich kleinere.
Da ich alle meine Backups in einem Unterverzeichnis sammle, war die Analyse mittels <code>find</code> recht simpel.</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 borgbackup -size +200M <span class="p">|</span> wc -l
</span></span></code></pre></div><p>Das Ergebnis: 816 Treffer.</p>
<p>Mein Testverzeichnis mit dem selbigen problembehafteten Dateien bzw. Repo in <code>restic</code> gebaut ergab keine Treffer</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 resticbackup -size +200M <span class="p">|</span> wc -l
</span></span></code></pre></div><p>und es ging <strong>wesentlich</strong> schneller. Alleine der initiale Durchlauf beim allerersten Erstellen des Archivs war um Zeitfaktor 10 schneller. Es waren nur zwei statt zwanzig Minuten nötig, was mich zusätzlich bestärkt hat umzusteigen.</p>

<h2 id="erleichterungen" data-numberify>Erleichterungen<a class="anchor ms-1" href="#erleichterungen"></a></h2>
<p>Um mich noch etwas unabhängiger im K-Fall zu machen, speichere zusätzlich das <code>restic</code>-Binary noch im Verzeichnis aller restic-Repos. Dieses wird dann ebenfalls via <code>rsync</code> mit auf die externen Festplatten kopiert, so dass ich immer eine aktuelle Version habe.</p>
<p>Für Neuigkeiten abonniere ich noch den RSS-Feed und habe einen
Watcher auf die <em>Github</em> Releases. Aber das ist einen extra Blog-Artikel wert.</p>
<p>Tipps und Tricks zu Bedienung lege ich in eine extra Datei namens <code>restic-hilfe.md</code>. Hier stehen dann die wichtigsten Befehle zum Wiederherstellen der Dateien und zum Mounten des gewünschten Repos ins aktuelle Dateisystems.</p>
<p>Alles im Sinne der Stress-Reduzierung während eines potentiellen Recover-Scenarios.</p>

<h2 id="ende" data-numberify>Ende<a class="anchor ms-1" href="#ende"></a></h2>
<p>Ich hoffe, ich konnte ein paar Einblicke in meine Überlegung zu meiner Backup-Strategie geben.
Vielleicht hilft es dem einem oder anderen ein Konzept selbst zu überlegen oder sein oder ihr existierendes anzupassen.</p>
<p>Wenn Ihr ich jetzt nach den Scripten fragt, muss ich Euch auf die Zukunft vertrösten.
Die Scripte selbst sind einen eigen Blog-Artikel wert.</p>

<h2 id="ausblick-update" data-numberify>Ausblick (Update)<a class="anchor ms-1" href="#ausblick-update"></a></h2>
<p>Inzwischen habe ich auf den Chemnitzer Linux-Tagen einen Vortrag über <code>restic</code> gehalten.
Auch habe ich vor demnächst<sup>tm</sup> die Scripte auf <code>resticprofile</code> umzustellen.
Über beide Tools erzähle ich was in meinem <a href="/docs/writings/2025/">Vortag</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>23.12.2023</td>
          <td>Fix: Typos, Grammatik</td>
      </tr>
      <tr>
          <td>03.01.2024</td>
          <td>Fix: Typo</td>
      </tr>
      <tr>
          <td>28.01.2025</td>
          <td>Fix: Typo</td>
      </tr>
      <tr>
          <td>27.03.2025</td>
          <td>Hinweis auf meinen <code>restic</code>/<code>resticprofile</code> CLT-Vortrag, Definition 3-2-1 nochmal deutlicher</td>
      </tr>
      <tr>
          <td>08.05.2025</td>
          <td>Hinweis auf <code>CACHEDIR.TAG</code>. Ein paar Umformulierungen und Typos fixed</td>
      </tr>
      <tr>
          <td>29.09.2025</td>
          <td>Besser verdeutlicht, dass ich Backblaze nicht mehr verwende.</td>
      </tr>
      <tr>
          <td>15.01.2026</td>
          <td>Ein paar Typos korrigiert.</td>
      </tr>
  </tbody>
</table>

<h2 id="fußnoten" data-numberify>Fußnoten<a class="anchor ms-1" href="#fußnoten"></a></h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Diese Angabe stimmt inzwischen nicht mehr. Im Rahmen von <code>#UnPlugTrump</code> bin ich zu <em>Hetzner</em> <em>StorageShare</em> gewechselt.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref4:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref5:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Krisen-Fall&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/wallpaper/posts/backupkonzept.png" length="24827" type="image/.png" />
    </item>
    
    <item>
      <title>update_docker_images.sh</title>
      <link>https://www.rainerrose.de/posts/update_docker_images/</link>
      <pubDate>Thu, 30 Nov 2023 22:54:19 +0200</pubDate>
      <guid>https://www.rainerrose.de/posts/update_docker_images/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Beim Arbeiten mit vielen <code>docker-compose.yml</code> Dateien habe ich öfter die Anforderung gehabt, <strong>einzelne</strong> Images neu zu pullen. In der Regel reicht ein <code>docker compose pull</code>, was aber heißt, dass er <strong>alle</strong> images zieht.</p>
<p>Es gibt jedoch bei mir durchaus öfters den Fall, dass ich dies nicht möchte. Zudem hatte ich einige Container, wo nur auf ein bestimmtes Versions-Tag (z.B. <code>mysql:8</code>) gepulled wird oder ich sogar mit <code>latest</code> arbeite. Dies bedeutete, dass ich mir jedes Image einzeln aus der docker-compose-datei rauspfriemeln musste.
Damit ich im Wiederherstellungsfall das alte Image schneller wiederfinde, wollte ich das speziell taggen.
In der Regel habe ich an den Namen des Tags ein <code>_backup</code> dran gehängt. Vorher musste das vorherige Image mit diesem Tag gelöscht werden. Diese manuelle Arbeit habe ich nun in ein Script gegossen.</p>
<p><strong>Update 30.11.2023:</strong> siehe unten Changelog</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Beim Arbeiten mit vielen <code>docker-compose.yml</code> Dateien habe ich öfter die Anforderung gehabt, <strong>einzelne</strong> Images neu zu pullen. In der Regel reicht ein <code>docker compose pull</code>, was aber heißt, dass er <strong>alle</strong> images zieht.</p>
<p>Es gibt jedoch bei mir durchaus öfters den Fall, dass ich dies nicht möchte. Zudem hatte ich einige Container, wo nur auf ein bestimmtes Versions-Tag (z.B. <code>mysql:8</code>) gepulled wird oder ich sogar mit <code>latest</code> arbeite. Dies bedeutete, dass ich mir jedes Image einzeln aus der docker-compose-datei rauspfriemeln musste.
Damit ich im Wiederherstellungsfall das alte Image schneller wiederfinde, wollte ich das speziell taggen.
In der Regel habe ich an den Namen des Tags ein <code>_backup</code> dran gehängt. Vorher musste das vorherige Image mit diesem Tag gelöscht werden. Diese manuelle Arbeit habe ich nun in ein Script gegossen.</p>
<p><strong>Update 30.11.2023:</strong> siehe unten Changelog</p>
<p>Das Script was auf <a href="https://gitlab.com/rainerrose/cli-tools/-/blob/main/docker/update_docker_images.sh" target="_blank" rel="noopener noreferrer">GitLab<i class="fas fa-external-link-square-alt ms-1"></i></a> liegt, durchpflügt die <code>docker-compose.yml</code>-Datei (parameterisierbar in der Datei) und fragt interaktiv nach, ob ein neues Image gepulled werden soll.</p>
<p>Wenn ja (default), dann legt das Script vorher ein <em>&ldquo;Backup&rdquo;</em> des Containers an, indem es an den Tag-Namen des aktuellen Containers ein <code>_backup</code> dran hängt.
Vorher wird das evtl. vorhandene und so benannte Image noch gelöscht, damit die Festplatte nicht platzt.
Das Suffix des Tags ist anpassbar im Script.</p>
<p>Bei jedem Durchlauf werden nochmal alle passenden Images angezeigt, damit alte Images nochmal aufgelistet werden. So fallen ungetaggte Images mit z.B. <code>&lt;none&gt;</code> leichter auf.</p>
<p>Die Ausgabe sieht dann z.B. so aus:</p>
<pre><code>Update docker image: mysql:8 [j]|n: 
Lösche evtl. Backups
docker images mysql:8_backup
LINES_MIT_HEADER=2
lösche altes Image
docker image rm mysql:8_backup
Untagged: mysql:8_backup
Untagged: mysql@sha256:566007208a3f1cc8f9df6b767665b5c9b800fc4fb5f863d17aa1df362880ed04
Deleted: sha256:b2013ac9910129ded1da4c96495142b2ed0638ddf7e86e65156400d9a8503777
Deleted: sha256:d24171674816d2cdb9a7cfaf9980f3ba574930efe226ca7fa840084072974e08
Deleted: sha256:046ec448aa8eaaa27e4118211c9f77da24d35b8a84b25f997334c3473d14a870
Deleted: sha256:ab05f09658d83ed8ff487d346fcc9fa4cecf5a23906a7e7feffaea83a1332a14
Deleted: sha256:96db44e22a968a3a16890685d95bd96d420d99a8880966056721c36842fa38af
Deleted: sha256:402981938079002ce0b77d9fe7a892608155b3866ad1967313b4934d0d8a6d3c
Deleted: sha256:e38cf6d3efcf0edd5d80758556fdafa43e9e3136edbc05928febf8771b1cba31
Deleted: sha256:0d197db846f4e62ab3d29cef5d359f5896ddd6869ba49608f276741da328ef29
Deleted: sha256:121010cc4276929c2ba287fe57dfd0c61483862809dfc884c2aa3d998d6cc292
Deleted: sha256:b72678d2fc4485cc1febd27ab3cc6fe2ddc9ef62b2f016aaa8620040a339677f
Deleted: sha256:b42107e741524c1f305a1a4649a4d96a5921c48c5b8c438ad871ab7cc5815231
Lege Backup an 
docker tag mysql:8 mysql:8_backup
8: Pulling from library/mysql
210eb976c4c7: Pull complete 
ebb4c64fd647: Pull complete 
cb8397d67a0f: Pull complete 
ff0e9bc8aa4e: Pull complete 
e6f52b272e7f: Pull complete 
e2d9e8e437ee: Pull complete 
e61517738248: Pull complete 
cfb2700e7252: Pull complete 
4044404847dd: Pull complete 
c142db3aeff8: Pull complete 
Digest: sha256:1ee299bf9eb8d2218fcb4fad666a090c92caef48ce524e6edce35f2e2d55170d
Status: Downloaded newer image for mysql:8
docker.io/library/mysql:8
REPOSITORY   TAG        IMAGE ID       CREATED        SIZE
mysql        8_backup   8da80fe49fcf   4 weeks ago    577MB
mysql        8          2d9aad1b5856   2 months ago   574MB
</code></pre>

<h2 id="besonderheit" data-numberify>Besonderheit<a class="anchor ms-1" href="#besonderheit"></a></h2>
<p>Bei einigen Containern arbeite ich mit konkreten Versionsnummern in den Tags.</p>
<p>Beispiel-Ausschnitt aus einer <code>docker-compose.yml</code></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">image</span><span class="p">:</span><span class="w"> </span><span class="l">onlyoffice/documentserver:7.5.1.1</span><span class="w">
</span></span></span></code></pre></div><p>In diesem Fall muss die obige Abfrage</p>
<pre><code>Update docker image: onlyoffice/documentserver:7.5.1.1 [j]|n: 
</code></pre>
<p>mit <code>n</code> für <em>nein</em> beantwortet werden. Hier findet ja gerade eine Versionierung bzw. Backup über den Namen des Tags statt. Zudem kann ich hier schlecht die nächste Versionsnummer erraten.
Weiterhin kann ja auch ein Überspringen einer Subversion gewünscht sein.
Da die Images sowieso anhand Ihres Tags schon als Image-Version vorliegen, muss die Version somit im  <code>docker-compose.yml</code> -File angepasst werden.</p>
<p>Dieses Script ist somit nur für die fixen Version-Strings wie oben bei <em>mysql</em> oder dem oben erwähnten <em>lastest</em> geeignet, erleichert aber meinen Alltag ungemein.</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>30.11.2023</td>
          <td>Teaser erweitert, einige Sätze ausgebessert. Letzten Absatz &ldquo;Besonderheit&rdquo; hinzugefügt.</td>
      </tr>
  </tbody>
</table>]]></content:encoded>
    <enclosure url="https://www.rainerrose.de/wallpaper/posts/update_docker_images.png" length="33926" type="image/.png" />
    </item>
    
    <item>
      <title>vim</title>
      <link>https://www.rainerrose.de/docs/linux/vim/</link>
      <pubDate>Thu, 04 May 2023 17:39:42 +0200</pubDate>
      <guid>https://www.rainerrose.de/docs/linux/vim/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Tipps und Tricks zum Editor vim.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Tipps und Tricks zum Editor vim.</p>
<ul>
<li><code>set ignorecase</code> Groß-/Kleinschreibung egal</li>
<li><code>set smartcase</code> wenn Pattern kleingeschrieben: Groß-/Kleinschreibung egal, sonst <em>case sensitive</em></li>
<li><code>:set relativenumber</code> relative Zeilennummern</li>
<li><code>:set number</code> Zeilennummern ein</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vim" data-lang="vim"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">colorscheme</span> <span class="nx">darkblue</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">nmap</span> <span class="p">&lt;</span><span class="nx">F8</span><span class="p">&gt;</span> :<span class="nx">TagbarToggle</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">nmap</span> <span class="p">&lt;</span><span class="nx">F9</span><span class="p">&gt;</span> :<span class="p">!</span><span class="nx">ctags</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="k">set</span> <span class="nx">backup</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c">&#34; automatically indent lines (default)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c">&#34; set noautoindent</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">set</span> <span class="nx">autoindent</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c">&#34; select case-insenitiv search (not default) set noignorecase</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c">&#34; Suchergebnisse hervorheben</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">set</span> <span class="nx">hlsearch</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c">&#34; show cursor line and column in the status line set ruler</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c">&#34; show matching brackets</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">set</span> <span class="nx">showmatch</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c">&#34; show existing tab with 4 spaces width</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">set</span> <span class="nx">tabstop</span><span class="p">=</span><span class="m">4</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c">&#34; when indenting with &#39;&gt;&#39;, use 4 spaces width set shiftwidth=4 </span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c">&#34; On pressing tab, insert 4 spaces set expandtab</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c">&#34; display mode INSERT/REPLACE/...</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">set</span> <span class="nx">showmode</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c">&#34; changes special characters in search patterns (default)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c">&#34; set magic</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c">&#34; Required to be able to use keypad keys and map missed escape sequences set esckeys</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></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">syntax</span> <span class="nx">on</span>
</span></span></code></pre></div>]]></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>Nostalgie-Script</title>
      <link>https://www.rainerrose.de/docs/linux/nostalgie-script/</link>
      <pubDate>Sat, 02 May 2020 15:25:17 +0100</pubDate>
      <guid>https://www.rainerrose.de/docs/linux/nostalgie-script/</guid>
      <description><![CDATA[<!-- Anreißer -->
<p>Nach diesem Script habe ich mich in den Anfängen meiner Linux-Zeit[^1) dumm und dämlich gesucht. Aus Nostalgie-Gründen (und vielleicht braucht man es ja doch noch mal, paste ich es hier nochmal rein. Alles andere kommt inzwischen ins git.</p>]]></description>
      <content:encoded><![CDATA[<!-- Anreißer -->
<p>Nach diesem Script habe ich mich in den Anfängen meiner Linux-Zeit[^1) dumm und dämlich gesucht. Aus Nostalgie-Gründen (und vielleicht braucht man es ja doch noch mal, paste ich es hier nochmal rein. Alles andere kommt inzwischen ins git.</p>

<h2 id="aufgabe" data-numberify>Aufgabe<a class="anchor ms-1" href="#aufgabe"></a></h2>
<p>Es ersetzt in allen Dateien der angegebenen Verzeichnisse, das per
Parameter angegebene Suchmuster.</p>
<p>Gebraucht habe ich es mal für meinen HTML-Seiten wo ich &quot;EUR&quot; durch
&quot;€&quot; ersetzen wollte.</p>

<h2 id="anwendung" data-numberify>Anwendung<a class="anchor ms-1" href="#anwendung"></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">usage: gsed sed-command <span class="o">[</span>dir ...<span class="o">]</span>
</span></span></code></pre></div><p><code>sed-command</code> entspricht dabei den normalen <code>sed</code> Parameter. Siehe man-Page.</p>

<h3 id="einige-anwendungsbeispiele" data-numberify>Einige Anwendungsbeispiele:<a class="anchor ms-1" href="#einige-anwendungsbeispiele"></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">gsub s/EUR/<span class="se">\\\&amp;</span>euro<span class="se">\;</span>/g <span class="c1"># ====&gt; EUR =&gt; &amp;euro;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">gsub s/<span class="se">\Ä</span>/<span class="se">\\\&amp;</span>Auml<span class="se">\;</span>/g <span class="c1"># ====&gt; Ä =&gt; &amp;Auml; (Groß- Kleinschreibung beachten !)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">gsub s/<span class="se">\Ä</span>/<span class="se">\\\&amp;</span>Auml<span class="se">\;</span>/gi <span class="c1"># ====&gt; Ä =&gt; &amp;Auml; (Groß- Kleinschreibung egal !)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">gsub s/<span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se">\&lt;\\\/</span>td<span class="se">\&gt;</span>/<span class="se">\&lt;\\\/</span>td<span class="se">\&gt;</span>/g <span class="c1"># =====&gt; &lt;br&gt;&lt;/td&gt; =&gt; &lt;/td&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">gsub s/http<span class="se">\:\\</span>/<span class="se">\\</span>/www./..<span class="se">\\</span>/img/g <span class="c1"># ====&gt; http://www. =&gt; /img</span>
</span></span></code></pre></div>
<h3 id="prinzip" data-numberify>Prinzip:<a class="anchor ms-1" href="#prinzip"></a></h3>
<p><code>s/raus/rein/g</code></p>

<h3 id="weiterer-tipp" data-numberify>Weiterer Tipp<a class="anchor ms-1" href="#weiterer-tipp"></a></h3>
<p>Und wenn das ersetzen nicht klappt ruhig mal öfters ein \ setzen, wie
bei:</p>
<ul>
<li><code>\\\&amp;</code></li>
<li><code>\\/</code></li>
</ul>

<h2 id="quell-code" data-numberify>Quell-Code<a class="anchor ms-1" href="#quell-code"></a></h2>
<div class="highlight" title="gsub.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="c1">#!/bin/sh</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1">#</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># Global sed on all files contained in a list of directories.</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># The command</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># gsub cmd dir1 dir2 ...</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># will do</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="c1"># sed -e cmd</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># on each file found in dir1, dir2, ...</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1">#</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># If a file is changed, a backup copy is created.</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># Files .*.swp and CVS directories are skipped.</span>
</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">     
</span></span><span class="line"><span class="ln">15</span><span class="cl">    usage<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;usage: gsed sed-command [dir ...]&#34;</span> &gt;<span class="p">&amp;</span> <span class="m">2</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="nb">exit</span> <span class="m">1</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="nv">tmp</span><span class="o">=</span>/var/tmp/gsub.<span class="nv">$$</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">     
</span></span><span class="line"><span class="ln">22</span><span class="cl">     
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nb">trap</span> <span class="s2">&#34;rm -f </span><span class="nv">$tmp</span><span class="s2">&#34;</span> <span class="m">0</span> <span class="m">1</span> <span class="m">2</span> <span class="m">3</span> <span class="m">15</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">     
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">if</span> <span class="o">[</span> <span class="nv">$#</span> -lt <span class="m">1</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    usage
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">fi</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></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="nv">cmd</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">     
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nb">shift</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="k">if</span> <span class="o">[</span> <span class="nv">$#</span> <span class="o">=</span> <span class="m">0</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="nb">set</span> .
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">     
</span></span><span class="line"><span class="ln">38</span><span class="cl">     
</span></span><span class="line"><span class="ln">39</span><span class="cl">    find <span class="nv">$*</span> -name CVS -prune -o <span class="se">\(</span> -type f ! -name <span class="s1">&#39;*~&#39;</span> ! -name <span class="s1">&#39;.*.swp&#39;</span> -print <span class="se">\)</span> <span class="p">|</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">while</span> <span class="nb">read</span> file<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    sed -e <span class="s2">&#34;</span><span class="nv">$cmd</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span> &gt; <span class="s2">&#34;</span><span class="nv">$tmp</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">if</span> cmp -s <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$tmp</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    :
</span></span><span class="line"><span class="ln">44</span><span class="cl">     
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">: patched&#34;</span> &gt;<span class="p">&amp;</span> <span class="m">2</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    mv <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">~&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    cp <span class="s2">&#34;</span><span class="nv">$tmp</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">     
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">done</span>
</span></span></code></pre></div>
<h2 id="dank" data-numberify>Dank<a class="anchor ms-1" href="#dank"></a></h2>
<p><strong>Dank an Matthias Kilian von der LUG-Braunschweig</strong> :!:</p>
<p>Original-Ton aus seiner Mail:</p>
<blockquote>
<p>Bitte nach Bedarf tunen. [Ja, ich weiß, dass es böse ist, das
temporäre Verzeichnis fest zu verdrahten. Wer das nicht mag, kann ja
zusätzliche TMPDIR auswerten.]</p>
<p>Das Script ist sogar so nett, nur Dateien, die sich wirklich geändert
haben, zu überschreiben, bei unmodifizierten Dateien bleibt also die
Modificationtime unverändert. Außerdem wird bei modifizierten Dateien
eine Backupdatei angelegt.</p>
<p>Würde ich es heute noch mal schreiben, dann würde ich aber wohl statt
sed(1) den ed(1) nehmen, um mir den ganzen Kram mit temporären Dateien
zu ersparen.</p>
</blockquote>]]></content:encoded>
    </item>
    
  </channel>
</rss>

