Blog

Konsequent flexibel

Die Marxistisch-Leninistische Partei Deutschlands nimmt für sich in Anspruch, politisch konsequent zu sein, was man als Kleinpartei auch durchaus sein kann. Um so überraschender ist das sprachlich inkonsequente Plakat:

Politik für Arbeiter*innen statt Milliardäre. Konsequent.

Ich moniere das natürlich, denn auch Susanne Klatten und Friede Springer haben einen Anspruch auf politische Aufmerksamkeit. Lange nach der Bundestagswahl und damit meiner Wahlentscheidung meldet sich die Partei zurück (aufgrund einer Fülle von Anfragen erst so spät). Sie referiert zunächst kurz ihr Profil (revolutionäre Arbeiterpartei, Marxismus-Leninismus und die Mao-Zedong-Ideen, gegen revisionistische Verfälschung, Schule des Klassenkampfs etc), um daraus abzuleiten, dass sich ihr Slogan offensichtlich auch gegen Milliardärinnen (und mutmaßlich nonbinäre Reiche) richte. Darüber hinaus sei für die Verwendung des Ausdrucks Milliardär*innen auf dem Plakat kein Platz gewesen.

Die Berufung auf das generische Maskulinum und auf gestalterische Sachzwänge führt die MLPD an die Seite ihrer konservativen und rechtsextremen Konkurrenz, während die Ansprache der Arbeiter*innen ganz im Sinne ihrer (unzureichend) linken Mitbewerber*innen sein dürfte. Könnte es sein, dass ausgerechnet die MLPD sich auf diesem Weg als kompromissbereite zentristische Partei positionieren möchte?

Ihre E-Mail an mich schließt sie jedenfalls mit einer versuchten Bedürfnisweckung (Abonnement der Roten Fahne, Buchwerbung), mutmaßlich um ihre Anschlussfähigkeit an das kapitalistische Schweinesystem zu illustrieren. Völker, hört die Signale.

Ausweislich

Vor sechs Jahren hat Marcus Schwarze im Techniktagebuch bereits die Nutzung der Online-Ausweisfunktion deutscher Personalausweise dokumentiert. Seitdem sollte sich einiges getan haben, und ich komme mit meinem neuen Personalausweis meiner Chronistenpflicht nach. Anders als 2015 kann statt eines Kartenlesegerätes ein NFC-fähiges Smartphone für die Kommunikation eines Notebooks mit dem Personalausweis verwendet werden, und es wird keine Java-Umgebung mehr benötigt. Es muss lediglich

Die gesamte Konfiguration wird in einem hilfreichen Videotutorial mit einer Laufzeit von knapp sechs Minuten erläutert, und die Kommunikation zwischen Ausweis, Smartphone und Notebook ist in drei von fünf Fällen erfolgreich.

Neben der selbstgewählten PIN (die im Personalausweisportal auch als Geheimnummer bezeichnet wird) gibt es noch eine PUK zur Aufhebung der PIN-Blockade (die laut PIN-Brief nur bei Bedarf freigerubbelt werden sollte) und ein Sperrkennwort – den vor sechs Jahren noch erforderlichen Berechtigungscode und die Kartenzugangsnummer gibt es nicht mehr.

Dafür ist das Diensteangebot erstaunlich konstant geblieben: Bundesweit kann man Informationen der Rentenversicherung und des Kraftfahrtbundesamtes als PDF-Dokumente herunterladen. Als Schwerbehinderte in Bayern, als De-Mail-Nutzerin, Kundin der Investitionsbank Berlin und der LVM-Versicherung oder als Z-EU-S-Projektträgerin könnte ich noch weitere Angebote nutzen, aber die meisten Dienste stellen Kommunen bereit. In meiner Kommune gibt es nur ein Antragsverfahren für (papierne) Urkunden, die ich persönlich abholen müsste.

Die Behörden haben aus dieser Stagnation den Schluss gezogen, die Nutzung der Online-Ausweisfunktion sei zu kompliziert, und bieten für ihre Dienste Servicekonten zur Authentifizierung mit Benutzername und Passwort an. Wenn man Dienste mit einem hohen Vertrauensniveau über ein Servicekonto nutzen möchte, muss man das Konto allerdings mit der Online-Ausweisfunktion eröffnen – und selbst dann können einzelne Dienste laut FAQs zusätzlich die Online-Ausweisfunktion bei Anträgen vorsehen. Ein Servicekonto übersteht eine Änderung von Daten im Personalausweis (Umzug, Namensänderung) nicht – in diesem Fall ist das Servicekonto (vor der Neuausstellung des neuen Personalausweises) zu löschen und mit dem neuen Personalausweis ein neues anzulegen.

Servicekonten sind also eher fragil und eignen sich nicht als Element einer dauerhaften digitalen Identität. Hintergrund der Servicekonten ist keine Benutzbarkeitsanalyse, sondern die rechtliche Verpflichtung zu einem Portalverbund mit einheitlichen Zugangsmodalitäten durch das Online-Zugangsgesetz (die theoretisch natürlich ebenfalls der Benutzbarkeit dienen soll). In der Realität des Jahres 2021 werden Servicekonten noch längst nicht von allen Kommunen des jeweiligen Landes unterstützt und es ist eher unwahrscheinlich, dass die vorgesehene Umsetzung bis Ende 2022 gelingt.

