<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/feed-en.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<title>Rotatio (en)</title>
	<subtitle>Raffael Jesche ist Künstler, Webentwickler und immer auf der Suche nach neuen Herausforderungen.</subtitle>
	<link rel="self" type="application/atom+xml" href="https://rotatio.de/feed_en.xml" />
	<link rel="alternate" type="text/html" hreflang="en" href="https://rotatio.de/" />
	<link rel="alternate" type="text/html" hreflang="en" href="https://rotatio.de/home/" />
	<updated>2026-09-28T00:20:29Z</updated>
	<id>https://rotatio.de/</id>
	<logo>/assets/img/logo.svg</logo>
	<author>
		<name>Raffael Jesche</name>
	</author>
	<entry xml:lang="en">
		<title>Multiplane – a frontend on top of Cockpit CMS</title>
		<link rel="alternate" type="text/html" hreflang="en" href="https://rotatio.de/software/multiplane/" />
		<published>2026-09-28T01:12:19Z</published>
		<updated>2026-09-28T01:12:19Z</updated>
		<id>https://rotatio.de/software/multiplane/</id>
		<category term="frontend" />
		<category term="framework" />
		<category term="PHP" />
		<category term="JavaScript" />
		<category term="HTML" />
		<category term="CSS" />
		<category term="Cockpit CMS" />
		<category term="CMS" />
		<content type="html">
			&lt;ul&gt;
	&lt;li&gt;Tags:
		&lt;a href=&quot;/tags/frontend/&quot; rel=&quot;tag&quot;&gt;frontend&lt;/a&gt;,
		&lt;a href=&quot;/tags/framework/&quot; rel=&quot;tag&quot;&gt;framework&lt;/a&gt;,
		&lt;a href=&quot;/tags/php/&quot; rel=&quot;tag&quot;&gt;PHP&lt;/a&gt;,
		&lt;a href=&quot;/tags/javascript/&quot; rel=&quot;tag&quot;&gt;JavaScript&lt;/a&gt;,
		&lt;a href=&quot;/tags/html/&quot; rel=&quot;tag&quot;&gt;HTML&lt;/a&gt;,
		&lt;a href=&quot;/tags/css/&quot; rel=&quot;tag&quot;&gt;CSS&lt;/a&gt;,
		&lt;a href=&quot;/tags/cockpit-cms/&quot; rel=&quot;tag&quot;&gt;Cockpit CMS&lt;/a&gt;,
		&lt;a href=&quot;/tags/cms/&quot; rel=&quot;tag&quot;&gt;CMS&lt;/a&gt;
	&lt;/li&gt;
	&lt;li&gt;
		Start:
		&lt;time datetime=&quot;2019-02-20T01:00:00.000+01:00&quot;&gt;20.02.2019&lt;/time&gt;
	&lt;/li&gt;
	&lt;li&gt;
		End: &lt;time datetime=&quot;2024-02-05T01:00:00.000+01:00&quot;&gt;05.02.2024&lt;/time&gt;
	&lt;/li&gt;
	&lt;li&gt;Roles:
		developer
	&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

			&lt;p&gt;Multiplane is a lightweight PHP frontend – based on the headless &lt;a href=&quot;/software/cockpit/&quot;&gt;Cockpit&lt;/a&gt; CMS. It was never meant as a one-click solution, but as a base for developers, who like to fiddle, to create individual websites.&lt;/p&gt;
&lt;p&gt;Unfortunately, it never took off. After &lt;s&gt;wasting half a decade of my life&lt;/s&gt; many iterations of learning and fine-tuning, I had to stop. The project heavily relied on internal APIs – and &lt;a href=&quot;https://discourse.getcockpit.com/t/why-i-wont-upgrade-to-cockpit-cms-v2-and-might-leave-the-project/2860&quot;&gt;Multiplane became incompatible with Cockpit version 2&lt;/a&gt;.&lt;/p&gt;
&lt;details class=&quot;toc&quot;&gt;
	&lt;summary&gt;Toggle table of contents&lt;/summary&gt;
	&lt;nav aria-labelledby=&quot;article-toc&quot;&gt;
		&lt;h2 id=&quot;article-toc&quot;&gt;Table of contents&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;#what-i&#39;ve-learned&quot;&gt;What I’ve learned&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#what-i&#39;ve-done&quot;&gt;What I’ve done&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#components&quot;&gt;Components&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;#cockpit-cms-(backend)&quot;&gt;Cockpit CMS (backend)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#multiplane-(frontend)&quot;&gt;Multiplane (frontend)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#uniqueslugs-addon&quot;&gt;UniqueSlugs addon&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#formvalidation-addon&quot;&gt;FormValidation addon&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#other-addons&quot;&gt;Other addons&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#base-and-child-theme&quot;&gt;Base and child theme&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#resources&quot;&gt;Resources&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;&lt;/details&gt;
