diff options
48 files changed, 1683 insertions, 37 deletions
diff --git a/.well-known/host-meta.xml b/.well-known/host-meta.xml new file mode 100644 index 0000000..3f156a0 --- /dev/null +++ b/.well-known/host-meta.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> + <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"> + <Link rel="lrdd" type="application/json" template="https://blog.uvokchee.de/.well-known/webfinger?resource={uri}"></Link> + </XRD>
\ No newline at end of file @@ -21,3 +21,20 @@ watch: clean ipfs: clean JEKYLL_ENV=ipfs bundle exec jekyll build cd _site && cp all.html index.html && rm google* && sed -i -e 's|="/|="|g' *.html && tar cvf ../ipfs.tar $$(grep -L redirect *.html) assets index.html + +gemini: clean + mv _layouts _layouts.bak + mv _layouts.gemini _layouts + sed -i.bak -e '/import/ d' assets/main.scss + sed -i.bak -e '/seo/ d' _includes/head.html + cp _config.yml _config.yml.bak + awk '/pagination/ {replace=1;} /enabled/ && replace==1 {replace=0; print " enabled: false"; next;} {print;}' _config.yml > x + sed -i -e '/theme/ d' x + mv x _config.yml + JEKYLL_ENV=gemini bundle3.1 exec jekyll build + mv _config.yml.bak _config.yml + mv assets/main.scss.bak assets/main.scss + mv _includes/head.html.bak _includes/head.html + mv _layouts _layouts.gemini + mv _layouts.bak _layouts + rm -rf _site/category diff --git a/_comments.sh b/_comments.sh new file mode 100644 index 0000000..3c29a1a --- /dev/null +++ b/_comments.sh @@ -0,0 +1,3 @@ +#!/bin/sh +[[ "$1" = "-c" ]] && scp hetzner:/var/www/hatsu/hatsu.sqlite3 . +sqlite3 -box hatsu.sqlite3 'SELECT id, in_reply_to_root FROM post where in_reply_to IS NOT NULL;' diff --git a/_config.yml b/_config.yml index 80f282c..911fae5 100644 --- a/_config.yml +++ b/_config.yml @@ -49,7 +49,7 @@ minima: mastodon: - username: uvok - instance: furry.engineer + instance: woof.tech #show_excerpts: true @@ -71,4 +71,7 @@ autopages: collections: enabled: false -cssversion: "2023120301" +cssversion: "2024010301" + +feed: + posts_limit: 20 diff --git a/_data/news.yml b/_data/news.yml index 4684514..b73a5e9 100644 --- a/_data/news.yml +++ b/_data/news.yml @@ -1,8 +1,8 @@ -- id: AdBlock +- id: AdBanner content: >- <div> <div style="font-weight: bold">You don't seem to be using an ad blocker.</div> - <div>Please consider installing one.</div> + <div>Please consider installing one. I would recommend uBlock Origin.</div> </div> - id: news1 content: >- diff --git a/_drafts/bird-cpu-usage.md b/_drafts/bird-cpu-usage.md new file mode 100644 index 0000000..c0fa5b7 --- /dev/null +++ b/_drafts/bird-cpu-usage.md @@ -0,0 +1,170 @@ +--- +layout: post +title: Bird CPU usage +date: 2025-01-07 19:06 +0100 +categories: tech +lang: en +--- + +**Preface**: This is kind of a "bug hunt log". I extensively posted on Mastodon +about it, but wanted to keep the progress in writing as well. This might lead +like random rambling in some parts, because I edited and rearranged the +paragraphs of this post a few times. Sorry for that. + +**Preface 2**: Starting point: In September 2024 I upgraded to bird2 +`2.15.1-1~bpo12+1` - probably a self-built backport of mine. This, I "had to +to", since I wanted the Babel metrics measurement, which is not present in +Debian stable (`2.0.12-7`). Yeah, I won't complain. For 99 % of the stuff I do, +Debian stable is *perfect*. I want a server without regularly breaking things, +after all. Only recently (2015-01-07), I added the bird / network.cz repos and +installed bird2 from there. + +Several times already, I noticed this in my Munin monitoring: + +{% image +img="https://pics.uvokchee.de/upload/2025/01/07/20250107180512-c1453895.png" +alt="RRD tool graphic showing a high CPU usage" %} + +I found it strange, but had no time to inspect further. + +Recently, I tried to investigate what happens on the server there. htop showed +high CPU usage for both bird and dnsmasq (always together) in these times. + +Fuming (angry) a bit, I went with a brute-force approach: + +``` +#!/bin/bash + +# Configuration +THRESHOLD=1.0 +# 3 hours +DURATION=$((3*80*80)) +MAIL_TO="lolnope" +SUBJECT="High Load Average Alert" +BODY="The load average has been above ${THRESHOLD} for more than 3 hours." +REBOOT_CMD="/sbin/reboot" + +# Function to check the load average +check_load() { + # 15 min loadavg + loadavg=$(awk '{print $3}' /proc/loadavg) + echo "$(date): Current Load Average: $loadavg" + + if (( $(echo "$loadavg > $THRESHOLD" | bc -l) )); then + echo "$(date): Load average is above threshold." + return 0 + else + echo "$(date): Load average is below threshold." + return 1 + fi +} + +# Monitor the load average +start_time=$(date +%s) +while true; do + if check_load; then + current_time=$(date +%s) + elapsed_time=$((current_time - start_time)) + + if [ "$elapsed_time" -gt "$DURATION" ]; then + echo "$(date): Load average has been above threshold for more than 3 hours." + + # Send alert email + (echo "$BODY"; ps -e -o %cpu,%mem,cmd --sort pcpu | tail) | mail -s "$SUBJECT" "$MAIL_TO" + + # Reboot the server - room for improvement +# systemctl stop bird +# systemctl start bird + $REBOOT_CMD + break + fi + else + start_time=$(date +%s) + fi + sleep 300 # Check every 5 minutes +done + +``` + +Specifically, the output of ps + +``` +22.7 2.7 /usr/sbin/bird -f -u bird -g bird +33.3 0.1 ps -e -o %cpu,%mem,cmd --sort pcpu +37.4 0.0 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new,.bak --local-service --trust-anchor=.,20326,8,2,e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc683457104237c7f8ec8d +``` + +confirmed the quick look at htop - although the "percentage" is a bit weird. +From the manpage: + +> Currently, it is the CPU time used divided by the time the process has been +> running (cputime/realtime ratio), expressed as a percentage. + +(So if the process runs "long enough" and only starts misbehaving after a year, +it won't show up?). + +I asked an LLM what to do, in addition to strace, and it suggested perf. +Unfortunately, this requires debug symbols [1]. [And while Debian does provide +debug symbols](https://wiki.debian.org/HowToGetABacktrace) - it doesn't for +dnsmasq (yet) in bookworm. Luckily, the nice people at labs.nic.cz provide a +dbgsym package in their Bird(2) Debian repository. + +Aside, the script above could benefit from some improvements, instead of +rebooting, I could actually record and strace and perf when this happens, and +only then reboot / restart the service. + +Now, stracing dnsmasq (when "idle") reveals some recvmsg of type +`RTM_NEWROUTE`. I have *no idea* why dnsmasq would need that. But I already +*assume* the high CPU usage occurs when Bird exports lots of routes to the +kernel. *I have no hecking clue why dnsmasq would need to use the CPU at the +same time. If someone can enlighten me, **please** message me.* + +Also, in journalctl, I see lots of the infamous `Kernel dropped some netlink +messages, will resync on next scan.` messages at times - the message apparently +nobody has a solution to, and even though there are mailing list posts telling +to sysctl `net.core.rmem_default`, I doesn't seem to yield a solution. + +I actually did a `perf record` of bird when the CPU usage was high, and I saw +that `rt_event` was running 33% of the time. I don't know what to make of that. + +So… step-by-step? I have both DN42 and clearnet bird running on this VPS, in +parallel. So maybe start by disabling one of these? Or, I have a better idea, +keep it enabled, and disable all protocols! (`birdc disable \"*\"`). That +helped, until midnight. When suddenly the CPU went up again. WTF? Let's have a +look at `birdc s p`. All protocols up. Huh!?!? + +Let's investigate further: + +* Oooo, log rotation happens at midnight +* Fuck, I specified the same log files for both bird's + +Well, log rotation. Which I added myself. This does a `birdc configure` +afterward. Which means the protocols go up again, because I disabled them on the +command line, not in the config. + +Ungh. Okay, this is getting really ugly. `systemctl disable --now bird-clear`. +Now let's run this for a few days... + +That seem to have helped. I now decided to edit the clearnet config and disable +all "external" protocols (to IXPs), keeping Babel, RPKI, and IBGP enabled. +Immediately after starting bird-clearnet, the CPU usage went up again. To be +expected, for the initial sync. But it kept being high. So, I disabled the RPKI +protocols as well and... suddenly, the CPU usage is down??? + +(2025-01-09) So, now, the RPKI stuff... I saw that the connection to one of my +RPKI servers wouldn't work (Fort Validator), and Fort complained about `Bad RTR +version: 2`. The [Bird +docs](https://bird.network.cz/?get_doc&v=20&f=bird-6.html) mention a `max +version` option, and with that, the connection worked. And the CPU usage was +down. *However*, this was not "the root issue". This option is only present +since bird 2.16, which I only installed on 2025-01-07 [2]. And I had the CPU +problems back in November last year! So this is "something completely +different", it appears. I currently have the BGP sessions to virtual IXPs +disabled, and will run it like this for a few days. The average load is 0.4. +Still too high, compared to my other VPS. + +[1] At least when I want to see the binaries function names. Kernel symbols +seem to show up fine. \ +[2] Which is pretty annoying, when you chase one bug, you change something in +your config, and then something else breaks. That's on me. + diff --git a/_includes/commentNotice.html b/_includes/commentNotice.html index b1be501..a46dadf 100644 --- a/_includes/commentNotice.html +++ b/_includes/commentNotice.html @@ -1,19 +1,44 @@ <section> + {% assign base_url = 'https://blog.uvokchee.de/notice/' %} + {% assign url = page.url %} + {% assign domain = 'https://blog.uvokchee.de' %} + {% assign full_url = domain | append: page.url %} + {% capture encoded_url %}{{ full_url | base64_encode }}{% endcapture %} + {% assign src_url = base_url | append: encoded_url %} + <h2>Kommentare / Comments</h2> - <p>Kommentare werden von mir selbst auf einem anderen Server über <a href="https://isso-comments.de/">Isso</a> gehostet.</p> + <h3>Isso</h3> + + <p>Kommentare werden von mir selbst auf einem anderen Server über <a href="https://isso-comments.de/">Isso</a> + gehostet.</p> <p>Comments are hosted by myself on another server, powered by <a href="https://isso-comments.de/">Isso</a>.</p> - <script data-isso="//c.uvokchee.de/" - data-isso-require-author=true - data-isso-vote=false + <script data-isso="//c.uvokchee.de/" data-isso-require-author=true data-isso-vote=false src="//c.uvokchee.de/js/embed.min.js"></script> <section id="isso-thread"> {% if jekyll.environment == "development" %} {% include testc.html %} {% endif %} </section> + + <h3>Fediverse</h3> + <script type="module"> + import Comments from '{{ "/assets/mscomm/comments.js" | relative_url }}' + export function loadComments() { + document.getElementById('load-comments-btn').remove(); + customElements.define('oom-comments', Comments); + } + window.loadComments = loadComments; + </script> + <link rel="stylesheet" href="{{ "/assets/mscomm/styles.css" | relative_url }}?v={{ site.cssversion }}"> + <oom-comments src="{{ src_url }}"> + <button id="load-comments-btn" class="load-comments-btn" onclick="loadComments()">Load Comments from + ActivityPub</button> + </oom-comments> + <p style="margin-top: 1em"><a href="{{ src_url }}">ActivityPub-Link</a></p> + <noscript> <p>Um Kommentare zu hinterlassen, ist leider JavaScript nötig.</p> - <p>Unfortunately, JavaScript is required to leave comments</p> + <p>Unfortunately, JavaScript is required to leave comments.</p> </noscript> </section> diff --git a/_includes/header.html b/_includes/header.html index 492dcd5..86e812e 100644 --- a/_includes/header.html +++ b/_includes/header.html @@ -13,7 +13,6 @@ </label> <div class="trigger" lang="en"> {% include_cached navlinks.html %} - <a class="page-link" href="https://uvokchee.de/wiki/">Wiki</a> <a class="rss-subscribe page-link" href="{{ "/feed.xml" | relative_url }}">RSS feed</a> </div> </nav> diff --git a/_layouts.gemini/default.html b/_layouts.gemini/default.html new file mode 100644 index 0000000..cddd070 --- /dev/null +++ b/_layouts.gemini/default.html @@ -0,0 +1 @@ +{{ content }} diff --git a/_layouts.gemini/home.html b/_layouts.gemini/home.html new file mode 100644 index 0000000..4e877fb --- /dev/null +++ b/_layouts.gemini/home.html @@ -0,0 +1,16 @@ +--- +layout: default +--- + +{{ content }} + + {%- if site.posts.size > 0 -%} + {%- for post in site.posts -%} + {%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%} + <span class="post-meta">{{ post.date | date: date_format }} + {%- if post.lang == 'de' %} (DE){%- endif -%} + <a class="post-link" href="{{ post.url | relative_url }}"> + {{ post.title | escape }} + </a> + {%- endfor -%} + {%- endif -%} diff --git a/_layouts.gemini/page.html b/_layouts.gemini/page.html new file mode 100644 index 0000000..dda953c --- /dev/null +++ b/_layouts.gemini/page.html @@ -0,0 +1,9 @@ +--- +layout: default +--- + <h1 class="post-title">{{ page.title | escape }}</h1> + + <div class="post-content"> + {{ content }} + </div> + diff --git a/_layouts.gemini/post.html b/_layouts.gemini/post.html new file mode 100644 index 0000000..d3a93ae --- /dev/null +++ b/_layouts.gemini/post.html @@ -0,0 +1,19 @@ +--- +layout: default +--- + <h1 class="post-title p-name" itemprop="name headline">{{ page.title | escape }}</h1> + <p class="post-meta"> + <p> + {%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%} + {%- assign date_format = "%A, %e. %B %Y, %k:%M" -%} + Published: {{ page.date | date: date_format }} + + {%- if page.last_modified_at -%} + , Updated: {{ page.last_modified_at | date: date_format }} + {%- endif -%} + </p> + + </p> + <div class="post-content e-content" itemprop="articleBody"> + {{ content }} + </div> diff --git a/_layouts.gemini/postlist.html b/_layouts.gemini/postlist.html new file mode 100644 index 0000000..7be4784 --- /dev/null +++ b/_layouts.gemini/postlist.html @@ -0,0 +1,63 @@ +--- +layout: default +--- + +<p> + This Gemini site has been converted without all-to-much effort from + my Jekyll blog, which uses Markdown as input. + As the conversion process Markdown > HTML > Markdown > Gemini is lossy, + the result looks somewhat unsatisfactory. + I haven't found a good conversion process that saves me the effort of + manually rewriting everything. If you know something, please let me know. +</p> + +<p>Navigation</p> + + {% assign spag = site.pages | sort: "order" %} + {% for my_page in spag %} + {% if my_page.title and my_page.content and my_page.url != "/index.html" and my_page.in_navbar %} +<a href="{{ my_page.url | relative_url }}">{{ my_page.title | escape }}</a> + {% endif %} + {% endfor %} +<a href="/feed.gmi">Feed link</a> + + {%- if page.title -%} + <h1 class="page-heading">{{ page.title }}</h1> + {%- endif -%} + + {%- if site.posts.size > 0 -%} + + {% assign paginator = site %} + + {%- for post in paginator.posts -%} + {%- assign showsep = false -%} + {%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%} + {%- if post.lang == 'de' %} (DE) {%- endif -%} + <div lang="{{ post.lang | default: site.lang | default: "en" }}"> + <h2> + {{ post.title | escape }} + </h2> + <span class="post-meta">Published: {{ post.date | date: date_format }}</span> +<p> + {%- if forloop.first and paginator.page == 1 or paginator == site -%} + {{ post.content }} + {%- assign showsep = true -%} + {%- elsif site.show_excerpts -%} + {{ post.excerpt }} + {%- assign showsep = true -%} + {%- elsif post.description -%} + {{ post.description }} + {%- assign showsep = true -%} + {%- endif -%} +</p> + <a class="post-link" href="{{ post.url | relative_url }}">Link to full post</a> + + </div> + {%- if showsep -%} + <hr class="postsep" /> + {%- endif -%} + {%- endfor -%} + + {%- endif -%} + +</div> diff --git a/_layouts/postlist.html b/_layouts/postlist.html index 67a1d48..b6b29ef 100644 --- a/_layouts/postlist.html +++ b/_layouts/postlist.html @@ -39,6 +39,9 @@ layout: default {%- elsif site.show_excerpts -%} {{ post.excerpt }} {%- assign showsep = true -%} + {%- elsif post.description -%} + {{ post.description }} + {%- assign showsep = true -%} {%- endif -%} </div> </li> @@ -56,9 +59,14 @@ layout: default <script> document.addEventListener("DOMContentLoaded", function() { - const ids = [ "AdBlock", "news1" ]; + {%- assign news_ids = site.data.news | map: 'id' | jsonify %} + + const ids = {{ news_ids }}; for (currId of ids) { const currentBanner = document.getElementById(currId); + if (!currentBanner) { + continue; + } const closeLink = currentBanner.firstElementChild; const currentNewsClosed = localStorage.getItem(currId+"closed"); if (currentNewsClosed === "true") { diff --git a/_posts/2009-02-07-techtalk-einen-gameboy-auf-dem-pc.md b/_posts/2009-02-07-techtalk-einen-gameboy-auf-dem-pc.md index 2c556d6..f02d62a 100644 --- a/_posts/2009-02-07-techtalk-einen-gameboy-auf-dem-pc.md +++ b/_posts/2009-02-07-techtalk-einen-gameboy-auf-dem-pc.md @@ -103,19 +103,13 @@ UPDATE (30.4.2009): ### Emulatoren -- <a href="https://de.wikipedia.org/wiki/Emulator">Emulator</a> auf -Wikipedia -- <a href="https://emulator-zone.com/">Emulator Zone</a> Eine -Übersicht über Spielkonsolenemulatoren. -- _VisualBoyAdvance (toter Link entfernt)_ -Homepage des Emulators "VisualBoyAdvance" +- <a href="https://de.wikipedia.org/wiki/Emulator">Emulator</a> auf Wikipedia +- <a href="https://emulator-zone.com/">Emulator Zone</a> Eine Übersicht über Spielkonsolenemulatoren. +- _VisualBoyAdvance (toter Link entfernt)_ Homepage des Emulators "VisualBoyAdvance" - [SourceForge-Projektseite vom Emulator](https://sourceforge.net/projects/vba/) ### Spiele -- <a href="https://de.wikipedia.org/wiki/ROM_%28Spielmodul%29">ROM</a> -auf Wikipedia -- _PDRoms / Gameboy (toter Link entfernt)_ -Legale ROMs für den GameBoy -- _PDRoms / GameBoy Advance (toter Link entfernt)_ -Legale ROMs für den GameBoy Advance +- <a href="https://de.wikipedia.org/wiki/ROM_%28Spielmodul%29">ROM</a> auf Wikipedia +- _PDRoms / Gameboy (toter Link entfernt)_ Legale ROMs für den GameBoy +- _PDRoms / GameBoy Advance (toter Link entfernt)_ Legale ROMs für den GameBoy Advance diff --git a/_posts/2021-03-23-winvm.md b/_posts/2021-03-23-winvm.md index 528d636..3ee73f4 100644 --- a/_posts/2021-03-23-winvm.md +++ b/_posts/2021-03-23-winvm.md @@ -4,6 +4,7 @@ title: Windows VM under Devuan with QEMU / libvirt date: 2021-03-23 20:32 +0100 categories: tech description: How to run a Windows VM under Devuan with QEMU / libvirt +lang: en --- So, I'm running Devuan Beowulf (Debian Buster based). Recently, I was in the need of running a diff --git a/_posts/2021-04-25-networkstuff.md b/_posts/2021-04-25-networkstuff.md index 3152f3f..7b2cff6 100644 --- a/_posts/2021-04-25-networkstuff.md +++ b/_posts/2021-04-25-networkstuff.md @@ -4,6 +4,7 @@ title: Network stuff date: 2021-04-25 21:50 +0200 categories: tech description: "I do some experiments with networking stuff. Proxmox and running OpenWRT in a VM." +lang: en --- Been a long time. diff --git a/_posts/2021-06-03-fakir-teardown.md b/_posts/2021-06-03-fakir-teardown.md index 1de8bf9..75f609d 100644 --- a/_posts/2021-06-03-fakir-teardown.md +++ b/_posts/2021-06-03-fakir-teardown.md @@ -3,6 +3,7 @@ layout: post title: Fakir Robert Teardown date: 2021-06-03 20:39 +0200 description: Ein Teardown eines alten Staubsauger-Roboters +lang: de --- Da ich mir demnächst mal einen neuen gebrauchten Saugroboter holen werde, diff --git a/_posts/2021-06-27-wpa-enterprise-unifi.md b/_posts/2021-06-27-wpa-enterprise-unifi.md index 61c49a7..36b9e6e 100644 --- a/_posts/2021-06-27-wpa-enterprise-unifi.md +++ b/_posts/2021-06-27-wpa-enterprise-unifi.md @@ -4,6 +4,7 @@ title: WPA Enterprise mit Unifi Access Points date: 2021-06-27 20:28 +0200 categories: tech description: "Wie man WPA Enterprise einrichtet, mit Unifi WiFi Access Points und Software, die unter Linux läuft" +lang: de --- Ich hab mal einen kurzen Artikel geschrieben, wie man WPA Enterprise diff --git a/_posts/2021-09-22-problems-updating-proxmox.md b/_posts/2021-09-22-problems-updating-proxmox.md index 3885b79..7353d67 100644 --- a/_posts/2021-09-22-problems-updating-proxmox.md +++ b/_posts/2021-09-22-problems-updating-proxmox.md @@ -2,6 +2,7 @@ layout: post title: Network gone after updating Proxmox date: 2021-09-22 20:59 +0200 +lang: en --- So, after upgrading Proxmox from version 6.x to 7.x, the network diff --git a/_posts/2021-10-28-rant-devuan-upgrade.md b/_posts/2021-10-28-rant-devuan-upgrade.md index 100dd3b..c3da9b0 100644 --- a/_posts/2021-10-28-rant-devuan-upgrade.md +++ b/_posts/2021-10-28-rant-devuan-upgrade.md @@ -3,6 +3,7 @@ layout: post title: 'Rant: Devuan Upgrade' date: 2021-10-28 19:58 +0200 categories: tech +lang: en --- Gna. So I upgraded my Devuan installation from Beowulf to Chimaera. Process was a bit tricky, diff --git a/_posts/2022-01-20-server-stuff.md b/_posts/2022-01-20-server-stuff.md index 40d39a1..f5f053f 100644 --- a/_posts/2022-01-20-server-stuff.md +++ b/_posts/2022-01-20-server-stuff.md @@ -3,7 +3,7 @@ layout: post title: Server stuff date: 2022-01-20 18:55 +0100 category: tech -language: en +lang: en description: "I experiment with SPF, DMARC and DKIM." --- diff --git a/_posts/2022-04-03-backporting-pipewire-in-devuan.md b/_posts/2022-04-03-backporting-pipewire-in-devuan.md index 1d20df2..c0a9fe3 100644 --- a/_posts/2022-04-03-backporting-pipewire-in-devuan.md +++ b/_posts/2022-04-03-backporting-pipewire-in-devuan.md @@ -3,7 +3,7 @@ layout: post title: Backporting Pipewire in Devuan date: 2022-04-03 17:45 +0200 category: tech -language: en +lang: en --- **Update 2023-09-07**: With Devuan Daedelus Pipewire is in the diff --git a/_posts/2024-08-02-use-a-second-signal-account-kinda.md b/_posts/2024-08-02-use-a-second-signal-account-kinda.md index 90b7687..a9753ba 100644 --- a/_posts/2024-08-02-use-a-second-signal-account-kinda.md +++ b/_posts/2024-08-02-use-a-second-signal-account-kinda.md @@ -2,6 +2,7 @@ layout: post title: Use a second Signal account (kinda) date: 2024-08-02 18:48 +0200 +last_modified_at: 2025-05-29 16:50 +0200 lang: en categories: ["tech"] description: "How to use a second Signal account on your Smartphone" @@ -52,3 +53,9 @@ already, so not a huge disadvantage). But hey, maybe this is still useful for some people. +**Update 2025-05-29** + +Actually, you can also use multiple accounts on the Desktop client. +Since it's just an Electron app (?), you only need a second Launcher +with the executable and arguments being +`/opt/Signal/signal-desktop --user-data-dir=<whatever you want> %U`. diff --git a/_posts/2024-09-04-convention-report-east-12-2024.md b/_posts/2024-09-04-convention-report-east-12-2024.md index 7c71898..31785a8 100644 --- a/_posts/2024-09-04-convention-report-east-12-2024.md +++ b/_posts/2024-09-04-convention-report-east-12-2024.md @@ -3,6 +3,7 @@ layout: post title: 'Convention Report: EAST 12 (2024)' date: 2024-09-04 20:00 +0200 description: My convention report of the furry convention "EAST 12" in 2024 +lang: en --- It's time for another convention report. I've been going to the EAST convention since EAST 7 (except EAST 10, which was diff --git a/_posts/2025-01-01-2024-review.md b/_posts/2025-01-01-2024-review.md index 5d0d433..80cf09d 100644 --- a/_posts/2025-01-01-2024-review.md +++ b/_posts/2025-01-01-2024-review.md @@ -4,6 +4,7 @@ title: '2024: Review' date: 2025-01-01 10:47 +0100 lang: "en" categories: "life" +description: "I muse over what I did in 2024" --- So, already wanted to do this yesterday, but I think today works as well. diff --git a/_posts/2025-01-01-my-static-blog-now-has-activitypub.md b/_posts/2025-01-01-my-static-blog-now-has-activitypub.md index 62feced..1a5af4d 100644 --- a/_posts/2025-01-01-my-static-blog-now-has-activitypub.md +++ b/_posts/2025-01-01-my-static-blog-now-has-activitypub.md @@ -5,6 +5,7 @@ date: 2025-01-01 18:56 +0100 last_modified_at: 2025-01-01 19:34 +0100 categories: ["tech"] lang: "en" +description: "A short praise for Hatsu, thanks to which my posts now appear in the Fediverse." --- Thanks to [Hatsu](https://github.com/importantimport/hatsu), my blog now has ActivityPub support. diff --git a/_posts/2025-01-12-migrating-dns-servers.md b/_posts/2025-01-12-migrating-dns-servers.md new file mode 100644 index 0000000..a0bf09b --- /dev/null +++ b/_posts/2025-01-12-migrating-dns-servers.md @@ -0,0 +1,289 @@ +--- +layout: post +title: Migrating DNS providers +lang: en +categories: tech +date: 2025-01-12 16:49 +0100 +last_modified_at: 2025-01-15 19:57 +0100 +redirect_from: /2025/01/2025-01-12-migrating-dns-servers.html +description: "An in-depth look into how to switch DNS providers without downtime." +--- + +**Update 2025-01-15:** Add links to tools, add clarification for DNSSEC tool, +add clarification for validation errors/warnings. + +## Preface + +[As I posted on Mastodon](https://furry.engineer/@uvok/113780013806190576), + +* the DNS registrar (where you register the domain at), +* the DNS hoster / provider (which is responsible for answering DNS requests for + your domain) and +* the (web) hosting service + +can be three different entities, even though for most beginner projects these +are the same. + +I either bought or migrated my domain over to [INWX](https://www.inwx.de/en) +quite some while ago. However, their web interface for editing records is a bit +clunky. And while they do provide an API, it's a bit awkward (XML based, and +yes, there are ready-made packages for various programming languages). [^1] + +With [DN42](https://blog.uvokchee.de/dn42.html), I already used +[PowerDNS](https://doc.powerdns.com/authoritative/) a lot. I actually just use +edit-zone and edit the zone[^2] in an editor, that's my preferred way. Definitely +preferred to some web interface. It would be nice to do this to my main domains +as well. Also, this is another nice "have a technical task to procrastinate +other stuff" thing ;3. Well, I like playing with tech. This is by no means +essential. + +## Glossary + +Quick and dirty glossary, for the purpose of reading this article. It's not +100% accurate and complete, but should be enough to understand the article. + +* **Domain Name**: Basically "the name of a server" (what you see in the address + bar of the browser, e.g. "blog.uvokchee.de"). +* **DNS**: The "Domain Name System". Very simplified, translates domain names + to IP addresses (which computers use to connect to each other). + To be more precise, it's like a lookup in a database (-> DNS record). +* **TLD**: Top-Level Domain. It's "the last part of the domain name" (e.g. + ".de"). +* **Registry**: These run the TLDs. +* **Registrar**: These sell you domain names from the registries. As an "end + user", you can't talk to a registry yourself. +* **DNSSEC**: DNS is a plain-text, insecure protocol, from way back when the + internet consisted of a handful of trustworthy people. DNSSEC tries to prevent + malicious parties from sending wrong information in response to DNS requests. + It does so by signing the responses with a cryptographic key, of which the + public part is stored in the registry. +* **NSEC, NSEC3**: Basically these are "negative replies", i.e. "this domain + does not exist". These are signed as well. If not present, malicious parties + could send back replies to sites they don't want to be reachable as "doesn't + exist" all the time. +* **Zone**: A zone file contains all the -> DNS records for a specific domain. +* **DNS record**: Like "a row in the database" which belongs to your domain. Can + be an address, but also things like text, PGP keys, pointer to other domain + names, etc.. +* **TTL**: Time To Live. The time (in seconds) a reply to a DNS request may be + cached by a server. For example, a TTL of 3600 seconds means that a (caching) + DNS server can cache a record for 1 hour before checking with the + -> authoritative server again. +* **Authoritative Server:** The DNS server that holds the official and most + up-to-date records for a specific domain. + * **Primary**: The primary source of truth. + * **Secondary**: Mirrors the information from the primary. There can be more + than one. +* **Recursion**: DNS is hierarchical. There are fixed "root servers" which serve + the nameservers for all the TLDs. The TLDs provide the nameservers for the + domains "below them". A recursive server "walks along this path" to answer + requests. At some point, it arrives at the authoritative server. +* **Bogus reply**: A reply with invalid signature, either because it has been + tampered with, or because of wrong configuration. +* **AXFR**: A request type for a -> Domain zone transfer. +* **Domain Zone Transfer**: The process of copying the contents of a zone from + one DNS server to another. + +## Migration and DNSSEC + +So, why not migrate my domain over to my PowerDNS setup? Well, I want some +availability, and I don't trust myself enough not to fuck things up. When my +server is shut down, DNS requests should still be answered. But hey, that's +where secondaries come in! So, I manage the zone on my server, as primary, and +let another provider mirror its contents. These will actually "serve the zone". + +Now, only… there's the "problem" of DNSSEC, in conjunction with the DNS +propagation time and TTLs. If I simply set the nameservers (in the de zone) to +a newly created zone by PowerDNS, the DNSSEC keys will be either not present, +or different. I can't set *additional* keys at INWX easily, I can only switch +to manual keys (by switching off DNSSEC first). Which would lead to a downtime +of up to one day, because people requesting records from my domain (such as +myself) would receive bogus replies in that time. I want to avoid that. + +## Steps for migration + +But, I think I found a solution for that. This are the steps which I had to take +for the registrar INWX and my uvokchee.de domain. The process might be different +for other TLDs and other registrars. + +1. Download the zone data from the INWX web interface +1. Create the zone in PowerDNS and set various settings + + #!/bin/bash + zone="example.com" + pdnsutil create-zone $zone + pdnsutil set-kind $zone primary + pdnsutil secure-zone $zone + pdnsutil set-presigned $zone + pdnsutil set-meta $zone ALLOW-AXFR-FROM <ip of secondaries> + # optional? see below for a discussion + pdnsutil set-nsec3 $zone $parameters +1. Filter and import the zone. PowerDNS will actually complain about the NSEC3 + records otherwise once you open the zone in your editor. + + # any other regex that filters only NSEC3 records, but not RRSIG NSEC3, + # work as well + grep -vw "IN NSEC3" ./dl-zone.txt > imp-zone.txt + pdnsutil load-zone $zone ./imp-zone.txt + + This should keep the RRSIGs for the NSEC3 records. This doesn't help with + the errors I receive later, though… + +1. Set up the secondaries. I went with [Hurricane + Electric](https://dns.he.net/), but I had to write them an e-mail. I couldn't + add the zone as secondary myself, because the web interface requires the + nameservers in the parent zone (?) already to be set to HE, but I wanted a + zero-downtime migration. +1. Wait for the DNS provider to AXFR. +1. (optional) check with [dnsviz](https://dnsviz.net/) whether you get any + errors. You should set the nameserver on the "Analyze" tab, and then set the + "Additional trusted keys:" to what `dig example.com DNSKEY` tells you. At + this point, I got warnings/errors regarding the "Denial of existence" / + NSEC3 records, and some errors because the RRSIGs can't be checked. (Once + you set a manual nameserver, the tool doesn't seem to check for the parent + zone anymore? I'm unclear on that). +1. Try to set the new nameservers (nsx.he.net) in the registry (e.g. DENIC) via + the INWX web interface ("external nameservers"). +1. Receive an "UPDATE FAILED". Read the error message. + + ERROR: 53300102912 Nameserver error [ERROR: 118 Inconsistent set of NS + RRs (NS, IP, NS host names) (ns5.he.net, 2001:470:500::2, ['ns.inwx.de', + 'ns2.inwx.de', 'ns3.inwx.eu'])] + + Realize you fucked up. + You need to set the new nameservers within your own zone first. This seems + to be a requirement for DENIC at any case. +1. Add the NS entries to the HE nameservers within your zone in the INWX web + interface. [^4] +1. Download and import the zone file again. Wait for the AXFR. +1. Retry sending the nameservers to the registry. Wait until success. +1. At this point, I checked again with dnsviz. With the default options, + everything was fine. However, when I enabled the "Denial of existence:" + option, I received some *extremely scary looking error messages*. I can only + guess this is because PowerDNS can't generate these records? I *have no + clue*. Any useful information will be gladly accepted. I choose to ignore it. + + NSEC3 proving non-existence of l2v1y.7t5is.uvokchee.de/A: No RRSIG covering the RRset was returned in the response. See RFC 4035, Sec. 3.1.1. (216.218.130.2, 216.218.131.2, 216.218.132.2, 2001:470:100::2, 2001:470:200::2, 2001:470:300::2, UDP_-_EDNS0_4096_D_KN) + NSEC3 proving non-existence of uvokchee.de/CNAME: No RRSIG covering the RRset was returned in the response. See RFC 4035, Sec. 3.1.1. (216.218.130.2, 216.218.131.2, 216.218.132.2, 2001:470:100::2, 2001:470:200::2, 2001:470:300::2, UDP_-_EDNS0_4096_D_KN) + + As of 2015-01-11, I read the [PowerDNS + docs](https://doc.powerdns.com/authoritative/dnssec/migration.html#from-existing-dnssec-non-powerdns-setups-pre-signed) + again. Specifically, I need to set `pdnsutil set-nsec3 $zone <whatever + NSEC3PARAM says>`. Then, PowerDNS gives back SERVFAILs, because, of course, + PowerDNS can't sign the reply, because it doesn't have the keys to sign the + NSEC3 replies (??? I guess ???). So now, instead of sending back an unsigned + reply, it sends back an error. + + I imported the RRSIGs for these NSEC3 records, though, at least in a second + attempt. I tried all combinations of `(set-nsec3|unset-nsec3)` and `(import + NSEC3-RRSIG|don't import NSEC3-RRSIG)`. All variants failed. I have no clue + how this is supposed to work cleanly. [^7] + + **Update 2025-01-15**: The GitHub issue linked below (#9263) actually + contains a "solution"/workaround for this. The order of commands needs to be + 1) Import, 2) set-nsec3, 3) rectify, 4) set-presigned. + +1. Wait at least 24 hours (TTLs, DNS propagation time). +1. Let PowerDNS output its own keys it generated for the zone. Unfortunately, + `pdnsutil export-zone-dnskey $zone $keynr` *does not output a completely + valid record*, nor does `pdnsutil export-zone-ds $zone`. These outputs are + missing the TTLs in the second column, at least with PowerDNS 4.7.3 in the + Debian stable repos. You have to add those yourself. I saw some tools like + dnsviz break when you enter the records as-is. + + **Important**, I had to temporarily run `unset-presigned $zone` (see + below) before running `pdnsutil export-zone-ds`, so PowerDNS actually outputs + the hash of the new key(s)! Otherwise it will only show the current keys + signature. Otherwise, you may also use online tools that convert the DNSKEY + to a DS record. +1. Have a copy of the *current* DS / DNSKEY records as well (`dig` is your + friend). +1. Set the DNSSEC from "auto" to "manual" in the INWX web interface. + * Delete all keys from the domain. + * Add DNSSEC again for the domain, in manual mode. + * Add DNSKEY and DS records of the old keys. + * Add key, insert DNSKEY and DS records emitted by pdnsutil for the new + key. + + As far as I know, this will be sent to the parent zone. [^8] +1. After I got an email from INWX confirming the new DNSSEC entries, I ran + dnsviz again and nearly got an heart attack, because there were a lot of + errors and red exclamation marks. Actually, the DNSSEC was still valid (i.e. + not returning bogus records). The tool only complained about + + The DS RRset for the zone included algorithm 13 (ECDSAP256SHA256), but no + RRSIG with algorithm 13 covering the RRset was returned in the response. + + I *think* dnsviz expects both `RRSIG`s, i.e. both keys must sign the replies? + That shouldn't have an effect on the reachability, otherwise the tool would + report BOGUS records? + + **Update 2025-01-15**: Yep. As stated in the IETF document linked in the + references: + + > Current reading is that an algorithm rollover requires a full validation + > with all algorithms involved, whereas a key rollover will work whenever + > data can be validated using either key ([RFC4035], section 2.2). + + N.B.: So if you *really* want to avoid that, you need to tell PowerDNS to + create an KSK/ZSK pair (with the same parameters?) and use these, and only + later switch over to algorithm 13. +1. Wait at least 24 hours (TTLs, DNS propagation time). - Depending on the + previous DS records TTL. +1. Switch to automatic signing by PowerDNS. I guess at this point you must or should + * stop PowerDNS, [^5] + * `pdnsutil unset-presigned $zone`, + * `pdnsutil unset-nsec3 $zone`, + * edit the zone (clearing out the RRSIGs you imported. If I understood + the PowerDNS docs correctly, there will be trouble [^6] if you don't. Oh, + don't forget the NSEC3PARAM record, any leftover (C)DNSKEYS and (C)DS, + while also increasing the serial (otherwise the AXFR might not take + place), [^9] + * `pdnsutil rectify-zone $zone` + * start PowerDNS, + * (optional) notify the secondaries +1. At this point, you should be done. And I *guess* at this point you could + already delete the other DS/DNSKEY via the registrar? + +By the way, I'll choose to use NSEC instead of NSEC3. NSEC3 seems like too much +of a headache for me, also, domain names are not secrets. + +## Further references + +* [PowerDNS docs](https://doc.powerdns.com/authoritative/) +* IETF Draft: [Changing DNS Operators for DNSSEC signed + Zones](https://datatracker.ietf.org/doc/html/draft-koch-dnsop-dnssec-operator-change-06) +* [DNSSEC visualizer](https://dnsviz.net/) +* [Verisign Labs DNSSEC debugger](https://dnssec-debugger.verisignlabs.com/) + +## Footnotes + +[^1]: Okay, it's not like I edit my records that often. + +[^2]: + For the purposes of this blog article, I think you can freely substitute + zone with domain in your head, I probably won't use the words correctly 100% of + the time either. + +[^4]: + I think that shouldn't to any damage. DNS recursion should always ask the parent + zone? + +[^5]: + Avoiding a spurious notify or AXFR directly after editing? No idea if needed. + +[^6]: such as duplicate RRSIG replies. + +[^7]: + There are two GitHub issues similar to this: + [#9263](https://github.com/PowerDNS/pdns/issues/9263) and + [#8892](https://github.com/PowerDNS/pdns/issues/8892). + +[^8]: + Actually, I have no idea why INWX wants me to enter both the DS and the + DNSKEY. Only the DS gets entered in the DENIC servers. + +[^9]: + e.g. `:g/\<\(RRSIG\|NSEC3PARAM\|DS\|CDS\|DNSKEY\|CDNSKEY\)\>/d` in vim. +<!-- vim: set ft=markdown tw=80 ai tabstop=4 shiftwidth=4 expandtab: --> + diff --git a/_posts/2025-02-02-dn42-put-it-in-a-box-linux-network-namespace.md b/_posts/2025-02-02-dn42-put-it-in-a-box-linux-network-namespace.md new file mode 100644 index 0000000..3a43f1a --- /dev/null +++ b/_posts/2025-02-02-dn42-put-it-in-a-box-linux-network-namespace.md @@ -0,0 +1,134 @@ +--- +layout: post +title: 'DN42: Put it in a box (Linux network namespace)' +date: 2025-02-02 17:10 +0100 +last_modified_at: 2025-06-18 19:12 +0200 +lang: "en" +categories: "tech" +description: "I explain how I put my Autonomous System in a network namespace." +--- + +What did I do the previous week? What robbed me of my sleep at night, keeping my +thoughts churning? Let's look at "doing containers the hard way". + +For some time, I had CPU usage problems on my VPS. Looking at `htop`, I always +saw two bird instances and dnsmasq as "offenders". (I run both my clearnet AS as +well as my DN42 AS on this box) [^1]. I was originally going to write a blog article +about that, but it's still in my drafts folder. Here's the gist: + +- All these processes receive "netlink messages". +- Probably the IBGP session for my AS generates lots of these netlink messages. +- The other bird process (for DN42) and dnsmasq suffer from it. + +One possible solution is to put the processes into a container (Docker, Podman, +LXC, LXD, Incus, …). However, I'm on a budget. I have only 40 GB of storage on +my Hetzner VPS and don't want to waste it with duplicate file systems. However, I +can use the *technology* these container (runtimes/engines) use: Linux +namespaces. + +I won't cover the basics here. There are other blog articles from other people +who are *far better* at explaining that (e.g. from +[anracon](https://linux-blog.anracom.com/2017/10/30/fun-with-veth-devices-linux-bridges-and-vlans-in-unnamed-linux-network-namespaces-i/)). + +Suffice to say, I can put DN42 in a box, which put the network in isolation. To +a degree. I still need to communicate with the outside world. I dumped my setup +[in a git repo](https://git.uvok.de/ansible/tree/roles/linux-ns/files). + +[jamesits](https://github.com/Jamesits/systemd-named-netns) provides some nice +templates for setting up a network namespace. I used that template, but tweaked +some parts to they suit me better [^2]. Also, using the Debian systemd files, I +manually created new ones and made sure the processes use the separate namespace. +I admit to never having particularly liked systemd, but at least the service +manager is pretty nice. + +I also felt pretty clever coming up with [this setup +script](https://git.uvok.de/ansible/tree/roles/linux-ns/files/usrlocalbin/dn42-route-namespace.sh) +which gets called by systemd and then calls itself again, but in the newly created +namespace. + +Inside the namespace run: + +- The Wireguard interfaces to my peers, +- Tinc (which I only have because I want to have a broadcast-capable VPN), +- PowerDNS (a separate instance from "the outer VPS one", serving my DN42 domain), +- BIRD2, +- And a looking glass + +*Outside* the namespace run: + +- dnsmasq, which allows me to resolve my DN42 domains "from the clearnet" + (from within a Wireguard net) +- Nginx, which serves my DN42 website + +**Update 2025-06-18**: nginx now runs inside the namespace as well. + +It took me a while and some internet searches to come up with the firewall +rules. On my VPS itself I use ufw, for the network namespace, I *could probably* +make this work as well, but I decided to use "iptables", or rather, the wrapper +scripts which provide the same syntax but use nftables in the background. This +is because with this namespace separation, I don't have to worry about +"cross-leaking" network traffic between DN42 and the clearnet. So the rules get +a lot simpler + +I was happy that both DNS and the website work, when I realized that the default +policy for FORWARD was ALLOW. I found it suitable to set it to DROP, and +suddenly, it didn't work anymore, because I was employing destination NAT: + +``` +*nat +-A PREROUTING -d fd3e:bc05:2d6::80/128 -p tcp --dport 80 -j DNAT --to-destination fcee::1 +-A PREROUTING -d fd3e:bc05:2d6::80/128 -p tcp --dport 443 -j DNAT --to-destination fcee::1 +``` + +This is because the webserver is running on the "outer" VPS, not in the network +namespace (hey, it took me long enough to finish this up as it is). This is +probably a relatively small adjustment. + +With an additional forward rule, everything is happy again: + +``` +*filter +-A FORWARD -s fd00::/8 -d fcee::1/128 -j ACCEPT +-A FORWARD -s fcee::1/128 -d fd00::/8 -j ACCEPT +``` + +**Update 2025-06-18**: I modified this several times since then. +Since I also need to take care of actual routing/forwarding within DN42, +I now ended up with + +``` +-A FORWARD -s fd00::/8 -d fd00::/8 -j ACCEPT +-A FORWARD -i eth0 -d fd00::/8 -j ACCEPT +-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT +``` + +i.e. I don't rely on the source address being fcee::1 anymore. + +Also, I also feel pretty clever for making sure I can access DN42 from my +clearnet: + +``` +*mangle +-A PREROUTING -i eth0 -j MARK --set-mark 0x4242 +COMMIT + +*nat +-A POSTROUTING -d fd00::/8 -m mark --mark 0x4242 -j MASQUERADE +COMMIT +``` + +I am actually not sure if the latter needs an "-i (all wg interfaces)" or "! -i +(internal interfaces)" to avoid unnecessary NATting for when I access a service +in the namespace. But nevertheless, I achieved a working state. Best of all, I +can reuse some of the work if I ever want to put my clearnet AS in a namespace, +too. + +[^1]: + An AS is in ["autonomous system"]({% post_url + 2023-08-18-networking-adventure-my-own-ipv6-prefix-and-as %}), [DN42]({% link + dn42.md %}) is a "lab environment" for networking. + +[^2]: + For example, I have no idea why they unmount and mount the netns path + manually. Probably this fixes some bug of an old systemd version? + diff --git a/_posts/2025-03-06-monitoring-with-uptime-kuma.md b/_posts/2025-03-06-monitoring-with-uptime-kuma.md new file mode 100644 index 0000000..0d1a6d1 --- /dev/null +++ b/_posts/2025-03-06-monitoring-with-uptime-kuma.md @@ -0,0 +1,49 @@ +--- +layout: post +title: Monitoring with Uptime Kuma +date: 2025-03-06 18:55 +0100 +lang: en +description: "A short praise for Uptime Kuma, offering uptime monitoring." +--- + +[Recently](https://furry.engineer/@uvok/114026509150081909), I wanted to do add +some kind of monitoring to my servers. The prevalent reason was monitoring my +AS, which I accidentally broke by shutting down one server, or the hosting +provider had an outage (This was because I didn't configure a fallback default +route, but that's another story). + +So, I took a look at the monitoring landscape. And immediately wanted to drop +the idea again, because there were too many choices. And had no intention to +install and test them all, and no energy to read dozens of websites with user +reports. + +But then I said, "wait. Let's not do it the overthinking way again". So I took +the first best/simple thing and chose [Uptime +Kuma](https://github.com/louislam/uptime-kuma). I actually wanted to drop it +right away once I saw it's running on NodeJS (ehhh). But then I decided to spin +up an LXC on my Proxmox anyway. + +And lo and behold, it works: + +{% image +img="https://pics.uvokchee.de/_data/i/upload/2025/03/06/20250306175521-4763fdf7-me.png" +alt="Screenshot of Uptime Kuma - showing the ping times of various servers." +%} + +And it even has a certificate validity check. + +{% image +img="https://pics.uvokchee.de/_data/i/upload/2025/03/06/20250306175521-7805c0bb-me.png" +alt="Screenshot of Uptime Kuma - showing the HTTPS monitor, including the +remaining certificate validity time." +%} + + +It runs in my home network, so I can actually detect whether my AS is reachable +(to a degree). And it brings various notification options, from which I chose +Telegram and Matrix. + +I actually got an outage notification this week, on my Hetzner servers. No idea +what happened there, though, I didn't receive an outage report / notification +from them. + diff --git a/_posts/2025-03-18-games-i-play_ed.md b/_posts/2025-03-18-games-i-play_ed.md new file mode 100644 index 0000000..9e2c805 --- /dev/null +++ b/_posts/2025-03-18-games-i-play_ed.md @@ -0,0 +1,112 @@ +--- +layout: post +title: Computer games I (used to) play +lang: en +categories: game +date: 2025-03-18 10:17 +0100 +description: "I muse over computer games I played recently(-ish)." +--- + +I'm not big on computer games. Usually "I don't have the time" to deal with +them. Or rather, spending so much time on the computer at work, I'm quickly fed +up with sitting on the computer. Also, I get frustrated quickly at times, when +I can't make progress or repeatedly fail. So I usually only play computer games +on vacation. + +So, as for games I played (recently-ish)? Roughly sorted by time last played +ascending: + +- Waaaaaaaaaaaaaaay back in my childhood (in no specific order): + - [Civilization](https://archive.org/details/msdos_sid_meier_civilization) + [^civ1], [^civ2]. Yes, the original one! If I remember correctly, I always + got destroyed sooner or later… - [FreeCiv](https://www.freeciv.org/) is an + open source game inspired by it. + - [3D Pinball Space Cadet](https://alula.github.io/SpaceCadetPinball/). I + spent hours on that one. Never quite succeeding on hitting the targets… + - Anno 1602. I think the first game I searched for cheats to improve my + chances… - [Unknown Horizons](https://unknown-horizons.org) is a similar + game in open source. [^unkhor] + - SimCity 2000 [^simcit] - + [LinCity-NG](https://github.com/lincity-ng/lincity-ng) is a similar open + source game. + - Need For Speed III + - Empire Earth (quite like Age Of Empires, as I found out later) - As far as + open source goes, [0 A.D.](https://play0ad.com/) is probably the closest? + - "Die Völker" (apparently, "Alien Nations" in English, "The Nations" was + actually the sequel. Yeah, what?). I sunk quite some time into that one as + well. + - "Funk-Flitzer", which was on a "Mickey Maus" comic magazine. Go search for + "3-D Ultra Radio Control Racers". + - Worms 2 - [Hedgewars](http://www.hedgewars.org/) seems to be the open source + alternative. + - Moorhuhn, Winter Edition +- Sometime later deep in the past: Minecraft (I stopped way before Microsoft + acquired it), recently followed by [Minetest, or rather, + Luanti](https://www.luanti.org/) (Though, "it's just not the same anymore". + I'm not sure what exactly it is, maybe my changed mindset, maybe I liked the + original soundtrack and game elements more…). +- [Starbound](https://playstarbound.com/), very shortly +- [OpenTTD](https://www.openttd.org/): Trains and buses! Though, admittedly, I + never quite "got it" (why do you have to manually "park" passengers on train + stations, instead of unloading them completely, so they continue taking the + train?) +- [Dwarf Fortress](https://www.bay12games.com/dwarves/). Go with + [LNP](https://dwarffortresswiki.org/index.php/Utility:Lazy_Newb_Pack) if you + wanna give it a try, or the Steam version. +- [Stardew Valley](https://www.stardewvalley.net/) (on Android tablet) +- [Factorio](https://www.factorio.com/) - Shaking my head over the people + arguing over how to perfectly optimize everything ;). I disabled biters in + recent play-throughs, it got to annoying for me. +- [Terraria](https://terraria.org/). Another one where I had to resort to the + Wiki for the bosses. +- [Cattails: Wildwood Story](https://cattailsgame.com/). Got through the main + story, had to resort to YouTube for looking up how to beat the final boss. +- [Turing Complete](https://turingcomplete.game/) +- And, the most recent one, + [dotAge](https://www.michelepirovano.com/dotage.html) + +The latter [^dotnote1] one is a "turn-based village builder", with challenges. I +*guess* I can *kinda* compare it to Dwarf Fortress, as in, it can be incredibly +hard, and you have to build a civilization [^dotdwarf] and take care of it, as +in terms of food and drink, for example. Only you don't have to deal with goblin +or forgotten beast invasion, but, basically, nature. And DF doesn't have tech +research… Okay, maybe they're not comparable after all. + +The first play-throughs are rather rough, since you will only unlock additional +content, such as farming, with play time. So your first villages will cease to +exist quite quickly, I think. That's expected. Probably to show you the failure +modes? + +From personal experience: Don't build some new buildings too quickly. The game +allows you to build them even though they're "useless" at that specific point in +the game, and you need additional tech research to make them usable. + +{% linked_image +img="https://pics.uvokchee.de/_data/i/upload/2025/03/17/20250317185230-40c102c2-me.jpg" +url="https://pics.uvokchee.de/upload/2025/03/17/20250317185230-40c102c2.jpg" +alt="Screenshot of the game dotAge, of fairly early game play." +%} + +[^dotnote1]: + Yes, kinda unfair I only go into detail into one. I wanted to start + "reviewing" only this one, but then thought, "well, might as well list all + games I played so far". Maybe that's a chance for future blog posts. + +[^simcit]: + I didn't like SimCity 3000 as much. And *don't get me started with Cities: + Skylines*. + +[^dotdwarf]: + General advice seems to be not to go over 50 in dotAge. In Dwarf Fortress, + you have to deal with the pesky Nobles instead. Something similar seems to + exist in dotAge as well? + +[^civ1]: Go drop archive.org a small donation, won't you? + +[^civ2]: You can actually still play it thanks to [DOSBox](https://www.dosbox.com/) + +[^unkhor]: + Not sure about the current state. The port to the Godot + engine seems still to be a WIP. Last blog post is quite some time ago, but + the [GitHub repo](https://github.com/unknown-horizons/godot-port) is active. + diff --git a/_posts/2025-04-05-random-thought-smartphones-with-old-technology.md b/_posts/2025-04-05-random-thought-smartphones-with-old-technology.md new file mode 100644 index 0000000..4649817 --- /dev/null +++ b/_posts/2025-04-05-random-thought-smartphones-with-old-technology.md @@ -0,0 +1,20 @@ +--- +layout: post +title: 'Random Thought: Smartphones with old technology' +date: 2025-04-05 20:23 +0200 +lang: en +categories: tech +--- + +How would smartphones look with old technology? What if we never invented flash +storage? (Leave aside the answer "the smartphone would not have been +possible"). Like, what if they were using tapes / tape drives as primary +storage? Or floppy disks? + +<insert AI generated image here - hah, you wish. I'll have none of that on my +blog.> + +This thought was presented to me while listening to the ["Stay +Forever"](https://www.stayforever.de/) podcast, a German-language podcasts about +old games and their technology. + diff --git a/_posts/2025-04-06-i-don-t-get-kubernetes.md b/_posts/2025-04-06-i-don-t-get-kubernetes.md new file mode 100644 index 0000000..28224b4 --- /dev/null +++ b/_posts/2025-04-06-i-don-t-get-kubernetes.md @@ -0,0 +1,38 @@ +--- +layout: post +title: I don't get Kubernetes +date: 2025-04-06 15:59 +0200 +categories: tech +lang: en +--- + +No real content, just me ranting. + +There are [some ;)](https://xeiaso.net/talks/2025/surreal-joy-homelab/) who take +great joy in Kubernetes. I never really understood how I could use it. + +Okay, so, very simplified, K8s let's you say "I wanna run these services (Docker +containers) on my cluster (various computers/servers), optionally with +redundancy"? Well, I only have one "server" in my "homelab", and then 3 VPS or +so. Only one of them runs something in Docker/Podman. + +I *can't imagine*/don't understand how I would run, say, Nextcloud in K8s. I +*want* to know which server is currently running it, and where the data is +stored. Especially with something like Nextcloud, I want to have the Nextcloud +application, the database, and the data store on **one** VPS, without any +indirections, without any communication over the internet or a VPN, via some +proxy software. From what little I've heard about K8s (forgive me my ignorance), +I had more questions that I could answer. K8s seems really complex, because it +~~has~~ can have so many moving parts (yes, you can probably run a very basic +setup, most of the components seem optional). I don't want my database suddenly +moved around to somewhere else. + +That is to say: I never had a use case of trying out K8s. No *actual, real* one. +I could probably construct one if I tried really hard. But, eh. "My current +setup works". + +My attempt to run K8s on my Proxmox via LXC all failed, even if they're +privileged containers, probably because I need to mount Linux Namespaces, or +load specific kernel modules on Proxmox itself so they can be loaded in the LXC +containers as well (???). + diff --git a/_posts/2025-05-11-table-tennis.md b/_posts/2025-05-11-table-tennis.md new file mode 100644 index 0000000..8d9ce82 --- /dev/null +++ b/_posts/2025-05-11-table-tennis.md @@ -0,0 +1,14 @@ +--- +layout: post +title: Table Tennis! +date: 2025-05-11 08:16 +0200 +lang: en +categories: life +--- + +Recently, I was at a seminar. I learned a lot. It was an exquisite group of inquisitive people. +But we also had some free time in which we played some table tennis. +I have no idea how to play it besides "hit the ball with the racket". +Actually makes me want to join the local sports club for once, which does have a +table tennis department. + diff --git a/_posts/2025-05-25-cpu-usage-problems-again.md b/_posts/2025-05-25-cpu-usage-problems-again.md new file mode 100644 index 0000000..beb905a --- /dev/null +++ b/_posts/2025-05-25-cpu-usage-problems-again.md @@ -0,0 +1,16 @@ +--- +layout: post +title: CPU usage problems on my VPS again +date: 2025-05-25 19:29 +0200 +lang: en +categories: tech +--- + +<a href="{% post_url 2025-02-02-dn42-put-it-in-a-box-linux-network-namespace +%}">As reported previously</a>, I had problems with the CPU usage on my VPS +again, even after namespace isolation. This time, it was dnsmasq causing the +loadavg to rise. "This is a problem for future me to solve properly", is the +motto. For the time being, I simply added a service restart when I detect a high +loadavg. Works well enough. I'll keep monitoring it to see if this occurs more +than once a week or so. + diff --git a/_posts/2025-05-29-furdew-valley.md b/_posts/2025-05-29-furdew-valley.md new file mode 100644 index 0000000..cbdf420 --- /dev/null +++ b/_posts/2025-05-29-furdew-valley.md @@ -0,0 +1,24 @@ +--- +layout: post +title: Furdew Valley +date: 2025-05-29 15:21 +0200 +lang: en +categories: gaming +--- + +[In my last blog post]({% post_url 2025-03-18-games-i-play_ed %}) about gaming, +I think I already mentioned I played Stardew Valley. Back then, on an Android +tablet. Due to [Pan on Mastodon](https://woof.tech/@Pan_H_Shiroi@toot.cat) +streaming that game recently, I picked it up again. + +I found it can even be modded! The mod in question is +[Animals and Myths Pack](https://www.moddrop.com/stardew-valley/mods/1119098-fs-shs-animals-and-myths-pack). + +{% image + img="https://pics.uvokchee.de/upload/2025/05/29/20250529132445-6dbbe14d.jpg" + alt="Screenshot of modded Stardew Valley, showing the player character with anthro legs + and a fox tail" +%} + +That adds to the fun. I might even get a mod that adds NPC lines. The build-in +ones can get monotonous after some time. diff --git a/_posts/2025-06-16-oops-dn42-stops-working.md b/_posts/2025-06-16-oops-dn42-stops-working.md new file mode 100644 index 0000000..26d4ed7 --- /dev/null +++ b/_posts/2025-06-16-oops-dn42-stops-working.md @@ -0,0 +1,73 @@ +--- +layout: post +title: Oops, DN42 stopped working +date: 2025-06-16 20:39 +0200 +last_modified_at: 2025-06-17 18:12 +0200 +lang: en +categories: tech +--- + +As you might know, I participate in [DN42]({% link dn42.md %}). +I have a somewhat peculiar setup, in +which some VPS run the routing daemons, and my home router simply has a VPN +connection to them and statically routes everything `fd00::/8` to them. The router +runs OpenWRT, and has dnsmasq setup to resolve DN42 domains via the DN42 anycast +servers. I set this up *months ago*, it worked, I was happy, and didn't need it +since. + +Cue last weekend. "Suddenly" the resolution didn't work anymore. It simply timed +out. So I connected to my VPS (which is running DN42 +[in a namespace]({%post_url 2025-02-02-dn42-put-it-in-a-box-linux-network-namespace %})) +and took a look at tcpdump. Inside the namespace, I saw the strange lines + +``` +18:49:05.296629 eth0 In IP6 fd7a:115c:a1e0::xxx > fd42:d42:d42:53::1.53: 42631+ [1au] AAAA? wiki.dn42. (50) +18:49:05.296679 kioubit Out IP6 fd3e:bc05:2d6::80.50255 > fd42:d42:d42:53::1.53: 42631+ [1au] AAAA? wiki.dn42. (50) +18:49:05.302946 tinc_dn42 In IP6 fd42:d42:d42:53::1.53 > fd3e:bc05:2d6::80.50255: 42631 1/0/1 AAAA fd42:d42:d42:80::1 (66) +18:49:05.302990 kioubit Out IP6 fd3e:bc05:2d6::80 > fd42:d42:d42:53::1: ICMP6, destination unreachable, unreachable route fd3e:bc05:2d6::80, length 122 +``` + +And all the time I was thinking… "huh??? Why is `fd3e:bc05:2d6::80` unreachable??? +It is **clearly** in the `ip -6 a` output!!!". I looked through all the +iptables statistics and couldn't find the culprit. + +A join in the DN42 IRC and some back and forth later, someone suggested +"Hey, what's with the `fd7a:…` address? Is there a route for *that*?". + +And of course, no, it wasn't! I was so focused on the ICMP6 message that I +didn't notice the incoming line. As you can read in my other article linked +above, I perform NAT. Of course in that case it probably wouldn't make sense for the +ICMP6 message to tell someone that there's no route for the original IP (before NAT). + +**So, but… where does the `fd7a:…` address come from?**<br/> +The answer is [Tailscale]({% post_url 2024-12-08-trying-out-tailscale %}). +Unfortunately, they decided to use the `fd00::/8` IP range, which collides with DN42. +I didn't do any DN42 stuff since installing it, so I didn't notice that.<br/> +**But… why is that address used at all for the DNS request?**<br/> +Weeeeell… I found out as well. It comes from OpenWRT. I simply set up +a static route there, and Linux does its best to determine the source address for the +DNS request. And it seems the Tailscale one was a closer match than +the address from my own DN42 prefix.<br/> +**So, how to fix that?**<br/> +[It's not as easy as you think!](https://github.com/openwrt/openwrt/issues/13720). +Merely using the "source" option in the config file would work for IPv4, but +for IPv6 it has a different meaning!<br/> +**But why does it say "no route to host" instead of using a default route?**<br/> +Because I configured bird to insert an "unreachable" route for the `fd::/8` prefix +to avoid leaking traffic. +``` +# ip -6 route show fd00::/8 table dn42 +unreachable fd00::/8 dev lo proto bird src fd3e:bc05:2d6::1 metric 500 pref medium +``` + +At this point, it's Monday evening. Unnerved, I threw my hands in the air and +simply put a line of +``` +ip -6 route replace fd00::/8 ... src <my-openwrt-dn42-address> +``` +in `/etc/rc.local` and called it a day. My setup +works again, and I learned something again. Don't just look at the last line, +look at the whole picture. A lesson I actually already learned in the +ubuntuusers forums when asking for help compiling a package and only posted the +last few make output lines, which of course didn't contain the actual +compilation error… diff --git a/_posts/2025-06-22-blog-now-on-gemini-with-dn42.md b/_posts/2025-06-22-blog-now-on-gemini-with-dn42.md new file mode 100644 index 0000000..fa46a58 --- /dev/null +++ b/_posts/2025-06-22-blog-now-on-gemini-with-dn42.md @@ -0,0 +1,39 @@ +--- +layout: post +title: Blog now on Gemini - with DN42 +date: 2025-06-22 17:42 +0200 +--- + +The Gemini protocol is a minimal protocol to serve content, and is +the "spiritual successor" to the Gopher protocol. +(Do not confuse with Google's AI Gemini. I hate naming collissions), + +It took some tinkering, especially because Gemini's markup language +("Gemtext") +is deliberately +minimal, and Jekyll can't output/convert Markdown to Gemini, so I had to +take "the ugly route" to convert the "finished" Jekyll HTML page to Gemtext +via the below-mentioned tool, which basically does +HTML > Markdown > Gemtext. +I can't directly use the raw Markdown files of my blog, since I use +Liquid tags. + +Anyway, my blog is now accessible in "Geminispace", alas, only within DN42, +because I'm too lazy right now to do it properly. (With the DN42 namespace +isolation and all). And also, it's a really "nerdy" protocol, with I don't +know how many users. + +`gemini://uvok.dn42/` + +Addendum: What I really like is the "feed" specification, which basically consists +of separate lines of <br/> `=> $link $date $heading`, <br/> and browsers like +Lagrange can subscribe +to these (like RSS feeds, but the format is much simpler!). + +- [Gemini Protocol on Wikipedia](https://en.wikipedia.org/wiki/Gemini_(protocol)) +- [Gopher Protocol on Wikipedia](https://en.wikipedia.org/wiki/Gopher_(protocol)) +- [Gemini Protocol homepage](https://geminiprotocol.net/) +- [Gemini "Quickstart"](https://geminiquickst.art/) +- [Gemtext intro](https://gemini.flounder.online/docs/gemtext.gmi) +- [Html2Gemini](https://github.com/Aarontheissueguy/Html2GeminiPy) +- [Lagrange browser](https://git.skyjake.fi/gemini/lagrange) diff --git a/assets/main.scss b/assets/main.scss index ff45870..4ff4a71 100644 --- a/assets/main.scss +++ b/assets/main.scss @@ -53,7 +53,6 @@ a:visited { background: #eeeeee; margin-bottom: 1em; position: relative; - z-index: -1; } .banner-close-button { @@ -67,3 +66,8 @@ hr.postsep { margin: auto; margin-bottom: 1em; } + +/* site nav above news items */ +.site-nav { + z-index: 10; +} diff --git a/assets/mscomm/comments.js b/assets/mscomm/comments.js new file mode 100644 index 0000000..57b9793 --- /dev/null +++ b/assets/mscomm/comments.js @@ -0,0 +1,311 @@ +// © https://phosphoricons.com/ +export const icons = { + reblog: + `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="none" stroke="currentColor" stroke-width="24"><polyline points="200 88 224 64 200 40"/><path d="M32,128A64,64,0,0,1,96,64H224"/><polyline points="56 168 32 192 56 216"/><path d="M224,128a64,64,0,0,1-64,64H32"/></svg>`, + favourite: + `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor"><path d="M240,94c0,70-103.79,126.66-108.21,129a8,8,0,0,1-7.58,0C119.79,220.66,16,164,16,94A62.07,62.07,0,0,1,78,32c20.65,0,38.73,8.88,50,23.89C139.27,40.88,157.35,32,178,32A62.07,62.07,0,0,1,240,94Z"/></svg>`, + author: + `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor" class="comment-author"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm45.66,85.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z"></path></svg>`, + + // @ https://simpleicons.org/ + mastodon: + `<svg role="img" width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Mastodon</title><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>`, + + pleroma: + `<svg role="img" width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Pleroma</title><path d="M6.36 0A1.868 1.868 0 004.49 1.868V24h5.964V0zm7.113 0v12h4.168a1.868 1.868 0 001.868-1.868V0zm0 18.036V24h4.168a1.868 1.868 0 001.868-1.868v-4.096Z"/></svg>`, + + bluesky: + `<svg role="img" width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Bluesky</title><path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z"/></svg>`, +}; + +export default class SocialComments extends HTMLElement { + comments = {}; + + async connectedCallback() { + const lang = this.closest("[lang]")?.lang || navigator.language || "en"; + + this.dateTimeFormatter = new Intl.DateTimeFormat(lang, { + dateStyle: "medium", + timeStyle: "short", + }); + + const mastodon = this.getAttribute("mastodon") || this.getAttribute("src"); + const bluesky = this.getAttribute("bluesky"); + + await Promise.all([ + mastodon && this.#fetchMastodon(new URL(mastodon)), + bluesky && this.#fetchBluesky(new URL(bluesky)), + ]); + + this.refresh(); + } + + refresh() { + const comments = [ + ...this.comments.mastodon || [], + ...this.comments.bluesky || [], + ].sort( + (a, b) => new Date(a.createdAt) - new Date(b.createdAt), + ); + + if (comments.length) { + this.innerHTML = ""; + this.render(this, comments); + } + } + + async #fetchBluesky(url) { + const { pathname } = url; + + const [, handle, rkey] = pathname.match( + /\/profile\/([\w\.]+)\/post\/(\w+)/, + ); + + if (!handle || !rkey) { + return; + } + + const options = { + ttl: Number(this.getAttribute("cache") || 0), + }; + + const didData = await fetchJSON( + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}`, + options, + ); + const uri = `at://${didData.did}/app.bsky.feed.post/${rkey}`; + + this.comments.bluesky = dataFromBluesky( + await fetchJSON( + `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${uri}`, + options, + ), + ); + } + + async #fetchMastodon(url) { + const { origin, pathname } = url; + let id; + + const source = pathname.includes("/notice/") ? "pleroma" : "mastodon"; + + if (source === "pleroma") { + [, id] = pathname.match(/^\/notice\/([^\/?#]+)/); + } else { + [, id] = pathname.match(/\/(\d+)$/); + } + + if (!id) { + return; + } + + const token = this.getAttribute("token"); + const options = { + ttl: Number(this.getAttribute("cache") || 0), + }; + if (token) { + options.headers = { + Authorization: `Bearer ${token}`, + }; + } + + const user = url.pathname.split("/")[1]; + const author = `${user}@${url.hostname}`; + + const comments = dataFromMastodon( + await fetchJSON( + new URL(`${origin}/api/v1/statuses/${id}/context`), + options, + ), + author, + source, + ); + + this.comments.mastodon = comments.filter((comment) => + comment.parent === id + ); + } + + render(container, replies) { + const ul = document.createElement("ul"); + + for (const reply of replies) { + const comment = document.createElement("li"); + comment.innerHTML = this.renderComment(reply); + + if (reply.replies.length) { + this.render(comment, reply.replies); + } + ul.appendChild(comment); + } + + container.appendChild(ul); + } + + renderComment(comment) { + return ` + <article class="comment" id="comment-${comment.id}"> + <footer class="comment-footer"> + <a href="${comment.author.url}" class="comment-user"> + <img class="comment-avatar" src="${comment.author.avatar}" alt="${comment.author.alt}'s avatar" width="200" height="200"> + ${comment.isMine ? icons.author : ""} + <strong class="comment-username"> + ${comment.author.name} + </strong> + <em class="comment-useraddress">${comment.author.handler}</em> + </a> + <a href="${comment.url}" class="comment-address"> + <time class="comment-time" title="${comment.createdAt.toISOString()}"> + ${this.dateTimeFormatter.format(comment.createdAt)} + ${icons[comment.source]} + </time> + </a> + </footer> + <div class="comment-body"> + ${comment.content} + + <p class="comment-counts"> + ${ + comment.boosts ? `<span>${icons.reblog} ${comment.boosts}</span>` : "" + } + ${ + comment.likes ? `<span>${icons.favourite} ${comment.likes}</span>` : "" + } + </p> + </div> + </article> + `; + } +} + +function formatEmojis(html, emojis) { + emojis.forEach(({ shortcode, static_url, url }) => { + html = html.replace( + `:${shortcode}:`, + `<picture> + <source srcset="${url}" media="(prefers-reduced-motion: no-preference)"> + <img src="${static_url}" alt=":${shortcode}:" title=":${shortcode}:" width="16" height="16"> + </picture>`, + ); + }); + return html; +} + +async function fetchJSON(url, options = {}) { + const headers = new Headers(); + + if (options.headers) { + for (const [key, value] of Object.entries(options.headers)) { + headers.set(key, value); + } + } + + if (typeof caches === "undefined") { + return await (await fetch(url), { headers }).json(); + } + + const cache = await caches.open("mastodon-comments"); + let cached = await cache.match(url); + + if (cached && options.ttl) { + const cacheTime = new Date(cached.headers.get("x-cached-at")); + const diff = Date.now() - cacheTime.getTime(); + + if (diff <= options.ttl * 1000) { + return await cached.json(); + } + } + + try { + const response = await fetch(url, { headers }); + const body = await response.json(); + + cached = new Response(JSON.stringify(body)); + cached.headers.set("x-cached-at", new Date()); + cached.headers.set("content-type", "application/json; charset=utf-8"); + await cache.put(url, cached); + return body; + } catch { + if (cached) { + return await cached.json(); + } + } +} + +function dataFromMastodon(data, author, source) { + const comments = new Map(); + + // Transform data to a more usable format + for (const comment of data.descendants) { + if (comment.visibility !== "public") { + continue; + } + + const { account } = comment; + const handler = `@${account.username}@${new URL(account.url).hostname}`; + comments.set(comment.id, { + id: comment.id, + isMine: author === handler, + source, + url: comment.url, + parent: comment.in_reply_to_id, + createdAt: new Date(comment.created_at), + content: formatEmojis(comment.content, comment.emojis), + author: { + name: formatEmojis(account.display_name, account.emojis), + handler, + url: account.url, + avatar: account.avatar_static, + alt: account.display_name, + }, + boosts: comment.reblogs_count, + likes: comment.favourites_count, + replies: [], + }); + } + + // Group comments by parent + for (const comment of comments.values()) { + if (comment.parent && comments.has(comment.parent)) { + comments.get(comment.parent).replies.push(comment); + } + } + + return Array.from(comments.values()); +} + +function dataFromBluesky(data) { + const { thread } = data; + + return blueskyComments( + thread.post.author.did, + thread.post.cid, + thread.replies, + ); +} + +function blueskyComments(author, parent, comments) { + return comments.map((reply) => { + const { post, replies } = reply; + const rkey = post.uri.split("/").pop(); + return { + id: post.cid, + isMine: post.author.did === author, + source: "bluesky", + url: `https://bsky.app/profile/${post.author.handle}/post/${rkey}`, + parent, + createdAt: new Date(post.record.createdAt), + content: post.record.text, + author: { + name: post.author.displayName, + handler: post.author.handle, + url: `https://bsky.app/profile/${post.author.handle}`, + avatar: post.author.avatar, + alt: post.author.displayName, + }, + boosts: post.repostCount, + likes: post.likeCount, + replies: blueskyComments(author, post.cid, replies || []), + }; + }); +} diff --git a/assets/mscomm/styles.css b/assets/mscomm/styles.css new file mode 100644 index 0000000..61d4d0d --- /dev/null +++ b/assets/mscomm/styles.css @@ -0,0 +1,92 @@ +oom-comments { + display: block; + /*padding: 2em;*/ +} +oom-comments ul { + list-style: none; + margin: 0; + padding: 0; +} +oom-comments li { + margin: 32px 0; +} +oom-comments article { + max-width: 600px; +} +oom-comments ul ul { + margin-left: 64px; +} +oom-comments .comment-avatar { + width: 50px; + height: 50px; + border-radius: 6px; + float: left; + margin-right: 14px; + box-shadow: 0 0 1px #0009; +} +oom-comments .comment-user { + color: currentColor; + text-decoration: none; + display: block; + position: relative; +} +oom-comments .comment-author { + position: absolute; + left: 35px; + top: 35px; + background: white; + border-radius: 50%; + width: 20px; + height: 20px; + color: gray; +} +oom-comments .comment-user:hover .comment-username { + text-decoration: underline; +} +oom-comments .comment-username { + margin-right: 0.5em; +} +oom-comments .comment-useraddress { + color: gray; + font-size: small; + font-style: normal; +} +oom-comments .comment-time { + font-size: small; + display: flex; + align-items: center; + column-gap: 0.4em; +} +oom-comments .comment-time svg { + width: 1em; + height: 1em; + fill: gray; +} +oom-comments .comment-address { + color: currentColor; + text-decoration: none; + display: block; + margin-top: 0.25em; +} +oom-comments .comment-address:hover { + text-decoration: underline; +} +oom-comments .comment-body { + margin-top: 0.5em; + margin-left: 64px; + line-height: 1.5; +} +oom-comments .comment-body p { + margin: 0.5em 0; +} +oom-comments .comment-counts { + display: flex; + column-gap: 1em; + font-size: small; +} +oom-comments .comment-counts > span { + display: flex; + align-items: center; + column-gap: 0.3em; + color: gray; +} diff --git a/contact.html b/contact.html index e4e39f3..8c8fc4c 100644 --- a/contact.html +++ b/contact.html @@ -20,6 +20,6 @@ lang: "en" Signal: uvok_cheetah.10 </li> <li> - <a href="https://furry.engineer/@uvok" target="_blank" rel="noreferrer">Mastodon</a> + <a href="https://woof.tech/@uvok" target="_blank" rel="noreferrer">uvok@woof.tech</a> on Mastodon </li> </ul> @@ -1,7 +1,7 @@ --- layout: page title: DN42 -in_navbar: true +in_navbar: false order: 50 lang: "en" --- diff --git a/feed.gmi b/feed.gmi new file mode 100644 index 0000000..ac3e185 --- /dev/null +++ b/feed.gmi @@ -0,0 +1,22 @@ +--- +--- + {% assign title = site.title | default: site.name %} + + {% if title %} +# {{ title | smartify | xml_escape }} + {% endif %} + + {% if site.description %} +## {{ site.description | xml_escape }} + {% endif %} + + {% unless site.show_drafts %} + {% assign posts = site.posts | where_exp: "post", "post.draft != true" %} + {% endunless %} + {% assign posts = posts | sort: "date" | reverse %} + {% for post in posts %} + {% assign post_title = post.title | smartify | strip_html | normalize_whitespace | xml_escape %} + +=> {{ post.url }} {{ post.date | date: "%Y-%m-%d" }} {{ post_title }} + {% endfor %} + @@ -5,4 +5,19 @@ in_navbar: true lang: en --- +## Electronics and such + - [Nandgame](https://nandgame.com/): Build a computer from basic logic gates. Great learning resource. +- [Nand2tetris](https://www.nand2tetris.org/): In similar vain. +- Also check out Ben Eater's YouTube channel and [website](https://eater.net/). +- If you like maths, 3Blue1Brown is for you. + +## Random software I use / appreciate + +- [GrapheneOS](https://grapheneos.org/): Alternative OS for Google Pixel smartphones. +- [F-Droid](https://f-droid.org/): Open Source App store for Android. +- [Wireguard](https://www.wireguard.com/): My go-to "VPN" solution. (VPN as in + "connect computers/servers to a network", not "I wanna watch $SomeStramingService in + another country!!!!!!11111111!!!!!"). +- [OpenWRT](https://openwrt.org/): Router OS which runs my home network. + diff --git a/projects.md b/projects.md new file mode 100644 index 0000000..c8350fb --- /dev/null +++ b/projects.md @@ -0,0 +1,33 @@ +--- +layout: page +title: Projects +in_navbar: true +order: 50 +lang: "en" +--- + +Some of my projects and sites: + +- This blog you're reading right now +- [DN42]({% link dn42.md %}) +- [Wiki](https://uvokchee.de/wiki/) +- [Funkwhale](https://fw.uvok.de/) (currently defunc) +- [Personal Matrix and XMPP Server]({% link contact.html %}) +- Running an authoritative DNS server with [PowerDNS](https://www.powerdns.com/powerdns-community) +- Running various VPS (all with Debian, of course) +- Running [OpenWRT](https://openwrt.org/) in my home network (e.g. for tagged VLAN) [2] +- Running [Proxmox](https://www.proxmox.com/en/) (hosting various LXC containers) in my home +- Running an [RIPE ATLAS](https://atlas.ripe.net/docs/) probe - [software](https://github.com/RIPE-NCC/ripe-atlas-software-probe) +- [OpenPGP WKD](https://wiki.gnupg.org/WKD) via DNS + (for shits and giggles - I don't really write mail, and PGP has it's usability problems) +- [Git server](https://git.uvok.de/) + using [gitolite](https://gitolite.com/gitolite/index.html) [1] + and [cgit](https://git.zx2c4.com/cgit/) +- My blog is now also on Gemini, although only accessible via DN42. + gemini://uvok.dn42 + +[1] Can really recommend this if you *don't* want a full-blown Git hosting with + "UI" / CI etc. - just the bare-bones git repository hosting. + (Which saves you setting up the bare git repos manually, though). \ +[2] I tried OPNsense in the past, too. But at some point I ran into problems I couldn't fix. + Also, it was virtualized inside Proxmox. Not the optimal solution. diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..a8e359f --- /dev/null +++ b/readme.txt @@ -0,0 +1,3 @@ +URL scheme for piwigo: + +i.php?/upload/2021/06/04/20210604124815-78046f20-sm.jpg diff --git a/search.html b/search.html index fe400ce..2768f1a 100644 --- a/search.html +++ b/search.html @@ -10,10 +10,12 @@ lang: "en" <script src="/assets/js/lunr.min.js"></script> <script src="/assets/js/search.js"></script> -<p> -<input id="input_qterm"></input> -<button id="qsubmit">Search</button> -</p> +<form id="searchForm"> + <p> + <input id="input_qterm" type="text"> + <button id="qsubmit" type="submit">Search</button> + </p> +</form> <h2>Results for <span id="qterm"></span></h1> <div id="results"> @@ -45,10 +47,19 @@ lang: "en" } document.getElementById("qterm").textContent = queryterm; } - document.getElementById("qsubmit").onclick = function(ev) { - const qbox = document.getElementById("input_qterm").value; - if (qbox.length !== 0) { - query(qbox); - } + + // Function to be called on submit + function submitSearch() { + console.log("Search submitted"); + const qbox = document.getElementById("input_qterm").value; + if (qbox.length !== 0) { + query(qbox); + } + } + + // Attach event handler to form submission + document.getElementById("searchForm").onsubmit = function(ev) { + ev.preventDefault(); // Prevent default form submission behavior + submitSearch(); }; </script> |