Blog

Dissens

Trotz aller Bemühungen um Geschlossenheit sind sich die Unionsparteien noch nicht ganz einig, wie sie das Wirken des Bundesfinanzministers bewerten sollen. Während dem bayerischen Ministerpräsidenten im Mai Übles schwante

Ich habe im Moment etwas Sorgen, was den Bundeshaushalt angeht. Der Bundesfinanzminister hat vielleicht Luftbuchungen gemacht, die wir jetzt noch gar nicht überblicken. Nach der Bundestagswahl brauchen wir eine ehrliche Eröffnungsbilanz.

– verlässt sich der nordrhein-westfälische Ministerpräsident kurz vor der Wahl auf den Anschein von Solidität und im Übrigen auf die Bundeskanzlerin, die auf den Finanzminister aufgepasst habe.

Unprofessionelle Tierquälerei

Die Aussetzung einiger Ferkel in nordrhein-westfälischen Städten hat glücklicherweise zur Aufnahme der Tiere in Tierheimen und auf Gnadenhöfen geführt. Dennoch: Die kurze Zeit außerhalb der gewohnten Massenhaltung ohne Tageslicht war sicher traumatisch, und der Sprecher der Stadt Mülheim spricht ganz zu Recht von einem Skandal und von Tierquälerei. Wie viel Leid hätte den armen Ferkeln erspart werden können, wenn sie aus der Abferkelanlage in den Kastenstand gewechselt und nach der Intensivmast professionell getötet worden wären?

Ausreiseberechtigung

Der Bundesaußenminister hat klare Erwartungen an die neue, religiös geprägte Regierung Afghanistans:

Wir erwarten, dass Menschen, die ausreiseberechtigt sind, das Land auch verlassen dürfen.

Es ist wirklich sehr fürsorglich, dass ein deutscher Politiker die Taliban vor einer contradictio in adiecto bewahrt, und der leicht paternalistische Tonfall ist schon ein Fortschritt gegenüber einer kolonialen Attitüde, in der die deutsche Bundesregierung Ausreiseberechtigte in anderen Ländern definiert.

Regionalvolkspartei

Da sich der Unterschied zwischen einer Klientelpartei und einer Volkspartei mittlerweile nicht mehr an Umfragewerten illustrieren lässt, hat Markus Söder noch einmal den semantischen Kern des Begriffs Volkspartei hervorgehoben, und um jede Ambiguität in Bezug auf den Wortbestandteil Volk aufzulösen, erläutert Alexander Dobrindt, auf welches Volk die CSU auch die Leistungen eines Bundesministers bezieht:

Scheuer macht eine erstklassige Arbeit als Minister, hat Milliarden für Verkehrsprojekte nach Bayern gebracht

Update (2021-09-10): Bei dieser Bewertung handelt es sich nicht um eine Einzelmeinung des CSU-Landesgruppenchefs.

Tunnussana

Ich hätte mir denken können, dass Randall Munroe nicht nur Dr. Drang, sondern auch die Python-Community inspiriert, und der mit xkcdpass gelieferte Korpus KOTUS lässt keine Wünsche offen. Mein neues Master-Passwort (virtaama noituus pyörö viritä aloitus poreilla) ist jedenfalls sehr schön geworden.

Merger

Einige Jahre nach dem erfolgreichen Outsourcing des Literaturnetzes haben sich die Rahmenbedingungen geändert und der Betrieb einer eigenen Django-Umgebung für literarische Texte ist unwirtschaftlich geworden (was ausdrücklich nicht als Ausdruck der Geringschätzung von Kunst und Kultur verstanden werden sollte).

Im Gegensatz zur relativ einfachen Separation ist die Zusammenführung der Daten auf Grund unterschiedlicher Tabellenstrukturen aber nicht ganz einfach, und sowohl Djangos Serialisierungsfunktionen als auch die Nutzung des Django ORM scheitern an der Verwendung zweier Django-Umgebungen in einem Skript. Auf Datenbankebene lassen sich die Tabellen authors und pages durch Ergänzung von models.py und anschließende DB-Migrationen vorbereiten, und der Transfer der Autorinnen funktioniert mit pg_dump (auch wenn der django_content_type author durch die Migrationen nicht erzeugt, sondern manuell nachgetragen werden muss). Die Tabelle pages enthält aber einen selbstreferentiellen Fremdschlüssel (mother_id), so dass die Datensätze wie folgt transferiert werden müssen:

#! /usr/local/bin/python3 import psycopg2 con_source = psycopg2.connect(database="db1", user="user", password="pw") con_target = psycopg2.connect(database="db2", user="user", password="pw") print("Databases opened successfully") def move_page(id, target_mother): cursor_source = con_source.cursor() cursor_source.execute(f'SELECT title, asin, content, author_id, mod_date, position, expansion, docbook, epub, url_name from pages WHERE id = {id}') print(f'selecting page {id} from literaturnetz.pages...') current_page = cursor_source.fetchone() # adding the target mother and the standard page type to the db values... current_page = current_page + (target_mother, 'P', False, False, False, '') cursor_target = con_target.cursor() print(f'inserting page {id} into mysites.pages...') sql_string = "INSERT INTO pages (title, asin, content, author_id, create_date, position, expansion, docbookcontent, epubcontent, url_name, mother_id, page_type, texcontent, pdfcontent, private, short_name) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) RETURNING id;" cursor_target.execute(sql_string, current_page) new_id = cursor_target.fetchone()[0] print(f'new id for current page is {new_id}...') print(f'selecting children of page {id} from literaturnetz.pages...') cursor_source.execute(f'SELECT id from pages WHERE mother_id = {id}') rows = cursor_source.fetchall() if rows: for row in rows: move_page(row[0], new_id) move_page(11976, 1) con_target.commit() print("Operation completed successfully") con_source.close() con_target.close()