Einen anderen Weg zum Ökosystem Digitale Identitäten hat vor einigen Wochen die Bundesregierung mit der ID Wallet eingeschlagen: In einer Smartphone-App sollen Daten aus dem Personalausweis (und anderen Ausweisdokumenten) gespeichert werden, aktuell gibt es eine Basis-ID und einen digitalen Führerscheinnachweis. Das Verfahren enthält allerdings konzeptionell so gravierende (und vorab bekannte) Schwachstellen, dass die Anwendung aus den App Stores zurückgezogen werden musste. Bayerische Amtsträgerinnen in Berlin können Digitalisierung eben mit einer gewissen Grandezza vergeigen.

Selbst die De-Mail als Vorzeigeprojekt der digitalen Bundesrepublik lahmt mittlerweile etwas, und nun fällt auch noch das Modernisierungsjahrzehnt aus. Aber die Blockchain wird's schon richten.

(Dieser Beitrag erschien leicht gekürzt und disambiguiert auch im Techniktagebuch. Kathrin Passig hat mich ermahnt, ironische Äußerungen zu unterlassen, um die langfristige Interpretierbarkeit zu erhöhen, und sogar die Gastbeitragsrichtlinien um einen entsprechenden Hinweis ergänzt.)

Experimente

Die Christlich-Demokratische Union steht nach Auffassung der Mandatsträgerin Nadine Schön für Experimentierfreude und Deregulation, ganz anders als etwa die Freien Demokraten:

Es werde genug zu tun geben, ist sich auch Schön sicher. Sollte die Ampelregierung kommen, dann werde die Union sicherlich Unterschiede zu ihren eigenen christdemokratischen Positionen feststellen. Etwa beim Thema Künstliche Intelligenz. Grüne und FDP tendierten dazu, vieles zu regulieren, während die CDU auch Experimentierräume schaffen wolle, so Schön.

Die Stammwählerinnen der Union und auch ihr jüngster Kanzlerkandidat dürften überrascht sein, wie progressiv und unkonventionell ihre politische Heimat insgeheim immer war.

Freund – Feind – Parteifreund – Mitbruder

Über die Namen der pflichtvergessenen Kirchenfunktionäre Rainer Maria Woelki, Ansgar Puff und Dominikus Schwaderlapp ist viel gescherzt worden, aber die Herren haben es auch sonst nicht leicht: Herr Schwaderlapp muss ein Jahr demütig fernab der Heimat seelsorgen (wenn auch in vollem Ornat), Herr Woelki wird eine geistliche Auszeit nehmen müssen (wenn auch bei vollen Bezügen), und nur Herr Puff darf seinen Dienst unverzüglich wieder aufnehmen.

Puff allerdings musste maliziöse Beistandsbekundungen von Kirchenbrüdern ertragen, als er während der Freistellung von seinen bischöflichen Aufgaben einen Kleinkinder-Gottesdienst in Köln-Klettenberg zelebrierte. Der dortige Pfarrer sah laut WDR keine Gefahr für die Kinder, da es eine öffenliche Veranstaltung sei. Ob Karl-Josef Schurf auch die Herren Woelki und Schwaderlapp für Menschen hält, die man nicht ohne Aufsicht mit Kindern (oder jungen Theologinnen) allein lassen dürfe, bleibt unklar. Eventuell erledigen sich Ränkespiele unter Mitbrüdern und das gesamte Problem einer machtvollen und missbrauchsbegünstigenden Institution aber auch bald von selbst.

Some Leniency

Seit einigen Jahren habe ich mich daran gewöhnt, Dateianhänge mit Umlauten manuell nachbenennen zu müssen, weil mutt sie wie folgt zur Speicherung anbietet:

=?iso-8859-1?Q?Ma=DFnahmenplan_Qualit?= =?iso-8859-1?Q?=E4tsmanagement.docx?=

In einem Forum wurde mir damals beschieden, das Problem seien alle anderen E-Mail-Clients, die mit Anhängen nicht standardkonform umgingen.

Zufällig stoße ich heute auf einen vergleichsweise jungen Gitlab-Eintrag, in dem McCarthy en passant die Konfigurationsvariable rfc2047_parameters erwähnt, mit der sich das Problem sofort lösen lässt. Auf die konsternierte Rückfrage, warum dieser Parameter nicht standardmäßig gesetzt sei, bekräftigt er die slippery slope-Position der Entwicklerinnen:

Accepting garbage by default encourages the proliferation of non-standard emails and further decay of the standards. Some leniency is of course appropriate: thus the inclusion of the config variable, and for example commit 26bba6f9 which adjusted 2047 decoding to cope with non-compliant partitioning.

But usage of 2047 vs 2231 encoding is straight-forward, and has been for over 20 years. Roessler didn't think $rfc2047_parameters merited default on, and neither have subsequent Mutt committers. I'm not inclined to change it either.

