<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Announcements</title><link>https://obsp.de/en/tags/announcements/</link><description>Observer's Space</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://obsp.de/en/tags/announcements/" rel="self" type="application/rss+xml"/><item><title>Primary Domain Switch to obsp.dev</title><link>https://obsp.de/en/posts/domain-switch-to-obsp-dev/</link><guid isPermaLink="true">https://obsp.de/en/posts/domain-switch-to-obsp-dev/</guid><pubDate>Tue, 15 Sep 2026 02:30:00 +0200</pubDate><description>&lt;h2 id="1-new-primary-domain"&gt;1. New Primary Domain&lt;/h2&gt;
&lt;p&gt;The primary domain of this site will be switched from &lt;code&gt;obsp.de&lt;/code&gt; to &lt;code&gt;obsp.dev&lt;/code&gt;. &lt;del&gt;The maintainer would like to build a more international outlook instead of focusing on Europe, hence this change.&lt;/del&gt; (Na ah they just like the vibe)&lt;/p&gt;
&lt;h2 id="2-scope-of-the-change"&gt;2. Scope of the Change&lt;/h2&gt;
&lt;p&gt;This change only affects the site itself (&lt;code&gt;@&lt;/code&gt; and &lt;code&gt;www&lt;/code&gt;). &lt;strong&gt;All other services will remain on the &lt;code&gt;obsp.de&lt;/code&gt; domain, so there is no need to change anything on your end.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For now, &lt;code&gt;www&lt;/code&gt; and &lt;code&gt;@&lt;/code&gt; of &lt;code&gt;obsp.dev&lt;/code&gt; will redirect to &lt;code&gt;obsp.de&lt;/code&gt;. After 2–3 weeks, the redirect will be the other way around. When that happens, RSS entries might break — apologies in advance.&lt;/p&gt;
&lt;h2 id="3-identity--wkd"&gt;3. Identity &amp;amp; WKD&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;root@obsp.dev&lt;/code&gt; is now another identity of the maintainer. The OpenPGP key fingerprint remains the same:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy text code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;9967 35BE 4961 80AF B582 ECAB EE31 BB28 ECF6 B268&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;It is now also discoverable via WKD, so you can simply run &lt;code&gt;gpg --locate-key root@obsp.dev&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="4-dnssec"&gt;4. DNSSEC&lt;/h2&gt;
&lt;p&gt;DNSSEC has not yet been implemented, as this is still a new domain. It will be live within a month, using either algorithm 13 (&lt;code&gt;ECDSA-P256-SHA256&lt;/code&gt;) or algorithm 15 (&lt;code&gt;Ed25519&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Privacy and Whatsoever</title><link>https://obsp.de/en/posts/privacy-and-whatsoever/</link><guid isPermaLink="true">https://obsp.de/en/posts/privacy-and-whatsoever/</guid><pubDate>Fri, 07 Aug 2026 23:41:50 +0800</pubDate><description>&lt;p&gt;It&amp;rsquo;s been a while since I last posted. I spent a meaningful year in the university, or meaningless, depends on your standards.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m trying to find something to write on, but aside from vibe-coding an Arduino board to control a three-wheeled robot, doing some random analysis on energy storage that convinces nobody, and getting some 10s in exams while some 6s in others, there&amp;rsquo;s nothing that I actually want to write on.&lt;/p&gt;
&lt;p&gt;I stopped making true friends since 17, not deliberately, it&amp;rsquo;s just I&amp;rsquo;m tired of getting in any kind of new relationships. So nothing much to say about my social life either.&lt;/p&gt;
&lt;p&gt;So&amp;hellip; let&amp;rsquo;s talk about my point of view of privacy, maybe?&lt;/p&gt;
&lt;p&gt;This may surprise you, but no, I am not a &lt;em&gt;privacy-savvy&lt;/em&gt; person.&lt;/p&gt;
&lt;p&gt;The reason why I don&amp;rsquo;t use Google workspace or Microsoft 365 to host my domain email is because I want to use the simplest way to authorize my email clients. &lt;em&gt;Any client&lt;/em&gt;. So just IMAP/SMTP, with username and passwords. Not using a helper like &lt;code&gt;ortie&lt;/code&gt; pretending they are Mozilla Thunderbird to get the token and then put into &lt;code&gt;himalaya&lt;/code&gt; (unfortunately my school still uses MS365 so thanks TU Eindhoven for making use of &lt;code&gt;ortie&lt;/code&gt;). I reject Pro*on and T*ta for the same reason.&lt;/p&gt;
&lt;p&gt;The reason why I use something else than Windows on my personal PC is because Windows is really resource consuming and the fact that the package manager &lt;code&gt;winget&lt;/code&gt; is often not useful forces me to execute the installers again and again.&lt;/p&gt;
&lt;p&gt;The reason why I write blogs and post on Fediverse instead of just using X or rednote is because I&amp;rsquo;m anxious about everything and watching other people showing of their perfectly-planned, well-decorated, successful-in-all-aspect life drains me out. (The algorithm knows that perfectly well, I don&amp;rsquo;t know if their ultimate goal is to convince me to commit suicide - just joking.)&lt;/p&gt;
&lt;p&gt;The reason why I self-host my photo service, notesbook server, search engine and everything else is because I can&amp;rsquo;t afford the services or I&amp;rsquo;m fed up with promotions.&lt;/p&gt;
&lt;p&gt;The reason why I unlocked my phone&amp;rsquo;s bootloader is because I want to use a custom font.&lt;/p&gt;
&lt;p&gt;SEE? It&amp;rsquo;s nothing with privacy. I&amp;rsquo;m not seeking somewhere to hide, just somewhere I could live very comfortably, not invasive towards others at all, and I don&amp;rsquo;t even care if some random guy from the internet knows who I am. But this is not possible in this centralized, information-overloaded, protective InTeRnEt. People from 2000s might think if there is a utopia on earth it might be built on the internet. In 2020s we should just admit it sucks even more than offline life (I&amp;rsquo;m avoiding the term &amp;ldquo;real life&amp;rdquo;, after all, online activities are real as well).&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t think the above statements are pessimistic, they are simply the truth. My depression is not taking over anything.&lt;/p&gt;
&lt;p&gt;Oh, also, maybe talk about something in the spotlight, large language models. They emerged as a product long time ago but had not prevailed until recently. Personally I don&amp;rsquo;t think that&amp;rsquo;s a &amp;ldquo;technological explosion&amp;rdquo; that changes the lifestyle. Of course they perform a lot of meaningless or ethically doubtful actions, that drains resource that could be used by human, but it might be meaningful or ethically fair, depends on your standards.&lt;/p&gt;</description></item><item><title>Notes on Setting Up and Backing Up GoToSocial</title><link>https://obsp.de/en/posts/gts/</link><guid isPermaLink="true">https://obsp.de/en/posts/gts/</guid><pubDate>Sat, 27 Jun 2026 03:05:15 +0200</pubDate><description>&lt;h2 id="installing-gts"&gt;Installing GTS&lt;/h2&gt;
&lt;p&gt;I got an Oracle Cloud instance, so I decided to set up my own Fediverse instance. After some thought, I went with &lt;a href="https://gotosocial.org"&gt;GoToSocial&lt;/a&gt; — it&amp;rsquo;s fairly lightweight and works with any Mastodon API-compatible client. The downside is that it&amp;rsquo;s pretty bare-minimum: features like emoji reactions and quote posts are absent.&lt;/p&gt;
&lt;p&gt;The official GTS &lt;code&gt;docker-compose.yaml&lt;/code&gt; can be fetched like this:&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy bash code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/example/docker-compose/docker-compose.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy yaml code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gotosocial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/superseriousbusiness/gotosocial:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gotosocial&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;gotosocial&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Change this to your actual host value.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example.org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_DB_TYPE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sqlite&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Path in the GtS Docker container where&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# the sqlite.db file will be stored.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_DB_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/gotosocial/storage/sqlite.db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Change this to true if you&amp;#39;re not running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# GoToSocial behind a reverse proxy.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_LETSENCRYPT_ENABLED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set your email address here if you&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# want to receive letsencrypt notices.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_LETSENCRYPT_EMAIL_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Path in the GtS Docker container where the&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Wazero compilation cache will be stored.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_WAZERO_COMPILATION_CACHE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/gotosocial/.cache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;## For reverse proxy setups:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_TRUSTED_PROXIES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;172.18.0.1/16&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;## Set the timezone of your server:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#TZ: UTC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;443:8080&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;## For letsencrypt:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#- &amp;#34;80:80&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;## For reverse proxy setups:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#- &amp;#34;127.0.0.1:8080:8080&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Your data volume, for your&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# sqlite.db file and media files.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;~/gotosocial/data:/gotosocial/storage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# OPTIONAL: To mount volume for the WAZERO&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# compilation cache, for speedier restart&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# times, uncomment the below line:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#- ~/gotosocial/.cache:/gotosocial/.cache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;always&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gotosocial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ipam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;subnet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;172.18.0.0/16&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;172.18.0.1&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Since I already had Caddy running on the machine, I commented out all the reverse-proxy-related parts.&lt;/p&gt;
&lt;p&gt;I also added a few lines under &lt;code&gt;environment&lt;/code&gt; (GTS has a separate config file for environment variables, but if you don&amp;rsquo;t want to mount it you can write them directly in &lt;code&gt;compose.yml&lt;/code&gt; — just uppercase the variable name, replace &lt;code&gt;-&lt;/code&gt; with &lt;code&gt;_&lt;/code&gt;, and prepend &lt;code&gt;GTS_&lt;/code&gt;. See &lt;a href="https://docs.gotosocial.org/en/v0.20.3/configuration/#environment-variables"&gt;the docs&lt;/a&gt;):&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy yaml code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_ACCOUNTS_ALLOW_CUSTOM_CSS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# allow custom CSS per user&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Europe/Berlin&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GTS_INSTANCE_LANGUAGES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;zh,en,nl,de,fr,ja&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;And under &lt;code&gt;volumes&lt;/code&gt; I mounted a custom font:&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy yaml code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/home/ubuntu/gotosocial/fonts/GeistPixel-Square.woff2:/gotosocial/web/assets/fonts/GeistPixel-Square.woff2:ro&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;After doing this, the font can be referenced in CSS.&lt;/p&gt;
&lt;h2 id="css-customisation"&gt;CSS Customisation&lt;/h2&gt;
&lt;p&gt;GTS profile themes come from two sources: themes uploaded by the admin as preset options, and CSS written directly by users. Both layers stack on top of each other.&lt;/p&gt;
&lt;p&gt;The site-wide CSS is also customisable. I went with a combination of &lt;a href="https://catppuccin.com/palette/"&gt;Catppuccin Frappé&lt;/a&gt;, &lt;a href="https://vercel.com/font?type=pixel"&gt;Geist Pixel Square&lt;/a&gt;, and &lt;a href="https://github.com/TakWolf/fusion-pixel-font"&gt;Fusion Pixel&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="backing-up-the-database-and-media"&gt;Backing Up the Database and Media&lt;/h2&gt;
&lt;p&gt;Honestly, I&amp;rsquo;ve always been hesitant about self-hosting, because I don&amp;rsquo;t have much confidence in my own backups. If the database gets corrupted, the signing keys are lost, and rejoining the federation from the same domain becomes extremely painful.&lt;/p&gt;
&lt;p&gt;The GTS docs have a detailed guide on &lt;a href="https://docs.gotosocial.org/en/v0.20.3/admin/backup_and_restore/"&gt;backup and restore&lt;/a&gt;, and the recommended database backup tool is &lt;a href="https://torsion.org/borgmatic/"&gt;Borgmatic&lt;/a&gt;. &lt;del&gt;Though I was lazy and hadn&amp;rsquo;t read that section before writing this, so I didn&amp;rsquo;t use it.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;For backing up media and the database I use an S3 bucket, mounted via rclone.&lt;/p&gt;
&lt;p&gt;You could just upload everything from the Docker-mapped folder on the host, but that&amp;rsquo;s inelegant — the media directory includes caches from remote instances, which take up a lot of space but don&amp;rsquo;t need to be backed up. You only need to preserve media from local accounts.&lt;/p&gt;
&lt;p&gt;The GTS CLI tools provide &lt;code&gt;gotosocial admin media list-attachments&lt;/code&gt; and &lt;code&gt;gotosocial admin media list-emojis&lt;/code&gt;. Adding the &lt;code&gt;--local-only&lt;/code&gt; flag lists only local media files.&lt;/p&gt;
&lt;p&gt;The output looks something like this:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy text code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;/gotosocial/062G5WYKY35KKD12EMSM3F8PJ8/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3R.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg
/gotosocial/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH7TDVANYKWVE8VVKFPJTJ.gif
/gotosocial/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH58A357CV5K7R7TJMSH6S.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01CDR64G398ADCHXK08WWTHEZ5.gif&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;Using this, we can filter media by local account ID during scheduled backups.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example backup script using rclone against the host folder mapped from the Docker container:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/usr/local/bin/rclone-backup-optimized.sh&lt;/code&gt;&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy yaml code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;set -uo pipefail&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;SRC=&amp;#34;/home/ubuntu/gotosocial/data&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;MEDIA_DST=&amp;#34;remote:bucket/media&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;DB_DST=&amp;#34;remote:bucket/db&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;CONF=&amp;#34;/home/ubuntu/.config/rclone/rclone.conf&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;COMPOSE_FILE=&amp;#34;/home/ubuntu/gotosocial/docker-compose.yaml&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;SERVICE=&amp;#34;gotosocial&amp;#34; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# container name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;GTS_BIN=&amp;#34;/gotosocial/gotosocial&amp;#34; &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;DB_FILE=&amp;#34;/home/ubuntu/gotosocial/data/sqlite.db&amp;#34; &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# SQLite (vacuum into snapshot)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;DB_OK=1; DB_OUT=&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;SNAPDIR=$(mktemp -d); SNAP=&amp;#34;$SNAPDIR/sqlite.db&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;if DB_OUT=$(sqlite3 &amp;#34;$DB_FILE&amp;#34; &amp;#34;PRAGMA busy_timeout=10000; VACUUM INTO &amp;#39;$SNAP&amp;#39;&amp;#34; 2&amp;gt;&amp;amp;1); then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CHK=$(sqlite3 &amp;#34;$SNAP&amp;#34; &amp;#34;PRAGMA integrity_check&amp;#34; 2&amp;gt;&amp;amp;1)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;if [ &amp;#34;$CHK&amp;#34; = &amp;#34;ok&amp;#34; ]; then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;if DB_OUT=$(rclone copy &amp;#34;$SNAP&amp;#34; &amp;#34;$DB_DST/&amp;#34; --config &amp;#34;$CONF&amp;#34; 2&amp;gt;&amp;amp;1); then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;DB_OK=0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;DB_OUT=&amp;#34;Snapshot integrity failed: $CHK&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;rm -rf &amp;#34;$SNAPDIR&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# list local account ids&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;get_ids() {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sudo docker exec &amp;#34;$SERVICE&amp;#34; \&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;$GTS_BIN&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;admin media &amp;#34;$1&amp;#34; --local-only 2&amp;gt;/dev/null \&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;| grep -oE &amp;#39;/[0-9A-HJKMNP-TV-Z]{26}/(attachment|emoji)/&amp;#39; \&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;| grep -oE &amp;#39;[0-9A-HJKMNP-TV-Z]{26}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;IDS=$( { get_ids list-attachments; get_ids list-emojis; } | sort -u )&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# sync media&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;FILTER=$(mktemp)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;while IFS= read -r id; do printf &amp;#39;+ /%s/**\n&amp;#39; &amp;#34;$id&amp;#34;; done &amp;lt;&amp;lt;&amp;lt; &amp;#34;$IDS&amp;#34; &amp;gt;&amp;gt; &amp;#34;$FILTER&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;printf -- &amp;#39;- **\n&amp;#39; &amp;gt;&amp;gt; &amp;#34;$FILTER&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;MEDIA_OUT=$(rclone sync &amp;#34;$SRC&amp;#34; &amp;#34;$MEDIA_DST&amp;#34; --filter-from &amp;#34;$FILTER&amp;#34; --config &amp;#34;$CONF&amp;#34; 2&amp;gt;&amp;amp;1)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;MEDIA_CODE=$?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;rm -f &amp;#34;$FILTER&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;After each backup you can also send a Discord webhook notification so you know if something goes wrong.&lt;/p&gt;
&lt;p&gt;I added this to the script:&lt;/p&gt;
&lt;figure class="code-block" data-language="yaml"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;YAML&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy yaml code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;WEBHOOK=&amp;#34;https://discord.com/api/webhooks/.../...&amp;#34; &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;notify() { &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# $1=title $2=color $3=description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;local payload&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;payload=$(jq -n --arg t &amp;#34;$1&amp;#34; --arg d &amp;#34;$3&amp;#34; --arg h &amp;#34;$(hostname)&amp;#34; --argjson c &amp;#34;$2&amp;#34; \&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{embeds:[{title:$t, description:$d, color:$c, footer:{text:$h}, timestamp:(now|todate)}]}&amp;#39;&lt;/span&gt;&lt;span class="l"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;curl -sf -H &amp;#34;Content-Type: application/json&amp;#34; -d &amp;#34;$payload&amp;#34; &amp;#34;$WEBHOOK&amp;#34; &amp;gt;/dev/null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;NUM=$(printf &amp;#39;%s\n&amp;#39; &amp;#34;$IDS&amp;#34; | wc -l | tr -d &amp;#39; &amp;#39;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;$DB_OK&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="l"&gt;eq 0 ] &amp;amp;&amp;amp; DB_LINE=&amp;#34;Database: success&amp;#34; || DB_LINE=&amp;#34;Database: failed ($(printf &amp;#39;%s&amp;#39; &amp;#34;$DB_OUT&amp;#34; | tail -c 300))&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;$MEDIA_CODE&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="l"&gt;eq 0 ] &amp;amp;&amp;amp; MEDIA_LINE=&amp;#34;Media: success (${NUM} local account directory/ies)&amp;#34; || MEDIA_LINE=&amp;#34;Media: failed (exit $MEDIA_CODE)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;if [ &amp;#34;$DB_OK&amp;#34; -eq 0 ] &amp;amp;&amp;amp; [ &amp;#34;$MEDIA_CODE&amp;#34; -eq 0 ]; then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TITLE=&amp;#34;GtS backup success&amp;#34;; COLOR=3066993&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TITLE=&amp;#34;GtS backup failed&amp;#34;; COLOR=15158332&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;fi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;TAIL=$(printf &amp;#39;%s&amp;#39; &amp;#34;$MEDIA_OUT&amp;#34; | tail -c 1000)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;DESC=$(printf &amp;#39;%s\n%s\n```\n%s\n```&amp;#39; &amp;#34;$DB_LINE&amp;#34; &amp;#34;$MEDIA_LINE&amp;#34; &amp;#34;${TAIL:-（Media no output）}&amp;#34;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;notify &amp;#34;$TITLE&amp;#34; &amp;#34;$COLOR&amp;#34; &amp;#34;$DESC&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="phanpy-a-nicer-frontend"&gt;Phanpy: A Nicer Frontend&lt;/h2&gt;
&lt;p&gt;Installing &lt;a href="https://phanpy.social"&gt;Phanpy&lt;/a&gt; is quite painless since it&amp;rsquo;s a purely static site — you can just download a release and serve it behind a web server. However, the project doesn&amp;rsquo;t recommend this approach; the preferred way is a custom build.&lt;/p&gt;
&lt;p&gt;After cloning the repo, something like this works:&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy bash code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PHANPY_DEFAULT_INSTANCE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;social.obsp.de &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;PHANPY_CLIENT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Observer&amp;#39;s Space Social&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	 &lt;span class="nv"&gt;PHANPY_WEBSITE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://phanpy.obsp.de&amp;#34;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;PHANPY_PRIVACY_POLICY_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://social.obsp.de/about &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; npm run build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;dist&lt;/code&gt; folder will contain everything you need.&lt;/p&gt;
&lt;p&gt;Phanpy doesn&amp;rsquo;t yet support an instance whitelist, so I &lt;a href="https://github.com/devkamiki/phanpy-obsp/commit/5a982630644943785727d856e31839d97b77e8c2"&gt;made a small modification&lt;/a&gt; to &lt;code&gt;src/pages/login.jsx&lt;/code&gt; to restrict login to a single specific instance.&lt;/p&gt;</description></item><item><title>Quick Installation Matlab 2025b on Arch</title><link>https://obsp.de/en/posts/quick-installation-matlab-2025b-on-arch/</link><guid isPermaLink="true">https://obsp.de/en/posts/quick-installation-matlab-2025b-on-arch/</guid><pubDate>Mon, 10 Nov 2025 01:00:34 +0100</pubDate><description>&lt;p&gt;Since I&amp;rsquo;m on Wayland, the best way to install Matlab should be &lt;code&gt;mpm&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://wiki.archlinux.org/title/MATLAB"&gt;The Wiki page&lt;/a&gt; worked for me and the steps I executed include:&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy bash code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yay matlab-mpm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mpm install --release&lt;span class="o"&gt;=&lt;/span&gt;R2025b --destination&lt;span class="o"&gt;=&lt;/span&gt;~/matlab MATLAB 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#ofc, you can change the destination&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Normally, you should be able to just execute &lt;code&gt;~/matlab/bin/glnxa64/MathWorksProductAuthorizer.sh&lt;/code&gt; and finish activating your license. However it didn&amp;rsquo;t went that smooth for me, because recent &lt;code&gt;gnutls&lt;/code&gt; update (likely 3.8.10-1 or newer) breaks the TLS handshake in MATLAB&amp;rsquo;s bundled licensing libraries (&lt;code&gt;libmwinstall\_activationwsclientimpl.so&lt;/code&gt; and &lt;code&gt;libmwlmgrimpl.so&lt;/code&gt;), causing the segfault when the tool tries to connect to MathWorks servers.&lt;br&gt;
So I ran these to make Matlab use the old version of &lt;code&gt;gnutls&lt;/code&gt; :&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy bash code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://archive.archlinux.org/packages/g/gnutls/gnutls-3.8.9-1-x86_64.pkg.tar.zst
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p matlab/gnutls
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -xf gnutls-3.8.9-1-x86_64.pkg.tar.zst -C matlab/gnutls
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p ~/matlab/bin/glnxa64/gnutls
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp -a ~/matlab/gnutls/usr/lib/libgnutls* ~/matlab/bin/glnxa64/gnutls/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /home/user/matlab/bin/glnxa64/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ln -s gnutls/* ./&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;And that should be fine. Now you can continue to run &lt;code&gt;~/matlab/bin/glnxa64/MathWorksProductAuthorizer.sh&lt;/code&gt;, finishing setup and launch.&lt;br&gt;
To launch Matlab faster from terminal you can add this line in &lt;code&gt;.bashrc&lt;/code&gt;:&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy bash code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;matlab&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/home/user/matlab/bin/matlab&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;#change it to the path the executable locates&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Or, alternatively, make a desktop entry with &lt;code&gt;sudo nano /usr/share/applications/matlab.desktop&lt;/code&gt;:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy text code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Desktop Entry]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Type=Application
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Terminal=false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MimeType=text/x-matlab
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exec=/home/user/matlab/bin/matlab -desktop -useStartupFolderPref
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Name=MATLAB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Icon=matlab
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Categories=Development;Math;Science
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Comment=Scientific computing environment
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;StartupNotify=true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;</description></item><item><title>Fix Copy SMS Code on Graphene</title><link>https://obsp.de/en/posts/fix-copy-sms-code-on-graphene/</link><guid isPermaLink="true">https://obsp.de/en/posts/fix-copy-sms-code-on-graphene/</guid><pubDate>Sat, 27 Sep 2025 14:30:58 +0200</pubDate><description>&lt;p&gt;You can read the detail description &lt;a href="https://github.com/GrapheneOS/os-issue-tracker/issues/6216"&gt;in this issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The fix is, find out the listener service with this adb command:&lt;/p&gt;
&lt;figure class="code-block" data-language="shell"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;SHELL&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy shell code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell pm dump io.github.jd1378.otphelper &lt;span class="p"&gt;|&lt;/span&gt; grep -i &lt;span class="s2"&gt;&amp;#34;notification\|listener&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;And you can see it&amp;rsquo;s called &lt;code&gt;io.github.jd1378.otphelper/.NotificationListener&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, we grant it special access to read notifications (which is not possible via settings GUI, and I don&amp;rsquo; know why):&lt;/p&gt;
&lt;figure class="code-block" data-language="shell"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;SHELL&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy shell code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell cmd notification allow_listener io.github.jd1378.otphelper/.NotificationListener&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;</description></item><item><title>How to Make Microsoft Bluetooth Mouse 3600 Work in Linux Windows Dual Boot</title><link>https://obsp.de/en/posts/how-to-use-microsoft-bluetooth-mouse-3600-work-in-linux-windows-dual-boot/</link><guid isPermaLink="true">https://obsp.de/en/posts/how-to-use-microsoft-bluetooth-mouse-3600-work-in-linux-windows-dual-boot/</guid><pubDate>Thu, 28 Aug 2025 19:06:24 +0200</pubDate><description>&lt;p&gt;Before moving in the Netherlands, I used a USB mouse, and as a result I never bothered for pairing issues on dual booting Linux/Windows. However, I found Microsoft Bluetooth Mouse 3600 specifically helpful as it functions smoothly even without a mousepad.&lt;/p&gt;
&lt;p&gt;Certainly, you can read &lt;a href="https://wiki.archlinux.org/title/Bluetooth#Dual_boot_pairing"&gt;this detailed tutorial from Arch Wiki&lt;/a&gt;, so I won&amp;rsquo;t explain too much on the general process.&lt;/p&gt;
&lt;p&gt;The problem is, the table for Bluetooth 5.1 devices does not cover the model Microsoft Bluetooth Mouse 3600, and I have to refer to &amp;ldquo;Other devices&amp;rdquo;, the guidance of which does not really work for me.&lt;/p&gt;
&lt;p&gt;What turns out to be working is as the followings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;this mouse will automatically create a new MAC address at pairing, thus you should change the Linux config accordingly (which has been mentioned in Arch Wiki)&lt;/li&gt;
&lt;li&gt;copy &lt;code&gt;CSRK&lt;/code&gt; from Windows to &lt;code&gt;LocalSignatureKey.Key&lt;/code&gt; in Linux (all capital, no space)&lt;/li&gt;
&lt;li&gt;copy &lt;code&gt;LTK&lt;/code&gt; to &lt;code&gt;LongTermKey.Key&lt;/code&gt; (all capital, no space)&lt;/li&gt;
&lt;li&gt;copy the &lt;strong&gt;decimal&lt;/strong&gt; value of &lt;code&gt;EDIV&lt;/code&gt; to &lt;code&gt;LongTermKey.EDiv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;don&amp;rsquo;t modify &lt;code&gt;LongTermKey.EncSize&lt;/code&gt; if it&amp;rsquo;s 16 already (this is correct)&lt;/li&gt;
&lt;li&gt;copy the &lt;strong&gt;decimal&lt;/strong&gt; value of &lt;code&gt;ERand&lt;/code&gt; to &lt;code&gt;LongTermKey.Rand&lt;/code&gt;, &lt;strong&gt;without any modification&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most devices require you to reverse the hexadecimal value of &lt;code&gt;ERand&lt;/code&gt; and then convert it to decimal. This is not the case on 3600. If you see a decimal value in the registry, just use it.&lt;/p&gt;
&lt;p&gt;By the way, this script is really helpful if your configuration doesn&amp;rsquo;t work: &lt;a href="https://github.com/nbanks/bluetooth-dualboot"&gt;Bluetooth Dual Boot Pairing Helper&lt;/a&gt;. Credits to the author!&lt;/p&gt;</description></item><item><title>Adding Printer Support for Epson L3150 on Arch Linux</title><link>https://obsp.de/en/posts/adding-priter-support-for-epson-l3150-on-arch-linux/</link><guid isPermaLink="true">https://obsp.de/en/posts/adding-priter-support-for-epson-l3150-on-arch-linux/</guid><pubDate>Thu, 07 Aug 2025 20:01:58 +0800</pubDate><description>&lt;p&gt;My printer is on network at address &lt;code&gt;192.168.138.250&lt;/code&gt; .&lt;/p&gt;
&lt;p&gt;First add CUPS:&lt;/p&gt;
&lt;figure class="code-block" data-language="zsh"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;ZSH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy zsh code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo pacman -Sy cups cups-browsed bluez-cups cups-pdf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; cups 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemctl start cups&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Then some other dependencies (and drivers, if you want to use some other type than Epson L3150):&lt;/p&gt;
&lt;figure class="code-block" data-language="zsh"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;ZSH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy zsh code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo pacman -Sy system-config-printer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo pacman -Sy nss-mdns
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo pacman -Sy foomatic-db foomatic-db-ppds 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo pacman -S foomatic-db-nonfree foomatic-db-nonfree-ppds&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;And we should install the driver for L3150, which can be achieved with an AUR package (Epson only offer &lt;code&gt;.deb&lt;/code&gt; and &lt;code&gt;.rpm&lt;/code&gt; packages &lt;a href="https://download.ebz.epson.net/dsc/search/01/search/searchModule"&gt;officially&lt;/a&gt;):&lt;/p&gt;
&lt;figure class="code-block" data-language="zsh"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;ZSH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy zsh code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zsh" data-lang="zsh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yay -S epson-inkjet-printer-escpr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;All good to go! Try to search for printer with the address, and make the system discover the driver automatically. If it says the &lt;code&gt;model&lt;/code&gt; is exactly Epson L3150, then the correct driver should be added.&lt;/p&gt;</description></item><item><title>Mastodon Comment Test</title><link>https://obsp.de/en/posts/mastodon-comment-test/</link><guid isPermaLink="true">https://obsp.de/en/posts/mastodon-comment-test/</guid><pubDate>Wed, 06 Aug 2025 14:45:33 +0800</pubDate><description>&lt;p&gt;Does it work?&lt;/p&gt;</description></item><item><title>Interpreting Internet.nl Test Results</title><link>https://obsp.de/en/posts/interpreting-internet.nl-test-results/</link><guid isPermaLink="true">https://obsp.de/en/posts/interpreting-internet.nl-test-results/</guid><pubDate>Tue, 05 Aug 2025 17:00:59 +0800</pubDate><description>&lt;p&gt;&lt;em&gt;Disclaimer: I have been a user of almost every email provider mentioned below, and I am not affiliated with any of them.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can read Forward Email&amp;rsquo;s comparison with many providers here: &lt;a href="https://forwardemail.net/en/blog/forward-email-vs-mailbox-org-email-service-comparison"&gt;https://forwardemail.net/en/blog/forward-email-vs-mailbox-org-email-service-comparison&lt;/a&gt; , by substituting mailbox.org with others on their list.&lt;/p&gt;
&lt;p&gt;In short, Forward Email has some wrong information. They attempt to achieve full score in every security test and prove they are superior to their competitors with scores. I don&amp;rsquo;t know what is driving Forward Email towards being a pupil, and it seems like many privacy seekers get trapped into how high they score in the &lt;a href="https://internet.nl"&gt;Internet.nl&lt;/a&gt; tests.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not denying its validity, but do you know what is it intended for? &amp;ldquo;Modern Internet&amp;rdquo; - it&amp;rsquo;s not exactly security or privacy, it&amp;rsquo;s their standard of future which I do agree with and appreciate their efforts to promote. But it&amp;rsquo;s really NOT a security test, because a large part of the score speaks for how well the web server and mail server support IPV6. A lower overall score with bad support of IPV6 and good performance in DNSSEC, DMARC and DANE, should be valued as outperforming a higher overall score with limited performance in DNSSEC, DMARC and DANE. Also, do you know Internet.nl returns zero score if they hit a rate limit towards the tested server? It happens to me when I set up Stalwart with default limiters on my machine. To get a full score on my domain, I have to manually tweak the limiters so that all the test would perform smoothly. It&amp;rsquo;s also not fair for hosting providers to test on their main domain, for example, if you test &lt;code&gt;migadu.com&lt;/code&gt;, you will see &lt;a href="https://internet.nl/mail/migadu.com/1581463/#control-panel-28"&gt;worse result&lt;/a&gt; since DANE is temporarily off on &lt;code&gt;mx.migadu.com&lt;/code&gt; which handles mail for it, but it doesn&amp;rsquo;t affect their customers, since DANE is still on for &lt;code&gt;aspmx1.migadu.com&lt;/code&gt; and &lt;code&gt;aspmx2.migadu.com&lt;/code&gt; , as seen on &lt;a href="https://internet.nl/mail/divested.dev/1581466/#control-panel-28"&gt;test of divested.dev&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/observatory"&gt;Mozilla HTTP Observatory&lt;/a&gt; and &lt;a href="https://www.hardenize.com"&gt;Hardenize Test&lt;/a&gt; are tests dedicated for WEB SERVERS. It has little thing to do with mail server examination, and showing off your high score here only make you act like an amateur. At least it&amp;rsquo;s not appropriate - Sheldon Cooper is smart in math, but he is a bad engineer.&lt;/p&gt;
&lt;p&gt;And what is Forward Email trying to achieve by comparing themselves with pure relaying services like addy.io and pure SMTP API services like resend, and arguing they are not good enough because they don&amp;rsquo;t offer email storage? Hilarious.&lt;/p&gt;
&lt;p&gt;All this tests have validity of a certain field to some extent, but you shouldn&amp;rsquo;t just take them all and claim that higher score necessarily means better security. For a dedicated email security test aggregation I would suggest &lt;a href="https://dismail.de/serverlist.html"&gt;Servers List from dismail&lt;/a&gt;, while it&amp;rsquo;s no longer actively maintained and all the data is outdated in 2025, you can always run the tests yourself, with the list for reference.&lt;/p&gt;</description></item><item><title>Arch Setup</title><link>https://obsp.de/en/posts/arch-setup/</link><guid isPermaLink="true">https://obsp.de/en/posts/arch-setup/</guid><pubDate>Mon, 04 Aug 2025 15:45:50 +0800</pubDate><description>&lt;p&gt;Just performed a fresh install of Arch, and try to use &lt;code&gt;zsh&lt;/code&gt; instead of &lt;code&gt;bash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To make it remember history, and inherit nice aliases from &lt;code&gt;bash&lt;/code&gt; , make a file &lt;code&gt;.zshrc&lt;/code&gt; with the following:&lt;/p&gt;
&lt;figure class="code-block" data-language="shell"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;SHELL&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy shell code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Created by newuser for 5.9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:/home/user/.local/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# uncomment the line below if you feel like using oh-my-posh &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# eval &amp;#34;$(oh-my-posh init zsh --config &amp;#39;catppuccin&amp;#39;)&amp;#34; &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ls --color=auto&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;grep --color=auto&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PS1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[\u@\h \W]\$ &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;XDG_DATA_DIRS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/var/lib/flatpak/exports/share:/home/user/.local/share/flatpak/exports/share:/home/user/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share/:/usr/share/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;HISTFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.zsh_history
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# How many commands to store in history&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;HISTSIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;SAVEHIST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;setopt SHARE_HISTORY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# uncomment the lines below if you installed Fcitx &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# export XMODIFIERS=&amp;#34;@im=fcitx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# export GTK_IM_MODULE=fcitx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# export QT_IM_MODULE=fcitx&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;By default, GNOME doesn&amp;rsquo;t recognize tray icons from applications. This would be a headache for applications that starts up silently and automatically minimizes to tray. Head to Flatpak manager and install &lt;code&gt;Extension Manager&lt;/code&gt;, open it and search for &lt;code&gt;AppIndicator and KStatusNotifierItem Support&lt;/code&gt;, install the extension and you&amp;rsquo;ll be able to see icons showing up.&lt;/p&gt;
&lt;p&gt;EduVPN doesn&amp;rsquo;t have an official client for Arch yet an unofficial one can be obtained easily:&lt;/p&gt;
&lt;figure class="code-block" data-language="shell"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;SHELL&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy shell code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl https://app.eduvpn.org/linux/v4/deb/app+linux@eduvpn.org.asc &lt;span class="p"&gt;|&lt;/span&gt; gpg --import - 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yay -S python-eduvpn-client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;That&amp;rsquo;s all.&lt;/p&gt;</description></item><item><title>Using GnuPG to Verify Yourself on Keyoxide</title><link>https://obsp.de/en/posts/using-gpg-to-verify-yourself-on-keyoxide/</link><guid isPermaLink="true">https://obsp.de/en/posts/using-gpg-to-verify-yourself-on-keyoxide/</guid><pubDate>Sat, 26 Jul 2025 20:57:44 +0800</pubDate><description>&lt;p&gt;&lt;em&gt;Disclaimer: this post is a machine translation of the original &lt;a href="https://obsp.de/zh/posts/using-gpg-to-verify-yourself-on-keyoxide/"&gt;Chinese version&lt;/a&gt;. Please refer to the original content for accurate information.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="preface"&gt;Preface&lt;/h2&gt;
&lt;p&gt;Keyoxide is similar to Keybase.io, used for verifying the identity consistency of online accounts.&lt;/p&gt;
&lt;p&gt;For example, search for &lt;a href="mailto:contact@forgejo.org"&gt;contact@forgejo.org&lt;/a&gt; on Keyoxide.org, and you&amp;rsquo;ll see that Forgejo owns a Mastodon instance account @&lt;a href="mailto:forgejo@floss.social"&gt;forgejo@floss.social&lt;/a&gt;, the domain forgejo.org, and a Forgejo instance account @&lt;a href="mailto:forgejo@codeberg.org"&gt;forgejo@codeberg.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also search using OpenPGP public key fingerprints. For instance, search for my fingerprint (which can be found on the &amp;ldquo;Contact&amp;rdquo; page) on Keyoxide.org, and you&amp;rsquo;ll see that Yuki claims ownership of accounts such as ActivityPub, Matrix, Forgejo, as well as the domain obsp.de.&lt;/p&gt;
&lt;p&gt;Each query performs real-time verification, and successfully verified entries will display a green checkmark next to them. This ensures the information is up to date.&lt;/p&gt;
&lt;p&gt;Currently, Keyoxide employs two identity verification systems: one is the Ariadne Signature Profile, and the other is the widely adopted OpenPGP.&lt;/p&gt;
&lt;p&gt;Keyoxide is decentralized—not only because anyone can run their own Keyoxide instance, but also because OpenPGP utilizes a federated information system. When you need to retrieve a public key, you can obtain the public key associated with an email address from large key servers like keys.openpgp.org, exchange public keys directly with your contacts in a peer-to-peer manner, or use WKD to fetch the public key.&lt;/p&gt;
&lt;p&gt;So, what is WKD? WKD stands for Web Key Directory, and as the name suggests, it is a method of retrieving keys by making an HTTPS request to a specific directory on a website. For example, if Alice&amp;rsquo;s email address is &lt;a href="mailto:alice@example.org"&gt;alice@example.org&lt;/a&gt;, and Bob wants to obtain her public key but cannot find valid results on large key servers (because Alice hasn&amp;rsquo;t published it publicly), asking Alice directly would waste time waiting for her reply. If example.org supports WKD, Bob can run &lt;code&gt;gpg --locate-key alice@example.org&lt;/code&gt; to fetch her public key. This command essentially performs the following actions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perform a WKD hash on the string &amp;ldquo;alice&amp;rdquo; to obtain the hash string &amp;ldquo;kei1q4tipxxu1yj79k9kfukdhfy631xe&amp;rdquo;
&lt;ul&gt;
&lt;li&gt;You can verify it by running &lt;code&gt;gpg-wks-client --print-wkd-hash alice@example.org&lt;/code&gt; in the terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Combine the hash string, the domain example.org, and the general format of WKD to obtain the WKD URL
&lt;ul&gt;
&lt;li&gt;It should look like &lt;code&gt;https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/kei1q4tipxxu1yj79k9kfukdhfy631xe?l=alice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Or &lt;code&gt;https://example.org/.well-known/openpgpkey/example.org/hu/kei1q4tipxxu1yj79k9kfukdhfy631xe?l=alice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The first type, known as the subdomain method URL, is more favored by GnuPG compared to the second type, the direct method URL.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The content of this WKD URL via curl
&lt;ul&gt;
&lt;li&gt;It is not ASCII-armored but in binary format, making it unreadable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpg --import&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might notice that unlike the &lt;code&gt;--search-key&lt;/code&gt; operation on a keyserver, GnuPG does not prompt you to confirm whether to import the key. This is because keys published via WKD are generally considered valid and trustworthy. If Alice owns the domain example.org and has exclusive control over its directory, the key discovered through WKD can be assumed to have been published by Alice herself. If Alice is merely a user of example.org, then at the very least, the key was published by Alice&amp;rsquo;s administrator.&lt;/p&gt;
&lt;p&gt;Currently, email providers supporting WKD include systemli.org, posteo.de, Proton, Mailfence, and ForwardEmail, among others. Since disroot.org, which I use, does not support WKD, the public key you find on Keyoxide will show as originating from an HKP server. However, Forgejo provides WKD support for forgejo.org, so Keyoxide will display the public key as coming from WKD, and you can click the WKD link to verify it.&lt;/p&gt;
&lt;p&gt;If you use a domain email, you can self-host WKD. If your provider supports WKD, you can CNAME the subdomain openpgpkey to the provider&amp;rsquo;s domain. If the provider does not support it, you can also directly CNAME to wkd.keys.openpgp.org, which is a &lt;a href="https://keys.openpgp.org/about/usage#wkd-as-a-service"&gt;WKD as a service&lt;/a&gt; offering by openpgp.org.&lt;/p&gt;
&lt;h2 id="creating-notation"&gt;Creating Notation&lt;/h2&gt;
&lt;p&gt;Assuming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You own a domain named doma.in&lt;/li&gt;
&lt;li&gt;You have created a public key for your email address &lt;a href="mailto:you@doma.in"&gt;you@doma.in&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;You wish to prove ownership of this domain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What you need to do is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a note to your public key: &amp;ldquo;I own doma.in&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Add a TXT record to the domain: &amp;ldquo;Public key fingerprint xxx is correct&amp;rdquo; (this record can optionally be hashed)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you&amp;rsquo;ve likely noticed, these two records are not equivalent. With just the note on the public key, we can find doma.in, but the fingerprint in the TXT record on doma.in doesn&amp;rsquo;t lead us back to the public key. Moreover, since the fingerprint remains unchanged before and after the note, you have no way of knowing what public key fingerprint xxx actually said. We cannot and do not need to trace back from doma.in to &lt;a href="mailto:you@doma.in"&gt;you@doma.in&lt;/a&gt;, which is why the TXT record can be replaced with a hash (as for why someone might want to use a hash—it&amp;rsquo;s because they may have published their public key to a keyserver, where the fingerprint can be used to search and trace back to the public key).&lt;/p&gt;
&lt;p&gt;Notes on public keys are referred to as &lt;em&gt;Identity Claims&lt;/em&gt; on Keyoxide, which is quite straightforward—they are declarations of ownership over an identity. While asserting ownership, they also indicate where the proof can be found for that identity. The records returned by the claimed ownership items are called &lt;em&gt;Identity Proofs&lt;/em&gt;, which can appear in places like TXT records of a domain, repositories on a Git instance, or posts on a Fediverse account. These locations must be editable only by the account owner. For example, you cannot claim a &lt;em&gt;proof&lt;/em&gt; for a GitHub account in a gist under a username you do not &lt;em&gt;claim&lt;/em&gt;—the reasoning is simple.&lt;/p&gt;
&lt;p&gt;We run the following command to create a note:&lt;/p&gt;
&lt;figure class="code-block" data-language="bash"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;BASH&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy bash code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpg --edit-key FINGERPRINT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;list &lt;span class="c1"&gt;#list all the uid under the fingerprint&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uid N &lt;span class="c1"&gt;#select the uid to be edited&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notation &lt;span class="c1"&gt;#add a new notation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;proof@ariadne.id&lt;span class="o"&gt;=&lt;/span&gt;dns:doma.in?type&lt;span class="o"&gt;=&lt;/span&gt;TXT &lt;span class="c1"&gt;#claims that you own doma.in，with proof in its TXT record&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Different online identities have different &lt;em&gt;claims&lt;/em&gt;. You can &lt;a href="https://docs.keyoxide.org/service-providers/"&gt;check the supported &lt;em&gt;claim&lt;/em&gt;/&lt;em&gt;proof&lt;/em&gt; types yourself&lt;/a&gt;. Generally, &lt;em&gt;claims&lt;/em&gt; follow the format &lt;code&gt;proof@ariadne.id=CLAIM&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="creating-a-proof"&gt;Creating a Proof&lt;/h2&gt;
&lt;p&gt;We add a &lt;em&gt;proof&lt;/em&gt; to doma.in by creating a TXT record in the following format:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy text code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openpgp4fpr: FINGERPRINT &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The TTL can be any value.&lt;/p&gt;
&lt;p&gt;Oh, and you can use &lt;code&gt;argon2&lt;/code&gt; or &lt;code&gt;bcrypt&lt;/code&gt; for hashing to ensure a certain level of anonymity. Keyoxide&amp;rsquo;s documentation thoughtfully provides a &lt;a href="https://docs.keyoxide.org/wiki/identity-proof-formats/#Hashed_URI"&gt;hashing tool&lt;/a&gt;. Simply input the string &lt;code&gt;openpgp4fpr: FINGERPRINT&lt;/code&gt;, then paste the output into the TXT record.&lt;/p&gt;
&lt;p&gt;Now, upload your key to WKD or HKP! If all goes well, within the TTL period, you should be able to search for your email address or public key fingerprint on Keyoxide and see that you&amp;rsquo;ve successfully verified ownership of doma.in.&lt;/p&gt;
&lt;h2 id="miscellaneous"&gt;Miscellaneous&lt;/h2&gt;
&lt;p&gt;If you accidentally write the &lt;em&gt;claim&lt;/em&gt; incorrectly (which happens often), you can re-enter the notation section of the GnuPG identity editor and input &lt;code&gt;-the incorrect notation&lt;/code&gt; to delete it. If you forget which notations you&amp;rsquo;ve added, you can enter &lt;code&gt;showpref&lt;/code&gt; while editing the identity to view them.&lt;/p&gt;
&lt;p&gt;For most of my &lt;em&gt;proof&lt;/em&gt;s, I used &lt;code&gt;argon2&lt;/code&gt; hashing, though Matrix uses plaintext. Hashed values always show verification failures—even switching to &lt;code&gt;bcrypt&lt;/code&gt; didn&amp;rsquo;t work. I&amp;rsquo;m not sure exactly where the issue lies, but I&amp;rsquo;ll check later to see if I can reproduce it. (Update: It&amp;rsquo;s solved. use &lt;code&gt;/plain&lt;/code&gt; command to prevent Matrix from formatting the string.) That said, it&amp;rsquo;s not a big deal since the hash doesn&amp;rsquo;t serve much purpose unless you genuinely don&amp;rsquo;t want someone who knows the location of your &lt;em&gt;proof&lt;/em&gt; to discover the location of your &lt;em&gt;claim&lt;/em&gt;, and you also won&amp;rsquo;t leak this connection through other means&amp;hellip; The likelihood of this seems quite low to me. If that&amp;rsquo;s truly the case, it&amp;rsquo;s better not to &lt;em&gt;claim&lt;/em&gt; at all, as it could compromise your anonymity.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t yet tried using Ariadne Signature Profile or self-hosting a Keyoxide instance—I might write another tutorial on that later. Keyoxide&amp;rsquo;s official documentation is highly recommended; it&amp;rsquo;s well-structured and easy to understand.&lt;/p&gt;</description></item><item><title>In Seek of Private Email Solution</title><link>https://obsp.de/en/posts/in-seek-of-private-email-solution/</link><guid isPermaLink="true">https://obsp.de/en/posts/in-seek-of-private-email-solution/</guid><pubDate>Mon, 21 Jul 2025 22:36:45 +0800</pubDate><description>&lt;p&gt;Earlier this month I wrote an article with the title &lt;a href="https://obsp.de/en/posts/encryption-in-email-practice/"&gt;Encryption in Email Practice&lt;/a&gt;. My point of view is pretty straightforward, &amp;ldquo;encryption not complete means no encryption at all&amp;rdquo;, and people should stop relying on email for any secret or privacy. Nevertheless, people would not just stop because I (and many others) said don&amp;rsquo;t, as the inertia of communication is hard to revert.&lt;/p&gt;
&lt;p&gt;The most interesting article I have read as an email enthusiast should be &lt;a href="https://digdeeper.neocities.org/articles/email.xhtml"&gt;E-mail providers - which one to choose?&lt;/a&gt;, as the author themself appears to be a super paranoid and the ultimate seeker of privacy. The inspection method they have used is not technical, as they basically went through the privacy policy page of each provider and tried to sign up through Tor. Their criteria is at least extreme and trivial, if not hilarious:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If they said they would log your IP address, that&amp;rsquo;s too bad!&lt;/li&gt;
&lt;li&gt;If they said they use analytic tools on their website (even the ones like self-hosted Matomo, and not Google Analytics), that&amp;rsquo;s too bad!&lt;/li&gt;
&lt;li&gt;If they use a CDN, that&amp;rsquo;s too bad! If the CDN provider happens to be Cloudflare, that&amp;rsquo;s the worst garbage!&lt;/li&gt;
&lt;li&gt;If you can&amp;rsquo;t sign up with Tor, that&amp;rsquo;s too bad!&lt;/li&gt;
&lt;li&gt;If they charge you more than 2 bucks per month, that&amp;rsquo;s robbery!&lt;/li&gt;
&lt;li&gt;If you need to pay for a custom domain name at a registrar, that&amp;rsquo;s robbery!&lt;/li&gt;
&lt;li&gt;If they share your information with a third party, that&amp;rsquo;s the worst garbage!&lt;/li&gt;
&lt;li&gt;If you need to do a Captcha (even Friendly Captcha, not Google reCaptcha) or you have to sign up with a phone number, that&amp;rsquo;s the worst garbage!&lt;/li&gt;
&lt;li&gt;If the webpage doesn&amp;rsquo;t work without JavaScript, that&amp;rsquo;s the worst garbage!
Well, I partially agree with the last one, and they have one nice criterion that I fully agree with:&lt;/li&gt;
&lt;li&gt;If you can&amp;rsquo;t use email clients with IMAP/SMTP protocols, that&amp;rsquo;s too bad!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might ask: what&amp;rsquo;s wrong with the listed criteria if the author just wants maximum privacy?&lt;/p&gt;
&lt;p&gt;Apparently, they have mistaken two concepts, and I don&amp;rsquo;t know if they did that deliberately, since they seem to be &amp;ldquo;technical&amp;rdquo; enough to distinguish these terms by definition: &lt;em&gt;privacy&lt;/em&gt; and &lt;em&gt;anonymity&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Privacy&lt;/em&gt; is the ability of an individual or group to seclude themselves or information about themselves, and thereby express themselves selectively. &lt;em&gt;Anonymity&lt;/em&gt; describes situations where the acting person&amp;rsquo;s identity is unknown.&lt;/p&gt;
&lt;p&gt;VPN and Tor, they are the favorite &lt;em&gt;anonymizers&lt;/em&gt;, when it comes to signing up with an email provider. Captchas and phone number verification help them to stop malicious automated requests. I&amp;rsquo;m not trying to speak in favor of any email hosting providers, but that&amp;rsquo;s the truth, especially for those who introduces a freemium pricing model. Proton and Tuta have become extraordinarily prevailing in recent years because they have a free tier. Of course marketing counts, but offering something for free itself would speak louder than any marketing approach. And this reflects on the user count. Proton claims to have 100 million users currently, Tuta &lt;a href="https://tuta.com/blog/10-million-users"&gt;has reached 10 million users in 2023&lt;/a&gt; when Fastmail &lt;a href="https://www.inquirer.com/business/email-privacy-protonmail-fastmail-gmail-alternative-20230829.html"&gt;with 30 years of history only had less then a half million&lt;/a&gt;. Tuta has deprecated their 12 EUR/year paid plan because it&amp;rsquo;s not sustainable. While Posteo manages to remain a minimum price of 12 EUR/year plan for over 15 years, Proton and Tuta&amp;rsquo;s paid customers always donate a part of money to the charity of those greed free customers. To make sure that such charity actually makes sense (in order these potential paid customers into real ones that would provide profit) instead of going to nowhere, they have to establish a rule preventing multiple free accounts, to adopt an approach to discriminate human from bots, as well as using identifiers to prevent making multiple free accounts. The only way is forbid anonymity, because the usage of Tor prevents monitoring free account count from a single user, and while the user&amp;rsquo;s ultimate goal is to emerge from nowhere and disappear in nowhere (plus, you can&amp;rsquo;t suspend my account if I disappear, that&amp;rsquo;s too bad!), the conflict of covering expense and providing absolute free service as in beer and freedom would be harsh. I don&amp;rsquo;t know why the author just pretend he doesn&amp;rsquo;t know that, as the free services that he speak highly of, like Disroot and RiseUp, live on users&amp;rsquo; donation and the community is small enough for a freemium pricing model. If RiseUp is no longer invite-only, or Disroot disables manual registration approval, or they start to market themselves as private email providers who wants to make profit from that, they will eventually become Proton or Tuta. Anonymous Tor users would flood in, server&amp;rsquo;s won&amp;rsquo;t be capable, and malicious traffic, automated spam would harm mail server&amp;rsquo;s reputation.&lt;/p&gt;
&lt;p&gt;Enough for &lt;em&gt;anonymity&lt;/em&gt;, let&amp;rsquo;s get down to &lt;em&gt;privacy&lt;/em&gt;. Thanks to this digdeeper guy, I have never read so many companies&amp;rsquo; privacy policy with comments all at once. Still, the digdeeper guy admits that a company can just hide their invasive activities from the terms and pretend it haven&amp;rsquo;t happened or will not happen in the future, and sometimes they have to state in the terms that they must obey the law to cooperate with governments because it will be unlawful otherwise! So what&amp;rsquo;s the point of picking the words and amusing yourself with your &amp;ldquo;masturbatory&amp;rdquo; opinions (I return the word to the author)? Most of the email services, as I could see in the article, has not been test by them at all, for stupid reasons like JavaScript, Captcha, pricing, banning Tor or Privacy Policy doesn&amp;rsquo;t look like something a drug dealer might fancy. The real privacy implementation is zero-knowledge encryption to mail servers, and you can do that with OpenPGP. I know, someone would yell at this: but if you use Gmail, Google would log your IP address and other data like client type, time zone and blah blah&amp;hellip; Well, this is NOT IN THE REALM of email privacy! These kinds of evil that Google does to you doesn&amp;rsquo;t have anything to do with email itself. Unless the provider explicitly prohibits usage of third party client or put a restriction on the client type, I don&amp;rsquo;t think this should be considered any further.&lt;/p&gt;
&lt;p&gt;Some people also mistake &lt;em&gt;security&lt;/em&gt; with &lt;em&gt;privacy&lt;/em&gt;. &lt;a href="https://discuss.privacyguides.net/t/posteo-email-provider/13346"&gt;A thread on Privacy Guides forum&lt;/a&gt; shows how average user tries to care about everything before they know what it really means. Posteo is not recommended among email providers in PrivacyGuides.org for their DMARC policy being set to &amp;ldquo;none&amp;rdquo;, instead of &amp;ldquo;reject&amp;rdquo; or &amp;ldquo;quarantine&amp;rdquo;. I do appreciate the standard, but rookies would say: &amp;ldquo;your DMARC policy is none, that&amp;rsquo;s too bad! Anyone can just spoof you!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Well, yes, this is a &lt;em&gt;security&lt;/em&gt; issue, and security by definition means you believe the entity is indeed who they claim to be. &lt;code&gt;example.org&lt;/code&gt;&amp;rsquo;s DMARC policy indicates the mailserver&amp;rsquo;s recommended action if someone who claims to send an email from &lt;code&gt;user@example.org&lt;/code&gt; . &amp;ldquo;None&amp;rdquo; means just let it reach wherever, &amp;ldquo;quarantine&amp;rdquo; means drop it to junk folder and &amp;ldquo;reject&amp;rdquo; means reject.&lt;/p&gt;
&lt;p&gt;Because email lacks a method to check if you are authorized to send email from &lt;code&gt;user@example.org&lt;/code&gt;, you can send email from any server that have its port 25 open claiming yourself to be &lt;code&gt;user@example.org&lt;/code&gt;. But if &lt;code&gt;example.org&lt;/code&gt; does not list your IP address in their SPF record, or your DKIM signature is invalid, then the receiver would probably refer to &lt;code&gt;example.org&lt;/code&gt;&amp;rsquo;s DMARC record for further actions. So, DMARC records is domain wide, and should only be decided by the domain owner. If they think &amp;ldquo;well it&amp;rsquo;s fine to just let people spoof me, since the reply will arrive at my mailbox anyway&amp;rdquo;, they set it to &amp;ldquo;none&amp;rdquo;. It&amp;rsquo;s very interesting that &lt;a href="https://internet.nl/mail/gmail.com/1497410/#control-panel-9"&gt;Gmail.com has DMARC policy set to &amp;ldquo;none&amp;rdquo;&lt;/a&gt; and &lt;a href="https://internet.nl/mail/outlook.com/1571379/#control-panel-9"&gt;Outlook.com has DMARC policy set to &amp;ldquo;none&amp;rdquo; with subdomain policy set to &amp;ldquo;quarantine&amp;rdquo;&lt;/a&gt;. They don&amp;rsquo;t really care about anyone on that domain to be spoofed, right? (I&amp;rsquo;m just pointing out a fact, I will never suggest anyone who use a domain for their personal email to set DMARC policy to &amp;ldquo;none&amp;rdquo;, that&amp;rsquo;s not secure. Meanwhile many other email providers like Proton, Tuta, Zoho and iCloud has policy &amp;ldquo;quarantine&amp;rdquo; or &amp;ldquo;reject&amp;rdquo;.)&lt;/p&gt;
&lt;p&gt;Here comes the problem: you tell receiver to drop SPF or DKIM invalid incoming mails, by setting up &amp;ldquo;reject&amp;rdquo; policy, but&amp;hellip; is your wish their command? You would be surprised that many &lt;em&gt;private&lt;/em&gt; email providers themselves do not respect DMARC policy, despite they themselves have a strict one. That&amp;rsquo;s because they are so afraid of missing important incoming mails that have been carelessly improperly authenticated, that they would unlikely to do what they&amp;rsquo;ve been told, and the maximum respect they could give to &amp;ldquo;reject&amp;rdquo; DMARC policy is quarantine, that is to say, put it in Junk folder so that it won&amp;rsquo;t just go to nowhere. Any some of them doesn&amp;rsquo;t even rely on DMARC policy at all, they calculate DKIM signature and put all the failures into junk folder regardless of a &amp;ldquo;none&amp;rdquo; policy. But there are some respectful ones that really listen to instructions: Gmail (yeah&amp;hellip;), Purelymail (you decide where to respect DMARC or not, they have a toggle in their web panel), MXRoute. These provider&amp;rsquo;s aren&amp;rsquo;t for &lt;em&gt;privacy&lt;/em&gt; seekers that much, but they do take &lt;em&gt;security&lt;/em&gt; protocols seriously.&lt;/p&gt;
&lt;p&gt;Ask yourself what do you really want from email before seeking private email providers, and don&amp;rsquo;t confuse one thing with another. Otherwise, you won&amp;rsquo;t be able to protect yourself from threats.&lt;/p&gt;</description></item><item><title>Use Your Custom Domain or Not for Email</title><link>https://obsp.de/en/posts/use-your-custom-domain-or-not-for-email/</link><guid isPermaLink="true">https://obsp.de/en/posts/use-your-custom-domain-or-not-for-email/</guid><pubDate>Mon, 14 Jul 2025 08:57:15 +0800</pubDate><description>&lt;p&gt;The short answer is yes, the long answer is not always.&lt;/p&gt;
&lt;p&gt;The benefits of using your own custom domain, and the drawbacks of not using it, are listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Email providers would shut down, and all you have to do is changing MX records.&lt;/li&gt;
&lt;li&gt;You don&amp;rsquo;t always need to pay for extra addresses. A simple catch-all would solve the problem, and you can reduce spam by setting up rules with each recipient address.&lt;/li&gt;
&lt;li&gt;More solutions available. Some email hosting providers are exclusive for custom domain users, and you can engage with different providers with your inbound and outbound, get the best solution of each. For example, I self host my inbound email with mail-in-a-box and have full control over the spam filter, but I&amp;rsquo;m concerned with IP reputation and deliverablity, so for the SMTP service I simply choose Amazon SES at a low price.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The points above sound pretty valid, but people have raised some concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have to own an email address to register for a domain anyway. It&amp;rsquo;s a bad idea to make &lt;code&gt;example.org&lt;/code&gt; &amp;rsquo;s contact &lt;code&gt;admin@example.org&lt;/code&gt; , and you know why. Zoho, Proton, Tuta, whatever, better to be a free address. Why free address? Well, do you happen to pay the bill for email and domain with Paypal and your Paypal needs an email address? If it&amp;rsquo;s associated to the address you pay for and you happen to run out of credit, you might be locked out. If you pay with credit card / debit card / cash / crypto, the risk would be smaller.
&lt;ul&gt;
&lt;li&gt;Argument: just use the free email address for registering the domain, and use your custom domain email for everything else.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Domain registrars might as well not be nice to you, such as closing your account without notification, the odds not being significant.&lt;/li&gt;
&lt;li&gt;The biggest problem is domain registry, they can raise prices whenever they would like to, and you have no choice but pay.
&lt;ul&gt;
&lt;li&gt;Argument: Do you want a fancy domain name or just a reliable one? If your answer is the latter, then the solution to this problem is straightforward: get a tld among &lt;code&gt;.com&lt;/code&gt; , &lt;code&gt;.net&lt;/code&gt; and &lt;code&gt;.org&lt;/code&gt; . Unless you are a citizen of the country, don&amp;rsquo;t get any ccTLDs. ccTLDs are more likely to be associated with bunch of regulations that you might not notice before violation and have restrictions with KYC.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;You might not be able to afford the domain and email hosting price someday in your life anymore. While cheap solutions like Purelymail and Migadu exist (and MXRoute, but only when their black Friday sales are in stock), most &amp;ldquo;serious and reliable&amp;rdquo; providers (I&amp;rsquo;m referring to Google Workspace, Microsoft 365, iCloud, Proton and Fastmail, based on their reverse MX lookup domain count) would cost quite a lot if you need more than one inbox.&lt;/li&gt;
&lt;li&gt;Not good for anonymity, you always hand out personal information when dealing with registry and registrar. Anonymous registrars like 1984.is and njal.la does not allow you claim the ownership for the domain.
&lt;ul&gt;
&lt;li&gt;Argument: use disposable addresses on public domains when you need anonymity or pseudononymity, and use a custom domain when you don&amp;rsquo;t.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;While your new MX records are propagating, you may miss several emails.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Encryption in Email Practice</title><link>https://obsp.de/en/posts/encryption-in-email-practice/</link><guid isPermaLink="true">https://obsp.de/en/posts/encryption-in-email-practice/</guid><pubDate>Wed, 09 Jul 2025 22:06:00 +0800</pubDate><description>&lt;p&gt;Modern demands on email privacy are exceeding its original design and are still growing even more enormous. This is morbid.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at some certain use case: Alice created a banking/finance account (something similar to Revolut/Paypal), and her login credential are listed below:&lt;/p&gt;
&lt;figure class="code-block" data-language="text"&gt;
 &lt;figcaption class="code-block-header"&gt;
 &lt;span class="code-language"&gt;TEXT&lt;/span&gt;
 &lt;button class="copy-code" type="button"
 data-copy-label="Copy"
 data-copied-label="Copied!"
 data-failed-label="Copy failed"
 aria-label="Copy text code"&gt;Copy&lt;/button&gt;
 &lt;/figcaption&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;username: alice@example.org
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;password: You1-Should9-Use0-A7-Password4-Manager2!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Alice was careless, she didn&amp;rsquo;t follow the best practice of backup and lost her KeePass database (or her Vaultwarden database, or she forgot master password, or the paper that she wrote passwords on). Unfortunately, this is the only copy of the above username/password combination, there&amp;rsquo;s no way to retrieve.&lt;/p&gt;
&lt;p&gt;Nevertheless, Alice can reset her password by providing OTP sent to email address &lt;code&gt;alice@example.org&lt;/code&gt; . That&amp;rsquo;s wonderful, isn&amp;rsquo;t it?&lt;/p&gt;
&lt;p&gt;Well, that&amp;rsquo;s not a good idea, because OTP is transmitted in clear text email. There&amp;rsquo;s no insurance that only the sender and Alice could read the content. If Bob wants to access Alice&amp;rsquo;s bank account, which he is not permitted to, all he has to do is getting the email content and entering OTP before it expires (let&amp;rsquo;s say 30 minutes).&lt;/p&gt;
&lt;p&gt;Email and SMS have never been built with such consideration. It&amp;rsquo;s generally a bad idea to use email or SMS as some kind of account recovery method or 2-step verification method.&lt;/p&gt;
&lt;p&gt;Instead of coming up with something else, people start fixing email with the problem of storing and transmitting sensitive data in clear text. The transmission part is 90% done, as long as you force SSL/TLS instead of STARTTLS every handshake with mail server. But storing is a bit tricky.&lt;/p&gt;
&lt;p&gt;Technically, if Alice doesn&amp;rsquo;t want to do anything, and her email provider, as well as the bank&amp;rsquo;s email provider, use SSL/TLS and encrypt the data at rest (the key is selected by the provider, we assume, since Alice doesn&amp;rsquo;t do anything), then Bob won&amp;rsquo;t be able to decrypt email from network traffic (that&amp;rsquo;s how TLS works) or hacking the provider&amp;rsquo;s machine.&lt;/p&gt;
&lt;p&gt;Under some circumstances, this is more than enough. However, four entities still holds the ability to read the email. Ideally, only Alice and the bank should be able to read the email, but as both their email providers hold the encryption key, they could read them if they want to. In most cases, we could safely assume that Alice&amp;rsquo;s OTP doesn&amp;rsquo;t interest the provider. But they might still be interested in Alice&amp;rsquo;s financial situation, like which bank she is dealing with, how much money she spends each month for what purpose, and in which country she is. This is a big no for privacy advocates and pursuits.&lt;/p&gt;
&lt;p&gt;With such concerns, PGP emerges, and evolves. Nowadays, the most widely accepted email end-to-end encryption standard is OpenPGP. The downsides would be quite clear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unless the sender is willing to do encryption, you can&amp;rsquo;t guarantee e2ee for the incoming email. That is to say, if Alice want to keep her banking emails private with OpenPGP, she has to reach out to the bank, provide them her public key and says &amp;ldquo;Please use encryption every time you send a report to email address &lt;code&gt;alice@example.org&lt;/code&gt; &amp;ldquo;, and get refused.&lt;/li&gt;
&lt;li&gt;Sender and receiver address remain in cleartext. I&amp;rsquo;m not sure if that would count as a downside, since you can&amp;rsquo;t send/receive any type of messages without an identifier. (But I&amp;rsquo;m still listing, since UUID or random numbers are less likely to be associated with a real identity of Alice than &lt;code&gt;alice@example.org&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;Subject remains in cleartext. You use encryption, good. But &amp;ldquo;Subject: Your OTP is 123456&amp;rdquo; would just disclose everything (as far as I am concerned, WhatsApp does this).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To handle with the above problems,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some email providers, for example, &lt;a href="https://tuta.com"&gt;Tuta&lt;/a&gt;, ditched OpenPGP and refuses to be compatible with universal email protocols such as IMAP. The advantage is evident, they can encrypt subject now, but the cost is high - you have to use their email client (although it&amp;rsquo;s open source on each platform, free to use and does not contain trackers), which makes it a lock-in. The worst part is without OpenPGP, you can only do symmetric encryption if your correspondent isn&amp;rsquo;t on Tuta.&lt;/li&gt;
&lt;li&gt;Milder workarounds would be &lt;a href="https://simplelogin.io"&gt;SimpleLogin&lt;/a&gt; and &lt;a href="https://addy.io"&gt;Addy.io&lt;/a&gt;&amp;rsquo;s &amp;ldquo;Generic Subject&amp;rdquo; function when encrypting incoming emails, but you can&amp;rsquo;t pretend the clear text subject is non-existent on your correspondent&amp;rsquo;s mail server.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mailbox.org"&gt;mailbox.org&lt;/a&gt; as well as &lt;a href="https://proton.me"&gt;Proton&lt;/a&gt; provides inbox encryption, this is technically the same as the second one, when you receive an email, the provider uses your public key to encrypt immediately (it&amp;rsquo;s kind of worth noticing that while SL, addy and mailbox doesn&amp;rsquo;t require your private key to do this, Proton would generate and manage the key pair for you, and there doesn&amp;rsquo;t really seem to be an option to encrypt incoming emails with only your public key uploaded. My experience on Proton is limited and if this got fixed in the future please kindly inform me), but this isn&amp;rsquo;t end-to-end encryption, and it would only be useful if you trust every entity that could access your email except your email provider (ironically, this is the real threat model for many people).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.email"&gt;open.email&lt;/a&gt; looks forward to an email network over https, but can it really be called email if you can&amp;rsquo;t communicate with traditional protocol accounts?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Encryption enjoyers are very rare among all the email users, but those who rely on email for critical or sensitive information are the majority. Modern expectations for email include being an authenticator, a secret manager, a platform to issue invoice and pay for them, and so on. To satisfy all the exceedingly serious demands, more and more patches have been introduced, but non of them can be as convincing as email itself to forge an evolution. What would be waiting for us in the future of email?&lt;/p&gt;</description></item></channel></rss>