Compare commits
68 Commits
dbd7893655
...
main
Author | SHA1 | Date | |
---|---|---|---|
858b18d33e | |||
568eeed18e | |||
4167963ce3 | |||
7345f0ebc4 | |||
fa78ccec80 | |||
e4e432790c | |||
2eb833436e | |||
f14e379996 | |||
a02e3eb569 | |||
664b195cf3 | |||
c08ae199f9 | |||
e1ed459c59 | |||
2f7506fbad | |||
416a9be424 | |||
8f71737822 | |||
2d2e1866fa | |||
16052c6218 | |||
682a999cdb | |||
d7c75324b3 | |||
3fd639bd9a | |||
8afbd539de | |||
c186102be1 | |||
317de5e09a | |||
275426d7a3 | |||
503b7b134f | |||
2e8e9f5f93 | |||
fd71ea2590 | |||
c2df6c42aa | |||
a9564ba3b2 | |||
c4b54a375c | |||
2b404604f2 | |||
044713c8a0 | |||
03afdaa60d | |||
495f60003e | |||
9695785e5c | |||
00b49683fd | |||
19b0aad18e | |||
98aed49586 | |||
6e1c5bc28e | |||
3f2e0cfdd8 | |||
a390110d99 | |||
778deb5fc6 | |||
01cd37f30a | |||
ac51f7e6c4 | |||
0fe51c2d8b | |||
cda4e76a0a | |||
4ff291d3aa | |||
7092b10849 | |||
b5118b2835 | |||
6eefaf6b90 | |||
9f8d57aa62 | |||
548e25d814 | |||
830c120e54 | |||
9e1a2dfce4 | |||
e3d4fd1719 | |||
9d52cab006 | |||
c9a1cb2a46 | |||
650e61d07d | |||
4c592a0dfc | |||
75bfe523e8 | |||
5d919a51f6 | |||
96ccdaac05 | |||
63cec178e5 | |||
de9708d049 | |||
fa665929d7 | |||
4c96940da1 | |||
f74d74f489 | |||
2848173525 |
41
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,41 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose
|
||||
{
|
||||
"name": "Existing Docker Compose (Extend)",
|
||||
|
||||
// Update the 'dockerComposeFile' list if you have more compose files or use different names.
|
||||
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
|
||||
"dockerComposeFile": [
|
||||
"../docker-compose.yml",
|
||||
"docker-compose.yml"
|
||||
],
|
||||
|
||||
// The 'service' property is the name of the service for the container that VS Code should
|
||||
// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
|
||||
"service": "server",
|
||||
|
||||
// The optional 'workspaceFolder' property is the path VS Code should open by default when
|
||||
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}"
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.>
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Uncomment the next line if you want start specific services in your Docker Compose config.
|
||||
// "runServices": [],
|
||||
|
||||
// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
|
||||
// "shutdownAction": "none",
|
||||
|
||||
// Uncomment the next line to run commands after the container is created.
|
||||
// "postCreateCommand": "cat /etc/os-release",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "devcontainer"
|
||||
}
|
26
.devcontainer/docker-compose.yml
Normal file
@ -0,0 +1,26 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
# Update this to the name of the service you want to work with in your docker-compose.yml file
|
||||
server:
|
||||
# Uncomment if you want to override the service's Dockerfile to one in the .devcontainer
|
||||
# folder. Note that the path of the Dockerfile and context is relative to the *primary*
|
||||
# docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
|
||||
# array). The sample below assumes your primary file is in the root of your project.
|
||||
#
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: .devcontainer/Dockerfile
|
||||
|
||||
volumes:
|
||||
# Update this to wherever you want VS Code to mount the folder of your project
|
||||
- ..:/workspaces:cached
|
||||
|
||||
# Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
|
||||
# cap_add:
|
||||
# - SYS_PTRACE
|
||||
# security_opt:
|
||||
# - seccomp:unconfined
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
# command: sleep infinity
|
||||
|
12
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for more information:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
# https://containers.dev/guide/dependabot
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "devcontainers"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
@ -1,6 +1,8 @@
|
||||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
desription:
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
draft: false
|
||||
ShowLastmod: true
|
||||
---
|
||||
|
||||
|
12
archetypes/posts.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
description:
|
||||
date: {{ .Date }}
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
images: []
|
||||
tags: []
|
||||
---
|
||||
|
@ -4,12 +4,25 @@ $text: hsl(204, 28%, 93%);
|
||||
$light-grey: #292e32; // #494f5c;
|
||||
$dark-grey: #3B3E48;
|
||||
$highlight-grey: #7d828a;
|
||||
$midnightblue: #2c3e50;
|
||||
$typewriter: hsl(172, 100%, 36%);
|
||||
$codebackground: #1c2023 !default;
|
||||
$midnightblue: $codebackground;
|
||||
|
||||
// Scroll to Top Default colors
|
||||
|
||||
$stt-stroke:#CCC;
|
||||
$stt-circle:#3b3e48;
|
||||
$stt-arrow:#018574;
|
||||
|
||||
kbd {
|
||||
font-size: 0.9em !important;
|
||||
color: inherit;
|
||||
background-color: $midnightblue;
|
||||
}
|
||||
|
||||
// Fonts
|
||||
$fonts: "IBM Plex Sans Light", "Segoe UI", Candara, sans-serif;
|
||||
$code-fonts: "IBM Plex Mono", Consolas, "Andale Mono WT", "Andale Mono", Menlo, Monaco, monospace;
|
||||
$code-fonts: "IBM Plex Mono Light", Consolas, "Andale Mono WT", "Andale Mono", Menlo, Monaco, monospace;
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Light';
|
||||
@ -33,18 +46,18 @@ $code-fonts: "IBM Plex Mono", Consolas, "Andale Mono WT", "Andale Mono", Menlo,
|
||||
font-family: 'IBM Plex Sans Light';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('IBM Plex Sans Medm'),
|
||||
local('IBMPlexSans-Medm'),
|
||||
url('/fonts/IBMPlexSans-Medium.woff2') format('woff2');
|
||||
src: local('IBM Plex Sans Bold'),
|
||||
local('IBMPlexSans-Bold'),
|
||||
url('/fonts/IBMPlexSans-Bold.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Light';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('IBM Plex Sans Medm Italic'),
|
||||
local('IBMPlexSans-MedmItalic'),
|
||||
url('/fonts/IBMPlexSans-MediumItalic.woff2') format('woff2');
|
||||
src: local('IBM Plex Sans Bold Italic'),
|
||||
local('IBMPlexSans-BoldItalic'),
|
||||
url('/fonts/IBMPlexSans-BoldItalic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
26
config.toml
@ -13,9 +13,13 @@ pygmentsUseClasses = true
|
||||
|
||||
rssLimit = 10
|
||||
|
||||
copyright = "2024 Daniel Kraus"
|
||||
copyright = "2024-2025 Daniel Kraus"
|
||||
enableEmoji = true
|
||||
|
||||
[permalinks]
|
||||
[permalinks.page]
|
||||
posts = '/posts/:year/:month/:slug/'
|
||||
|
||||
#[services]
|
||||
# [services.disqus]
|
||||
# shortname = ''
|
||||
@ -42,6 +46,9 @@ expiryDate = ["expiryDate"]
|
||||
# Categories are disabled by default.
|
||||
# category = "categories"
|
||||
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
|
||||
# Enable to get proper Mathjax support
|
||||
#[markup]
|
||||
# [markup.goldmark]
|
||||
@ -70,7 +77,7 @@ expiryDate = ["expiryDate"]
|
||||
ShowLastmod = true
|
||||
gitUrl = "https://git.bovender.de/daniel/blog/commit/"
|
||||
|
||||
justifyContent = true
|
||||
justifyContent = false
|
||||
|
||||
relatedPosts = true
|
||||
code_copy_button = true
|
||||
@ -91,13 +98,17 @@ expiryDate = ["expiryDate"]
|
||||
initialPublish = "Initally Posted on: "
|
||||
human = ["single","posts"]
|
||||
|
||||
[[params.socialLinks]]
|
||||
name = "bluesky"
|
||||
url = "https://bsky.app/profile/bovender.bsky.social"
|
||||
|
||||
[[params.socialLinks]]
|
||||
name = "mastodon"
|
||||
url = "https://neph.social/@daniel_kraus"
|
||||
|
||||
[[params.socialLinks]]
|
||||
name = "x"
|
||||
url = "https://twitter.com/bovender_de"
|
||||
# [[params.socialLinks]]
|
||||
# name = "x"
|
||||
# url = "https://twitter.com/bovender_de"
|
||||
|
||||
[[params.socialLinks]]
|
||||
name = "github"
|
||||
@ -127,3 +138,8 @@ expiryDate = ["expiryDate"]
|
||||
name = "Impressum"
|
||||
url = "impressum/"
|
||||
weight = 30
|
||||
|
||||
[[menu.main]]
|
||||
name = "Cheat sheet"
|
||||
url = "cheatsheet/"
|
||||
weight = 15
|
||||
|
@ -3,12 +3,18 @@ title: "About bovender"
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< figure src="plesse.png" alt="Image of the Plesse castle" title="The Plesse castle (© Daniel Kraus)">}}
|
||||
|
||||
I'm a physician-scientist from Germany.
|
||||
|
||||
[Bovenden](https://www.bovenden.de) is the name of the village/small town
|
||||
in lovely Lower Saxony, Germany, where I grew up. Hence I'm a bovender --
|
||||
not _the_ bovender, of course, just one of many, but proud to be one :-)
|
||||
|
||||
Not far from our village is the [Plesse
|
||||
castle](https://en.wikipedia.org/wiki/Plesse_Castle) which was built c. 1015 AD
|
||||
and which is one of my favorite places to be.
|
||||
|
||||
Computers have been part of my life ever since I programmed my dad's
|
||||
calculator back in the 1980's.
|
||||
|
||||
@ -16,18 +22,31 @@ calculator back in the 1980's.
|
||||
|
||||
### [ggap-ev.org](https://ggap-ev.org)
|
||||
|
||||
GGAP is a registered charity in Germany. This NGO uses donated money to fund the
|
||||
vocational training of young adults on the Philippines who would otherwise have
|
||||
to "earn" their living in an unlawful way.
|
||||
|
||||
{{< figure src="ggap-ev.png">}}
|
||||
|
||||
### [nephrowiki.de](https://nephrowiki.de)
|
||||
|
||||
NephroWiki is a closed wiki system that has been up and running since 2011.
|
||||
|
||||
{{< figure src="nephrowiki.png">}}
|
||||
|
||||
## Portfolio -- software
|
||||
|
||||
### [Daniel's XL Toolbox](https://xltoolbox.net)
|
||||
|
||||
Daniel's XL Toolbox is an add-in for Excel that assists with data analysis
|
||||
and visualization.
|
||||
|
||||
{{< figure src="xltoolbox.png">}}
|
||||
|
||||
Even though I haven't been able to work on this project for quite some time,
|
||||
I am very happy to see that the latest release has been downloaded more than
|
||||
100,000 times. :-)
|
||||
|
||||
### [LinkTitles](https://github.com/bovender/linktitles)
|
||||
|
||||
LinkTitles is a [MediaWiki](https://mediawiki.org) extension that automatically
|
||||
@ -38,6 +57,10 @@ links terms to existing pages in a Wiki.
|
||||
PubmedParser is a [MediaWiki](https://mediawiki.org) extension to include and
|
||||
reference articles from [Pubmed](https://pubmed.gov) by PMID.
|
||||
|
||||
### Github Star History
|
||||
|
||||
[![Star History Chart][sh-img]][sh-link]
|
||||
|
||||
## Server administration
|
||||
|
||||
I have 10+ years experience with running and administrating my own server.
|
||||
@ -45,3 +68,7 @@ This includes running a mail and an IMAP server, hosting my own
|
||||
[Nextcloud](https://nextcloud.com) and [Gitea](https://gitea.io) instances,
|
||||
[Mastodon for Nephrology](https://neph.social)
|
||||
and more. Almost all of my services are dockerized.
|
||||
|
||||
|
||||
[sh-img]: https://api.star-history.com/svg?repos=bovender/linktitles,bovender/pubmedparser,bovender/imapcli,bovender/xltoolbox&type=Date
|
||||
[sh-link]: https://www.star-history.com/#bovender/linktitles&bovender/pubmedparser&bovender/imapcli&bovender/xltoolbox&Date
|
BIN
content/about/plesse.png
Normal file
After Width: | Height: | Size: 597 KiB |
97
content/cheatsheet.md
Normal file
@ -0,0 +1,97 @@
|
||||
---
|
||||
title: bovender's personal cheat sheet
|
||||
description: >
|
||||
On this page, I collect bits and pieces of information that I
|
||||
never seem to be able to memorize. This stuff is helpful for me
|
||||
and maybe it is helpful for you, too, internet wanderer.
|
||||
date: 2024-11-05T08:00:00+0100
|
||||
draft: false
|
||||
---
|
||||
{{< git-info >}}
|
||||
{{< param "description" >}}
|
||||
|
||||
## R
|
||||
|
||||
### Print R objects so that they can be re-generated from code
|
||||
|
||||
```r
|
||||
# dput takes an R object
|
||||
dput(x, file = "", control = c("keepNA", "keepInteger", "niceNames", "showAttributes"))
|
||||
dget(file, keep.source = FALSE)
|
||||
|
||||
# dump takes a list of R object names
|
||||
dump(list, file = "dumpdata.R", append = FALSE, control = "all", envir = parent.frame(), evaluate = TRUE)
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
``` r
|
||||
library(dplyr)
|
||||
my_data <-
|
||||
starwars |>
|
||||
select(species, homeworld)
|
||||
dput(my_data)
|
||||
#> structure(list(species = c("Human", "Droid", "Droid", "Human",
|
||||
#> "Human", "Human", "Human", "Droid", "Human", "Human", "Human",
|
||||
#> "Human", "Wookiee", "Human", "Rodian", "Hutt", "Human", NA, "Yoda's species",
|
||||
#> "Human", "Human", "Droid", "Trandoshan", "Human", "Human", "Mon Calamari",
|
||||
#> "Human", "Human", "Ewok", "Sullustan", "Human", "Neimodian",
|
||||
#> "Human", "Human", "Gungan", "Gungan", "Gungan", "Human", "Toydarian",
|
||||
#> "Dug", "Human", "Human", "Zabrak", "Twi'lek", "Twi'lek", "Aleena",
|
||||
#> "Vulptereen", "Xexto", "Toong", "Human", "Cerean", "Nautolan",
|
||||
#> "Zabrak", "Tholothian", "Iktotchi", "Quermian", "Kel Dor", "Chagrian",
|
||||
#> NA, NA, "Human", "Geonosian", "Mirialan", "Mirialan", "Human",
|
||||
#> "Human", "Human", "Human", "Clawdite", "Besalisk", "Kaminoan",
|
||||
#> "Kaminoan", "Human", "Droid", "Skakoan", "Muun", "Togruta", "Kaleesh",
|
||||
#> "Wookiee", "Human", NA, "Pau'an", "Human", "Human", "Human",
|
||||
#> "Droid", "Human"), homeworld = c("Tatooine", "Tatooine", "Naboo",
|
||||
#> "Tatooine", "Alderaan", "Tatooine", "Tatooine", "Tatooine", "Tatooine",
|
||||
#> "Stewjon", "Tatooine", "Eriadu", "Kashyyyk", "Corellia", "Rodia",
|
||||
#> "Nal Hutta", "Corellia", "Bestine IV", NA, "Naboo", "Kamino",
|
||||
#> NA, "Trandosha", "Socorro", "Bespin", "Mon Cala", "Chandrila",
|
||||
#> NA, "Endor", "Sullust", NA, "Cato Neimoidia", "Coruscant", "Naboo",
|
||||
#> "Naboo", "Naboo", "Naboo", "Naboo", "Toydaria", "Malastare",
|
||||
#> "Naboo", "Tatooine", "Dathomir", "Ryloth", "Ryloth", "Aleen Minor",
|
||||
#> "Vulpter", "Troiken", "Tund", "Haruun Kal", "Cerea", "Glee Anselm",
|
||||
#> "Iridonia", "Coruscant", "Iktotch", "Quermia", "Dorin", "Champala",
|
||||
#> "Naboo", "Naboo", "Tatooine", "Geonosis", "Mirial", "Mirial",
|
||||
#> "Naboo", "Serenno", "Alderaan", "Concord Dawn", "Zolan", "Ojom",
|
||||
#> "Kamino", "Kamino", "Coruscant", NA, "Skako", "Muunilinst", "Shili",
|
||||
#> "Kalee", "Kashyyyk", "Alderaan", "Umbara", "Utapau", NA, NA,
|
||||
#> NA, NA, NA)), row.names = c(NA, -87L), class = c("tbl_df", "tbl",
|
||||
#> "data.frame"))
|
||||
```
|
||||
|
||||
<sup>Created on 2024-11-05 with [reprex v2.1.1](https://reprex.tidyverse.org)</sup>
|
||||
|
||||
### Calculate percentages of subgroups
|
||||
|
||||
Principle: `Group` the dataset and `summarize` by counting (`n()`), drop the last group
|
||||
while doing so, then `mutate` the dataset by adding the count divided by the sum of counts.
|
||||
|
||||
Hint to better understand the example: Look at the sexes in black-eyed subjects.
|
||||
|
||||
``` r
|
||||
library(dplyr)
|
||||
starwars |>
|
||||
group_by(eye_color, gender) |>
|
||||
summarize(n = n(), .groups = "drop_last") |>
|
||||
mutate(p = n/sum(n))
|
||||
#> # A tibble: 23 × 4
|
||||
#> # Groups: eye_color [15]
|
||||
#> eye_color gender n p
|
||||
#> <chr> <chr> <int> <dbl>
|
||||
#> 1 black feminine 2 0.2
|
||||
#> 2 black masculine 8 0.8
|
||||
#> 3 blue feminine 6 0.316
|
||||
#> 4 blue masculine 12 0.632
|
||||
#> 5 blue <NA> 1 0.0526
|
||||
#> 6 blue-gray masculine 1 1
|
||||
#> 7 brown feminine 4 0.190
|
||||
#> 8 brown masculine 15 0.714
|
||||
#> 9 brown <NA> 2 0.0952
|
||||
#> 10 dark masculine 1 1
|
||||
#> # ℹ 13 more rows
|
||||
```
|
||||
|
||||
<sup>Created on 2024-11-05 with [reprex v2.1.1](https://reprex.tidyverse.org)</sup>
|
@ -3,15 +3,16 @@ title: "Impressum und Datenschutz"
|
||||
date: 2024-07-31T12:42:53Z
|
||||
draft: false
|
||||
---
|
||||
|
||||
Dr. Daniel Kraus
|
||||
H‌ö‌hen‌str‌aße 1‌5
|
||||
65451 K‌e‌l‌s‌t‌e‌r‌b‌a‌c‌h
|
||||
Tel. (0 61 07‌) 7 5‌6 88 4‌0
|
||||
<bovender@bovender.de>
|
||||
Mail: <bovender@bovender.de>
|
||||
|
||||
Ich bitte, von Anrufen abzusehen. Über Mail oder die verlinkten
|
||||
sozialen Netzwerke (siehe Icons) bin ich i.d.R. kurzfristig
|
||||
Ladungsfähige Anschrift bei meinem Arbeitgeber: Unimedizin Mainz, Langenbeckstr.
|
||||
1, 55131 Mainz. Dies ist eine rein private Homepage und in keiner Weise von
|
||||
meinem Arbeitgeber beworben oder unterstützt. Meine Privatanschrift ist zu
|
||||
meinem persönlichen Schutz und dem Schutz meiner Familie hier nicht
|
||||
veröffentlicht. **Ich bitte, von Anrufen abzusehen.** Über Mail oder die
|
||||
verlinkten sozialen Netzwerke (siehe Icons) bin ich i.d.R. kurzfristig
|
||||
erreichbar.
|
||||
|
||||
## Mailserver bovender.de
|
||||
|
215
content/posts/2024/dane-tlsa-record-for-letsencrypt/index.md
Normal file
@ -0,0 +1,215 @@
|
||||
---
|
||||
title: "How to create or renew a TLSA record for DANE with certificates from Let's Encrypt"
|
||||
slug: create-or-renew-tlsa-records
|
||||
description: >
|
||||
I had to renew my TLSA records for DNS-based Authentication of Named Entities (DANE)
|
||||
on my server that uses SSL certificates from Let's Encrypt.
|
||||
date: 2024-10-03T09:26:58Z
|
||||
draft: false
|
||||
ShowLastmod: true
|
||||
toc: true
|
||||
scrolltotop: true
|
||||
tags:
|
||||
- email
|
||||
- server
|
||||
- security
|
||||
---
|
||||
|
||||
I have been operating my own mail server for some 10 years. Recently, some
|
||||
e-mails that others attempted to send to me any my family would not longer be
|
||||
delivered. This is a very unfortunate situation, because most senders will not
|
||||
make a second attempt (e.g., with a different recipient address), leave alone
|
||||
provide you with an error message. However, luckily, this morning (Reunification
|
||||
Day in Germany!), I received this screenshot:
|
||||
|
||||
{{< figure src="outlook-error.jpg"
|
||||
alt="(German) screenshot of an e-mail delivery error due to an invalid TLSA record"
|
||||
caption="(German) screenshot of an e-mail delivery error due to an invalid TLSA record">}}
|
||||
|
||||
And then it was all clear to me. The resource records (RR) in my domain-name
|
||||
system (DNS) configuration that configure [DNS-based Authentication of Named
|
||||
Entities (DANE)][dane] were no longer valid. I use certificates issued by Let's
|
||||
Encrypt, and evidently their chain of trust had changed so that the trust
|
||||
anchors that I had written into my DNS records were no longer valid.
|
||||
|
||||
Correctly setting up TLSA records for DANE is everything but trivial. After some
|
||||
trial and error, I found the following resource most useful:
|
||||
|
||||
<https://dnssec-stats.ant.isi.edu/~viktor/x3hosts.html>
|
||||
|
||||
Key points from this and other pages to remember:
|
||||
|
||||
- When using TLSA RR in the form `2 1 1 ...`, i.e., declaring that the payload
|
||||
(`...`) of the record is the digest of a "trust anchor", be aware that this
|
||||
must be the `SHA256` digest of the _public key_ of your trust anchor.
|
||||
- Let's Encrypt's `certbot` tool places three files on your server: `cert.pem`,
|
||||
`chain.pem` and `fullchain.pem`, where `cert.pem` is the mail server's
|
||||
certificate, `chain.pem` is the trust chain _excluding the root CA_ and
|
||||
`fullchain.pem` is `cert.pem` + `chain.pem`. The important bit here is that
|
||||
**none of these certificate files contain the root CA**!
|
||||
- As a consequence, do not use a digest of Let's Encrypt's root CAs in your TLSA
|
||||
record, _unless_ you want to add that root CA to the certificate files on your
|
||||
server (don't).
|
||||
- Configure TLSA records for _all_ intermediate CAs, e.g., R10-R14.
|
||||
|
||||
Interstingly and embarassingly, the Information Sciences Institute (ISI) on
|
||||
their page linked to above has a list of mail servers that still use a TLSA
|
||||
record with a _retired_ CA from Let's Encrypt -- and that list contains
|
||||
`bovender.de' :-/ I guess it's about time to fix that!
|
||||
|
||||
## Let's Encrypt's chains of trust
|
||||
|
||||
Let's Encrypt's chains of trust are described here:
|
||||
|
||||
<https://letsencrypt.org/certificates/>
|
||||
|
||||
For the reasons mentioned above, we are not interested in the root CAs (ISRG
|
||||
Root X1 and ISRG Root X2). Instead, we want to use the digests of the public
|
||||
keys of the subordinate (intermediate) CAs. At the time of writing (2024-10),
|
||||
Let's Encrypt lists four active intermediate CAs:
|
||||
|
||||
- E5 and E6 for certificates with ECDSA public keys
|
||||
- R10 and R11 for certificates with RSA public keys
|
||||
|
||||
To find out which of the two algorithms (ECDSA and RSA) was used to generate
|
||||
your mail server's certificate, ssh into your server, navigate to
|
||||
`/etc/letsencrypt/live/<mailserver-certificate-name>` as root and issue:
|
||||
|
||||
```text
|
||||
$ openssl x509 -in cert.pem -noout -text
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
03:d3:73:ef:60:97:31:88:bc:e5:31:99:f3:00:d5:b0:c1:92
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C = US, O = Let's Encrypt, CN = R11
|
||||
Validity
|
||||
Not Before: Sep 24 22:37:51 2024 GMT
|
||||
Not After : Dec 23 22:37:50 2024 GMT
|
||||
Subject: CN = bovender.de
|
||||
Subject Public Key Info:
|
||||
# ...
|
||||
```
|
||||
|
||||
Thus, my mail server's current (!) certificate was issued by **R11**.
|
||||
|
||||
This may be different in the future when the certificate is renewed.
|
||||
|
||||
Currently my web server's certificate uses the ECDSA algorithm:
|
||||
|
||||
```text
|
||||
$ cd ../bovender/
|
||||
$ openssl x509 -in cert.pem -noout -text
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
04:22:00:17:8a:ed:51:09:56:15:62:2e:b5:85:49:70:05:ac
|
||||
Signature Algorithm: ecdsa-with-SHA384
|
||||
Issuer: C = US, O = Let's Encrypt, CN = E6
|
||||
Validity
|
||||
Not Before: Aug 19 11:34:11 2024 GMT
|
||||
Not After : Nov 17 11:34:10 2024 GMT
|
||||
Subject: CN = bovender.de
|
||||
```
|
||||
|
||||
This certificate was issued by the **E6** intermediate CA.
|
||||
|
||||
An alternative way to find out more about a web server's SSL certificate is to
|
||||
use `curl`:
|
||||
|
||||
```plain
|
||||
$ curl -I --write-out '%{certs}' https://bovender.de
|
||||
HTTP/2 301
|
||||
server: nginx/1.27.1
|
||||
date: Thu, 03 Oct 2024 09:25:05 GMT
|
||||
content-type: text/html
|
||||
content-length: 169
|
||||
location: https://www.bovender.de/
|
||||
strict-transport-security: max-age=63072000; includeSubDomains; preload
|
||||
|
||||
# ...
|
||||
|
||||
Subject:C = US, O = Let's Encrypt, CN = E6
|
||||
Issuer:C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
|
||||
# ...
|
||||
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
E6 again (surprise!).
|
||||
|
||||
The key type that `certbot` uses to renew certificates is contained in
|
||||
`/etc/letsencrypt/renewal/<mailserver-certificate-name>.conf`:
|
||||
|
||||
```plain
|
||||
$ cat mail.conf
|
||||
# renew_before_expiry = 30 days
|
||||
version = 2.11.0
|
||||
archive_dir = /etc/letsencrypt/archive/mail
|
||||
cert = /etc/letsencrypt/live/mail/cert.pem
|
||||
privkey = /etc/letsencrypt/live/mail/privkey.pem
|
||||
chain = /etc/letsencrypt/live/mail/chain.pem
|
||||
fullchain = /etc/letsencrypt/live/mail/fullchain.pem
|
||||
|
||||
# Options used in the renewal process
|
||||
[renewalparams]
|
||||
account = [REDACTED]
|
||||
authenticator = webroot
|
||||
server = https://acme-v02.api.letsencrypt.org/directory
|
||||
key_type = rsa
|
||||
# ...
|
||||
```
|
||||
|
||||
The line `key_type` declares that RSA be used for my mail server's certificate.
|
||||
|
||||
## Setting up TLSA RRs in your DNS zone
|
||||
|
||||
As advised by [ISI.edu][isi], it is best practive to have TLSA records for all
|
||||
possible intermediate CAs as you will never know which intermediate CA will be
|
||||
the next to issue a renewed certificate. This is even more important if you have
|
||||
a cron job set up to automatically renew certificates that are about to expire.
|
||||
|
||||
For the record (pun intended), here are my `_25._tcp` entries. Everything after
|
||||
the pound sign (`#`) MUST NOT be included in the record, of course.
|
||||
|
||||
```plain
|
||||
2 1 1 2bbad93ab5c79279ec121507f272cbe0c6647a3aae52e22f388afab426b4adba # R10
|
||||
2 1 1 6ddac18698f7f1f7e1c69b9bce420d974ac6f94ca8b2c761701623f99c767dc7 # R11
|
||||
```
|
||||
|
||||
Following the advice by _viktor_ on [ISI.edu][isi], I have left only those two
|
||||
records in my DNS zone that represent currently used intermediate CAs (R10 and
|
||||
R11). I know that `certbot` will not issue ECDSA certificates for my mail server
|
||||
because the configuration file (see above) contains the line `key_type=RSA`. My
|
||||
web server has an ECDSA certificate, but I currently see no use for DANE for my
|
||||
web server as [Chrome and Firefox do not support it][dane].
|
||||
|
||||
## Verify the validity of the TLSA record
|
||||
|
||||
To test that DANE works as expected, try one of these sites:
|
||||
|
||||
- <https://www.mailhardener.com/tools/dane-validator?domain=bovender.de>
|
||||
- <https://dane.sys4.de/smtp/bovender.de>
|
||||
|
||||
Query TLSA records:
|
||||
|
||||
- <https://www.nslookup.io/domains/_25._tcp.bovender.de/dns-records/tlsa/>
|
||||
|
||||
## "Die Moral von der Geschicht"
|
||||
|
||||
"The moral of the story":
|
||||
|
||||
- Server administration was, is, and will always be complicated.
|
||||
- Always mistrust yourself.
|
||||
- Regularly verify that everything works as expected, especially
|
||||
security-related stuff.
|
||||
- If something does not work, spend more time researching on the world wide web.
|
||||
I came across the [ISI.edu][isi] page only after several hours and is was far
|
||||
more useful for me than all the websites and blog and forum posts that I had
|
||||
previously read.
|
||||
|
||||
[dane]: https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities
|
||||
[isi]: https://dnssec-stats.ant.isi.edu/~viktor/x3hosts.html
|
After Width: | Height: | Size: 306 KiB |
74
content/posts/2024/duckplyr-performance/benchmarking1.svg
Normal file
@ -0,0 +1,74 @@
|
||||
<?xml version='1.0' encoding='UTF-8' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' class='svglite' width='576.00pt' height='360.00pt' viewBox='0 0 576.00 360.00'>
|
||||
<defs>
|
||||
<style type='text/css'><![CDATA[
|
||||
.svglite line, .svglite polyline, .svglite polygon, .svglite path, .svglite rect, .svglite circle {
|
||||
fill: none;
|
||||
stroke: #000000;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-miterlimit: 10.00;
|
||||
}
|
||||
.svglite text {
|
||||
white-space: pre;
|
||||
}
|
||||
]]></style>
|
||||
</defs>
|
||||
<rect width='100%' height='100%' style='stroke: none; fill: #292E32;'/>
|
||||
<defs>
|
||||
<clipPath id='cpMC4wMHw1NzYuMDB8MC4wMHwzNjAuMDA='>
|
||||
<rect x='0.00' y='0.00' width='576.00' height='360.00' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path='url(#cpMC4wMHw1NzYuMDB8MC4wMHwzNjAuMDA=)'>
|
||||
<rect x='0.000000000000064' y='0.00' width='576.00' height='360.00' style='stroke-width: 1.07; stroke: #FFFFFF; fill: #292E32;' />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id='cpNTkuMTB8NDIxLjE5fDQ2LjQxfDMwMi45OQ=='>
|
||||
<rect x='59.10' y='46.41' width='362.09' height='256.58' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path='url(#cpNTkuMTB8NDIxLjE5fDQ2LjQxfDMwMi45OQ==)'>
|
||||
<polyline points='59.10,302.99 421.19,302.99 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,238.06 421.19,238.06 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,173.14 421.19,173.14 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,108.22 421.19,108.22 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<circle cx='151.61' cy='98.97' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='177.19' cy='91.03' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='174.46' cy='99.26' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='296.36' cy='101.41' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='320.05' cy='105.05' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='353.79' cy='91.38' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='325.12,89.89 331.35,100.67 318.89,100.67 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='327.97,92.20 334.20,102.98 321.75,102.98 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='327.74,96.20 333.97,106.98 321.52,106.98 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='167.19,87.42 173.41,98.20 160.96,98.20 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='190.42,81.99 196.65,92.77 184.20,92.77 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='165.99,82.14 172.22,92.92 159.77,92.92 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
</g>
|
||||
<g clip-path='url(#cpMC4wMHw1NzYuMDB8MC4wMHwzNjAuMDA=)'>
|
||||
<polyline points='59.10,302.99 59.10,46.41 ' style='stroke-width: 2.13; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<text x='54.17' y='306.91' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='6.29px' lengthAdjust='spacingAndGlyphs'>0</text>
|
||||
<text x='54.17' y='241.99' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='12.59px' lengthAdjust='spacingAndGlyphs'>10</text>
|
||||
<text x='54.17' y='177.07' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='12.59px' lengthAdjust='spacingAndGlyphs'>20</text>
|
||||
<text x='54.17' y='112.14' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='12.59px' lengthAdjust='spacingAndGlyphs'>30</text>
|
||||
<polyline points='56.36,302.99 59.10,302.99 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='56.36,238.06 59.10,238.06 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='56.36,173.14 59.10,173.14 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='56.36,108.22 59.10,108.22 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,302.99 421.19,302.99 ' style='stroke-width: 2.13; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='157.85,305.73 157.85,302.99 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='322.44,305.73 322.44,302.99 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<text x='157.85' y='315.77' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='26.52px' lengthAdjust='spacingAndGlyphs'>dplyr</text>
|
||||
<text x='322.44' y='315.77' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='44.47px' lengthAdjust='spacingAndGlyphs'>duckplyr</text>
|
||||
<text x='240.15' y='329.01' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='36.24px' lengthAdjust='spacingAndGlyphs'>Library</text>
|
||||
<text transform='translate(36.20,174.70) rotate(-90)' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='40.16px' lengthAdjust='spacingAndGlyphs'>Time (s)</text>
|
||||
<rect x='432.15' y='143.95' width='115.50' height='61.50' style='stroke-width: 1.07; stroke: none; fill: #3B3E48;' />
|
||||
<text x='437.63' y='158.61' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='104.54px' lengthAdjust='spacingAndGlyphs'>Laptop power mode</text>
|
||||
<circle cx='446.27' cy='174.05' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='446.27,184.14 452.50,194.92 440.04,194.92 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<text x='460.39' y='177.19' style='font-size: 8.80px;fill: #FFFFFF; font-family: "Arial";' textLength='37.56px' lengthAdjust='spacingAndGlyphs'>balanced</text>
|
||||
<text x='460.39' y='194.47' style='font-size: 8.80px;fill: #FFFFFF; font-family: "Arial";' textLength='53.76px' lengthAdjust='spacingAndGlyphs'>performance</text>
|
||||
<text x='59.10' y='37.76' style='font-size: 13.20px;fill: #FFFFFF; font-family: "Arial";' textLength='224.18px' lengthAdjust='spacingAndGlyphs'>Time elapsed with dplyr vs. duckplyr</text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.5 KiB |
87
content/posts/2024/duckplyr-performance/benchmarking2.svg
Normal file
@ -0,0 +1,87 @@
|
||||
<?xml version='1.0' encoding='UTF-8' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' class='svglite' width='576.00pt' height='360.00pt' viewBox='0 0 576.00 360.00'>
|
||||
<defs>
|
||||
<style type='text/css'><![CDATA[
|
||||
.svglite line, .svglite polyline, .svglite polygon, .svglite path, .svglite rect, .svglite circle {
|
||||
fill: none;
|
||||
stroke: #000000;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-miterlimit: 10.00;
|
||||
}
|
||||
.svglite text {
|
||||
white-space: pre;
|
||||
}
|
||||
]]></style>
|
||||
</defs>
|
||||
<rect width='100%' height='100%' style='stroke: none; fill: #292E32;'/>
|
||||
<defs>
|
||||
<clipPath id='cpMC4wMHw1NzYuMDB8MC4wMHwzNjAuMDA='>
|
||||
<rect x='0.00' y='0.00' width='576.00' height='360.00' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path='url(#cpMC4wMHw1NzYuMDB8MC4wMHwzNjAuMDA=)'>
|
||||
<rect x='0.000000000000064' y='0.00' width='576.00' height='360.00' style='stroke-width: 1.07; stroke: #FFFFFF; fill: #292E32;' />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id='cpNTkuMTB8NDIxLjE5fDQ2LjQxfDI3OS4yMw=='>
|
||||
<rect x='59.10' y='46.41' width='362.09' height='232.82' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path='url(#cpNTkuMTB8NDIxLjE5fDQ2LjQxfDI3OS4yMw==)'>
|
||||
<polyline points='59.10,279.23 421.19,279.23 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,225.86 421.19,225.86 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,172.48 421.19,172.48 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,119.11 421.19,119.11 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,65.74 421.19,65.74 ' style='stroke-width: 1.07; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<circle cx='127.08' cy='111.51' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='142.09' cy='105.00' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='132.50' cy='111.77' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='246.17' cy='113.52' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='223.06' cy='116.55' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='260.64' cy='105.27' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='249.90,102.73 256.13,113.51 243.67,113.51 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='243.36,104.64 249.58,115.42 237.13,115.42 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='218.81,108.00 225.03,118.79 212.58,118.79 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='119.28,100.75 125.50,111.54 113.05,111.54 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='114.27,96.29 120.49,107.07 108.04,107.07 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='131.88,96.40 138.10,107.19 125.65,107.19 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='335.41' cy='90.10' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='351.34' cy='85.38' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<circle cx='336.95' cy='85.21' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='344.58,92.55 350.81,103.33 338.36,103.33 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='349.73,94.20 355.96,104.98 343.51,104.98 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='344.11,90.98 350.33,101.77 337.88,101.77 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
</g>
|
||||
<g clip-path='url(#cpMC4wMHw1NzYuMDB8MC4wMHwzNjAuMDA=)'>
|
||||
<polyline points='59.10,279.23 59.10,46.41 ' style='stroke-width: 2.13; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<text x='54.17' y='283.15' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='6.29px' lengthAdjust='spacingAndGlyphs'>0</text>
|
||||
<text x='54.17' y='229.78' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='12.59px' lengthAdjust='spacingAndGlyphs'>10</text>
|
||||
<text x='54.17' y='176.41' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='12.59px' lengthAdjust='spacingAndGlyphs'>20</text>
|
||||
<text x='54.17' y='123.04' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='12.59px' lengthAdjust='spacingAndGlyphs'>30</text>
|
||||
<text x='54.17' y='69.67' text-anchor='end' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='12.59px' lengthAdjust='spacingAndGlyphs'>40</text>
|
||||
<polyline points='56.36,279.23 59.10,279.23 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='56.36,225.86 59.10,225.86 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='56.36,172.48 59.10,172.48 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='56.36,119.11 59.10,119.11 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='56.36,65.74 59.10,65.74 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='59.10,279.23 421.19,279.23 ' style='stroke-width: 2.13; stroke: #3B3E48; stroke-linecap: butt;' />
|
||||
<polyline points='126.99,281.97 126.99,279.23 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='240.15,281.97 240.15,279.23 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<polyline points='353.30,281.97 353.30,279.23 ' style='stroke-width: 1.07; stroke: #333333; stroke-linecap: butt;' />
|
||||
<text x='126.99' y='292.01' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='26.52px' lengthAdjust='spacingAndGlyphs'>dplyr</text>
|
||||
<text x='240.15' y='292.01' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='44.47px' lengthAdjust='spacingAndGlyphs'>duckplyr</text>
|
||||
<text x='353.30' y='292.01' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='44.47px' lengthAdjust='spacingAndGlyphs'>duckplyr</text>
|
||||
<text x='353.30' y='303.89' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='22.26px' lengthAdjust='spacingAndGlyphs'>with</text>
|
||||
<text x='353.30' y='315.77' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='101.25px' lengthAdjust='spacingAndGlyphs'>`as_duckplyr_tibble`</text>
|
||||
<text x='240.15' y='329.01' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='36.24px' lengthAdjust='spacingAndGlyphs'>Library</text>
|
||||
<text transform='translate(36.20,162.82) rotate(-90)' text-anchor='middle' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='40.16px' lengthAdjust='spacingAndGlyphs'>Time (s)</text>
|
||||
<rect x='432.15' y='132.07' width='115.50' height='61.50' style='stroke-width: 1.07; stroke: none; fill: #3B3E48;' />
|
||||
<text x='437.63' y='146.73' style='font-size: 11.00px;fill: #FFFFFF; font-family: "Arial";' textLength='104.54px' lengthAdjust='spacingAndGlyphs'>Laptop power mode</text>
|
||||
<circle cx='446.27' cy='162.17' r='4.62' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<polygon points='446.27,172.26 452.50,183.04 440.04,183.04 ' style='stroke-width: 0.71; stroke: none; fill: #FFFFFF; fill-opacity: 0.80;' />
|
||||
<text x='460.39' y='165.31' style='font-size: 8.80px;fill: #FFFFFF; font-family: "Arial";' textLength='37.56px' lengthAdjust='spacingAndGlyphs'>balanced</text>
|
||||
<text x='460.39' y='182.59' style='font-size: 8.80px;fill: #FFFFFF; font-family: "Arial";' textLength='53.76px' lengthAdjust='spacingAndGlyphs'>performance</text>
|
||||
<text x='59.10' y='37.76' style='font-size: 13.20px;fill: #FFFFFF; font-family: "Arial";' textLength='224.18px' lengthAdjust='spacingAndGlyphs'>Time elapsed with dplyr vs. duckplyr</text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.4 KiB |
127
content/posts/2024/duckplyr-performance/index.md
Normal file
@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Performance experiments with duckplyr in R
|
||||
description: I recently became aware of the 'duckplyr' library for R. Here are the results of my experimenting with it and benchmarking it against `dplyr`.
|
||||
date: 2024-08-26T19:00:00+0200
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
tags:
|
||||
- R
|
||||
- statistics
|
||||
---
|
||||
I recently became aware of the [duckplyr][] library for R, which takes the place
|
||||
of tidyverse's [dplyr][] library, but uses the [DuckDB] database under the hood.
|
||||
Without really knowing anything about how dplyr works and if the use of DuckDB
|
||||
would improve my workflow at all, I decided to perform an experiment. I am
|
||||
currently analyzing two datasets, one with ~80k records and ~70 variables and
|
||||
one with ~60k records and ~100 variables. Both datasets are wrangled with
|
||||
[Tidyverse][]-foo in multiple ways and finally combined. The wrangling of the
|
||||
data involves things like `rowwise()` and `c_across()`, which I know from
|
||||
experience is quite an 'expensive' operation.
|
||||
|
||||
In order to get the execution times of my code, I did this repeatedly:
|
||||
|
||||
1. Restart R (by pressing <kbd>CTRL</kbd> <kbd>SHIFT</kbd> <kbd>F10</kbd>).
|
||||
2. Run
|
||||
|
||||
```r
|
||||
system.time(rmarkdown::render("my_file.Rmd"))
|
||||
```
|
||||
|
||||
3. Record the user time and the system time elapsed.
|
||||
4. Repeat twice.
|
||||
|
||||
I did this with both the "balanced power mode" and the "performance mode" on my
|
||||
[laptop][]. During execution of the code, I left the laptop alone in order not
|
||||
to interfere with the timing.
|
||||
|
||||
This is the result of my benchmarking:
|
||||
|
||||
{{< figure src="benchmarking1.svg" >}}
|
||||
|
||||
The times are user times. I left out the system times, which are in the range of
|
||||
2-3 seconds.
|
||||
|
||||
Not really mind-boggling, right? It occurred to me that I rather double-check
|
||||
that `duckplyr` was really being used. Indeed, this was _not_ the case:
|
||||
|
||||
```r
|
||||
> class(clinical_data)
|
||||
[1] "tbl_df" "tbl" "data.frame"
|
||||
```
|
||||
|
||||
`clinical_data` was missing the `duckplyr_df' class. How come?
|
||||
|
||||
I import the raw data from Excel files (don't ask...) into tibbles, and
|
||||
evidently, this prevents `duckplyr` from seeing the data frames. So I piped the
|
||||
data frames through `as_duckplyr_tibble()` explicitly, and this got me the right
|
||||
classes:
|
||||
|
||||
```r
|
||||
> class(clinical_data)
|
||||
[1] "duckplyr_df" "tbl_df" "tbl" "data.frame"
|
||||
```
|
||||
|
||||
However, this did not really speed up the execution either.
|
||||
|
||||
{{< figure src="benchmarking2.svg" >}}
|
||||
|
||||
I looked around my RMarkdown chunks and their outputs, but I did not find any
|
||||
warning that `duckplyr` had to fall back to `dplyr`'s methods. This could have
|
||||
explained the absence of a noticeable difference.
|
||||
|
||||
Here are the average times (in seconds) for the benchmarking runs.
|
||||
|
||||
```r
|
||||
> runs_table
|
||||
# A tibble: 6 × 4
|
||||
# Groups: library, power_mode [6]
|
||||
library power_mode mean sd
|
||||
<chr> <chr> <dbl> <dbl>
|
||||
1 dplyr balanced 31.8 0.722
|
||||
2 dplyr performance 32.6 0.477
|
||||
3 duckplyr balanced 31.4 1.10
|
||||
4 duckplyr performance 31.3 0.495
|
||||
5 duckplyr with `as_duckplyr_tibble` balanced 36.0 0.517
|
||||
6 duckplyr with `as_duckplyr_tibble` performance 33.6 0.303
|
||||
```
|
||||
|
||||
So at least for my (!!!) use case, the use of `duckplyr` instead of `dplyr` did
|
||||
not make any practical difference, and I can also leave my laptop's performance
|
||||
mode alone. When it comes to optimizing performance, you can't just buy a
|
||||
solution off the shelf, you always have to try and find the best solution for
|
||||
your specific problem.
|
||||
|
||||
Your mileage will vary, of course. The people who develop `duckplyr` are
|
||||
brilliant, and the fact that it does not work for me tells more about me and my
|
||||
work than it does about `duckplyr`.
|
||||
|
||||
## The duckplyr demo dataset
|
||||
|
||||
As a case in point, the [duckplyr demo repository][duckplyr-demo] contains a
|
||||
taxi data set. The ZIP file alone is a ~1.7 GB download. Deflated, the files
|
||||
take up 2.4 GB. With about 21 million records (24 variables), this dataset
|
||||
is _considerably_ larger than mine.
|
||||
|
||||
Here are the results from running `dplyr/run_all_queries.R` and
|
||||
`duckplyr/run_all_queries.R` on my Thinkpad P14s (performance mode in F40 KDE):
|
||||
|
||||
| Library | q01 | q02 | q03 | q04 |
|
||||
|----------|------:|------:|------:|-------:|
|
||||
| dplyr | 3.4 s | 3.9 s | 9.1 s | 14.3 s |
|
||||
| duckplyr | 4.3 s | 4.4 s | 9.4 s | 14.8 s |
|
||||
|
||||
I should add that execution times vary with each run, but the big picture stays
|
||||
the same.
|
||||
|
||||
Maybe I'm missing the point and it's not about execution times, after all.
|
||||
|
||||
`¯\_(ツ)_/`
|
||||
|
||||
[dplyr]: https:/dplyr.tidyverse.org
|
||||
[duckdb]: https://duckdb.org
|
||||
[duckplyr]: https://duckplyr.tidyverse.org
|
||||
[duckplyr-demo]: https://github.com/Tmonster/duckplyr_demo
|
||||
[laptop]: {{< relref "P14s" >}}
|
||||
[tidyverse]: https://tidyverse.org
|
BIN
content/posts/2024/halloween/IMGP8429_G.JPG
Normal file
After Width: | Height: | Size: 399 KiB |
BIN
content/posts/2024/halloween/IMGP8436_G.JPG
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
content/posts/2024/halloween/IMGP8437_G.JPG
Normal file
After Width: | Height: | Size: 268 KiB |
90
content/posts/2024/halloween/index.md
Normal file
@ -0,0 +1,90 @@
|
||||
---
|
||||
title: My gripes with Halloween in Germany
|
||||
description: >
|
||||
Celebrating Halloween in Germany is a relatively new phenomenon that
|
||||
I believe is a manifestation of the ego-centered and hedonistic
|
||||
state of the German society.
|
||||
date: 2024-10-31T23:00:00+0200
|
||||
draft: false
|
||||
ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
images:
|
||||
- IMGP8436_G.JPG
|
||||
tags:
|
||||
- opinion
|
||||
---
|
||||
|
||||
{{< figure src="IMGP8429_G.JPG" alt="Halloween in Jamaica Plain, 2009"
|
||||
title="Halloween in '09, Jamaica Plain, Boston, MA © Daniel Kraus">}}
|
||||
|
||||
In the three years that I lived in Boston, Massachusetts, while persuing my
|
||||
postdoctoral fellowship at [Beth Israel Deaconess Medical Center][bidmc],
|
||||
Halloween was a wonderful event. We had a little appartment literally under the
|
||||
roof of a very nice and welcoming family in Boston's Jameica Plain
|
||||
neighbourhood. The family was not just our landlords -- they quickly became
|
||||
friends. We celebrated all kinds of events together: the [Red Sox' Win of the
|
||||
World Series in 2007][sox], birthdays, Thanksgiving, ... and [Halloween][].
|
||||
|
||||
{{< figure src="IMGP8436_G.JPG" alt="Halloween in Jamaica Plain, 2009"
|
||||
title="Halloween in '09, Jamaica Plain, Boston, MA © Daniel Kraus">}}
|
||||
|
||||
Halloween has a long tradition in the United States and elsewhere. Where we used
|
||||
to live in Boston, the entire street celebrated together. The street was divided
|
||||
into two halves that took turns in organizing the event each year. Everybody met
|
||||
outside. There was live music, and of course the kids did the trick-or-treat thing.
|
||||
I loved it.
|
||||
|
||||
{{< figure src="IMGP8437_G.JPG" alt="Halloween in Jamaica Plain, 2009"
|
||||
title="Halloween in '09, Jamaica Plain, Boston, MA © Daniel Kraus">}}
|
||||
|
||||
Growing up in Germany in the 1980s, I was accustomed to [St. Martin's
|
||||
Day][martin] on or around 11 November in commemoration of St. Martin of Tours
|
||||
who is said to have divided his coat to share it with a poor person.
|
||||
|
||||
We would walk through our neighbourhood, ring people's door bells, and when
|
||||
someone opened we would sing a song about St. Martin and ask for sweets. When I
|
||||
thought about this now and before it occurred to me to write a blog post about
|
||||
this, I seemed to remember that we would afterwards give most of the sweets to
|
||||
children in need (refugees, in fact) that had found shelter in a nearby
|
||||
facility. However, I now believe that we did this only on a similar occasion in
|
||||
early January when we celebrated [Epiphany with the Star singers][epiphany]
|
||||
which also involved walking around, singing, and collecting sweets. So St.
|
||||
Martin's day had a lot to do with asking for sweets for our own consumption and
|
||||
pleasure and not so much about caring and sharing, and this is important to keep
|
||||
in mind. It is also important to realize that this tradition around St. Martin
|
||||
was a regional thing and nothing nationwide (as I learned from an [article on
|
||||
Wikipedia][martin-in-germany]).
|
||||
|
||||
In recent years, Halloween has grown very strong in Germany, and I believe that
|
||||
this is a result of deliberately injecting the tradition into our culture.
|
||||
Interestingly, this is also [Wikipedia's view][germany] (see references in the
|
||||
linked article).
|
||||
|
||||
And that's my gripe with celebrating Halloween in Germany. There's nothing wrong
|
||||
with partying and having a good time with friends and family. But this event is
|
||||
tightly linked to buying and consuming stuff, and it is driven by "the
|
||||
industry". Again, there's nothing wrong in itself with "the industry" trying to
|
||||
sell stuff to people. But I do think that this is an example of how people can
|
||||
be made to do things. Just like [lemmings][], everybody is following suit.
|
||||
|
||||
Halloween in Germany, as I see it, is a festival of hedonism. It has nothing to
|
||||
to with sharing, it has nothing to do with taking a break and thinking about the
|
||||
[faithful departed][halloween], but it has a lot to do with buying stuff and
|
||||
consuming stuff that you don't really need.
|
||||
|
||||
For me, Halloween in Germany is an example for the state of our society.
|
||||
|
||||
---
|
||||
|
||||
Having said that, I greatly enjoyed the more or less spontaneous Halloween party
|
||||
that we had at our neighbors' place tonight ;-)
|
||||
|
||||
[bidmc]: https://bidmc.org
|
||||
[epiphany]: https://en.wikipedia.org/wiki/Star_singers
|
||||
[germany]: https://en.wikipedia.org/wiki/Geography_of_Halloween#Germany
|
||||
[halloween]: https://en.wikipedia.org/wiki/Halloween
|
||||
[lemmings]:https://en.wikipedia.org/wiki/St._Martin%27s_Day
|
||||
[martin]: https://en.wikipedia.org/wiki/St._Martin%27s_Day
|
||||
[martin-in-germany]: https://en.wikipedia.org/wiki/Martinisingen#Present-day_customs
|
||||
[sox]: https://en.wikipedia.org/wiki/2007_World_Series
|
36
content/posts/2024/kmail-crash/index.md
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
title: How to fix KMail not starting
|
||||
description: How I got my KMail working again when it crashed during the startup process
|
||||
date: 2024-09-17T06:00:00+0200
|
||||
draft: false
|
||||
ShowLastmod: false
|
||||
tags:
|
||||
- Plasma
|
||||
- Fix
|
||||
- KMail
|
||||
---
|
||||
|
||||
Last night, I suddenly could not start KMail anymore.
|
||||
|
||||
Konsole revealed this:
|
||||
|
||||
```fish
|
||||
$ kmail
|
||||
org.kde.pim.kmail: setFcc: collection invalid ""
|
||||
We have an error during reading password "Entry not found"
|
||||
*** KMail got signal 11 (Exiting)
|
||||
*** Dead letters dumped.
|
||||
KCrash: Application 'kmail' crashing... crashRecursionCounter = 2
|
||||
fish: Job 1, 'kmail' terminated by signal SIGSEGV (Address boundary error)
|
||||
````
|
||||
|
||||
Luckily, I quickly [found a fix][1]:
|
||||
|
||||
```fish
|
||||
rm ~/.local/share/kmail2/autosave/*
|
||||
```
|
||||
|
||||
Don't know what caused this problem and why KMail chokes on
|
||||
autosave files, but at least I can now start it again.
|
||||
|
||||
[1]: <https://forum.archlinux.de/d/34333-kmail-kontact-starten-nach-plasma-update-nicht-mehr/10>
|
24
content/posts/2024/new-blog/index.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "Welcome to the new blog"
|
||||
date: 2024-08-01T00:00:00+02:00
|
||||
draft: false
|
||||
tags:
|
||||
- meta
|
||||
---
|
||||
|
||||
Welcome to my new blog.
|
||||
|
||||
This blog continues where my old one at
|
||||
<https://www.xltoolbox.net/blog/index.html> left off.
|
||||
|
||||
I thought about moving the old blog posts over to this new blog, but those posts
|
||||
are really ancient and not relevant any more. I leave them at the old place for
|
||||
archival purposes.
|
||||
|
||||
I have implemented a commenting system based on [Discourse][]. Please feel free
|
||||
to comment on my posts, ask questions etc. You'll either have to create a
|
||||
standalone account, or you can log in with your Google or Github account.
|
||||
|
||||
See the discussion forum at <https://discuss.bovender.de>.
|
||||
|
||||
[discourse]: https://discourse.org
|
@ -1,16 +1,18 @@
|
||||
---
|
||||
title: "Experience with running Fedora Linux on a Thinkpad P14s Gen 5"
|
||||
description: "Everything works out of the box, but the keyboard and the battery leave something to be desired."
|
||||
description: "Everything works out of the box with KDE Plasma, but the keyboard and the battery leave something to be desired."
|
||||
date: 2024-08-05T21:00:00+02:00
|
||||
ShowLastmod: true
|
||||
ShowLastmod: false
|
||||
draft: false
|
||||
toc: true
|
||||
scrolltotop: true
|
||||
tags:
|
||||
- Thinkpad
|
||||
- Linux
|
||||
- Fedora
|
||||
- F40
|
||||
- KDE
|
||||
- Linux
|
||||
- NVIDIA
|
||||
- Thinkpad
|
||||
---
|
||||
|
||||
I have recently acquired a Lenovo Thinkpad P14s Gen 5. As of the time of writing
|
||||
@ -193,13 +195,94 @@ All in all, I am quite happy with the P14s. In the past couple of weeks, it has
|
||||
already proven to be a reliable work horse, and this is what I need. The
|
||||
keyboard really should be better, but hey, life is not perfect, is it?
|
||||
|
||||
-----
|
||||
|
||||
## Update 2024-08-14
|
||||
|
||||
Two important things to add:
|
||||
|
||||
1. After a recent system update, **suspend (sleep) stopped working**. First I
|
||||
suspected the NVIDIA driver to be responsible, it had been upgrade to version
|
||||
560. However, when I downgraded the driver, the problem persisted. Finally I
|
||||
found a [thread][] on the Fedora Discussion forum which suggested to disable
|
||||
ethernet in BIOS. Tada! That was it. Hopefully, with a future kernel or
|
||||
firmware upgrade, I will be able to use ethernet again, as I prefer to have
|
||||
cable-bound internet at home. The current linux kernel is 6.10, upgraded from
|
||||
6.9 just recently.
|
||||
|
||||
2. Here's another [interesting report][techtipsy] from someone running **Linux on a P14S**,
|
||||
albeit Gen 4 (mine is Gen 5) and with AMD and integrated graphics (I have
|
||||
Intel with dedicated NVIDIA GPU). The author (Herman) also notes the battery
|
||||
drain (even with AMD and iGPU) and the build quality which is somewhat below
|
||||
the T series. Herman has also posted about [controlling fan speed with a simple
|
||||
script][fancontrol], and I think I am going to tinker with it in the future,
|
||||
because the following works on my P14s Gen 5 with Intel CPU and NVIDIA GPU:
|
||||
|
||||
```bash
|
||||
# This is just a proof of concept, spin up the fan and spin it down again
|
||||
# Run as root
|
||||
echo level 7 > /proc/acpi/ibm/fan; sleep 5; echo level 1 > /proc/acpi/ibm/fan
|
||||
```
|
||||
|
||||
## Update 2024-08-22
|
||||
|
||||
Fedora installs only free software by default, which means that [Kdenlive][] and
|
||||
other multimedia software will have trouble using H.264 and H.265 video codecs.
|
||||
A free version of [ffmpeg][] is installed by default. This is what I did to
|
||||
replace `ffmpeg-free` with the non-free `ffmpeg` on F40:
|
||||
|
||||
1. Enable the non-free [RPM Fusion][] repositories as written on the [RPM Fusion
|
||||
homepage][]:
|
||||
|
||||
```bash
|
||||
sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
|
||||
sudo dnf config-manager --enable fedora-cisco-openh264
|
||||
```
|
||||
|
||||
2. Remove `ffmpeg-free` in favor of `ffmpeg`:
|
||||
|
||||
```bash
|
||||
sudo dnf swap ffmpeg-free ffmpeg --allowerasing
|
||||
```
|
||||
|
||||
After doing that and restarting Kdenlive, I was able to render my project using
|
||||
the H.264 and H.265 codecs as I used to be able to do with Ubuntu-based systems.
|
||||
|
||||
## Update 2024-11-10
|
||||
|
||||
In the last couple of months, battery life has improved significantly. As
|
||||
described above, in summer, battery drained almost completely (from ~90% to
|
||||
~15%) within 3.5 hours even without heavy workload.
|
||||
|
||||
Today I spent about the same amount of time on a train, working constantly on my
|
||||
laptop (browsing, downloading articles), and the battery level only dropped from
|
||||
~90% to ~40% within 3 hours. Some system update, be it a kernel update or some
|
||||
other software running in the background, must have reduced energy consumption
|
||||
considerably :-)
|
||||
|
||||
{{< figure src="p14s-battery-drain-2.png">}}
|
||||
|
||||
(I am well aware that there are laptops whose battery run time is in an entirely
|
||||
different ball park, and even some of my own Thinkpads used to do better, but
|
||||
I'm overall quite happy with battery life on this machine with bright 3k display
|
||||
and dedicated GPU. I _could_ have bought a machine which can run on battery like
|
||||
forever, but that's not my use case and plus, I'd never buy anything else but a
|
||||
Thinkpad...)
|
||||
|
||||
[Darktable]: https://darktable.org
|
||||
[fancontrol]: https://ounapuu.ee/posts/2022/09/26/minimum-viable-fan-control-script/
|
||||
[Fedora]: https://fedoraproject.org/
|
||||
[ffmpeg]: https://ffmpeg.org
|
||||
[KDE forum]: https://discuss.kde.org/t/unable-to-start-kde-neon-plasma-on-core-ultra-nvidia-rtx-500-ada/18578
|
||||
[KDE neon]: https://neon.kde.org
|
||||
[KDE plasma]: https://kde.org/plasma-desktop/
|
||||
[KDE spin]: https://fedoraproject.org/spins/
|
||||
[Kdenlive]: https://kdenlive.org
|
||||
[p14s-weight]: https://www.lenovo.com/us/en/p/laptops/thinkpad/thinkpadp/thinkpad-p14s-gen-5-(14-inch-intel)-mobile-workstation/21g2002cus#tech_specs
|
||||
[rpm fusion]: https://rpmfusion.org
|
||||
[rpm fusion homepage]: https://rpmfusion.org/Configuration
|
||||
[rstudio]: https://posit.co/products/open-source/rstudio
|
||||
[s3]: https://en.wikipedia.org/wiki/ACPI#S3
|
||||
[techtipsy]: https://ounapuu.ee/posts/2024/04/12/lenovo-p14s-gen4/
|
||||
[thread]: https://discussion.fedoraproject.org/t/suspend-failure-and-degraded-performance-after-failed-suspend-on-kernel-6-10-3/128299
|
||||
[xltoolbox.net]: https://www.xltoolbox.net/blog/2018/08/exit-thinkpad-t430s-enter-thinkpad-t480s.html
|
BIN
content/posts/2024/p14s/p14s-battery-drain-2.png
Normal file
After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 177 KiB |
Before Width: | Height: | Size: 954 KiB After Width: | Height: | Size: 954 KiB |
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 3.8 MiB |
40
content/posts/2024/plasma-5-tiling-manager/index.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
title: "Windows tiling in KDE Plasma 5"
|
||||
date: 2024-08-08T18:04:20+0200
|
||||
draft: false
|
||||
toc: false
|
||||
# ShowLastmod: true
|
||||
images:
|
||||
tags:
|
||||
- Linux
|
||||
- KDE
|
||||
- Plasma
|
||||
- productivity
|
||||
---
|
||||
KDE Plasma 5 introduced a (more or less) advanced window tiling manager. Most of
|
||||
the time I use `META` plus one or two of the arrow keys to move windows quickly
|
||||
to one half or one quarter of the screen. Plasma's advanced tiling manager
|
||||
enables us to use layouts that are not defined by half or quarter of the screen
|
||||
real estate.
|
||||
|
||||
I keep forgetting how to use this, so this is my note to self:
|
||||
|
||||
- Define layout: `META+T`
|
||||
- Move window to predefined location: hold `SHIFT` while dragging the title bar
|
||||
of a window with the mouse, or press `META`, klick anywhere into the window
|
||||
and start dragging, _then_ while still holding `META`, additionally press
|
||||
`SHIFT` to make the window snap to one of the predefined layout.
|
||||
|
||||
Link: <https://kde.org/announcements/plasma/5/5.27.0/>
|
||||
|
||||
I wish there were keyboard shortcuts to move windows into one of the custom tile
|
||||
layouts as well: <https://discuss.kde.org/t/keyboard-shortcuts-for-new-tiling-feature/1843>
|
||||
|
||||
---
|
||||
|
||||
### Added 2025-03-02
|
||||
|
||||
With Plasma 5.3, you can now define keyboard shortcuts to move a window to
|
||||
a custom tile. On my system, there were now shortcuts defined by default, but
|
||||
it is very easy to do so in `System Settings > Shortcuts > KWin`. I have now
|
||||
mapped `META`+`SHIFT` and the arrow keys to move windows to custom tiles.
|
@ -0,0 +1,85 @@
|
||||
---
|
||||
title: "Reconcile Docker With Deutsche Bahn"
|
||||
description: >
|
||||
If WiFi does not work on Deutsche Bahn's ICE trains and you have Docker installed,
|
||||
there may be network interference which can be fixed easily.
|
||||
date: 2024-09-26T09:07:30Z
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
#featuredImg: reconcile-docker-with-deutsche-bahn.png
|
||||
tags:
|
||||
- network
|
||||
- Deutsche Bahn
|
||||
- Docker
|
||||
- travel
|
||||
---
|
||||
|
||||

|
||||
|
||||
If you happen to be a (Linux) power user travelling on a Deutsche Bahn [ICE
|
||||
long-distance train][ice] and you wonder why you don't get a working network
|
||||
connection with the complemenatary "WIFIonICE" wireless network, there may be a
|
||||
network interference on your laptop.
|
||||
|
||||
There are multiple solutions for this on the web. My favorite solution is to
|
||||
change Docker's network configuration by editing (or creating) the file
|
||||
`/etc/docker/daemons.json`. However, you may need to prune existing Docker
|
||||
networks first. Otherwise the Docker service will fail to start with the
|
||||
following message:
|
||||
|
||||
```plain
|
||||
failed to start daemon: Error initializing network controller: error creating default "bridge" network: all predefined address pools have been fully subnetted
|
||||
```
|
||||
|
||||
Therefore, I recommend the following steps (this is on a Fedora Linux system with *systemd*):
|
||||
|
||||
1. Stop any docker containers that you may have running, including services managed by docker compose.
|
||||
2. Prune the networks:
|
||||
|
||||
```bash
|
||||
docker network prune
|
||||
```
|
||||
|
||||
3. Create or edit `/etc/docker/daemon.json` so that it contains the following:
|
||||
|
||||
```plain
|
||||
{
|
||||
"default-address-pools":
|
||||
[
|
||||
{"base":"172.19.0.0/16","size":24}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
4. Restart docker:
|
||||
|
||||
```bash
|
||||
systemctl restart docker
|
||||
```
|
||||
|
||||
5. Connect to `WIFIonICE` and enjoy your network connection :-)
|
||||
|
||||
## References
|
||||
|
||||
- StackExchange answer that provides the solution as presented here -- see also
|
||||
the answer about pruning the networks below.
|
||||
|
||||
<https://unix.stackexchange.com/a/539258/110635>
|
||||
|
||||
- If you don't want to or cannot configure your Docker networks, you can also
|
||||
turn Docker off and on again when you travel:
|
||||
|
||||
<https://kuttler.eu/code/ice-wlan-linux-docker/>
|
||||
|
||||
This is what I used to do myself, but nowadays I prefer to just configure the
|
||||
Docker network differently.
|
||||
|
||||
- There are many more internet sources (and I have just added another one...), I
|
||||
won't copy all those links into my post, see DuckDuckGo search results
|
||||
for yourself:
|
||||
|
||||
<https://duckduckgo.com/?q=docker+wifi+on+ice>
|
||||
|
||||
[ice]: https://int.bahn.de/en/trains/long-distance-trains
|
After Width: | Height: | Size: 750 KiB |
BIN
content/posts/2025/install-pynitrokey/IMGP0069.jpg
Normal file
After Width: | Height: | Size: 213 KiB |
58
content/posts/2025/install-pynitrokey/index.md
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Install pynitrokey on Fedora 41
|
||||
description: Here's how I installed pynitrokey on my Fedora 41 KDE laptop, resolving missing dependencies.
|
||||
date: 2025-03-30T10:53:35+0100
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
images: []
|
||||
tags:
|
||||
- security
|
||||
- Linux
|
||||
- Fedora
|
||||
---
|
||||
|
||||
{{< figure src="IMGP0069.jpg"
|
||||
alt="My Nitrokey 3A mini"
|
||||
title="My Nitrokey 3A mini on the right side of the picture" >}}
|
||||
|
||||
I use [Yubikey][]s and [Nitrokey][]s as a second factor during 2FA
|
||||
authentication.
|
||||
|
||||
My experiences with Nitrokey devices have been mixed. Currently my 3A mini
|
||||
stopped working. I wanted to investigate what's going on, only to find out that
|
||||
the commandline tool `nitropy` no longer worked on my machine either! :-(
|
||||
|
||||
Here's how I got it to work again. I'm a Python noob, so maybe there's a better
|
||||
way to install the dependencies. The operating system is Fedora 41.
|
||||
|
||||
```bash
|
||||
sudo dnf install systemd-devel python3-devel
|
||||
pipx install pynitrokey
|
||||
```
|
||||
|
||||
Afterwards I was greeted with this:
|
||||
|
||||
```text
|
||||
$ nitropy nk3 test
|
||||
Command line tool to interact with Nitrokey devices 0.8.1
|
||||
Critical error:
|
||||
No connected NK3 devices found
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Critical error occurred, exiting now
|
||||
Unexpected? Is this a bug? Would you like to get support/help?
|
||||
- You can report issues at: https://support.nitrokey.com/
|
||||
- Writing an e-mail to support@nitrokey.com is also possible
|
||||
- Please attach the log: '/tmp/nitropy.log._p6mpa3e' with any support/help request!
|
||||
- Please check if you have udev rules installed: https://docs.nitrokey.com/nitrokeys/nitrokey3/firmware-update#troubleshooting-linux
|
||||
```
|
||||
|
||||
I made sure that I had the abovementioned `udev` rules in place. The key is not
|
||||
detected by any system, be it my personal Linux laptop or any Windows machine
|
||||
that I could access. [D'oh][]!
|
||||
|
||||
[Nitrokey]: https://www.nitrokey.com
|
||||
[Yubikey]: https://www.yubico.com
|
||||
[d'oh]: https://en.wikipedia.org/wiki/D'oh!
|
BIN
content/posts/2025/leaving-x/account-deactivated.png
Normal file
After Width: | Height: | Size: 48 KiB |
40
content/posts/2025/leaving-x/index.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Leaving X
|
||||
description: German academic institutions are leaving Twitter/X. I'm not an academic institution, but still I'm following suit.
|
||||
date: 2025-01-14T21:30:00+0100
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
images: []
|
||||
tags:
|
||||
- academia
|
||||
- social media
|
||||
---
|
||||
Several academic institutions in Germany have decided to terminate their
|
||||
presence on X, formerly known as Twitter.
|
||||
|
||||
Press release of the "Informationsdienst Wissenschaft" (scientific news service,
|
||||
in German): <https://idw-online.de/de/news845520>
|
||||
|
||||
Ironically, the press release page has several buttons to share this information
|
||||
on social media, right now still including a sharing button for X :smile:
|
||||
Surely just a matter of time.
|
||||
|
||||
{{< figure src="press-release-with-x-sharing-button.png"
|
||||
title="Press release (in German) informing about German academic institutions leaving X, with X sharing button." >}}
|
||||
|
||||
I got aware of this by reading my former university's newsletter:
|
||||
<https://www.uni-wuerzburg.de/aktuelles/einblick/single/news/universitaet-wuerzburg-kehrt-plattform-x-den-ruecken-1>
|
||||
|
||||
I'm following suit. I've just now deleted my account on X.
|
||||
|
||||
{{< figure src="account-deactivated.png"
|
||||
title="I did it! My account on X has been deactivated. (Screenshot in German.)" >}}
|
||||
|
||||
If you would like to connect with me on social media, please head over to my profiles on [Mastodon][] or [Bluesky][].
|
||||
|
||||
See you!
|
||||
|
||||
[Mastodon]: https://neph.social/@daniel_kraus
|
||||
[Bluesky]: https://bsky.app/profile/bovender.bsky.social
|
After Width: | Height: | Size: 105 KiB |
80
content/posts/2025/media-mounts-at-dm/index.md
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
title: "[In German] USB-Sticks in den Fotostationen von dm"
|
||||
description:
|
||||
date: 2025-03-22T22:20:07+0100
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
images: []
|
||||
tags:
|
||||
- German
|
||||
---
|
||||
|
||||
(This post is in German.)
|
||||
|
||||
In den letzten Wochen habe ich ein paar Mal Fotoabzüge benötigt und bin mit
|
||||
einem USB-Stick zur örtlichen Drogerie. Zu meiner Überraschung konnten keinerlei
|
||||
Bilder auf dem Stick gefunden werden. Inzwischen habe ich das mit mehreren
|
||||
Sticks und an verschiedenen Foto-Stationen (aber immer in demselben Laden)
|
||||
probiert, Fehlanzeige. Interessanterweise hat es mit einer SD-Karte problemlos
|
||||
geklappt.
|
||||
|
||||
Offenbar bin ich weder der erste noch der einzige, der dieses Problem schon
|
||||
einmal hatte, es gibt zu einem ähnlichen Problem mit einer CEWE-Fotostation
|
||||
einen [Forumsthread][].
|
||||
|
||||
In diesem Blog-Beitrag sammele ich einfach mal meine Beobachtungen, vielleicht
|
||||
hilft es ja einmal jemandem. Von Mal zu Mal werde ich dies updaten.
|
||||
|
||||
## Beispiele für "geht" und "geht nicht"
|
||||
|
||||
Ich hatte zuletzt zwei Speichermedien dabei, einen USB-Stick und eine SD-Karte:
|
||||
|
||||
```plain
|
||||
$ lsblk -f
|
||||
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
|
||||
sda
|
||||
└─sda1 exfat 1.0 [REDACTED] E7F3-FDF9 114,5G 0% /run/media/daniel/[REDACTED]
|
||||
sdb
|
||||
└─sdb1 exfat 1.0 7FDE-A701 119G 0% /run/media/daniel/7FDE-A701
|
||||
```
|
||||
|
||||
`sda` ist ein USB-Stick, den ich häufig in anderen Zusammenhängen im Einsatz
|
||||
habe. `sdb` ist eine ältere SD-Karte. Beide haben nominell 128 GB Kapazität, und
|
||||
beide haben eine Partitionstabelle im herkömmlichen MSDOS-Format und eine
|
||||
Primärpartition, die mit dem Dateisystem EXFAT formatiert ist. Das Dateisystem
|
||||
auf dem USB-Stick hat ein Label, welches aus wenigen Buchstaben ohne
|
||||
Sonderzeichen besteht.
|
||||
|
||||
Auf beiden Speichermedien hatte ich denselben Foto-Ordner kopiert, mit etwa
|
||||
einer Handvoll Fotos. Die Bilder lagen also auf beiden Medien in einem
|
||||
Unterorder.
|
||||
|
||||
**Interessanterweise wurden die Bilder auf der SD-Karte gefunden, die Bilder auf
|
||||
dem USB-Stick jedoch nicht.**
|
||||
|
||||
Hier meine Beobachtungen im einzelnen, denn entsprechende Überlegungen findet
|
||||
man auch im Netz:
|
||||
|
||||
### Dateisystem
|
||||
|
||||
Bevor ich es sowohl mit dem Stick als auch mit der SD-Karte probierte, habe ich
|
||||
folgende Dateisysteme auf dem USB-Stick ausprobiert:
|
||||
|
||||
- EXFAT
|
||||
- FAT32
|
||||
|
||||
Mit beiden Dateisystemen wurden keine Bilder gefunden.
|
||||
|
||||
### Root-Verzeichnis vs. Unterverzeichnis
|
||||
|
||||
Es hat keinen Unterschied gemacht, ob ich die Bilder (JPG-Dateien) im
|
||||
Wurzelverzeichnis oder in einem Unterordner auf dem Stick hatte.
|
||||
|
||||
### Verschiedene Terminals
|
||||
|
||||
Ich habe meinen USB-Stick an verschiedenen Foto-Terminals ausprobiert, an keinem
|
||||
von ihnen wurden die Bilder gefunden.
|
||||
|
||||
[Forumsthread]: https://www.cewe-community.com/forum/fb/viewtopic.php?t=13036&p=135162
|
After Width: | Height: | Size: 130 KiB |
127
content/posts/2025/nvidia-drivers-on-fedora/index.md
Normal file
@ -0,0 +1,127 @@
|
||||
---
|
||||
title: "Nvidia Drivers on Fedora"
|
||||
description: >
|
||||
My own up-to-date instructions for installing Nvidia drivers on a Fedora Linux system.
|
||||
date: 2025-03-02T08:28:26Z
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
images: []
|
||||
tags:
|
||||
- nvidia
|
||||
- fedora
|
||||
- linux
|
||||
---
|
||||
|
||||
My [Thinkpad P14s]({{< relref "p14s" >}}) has a dedicated Nvidia GPU, which
|
||||
requires proprietary drivers. There is a gazillion of instructions on the web
|
||||
for installing theses drivers on a Fedora system. Every now and then (with
|
||||
kernel updates, I guess), my drivers stop working and I have to research _again_
|
||||
how to (re-)install these drivers. Usually I notice this when developing raw
|
||||
images with [Darktable][]. It gets dead slow and then I find out that OpenCL is
|
||||
not working.
|
||||
|
||||
This post mainly serves as a reminder for myself how to re-install the NVidia
|
||||
drivers on a Fedora laptop. It should be fairly up to date, because as I wrote,
|
||||
I keep running into this problem over and over again.
|
||||
|
||||
The prerequisite is to have the [RPM Fusion][] repositories enabled.
|
||||
|
||||
{{< figure src="discover-settings-rpm-fusion.png" >}}
|
||||
|
||||
## Installing the NVidia drivers
|
||||
|
||||
I found out that I basically only need two packages:
|
||||
|
||||
- `nvidia-settings` and
|
||||
- `xorg-x11-drv-nvidia-cuda`
|
||||
|
||||
The first one suffices to pull in the actual drivers and other packages as
|
||||
dependencies. The latter is required in order for Darktable to make use of OpenCL.
|
||||
|
||||
```fish
|
||||
sudo dnf install nvidia-settings xorg-x11-drv-nvidia-cuda
|
||||
```
|
||||
|
||||
```plain
|
||||
Updating and loading repositories:
|
||||
Repositories loaded.
|
||||
Package Arch Version Repository Size
|
||||
Installing:
|
||||
nvidia-settings x86_64 3:570.86.16-1.fc41 rpmfusion-nonfree-updates 4.4 MiB
|
||||
Installing dependencies:
|
||||
akmod-nvidia x86_64 3:570.86.16-3.fc41 rpmfusion-nonfree-updates 92.4 KiB
|
||||
egl-gbm x86_64 2:1.1.2^20240919gitb24587d-3.fc41 fedora 29.3 KiB
|
||||
egl-wayland x86_64 1.1.18~20250114git26ba0e3-2.fc41 updates 80.9 KiB
|
||||
egl-x11 x86_64 1.0.1~20241213git61e70b0-1.fc41 updates 161.1 KiB
|
||||
nvidia-modprobe x86_64 3:570.86.16-1.fc41 rpmfusion-nonfree-updates 51.0 KiB
|
||||
xorg-x11-drv-nvidia x86_64 3:570.86.16-5.fc41 rpmfusion-nonfree-updates 190.2 MiB
|
||||
xorg-x11-drv-nvidia-kmodsrc x86_64 3:570.86.16-5.fc41 rpmfusion-nonfree-updates 75.4 MiB
|
||||
xorg-x11-drv-nvidia-libs x86_64 3:570.86.16-5.fc41 rpmfusion-nonfree-updates 361.9 MiB
|
||||
Installing weak dependencies:
|
||||
xorg-x11-drv-nvidia-cuda-libs x86_64 3:570.86.16-5.fc41 rpmfusion-nonfree-updates 273.2 MiB
|
||||
xorg-x11-drv-nvidia-power x86_64 3:570.86.16-5.fc41 rpmfusion-nonfree-updates 233.7 KiB
|
||||
|
||||
Transaction Summary:
|
||||
Installing: 11 packages
|
||||
|
||||
Total size of inbound packages is 351 MiB. Need to download 114 KiB.
|
||||
After this operation, 906 MiB extra will be used (install 906 MiB, remove 0 B).
|
||||
Is this ok [y/N]:
|
||||
```
|
||||
|
||||
## Testing the installation
|
||||
|
||||
```fish
|
||||
inxi -G
|
||||
```
|
||||
|
||||
This will output the following if everything is configured correctly.
|
||||
|
||||
```plain
|
||||
Graphics:
|
||||
Device-1: Intel Meteor Lake-P [Intel Arc Graphics] driver: i915 v: kernel
|
||||
Device-2: NVIDIA AD107GLM [RTX 500 Ada Generation Laptop GPU]
|
||||
driver: nvidia v: 570.86.16
|
||||
Device-3: Syntek Integrated Camera driver: uvcvideo type: USB
|
||||
Display: wayland server: Xwayland v: 24.1.6 compositor: kwin_wayland
|
||||
driver: gpu: i915 resolution: 1: 3840x2160~60Hz 2: 3072x1920
|
||||
API: EGL v: 1.5 drivers: iris,nvidia
|
||||
platforms: gbm,wayland,x11,surfaceless,device
|
||||
API: OpenGL v: 4.6.0 compat-v: 4.6 vendor: intel mesa v: 25.0.0
|
||||
renderer: Mesa Intel Arc Graphics (MTL)
|
||||
API: Vulkan v: 1.4.304 drivers: N/A surfaces: xcb,xlib,wayland
|
||||
Info: Tools: api: clinfo, eglinfo, glxinfo, vulkaninfo
|
||||
de: kscreen-console,kscreen-doctor gpu: nvidia-settings,nvidia-smi
|
||||
wl: wayland-info x11: xdriinfo, xdpyinfo, xprop, xrandr```
|
||||
```
|
||||
|
||||
If the drivers aren't installed and loaded properly, the graphics card's name
|
||||
will be some generic term, not the exact name of the model.
|
||||
|
||||
To test if Darktable can use the CUDA driver, start if from the commandline
|
||||
like so:
|
||||
|
||||
```fish
|
||||
darktable -d opencl
|
||||
```
|
||||
|
||||
This should output a lot of technical information about the graphics card.
|
||||
|
||||
If it doesn't, but instead complains along the lines of
|
||||
|
||||
> FINALLY: opencl is NOT AVAILABLE on this system
|
||||
|
||||
then there is something wrong.
|
||||
|
||||
---
|
||||
|
||||
## Addendum 2025-04-17
|
||||
|
||||
Yesterday I upgraded from Fedora 41 to Fedora 42 (KDE), and of course the NVIDIA
|
||||
drivers were removed. Using the instructions above effortlessly installed the
|
||||
driver (currently version 570). :-)
|
||||
|
||||
[Darktable]: https://www.darktable.org
|
||||
[RPM fusion]: https://rpmfusion.org
|
After Width: | Height: | Size: 3.4 MiB |
After Width: | Height: | Size: 4.8 MiB |
36
content/posts/2025/schwanheimer-wiese/index.md
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "Schwanheimer Wiese"
|
||||
description: Two images from Schwanheimer Wiese, Frankfurt, Germany
|
||||
date: 2025-03-04T06:21:18Z
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
images: []
|
||||
tags:
|
||||
- photos
|
||||
- landscape
|
||||
- pentax
|
||||
---
|
||||
|
||||
I have decided to publish some of my photos on my blog.
|
||||
|
||||
Here are the first two of them, both taken on
|
||||
["Schwanheimer Wiese"][wiese] a meadow in the forests
|
||||
of [Frankfurt-Schwanheim][Schwanheim].
|
||||
|
||||
{{< figure src="20250223_152944 IMGP9786.jpg" >}}
|
||||
|
||||
The "Struwwelpeter" is a tree named after a figure from
|
||||
a famous [children's book][struwwelpeter]:
|
||||
|
||||
{{< figure src="20250223_153306 IMGP9790.jpg" >}}
|
||||
|
||||
Camera: Pentax K-3 APS-C, 18-55 mm kit lens (crop factor 1.5),
|
||||
image developed in [Darktable][]. Raw image file available
|
||||
upon reasonable request. All rights reserved.
|
||||
|
||||
[darktable]: https://darktable.org
|
||||
[Schwanheim]: https://en.wikipedia.org/wiki/Schwanheim_(Frankfurt_am_Main)
|
||||
[struwwelpeter]: https://en.wikipedia.org/wiki/Struwwelpeter
|
||||
[wiese]: https://de.wikipedia.org/wiki/Frankfurt-Schwanheim#Schwanheimer_Wiese
|
BIN
content/posts/2025/soleier/20250414_203010.jpg
Normal file
After Width: | Height: | Size: 584 KiB |
BIN
content/posts/2025/soleier/20250414_203911.jpg
Normal file
After Width: | Height: | Size: 289 KiB |
116
content/posts/2025/soleier/index.md
Normal file
@ -0,0 +1,116 @@
|
||||
---
|
||||
title: "Soleier: Pickled eggs for Easter"
|
||||
description: >
|
||||
Pickled eggs – "Soleier" – are a favorite Easter tradition in my
|
||||
family. Here's my personal recipe.
|
||||
date: 2025-04-17T17:55:07+02:00
|
||||
draft: false
|
||||
# ShowLastmod: true
|
||||
toc: false
|
||||
scrolltotop: true
|
||||
images: []
|
||||
tags:
|
||||
- recipes
|
||||
---
|
||||
|
||||
My family has a long-running tradition of producing pickled eggs for Easter
|
||||
breakfast. We call these "Soleier" which is a composite of "Sole" (brine) and
|
||||
"Eier" (eggs). Varous theories exist how pickled eggs came into existence, and
|
||||
there are various ways to relish these eggs. As for the theories, the reader is
|
||||
kindly referred to the [Wikipedia article][pickled eggs]
|
||||
(also [in German][Soleier]).
|
||||
|
||||
As for the recipe and the way we eat pickled eggs in my family: read on.
|
||||
|
||||
{{< figure src="20250414_203010.jpg"
|
||||
alt="My 2025 Soleier preparation."
|
||||
caption="My 2025 Soleier preparation. Smartphone raw image file edited with darktable."
|
||||
>}}
|
||||
|
||||
## How many eggs?
|
||||
|
||||
How many eggs you want to prepare depends on the number of people who want to
|
||||
eat the pickled eggs, the number of days you want to have them for breakfast (or
|
||||
any other meal to your liking) and last, but not least, what size of jar you
|
||||
have to prepare them in.
|
||||
|
||||
My 1.5 L [Weck jar][] holds 13 medium-sized eggs.
|
||||
|
||||
Typically an adult will have 2 of those eggs for breakfast. With 13 eggs, this
|
||||
leaves one spare egg (and toast), but there's usually a kid or two who doesn't
|
||||
eat these eggs in pairs (if at all).
|
||||
|
||||
{{< figure src="20250414_203911.jpg"
|
||||
alt="Pickled eggs with cracks."
|
||||
caption="I love my pickled eggs to have cracks so that they let some of the brine in."
|
||||
>}}
|
||||
|
||||
## Recipe
|
||||
|
||||
### Ingredients and materials
|
||||
|
||||
This recipe is for 13 eggs and a 1.5 L jar.
|
||||
|
||||
- 13 eggs
|
||||
- 0.75 L water
|
||||
- 50 g salt
|
||||
- 1-2 tablespoons of caraway seeds (if you like)
|
||||
- 1.5 L [Weck jar][] (doesn't need to be original Weck brand, of course, but must be sealed tight)
|
||||
- 13 slices of toast
|
||||
- butter
|
||||
- oil, e.g. rapeseed oil, to your liking
|
||||
- vinegar, e.g. any herb-flavored variant, to your liking
|
||||
|
||||
### Directions
|
||||
|
||||
You should prepare the pickled eggs a few days before you want to eat them. In
|
||||
my family we typically do this 7 days before Easter, i.e. on Palm Sunday.
|
||||
|
||||
1. Boil the eggs so they get hard. I usually place the eggs in a pot, cover them
|
||||
with water, and then slowly heat the water until it boils. Let it boil until
|
||||
10 minutes have passed since the eggs were placed on the stove. Your mileage
|
||||
may vary depending on your stove.
|
||||
2. Pour out the hot water and shock the eggs with cold tap water.
|
||||
3. Place the eggs into the jar using a clean spoon (not your hands!). I usually
|
||||
let them drop into the jar so they get nice little cracks, this makes for
|
||||
beautiful texture inside the eggs (see image).
|
||||
4. Add the salt and caraway seeds to the jar.
|
||||
5. Bring the water to boiling and pour it over the eggs in the jar.
|
||||
6. Close the jar.
|
||||
7. Invert the jar a couple of times (you may want to wear gloves while doing
|
||||
this, the jar is hot!).
|
||||
8. Store the jar in a cool place -- in our house, this is usually the basement.
|
||||
|
||||
## How to eat Soleier
|
||||
|
||||
There seem to be two fundamentally different ways to eat a _Solei_.
|
||||
|
||||
### Our way of eating Soleier
|
||||
|
||||
My family's tradition is to peel two eggs, place them on a dish, cut them into
|
||||
pieces, then pour some oil and vinegar over the pieces and mash the eggs with a
|
||||
fork. This leaves an ugly mess on the plate and I will update this post with a
|
||||
picture of this year's appearance.
|
||||
|
||||
Brown two slices of toast, spread butter on them, and eat the _Solei_ mash/mess
|
||||
using a fork while intermittently taking a bite from the bread.
|
||||
|
||||
### The more sophisticated way to eat Soleier
|
||||
|
||||
There is an alternative way to have your pickled eggs that supposedly is less
|
||||
messy. The [German Wikipedia article][Soleier] claims it's the 'proper' or let's
|
||||
say usual way to eat these eggs. I tried it once and I was totally unhappy with
|
||||
the experience, so I stick with our traditional way.
|
||||
|
||||
Peel two eggs, cut the eggs into halves, remove the egg yolk and mash the egg
|
||||
yolk with oil and vinegar. Transfer the mashed egg yolk back into the eggs. I
|
||||
will look slightly less ugly, but I promise you will have a hard time getting
|
||||
all the egg yolk--oil--vinegar mixture back into the eggs, the plate will look
|
||||
like someone's been having _Soleier_ the traditional way, and when trying to put
|
||||
the egg halves into your mouth, be prepared for everyone else who sits at the
|
||||
table with you bursting into spontaneous laughter. At least that's what happened
|
||||
to me one time... ;-)
|
||||
|
||||
[pickled eggs]: https://en.wikipedia.org/wiki/Pickled_egg
|
||||
[Soleier]: https://de.wikipedia.org/wiki/Solei
|
||||
[Weck jar]: https://en.wikipedia.org/wiki/Weck_jar
|
@ -1,15 +1,17 @@
|
||||
services:
|
||||
build:
|
||||
image: hugomods/hugo:latest
|
||||
image: hugomods/hugo:ci
|
||||
command: hugo
|
||||
volumes:
|
||||
- ".:/src"
|
||||
user: 1000:1000
|
||||
server:
|
||||
image: hugomods/hugo:latest
|
||||
image: hugomods/hugo:ci
|
||||
command: server --buildDrafts --buildFuture
|
||||
volumes:
|
||||
- ".:/src"
|
||||
ports:
|
||||
- "127.0.0.1:1313:1313"
|
||||
user: 1000:1000
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
|
17
layouts/partials/comments.html
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
<div id='discourse-comments'></div>
|
||||
<meta name='discourse-username' content='DISCOURSE_USERNAME'>
|
||||
|
||||
<script type="text/javascript">
|
||||
DiscourseEmbed = {
|
||||
discourseUrl: 'https://discuss.bovender.de/',
|
||||
discourseEmbedUrl: '{{ .Permalink }}',
|
||||
// className: 'CLASS_NAME',
|
||||
};
|
||||
|
||||
(function() {
|
||||
var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true;
|
||||
d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js';
|
||||
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d);
|
||||
})();
|
||||
</script>
|
1
layouts/partials/extra-head.html
Normal file
@ -0,0 +1 @@
|
||||
<link rel="me" href="https://neph.social/@daniel_kraus">
|
@ -9,10 +9,10 @@
|
||||
{{- with .Params.categories }}
|
||||
<p>{{- partial "svg.html" (dict "context" . "name" "posts_single_categories") -}}{{- range . -}}<span class="category"><a href="{{ "categories/" | absLangURL }}{{ . | urlize }}">{{.}}</a></span>{{- end }}</p>
|
||||
{{- end }}
|
||||
<p>{{- partial "svg.html" (dict "context" . "name" "posts_single_date") }}{{ dateFormat .Site.Params.dateformNumTime .Date.Local -}}
|
||||
<p>{{- partial "svg.html" (dict "context" . "name" "posts_single_date") }}{{ .Date.Format .Site.Params.dateformNumTime }}
|
||||
{{- if and (not (eq .Page.Params.ShowLastmod nil)) (.Page.Params.ShowLastmod) -}}
|
||||
{{- if and .GitInfo .Site.Params.gitUrl -}}
|
||||
[{{- partial "svg.html" (dict "context" . "name" "posts_single_git_commit") -}}<a href="{{ .Site.Params.gitUrl -}}{{ .GitInfo.Hash }}" target="_blank" rel="noopener">{{ .GitInfo.AbbreviatedHash -}}</a> @ {{ dateFormat .Site.Params.dateformNum .GitInfo.AuthorDate.Local -}}]
|
||||
{{ if and .GitInfo .Site.Params.gitUrl }}
|
||||
({{- partial "svg.html" (dict "context" . "name" "posts_single_git_commit") -}}<a href="{{ .Site.Params.gitUrl -}}{{ .GitInfo.Hash }}" target="_blank" rel="noopener">{{ .GitInfo.AbbreviatedHash -}}</a> @ {{ .GitInfo.AuthorDate.Local.Format .Site.Params.dateformNum -}})
|
||||
{{- else if not (eq .Lastmod .Date ) -}}
|
||||
[{{.Site.Params.initialPublish | default "Initial Published on : "}} {{ dateFormat .Site.Params.dateformNumTime .Lastmod.Local -}}]
|
||||
{{- else -}}
|
||||
|
12
layouts/shortcodes/git-info.html
Normal file
@ -0,0 +1,12 @@
|
||||
<p>
|
||||
{{ partial "svg.html" (dict "context" . "name" "posts_single_date") }} {{ dateFormat .Site.Params.dateformNumTime .Page.Date.Local }}
|
||||
{{ if and (not (eq .Page.Params.ShowLastmod nil)) (.Page.Params.ShowLastmod) }}
|
||||
{{ if and .GitInfo .Site.Params.gitUrl }}
|
||||
[{{ partial "svg.html" (dict "context" . "name" "posts_single_git_commit") }}<a href="{{ .Site.Params.gitUrl -}}{{ .Page.GitInfo.Hash }}" target="_blank" rel="noopener">{{ .Page.GitInfo.AbbreviatedHash -}}</a> @ {{ dateFormat .Site.Params.dateformNum .Page.GitInfo.AuthorDate.Local }}]
|
||||
{{ else if not (eq .Page.Lastmod .Page.Date ) }}
|
||||
[{{.Site.Params.initialPublish | default "Initial Published on : "}} {{ dateFormat .Site.Params.dateformNumTime .Page.Lastmod.Local }}]
|
||||
{{ else }}
|
||||
{{ errorf "Lastmod is not found in Page Frontmatter or Lastmod is same as Date" }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</p>
|
48
static/bovender.svg
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
static/fonts/IBMPlexSans-Medium.woff2
Normal file
BIN
static/fonts/IBMPlexSans-MediumItalic.woff2
Normal file
@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
pushd /home/daniel/local/Code/blog
|
||||
docker compose up build && rsync -av --delete public/* bovender.de:/var/www/bovender/
|
||||
docker compose up build && rsync -av --delete public/* bovender.de:/var/docker-data/nginx/www/bovender/
|
||||
popd
|
||||
|
||||
|