&lt;h2 id=&quot;what-i&#39;ve-learned&quot; tabindex=&quot;-1&quot;&gt;What I’ve learned&lt;/h2&gt;
&lt;p&gt;A lot! About object oriented programming, error handling, reactive UI frameworks, open source licenses, user interaction, writing documentation, weird edge cases and much more…&lt;/p&gt;
&lt;p&gt;Communicating in English with people from all around the globe was a great experience. At some point I noticed, that my restricted vocabulary wasn’t too small. It was actually the right amount to chat with other non-native speakers.&lt;/p&gt;
&lt;p&gt;Finding and solving bugs, helping others, seeing my own source code being used by others felt great. Now I know, how content management systems work. I touched all parts, wrote many of them myself, read vendor libraries and fixed bugs over there.&lt;/p&gt;
&lt;p&gt;Everything’s great, right? Some hard learned lessons:&lt;/p&gt;
&lt;p&gt;Writing a CMS is too big for one person. Also for two. And probably for three.&lt;/p&gt;
&lt;p&gt;Don’t use internal APIs! (Well…, I still do. But I keep the number low and add code comments with warnings now.)&lt;/p&gt;
&lt;p&gt;Avoid vendor lock-in! Always! And keep the number of dependencies low.&lt;/p&gt;
&lt;p&gt;Accessibility doesn’t work as an after-thought.&lt;/p&gt;
&lt;p&gt;There is no such thing as an “open source community”. Rarely, I encounter beautiful people and have intense, eye-opening conversations. Sometimes, I can peek in a brilliant mind by reading their source codes. Often, I lose faith in humanity. Mostly it’s reclusive, sometimes lonely – or para-social.&lt;/p&gt;
&lt;p&gt;Don’t use an open source project as the primary source for your self-esteem. You may break with the next vendor update. As a general rule: Don’t derive your self-esteem from a single source – not from work, not from a single person, not from your pet project. Or as marketers would say: diversification is key.&lt;/p&gt;
&lt;h2 id=&quot;what-i&#39;ve-done&quot; tabindex=&quot;-1&quot;&gt;What I’ve done&lt;/h2&gt;
&lt;p&gt;I wrote a head for a headless CMS. “Why reinventing the wheel?” you may ask.&lt;/p&gt;
&lt;p&gt;In short: I wasn’t able to find an existing system, that matched my goals:&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; role=&quot;doc-noteref&quot; id=&quot;fnref1&quot; aria-label=&quot;Footnote 1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Privacy&lt;/strong&gt; by design and by default&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer friendly&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;no plugins needed to deactivate half of the core features&lt;/li&gt;
&lt;li&gt;ability to adjust everything&lt;/li&gt;
&lt;li&gt;theming without div soup&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean backend&lt;/strong&gt; for my clients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured data&lt;/strong&gt; to keep the system and data portable and future proof&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modular&lt;/strong&gt; and reusable code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic&lt;/strong&gt; html, &lt;strong&gt;responsive&lt;/strong&gt; css, &lt;strong&gt;usable without JavaScript&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multilingualism&lt;/strong&gt; by design&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Long answer: In the past, I mostly worked with WordPress and the static site generator (SSG) jekyll. Sometimes I had a look at different systems.&lt;/p&gt;
&lt;p&gt;I liked jekyll’s data structure. Personally, I don’t have a problem with writing text in markdown, changing a data set in YAML or adding missing (liquid) templates matching that change. But you can’t expect this from non-programmers. They need a well-arranged user interface. Also, I missed forms without loading third party services via JavaScript – an unsolved problem of all SSGs. And… it’s written in Ruby, which I never learned or even touched. I can’t write or modify plugins.&lt;/p&gt;
&lt;p&gt;WordPress seems to be user friendly – as long, as your data fits their predefined structure. It’s not developer friendly: more than 2.000 functions in the global scope, backwards compatibility to the outdated PHP5, many similar named functions, no output buffer. Their plugin ecosystem is full of adware and bloatware. No sensibility for privacy (e. g. Google fonts, Gravatar), security issues… the list goes on.&lt;/p&gt;
&lt;p&gt;With Cockpit, I found the ideal backend. It’s easy to extend, stores data in a structured way and provides multiple interfaces to query that data. While exploring it, I discovered a lot of on-board tools – for building a frontend.&lt;/p&gt;
&lt;p&gt;My first prototype was Monoplane, only useful for simple, single-language portfolio websites. It evolved to CpMultiplane – later renamed to Multiplane – with many more features like multilingualism, full-text search and custom post types.&lt;/p&gt;
&lt;h2 id=&quot;components&quot; tabindex=&quot;-1&quot;&gt;Components&lt;/h2&gt;
&lt;p&gt;Multiplane is both: a wrapper (or framework) and a Cockpit addon. It loads Cockpit as a library, sets some constants, changes a few variables and than registers itself, the module Multiplane.&lt;/p&gt;
&lt;h3 id=&quot;cockpit-cms-(backend)&quot; tabindex=&quot;-1&quot;&gt;Cockpit CMS (backend)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/software/cockpit/&quot;&gt;Cockpit&lt;/a&gt; is headless without a frontend. It is created for pure data management in a structured way and has multiple interfaces to get or change that data. Multiplane uses the PHP API for the frontend and the default user interface as backend.&lt;/p&gt;
&lt;p&gt;The most important libraries are already included, but in total it was kept clean and simple. It’s modular, event based and was made to be customized.&lt;/p&gt;
&lt;img src=&quot;/software/multiplane/xwcccMa2in-1000.svg&quot; data-anchor=&quot;multiplane&quot; class=&quot;breakout ratio-landscape&quot; alt=&quot;Graph showing Cockpit&#39;s 4 API endpoints (CLI, REST, GUI, PHP library) around it&#39;s core with bidirectional arrows. Library points to Multiplane, bidirectional. REST points to Android and web app, bidirectional. Visitors point to Multiplane and Web app. User groups to GUI, Web App and Android App. Admin to CLI and GUI. All human pointers are one-directional.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1000&quot; height=&quot;652&quot; /&gt;
&lt;h3 id=&quot;multiplane-(frontend)&quot; tabindex=&quot;-1&quot;&gt;Multiplane (frontend)&lt;/h3&gt;
&lt;p&gt;This module is the gateway between backend (Cockpit) and frontend (Multiplane) to create different configuration profiles or to adjust the user interface.&lt;/p&gt;
&lt;p&gt;It also takes care of translating url paths into database request and to deliver the data as HTML.&lt;/p&gt;
&lt;h3 id=&quot;uniqueslugs-addon&quot; tabindex=&quot;-1&quot;&gt;UniqueSlugs addon&lt;/h3&gt;
&lt;p&gt;By default, Cockpit only provides cryptic strings as unique identifiers. This addon creates unique, human-readable and SEO-friendly url parts from page titles (called “slug”).&lt;/p&gt;
&lt;h3 id=&quot;formvalidation-addon&quot; tabindex=&quot;-1&quot;&gt;FormValidation addon&lt;/h3&gt;
&lt;p&gt;Cockpit’s core Forms module is quite rudimentary. The FormValidation addon provides a form builder and validator.&lt;/p&gt;
&lt;h3 id=&quot;other-addons&quot; tabindex=&quot;-1&quot;&gt;Other addons&lt;/h3&gt;
&lt;p&gt;Incomplete list of more useful addons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ImageResize to handle different image formats (thumbnail, medium, big) automatically&lt;/li&gt;
&lt;li&gt;rljUtils for useful helpers, UI adjustments and security fixes in multi-user setups&lt;/li&gt;
&lt;li&gt;VideoLinkField to extract information and download thumbnails from videos on YouTube or Vimeo&lt;/li&gt;
&lt;li&gt;EditorFormats (by Paulo Gomes) to fine-tune the WYSIWYG editor&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;base-and-child-theme&quot; tabindex=&quot;-1&quot;&gt;Base and child theme&lt;/h3&gt;
&lt;p&gt;Multiplane ships with one base theme, creatively named “rljbase”, that serves as skeleton for child themes and provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;template files for most use cases (using “Lexy” templating, part of Cockpit v1’s core)&lt;/li&gt;
&lt;li&gt;a rudimentary, responsive layout (CSS)&lt;/li&gt;
&lt;li&gt;a lightweight JavaScript library &lt;code class=&quot;language-text&quot;&gt;mp.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It works without JavaScript, but without JS, some features would be missing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;simple cookie management&lt;/li&gt;
&lt;li&gt;handling privacy events (cookie banner when embedding third-party resources)&lt;/li&gt;
&lt;li&gt;video embedding
&lt;ul&gt;
&lt;li&gt;without JS: display linked thumbnail to video&lt;/li&gt;
&lt;li&gt;with JS: emit privacy event for user consent before embedding&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;simple image lightbox&lt;/li&gt;
&lt;li&gt;simple mail address protection (converts obfuscated addresses to valid ones)&lt;/li&gt;
&lt;li&gt;preventing a &lt;code class=&quot;language-text&quot;&gt;:target&lt;/code&gt; jump in the pure CSS mobile navigation&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; role=&quot;doc-noteref&quot; id=&quot;fnref2&quot; aria-label=&quot;Footnote 2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/software/multiplane/jCyQrZsj-L-500.webp 500w, /software/multiplane/jCyQrZsj-L-900.webp 900w, /software/multiplane/jCyQrZsj-L-1349.webp 1349w&quot; sizes=&quot;(min-width: 98ch) 80ch, calc(100vw - 3rem)&quot; /&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/software/multiplane/jCyQrZsj-L-500.png&quot; class=&quot;ratio-landscape&quot; alt=&quot;Screenshot of docs theme&quot; width=&quot;1349&quot; height=&quot;888&quot; srcset=&quot;/software/multiplane/jCyQrZsj-L-500.png 500w, /software/multiplane/jCyQrZsj-L-900.png 900w, /software/multiplane/jCyQrZsj-L-1349.png 1349w&quot; sizes=&quot;(min-width: 98ch) 80ch, calc(100vw - 3rem)&quot; /&gt;&lt;/picture&gt;
&lt;figcaption&gt;
CpMultiplane docs with the &quot;docs&quot; child theme. A two-column layout with a wide content area with light background and a thin, dark sidebar on the left with logo, search bar and a vertical navigation.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;resources&quot; tabindex=&quot;-1&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://codeberg.org/multiplane&quot;&gt;Multiplane on Codeberg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discourse.getcockpit.com/t/monoplane-cpmultiplane-simple-php-frontend-that-uses-cockpit-as-a-library/720&quot;&gt;Thread in Cockpit forum about Monoplane and Multiplane&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discourse.getcockpit.com/t/why-i-wont-upgrade-to-cockpit-cms-v2-and-might-leave-the-project/2860&quot;&gt;Thread in Cockpit forum about why I left&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/raffaelj/cpmultiplane.rlj.me&quot;&gt;Multiplane docs repository&lt;/a&gt; (website with docs is offline)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zeraton.gitlab.io/cockpit-docs/&quot;&gt;inofficial docs for Cockpit v1 by zeraton&lt;/a&gt; (official v1 docs are offline)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/agentejo/cockpit/&quot;&gt;Cockpit v1 source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://getcockpit.com/&quot;&gt;official Cockpit website&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
&lt;h2 id=&quot;footnotes-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Accessibility wasn’t part of my listed goals back than. &lt;s&gt;Shame on me.&lt;/s&gt; Better late than never. &lt;a href=&quot;#fnref1&quot; role=&quot;doc-backlink&quot; class=&quot;footnote-backref&quot; aria-label=&quot;Back to reference 1&quot; title=&quot;Back to reference 1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;At that time, I was a bit obsessed with (ugly) CSS hacks to avoid JS wherever possible. Nowadays, I prefer progressive enhancement – with functional pure HTML/CSS defaults and fancy JS behavior. &lt;a href=&quot;#fnref2&quot; role=&quot;doc-backlink&quot; class=&quot;footnote-backref&quot; aria-label=&quot;Back to reference 2&quot; title=&quot;Back to reference 2&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
		</content>
	</entry>
	<entry xml:lang="en">
		<title>Cockpit – a headless, API-first CMS</title>
		<link rel="alternate" type="text/html" hreflang="en" href="https://rotatio.de/software/cockpit/" />
		<published>2026-09-28T01:12:19Z</published>
		<updated>2026-09-28T01:12:19Z</updated>
		<id>https://rotatio.de/software/cockpit/</id>
		<category term="PHP" />
		<category term="headless" />
		<category term="CMS" />
		<category term="Riot.js" />
		<category term="Vue.js" />
		<category term="UIkit" />
		<category term="Cockpit CMS" />
		<content type="html">
			&lt;ul&gt;
	&lt;li&gt;Tags:
		&lt;a href=&quot;/tags/php/&quot; rel=&quot;tag&quot;&gt;PHP&lt;/a&gt;,
		&lt;a href=&quot;/tags/headless/&quot; rel=&quot;tag&quot;&gt;headless&lt;/a&gt;,
		&lt;a href=&quot;/tags/cms/&quot; rel=&quot;tag&quot;&gt;CMS&lt;/a&gt;,
		&lt;a href=&quot;/tags/riot-js/&quot; rel=&quot;tag&quot;&gt;Riot.js&lt;/a&gt;,
		&lt;a href=&quot;/tags/vue-js/&quot; rel=&quot;tag&quot;&gt;Vue.js&lt;/a&gt;,
		&lt;a href=&quot;/tags/uikit/&quot; rel=&quot;tag&quot;&gt;UIkit&lt;/a&gt;,
		&lt;a href=&quot;/tags/cockpit-cms/&quot; rel=&quot;tag&quot;&gt;Cockpit CMS&lt;/a&gt;
	&lt;/li&gt;
	&lt;li&gt;
		Start:
		&lt;time datetime=&quot;2018-05-31T02:00:00.000+02:00&quot;&gt;31.05.2018&lt;/time&gt;
	&lt;/li&gt;
	&lt;li&gt;
		End: &lt;time datetime=&quot;2023-11-26T01:00:00.000+01:00&quot;&gt;26.11.2023&lt;/time&gt;
	&lt;/li&gt;
	&lt;li&gt;Roles:
		code contributor,
		addon developer,
		forum moderator
	&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

			&lt;p&gt;&lt;a href=&quot;https://getcockpit.com/&quot;&gt;Cockpit&lt;/a&gt; is a headless Content Management System (CMS), developed by Artur Heinze. The core is written in PHP and it has a permissive license (MIT). “Headless” means: backend for data and content, but no inbuilt frontend for websites.&lt;/p&gt;