Die ePub- und DocBook-Versionen einiger Seiten müssen manuell übertragen werden. Die Kinder einer einzigen Seite sorgen mit inkonsistenten Werten im Feld position für Fehlermeldungen (MultipleObjectsReturned für die Geschwister-Funktion), aber im Wesentlichen fügen sich die neuen Inhalte gut ein. Die Erzeugung statischer Seiten funktioniert ebenfalls auf Anhieb, allerdings werden bestimmte Seiten während des Abgleichs mit bereits vorhandenen HTML-Dateien –

changed = False if os.path.exists(filename): existing_file = io.open(filename, encoding='utf-8') existing_content = existing_file.read() existing_file.close() if existing_content != content: changed = True

– bei jedem Durchlauf als geändert markiert. Dieses Problem wird durch special characters verursacht, die offensichtlich beim Lesen der UTF-8-Datei und der Generierung des neuen Inhalts unterschiedlich interpretiert werden. Ich hatte vergeblich gehofft, mich nach der flächendeckenden Durchsetzung von UTF-8 nicht mehr mit Textkodierungen befassen zu müssen, aber auch in der Django-Weboberfläche, in BBEdit und in vim bleiben die problematischen Zeichen ungreifbar. Erst die Django-Shell enthüllt ihren wahren Charakter als Wagenrücklauf und Tabulator (\r und \t) und ermöglicht die systematische Entfernung (page.content = page.content.replace('\\r', '')).

Innerhalb der Django-App wird die Logik zur Handhabung unterschiedlicher Autorinnen übernommen (mit umfangreichen Änderungen an models.py, views.py, forms.py, admin.py, Templates und Filtern), und die urheberrechtskonforme Zugriffsbeschränkung für unlängst verstorbene Autorinnen wird statisch auf der Datenbankebene (UPDATE pages SET private = True WHERE pages.id IN (SELECT pages.id FROM pages INNER JOIN authors ON (pages.author_id = authors.id) WHERE authors.death > '1950-12-31');) implementiert.

Schließlich kann ich nginx.conf guten Gewissens ergänzen:

