Compare commits
83 Commits
e01d88b0c6
...
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 | |||
dbd7893655 | |||
ca1135f0f7 | |||
e7c7a26df0 | |||
c67ba35247 | |||
898f0d7e93 | |||
e94ebaa037 | |||
b956166d24 | |||
c442270362 | |||
f33a7a38a2 | |||
94771a2aa7 | |||
fbf00cd154 | |||
6a66df4a92 | |||
48696ad06c | |||
aa8fe46b43 | |||
e0bb762551 |
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
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
public/
|
||||
_gen/
|
||||
|
||||
|
19
README.md
@ -6,24 +6,23 @@ Site generated with [Hugo][] using the [hermit-v2][] theme.
|
||||
|
||||
Build site:
|
||||
|
||||
```
|
||||
$ docker compose up build
|
||||
```bash
|
||||
docker compose up build
|
||||
```
|
||||
|
||||
Run development server with live refresh:
|
||||
Run development server with live refresh and draft as well as future post
|
||||
rendering enabled:
|
||||
|
||||
```
|
||||
$ docker compose up server
|
||||
```bash
|
||||
docker compose up server
|
||||
```
|
||||
|
||||
Add new posts:
|
||||
|
||||
```bash
|
||||
docker compose up -d server
|
||||
docker compose exec server new content/posts/2024-07-31-my-new-blog-post.md
|
||||
```
|
||||
$ docker compose up -d server
|
||||
$ docker compose exec server new content/posts/2024-07-31-my-new-blog-post.md
|
||||
```
|
||||
|
||||
|
||||
|
||||
[hermit-v2]: https://github.com/1bl4z3r/hermit-V2
|
||||
[Hugo]: https:/gohugo.io
|
@ -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: []
|
||||
---
|
||||
|
75
assets/images/favicon.svg
Normal file
@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="83mm"
|
||||
height="83mm"
|
||||
viewBox="0 0 82.999996 82.999991"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0 (1.0+r73+1)"
|
||||
sodipodi:docname="Gänseliesel-Logo rund.svg"
|
||||
inkscape:export-filename="/home/daniel/cloud/Photos/Hintergrundbilder/Gänseliesel-Logo rund.png"
|
||||
inkscape:export-xdpi="162.82001"
|
||||
inkscape:export-ydpi="162.82001">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.4684"
|
||||
inkscape:cx="108.33847"
|
||||
inkscape:cy="178.35599"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="false"
|
||||
fit-margin-top="3"
|
||||
fit-margin-left="3"
|
||||
fit-margin-right="3"
|
||||
fit-margin-bottom="3"
|
||||
lock-margins="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>(c) Daniel Kraus <www.bovender.de></dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-56.138771,-86.109581)">
|
||||
<circle
|
||||
r="39"
|
||||
cy="128.10957"
|
||||
cx="97.609604"
|
||||
id="path857"
|
||||
style="color:black;overflow:visible;fill:#696969;fill-opacity:1;stroke:none;stroke-width:2.00001;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cscsssccscccsscsssccccscccssscsscsssscsccccccccccssccccssscsscssssscscccccsccsccccccccc"
|
||||
style="fill:navy;stroke:white;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stroke-linejoin:bevel"
|
||||
d="m 73.66954,156.68256 c -0.0257,-0.0212 -0.04676,-1.16622 -0.04676,-2.54442 v -2.50582 l -0.622191,-0.38414 c -1.041429,-0.64296 -1.426937,-2.10829 -1.070799,-4.07019 0.185083,-1.01962 0.168352,-1.20734 -0.157396,-1.76658 -0.703944,-1.2085 -0.948192,-4.22473 -0.610203,-7.5353 0.109691,-1.07467 0.224045,-1.39705 0.642726,-1.81227 0.438724,-0.43508 0.509281,-0.65636 0.509281,-1.5972 0,-0.89228 0.136876,-1.37491 0.747939,-2.63718 1.591474,-3.28755 3.650313,-5.74902 5.158472,-6.16732 0.401471,-0.11129 1.546325,-0.20294 2.544116,-0.20351 2.028793,-0.001 3.643675,-0.45634 5.034194,-1.41896 1.09234,-0.75621 3.697965,-3.58834 4.44801,-4.83468 l 0.640805,-1.06481 -0.350243,-0.64968 c -0.192622,-0.35731 -0.668559,-0.90152 -1.057631,-1.20935 -1.214761,-0.96111 -1.402229,-2.68752 -0.442115,-4.07152 0.261205,-0.37652 0.474599,-0.84815 0.474215,-1.04805 -7.42e-4,-0.40984 -0.565068,-1.40157 -1.144496,-2.01146 -0.333922,-0.3515 -0.374963,-0.58219 -0.304777,-1.7134 0.0934,-1.5051 0.471711,-2.36391 1.241469,-2.81815 0.307214,-0.18128 0.711892,-0.69671 0.936083,-1.19225 0.504605,-1.11535 3.143032,-3.706003 4.442274,-4.361847 0.595113,-0.300396 1.406641,-0.523321 2.121739,-0.58282 1.085968,-0.09039 1.224711,-0.158 2.229284,-1.087097 1.278124,-1.182077 2.812644,-2.223967 4.230694,-2.872493 2.74258,-1.25428 8.56759,-0.434746 11.85918,1.668514 1.45844,0.931903 4.02461,3.493836 4.20038,4.193423 0.0692,0.27543 0.33015,0.70489 0.57988,0.95437 0.24974,0.24945 0.45406,0.61947 0.45406,0.82223 0,0.20277 0.28544,0.96864 0.63432,1.70193 0.89526,1.88167 1.1891,2.79911 1.4438,4.50792 0.32272,2.16504 0.2756,3.01135 -0.25124,4.51229 -0.47937,1.3657 -1.00207,2.09536 -3.25909,4.54957 -1.73736,1.88914 -1.93271,2.37824 -1.93271,4.83888 0,1.96414 -0.0203,2.0706 -0.44756,2.35027 -0.26617,0.17422 -0.62899,0.24744 -0.89525,0.18069 -0.63539,-0.15931 -1.43808,0.19667 -1.64004,0.72732 -0.22403,0.58862 -0.51601,0.75501 -1.23952,0.70633 -0.85373,-0.0574 -1.59229,0.26091 -1.45207,0.6259 0.1529,0.39806 -0.57173,1.1417 -1.24329,1.27587 -0.37802,0.0756 -0.62843,0.30247 -0.85117,0.77139 -0.48801,1.0273 -1.18448,1.42249 -2.51826,1.42886 -1.32822,0.007 -1.42602,-0.0484 -3.50099,-1.96244 -0.84898,-0.7831 -1.6061,-1.42254 -1.68248,-1.42098 -0.32474,0.007 -0.57603,0.3556 -1.20847,1.67785 -1.555805,3.25282 -1.657654,3.5644 -1.769822,5.41463 l -0.107571,1.77504 1.099652,0.74367 c 1.280911,0.86625 2.842061,2.13904 3.834611,3.12634 0.54014,0.53728 0.72766,0.89812 0.8249,1.58734 0.0693,0.49058 0.33931,1.26394 0.60021,1.71855 l 0.47434,0.82657 0.36743,-0.77988 c 0.20208,-0.42894 0.44653,-0.77989 0.54317,-0.77989 0.0967,0 0.29875,-0.29412 0.4491,-0.6536 0.15034,-0.35949 0.36134,-0.65361 0.46883,-0.65361 0.10757,0 0.14348,-0.084 0.0799,-0.18675 -0.0636,-0.10269 0.0156,-0.18674 0.1759,-0.18674 0.1603,0 0.29144,-0.083 0.29144,-0.18455 0,-0.1015 0.33649,-0.23828 0.74776,-0.30398 0.41126,-0.0656 0.74776,-0.18317 0.74776,-0.26105 0,-0.0778 0.2734,-0.26955 0.60755,-0.42593 1.04677,-0.48989 4.87233,-0.18305 5.95124,0.47733 0.61483,0.37632 1.42997,1.87491 1.76605,3.24673 0.35754,1.45941 0.66483,2.03534 1.40645,2.63593 0.28695,0.23237 0.58092,0.69177 0.65328,1.02089 0.0723,0.32911 0.36623,0.89531 0.65304,1.25821 0.71105,0.89969 0.92402,1.82361 0.9269,4.02122 0.001,1.02709 0.0901,2.40659 0.19706,3.06554 0.16565,1.01985 0.15054,1.24214 -0.10161,1.49396 -17.98696,11.95593 -33.854294,9.38426 -47.53174,0.0543 z M 90.631571,114.6264 c 0.104392,0 0.189694,-0.093 0.189694,-0.20673 0,-0.11368 -0.13049,-0.15667 -0.290125,-0.0956 -0.159555,0.0611 -0.242313,0.0339 -0.183891,-0.0605 0.175174,-0.28313 -0.269127,-0.43381 -0.562339,-0.19072 -0.284613,0.23596 -0.355038,0.86752 -0.154852,1.38867 0.09485,0.24697 0.183931,0.19621 0.464597,-0.26485 0.190979,-0.31371 0.432591,-0.57038 0.536916,-0.57038 z m -0.917191,0.0778 c 0.0093,-0.21755 0.05326,-0.2618 0.113003,-0.11288 0.05405,0.13481 0.04743,0.29587 -0.01471,0.35794 -0.06213,0.0621 -0.106247,-0.0483 -0.09817,-0.24511 z"
|
||||
id="path935" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.2 KiB |
68
assets/js/mastodon-share.js
Normal file
@ -0,0 +1,68 @@
|
||||
|
||||
// I got the key, I got the secret…
|
||||
let key = 'mastodon-instance';
|
||||
let instance = localStorage.getItem(key);
|
||||
|
||||
// get the link from the DOM
|
||||
const button = document.querySelector('.mastodon-share');
|
||||
|
||||
// refresh the link with the instance name
|
||||
const refreshlink = (instance) => {
|
||||
button.href = `https://${instance}/share?text=${encodeURIComponent(document.title)}%0A${encodeURIComponent(location.href)}`;
|
||||
}
|
||||
|
||||
// got it? Let's go!
|
||||
if (button) {
|
||||
// labels and texts from the link
|
||||
let prompt = button.dataset.prompt || 'Please tell me your Mastodon instance';
|
||||
let editlabel = button.dataset.editlabel || 'Edit your Mastodon instance';
|
||||
let edittext = button.dataset.edittext || '✏️';
|
||||
|
||||
// Ask the user for the instance name and set it…
|
||||
const setinstance = _ => {
|
||||
instance = window.prompt(prompt, instance);
|
||||
if(instance) {
|
||||
localStorage.setItem(key, instance);
|
||||
createeditbutton();
|
||||
refreshlink(instance);
|
||||
button.click();
|
||||
}
|
||||
}
|
||||
|
||||
// create and insert the edit link
|
||||
const createeditbutton = _ => {
|
||||
if (document.querySelector('button.mastodon-edit')) return;
|
||||
let editlink = document.createElement('button');
|
||||
editlink.innerText = edittext;
|
||||
editlink.classList.add('mastodon-edit');
|
||||
editlink.title = editlabel;
|
||||
editlink.ariaLabel = editlabel;
|
||||
editlink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
localStorage.removeItem(key);
|
||||
setinstance();
|
||||
});
|
||||
button.insertAdjacentElement('afterend', editlink);
|
||||
}
|
||||
|
||||
// if there is a value in localstorage, create the edit link
|
||||
if(localStorage.getItem(key)) {
|
||||
createeditbutton();
|
||||
}
|
||||
|
||||
// When a user clicks the link
|
||||
button.addEventListener('click', (e) => {
|
||||
|
||||
// If the user has already entered their instance
|
||||
// and it is in localstorage write out the link href
|
||||
// with the instance and the current page title and URL
|
||||
if(localStorage.getItem(key)) {
|
||||
refreshlink(localStorage.getItem(key));
|
||||
// otherwise, prompt the user for their instance and save it to localstorage
|
||||
} else {
|
||||
e.preventDefault();
|
||||
setinstance();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
141
assets/scss/_predefined.scss
Normal file
@ -0,0 +1,141 @@
|
||||
// Colors
|
||||
$theme: hsl(172, 99%, 26%);
|
||||
$text: hsl(204, 28%, 93%);
|
||||
$light-grey: #292e32; // #494f5c;
|
||||
$dark-grey: #3B3E48;
|
||||
$highlight-grey: #7d828a;
|
||||
$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 Light", Consolas, "Andale Mono WT", "Andale Mono", Menlo, Monaco, monospace;
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Light';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('IBM Plex Sans Light'),
|
||||
local('IBMPlexSans-Light'),
|
||||
url('/fonts/IBMPlexSans-Light.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Light';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('IBM Plex Sans Light Italic'),
|
||||
local('IBMPlexSans-LightItalic'),
|
||||
url('/fonts/IBMPlexSans-LightItalic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans Light';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
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 Bold Italic'),
|
||||
local('IBMPlexSans-BoldItalic'),
|
||||
url('/fonts/IBMPlexSans-BoldItalic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono Light';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('IBM Plex Mono Light'),
|
||||
local('IBMPlexMono-Light'),
|
||||
url('/fonts/IBMPlexMono-Light.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono Light';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: local('IBM Plex Mono Light Italic'),
|
||||
local('IBMPlexMono-LightItalic'),
|
||||
url('/fonts/IBMPlexMono-LightItalic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono Light';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('IBM Plex Mono Medm'),
|
||||
local('IBMPlexMono-Medm'),
|
||||
url('/fonts/IBMPlexMono-Medium.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono Light';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: local('IBM Plex Mono Medm Italic'),
|
||||
local('IBMPlexMono-MedmItalic'),
|
||||
url('/fonts/IBMPlexMono-MediumItalic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
//Admonition
|
||||
$admonition-background: (
|
||||
'note': rgba(68, 138, 255, 0.1),
|
||||
'info': rgba(0, 184, 212, 0.1),
|
||||
'tip': rgba(0, 191, 165, 0.1),
|
||||
'success': rgba(0, 200, 83, 0.1),
|
||||
'warning': rgba(255, 145, 0, 0.1),
|
||||
'failure': rgba(255, 82, 82, 0.1),
|
||||
'danger': rgba(255, 23, 68, 0.1),
|
||||
'bug': rgba(245, 0, 87, 0.1),
|
||||
) !default;
|
||||
|
||||
$admonition-color: (
|
||||
'note': #448aff,
|
||||
'info': #00b8d4,
|
||||
'tip': #00bfa5,
|
||||
'success': #00c853,
|
||||
'warning': #ff9100,
|
||||
'failure': #ff5252,
|
||||
'danger': #ff1744,
|
||||
'bug': #f50057,
|
||||
) !default;
|
||||
|
||||
// Mixins
|
||||
@mixin dimmed {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
@mixin aTag {
|
||||
a {
|
||||
word-wrap: break-word;
|
||||
border: none;
|
||||
box-shadow: inset 0 -4px 0 $theme;
|
||||
transition-property: box-shadow;
|
||||
transition-duration: .1s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 -1em 0 $theme;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
64
config.toml
@ -1,5 +1,7 @@
|
||||
baseURL = 'https://bovender.de/'
|
||||
baseURL = 'https://www.bovender.de/'
|
||||
languageCode = 'en-us'
|
||||
timeZone = 'Europe/Berlin'
|
||||
defaultContentLanguage = 'en'
|
||||
title = 'bovender'
|
||||
theme = "hermit-v2"
|
||||
|
||||
@ -11,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 = ''
|
||||
@ -26,21 +32,23 @@ lastmod = ["lastmod", ":git", "date", "publishDate"]
|
||||
publishDate = ["publishDate", "date"]
|
||||
expiryDate = ["expiryDate"]
|
||||
|
||||
|
||||
[params.author]
|
||||
name = "Daniel Kraus"
|
||||
about = "CHANGE SITE AUTHOR ABOUT"
|
||||
about = "/about"
|
||||
|
||||
[blackfriday]
|
||||
# hrefTargetBlank = true
|
||||
hrefTargetBlank = true
|
||||
# noreferrerLinks = true
|
||||
# nofollowLinks = true
|
||||
|
||||
[taxonomies]
|
||||
tag = "tags"
|
||||
tag = "tags"
|
||||
# Categories are disabled by default.
|
||||
# category = "categories"
|
||||
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
|
||||
# Enable to get proper Mathjax support
|
||||
#[markup]
|
||||
# [markup.goldmark]
|
||||
@ -58,16 +66,18 @@ expiryDate = ["expiryDate"]
|
||||
dateformNum = "2006-01-02"
|
||||
dateformNumTime = "2006-01-02 15:04 -0700"
|
||||
|
||||
# description = "CHANGE ME SITE DESC RIPTION"
|
||||
description = "Daniel Kraus' personal homepage"
|
||||
# images = [""]
|
||||
themeColor = "#494f5c"
|
||||
themeColor = "#292e32"
|
||||
|
||||
homeSubtitle = "Daniel Kraus"
|
||||
footerCopyright = "all rights reserved"
|
||||
# bgImg = ""
|
||||
gitUrl = "https://github.com/1bl4z3r/hermit-V2/tree/staging"
|
||||
|
||||
justifyContent = true
|
||||
ShowLastmod = true
|
||||
gitUrl = "https://git.bovender.de/daniel/blog/commit/"
|
||||
|
||||
justifyContent = false
|
||||
|
||||
relatedPosts = true
|
||||
code_copy_button = true
|
||||
@ -75,8 +85,8 @@ expiryDate = ["expiryDate"]
|
||||
homeSubtitlePrinter = true
|
||||
scrollToTop = true
|
||||
global_mathjax = false
|
||||
# readTime = true
|
||||
# readTimeSeparator = "CHANGE ME READ TIME"
|
||||
readTime = false
|
||||
readTimeSeparator = "~"
|
||||
# legacyLayout = false
|
||||
shareSocial = true
|
||||
# googleTagManager = "GTM-XXXXXXXX"
|
||||
@ -85,16 +95,20 @@ expiryDate = ["expiryDate"]
|
||||
# pinned = "Pinned Posts"
|
||||
# pinnedSVGname = "pin"
|
||||
# descriptionInPosts= true
|
||||
# initialPublish = "Initally Posted on: "
|
||||
# human = ["single","posts"]
|
||||
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"
|
||||
@ -111,11 +125,21 @@ expiryDate = ["expiryDate"]
|
||||
[menu]
|
||||
|
||||
[[menu.main]]
|
||||
name = "Posts"
|
||||
name = "Blog"
|
||||
url = "posts/"
|
||||
weight = 10
|
||||
|
||||
[[menu.main]]
|
||||
name = "About"
|
||||
name = "About bovender"
|
||||
url = "about/"
|
||||
weight = 20
|
||||
weight = 20
|
||||
|
||||
[[menu.main]]
|
||||
name = "Impressum"
|
||||
url = "impressum/"
|
||||
weight = 30
|
||||
|
||||
[[menu.main]]
|
||||
name = "Cheat sheet"
|
||||
url = "cheatsheet/"
|
||||
weight = 15
|
||||
|
BIN
content/about/ggap-ev.png
Normal file
After Width: | Height: | Size: 2.9 MiB |
@ -1,8 +1,74 @@
|
||||
---
|
||||
title: "Content Placeholder"
|
||||
_build:
|
||||
render: never
|
||||
list: never
|
||||
publishResources: false
|
||||
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.
|
||||
|
||||
## Portfolio -- websites
|
||||
|
||||
### [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
|
||||
links terms to existing pages in a Wiki.
|
||||
|
||||
### [PubmedParser](https://github.com/bovender/pubmedparser)
|
||||
|
||||
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.
|
||||
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/nephrowiki.png
Normal file
After Width: | Height: | Size: 2.2 MiB |
BIN
content/about/plesse.png
Normal file
After Width: | Height: | Size: 597 KiB |
BIN
content/about/xltoolbox.png
Normal file
After Width: | Height: | Size: 2.4 MiB |
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>
|
47
content/impressum.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "Impressum und Datenschutz"
|
||||
date: 2024-07-31T12:42:53Z
|
||||
draft: false
|
||||
---
|
||||
Dr. Daniel Kraus
|
||||
Tel. (0 61 07‌) 7 5‌6 88 4‌0
|
||||
Mail: <bovender@bovender.de>
|
||||
|
||||
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
|
||||
|
||||
Der Mailserver bovender.de ist zuständig für die Domains:
|
||||
|
||||
- [bantel-stiftung.de](https://bantel-stiftung.de)
|
||||
- [bantelstiftung.de](https://bantelstiftung.de)
|
||||
- [bovender.de](https://bovender.de)
|
||||
- [gemeinsam-gegen-armut.org](https://gemeinsam-gegen-armut.org)
|
||||
- [ggap-ev.org](https://ggap-ev.org)
|
||||
- [mainzer-niere.de](https://mainzer-niere.de)
|
||||
- [neph.social](https://neph.social)
|
||||
- [nephrowiki.de](https://nephrowiki.de)
|
||||
- [nierenverein.de](https://nierenverein.de)
|
||||
- [xltoolbox.net](https://xltoolbox.net)
|
||||
|
||||
## Datenschutz
|
||||
|
||||
Diese Website erfasst keine personenbezogenen Daten, und es werden keine
|
||||
Dienste von Drittfirmen in Anspruch genommen.
|
||||
|
||||
Es findet eine anonymisierte Erfassung von Seitenbesuchen statt, die keine
|
||||
Rückverfolgung zu einzelnen Personen/Geräten ermöglicht.
|
||||
|
||||
## Dank
|
||||
|
||||
Besonderer Dank an [Christian Heilmann][] für das Script [mastodon-share][],
|
||||
um Links leicht auf Mastodon zu posten.
|
||||
|
||||
[Christian Heilmann]: https://christianheilmann.com/2023/08/18/adding-a-share-to-mastodon-link-to-any-web-site-and-here/
|
||||
[mastodon-share]: https://github.com/codepo8/mastodon-share
|
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
|
288
content/posts/2024/p14s/index.md
Normal file
@ -0,0 +1,288 @@
|
||||
---
|
||||
title: "Experience with running Fedora Linux on a Thinkpad P14s Gen 5"
|
||||
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: false
|
||||
draft: false
|
||||
toc: true
|
||||
scrolltotop: true
|
||||
tags:
|
||||
- Fedora
|
||||
- F40
|
||||
- KDE
|
||||
- Linux
|
||||
- NVIDIA
|
||||
- Thinkpad
|
||||
---
|
||||
|
||||
I have recently acquired a Lenovo Thinkpad P14s Gen 5. As of the time of writing
|
||||
-- summer of '24 -- these machines are brand new, and so is their architecture:
|
||||
Intel Core Ultra (Meteor Lake) with a dedicated NVIDIA GPU. Here I report my
|
||||
experience with running Linux with the [KDE Plasma][] desktop on this machine.
|
||||
|
||||
My previous daily driver was a T480s (see my old blog post over at [xltoolbox.net][]
|
||||
for a report). I also administer two different X1 Carbons (5th generation and
|
||||
9th generation), however, I don't use these very often myself.
|
||||
|
||||
Here are the specs (the 1 TB SSD has been swapped for a Samsung 990 PRO with 2 TB).
|
||||
|
||||
```plain
|
||||
daniel@seppel9 ~> fastfetch
|
||||
.',;::::;,'. daniel@seppel9
|
||||
.';:cccccccccccc:;,. --------------
|
||||
.;cccccccccccccccccccccc;. OS: Fedora Linux 40 (KDE Plasma) x86_64
|
||||
.:cccccccccccccccccccccccccc:. Host: 21G3S00A00 (ThinkPad P14s Gen 5)
|
||||
.;ccccccccccccc;.:dddl:.;ccccccc;. Kernel: Linux 6.9.11-200.fc40.x86_64
|
||||
.:ccccccccccccc;OWMKOOXMWd;ccccccc:. Uptime: 1 hour, 47 mins
|
||||
.:ccccccccccccc;KMMc;cc;xMMc;ccccccc:. Packages: 4338 (rpm)
|
||||
,cccccccccccccc;MMM.;cc;;WW:;cccccccc, Shell: fish 3.7.0
|
||||
:cccccccccccccc;MMM.;cccccccccccccccc: Display (LEN8AB1): 3072x1920 @ 120 Hz (as 2048x1280) in 14″ [Built-in]
|
||||
:ccccccc;oxOOOo;MMM000k.;cccccccccccc: DE: KDE Plasma 6.1.3
|
||||
cccccc;0MMKxdd:;MMMkddc.;cccccccccccc; WM: KWin (Wayland)
|
||||
ccccc;XMO';cccc;MMM.;cccccccccccccccc' WM Theme: Breeze
|
||||
ccccc;MMo;ccccc;MMW.;ccccccccccccccc; Theme: Breeze (Dark) [Qt], Breeze [GTK3]
|
||||
ccccc;0MNc.ccc.xMMd;ccccccccccccccc; Icons: breeze-dark [Qt], breeze-dark [GTK3/4]
|
||||
cccccc;dNMWXXXWM0:;cccccccccccccc:, Font: IBM Plex Sans (10pt) [Qt], IBM Plex Sans (10pt) [GTK3/4]
|
||||
cccccccc;.:odl:.;cccccccccccccc:,. Cursor: breeze (24px)
|
||||
ccccccccccccccccccccccccccccc:'. Terminal: konsole 24.5.2
|
||||
:ccccccccccccccccccccccc:;,.. CPU: Intel(R) Core(TM) Ultra 7 155H (22) @ 4,80 GHz
|
||||
':cccccccccccccccc::;,. GPU 1: Intel rc Graphics @ 2,25 GHz [Integrated]
|
||||
GPU 2: NVIDIA RTX 500 Ada Generation Laptop GPU
|
||||
Memory: 9,13 GiB / 62,29 GiB (15%)
|
||||
Swap: 302,50 MiB / 104,00 GiB (0%)
|
||||
Disk (/): 18,77 GiB / 146,59 GiB (13%) - ext4
|
||||
Disk (/home): 524,56 GiB / 1,55 TiB (33%) - ext4
|
||||
Local IP (wlp0s20f3): 192.168.3.114/24
|
||||
Battery: 93% [AC Connected]
|
||||
Locale: en_US.UTF-8
|
||||
```
|
||||
|
||||
## Difficulties and finally success with installing Linux
|
||||
|
||||
At first, I was totally unable to install [KDE neon][], my preferred Linux distribution,
|
||||
on this system. I freaked out. (I don't care if it's officially a 'distribution' or not.)
|
||||
The live CD just would not boot. It did work in safe graphics mode, but when I installed
|
||||
the system onto the laptop, I was once again unable to boot into Plasma.
|
||||
(I already posted in the [KDE forum][] about this).
|
||||
|
||||
At some point it occurred to me that I may need to try a distribution, so I tried [Fedora][]
|
||||
-- and it worked!! In order to boot the live CD, I also had to choose basic graphics mode
|
||||
just like with KDE neon (which is Ubuntu under the hood). Once I had the live desktop up
|
||||
and running, I could enable the NVIDIA repositories, so that the NVIDIA drivers were
|
||||
included during system installation. Luckily, the Fedora Project provides a [KDE spin][]
|
||||
so that I could continue to use my favorite desktop environment.
|
||||
|
||||
## Hardware
|
||||
|
||||
Except for the initial difficulties getting this system with a dedicated NVIDIA GPU
|
||||
to run Linux, everything else just works out of the box. Nice! :-)
|
||||
|
||||
The **trackpoint** works as usual. This is really the main reason why I keep buying
|
||||
Thinkpads. A laptop without trackpoint is a no-go for me. In fact, I did consider buying
|
||||
a MacBook Pro, but the absence of a trackpoint and the uncertainty whether I could do
|
||||
my beloved Linux foo with MacOS made me stick with a Thinkpad.
|
||||
|
||||
The **fingerprint scanner** is integrated into the power button. While not essential, it's
|
||||
a nice gimmick and KDE Plasma provides a pretty UI to set it up. Currently, logging
|
||||
into the system after booting is not (yet) supported, but unlocking (e.g., after resume
|
||||
from sleep) works very well.
|
||||
|
||||
The 3k **screen** is really good, it's very bright and super crisp. At 100%, the
|
||||
user interface is quite tiny. Luckily, with Wayland, fractional scaling is now
|
||||
easy to accomplish and reliable. With a scale factor of 1.2 (120%), I am very happy
|
||||
with my P14s' display. Interestingly though, the corners are rounded, even though
|
||||
the laptop frame has sharp corners. I tend to think: If you have enough time wondering
|
||||
about something like this, it's probably time to take a break and do something else
|
||||
for a while.
|
||||
|
||||
Unfortunately, the **keyboard** is the worst that I have ever had in any Thinkpad.
|
||||
It feels like typing on jelly. Luckily, for most of the heavy-duty work, I connect
|
||||
the laptop to a Thunderbolt dock with a proper mechanical keyboard attached.
|
||||
I wonder why Lenovo does not equip this machine with the same keyboard as in the
|
||||
other models -- given the price tag of this machine, the extra bucks surely cannot
|
||||
make such a big difference?
|
||||
|
||||
**Camera**, mike, and speakers just work. No problem running Zoom. I have not yet
|
||||
tried out Teams.
|
||||
|
||||
## Form factor and weight
|
||||
|
||||
The P14s is somewhat bigger than the old T480s and certainly somewhat heavier.
|
||||
Before deciding on the purchase, I carefully thought about my use cases and decided
|
||||
that I can live with an extra 200-300 grams that I mostly only carry onto and
|
||||
off from the commute train, as well as on my bicycle's rack. Therefore, I made a
|
||||
decision for the extra performance (with dedicated GPU, which the alternative
|
||||
models, i.e. X1 Carbons or T14s's do not offer).)
|
||||
|
||||
Lenovo [says][p14s-weight] the P14s starts at 1.6 kg, which compares to a MacBook Pro's
|
||||
nominal weight. On our kitchen scale, my P14s weighs 1.74 kg.
|
||||
|
||||
In real life, the laptop's body is somewhat bigger than the actual casing, because
|
||||
it has a very long "foot" underneath it. This ensures proper ventilation, because
|
||||
the two fans blow out the warm air underneath the laptop. While this "foot" makes
|
||||
the P14s rather thick, it turns out that this also provides a good grip when pulling
|
||||
it ouf of a bag or carrying it around.
|
||||
|
||||
I have yet to come to terms with the new camera casing that all new Thinkpads
|
||||
seem to have nowadays.
|
||||
|
||||
{{< figure src="p14s-camera-casing.jpg"
|
||||
alt="A photograph of the camera casing of a Thinkpad P14s Gen 5"
|
||||
caption="The camera casing. It disrupts the smooth surface of the Thinkpad.">}}
|
||||
|
||||
{{< figure src="p14s-foot.jpg"
|
||||
alt="A photograph of the 'foot' underneath a Thinkpad P14s Gen 5"
|
||||
caption="The 'foot' underneath the P14s makes it thicker, but also provides a good grip on the machine.">}}
|
||||
|
||||
## Performance
|
||||
|
||||
Compared to the old T480s, the performance gain of the Intel Core Ultra and the
|
||||
dedicated NVIDIA GPU is very noticeable. Whether I compile R packages or analyze
|
||||
clinical data with ~80k subjects in [RStudio][], develop RAW images in [Darktable][],
|
||||
or work with Office in a VirtualBox-hosted Windows machine, everything is very
|
||||
snappy. For instance, a typical RAW image is now processed in under 1 second, as
|
||||
opposed to ~10 seconds on the T480s.
|
||||
|
||||
The machine wakes from sleep (suspend to RAM) extremely quickly, I guess this is due to
|
||||
[novel suspend mechanisms][s3]? On the other hand, boot times are rather slow
|
||||
(34-40s on AC power, T480s on battery 31 s!!).
|
||||
|
||||
## Fan
|
||||
|
||||
The fans spin up quickly, but the noise is not unpleasant. Maybe I can tweak the
|
||||
fan control in order to have them spin up a little more lazily. But when I had both
|
||||
my old laptop and the new one side by side, I realized that the T480s' fan noise
|
||||
drowns the P14s' fan noise. Notably, the P14s was running on AC power, while the
|
||||
T480s ran on battery. In fact, even the T480s with built-in graphics had its fan
|
||||
running most of the time when I was working with it (academic work, researching,
|
||||
writing, data analysis etc.).
|
||||
|
||||
## Energy consumption
|
||||
|
||||
The P14s' battery has much more capacity than the T480s' one, 75 Wh vs. 42 Wh.
|
||||
However, with my normal work load, the battery drains rather quickly. The
|
||||
following screenshot shows the charge level over time as I sat outside,
|
||||
editing a manuscript with Word running in a virtual machine (VirtualBox
|
||||
with Windows 11 guest) and doing some internet research in a browser. The
|
||||
screen brightness was set to 100%. After some 3.5 hours, it's time for a
|
||||
break...
|
||||
|
||||
{{< figure src="p14s-battery-drain.png">}}
|
||||
|
||||
## Making the switch from Ubuntu to Fedora
|
||||
|
||||
When I downloaded the Fedora [KDE spin][], I was admittedly a bit anxious.
|
||||
I had been working with Ubuntu and KDE neon, which is based on Ubuntu,
|
||||
for many years, and I wondered if Fedora would be very different.
|
||||
|
||||
It's not.
|
||||
|
||||
The three things that are really different from my perspective:
|
||||
|
||||
- To install something on the command line, use `dnf` rather than `apt`:
|
||||
|
||||
```bash
|
||||
sudo dnf install good-stuff
|
||||
```
|
||||
|
||||
- Removable disks (USB drives) are mounted on `/run/media/<USER>` rather
|
||||
than `/media/<USER>`.
|
||||
- More up-to-date kernel and software
|
||||
|
||||
## Conclusion
|
||||
|
||||
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 |
BIN
content/posts/2024/p14s/p14s-battery-drain.png
Normal file
After Width: | Height: | Size: 177 KiB |
BIN
content/posts/2024/p14s/p14s-camera-casing.jpg
Normal file
After Width: | Height: | Size: 954 KiB |
BIN
content/posts/2024/p14s/p14s-foot.jpg
Normal file
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,13 +1,17 @@
|
||||
services:
|
||||
build:
|
||||
image: klakegg/hugo:ext-alpine
|
||||
image: hugomods/hugo:ci
|
||||
command: hugo
|
||||
volumes:
|
||||
- ".:/src"
|
||||
user: 1000:1000
|
||||
server:
|
||||
image: klakegg/hugo:ext-alpine
|
||||
command: server
|
||||
image: hugomods/hugo:ci
|
||||
command: server --buildDrafts --buildFuture
|
||||
volumes:
|
||||
- ".:/src"
|
||||
ports:
|
||||
- "1313:1313"
|
||||
|
||||
- "127.0.0.1:1313:1313"
|
||||
user: 1000:1000
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
|
16
layouts/partials/analytics.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!-- Matomo -->
|
||||
<script>
|
||||
var _paq = window._paq = window._paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="https://matomo.bovender.de/";
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', '1']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Matomo Code -->
|
||||
|
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">
|
22
layouts/partials/posts_single_info.html
Normal file
@ -0,0 +1,22 @@
|
||||
{{- with .Params.description }}<p>{{.}}</p>{{- end }}
|
||||
{{- if and .Params.author .Params.authorLink}}{{ $relreflink := relref . .Params.authorLink }}<p>{{ partial "svg.html" (dict "context" . "name" "author") }}<a href="{{ $relreflink }}" target="_blank">{{ .Params.author }}</a></p>
|
||||
{{- else if .Params.author }}{{ $relreflink := relref . .Site.Params.author.about }}<p>{{ partial "svg.html" (dict "context" . "name" "author") }}<a href="{{ $relreflink }}" target="_blank">{{ .Params.author }}</a></p>
|
||||
{{- else }}{{ $relreflink := relref . .Site.Params.author.about }}<p>{{ partial "svg.html" (dict "context" . "name" "author") }}<a href="{{ $relreflink }}" target="_blank">{{ .Site.Params.author.name }}</a></p>
|
||||
{{- end }}
|
||||
{{- with .Params.tags }}
|
||||
<p>{{- partial "svg.html" (dict "context" . "name" "posts_single_tags") -}}{{- range . -}}<span class="tag"><a href="{{ "tags/" | absLangURL }}{{ . | urlize }}">{{.}}</a></span>{{- end }}</p>
|
||||
{{- end }}
|
||||
{{- 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") }}{{ .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> @ {{ .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 -}}
|
||||
{{ errorf "Lastmod is not found in Page Frontmatter or Lastmod is same as Date" }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</p>
|
37
layouts/partials/social-share.html
Normal file
@ -0,0 +1,37 @@
|
||||
<button id="share-btn" class="hdr-btn" title="{{i18n "share"}}">{{- partial "svg.html" (dict "context" . "name" "share") -}}</button>
|
||||
|
||||
<div id="share-links" class="animated fast">
|
||||
{{ $title := .Title }}
|
||||
{{ $url := printf "%s" .Permalink }}
|
||||
{{ $author := (.Params.author | default .Site.Params.author.name) }}
|
||||
{{ $summary := print $title ", by " $author "\n\n" .Params.description "\n" }}
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" rel="noreferrer noopener"
|
||||
target="mastodon"
|
||||
data-prompt="Please tell me your Mastodon instance"
|
||||
data-edittext="✏️"
|
||||
data-editlabel="Edit your Mastodon instance"
|
||||
class="mastodon-share">
|
||||
Share to Mastodon
|
||||
</a>
|
||||
{{ $mastodon_share := resources.Get "js/mastodon-share.js" | js.Build "mastodon-share.js" }}
|
||||
<script src="{{ $mastodon_share.RelPermalink }}" defer></script>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/intent/tweet?hashtags=hermit2&url={{ .Permalink }}&text={{ .Title }}" target="_blank" rel="noopener" aria-label="Share on X">{{- partial "svg.html" (dict "context" . "name" "x") -}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://facebook.com/sharer/sharer.php?u={{ .Permalink }}" target="_blank" rel="noopener" aria-label="Share on Facebook">{{- partial "svg.html" (dict "context" . "name" "facebook") -}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:?subject={{ .Title }}&body={{ .Permalink }}" target="_self" rel="noopener" aria-label="Share on Email">{{- partial "svg.html" (dict "context" . "name" "email") -}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.linkedin.com/shareArticle?mini=true&url={{ $url }}&source={{ .Site.BaseURL }}&title={{ $title }}&summary={{ $summary }}" target="_blank" rel="noopener" aria-label="Share on LinkedIn">{{- partial "svg.html" (dict "context" . "name" "linkedin") -}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" onclick="linkShare({{ $title }},{{ $url }},{{ $summary }}); return false;" target="_self" rel="noopener" aria-label="Copy Link">{{- partial "svg.html" (dict "context" . "name" "share_2") -}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
38
layouts/shortcodes/figure.html
Normal file
@ -0,0 +1,38 @@
|
||||
<figure{{ with .Get "class" }} class="{{ . }}"{{ end }}>
|
||||
{{- if .Get "link" -}}
|
||||
<a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
|
||||
{{- end -}}
|
||||
|
||||
{{- $u := urls.Parse (.Get "src") -}}
|
||||
{{- $src := $u.String -}}
|
||||
{{- if not $u.IsAbs -}}
|
||||
{{- with or (.Page.Resources.Get $u.Path) (resources.Get $u.Path) -}}
|
||||
{{- $src = .RelPermalink -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<img src="{{ $src }}"
|
||||
{{- if or (.Get "alt") (.Get "caption") }}
|
||||
alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
|
||||
{{- end -}}
|
||||
{{- with .Get "width" }} width="{{ . }}"{{ end -}}
|
||||
{{- with .Get "height" }} height="{{ . }}"{{ end -}}
|
||||
{{- with .Get "loading" }} loading="{{ . }}"{{ end -}}
|
||||
><!-- Closing img tag -->
|
||||
{{- if .Get "link" }}</a>{{ end -}}
|
||||
{{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
|
||||
<figcaption>
|
||||
{{ with (.Get "title") -}}
|
||||
<h4>{{ . }}</h4>
|
||||
{{- end -}}
|
||||
{{- if or (.Get "caption") (.Get "attr") -}}<p>
|
||||
{{- .Get "caption" | markdownify -}}
|
||||
{{- with .Get "attrlink" }}
|
||||
<a href="{{ . }}">
|
||||
{{- end -}}
|
||||
{{- .Get "attr" | markdownify -}}
|
||||
{{- if .Get "attrlink" }}</a>{{ end }}</p>
|
||||
{{- end }}
|
||||
</figcaption>
|
||||
{{- end }}
|
||||
</figure>
|
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>
|
@ -1 +0,0 @@
|
||||
{"Target":"css/style.min.3b715be5bd264701c0fc591b2c4a36421d10ed026ad15fcf6d2f91991d4a77a7.css","MediaType":"text/css","Data":{"Integrity":"sha256-O3Fb5b0mRwHA/FkbLEo2Qh0Q7QJq0V/PbS+RmR1Kd6c="}}
|
48
static/bovender.svg
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
static/fonts/IBMPlexMono-Bold.woff2
Normal file
BIN
static/fonts/IBMPlexMono-BoldItalic.woff2
Normal file
BIN
static/fonts/IBMPlexMono-ExtraLight.woff2
Normal file
BIN
static/fonts/IBMPlexMono-ExtraLightItalic.woff2
Normal file
BIN
static/fonts/IBMPlexMono-Italic.woff2
Normal file
BIN
static/fonts/IBMPlexMono-Light.woff2
Normal file
BIN
static/fonts/IBMPlexMono-LightItalic.woff2
Normal file
BIN
static/fonts/IBMPlexMono-Medium.woff2
Normal file
BIN
static/fonts/IBMPlexMono-MediumItalic.woff2
Normal file
BIN
static/fonts/IBMPlexMono-Regular.woff2
Normal file
BIN
static/fonts/IBMPlexMono-SemiBold.woff2
Normal file
BIN
static/fonts/IBMPlexMono-SemiBoldItalic.woff2
Normal file
BIN
static/fonts/IBMPlexMono-Text.woff2
Normal file
BIN
static/fonts/IBMPlexMono-TextItalic.woff2
Normal file
BIN
static/fonts/IBMPlexMono-Thin.woff2
Normal file
BIN
static/fonts/IBMPlexMono-ThinItalic.woff2
Normal file
BIN
static/fonts/IBMPlexSans-Bold.woff2
Normal file
BIN
static/fonts/IBMPlexSans-BoldItalic.woff2
Normal file
BIN
static/fonts/IBMPlexSans-Italic.woff2
Normal file
BIN
static/fonts/IBMPlexSans-Light.woff2
Normal file
BIN
static/fonts/IBMPlexSans-LightItalic.woff2
Normal file
BIN
static/fonts/IBMPlexSans-Medium.woff2
Normal file
BIN
static/fonts/IBMPlexSans-MediumItalic.woff2
Normal file
BIN
static/fonts/IBMPlexSans-Regular.woff2
Normal file
5
update-blog.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
pushd /home/daniel/local/Code/blog
|
||||
docker compose up build && rsync -av --delete public/* bovender.de:/var/docker-data/nginx/www/bovender/
|
||||
popd
|
||||
|