A similar argument was attempted in ticket 60, in which a faulty client used 2047 encoding across the entire Message-ID header. The ticket there needed to be lodged with the faulty MUA, not Mutt, and I would say the same thing here.

Diese Vorlage für eine erregte Diskussion um den Grad adäquater leniency lasse ich aus Dankbarkeit für rfc2047_parameters ungenutzt.

Entwicklungsoffen

Unter Windows 11 lassen sich künftig Android-Apps ausführen, nicht aber iOS-Apps, was die versierte Technologie-Redaktion der tagesschau zu einer Paraphrase der Walled Garden-Metapher verleitet:

IPhone-Nutzerinnen und -Nutzer, also die Apple-Welt, haben davon nichts, weil Apple nach wie vor seine Apps abschottet und sozusagen hinter einer künstlichen Mauer in seinem App-Store versteckt.

In diesem Sinne hat Epic wohl auch nicht gegen zu hohe Provisionen und für alternative Vertriebswege, sondern gegen die Abtretung aller Lizenzrechte an Apple und gegen die berüchtigte iOS-Exklusivitätsklausel geklagt (auch wenn die Apple-nahe Presse den Klagegegenstand natürlich irreführend darstellt). Microsoft dagegen positioniert sich laut tagesschau mit der Bereitstellung einer Android-Umgebung sehr geschickt und gibt erstmals Fremdentwicklern die Chance, Anwendungen für eine Microsoft-Plattform zu anzubieten. Wenn Steve Ballmer das noch erlebt hätte! Immerhin bleibt Microsoft sich in einem Punkt treu:

Allerdings ist diese Funktion noch nicht sofort verfügbar, weil sie nicht rechtzeitig fertig wurde.

Wenn Apple nicht sehr bald eine Plattform bereitstellt, die Software aus unterschiedlichen Quellen zulässt, sehe ich schwarz für AAPL.

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.

Lebenszeit- und Cookie-Management

Ob der Holtzbrinck-Verlag automatisierte Downloads durch Abonnentinnen erschweren möchte oder schlicht ein neues CMS implementiert hat: Mein Skript funktioniert nicht mehr, obwohl sich am Quellcode der Download-Seite nichts geändert hat.

BeautifulSoup extrahiert weiterhin erfolgreich die Download-Links, aber die digitalen Zeitungsdokumente lassen sich nicht mehr mit

requests.get(file_link, auth=(username,password))

herunterladen. Offenbar akzeptiert der neue Download-Bereich (/restricted/) keine unmittelbare Authentifizierung mehr, sondern prüft den Session-Cookie, der durch einen vorangehenden Login gesetzt wurde. Wenn man also den Cookie namens creid aus dem Browser übernimmt und mitsendet –

response = requests.get(file_link, cookies=cookie_data)

werden die Dokumente anstandslos geliefert. Leider haben sie nun auch keine sprechenden Namen mehr (Name der Zeitung, Jahr und Ausgabe), sondern automatisch generierte Zeichenketten wie 0138e8cd-233f-43a0-8f33-ec13b2c70f31 oder 20f0ff56-5cea-4bf6-8a3e-c03c46673b3d. BeautifulSoup to the rescue:

edition = soup.find('p', {'class': 'epaper-info-title pull-left'}) result = re.match('.* (\d\d)/(\d\d\d\d)', edition.contents[0]) current_edition_name = f'zeitung_{result.group(2)}_{result.group(1)}'

Mit einem Anpassungsaufwand von einer Stunde kann ich nun jede Woche eine Minute sparen (manueller Download, manuelle Umbenennung der Dateien, manuelle Ablage im Zielordner) und schon ab Dezember 2022 wertvolle Lebenszeit sparen. Wenn der Holtzbrinck-Verlag mitspielt.

Bekanntentrick

Normalerweise produziert die katholische Kirche im juristischen Kontext bestürzende Schlagzeilen, aber mindestens in Aachen beherrscht ein Funktionsträger auch das Register erbärmlich. Das Verhalten des Weihbischofs Bündgens dürfte allenfalls Enkeltrickbetrügerinnen professionellen Respekt abnötigen.

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.

Something is rotten

Im Oktober 2020 wurde in Dänemark die Tötung von 15 Millionen Nerzen auf Grund von Corona-Infektionen ohne gesetzliche Grundlage angeordnet, anschließend wurden die Tierkadaver so dilettantisch entsorgt, dass sie durch Fäulnisgase an die Oberfläche gelangten und das Grundwasser verseuchten. Nun werden die verwesten Kadaver exhumiert und verbrannt.

Entweder hat die dänische Regierung es im Sinne der europäischen Solidarität und nach dem Ausscheiden des Vereinigten Königreichs übernommen, einen Maßstab für administrative Inkompetenz zu definieren, an dem sich andere Regierungen entspannt messen können, oder ein bösartiger Shakespeare-Fan zieht die Fäden in unserem nördlichen Nachbarland.

Noagerlzuzler

Noagerlzuzler gibt es offenbar nicht nur in Bayern, und in britischen Pubs wurde für diesen Besuchertypus sogar eine eigene Cuvée namens all sorts kreiert und zu einem verführerischen Preis angeboten.

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.