&lt;p&gt;This article focuses mostly on Cockpit v1. Soon after the rewrite to v2, I left the project.&lt;/p&gt;
&lt;details class=&quot;toc&quot;&gt;
	&lt;summary&gt;Toggle table of contents&lt;/summary&gt;
	&lt;nav aria-labelledby=&quot;article-toc&quot;&gt;
		&lt;h2 id=&quot;article-toc&quot;&gt;Table of contents&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;#my-role&quot;&gt;My role&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;#some-numbers&quot;&gt;Some numbers&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#how-cockpit-works&quot;&gt;How Cockpit works&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#why-i-chose-it&quot;&gt;Why I chose it&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#why-i-left&quot;&gt;Why I left&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#addons&quot;&gt;Addons&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#other-stuff&quot;&gt;Other stuff&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;&lt;/details&gt;
&lt;h2 id=&quot;my-role&quot; tabindex=&quot;-1&quot;&gt;My role&lt;/h2&gt;
&lt;p&gt;For multiple years, I helped as a volunteer to move the project forward. I fixed bugs, helped sorting out missing or outdated documentation, reported multiple security issues and wrote many addons.&lt;/p&gt;
&lt;p&gt;After a few months, I became moderator in their community/support forum. Moderating was a low effort. The user base is small and – as I observed in many development focused forums – the troll count was immensely low. Mostly I helped explaining (hidden) core concepts or solving weird edge cases. To be honest: helping strangers was &lt;em&gt;one&lt;/em&gt; motivation, but solving random code puzzles was the real fun. The forum and issue section became my favorite place for procrastination.&lt;/p&gt;
&lt;p&gt;It was an interesting and intense time of my life.&lt;/p&gt;
&lt;h3 id=&quot;some-numbers&quot; tabindex=&quot;-1&quot;&gt;Some numbers&lt;/h3&gt;
&lt;ul class=&quot;numbers&quot; role=&quot;list&quot;&gt;
&lt;li&gt;&lt;span&gt;1&lt;/span&gt; &lt;a href=&quot;/software/multiplane/&quot;&gt;Frontend written&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;23&lt;/span&gt; &lt;a href=&quot;#addons&quot;&gt;Addons&lt;/a&gt;, 2 of them compatible with v2&lt;/li&gt;
&lt;li&gt;&lt;span&gt;51&lt;/span&gt; &lt;a href=&quot;https://github.com/agentejo/cockpit/pulls?q=is%3Apr+author%3Araffaelj&quot;&gt;Pull Requests for v1&lt;/a&gt; (31 merged, 5 open, 15 closed)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;66&lt;/span&gt; &lt;a href=&quot;https://github.com/agentejo/cockpit/issues?q=is%3Aissue+author%3Araffaelj&quot;&gt;Issues opened for v1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;13&lt;/span&gt; &lt;a href=&quot;https://github.com/Cockpit-HQ/Cockpit/pulls?q=is%3Apr+author%3Araffaelj&quot;&gt;Pull Requests for v2&lt;/a&gt; (8 merged, 1 open, 4 closed)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;21&lt;/span&gt; &lt;a href=&quot;https://github.com/Cockpit-HQ/Cockpit/issues?q=is%3Aissue+author%3Araffaelj&quot;&gt;Issues opened for v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I’m always astonished, when looking at &lt;a href=&quot;https://discourse.getcockpit.com/u/raffaelj/summary&quot;&gt;my forum statistics&lt;/a&gt;:&lt;/p&gt;
&lt;ul class=&quot;columns&quot; role=&quot;list&quot;&gt;
&lt;li&gt;636 days visited&lt;/li&gt;
&lt;li&gt;4 days read time&lt;/li&gt;
&lt;li&gt;916 topics viewed&lt;/li&gt;
&lt;li&gt;3.1k posts read&lt;/li&gt;
&lt;li&gt;28 likes given&lt;/li&gt;
&lt;li&gt;215 likes received&lt;/li&gt;
&lt;li&gt;36 topics created&lt;/li&gt;
&lt;li&gt;467 posts created&lt;/li&gt;
&lt;li&gt;66 solutions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-cockpit-works&quot; tabindex=&quot;-1&quot;&gt;How Cockpit works&lt;/h2&gt;
&lt;p&gt;As mentioned, it’s headless. Cockpit focuses on data modeling and providing interfaces to access that data:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Admin panel&lt;/strong&gt;: graphical user interface (GUI) for creating collections, modeling and maintaining data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;REST API&lt;/strong&gt;: access to data sets from outside, secured with API keys&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLI&lt;/strong&gt;: many tasks can be done and automated via command line&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;as PHP library&lt;/strong&gt;: access to all modules, functions and data with custom-made applications&lt;/li&gt;
&lt;/ol&gt;
&lt;img src=&quot;/software/cockpit/xwcccMa2in-1000.svg&quot; data-anchor=&quot;cockpit&quot; class=&quot;breakout ratio-landscape&quot; alt=&quot;Graph showing Cockpit&amp;#39;s four API endpoints (CLI, REST, GUI, PHP library) around it&amp;#39;s core with bidirectional arrows. Library points to Multiplane, bidirectional. REST points to Android app and Web App, bidirectional. Visitors point to Multiplane and Web App. User groups to GUI, Web App and Android App. Admin to CLI and GUI. All human pointers are one-directional.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1000&quot; height=&quot;652&quot; /&gt;
&lt;p&gt;The admin panel (backend) can be modified with addons. A frontend can be written in any language, accessing data via REST API. When frontend and backend live on the same server, they can communicate directly via PHP API or even via CLI.&lt;/p&gt;
&lt;p&gt;At its’ core, Cockpit speaks JSON – or more specifically: MongoDB. Everything is document-oriented. Coming from SQL, I had to readapt:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No relations: e. g. renaming a category means changing a string in all documents. This can be automated using the event system to check for changed categories after saving. But without shifting the mental model to a non-relational database, these things were confusing.&lt;/li&gt;
&lt;li&gt;Syntax and logic: applying filters (&lt;code class=&quot;language-text&quot;&gt;$and&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;$or&lt;/code&gt;) instead of trying to translate &lt;code class=&quot;language-text&quot;&gt;WHERE&lt;/code&gt; clauses.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And – although Cockpit speaks MongoDB – it comes with an abstraction layer (MongoLite) to use SQLite as database. And I like SQLite: no server, no passwords, no sql export before and after changing things in a local development setup – just copy and paste.&lt;/p&gt;
&lt;h2 id=&quot;why-i-chose-it&quot; tabindex=&quot;-1&quot;&gt;Why I chose it&lt;/h2&gt;
&lt;p&gt;In May 2018, after moving from WordPress to Github Pages (jekyll) with prose.io as UI, I searched again for alternatives. Until then, the term “headless” never crossed my mind. After knowing, what I was looking for, the concept convinced immediately.&lt;/p&gt;
&lt;p&gt;Self-hosting and a permissive license were mandatory and I was most skilled in PHP – which left only two options: Cockpit and Directus&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; role=&quot;doc-noteref&quot; id=&quot;fnref1&quot; aria-label=&quot;Footnote 1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;. Both had Gravatar implemented. I asked for an option to disable it, to respect users’ privacy. A few hours later, Artur committed an update and disabled it completely. Months later, I got an answer from Directus. They never thought about Gravatar being a privacy problem and considered changing it.&lt;/p&gt;
&lt;p&gt;Obviously, my decision was long done.&lt;/p&gt;
&lt;h2 id=&quot;why-i-left&quot; tabindex=&quot;-1&quot;&gt;Why I left&lt;/h2&gt;
&lt;p&gt;From the beginning, I was more interested in the PHP (and the CLI) API, than in the REST API. On top of the &lt;em&gt;headless&lt;/em&gt; Cockpit CMS v1, I wrote my own frontend (&lt;em&gt;head&lt;/em&gt;) &lt;a href=&quot;/software/multiplane/&quot;&gt;Multiplane&lt;/a&gt;. So my usage was unusual, not like intended. But I always cared about the headless paradigm when writing addons.&lt;/p&gt;
&lt;p&gt;Over the years, I had a deep understanding of all internals. My ecosystem relied on Cockpit’s configurability and hackability. Then Cockpit grew up. Version 2 was released. It looked nice and I liked many changes. Most of my addons could have been rewritten for compatibility, but Multiplane became incompatible. I really tried to find workarounds, but it was unfixable. My vision of Cockpit didn’t match Artur’s anymore.&lt;/p&gt;
&lt;p&gt;For a while, I kept a &lt;a href=&quot;https://codeberg.org/raffaelj/cockpit&quot;&gt;fork of Cockpit v1&lt;/a&gt; alive – until I had to admit, that years of my work became worthless. Sure, I learnt a lot. And I don’t blame Artur for moving forward and building a stable business model. But it still hurts.&lt;/p&gt;
&lt;p&gt;On top of that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;V2 re-introduced a security issue and multiple accessibility issues, I fixed in v1 already – very annoying.&lt;/li&gt;
&lt;li&gt;Some core addons became proprietary, which was fine in general. But I couldn’t skim git diffs anymore to understand the motivation for some random looking core changes. Also annoying.&lt;/li&gt;
&lt;li&gt;Multilingualism was poorly implemented.&lt;/li&gt;
&lt;li&gt;Accessibility was clearly an after-thought.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the &lt;a href=&quot;https://discourse.getcockpit.com/t/why-i-wont-upgrade-to-cockpit-cms-v2-and-might-leave-the-project/2860&quot;&gt;forum thread about my reasoning for leaving&lt;/a&gt;, I go into more (technical) details.&lt;/p&gt;
&lt;h2 id=&quot;addons&quot; tabindex=&quot;-1&quot;&gt;Addons&lt;/h2&gt;
&lt;p&gt;Some of my addons were used in production for years, some were experimental and unfinished.&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_Babel&quot;&gt;Babel&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Manage translations of addons and modules&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1 and v2&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_BetterMarkdown&quot;&gt;BetterMarkdown&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Cache, tasks lists and table of contents for markdown conversion&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_BlockEditor&quot;&gt;BlockEditor&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Visual block editor&lt;/dd&gt;
&lt;dd&gt;draft/experimental&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_BootManager&quot;&gt;BootManager&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;addon dependency management, module loading order, conditional loading&lt;/dd&gt;
&lt;dd&gt;draft/experimental&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_DashboardGrid&quot;&gt;DashboardGrid&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Custom dashboard with grid&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_Feed&quot;&gt;Feed&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;RSS Feeds for collections&lt;/dd&gt;
&lt;dd&gt;draft/experimental&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_FormValidation&quot;&gt;FormValidation&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Form validator and form builder&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_ImageResize&quot;&gt;ImageResize&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Resizes uploaded images automatically to predefined sizes&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit-scripts/tree/master/cockpit-cms-Migrate&quot;&gt;Migrate&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Migrate and convert models, data, accounts etc. from Cockpit CMS v1 to v2.&lt;/dd&gt;
&lt;dd&gt;draft/experimental, never published as official addon&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1 and v2&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_ModuleLink&quot;&gt;ModuleLink&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Field with linked data from modules&lt;/dd&gt;
&lt;dd&gt;draft/experimental&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_MoreOptions&quot;&gt;MoreOptions&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Utility addon with more options (collection grouping, color picker)&lt;/dd&gt;
&lt;dd&gt;draft/experimental, mostly superseded by rljUtils addon&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://codeberg.org/multiplane/cockpit-cms-Multiplane&quot;&gt;Multiplane addon&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Frontend logic (head) for the headless Cockpit CMS&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://codeberg.org/multiplane/cockpit-cms-MultiplaneDocsMods&quot;&gt;MultiplaneDocsMods&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Modifications for Multiplane docs&lt;/dd&gt;
&lt;dd&gt;simple 2-way bind between markdown files and SQLite database&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_phpLiteAdmin&quot;&gt;phpLiteAdmin&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;phpLiteAdmin interface for debugging and data inspection&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_rljUtils&quot;&gt;rljUtils&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Utility addon with security hardening, cosmetics and helpers&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_SelectRequestOptions&quot;&gt;SelectRequestOptions&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Select field with options based on custom requests&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_SheetExport&quot;&gt;SheetExport&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Spread sheet export (csv, odt, xls, xlsx)&lt;/dd&gt;
&lt;dd&gt;draft/experimental&lt;/dd&gt;
&lt;dd&gt;quickly modified version of the sheet export from my Tables addon&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_ShowInMenu&quot;&gt;ShowInMenu&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;re-enable the feature to display collections etc. in the system menu&lt;/dd&gt;
&lt;dd&gt;superseded by rljUtils addon&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_SimpleImageFixBlackBackgrounds&quot;&gt;SimpleImageFixBlackBackgrounds&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;provides a modified SimpleImage library to prevent black backgrounds in transparent images on some systems with a non-bundled PHP-GD version (really nasty bug)&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_Tables&quot;&gt;Tables&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Manage SQL tables with 1:m and m:n relations&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_UniqueSlugs&quot;&gt;UniqueSlugs&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;generates unique url paths (called “slugs”) from page titles&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_VideoLinkField&quot;&gt;VideoLinkField&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;extract, download and store thumbnails and meta data from videos on YouTube and Vimeo&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit_WhiteLabel&quot;&gt;WhiteLabel&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Custom logo and custom colors&lt;/dd&gt;
&lt;dd&gt;addon for Cockpit CMS v1&lt;/dd&gt;
&lt;/dl&gt;
&lt;h2 id=&quot;other-stuff&quot; tabindex=&quot;-1&quot;&gt;Other stuff&lt;/h2&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;a href=&quot;https://codeberg.org/raffaelj/cockpit&quot;&gt;Cockpit v1 fork&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Fork of Cockpit CMS v1 (next branch)&lt;/dd&gt;
&lt;dd&gt;stable release for usage with composer and PHP 8.1&lt;/dd&gt;
&lt;dd&gt;contains some improvements from v2&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/cockpit-scripts&quot;&gt;Cockpit scripts repository&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;A collection of code snippets and notes to modify Cockpit CMS v1 and v2&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&quot;https://github.com/raffaelj/wordpress_wpmultiplane&quot;&gt;WPMultiplane&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Cockpit CMS v1 and Multiplane implementation as a WordPress plugin&lt;/dd&gt;
&lt;dd&gt;draft/experimental&lt;/dd&gt;
&lt;dd&gt;still not sure, if brilliant or utterly stupid&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
&lt;h2 id=&quot;footnotes-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This was before their complete rewrite to node.js. &lt;a href=&quot;#fnref1&quot; role=&quot;doc-backlink&quot; class=&quot;footnote-backref&quot; aria-label=&quot;Back to reference 1&quot; title=&quot;Back to reference 1&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
		</content>
	</entry>
	<entry xml:lang="en">
		<title>How I developed an ADHD friendly fitness routine</title>
		<link rel="alternate" type="text/html" hreflang="en" href="https://rotatio.de/notizen/adhd-friendly-fitness-routine/" />
		<published>2026-09-01T01:24:43Z</published>
		<updated>2026-09-06T20:42:57Z</updated>
		<id>https://rotatio.de/notizen/adhd-friendly-fitness-routine/</id>
		<category term="ADHD" />
		<category term="routines" />
		<category term="ADHD strategies" />
		<content type="html">
			&lt;ul&gt;
	&lt;li&gt;Tags:
		&lt;a href=&quot;/tags/adhd/&quot; rel=&quot;tag&quot;&gt;ADHD&lt;/a&gt;,
		&lt;a href=&quot;/tags/routines/&quot; rel=&quot;tag&quot;&gt;routines&lt;/a&gt;,
		&lt;a href=&quot;/tags/adhd-strategies/&quot; rel=&quot;tag&quot;&gt;ADHD strategies&lt;/a&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

			&lt;p&gt;Recently I scrolled through social media and got stuck on a &lt;a href=&quot;https://masto.hackers.town/@c0debabe/117168207086163860&quot;&gt;simple question by @c0debabe&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote cite=&quot;https://masto.hackers.town/@c0debabe/117168207086163860&quot;&gt;
