Nachdem ich meinen Webauftritt im Hinblick auf Leistung und Erscheinungsbild durch konsequente Kompression, den Verzicht auf dynamische Inhalte, CSS-Gitter und Webfonts abschließend optimiert habe, kommen mir gewisse Zweifel – mindestens der Verlust einer Suchfunktion für die umfangreiche Website ist bedauerlich.
Deshalb beschließe ich, erneut eine Django-basierte Website neben dem primären Auftritt aufzusetzen. Im ersten Schritt muss sichergestellt werden, dass Crawler diese diskrete Website nicht überrennen:
– in der Dummy-Variante, um Expires-Header zu generieren, nur um erneut zu realisieren, dass ich denselben Effekt mit einer einzigen Zeile in der nginx-Konfiguration (expires 7d;) erreichen kann. Zwischenzeitlich teste ich, ob sich die Expires-Map für statische Inhalte –
– für proxied content adaptieren lässt, was mir mit etwas Unterstützung durch Alexey und Richard auch gelingt:
# etc/nginx/nginx.conf
map $upstream_http_content_type $expires2 {
default off;
~text/html 7d; # regex to account for values like 'text/html; charset=utf-8'
text/plain 10d;
}
Natürlich ist die Differenzierung zwischen robots.txt und allen anderen Django-Views völlig unsinnig, so dass es bei der bewährten Anweisung expires 7d; bleibt. Nicht optional ist dagegen der uwsgi_cache, der die Serverlast massiv reduziert und die Antwortzeiten auf das Niveau des statischen Webauftritts – requests per second: 184,75 (statisch) vs. 185,49 (Django) – hebt:
# etc/nginx/nginx.conf
upstream django {
server unix:///tmp/django.sock;
}
uwsgi_cache_path /tmp/djangocache levels=1:2 keys_zone=djangocache:10m max_size=1g inactive=60m use_temp_path=off;
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'";
add_header Strict-Transport-Security "max-age=31536000";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
add_header Permissions-Policy interest-cohort=();
location / {
uwsgi_pass django;
include /etc/uwsgi/apps-enabled/uwsgi_params;
uwsgi_cache djangocache;
uwsgi_cache_key $request_uri;
uwsgi_cache_use_stale error timeout updating http_500 http_503;
uwsgi_cache_lock on;
uwsgi_cache_valid 200 302 7d;
uwsgi_cache_valid any 10m;
expires 7d;
}
location ~ ^/(css|images|pdf|tex|files|media|epub|docbook)/ {
root /var/www/django/static/;
expires $expires; # refers to the variable defined in the map for content types
}
}
Der Link auf example.com wird natürlich nur sehr guten Freundinnen übermittelt.
Die Google Fonts-Website weckt den Wunsch, sämtliche OFL-, UFL- und Apache-lizenzierten Schriften herunterzuladen, und natürlich gibt es zu diesem Zweck ein GitHub-Repository. Leider fehlen im Download die statischen Versionen variabler Schriften, die von der Google-Website on the flyerzeugt werden.
Glücklicherweise lassen sich statische Versionen auch manuell mit fontTools generieren, konkret mit dem Befehl:
Wenn man sich zu lange mit der Führung von Betrieben und Mehrung von Profiten befasst, entwickelt man ein eigenwilliges Verständnis des Wortes reasonable:
Tesla’s latest terms of service added a section saying Tesla will sue Cybertruck buyers for $50K or the sale price if they sell the truck within their first year of ownership.
This actually sounds like a reasonable mechanism to deter scalpers especially after the wave of cars selling way over MSRP ovee the past year or two.
It also has the benefit of restricting buyers to the Tesla faithful which will encourage more positive reviews than say from regular EV buyers.
Variable fonts are an evolution of the OpenType font specification that enables many different variations of a typeface to be incorporated into a single file, rather than having a separate font file for every width, weight, or style. They let you access all the variations contained in a given font file via CSS and a single @font-face reference.
Die Variabilität dieser Schriften realisiert sich jedenfalls auf Achsen, zu denen unter anderem weight (wght) und width (wdth) zählen. Das jeweils unterstützte Spektrum wird wie folgt definiert:
Weil die Umsetzung numerischer Werte in CSS-Schlüsselwörter (700 = bold) zwar einigermaßen etabliert, aber nicht normiert ist, wird für variable Webfonts die durchgängige Verwendung numerischer Werte empfohlen:
Sämtliche Achsen können auch über die Eigenschaft font-variation-settings adressiert werden (z.B. font-variation-settings: 'wdth' 75, 'wght' 700;), die aber keine Vererbung unterstützt und deshalb nur für Achsen ohne high-level property (z.B. EHLT) genutzt werden sollte.
Davon abgesehen, dass ich die verheißene Flexibilität für mein simples Design nicht benötige, schrecken mich die Dateigrößen. Schon mit statischen Webfonts mute ich Besucherinnen meiner Website (beim ersten Seitenaufruf) 507 KB zusätzlichen Datentransfer zu:
Mit den variablen Schriften der Source-Familie (Source Code Pro: wght, Source Sans 3: ital, wght, Source Serif 4: ital, opsz, wght) schrumpft die Anzahl der Dateien, aber das Datenvolumen verdoppelt sich auf 1,1 MB:
Fairerweise muss man einräumen, dass die statischen Noto-Schriften bereits 1,43 MB beanspruchen, aber auch hier verdoppelt sich die Dateigröße durch Variabilität (Noto Sans Mono: wdth, wght, Noto Sans Display: ital, wdth, wght, Noto Serif: ital, wdth, wght) auf 3,96 MB – ohne opsz:
Roboto (Roboto Mono: wght, Roboto Flex: opsz, slnt, wdth, wght, GRAD, XTRA, YOPQ, YTAS, YTDE, YTFI, YTLC, YTUC, Roboto Serif: ital, opsz, wdth, wght, GRAD) schließlich treibt die Bandbreitenverschwendung trotz einer fehlenden ital-Achse auf die Spitze (3,97 MB), wobei überraschenderweise nicht die 12-achsige Roboto Flex den meisten Platz beansprucht:
Nach einem Jahr und exakt 57 Jahre nach der NASA beende ich das Gemini-Experiment. Die parallele Pflege zweier Versionen jedes Blogposts war aufwendig, und der Glaube an die Überwindung eines kapitalistisch geprägten Internets voller Überwachungstechnologien durch das Gemini-Protokoll war nicht stark genug.
In den letzten Jahren hat Microsoft nach Rückschlägen im Bereich Mobile Computing den Nimbus als dunkles Imperium eingebüßt, aber auch unter Satya Nadella hat das Unternehmen seinen Biss nicht verloren: Wer die Unverschämtheit besitzt, ein externes E-Mail-Konto mit einem Microsoft-Produkt zu nutzen, muss selbstverständlich Einblick in dieses dubiose Konto geben, Microsoft-Admins regeln, wann in Deutschland gearbeitet wird, in Office-Produkten verwendete Bilder werden durch Microsoft 365 Intelligent Services analysiert, der Microsoft-eigene Keylogger trägt den schönen Namen writing assistance und mit dem grundlosen Beenden von Microsoft-Programmen ist ebenfalls Schluss – so zelebriert man Unvermeidlichkeit, Mr. Pichai. Nach diesen klaren Ansagen fällt es leicht, der wissenschaftlichen Community großzügig einen lang ersehnten Bugfix zu gewähren.
Gegenläufig zu meiner Tendenz, mich vornehmlich in ungestalteten Kommandozeilenumgebungen aufzuhalten (und entsprechende Protokolle zu verwenden), übernehme ich gern gut abgehangene gestalterische Innovationen für meine Website. Nach dem Aufbau eines CSS-Gitters vor einem Monat sind heute Web Fonts an der Reihe.
Google Fonts stellt mit Noto eine Sammlung von Schriften zur Verfügung, mit denen man zur Not auch eine multilinguale Website bespielen könnte, aber ich beschränke mich auf Noto Sans Display (für Überschriften), Noto Serif (für Fließtext) und Noto Sans Mono (für Code).
Natürlich stütze ich mich aus rechtlichen Erwägungen nicht auf Googles Font-Dienst, sondern komprimiere die OFL-lizenzierten Schriftdateien manuell mit woff2 –
PageSpeed macht mich freundlicherweise darauf aufmerksam, dass Besucherinnen ohne die Anweisung font-display:swap eine leere Seite sehen, bis sämtliche Webfonts geladen wurden. Außerdem merkt es kritisch an, dass die Webfonts nicht mit einem großzügig bemessenen expires-Header ausgeliefert werden. Das lässt sich leicht beheben, und bei dieser Gelegenheit ersetze/ergänze ich den veralteten Eintrag in mime.types der mit nginx 1.18.0 immer noch ausgeliefert wird:
Trotz dieser Maßnahmen beeinträchtigen Webfonts mein perfektes PageSpeed-Ergebnis: Nicht nur erhöht sich die Zeit bis zur Anzeige der Seite um 66%, PageSpeed bemängelt vor allem die Verschiebung von Seiteninhalten (CLS), die durch den Austausch der Systemschrift gegen den nachgeladenen Webfont (font-display: swap) entsteht. Google empfiehlt verschiedene Herangehensweisen, um layout shifts (und delayed text rendering) zu vermeiden:
Use fewer web fonts – Sehr lustig. Mit Systemschriften lassen sich layout shifts offensichtlich verhindern. Nutzerinnen sehr vieler Schriften aus einer Familie werden variable Webfonts ans Herz gelegt (n/a).
Choose an appropriate font-display strategy – Die Empfehlung bezieht sich auf delayed text rendering, für layout shifts gilt: font-display: auto, font-display: block, font-display: swap, and font-display: fallback all have the potential to cause layout shifts when the font is swapped.
Using self-hosted fonts with a CDN – Diese Herablassung gegenüber der Leistungsfähigkeit und Netzanbindung meines braven Servers weise ich entschieden zurück. Davon abgesehen wird meine Website nicht sehr stark frequentiert (10/2023: 19.355 Besucherinnen, ohne Crawler).
Subset fonts – n/a
Use WOFF2 – ✓
Reduce the shift between your fallback font and your webfont – Interessant, aber (derzeit noch) unpraktikabel. Die Verwendung von size-adjust erfordert eine Abschätzung des Größenunterschieds zwischen allen Fallback-Systemschriften und dem jeweiligen Webfont.
Inline font declarations – Nur sehr ungern pflege ich Formatierungsanweisungen außerhalb meiner CSS-Datei, zumal mit Font-Deklarationen auch nicht benötigte Schriften für jede Seite geladen werden, aber einen Versuch ist es wert.
Tatsächlich führt die Ergänzung des head-Bereichs der einzelnen Seiten um inline font declarations der Form –
Den hohen LCP-Wert, der für Mobilgeräte auf dem von PageSpeed/Lighthouse simulierten Moto G Power sogar bei 3,6 Sekunden liegt und die Bewertung der mobilen Performance auf 90/100 drückt, nehme ich zähneknirschend hin (wie auch die davon unabhängige suboptimale SEO-Einstufung wegen einer fehlenden meta description). Trotz dieses Wermutstropfens, und obgleich Gemini-Evangelistinnen die Nase rümpfen werden – schöne Schriften haben selbst im smolweb ihre Berechtigung.
Auch volljährige Menschen wickeln einen großen Teil ihrer Kommunikation über Messenger und bewahren wichtige Daten ausschließlich im Nachrichtenverlauf auf. Das betrifft sowohl Freundinnen von taz-Redakteurinnen –
Aus Versehen hätte sie alle Fotos unseres Whatsapp-Chatverlaufs gelöscht und da seien ja so viele Fotos ihrer Kinder dabei gewesen. Kurz und gut, ihre Anfrage lautete, ob ich ihr die bitte ALLE zusenden möge. ALLE bedeutet in diesem Fall: etwas über 2.000 Bilder.
TIL: Signal macht unter iOS aus Prinzip keine Backups von den eigenen Nachrichten. Wenn einem das Telefon ins Klo fällt: Sucks to be you! Macht es leider sehr schwer für mich, diese App weiterhin zu empfehlen.
Faszinierend. Ich finde in Chatverläufen bereits nach wenigen Tagen nichts mehr, so dass das automatische Verschwinden von Nachrichten (oder des Endgerätes) in der Praxis kaum einen Unterschied macht. Im Gegenzug ist mein E-Mail-Archiv bis zum Jahr 1993 durchsuchbar, und zwar recht geschwind. Es spricht eigentlich alles für die Beschaffung eines fuchtelfähigen Krückstocks.
Manchmal hängt Prokrastination weniger mit Faulheit zusammen als mit der Sorge, welche Ungeheuerlichkeiten man beim Putzen entdecken könnte. Aus diesem Grund habe ich den Einsatz von ruff lange vermieden – grundlos, wie sich jetzt herausstellt.
ruff bemängelt lediglich ein paar unnötig importierte Bibliotheken sowie pauschale except-Anweisungen und Importe (from mysites.forms import *). Von insgesamt 34 Fehlern im Code meiner gesamten Website kann der Linter 19 selbst beheben (--fix), und die Korrektur der übrigen kostet nur eine Minute.
Google ist tatsächlich zu zaghaft für ein dunkles Imperium. Nach einiger Kritik wird das angemessen perfide Web Integrity API wiederzurückgezogen, und man sucht nach anderen Wegen der gepflegten enshittification.
Menschen, die sich nach finished softwaresehnen, beziehen sich erstaunlich oft auf word processors wie Ted oder WordStar, die seit mindestens einem Jahrzehnt nicht mehr gepflegt werden.
In sehr isolierten Umgebungen mag dieser Grad der Unveränderlichkeit erstrebenswert sein, aber mir sind regelmäßige Fehlerbehebungen und Sicherheitsupdates ganz recht, auch wenn ich auf backward-incompatible changes bei meinen wichtigstenWerkzeugen gut verzichten kann. Oder, wie es ein Fan von Jef Poskanzer ausdrückt:
I'm currently studying web development, where we learn to use frameworks that constantly change and can break any code we write in a matter of days, weeks, or months. The fact that I can run this little C program you wrote over 30 years ago and it does exactly what it's supposed to with no fiddling? to me it points to a saner way of writing software that I hope to be able to practice at some point.
Meine Begeisterung für Diceware ist ähnlich irrational wie die für Emulationen alter HP-Taschenrechner (und ähnlich harmlos). Das frisch entwickelte phraze wird – anders als xkcdpass – nicht mit Wortlisten aus unterschiedlichen Sprachen geliefert, aber natürlich lassen sich die vorhandenen xkcdpass-Korpora verwenden, um Entropie-Werte oberhalb von 140 Bit bei überschaubarer Phrasenlänge zu realisieren:
% phraze --custom-list ./nor-nb -SSS -v --sep ' '
Passphrase has an estimated 153.59 bits of entropy (9 words from a list of 137172 words)
kjønrøk godtruenhet diktaturstat apparat niss kullete bøylelås felleskommunike proletarisk
Obwohl ich virtaama noituus pyörö viritä aloitus poreilla ein wenig vermissen werde – kjønrøk godtruenhet diktaturstat apparat niss kullete bøylelås felleskommunike proletarisk ist ein mindestens ebenso schönes Master-Passwort.
Natürlich kann man sich auch aus Hauffs Märchen eine Wortliste basteln –
#!/usr/local/bin/python3
import re
with open('/Users/snafu/hauff.txt', 'r') as corpus:
content = corpus.read()
# split text, convert all words to lower case, deduplicate and sort the resulting set
wordlist = sorted(set(map(str.lower, re.split('\W+', content))))
print('\n'.join(wordlist))
– um ein sehr deutsches Passwort zu erzeugen:
% phraze --custom-list ./hauff_list.txt -SSS -v --sep ' '
Passphrase has an estimated 141.36 bits of entropy (10 words from a list of 18008 words)
totemtheorie flimmernden lilienbette sichergestellt bedienstete foppen rieselten richtern perverse anblickte
Für selbstgebastelte Wortlisten bietet der phraze-Entwickler mit tidy übrigens ein weiteres Werkzeug an, mit dem sich zum Beispiel
nicht-alphanumerische Zeichen aus Wörtern (-n)
Wörter mit Zahlen (-i)
alle Wörter mit weniger als 5 Zeichen (-m 5)
Wörter, die der Listeneigenschaft uniquely decodable entgegenstehen (-P oder -S)
entfernen lassen:
% tidy -n -I -S -m 5 -AAAA --force --output wordlist_tidy.txt wordlist_hauff.txt
Printing new list...
Done making list.
Attributes of new list
----------------------
List length : 14366 words
Mean word length : 9.57 characters
Length of shortest word : 5 characters (abels)
Length of longest word : 28 characters (verwandtschaftsbezeichnungen)
Free of prefix words? : false
Free of suffix words? : true
Uniquely decodable? : true
Entropy per word : 13.810 bits
Efficiency per character : 1.443 bits
Assumed entropy per char : 2.762 bits
Above brute force line? : true
Above Shannon line? : false
Shortest edit distance : 1
Mean edit distance : 9.477
Longest shared prefix : 21
Unique character prefix : 22
Kraft-McMillan inequality : satisfied
Um die mit dem Parameter -SSS vorgegebene Mindestentropie von 140 Bit nicht zu unterschreiten, werden mit der gereinigten Hauff-Liste zwar 11 statt 10 Wörter benötigt, aber das Ergebnis kann sich ebenfalls sehen lassen:
% phraze --custom-list ./wordlist_tidy.txt -SSS -v --sep ' '
Passphrase has an estimated 151.91 bits of entropy (11 words from a list of 14366 words)
inhaltlich gleichgültig oranier kurios vergebe anhatte menschengeschlecht übernahme treppauf ablehnt reinigungen
Völlig unverständlich, dass manche Menschen sich mit der Generierungsfunktion ihres Passwortmanagers und der vorgegebenen Wortliste zufrieden geben.
Der Support für Python 2 wurde Ende 2019 eingestellt, und heute hat das Internet Archive den (weitgehenden) Abschluss seiner zweijährigen Migration auf Python 3 (one of the greatest software upgrade SNAFU’s in history) verkündet. Ich bin immer noch dankbar für die sehr schmale Codebase, die ich 2016 in wenigen Tagen migrieren konnte.
Wenn man mit msmtpq experimentiert und beim Aufräumen vergisst, den mutt-Parameter sendmail_wait auf den Standardwert zurückzusetzen, muss man sich über fehlende Fehlermeldungen (zum Beispiel wegen abweichender Zertifikatsfingerabdrücke) nicht wundern.
Es war nur eine Frage der Zeit, bis meine Häme über einen besonders zugänglichen Passwortmanager auf mich zurückfällt. Nun ist es im Umfeld meines Passwortmanagers zu einem Sicherheitsvorfallgekommen, und nach der kurzen Euphorie über die Vorteile von Passkeys umweht mich ein Hauch von Resignation.
There is a new feature of Mastodon that I wanted to highlight for you all.
I had no use for the Lists feature before- but as of the recent 4.2 update, you can put some people you follow into a list- and then you can check a box in that list's details to NOT SHOW their posts in your main feed.
Endlich kann man Firehose-Accounts abonnieren, ohne die Kontrolle über die eigene Timeline zu verlieren.
– bis ich realisiere, dass es sich bei den inkriminierten Nachrichten nicht um reguläre E-Mails handelt, sondern um von Postfix generiertedelivery status notifications. Diese Benachrichtigungen stammen nicht aus der wohldefinierten Domain eden.one, sondern aus der Subdomain mail.eden.one. Google greift auf die DMARC-Richtlinie der Domain eden.one zurück, kann aber einen SPF-Test mangels DNS-Eintrag für mail.eden.one nicht durchführen und vermisst eine DKIM-Signatur. Glücklicherweise lässt sich Google mit drei einfachen Maßnahmen –
# New DNS entry at domain provider
mail.eden.one 60 IN TXT "v=spf1 ip4:198.51.100.3 ~all"
# Addition to /etc/postfix/main.cf to have bounce messages get processed by the rspamd milter (for DKIM signatures)
internal_mail_filter_classes = bounce
# Create DKIM key for mail.eden.one
rspamadm dkim_keygen -s 's42' -b 2048 -d mail.eden.one -k /etc/rspamd/dkim_keys/mail.eden.one.s42.key
chmod 644 mail.eden.one.s42.key
Posteo ignoriert die DMARC-Richtlinie für die Basisdomain und scheitert am SPF-Test, weil Bounce-Nachrichten mangels aussagekräftigem Return-path (<>) keinen Absender, sondern nur den absendenden Server ausweist (smtp.helo). Das erste Problem lässt sich mit einem weiteren zusätzlichen DNS-Eintrag lösen –
# New DNS entry at domain provider
_dmarc.mail.eden.one 60 IN TXT v=DMARC1; p=reject; sp=reject; pct=100; rua=mailto:dmarc@eden.one; aspf=r; adkim=s; ri=259200;
– während ich den scheiternden SPF-Test (der dank der korrekten DKIM-Signatur auf das DMARC-Ergebnis keinen Einfluss hat) wohl hinnehmen muss:
Die renommierte Forschungseinrichtung Reddit hat herausgefunden, dass Blockchain-basierte Systeme einen kleinen Nachteil haben:
Reddit is winding down Community Points — the blockchain-based internet points program designed to reward creators and developers — in favor of prioritizing rewards programs that are less difficult to scale.
Though we saw some future opportunities for Community Points, the resourcing needed was unfortunately too high to justify, Reddit’s director of consumer and product communications Tim Rathschmidt told TechCrunch.
Zum Glück wurden bis zur Veröffentlichung dieses überraschenden Forschungsergebnisses keine größeren finanziellen oder energetischen Ressourcen in Blockchain-Projekte investiert.