server { listen 80; server_name literaturnetz.org www.literaturnetz.org; location = / { return 301 https://eden.one/literatur; } location / { return 301 https://eden.one$request_uri; } }

Beantwortbar

Mit meiner Abneigung gegen HTML-Mails bin ich nicht allein, denn auch jenseits der offensichtlich kriminellen Sphäre und schamloser Überwachung durch halbstaatliche Akteure werden die Möglichkeiten von HTML nach Kräften missbraucht. John Gruber befasst sich in einem Blog-Post mit dem Einsatz von tracking pixels und resümiert:

Don’t get me started on how predictable this entire privacy disaster was, once we lost the war over whether email messages should be plain text only or could contain embedded HTML. Effectively all email clients are web browsers now, yet don’t have any of the privacy protection features actual browsers do.

Leider kann ich seit kurzem HTML-Mails ohne Textfassung nicht mehr nur ignorieren oder im äußersten Fall an einen echten Browser übergeben, sondern muss sie auch beantworten. Wenn aber meine in mutt erstellte Antwort auf eine E-Mail wie folgt eingeleitet wird, verlieren die Empfängerinnen eventuell die Lust, weiterzulesen:

> <html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40"> > <head> > <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> > <meta name="Generator" content="Microsoft Word 15 (filtered medium)"> > <!--[if !mso]><style>v\:* {behavior:url(#default#VML);} > o\:* {behavior:url(#default#VML);} > w\:* {behavior:url(#default#VML);} > .shape {behavior:url(#default#VML);} > </style><![endif]--> > <style> <!-- > /* Font Definitions */ > @font-face > {font-family:"Cambria Math"; > panose-1:2 4 5 3 5 4 6 3 2 4;} > @font-face > {font-family:Calibri; > panose-1:2 15 5 2 2 2 4 3 2 4;} > /* Style Definitions */ > p.MsoNormal, li.MsoNormal, div.MsoNormal > {margin:0cm; > margin-bottom:.0001pt; > font-size:11.0pt; > font-family:"Calibri",sans-serif; > mso-fareast-language:EN-US;} > a:link, span.MsoHyperlink > {mso-style-priority:99; > color:#0563C1; > text-decoration:underline;} > a:visited, span.MsoHyperlinkFollowed > {mso-style-priority:99; > color:#954F72; > text-decoration:underline;} > p.msonormal0, li.msonormal0, div.msonormal0 > {mso-style-name:msonormal; > mso-margin-top-alt:auto; > margin-right:0cm; > mso-margin-bottom-alt:auto; > margin-left:0cm; > font-size:11.0pt; > font-family:"Calibri",sans-serif;} > span.E-MailFormatvorlage18 > {mso-style-type:personal; > font-family:"Arial",sans-serif; > color:windowtext;} > span.E-MailFormatvorlage19 > {mso-style-type:personal; > font-family:"Arial",sans-serif; > color:windowtext;} > span.E-MailFormatvorlage20 > {mso-style-type:personal; > font-family:"Calibri",sans-serif; > color:#1F497D;} > span.E-MailFormatvorlage21 > {mso-style-type:personal; > font-family:"Arial",sans-serif; > color:windowtext;} > span.E-MailFormatvorlage22 > {mso-style-type:personal; > font-family:"Arial",sans-serif;} > span.E-MailFormatvorlage24 > {mso-style-type:personal; > font-family:"Arial",sans-serif; > color:windowtext;} > .MsoChpDefault > {mso-style-type:export-only; > font-size:10.0pt;} > @page WordSection1 > {size:612.0pt 792.0pt; > margin:70.85pt 70.85pt 2.0cm 70.85pt;} > div.WordSection1 > {page:WordSection1;} > --> > </style> > > <!--[if gte mso 9]><xml> > <o:shapedefaults v:ext="edit" spidmax="1026" /> > </xml><![endif]--> > <!--[if gte mso 9]><xml> > <o:shapelayout v:ext="edit"> > <o:idmap v:ext="edit" data="1" /> > </o:shapelayout></xml><![endif]--> > </head> > <body lang="DE" link="#0563C1" vlink="#954F72"> > <div class="WordSection1">

Erfreulicherweise sind selbst Menschen, die HTML-Mails noch viel kritischer sehen –

HTML email is, without doubt, evidence of the imminent end of civilized life as we know it; much like the Golgafrincham diaspora, it is attributable to a depraved cabal of marketing consultants and provides the same level of social good as syphilis and fistulas. Suffice to say, it is a blight.

– lösungsorientiert und setzen auf eine Kombination aus dem textbasierten HTML-Browser w3m für ein mutt-internes Rendering und den regulären Browser als Rückfallposition:

# .muttrc auto_view text/html # view html automatically alternative_order text/plain text/enriched text/html # save html for last # .mailcap text/html; ~/scripts/view_attachment %s html text/html; w3m -I %{charset} -T text/html; copiousoutput;

Der Parameter copiousoutput sorgt dafür, dass w3m Vorrang vor dem Übergabeskript bekommt.

Backup 2021

Ohne konkreten Anlass überarbeite ich nach vier Jahren meine Backup-Strategie und füge ein zusätzliches NAS (mit 250GB SSD-Cache) als Primärspeicher und Medienserver hinzu. Peinlicherweise beginne ich bereits, mühsam gewonnene Erkenntnisse zu vergessen und versäume daher, den schlüsselbasierten SSH-Zugriff auf Anhieb richtig zu konfigurieren. Konkret ist das Home-Verzeichnis der SSH-Nutzerin allzu zugänglich, und nach einer halben Stunde erfolglosem Debugging erkundige ich mich etwas voreilig nach einer Lösung, die ich wenige Minuten später selbst finde, erst in einem fremden, dann in meinem eigenen Blog (chmod 700 ~). Auch die offensichtliche Tatsache, dass die Plex-Nutzerin Leserechte für die Medienordner benötigt, und dass für mein SFTP-Skript SFTP auch aktiviert sein sollte (File Services → FTP → Enable SFTP service), realisiere ich jeweils erst nach einigen Minuten. Vielleicht sollte ich meine Freizeit doch lieber damit verbringen, Sonette zu verfassen.

Immerhin stoße ich bei der Abkehr vom proprietären Hyper Backup-Format für den Abgleich von Datenständen zwischen den NAS-Systemen auf ein reales Problem, das viele Synology-Nutzerinnen nervt. Die Funktion Shared Folder Sync basiert auf rsync, ist aber sehr eigenwillig implementiert. Existierende Ordner auf dem Ziel-NAS, die zur Synchronisation förmlich einladen, werden zum Anlass genommen, neue Ordner anzulegen:

If on the destination exists a shared folder with the same name (e.g., SharedFolder) as the shared folder on the source, a new folder with a numbered name (e.g., SharedFolder_1) will be created at the destination when syncing.

Das ist nicht sehr günstig, wenn umfangreiche Ordner schon auf beiden beteiligten NAS existieren. Glücklicherweise gibt es einen Workaround:

  1. Alle zu synchronisierenden Ordner auf dem Ziel-NAS umbenennen (XXX_tmp)
  2. Einen Synchronisations-Task auf dem Quell-NAS anlegen, und für jeweils einen Quell-Ordner kurzzeitig (!) starten. Der neue Ziel-Ordner wird angelegt (mit ausgesprochen restriktiven Zugriffsrechten).
  3. Die Zugriffsrechte (Advanced Permissions) für die Ziel-Ordner anpassen (Entfernen der read only-Regel für die Admin-Gruppe, Einrichtung von Schreib-/Leserechten für die synchronisierende Benutzerin)
  4. Bewegen (Move (overwrite), nicht Copy) der Inhalte aus den umbenannten ursprünglichen Ordnern auf dem Ziel-NAS (XXX_tmp → XXX).
  5. Den Synchronisations-Task für alle Quell-Ordner konfigurieren und manuell starten.

Auf diesem Weg dauert die abschließende Synchronisation von mehr als 5 TB rund 5 Minuten.

Wenige Tage später tritt erstmals ein Problem auf, das ich schon vor Jahren erwartet hatte (und das bei anderen Nutzerinnen auch auftrat): Wie vom Security Audit anempfohlen, hatte ich im Zuge der NAS-Erstkonfiguration den allgemeinen SSH-Port (Control Panel → Terminal) geändert, nicht aber den SSH-Port für rsync (Control Panel → File Services → rsync). Nachdem meine rsync-Skripte lange Zeit trotz dieser offensichtlichen Diskrepanz funktioniert haben, wird das Thema Ports nach dem sonntäglichen DSM-Update plötzlich ernst genommen (Permission denied). Das Debugging dauert wieder etwas, die Fehlerbehebung selbst nicht. Warum aber jahrelang wohlwollend ein falscher Port akzeptiert wurde, bleibt mysteriös.

Nach der erfolgreichen Inbetriebnahme fällt mir auf, dass es bisher kein Backup der NAS-Konfiguration gab, was sich aber rasch korrigieren lässt (Update & Restore → Configuration Backup).

Erstaunlich schwierig gestaltet sich ein zusätzlicher Automatisierungsschritt für das bislang manuell ausgeführten Backup-Skript (ein Python-Wrapper, der Shortcuts für unterschiedliche rsync-Konfigurationen bereitstellt). Die Einstellung einer monatlichen Frequenz ist schon sehr viel umständlicher als ein entsprechender crontab-Eintrag, aber die entscheidende Hürde sind die Zugriffsberechtigungen für Anwendungen, die per launchd gestartet werden. Trotz vieler hilfreicher Foreneinträge, die mir raten, rsync, launchd und/oder der Shell (zsh/bash) Full Disk Access zu erteilen, verweigert das System rsync standhaft den Zugriff auf meine Ordner:

rsync: opendir "/Users/username/Backup" failed: Operation not permitted (1)

Andererseits funktioniert rsync mit derselben Konfiguration einwandfrei, wenn es direkt als LaunchAgent gestartet wird. Channing Walton bestätigt meinen Verdacht: Statt das Skript aufzurufen und auf die Shebang-Zeile zu vertrauen –

<key>ProgramArguments</key> <array> <string>/Users/jan/bin/rbackup.py</string> <string>nas1_core</string> </array>

– sollte der jeweilige Interpreter explizit in der property list für launchd genannt –

<key>ProgramArguments</key> <array> <string>/opt/homebrew/bin/python3</string> <string>/Users/jan/bin/rbackup.py</string> <string>nas1_core</string> </array>

und mindestens mit Zugriffsrechten auf ~ ausgestattet werden.

Dank launchd und Shared Folder Sync hält sich der regelmäßige Aufwand für die folgende Sicherungsmatrix in akzeptablen Grenzen:

DatenQuelleZielFrequenzMethodeScheduled
Dokumente / Konfiguration/Users/jan/Backupnas1:/Volume1/CoreBackupwöchentlich (So, 10 Uhr)rbackup.py
nas2:/Volume1/CoreBackupmonatlich (1. Sonntag, 11 Uhr)rbackup.py
nas3:/Volume1/CoreBackupmonatlich (3. Sonntag, 11 Uhr)rbackup.py
usbkey1:/CoreBackupwöchentlich (Sonntag)rbackup.py
usbkey2:/CoreBackupwöchentlich (Dienstag)rbackup.py
usbkey3:/CoreBackupwöchentlich (Donnerstag)rbackup.py
/Users/jan/CoreBackup.sparsebundlewöchentlich (Sonntag, 9 Uhr)rbackup.py
/Users/jan/CoreBackup.sparsebundleserver1:/home/backupwöchentlich (Montag, 10 Uhr)rbackup.py
server2:/home/backupwöchentlich (Mittwoch, 10 Uhr)rbackup.py
Bilder/Users/jan/Picturesnas1:/Volume1/Pictureswöchentlich (Sonntag, 12 Uhr)rbackup.py
nas2:/Volume1/Picturesmonatlich (1. Sonntag, 12 Uhr)rbackup.py
nas3:/Volume1/Picturesmonatlich (3. Sonntag, 12 Uhr)rbackup.py
Musik/Users/jan/Musicnas1:/Volume1/Musicmonatlich (1. Sonntag, 12 Uhr)rbackup.py
nas2:/Volume1/Musicmonatlich (3. Sonntag, 12 Uhr)rbackup.py
Büchernas1:/Volume1/Booksnas2:/Volume1/Booksmonatlich (1. Sonntag, 14 Uhr)Shared Folder Sync
nas3:/Volume1/Booksmonatlich (3. Sonntag, 15 Uhr)Shared Folder Sync
Zeitungennas1:/Volume1/Newsnas2:/Volume1/Newsmonatlich (1. Sonntag, 14 Uhr)Shared Folder Sync
nas3:/Volume1/Newsmonatlich (3. Sonntag, 15 Uhr)Shared Folder Sync
Filmenas1:/Volume1/Moviesnas2:/Volume1/Moviesmonatlich (1. Sonntag, 14 Uhr)Shared Folder Sync
TV-Seriennas1:/Volume1/TV Showsnas2:/Volume1/TV Showsmonatlich (1. Sonntag, 14 Uhr)Shared Folder Sync
Softwarenas1:/Volume1/Softwarenas2:/Volume1/Softwaremonatlich (1. Sonntag, 14 Uhr)Shared Folder Sync
[Data]/System/Volumes/Datahdd1:/monatlichSuperDuper!
hdd2:/monatlichSuperDuper!
hdd3:/vierteljährlichSuperDuper!
nas1:/Volume1/FullBackupwöchentlich (Sonntag)SuperDuper!

Die Fotobibliothek in /Users/jan/Pictures wird darüber hinaus in der iCloud gesichert. /Users/jan/Backup enthält hauptsächlich Symlinks auf verschiedene Verzeichnisse (~/...) und wird wöchentlich mit aktuellen Konfigurationsdateien des Webservers ergänzt.

Anders als noch vor einem Jahrzehnt nehme ich das Thema Ransomware sehr ernst und setze auf verschieden lange Backup-Zyklen, außerdem sind mittlerweile alle Backup-Medien verschlüsselt. Allerdings befinden sich die Speichermedien ausnahmslos auf dem Staatsgebiet der Bundesrepublik Deutschland – meine Daten werden also bestimmte apokalyptische Szenarien in Zentraleuropa nicht überstehen (abgesehen von den in Nordamerika gesicherten Fotos und Passwörtern).

Für eine vorwiegend nordamerikanische Apokalypse ist dagegen vorgesorgt: Der freundliche 1Password-Support hat mir für den Fall einer world catastrophe where all our servers have been wiped out den nicht ganz offensichtlichen Speicherort der lokalen 1Password-Datenbank (~/Library/Group Containers/2BUA8C4S2C.com.agilebits/Library/Application Support/1Password/Data) verraten – den ich als Symlink in mein Backup-Verzeichnis aufnehme – und zusätzlich die Ablage meiner exportierten Vaults in einem verschlüsselten Disk Image empfohlen:

Everything breaks at some point, so while it's extremely unlikely anything catastrophic would happen that you couldn't recover from, I understand where you're coming from and I can see why you'd like to manage your own backups in addition to what we do.

Das ist die richtige Einstellung.

Ernstfall

Auch wenn der Ausdruck Blog ursprünglich unschöne Assoziationen (roughly onomatopoeic of vomiting) wecken sollte, hat sich das Format durchgesetzt und wird von sturen Menschen wie Jason Kottke weiterhin nach dem POSIOP-Prinzip hergestellt:

I will continue to forgo publishing on platforms with ever-shifting strategies, morals, and agreements in favor of my patchwork "system" of publishing tools held together with chewing gum. At least it's *my* chewing gum.

Hear, hear. Nachdem verschiedene Anwendungen in meiner lokalen Arbeitsumgebung aktualisiert wurden, habe ich die Option, neues Kaugummi auf dem Webserver zu applizieren oder den Kaugummibedarf (die Komplexität des Systems) zu reduzieren. Konkret besteht eine Inkompatibilität zwischen PostgreSQL 9 und PostgreSQL 13, die das Import-Skript für den lokalen Datenbankdump auf dem Server scheitern lässt. Die Aussicht, einen kompletten Webstack bei laufendem Betrieb neu aufzusetzen, ist nicht sehr attraktiv, und ich greife auf ein Konzept zurück, das für Notzeiten gedacht war.

Die vorbereitete nginx-Konfiguration auf dem Server ergänze ich um Caching-Anweisungen –

http { map $sent_http_content_type $expires { default off; text/html 1d; text/css 30d; application/pdf 30d; ~image/ 30d; } server { expires $expires; } }

– und im Backrezept make_static korrigiere ich die Anweisungen für Blog-Seiten, um (wie in der dynamisch generierten Version) jeweils die jüngsten 20 Blogeinträge anzuzeigen. Weil das Modul django.utils.feedgenerator mittlerweile selbständig Bytestrings UTF-8-kodiert, muss ich die Funktion feed_creator() geringfügig anpassen (page.content.encode('utf-8')page.content). Die Backautomatik funktioniert auch nach Jahren noch ohne jeden Anpassungsbedarf, so dass ich lediglich meinem rsync-Wrapper rbackup.py eine Konfiguration zur Synchronisation mit dem Webserver hinzufüge. Es wäre natürlich möglich, auch diesen Schritt zu automatisieren, aber aus Solidarität mit den gebeutelten Zeitungsverlagen leiste ich mir eine manuelle Endredaktion.

Google PageSpeed blickt weiterhin sehr wohlwollend auf diese Website (100/100), und laut ab wird die Startseite in durchschnittlich 171 Millisekunden ausgeliefert (ab -n100 -c10 https://eden.one/).

Setup 2021

Nach vier Jahren mit einer ausgesprochen empfindsamen Tastatur wechsele ich zu einer solideren Konstruktion und einer leistungsfähigeren Prozessorarchitektur. Letztere lässt mich etwas besorgt auf das Setup-Ritual blicken, denn die letzten Durchgänge waren ziemlich aufwendig.

Homebrew

Der Schlüssel zu einem Setup in Rekordzeit ist der Verzicht auf die relativ plattformunabhängige Low Level-Reproduzierbarkeit einzelner Installationsvorgänge, das heißt: Die Verwendung eines Paketmanagers. Dank Homebrew sind Mailstack (samt GnuPG!) und Webstack innerhalb von Minuten installiert:

brew install mutt brew install offlineimap brew install msmtp brew install w3m brew install nginx brew install postgresql@13

Weil die von Apple gelieferten vim- und rsync-Versionen einige Funktionen vermissen lassen (u.a. den für meine Backup-Strategie zentralen rsync-Parameter --ignore-missing-args), folgt:

brew install vim brew install rsync

Python 3.9 installiere ich zunächst über das Installationspaket für macOS in /Library/Frameworks, bevor mir (gerade noch rechtzeitig) auffällt, dass Homebrew Python 3.9 bereits (als Voraussetzung für vim) in /opt/homebrew/bin platziert hat. Ich entferne das Mac-Python (und die entsprechenden Symlinks in /usr/local/bin) und fahre mit dem Brew-Python fort:

pip3 install Django==3.1.7 brew install uwsgi pip3 install uwsgi

Die doppelte uwsgi-Installation ist eine – nicht vollständig rationale – Verzweiflungstat. Beim manuellem Aufruf (uwsgi --socket /private/tmp/eden.sock --module djangoapp.wsgi --chmod-socket=664) lässt sich uwsgi nämlich durchaus bewegen, meine Django-Applikation auszuliefern, beim Start als launchctl-Job (homebrew.mxcl.uwsgi) wird das Python-Plugin aber erst gefunden, wenn uwsgi.ini ausdrücklich auf Python hinweist (plugins = python3). Und auch nur dann, wenn man der Brew-Installation mit der pip3-Installation nachhilft. Ich muss nicht alles verstehen, obgleich das Deployment-Tutorial die Lieferkette (the web client ←→ the web server ←→ the socket ←→ uwsgi ←→ Django) und mögliche Fehlerquellen eigentlich sehr verständlich darstellt. Für verschiedene Skripte werden einige Python-Module nachinstalliert (pip3 install xxx), und das letzte verbleibende Python 2-Skript wird endlich migriert. In der Django-Applikation muss trotz des Versionssprungs (Django 1.x → Django 3.1.7) nur sehr wenig geändert werden: Das Modul django.core.urlresolvers heißt nun django.urls, und die Klasse ForeignKeys sieht einen zusätzlichen Parameter (on_deletion) vor. Selbst das komplexe Kommando make_static funktioniert auf Anhieb.

Konfiguration

Aus dem Backup übernehme ich die wichtigsten Konfigurationsdateien (mutt, GnuPG, MSMTP, Offlineimap, SSH), so dass der Mailstack völlig ohne Anpassungen in Betrieb genommen werden kann. Wenn man davon absieht, dass ich einen dritten Mail-Account konfiguriere (→ .muttrc, .msmtprc und .offlineimaprc) und etwas länger brauche, um den Wert des Offlineimap-Parameters maxsyncaccounts entsprechend zu erhöhen. Die Migration von bash auf zsh ist erstaunlich einfach (.bash_profile.zshrc), lediglich die Pfadergänzungen aus .bash_profile erhalten eine neue Heimat in .zshenv.

Ebenfalls aus dem Backup werden ~/Library/Scripts, ~/Library/Keychains, ~/Library/LaunchAgents, ~/Library/texmf und einige Ordner in ~/Library/Application Support übernommen. Dank Homebrew lassen sich die LaunchAgents für PostgreSQL, Offlineimap und nginx ohne selbstgeschriebene property files verwalten (brew services start|stop postgresql|offlineimap|nginx)), meine eigenen LaunchAgents müssen manuell in ~/Library/LaunchAgents abgelegt und geladen (launchctl load net.janeden.xxx.plist) werden. Damit die LaunchAgents ihre Arbeit verrichten können, erhalten zsh und python3.9 (/opt/homebrew/bin/python3) Full Disk Access (System Preferences → Security & Privacy Preference Pane → Privacy). In diesem Zusammenhang tritt später der einzige Nachteil der aktiven Paketpflege durch das Homebrew-Projekt zu Tage: Nach jeder Aktualisierung des Python-Paketes muss ich dem Interpreter erneut Zugriffsrechte erteilen. Ein Problem mit brew cleanup entsteht dagegen nur dann, wenn man ungeschickterweise ein Homebrew-Paket per sudo installiert und entsprechend keine Zugriffsrechte auf bestimmte Dateien hat. Die Anpassung der macOS-Standardkonfiguration übernimmt eine modifizierte Fassung des .macOS-Skripts.

Apps

Im dritten Schritt begebe ich mich in das macOS-GUI und installiere die folgenden Apps:

Anders als in den Vorjahren lautet der korrekte Befehl, um meine Schriften in LaTeX nutzen zu können, sudo texhash; updmap -user --enable MixedMap pad.map. TeXLive unterscheidet mittlerweile zwischen systemweiten und nutzerspezifischen Mappings.

Daten

Schließlich muss ~ aus dem Backup rekonstruiert werden. Bei dieser Gelegenheit repariere ich endlich die Fotobibliothek, die seit Jahren nicht vollständig mit der iCloud synchronisiert wird: Ein vollständiger Export und Re-Import aller Bilder bedeutet weniger Arbeit als befürchtet, weil Photos Duplikate beim Import einigermaßen zuverlässig erkennt. Das Verzeichnis ~/Backup wird zum Schluss mit Symlinks zu den wichtigsten Ordnern innerhalb von ~ versehen, ~/Documents/configuration/webstack ergänze ich um Symlinks zu /opt/homebrew/etc/nginx/nginx.conf und /opt/homebrew/etc/uwsgi/apps_enabled/uwsgi.ini.

TTL < 4h.

Meinungsvielfalt

Einer der bemerkenswertesten Aspekte der US-Demokratie ist die konsequente Priorisierung der Meinungsfreiheit, auch innerhalb der Bundesregierung: Während der Präsident China für den SolarWinds-Angriff auf verschiedene US-Behörden verantwortlich macht, verorten der Außenminister, der Justizminister und die US-Nachrichtendienste die Verantwortlichen in Russland.

Mit dieser Episode lässt sich gut illustrieren, dass Donald J. Trump innerhalb der US-Regierung als Faktotum gilt, dem aus nostalgischen Gründen der Anstrich eines primus inter pares zugestanden wird und dessen Einlassungen von den Verantwortlichen meist amüsiert zur Kenntnis genommen werden. Dieses hierarchiefreie Regierungsprinzip bildet einen scharfen Kontrast zum deutschen Präsidialsystem mit einem nahezu allmächtigen Frank-Walter Steinmeier, dem ich an dieser Stelle recht herzlich zum 65. Geburtstag gratulieren möchte.

Digitale Leihbücherei 2

Das Adobe-basierte Onleihe-Angebot deutscher Bibliotheken hat sich in den vergangenen sechs Jahren offenbar nicht wesentlich weiterentwickelt, bildet aber nach wie vor ein zentrales Argument gegen Amazons Kindle-Plattform.

Vervirend

Wenigstens die Statistikerinnen haben während der Corona-Pandemie ihren feinen Humor nicht verloren: Während das RKI für Deutschland die berühmte 7-Tage-Inzidenz (neue Corona-Fälle pro 100.000 Einwohnerinnen innerhalb von 7 Tagen) veröffentlicht, gibt die Oxford-Publikation Our World in Data einen 7-day rolling average per million people (i.e. einen durchschnittlichen Tageswert) an. Am 2020-12-27 gilt entsprechend für Deutschland eine Inzidenz von 157,8 (RKI) und von 244,98 (Our World in Data).

Sicherheitsroutine

So gern ich Bruce Schneiers Optimismus zum IoT als Killer-App für IT-Sicherheit teilen würde – schon vor sechs Jahren hat das BSI auf einen entsprechenden Angriff mit routinierten Floskeln reagiert:

Als Gegenmaßnahmen empfiehlt der Bericht vier Punkte. So soll die Kompetenz und Vertrauenswürdigkeit in der IT-Sicherheit gefördert werden und es mehr Standardisierung und Zertifizierung geben. Zudem fordert das BSI, sichere Techniken für Bürger und Unternehmen bereitzustellen. Damit verweist die Behörde jedoch auf das umstrittene De-Mail-Angebot, das bislang wenig angenommen wird. Zu guter Letzt müsse auch der Schutz kritischer Infrastrukturen gesichert werden.

Die Funke-Mediengruppe wird umfassend zu den Fortschritten seit 2014 berichten, sobald sie wieder arbeitsfähig ist.

UKexit

Schon die unpräzise journalistische Begleitung der Brexit-Saga dürfte Staatsrechtlerinnen irritieren, aber selbst das BMI behandelt Großbritannien und Vereinigtes Königreich als Synonyme:

Brexit - Austritt Großbritanniens aus der Europäischen Union

Seit dem 1. Februar ist das Vereinigte Königreich nicht mehr Mitglied der Europäischen Union. [...] Für die Einreise aus Großbritannien gelten ab dem 1. Januar 2021 die Bestimmungen für Drittstaaten. [...] Nach der Unterzeichnung des Austrittsabkommens durch Großbritannien und die Europäische Union (EU) ist der Brexit am 1. Februar 2020 in Kraft getreten. [...] Für Bürgerinnen und Bürger und Unternehmen änderte sich zunächst nichts - bis Ende 2020 lief eine Übergangsphase, in der das EU-Recht im und für das Vereinigte Königreich grundsätzlich weiterhin gegolten hatte, jedoch ohne britisches Mitbestimmungsrecht in den EU-Institutionen. Das Vereinigte Königreich war in dieser Zeit auch weiterhin Teil des EU-Binnenmarktes und der EU-Zollunion.

Zum Ausgleich hat das BMI in seinen Anwendungshinweisen zur Umsetzung des Austrittsabkommens Vereinigtes Königreich – Europäische Union schöne Beispiele für die Behandlung von nicht unmittelbar aus dem Austrittsabkommen berechtigten Briten als Drittstaatsangehörige erdacht:

Die britische Staatsangehörige Catherine hat die Tochter Charlotte. Zum Ende des Übergangszeitraums lebte Catherine als Arbeitnehmerin in Deutschland, während Charlotte zu diesem Zeitpunkt in Großbritannien lebte. Im Jahr 2022 möchte die dann 18jährige Charlotte zu Catherine nach Deutschland umziehen. Sie ist nach dem Austrittsabkommen in Deutschland zum Aufenthalt als Familienangehörige von Catherine berechtigt. Im Jahr 2024 heiratet Charlotte den Drittstaatsangehörigen Steven, der mit Charlotte in Deutschland leben möchte. Der Nachzug von Steven nach Deutschland richtet sich nicht nach dem Austrittsabkommen, denn Charlotte hatte zum Ende des Übergangszeitraums selbst keine Rechte als Freizügigkeitsberechtigte, sondern ist nach dem Austrittsabkommen nur mittelbar begünstigt.

Ich wünsche Charlotte und Steven trotz Charlottes nur mittelbarer Begünstigung und des erforderlichen Rückgriffs auf §29 AufenthG für Steves Nachzug eine glückliche und harmonische Ehe.

Produktorientiert

Once upon a time, in a kingdom not far from here, a king summoned two of his advisors for a test. He showed them both a shiny metal box with two slots in the top, a control knob, and a lever. "What do you think this is?"

One advisor, an Electrical Engineer, answered first. "It is a toaster," he said. The king asked, "How would you design an embedded computer for it?" The advisor: "Using a four-bit microcontroller, I would write a simple program that reads the darkness knob and quantifies its position to one of 16 shades of darkness, from snow white to coal black. The program would use that darkness level as the index to a 16-element table of initial timer values. Then it would turn on the heating elements and start the timer with the initial value selected from the table. At the end of the time delay, it would turn off the heat and pop up the toast. Come back next week, and I'll show you a working prototype."

The second advisor, a software developer, immediately recognized the danger of such short-sighted thinking. He said, "Toasters don't just turn bread into toast, they are also used to warm frozen waffles. What you see before you is really a breakfast food cooker. As the subjects of your kingdom become more sophisticated, they will demand more capabilities. They will need a breakfast food cooker that can also cook sausage, fry bacon, and make scrambled eggs. A toaster that only makes toast will soon be obsolete. If we don't look to the future, we will have to completely redesign the toaster in just a few years."

"With this in mind, we can formulate a more intelligent solution to the problem. First, create a class of breakfast foods. Specialize this class into subclasses: grains, pork, and poultry. The specialization process should be repeated with grains divided into toast, muffins, pancakes, and waffles; pork divided into sausage, links, and bacon; and poultry divided into scrambled eggs, hard-boiled eggs, poached eggs, fried eggs, and various omelette classes."

"The ham and cheese omelette class is worth special attention because it must inherit characteristics from the pork, dairy, and poultry classes. Thus, we see that the problem cannot be properly solved without multiple inheritance. At run time, the program must create the proper object and send a message to the object that says, 'Cook yourself.' The semantics of this message depend, of course, on the kind of object, so they have a different meaning to a piece of toast than to scrambled eggs."

"Reviewing the process so far, we see that the analysis phase has revealed that the primary requirement is to cook any kind of breakfast food. In the design phase, we have discovered some derived requirements. Specifically, we need an object-oriented language with multiple inheritance. Of course, users don't want the eggs to get cold while the bacon is frying, so concurrent processing is required, too."

"We must not forget the user interface. The lever that lowers the food lacks versatility, and the darkness knob is confusing. Users won't buy the product unless it has a user-friendly, graphical interface. When the breakfast cooker is plugged in, users should see a cowboy boot on the screen. Users click on it, and the message 'Booting UNIX v.8.3' appears on the screen. (UNIX 8.3 should be out by the time the product gets to the market.) Users can pull down a menu and click on the foods they want to cook."

"Having made the wise decision of specifying the software first in the design phase, all that remains is to pick an adequate hardware platform for the implementation phase. An Intel Pentium with 48MB of memory, a 1.2GB hard disk, and a SVGA monitor should be sufficient. If you select a multitasking, object oriented language that supports multiple inheritance and has a built-in GUI, writing the program will be a snap."

The king wisely had the software developer beheaded, and they all lived happily ever after.

Nicht nur John Gruber ist überrascht, dass es Menschen gibt, die die Warnung für überambitionierte Produktentwicklerinnen übersehen und daran scheitern, das zentrale Thema dieser Parabel korrekt zu identifizieren.

Fingerabdrücke

Anders als die flatterhaften E-Mail-Clients des 21. Jahrhunderts orientieren sich die kommunikativen Elemente meines Mailstacks an statisch konfigurierten Fingerabdrücken der beteiligten Mailserver.

Da sich diese Fingerabdrücke im Zuge der Erneuerung von SSL-Zertifikaten regelmäßig ändern, muss ich diese Änderung manuell nachvollziehen und die neuen Fingerabdrücke wie folgt erfragen

openssl s_client -connect imap.provider.com:993 < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -text -in /dev/stdin openssl s_client -connect smtp.provider.com:587 -starttls smtp < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -text -in /dev/stdin

und anschließend in .offlineimaprc bzw. .msmtprc hinterlegen.

Der Wampfler-Double-Blind-Effekt

Im Jahr 2020 auf das Phänomen des unverbrüchlichen Vertrauens in journalistische Arbeit zu verweisen –

You open the newspaper to an article on some subject you know well. [...] You read the article and see the journalist has absolutely no understanding of either the facts or the issues. Often, the article is so wrong it actually presents the story backward — reversing cause and effect. I call these the "wet streets cause rain" stories. Paper's full of them. In any case, you read with exasperation or amusement the multiple errors in a story, and then turn the page to national or international affairs, and read as if the rest of the newspaper was somehow more accurate about Palestine than the baloney you just read. You turn the page, and forget what you know.

– und diesen Verweis ausgerechnet auf einer Social Media-Plattform zu veröffentlichen, zeugt von einer beneidenswerten Distanz zur Situation der Medien nicht nur in den Kolonien.

Sportverletzlichkeit

Einige Aspekte der Corona-Pandemie sind unangenehm, manche sogar belastend, erschütternd und traumatisierend. Schwer zu ertragen sind nach Auffassung von Moritz Rödle zum Beispiel – Sportveranstaltungen ohne Zuschauerinnen. Ich hatte Sportfans für robuster gehalten und sorge mich jetzt etwas um ihre psychische Stabilität in den kommenden Monaten.

World War C

Die Corona-induzierte Zombieapokalypse macht erneut deutlich, in wie unterschiedlichem Maße sich die Bevölkerung einzelner Länder auch in Krisensituationen von rationalen Überlegungen leiten lässt. Im Vergleich zur völlig kopflosen deutschen Bevölkerung, die Haushaltsutensilien mit minimalem Defensivpotential wie Nudeln oder Seife in großen Mengen einkauft, konzentrieren sich US-Amerikanerinnen vernünftigerweise auf die relevante Grundversorgung zur Abwehr marodierender Zombiehorden.