&lt;p&gt;Anyone have any ADHD friendly fitness tips or resources?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Three hours later (instant hyper focus), my deconstruction was done. Now (three days later doing other stuff) I finally finished this text.&lt;/p&gt;
&lt;details class=&quot;toc&quot;&gt;
	&lt;summary&gt;Toggle table of contents&lt;/summary&gt;
	&lt;nav aria-labelledby=&quot;article-toc&quot;&gt;
		&lt;h2 id=&quot;article-toc&quot;&gt;Table of contents&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;#solution&quot;&gt;Solution&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;#fun&quot;&gt;Fun&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#short&quot;&gt;Short&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#daily&quot;&gt;Daily&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#how-i-got-there&quot;&gt;How I got there&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#side-effects&quot;&gt;Side effects&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;#trivia&quot;&gt;Trivia&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;&lt;/details&gt;
&lt;h2 id=&quot;solution&quot; tabindex=&quot;-1&quot;&gt;Solution&lt;/h2&gt;
&lt;p&gt;It must be &lt;strong&gt;fun&lt;/strong&gt;, &lt;strong&gt;short&lt;/strong&gt; and &lt;strong&gt;daily&lt;/strong&gt;. I practice 10 minutes. Every morning. Before anything else.&lt;/p&gt;
&lt;h3 id=&quot;fun&quot; tabindex=&quot;-1&quot;&gt;Fun&lt;/h3&gt;
&lt;p&gt;You’ll never see me jogging or going to the gym. But I love dancing – in my case: playing &lt;a href=&quot;https://en.wikipedia.org/wiki/Hula_hoop&quot;&gt;Hula Hoop&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Fire_staff#Dragonstaff&quot;&gt;Dragon Staff&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;short&quot; tabindex=&quot;-1&quot;&gt;Short&lt;/h3&gt;
&lt;p&gt;It’s short enough to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;get started without being overwhelmed&lt;/li&gt;
&lt;li&gt;do it while other things seem more important&lt;/li&gt;
&lt;li&gt;pull through the last 4 minutes when I’m already bored or feel sick&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s long enough to see and feel an effect.&lt;/p&gt;
&lt;h3 id=&quot;daily&quot; tabindex=&quot;-1&quot;&gt;Daily&lt;/h3&gt;
&lt;p&gt;And it’s daily! If I forget it once, I still practice a whole hour per week. Missing a weekly one-hour session instead would halve the overall time. Missing a weekly routine twice probably ends it for all eternity.&lt;/p&gt;
&lt;h2 id=&quot;how-i-got-there&quot; tabindex=&quot;-1&quot;&gt;How I got there&lt;/h2&gt;
&lt;p&gt;It evolved from a daily reminder (10 minutes during the day). Often I didn’t practice. Instead of feeling bad, I tried to double the time after a missed day.&lt;/p&gt;
&lt;p&gt;I coupled it with watching news (another task checked) or with procrastinating on YouTube (didn’t feel bad afterwards, because at least I practiced).&lt;/p&gt;
&lt;p&gt;At some point I finally realized, that mornings are a useless time span. I literally can’t think. But I’m able to move.  So I replaced 10 minutes of staring into the void with training.&lt;/p&gt;
&lt;p&gt;After realizing, that my emotions are tightly coupled to music, I added “5 minutes listening to music” to my daily tasks. Now I could check two daily tasks after one training session from my list. Watching news moved to the morning bath routine.&lt;/p&gt;
&lt;p&gt;Goals helped a lot. After committing to a show at an open stage, I had to practice multiple times per week. Goal: being able to speak after 10 minutes of movement.&lt;/p&gt;
&lt;p&gt;Some more iterations, some more goals, a lot more awareness about myself… Now every day starts with 30 minutes of training and dancing. Maybe I’ll up it to 40 minutes soon. If I’m in a rush, 10 minutes is the minimum. Not debatable.&lt;/p&gt;
&lt;h2 id=&quot;side-effects&quot; tabindex=&quot;-1&quot;&gt;Side effects&lt;/h2&gt;
&lt;p&gt;Making it a habit solved many problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An empty, useless time span every morning is filled with something healthy, that I enjoy.&lt;/li&gt;
&lt;li&gt;Training, dancing and music help me to get awake. Often I’m able to think in less than two hours.&lt;/li&gt;
&lt;li&gt;Making decisions – especially in the morning – is exhausting. Because it’s a strict routine, I don’t have to decide anything. When I bust myself debating about something more important, I remember the reasons: I need it to stop staring into the void and to be able to make decisions. Realizing it and maintaining my routine makes me happy.&lt;/li&gt;
&lt;li&gt;Some days are useless. Nothing works. But at least I practiced.&lt;/li&gt;
&lt;li&gt;Finishing two tasks (music, training) from my daily reminder – every morning – causes a small dopamine spike.&lt;/li&gt;
&lt;li&gt;The music stays on after practicing and causes dancing during breakfast. Nice! Sadly, it also has bad side effects:
&lt;ol&gt;
&lt;li&gt;I can’t hear my shower timer anymore (can live with that).&lt;/li&gt;
&lt;li&gt;I don’t hear my coffee sizzling. This is dangerous (fire involved, old gas stove). If I don’t eat nearby, I have to remember to start the coffee timer. I accept that risk.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;After a few months I really know my old music library. I listened to albums, I heard decades ago, which brought back childhood memories or great moments at concerts.&lt;/li&gt;
&lt;li&gt;The last thing I want to do in the morning is tidying up. So I always clean my living room to have enough space for training tomorrow.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;“Just” make it a habit. To get there, combine fun, multiple goals and other side effects.&lt;/p&gt;
&lt;p&gt;Accept, that a new routine will break some others. When you notice it, deconstruct it (writing, talking).&lt;/p&gt;
&lt;p&gt;Really: make notes! In a few months or years, that beautiful daily training routine will suddenly fade away. Some side effect may be lost or not important anymore. But they were essential to start the routine. Using full-text-search in my notes folder helps to find the lost path. Now I can try to replace anchor points to get started again. Or I just needed that reminder (loved to do that thing every day in the past, but randomly stopped months ago) to restart the routine immediately.&lt;/p&gt;
&lt;p&gt;It’s that easy ;-)&lt;/p&gt;
&lt;h2 id=&quot;trivia&quot; tabindex=&quot;-1&quot;&gt;Trivia&lt;/h2&gt;
&lt;p&gt;Three days ago, I allowed myself to have a break. No obligations, no appointments, nothing urgent (OK, my plan was to clean up – nothing &lt;em&gt;very&lt;/em&gt; urgent). So I scrolled through social media, read blogs and got stuck on that question.&lt;/p&gt;
&lt;p&gt;A few lines became an article. After pausing my “monthly task” of publishing a blog post for four months, this felt great. Also I planned to write about my ADHD strategies anyways, but didn’t expect it to be in English (new task: translate it to German). Because of that sudden instant hyper focus, I forgot to finish my morning routine (brushing teeth, watching news). Also I forgot to eat. Because I didn’t go to the supermarket (part of the plan to tidy up), I ate muesli for lunch after 7pm – which caused heartburn (hungry and no time to soak because the supermarket closed at 9pm).&lt;/p&gt;
&lt;p&gt;During the last hours, to finish this post, I learned a lot. About the blockquote’s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/blockquote#cite&quot;&gt;cite attribute&lt;/a&gt; or the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/cite&quot;&gt;cite tag&lt;/a&gt;. I started to read a blog post about &lt;a href=&quot;https://www.matuzo.at/blog/2026/html-boilerplate&quot;&gt;@matuzo’s head section&lt;/a&gt; I found in @c0debabe’s feed – and was able to stop that side quest. Then I found a bug in &lt;a href=&quot;https://github.com/arve0/markdown-it-attrs&quot;&gt;markdown-it-attr&lt;/a&gt; in combination with &lt;a href=&quot;https://github.com/markdown-it/markdown-it/blob/master/src/rules_inline/linkify.ts&quot;&gt;markdown-it’s linkify inline parser&lt;/a&gt;, read many lines of source code and found a &lt;a href=&quot;https://codeberg.org/raffaelj/rotatio.de-11ty/commit/6601b3ef4234072133b9e9ccac8978c7e5a5120f&quot;&gt;workaround&lt;/a&gt; (new task: open bug report).&lt;/p&gt;
&lt;p&gt;Keep this in mind, if you want to trust my advice about building routines ;-)&lt;/p&gt;
&lt;p&gt;But at least I practiced this morning.&lt;/p&gt;

		</content>
	</entry>
	<entry xml:lang="en">
		<title>Multiple domains, rewrite rules and localhost</title>
		<link rel="alternate" type="text/html" hreflang="en" href="https://rotatio.de/notizen/domain-redirect-htaccess/" />
		<published>2025-11-18T20:10:35Z</published>
		<updated>2026-06-23T18:14:54Z</updated>
		<id>https://rotatio.de/notizen/domain-redirect-htaccess/</id>
		<category term="Uberspace" />
		<category term="htaccess" />
		<content type="html">
			&lt;ul&gt;
	&lt;li&gt;Tags:
		&lt;a href=&quot;/tags/uberspace/&quot; rel=&quot;tag&quot;&gt;Uberspace&lt;/a&gt;,
		&lt;a href=&quot;/tags/htaccess/&quot; rel=&quot;tag&quot;&gt;htaccess&lt;/a&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;

			&lt;p&gt;So yesterday I finally published this shiny, new website. After weeks of work to tell 11ty to match (most of) my expactations it felt like a relief. But a new problem came up: redirecting some of my other domains to this site.&lt;/p&gt;
