Blog

Stabilitätsumgebung

Nachdem mich die ständigen Unannehmlichkeiten in Folge von Homebrew-Updates mürbe gemacht haben, liefert PEP 704 den Anlass, endlich eine virtuelle Umgebung für meine Django-Projekte einzurichten:

$ python3 -m venv virtualdjango $ source virtualdjango/bin/activate (virtualdjango) $ pip install Django (virtualdjango) $ pip install Django (virtualdjango) $ pip install uwsgi (virtualdjango) $ pip install psycopg2 (virtualdjango) $ pip install keyring (virtualdjango) $ pip install gnupg (virtualdjango) $ pip freeze > ~/Sites/virtualdjango_requirements.txt (virtualdjango) $ deactivate

Die Aktivierung der virtuellen Umgebung scheitert zwar im ersten Anlauf, weil ich auf die glorreiche Idee gekommen bin, in .zshrc das Alias hash für eines meiner Skripte zu definieren, und das activate-Skript die Existenz der gleichnamigen Shell-Funktion überprüft:

if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then hash -r 2> /dev/null fi

Anschließend beschränkt sich die Nutzung der neuen Umgebung aber – zu meiner Überraschung – auf die Anpassung einer einzigen Zeile in ~/Sites/djangoapp/manage.py

#!/Users/snafu/virtualdjango/bin/python3

– sowie die Überarbeitung des LaunchAgent-Konfiguration für uwsgi:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>KeepAlive</key> <true/> <key>Label</key> <string>net.janeden.uwsgi</string> <key>ProgramArguments</key> <array> <string>/Users/snafu/virtualdjango/bin/uwsgi</string> <string>--uid</string> <string>_www</string> <string>--gid</string> <string>_www</string> <string>--master</string> <string>--die-on-term</string> <string>--autoload</string> <string>--logto</string> <string>/Users/snafu/Library/Logs/Django/uwsgi.log</string> <string>--emperor</string> <string>/Users/snafu/Sites/djangoapp</string> </array> <key>RunAtLoad</key> <true/> <key>WorkingDirectory</key> <string>/Users/snafu/virtualdjango</string> </dict> </plist>

Dank Python 3.11 ist die virtuelle Django-Umgebung nicht nur stabiler, sondern auch substantiell schneller:

Server Software: nginx 1.23.1 / Python 3.9 nginx 1.23.3 / Python 3.11
Server Port: 80 80
Document Path: / /
Document Length: 65820 bytes 65820 bytes
Concurrency Level: 50 50
Time taken for tests: 1.595 seconds 0.999 seconds
Complete requests: 500 500
Failed requests: 0 0
Total transferred: 33065000 bytes 33065000 bytes
HTML transferred: 32910000 bytes 32910000 bytes
Requests per second: 313.49 [#/sec] (mean) 500.27 [#/sec] (mean)
Time per request: 159.492 [ms] (mean) 99.945 [ms] (mean)
Time per request: 3.190 [ms] (mean, across all concurrent requests) 1.999 [ms] (mean, across all concurrent requests)
Transfer rate: 20245.53 [Kbytes/sec] received 31108.88 [Kbytes/sec] received

Fernsuche

Die Kombination von leistungsfähiger Suchfunktion und undifferenzierter Ordnerstruktur ermöglicht es mir, auf eine ordnerübergreifende Suche weitgehend zu verzichten. Für besondere Anlässe – wie die Suche nach einer mehrere Jahre zurückliegenden Konversation – greife ich auf notmuch zurück:

brew install notmuch brew install notmuch-mutt # ~/.muttrc macro index <F8> "<enter-command>unset wait_key<enter><shell-escape>/opt/homebrew/bin/notmuch-mutt --prompt search<enter><change-folder-readonly>/Users/snafu/.cache/notmuch/mutt/results<enter>" "search mail (using notmuch)" macro index <F9> "<enter-command>unset wait_key<enter><pipe-message>/opt/homebrew/bin/notmuch-mutt thread<enter><change-folder-readonly>/Users/snafu/.cache/notmuch/mutt/results<enter><enter-command>set wait_key<enter>" "search and reconstruct owning thread (using notmuch)"

Weil isync/mbsync über den von notmuch verwendeten Nachrichtenindex stolpert (Maildir error: found subfolder '.notmuch/xapian', but store 'personal-local' does not specify SubFolders style), muss auch .mbsyncrc ergänzt werden:

# ~/.mbsyncrc MaildirStore personal-local Path ~/PersonalMail/ Inbox ~/PersonalMail/INBOX Subfolders Verbatim

Das Perl-Skript notmuch-mutt create[s] a virtual maildir folder with search results whenever a search is made, und abgesehen von der geringfügig anderen Syntax für Suchbegriffe ist die Integration von mutt und notmuch sehr gelungen.

Absenderausrichtung

Einige Monate, nachdem ich das Thema DMARC für mich abgeschlossen habe, rächt sich mein oberflächliches Verständnis der DMARC-Prinzipien. Als ich zum ersten Mal einen Blick auf einen DMARC-Report von Google für lists.eden.one werfe, bin ich sowohl erschüttert als auch verwirrt:

<record> <row> <source_ip>123.123.123.123</source_ip> <count>1</count> <policy_evaluated> <disposition>none</disposition> <dkim>pass</dkim> <spf>fail</spf> </policy_evaluated> </row> <identifiers> <header_from>lists.eden.one</header_from> </identifiers> <auth_results> <dkim> <domain>lists.eden.one</domain> <result>pass</result> <selector>s42</selector> </dkim> <spf> <domain>eden.one</domain> <result>pass</result> </spf> </auth_results> </record>

Warum ist der SPF-Test gleichzeitig erfolgreich und nicht erfolgreich? Einen ersten Hinweis liefert der etwas detailliertere Report eines anderen Providers:

<record> <row> <source_ip>123.123.123.123</source_ip> <count>1</count> <policy_evaluated> <disposition>none</disposition> <dkim>pass</dkim> <spf>fail</spf> </policy_evaluated> </row> <identifiers> <header_from>lists.eden.one</header_from> <envelope_from>eden.one</envelope_from> </identifiers> <auth_results> <dkim> <domain>janeden.net</domain> <selector>s42</selector> <result>pass</result> </dkim> <spf> <domain>eden.one</domain> <scope>mfrom</scope> <result>pass</result> </spf> </auth_results> </record>

Die Ursache ist offensichtlich die Abweichung zwischen header_from und envelope_from durch die Verwendung des Sender Rewriting Scheme auf meinem Mailserver. Während der SPF-Test selbst lediglich den relevanten DNS-Eintrag für die Briefumschlagsdomain (eden.one) berücksichtigt, prüft der DMARC-Mechanismus in Bezug auf SPF, ob header_from und envelope_from zueinander passen (Under DMARC a message can fail even if it passes SPF or DKIM, but fails alignment.). Weil meine DMARC-Policy für SPF (und DKIM) eine strikte Übereinstimmung fordert –

<policy_published> <domain>lists.eden.one</domain> <adkim>s</adkim> <aspf>s</aspf> <p>quarantine</p> <sp>quarantine</sp> <pct>75</pct> </policy_published>

– muss dieser alignment check für SPF scheitern. DKIM ist nicht betroffen, weil Listenmails eine eigene DKIM-Signatur für lists.eden.one erhalten. Für Subdomains wie lists.eden.one lässt sich das SPF-Problem lösen, indem ich auf eine entspanntere Policy wechsele:

<policy_published> <domain>lists.eden.one</domain> <adkim>s</adkim> <aspf>r</aspf> <p>quarantine</p> <sp>quarantine</sp> <pct>75</pct> </policy_published> <record> <row> <source_ip>217.160.240.138</source_ip> <count>1</count> <policy_evaluated> <disposition>none</disposition> <dkim>pass</dkim> <spf>pass</spf> </policy_evaluated> </row>

Für andere Domains (z.B. janeden.net) käme nur ein Verzicht auf SRS in Frage, um die Abweichung von header_from und envelope_from zu eliminieren –

<record> <row> <source_ip>123.123.123.123</source_ip> <count>1</count> <policy_evaluated> <disposition>none</disposition> <dkim>pass</dkim> <spf>pass</spf> </policy_evaluated> </row> <identifiers> <header_from>janeden.net</header_from> </identifiers> <auth_results> <dkim> <domain>janeden.net</domain> <result>pass</result> <selector>s42</selector> </dkim> <spf> <domain>janeden.net</domain> <result>pass</result> </spf> </auth_results> </record>

– was aber neue Probleme für die vielen über meinen Mailserver weitergeleiteten E-Mails aufwürfe. Ich tröste mich damit, dass das passende DKIM-Alignment eine hinreichende Bedingung für ein positives DMARC-Ergebnis ist.

Listenzitatdarstellung

Seit mehr als zehn Jahren beobachte ich mit einer Mischung aus zusammengebissenen Zähnen und Gleichmut, dass Listen innerhalb von Zitaten (<blockquote><ol><li>...</li></ol></blockquote>) auf meiner Website sehr unschön dargestellt werden. Dafür verantwortlich sind die folgenden CSS-Anweisungen, die die Zitatbegrenzungslinie zerhacken:

blockquote > ul, blockquote > ol { padding: 1em 2em 1em 2em; border-width: 0px 0px 0px 1px; border-color: #369; border-style: solid; } ul, ol { margin-left: 3em; margin-bottom: 0.8em; }

Es spricht nicht für meinen gestalterischen Willen, dass ich die triviale Lösung –

blockquote > ul, blockquote > ol { padding: 1em 2em 1em 2em; margin-left: 0em; margin-bottom: 0em; border-width: 0px 0px 0px 1px; border-color: #369; border-style: solid; } blockquote > ul > li, blockquote > ol > li { margin-left: 3em; } ul, ol { margin-left: 3em; margin-bottom: 0.8em; }

– erst heute implementiere.

Missionarisch

Der gestrenge Herr Reiter mahnt zum neuen Jahr –

Friends of GnuPG,
a happy new year to all of you!

Now I am taking Andrew (hi) as an example to send a reminder why using text/plain format only mails is a good idea on this (and other mailing lists).

Am Samstag 17 Dezember 2022 19:54:39 schrieb Andrew Gallagher via Gnupg-users:
> <html><head><meta http-equiv="content-type" content="text/html;
> charset=utf-8"></head><body dir="auto">I’ve been

Because HTML can have a lot of active contents, a number of people I know sanitize email that have text/html parts. Some ignore such emails completely.

In the past I know that Werner ignored (most) emails with text/html.

There are more advanted [sic!] to text/plain mails:

  • people can better chose how their email client is displaying the contents, for instance the font size and color.
  • it saves energy because of less bytes transmitted and backuped (and indexed, archived and searched).

– und verweist auf die

productivity gap between people that use full fledged and customised emails clients to those with only web and mobile clients. As email is one of the working decentralised communication solutions, I think we should value it more and thus help people to learn about the productivity of an email client that they can fully control (on their hardware) and customize to have one unified interface to several communities.

Der missionarische Eifer meiner Mitgeeks rührt mich sehr.

Winterputz

Ein knappes halbes Jahr nach der Veröffentlichung von Vim 9.0 widme ich mich endlich dem Upgrade von .vimrc auf Vim9 Script. Auf den ersten Blick sind das neue Kommentarzeichen, die geänderte Variablendeklaration und die vereinfachte Funktionssyntax keine großen Herausforderungen, aber der Teufel steckt im Detail. Die Angabe von ranges für Kommandos erfordert nun ein Präfix (:), und in many places ist Weißraum obligatorisch (während er beim Setzen von Optionen – set formatoptions+=n – nach wie vor untersagt ist).

Ich nutze die Gelegenheit, um meine überbordende .vimrc mit Hilfe von filetype plugins etwas zu verschlanken und bei dieser Gelegenheit die unterschiedlichen Mappings für Markup-Sprachen zu vereinheitlichen:

# ~/.vim/ftplugins/markdown.vim vnoremap <buffer> <silent> ,b <Esc>`>a**<Esc>`<i**<Esc> vnoremap <buffer> <silent> ,a <Esc>`>a]()<Esc>P`<i[<Esc> # ~/.vim/ftplugins/html.vim vnoremap <buffer> ,a <Esc>`>a</a><Esc>`<i<a href="<Esc>pa"><Esc> vnoremap <buffer> ,b <Esc>`>a</strong><Esc>`<i<strong><Esc>

Besonders tückisch ist, dass html.vim auch für Markdown-Buffer geladen wird (mit sehr verwirrenden Konsequenzen), wenn man nicht frühzeitig eingreift:

# ~/.vim/ftplugins/html.vim if &ft == "markdown" finish endif

Ebenfalls nicht trivial ist die Handhabung des substitute-Kommandos im virtual mode. Einfache Funktionen könnten im Prinzip unmittelbar als Mapping realisiert werden:

def g:AddLinebreaks() silent s/.\zs\n\ze./\<br \/\>\r/ge nohl enddef vnoremap <f3> :call AddLinebreaks()<CR>

Interessant wird es, wenn ein Kommando innerhalb der Funktion die Markierung als Ganze behandeln soll, während das substitute-Kommando auf jeder Zeile operieren muss:

def g:EncloseParagraphs() execute "normal `>a</p>\<Esc>`<i<p>\<Esc>" silent :%s/\%V\n\{2,}\%V/<\/p>\r\r<p>/ge nohl enddef vnoremap <buffer> <silent> ,p :<C-U>call EncloseParagraphs()<CR>

Mittels <C-U> wird verhindert, dass die gesamte Funktion (einschließlich des execute-Kommandos) für jede Zeile innerhalb der Markierung ausgeführt wird. Das substitute-Präfix :% verschafft diesem Kommando dann eine maximale Reichweite (alle Zeilen des Buffers), die schließlich durch das Musteratom \%V wieder auf die ursprüngliche Markierung begrenzt wird.

Diep Pham bringt es auf den Punkt:

Q: Is it worth converting all my vim scripts to Vim9 script?
A: IMO, no. The syntax looks a little better to read and write, but not by a huge margin. But if you have some free time, why not?

Raketenprotokollerweiterung

Im Windschatten von Mastodon und angesichts des Digital Markets Act rückt Paul Sawers auf TechCrunch das Matrix-Protokoll ins Rampenlicht und überhöht die (unfertige, halbherzige, komplizierte) Matrix-Unterstützung von Rocket.Chat ein wenig:

Back in May, open source enterprise messaging platform Rocket.Chat revealed that it would be transitioning to the Matrix protocol. While this process is still ongoing, this represented a major coup for the Matrix movement, given that Rocket.Chat claims some 12 million users across major organizations such as Audi, Continental and Germany’s national railway company, The Deutsche Bahn.

In diesem Sinne leitete Apple mit iTunes for Windows auch den Wechsel auf die Windows-Plattform ein.

Textmatrix

Nach einer gewissen Abklingzeit versuche ich mich noch einmal an der Installation eines CLI-Clients für Matrix und siehe da – ein rabiates Upgrade der Xcode Command Line Tools löst das Problem:

sudo rm -rf /Library/Developer/CommandLineTools sudo xcode-select --install brew tap aaronraimist/tap brew cask install gomuks

Um die bestehenden (verschlüsselten) Konversationen lesen zu können, muss ich lediglich die Raumschlüssel aus Element exportieren und in gomuks importieren (/import /Users/snafu/Desktop/room_keys.txt; /clearcache). Die anschließende Verifikation der neuen gomuks-Session anhand ihres Fingerabdrucks (/fingerprint) funktioniert natürlich etwas anders als mit einem GUI-Client:

To find the manual verify option in Element, click on that user in the list of users or click their profile picture in the timeline. That will open their profile. Then click on the device you want to verify and then click the manually verify button.

Mein Dank gilt Aaron Raimist für die Zapfanlage und den individuellen Support.

Nachrichtenverwalter 2

Die Mailman Suite zeichnet sich nicht nur durch eine komplexe Architektur, sondern auch durch viele kleine und leicht zu behebende Fehler aus. Neben der falschen Platzierung von Konfigurationsdateien, über die ich bei der Installation gestolpert bin, funktioniert auch der Export von Listenmitgliedern per Kommandozeile (mailman members listname@domain --output list_members.txt) nur dann einwandfrei, wenn die display names sich auf den ASCII-Zeichensatz beschränken. Andernfalls: =?utf-8?q?J=C3=BCrgen?=.

Mark Sapiro stellt natürlich innerhalb weniger Stunden einen Patch bereit, muss aber kurz darauf ein Python-Skript zur Korrektur von display names nachliefern, die mit Mailman < 3.3.3 erstellt (und daher rfc2047-encoded gespeichert) wurden. Er räumt auch freimütig ein, dass eine Bearbeitung von display names eigentlich via Postorius möglich sein sollte. Aber:

Understood, but there are a couple of things at work here. One is that this particular task is a one-off the correct the result of a bug, but perhaps more significantly, mailman developers (at least me) are comfortable with withlist (it's been around since MM 2.1) and if I can do it that way, it reduces the motivation to make something more friendly for the normal admin. Unfortunately doing things easily that way requires a lot of specialized knowledge of Mailman internals, so most admins need help with it.

Diese Graubärtigkeit ist mir sehr sympathisch, auch wenn sie mit meiner dauerhaften Abhängigkeit von mailman-users[at]mailman3.org einhergeht. Immerhin hat das heutige Upgrade auf die aktuellen Mailman-Komponenten ohne Rücksprache mit Mark funktioniert:

systemctl stop mailman3 systemctl stop mailmanweb su - mailman pip3 install -U mailman postorius django-mailman3 hyperkitty mailman-web mailman-web migrate mailman-web compress mailman-web collectstatic mailman-web compilemessages exit systemctl start mailman3 systemctl start mailmanweb