&lt;p&gt;As mentioned in &lt;a href=&quot;/notizen/neuer-blog-neues-glueck-mit-11ty/&quot;&gt;my last post (de)&lt;/a&gt;, I have a small collection of domains. One is only used for mails: &lt;code class=&quot;language-text&quot;&gt;raffaeljesche.de&lt;/code&gt;. Another one from my first website &lt;code class=&quot;language-text&quot;&gt;verstyler.de&lt;/code&gt;, abandoned for a few years and re-acquired recently, collects dust, too. Both should redirect to &lt;code class=&quot;language-text&quot;&gt;rotatio.de&lt;/code&gt; now.&lt;/p&gt;
&lt;p&gt;The following text is very technical. I expect you to know about the basics of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html&quot;&gt;Apache mod_rewrite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Domain_Name_System&quot;&gt;Domain Name System (DNS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_HTTP_status_codes&quot;&gt;HTTP status codes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now let’s move on (if you’re still interested).&lt;/p&gt;
&lt;h2 id=&quot;options&quot; tabindex=&quot;-1&quot;&gt;Options&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.inwx.de/&quot;&gt;INWX&lt;/a&gt; url redirect in DNS settings
&lt;ul&gt;
&lt;li&gt;This is not real DNS. It just looks like it in the user interface.&lt;/li&gt;
&lt;li&gt;Causes missing SSL certificate issues when typing &lt;code class=&quot;language-text&quot;&gt;https://example.com&lt;/code&gt; (with s) directly in the browser address bar. Just typing &lt;code class=&quot;language-text&quot;&gt;example.com&lt;/code&gt; without protocol hides this problem, because the first redirect on an INWX server is unencrypted.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;point domains (with and without &lt;code class=&quot;language-text&quot;&gt;www.&lt;/code&gt;) to &lt;a href=&quot;https://uberspace.de/&quot;&gt;Uberspace&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Uberspace takes care of Let’s Encrypt certificates automatically (&lt;a href=&quot;https://manual.uberspace.de/web-https/&quot;&gt;Uberspace docs: https&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;causes duplicated content if not redirected properly&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;my-old-strategy&quot; tabindex=&quot;-1&quot;&gt;My old strategy&lt;/h2&gt;
&lt;p&gt;Uberspace has one real document root in &lt;code class=&quot;language-text&quot;&gt;/var/www/virtual/$USER/html/&lt;/code&gt;. By default, all domains point to this directory. If a folder &lt;code class=&quot;language-text&quot;&gt;/var/www/virtual/$USER/example.com&lt;/code&gt; exists, the site is served from over there. &lt;code class=&quot;language-text&quot;&gt;$_SERVER[&#39;DOCUMENT_ROOT&#39;]&lt;/code&gt; still points to the real document root &lt;code class=&quot;language-text&quot;&gt;/var/www/virtual/$USER/html/&lt;/code&gt;. This can cause issues for some applications and may need a &lt;code class=&quot;language-text&quot;&gt;RewriteBase /&lt;/code&gt; in &lt;code class=&quot;language-text&quot;&gt;.htaccess&lt;/code&gt;, but all my files are static html files, so I don’t care. Apache can take care of the domain redirects by adding a &lt;code class=&quot;language-text&quot;&gt;.htaccess&lt;/code&gt; to all (sub) domain directories on my Uberspace.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;.htaccess&lt;/code&gt; file:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-apacheconf&quot;&gt;&lt;code&gt;&lt;span class=&quot;token comment&quot;&gt;# redirect all other domains (e. g. $USER.uber.space, www subdomain)&lt;/span&gt;
&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteEngine&lt;/span&gt; On
&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteCond&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;%{HTTP_HOST}&lt;/span&gt; !=rotatio.de
&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteRule&lt;/span&gt; (.*) https://rotatio.de/&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;token directive-flags keyword&quot;&gt;[R=301,L]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Folder setup:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-text&quot;&gt;&lt;code&gt;# https://rotatio.de (synced with 11ty output directory)
/var/www/virtual/$USER/html/

# https://www.rotatio.de
/var/www/virtual/$USER/www.rotatio.de/.htaccess

# https://rotatio.uber.space (default Uberspace user domain)
/var/www/virtual/$USER/rotatio.uber.space/.htaccess
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This setup can be simplified by creating only one folder and creating symlinks for all domains to that directory. This strategy is nice, because I don’t have do deal with modifying &lt;code class=&quot;language-text&quot;&gt;.htaccess&lt;/code&gt; files provided by a CMS (which might change with an update unexpectedly).&lt;/p&gt;
&lt;p&gt;When pointing some more (outdated) domains to this setup, it becomes messy:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-text&quot;&gt;&lt;code&gt;# https://raffaeljesche.de
/var/www/virtual/$USER/raffaeljesche.de/.htaccess

# https://www.raffaeljesche.de
/var/www/virtual/$USER/www.raffaeljesche.de/.htaccess

# https://verstyler.de
/var/www/virtual/$USER/verstyler.de/.htaccess

# https://www.verstyler.de
/var/www/virtual/$USER/www.verstyler.de/.htaccess
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I don’t use a CMS and I have full control on my settings. So I thought, I could skip the manual process of creating all these empty folder duplicates, that only exist for redirecting.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-.htaccess&quot; tabindex=&quot;-1&quot;&gt;The problem with &lt;code class=&quot;language-text&quot;&gt;.htaccess&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;It’s complicated.&lt;/p&gt;
&lt;p&gt;Sadly, while researching, Cloudflare was down (again) and with it half of the internet. Multiple search results weren’t accessible and the &lt;a href=&quot;https://htaccess.madewithlove.com/&quot;&gt;htaccess testing tool&lt;/a&gt; suddenly stopped working because it’s API requests were down too.&lt;/p&gt;
&lt;p&gt;Some useful information from a &lt;a href=&quot;https://stackoverflow.com/questions/3184018/htaccess-rewritecond-without-messing-up-localhost#answer-19644124&quot;&gt;Stack Overflow post&lt;/a&gt; didn’t work for me, but at least their server was online. And I found another thread on Stack Overflow with many tips for &lt;a href=&quot;https://stackoverflow.com/questions/9153262/tips-for-debugging-htaccess-rewrite-rules&quot;&gt;debugging &lt;code class=&quot;language-text&quot;&gt;.htaccess&lt;/code&gt; files&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Luckily I remembered, that I solved this problem in the past already – and I found a note from 2020. It needed some refactoring, but it showed me the missing piece: I forgot to match the port number while testing. So checking against &lt;code class=&quot;language-text&quot;&gt;localhost&lt;/code&gt; etc. always failed while running on &lt;code class=&quot;language-text&quot;&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Changes to my old note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;don’t use 301 for all domains, only for www subdomain&lt;/li&gt;
&lt;li&gt;use 302 redirect instead for other domains&lt;/li&gt;
&lt;li&gt;check against &lt;code class=&quot;language-text&quot;&gt;127.x.x.x&lt;/code&gt; range instead of &lt;code class=&quot;language-text&quot;&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;added check against &lt;code class=&quot;language-text&quot;&gt;192.168.x.x&lt;/code&gt; range&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The final config:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-apacheconf&quot;&gt;&lt;code&gt;&lt;span class=&quot;token directive-block tag&quot;&gt;&lt;span class=&quot;token directive-block tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;IfModule&lt;/span&gt;&lt;span class=&quot;token directive-block-parameter attr-value&quot;&gt; mod_rewrite.c&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteEngine&lt;/span&gt; on

	&lt;span class=&quot;token comment&quot;&gt;# Redirect all other domains (e. g. $USER.uber.space, www subdomain)&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;# Checking for HTTPS or port 443 is not needed, because Uberspace doesn&#39;t&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;# support unencrypted connections and they renew Let&#39;s Encrypt certificates&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;# automatically.&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;# 301 for www subdomain&lt;/span&gt;
	&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteCond&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;%{HTTP_HOST}&lt;/span&gt; =www.rotatio.de
	&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteRule&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt; ^(.*)$&lt;/span&gt; https://rotatio.de/&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;token directive-flags keyword&quot;&gt;[R=301,L]&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;# 302 for all other domains pointing to this server&lt;/span&gt;
	&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteCond&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;%{HTTP_HOST}&lt;/span&gt; !=rotatio.de
	&lt;span class=&quot;token comment&quot;&gt;# but not on localhost (with optional port)&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;# The following conditions could be merged into one unreadable regex.&lt;/span&gt;
	&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteCond&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;%{HTTP_HOST}&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt; !^localhost(?::&#92;d+)?$&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;# still on localhost, but covering the whole 127.x.x.x range&lt;/span&gt;
	&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteCond&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;%{HTTP_HOST}&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt; !^127&#92;.[0-9]{1,3}&#92;.[0-9]{1,3}&#92;.[0-9]{1,3}(?::&#92;d+)?$&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;# and not while exposing dev machine to (W)LAN for cross device testing&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;# covering 192.168.x.x range&lt;/span&gt;
	&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteCond&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;%{HTTP_HOST}&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt; !^192&#92;.168&#92;.[0-9]{1,3}&#92;.[0-9]{1,3}(?::&#92;d+)?$&lt;/span&gt;
	&lt;span class=&quot;token directive-inline property&quot;&gt;RewriteRule&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt; ^(.*)$&lt;/span&gt; https://rotatio.de/&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt; &lt;span class=&quot;token directive-flags keyword&quot;&gt;[R=302,L]&lt;/span&gt;
&lt;span class=&quot;token directive-block tag&quot;&gt;&lt;span class=&quot;token directive-block tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;IfModule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now my document roots are clean. All domains point to the &lt;code class=&quot;language-text&quot;&gt;html/&lt;/code&gt; directory and are handled with a single &lt;code class=&quot;language-text&quot;&gt;.htaccess&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Nice!&lt;/p&gt;
&lt;p&gt;Of course, the whole file is a bit longer. You can inspect &lt;a href=&quot;https://codeberg.org/raffaelj/rotatio.de-11ty/src/branch/main/content/.htaccess&quot;&gt;the &lt;code class=&quot;language-text&quot;&gt;.htaccess&lt;/code&gt; source code on Codeberg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And don’t forget: Never set a 301 redirect before all your tests are finished.&lt;/p&gt;

		</content>
	</entry>
</feed>
