<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Janik von Rotz</title>
    <description>Janik von Rotz - Blog</description>
    <link>https://janikvonrotz.ch/</link>
    <item>
      <title>Working with LLM agents</title>
      <link>https://janikvonrotz.ch/2026/02/17/working-with-llm-agents/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/drinking-tea-with-robot.png"/>
<br/>
<small>
  5 min read
</small>
<h1>
  Working with LLM agents
</h1>
<time>
  February 17, 2026
</time>
<p></p>
<p>
  In the last few days I tinkered with
  <a href="https://cortecs.ai/">
    Cortecs
  </a>
  and
  <a href="https://opencode.ai/">
    OpenCode
  </a>
  . It seems LLM agents are here to stay and I need to deal with them. I admit that I am very skeptic of the AI hype. However, I see a lot of benefits when working with LLM agents in a controlled environment.
</p>
<p>
  People have built great tools and practices. There are many providers that host LLMs or route between multiple providers. The ecosystem as matured and this led me to explore more and put in more effort to understand what is going on.
</p>
<p>
  The goal of this exploration was to find a new coding workflow that includes an LLM agent and fits well into my existing workflow.
</p>
<p>
  As the good engineer that I am, I first wrote down some requirements for the workflow.
</p>
<h2 id="requirements">
  Requirements
</h2>
<p>
  <strong>
    No unnecessary features
  </strong>
</p>
<p>
  The workflow should not encourage me to build more stuff, but to fix issues and improve features.
</p>
<p>
  Code is technical debt and the theory of code is cognitive debt. Building a lot of features and not using them is producing waste and noise.
</p>
<p>
  <strong>
    No digressions
  </strong>
</p>
<p>
  In my experience, if the agent does not satisfy the requirements, you start explaining the issue over and over again. You often end up in argument and LLM always promises to do better.
</p>
<p>
  This kind of arguing and guessing can be a huge waste of time and defeats the very reason for using an agent. Instead of having an ongoing conversation and a bloated context, it is often easier to start over.
</p>
<p>
  And thus the workflow should encourage me to abort and refine my initial prompt.
</p>
<p>
  <strong>
    Spec-driven prompts
  </strong>
</p>
<p>
  Form experience it is worth to write an elaborate prompt. The initial prompt is a specification of of the new feature or bug to resolve.
</p>
<p>
  Before starting an agent session, a well defined prompt must already exist.
</p>
<p>
  <strong>
    Quick boostrap
  </strong>
</p>
<p>
  I have many software projects with a different layout and framework. Before letting an agent go rough in the repo, I want to provide clear guideliens.
</p>
<p>
  The workflow should make it easy to provide the necessary context for very different projects.
</p>
<p>
  <strong>
    Task file integration
  </strong>
</p>
<p>
  This is the most important requirement. I manage all my software projects with
  <a href="https://taskfile.build/">
    taskfile.build
  </a>
  . It is a standard for a bash script and also a library for commands. It allows me to manage and bootstrap very different software projects.
</p>
<p>
  Whatever workflow I am going to build, it can be called from a task file.
</p>
<h2 id="workflow">
  Workflow
</h2>
<p>
  Now let me show you how the coding workflow looks like.
</p>
<p>
  After iterating on many ideas, practices, scripts and commands, I ended up with three step workflow:
</p>
<ol>
  <li>
    <strong>
      Setup
    </strong>
    : Prepare the project to run an LLM agent
  </li>
  <li>
    <strong>
      Run
    </strong>
    : Create and run a prompt
  </li>
  <li>
    <strong>
      Finish
    </strong>
    : Summarize and commit the changes
  </li>
</ol>
<p>
  The commands I use in my workflow are imported from
  <a href="https://taskfile.build/library/#llm">
    https://taskfile.build/library/#llm
  </a>
  .
</p>
<h3 id="setup">
  Setup
</h3>
<p>
  First, I enter an new or existing project and run the init project command:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">task init-project
</code></pre>
</div>
<p>
  This command creates several files and folders. The relevant files are:
</p>
<ul>
  <li>
    <code>
      AGENTS.md
    </code>
    : Tempalted guideline for the LLM agent
  </li>
  <li>
    <code>
      README.md
    </code>
    : Guide for the user and the LLM agent
  </li>
  <li>
    <code>
      prompts
    </code>
    : Folder that contains spec-driven prompts
  </li>
  <li>
    <code>
      task
    </code>
    : Templated project-level task file
  </li>
</ul>
<p>
  I update these files according to the context of the project.
</p>
<h3 id="run">
  Run
</h3>
<p>
  I create new prompt file with this command:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">task create-prompt <span style="color:#e6db74">&#34;Add prompt commands&#34;</span>
</code></pre>
</div>
<p>
  This creates and opens the file
  <code>
    prompts/08_add-prompt-commands.md
  </code>
  in the default editor. Then I update the task section with the specification.
</p>
<p>
  Here is an example of what the prompt file might look like before being executed:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-markdown" data-lang="markdown">---
title: Add prompt commands
---

# Run 08

Replace the <span style="color:#e6db74">`==`</span> marked instructions in this file while you work on the task.

<span style="color:#75715e">## Context
</span><span style="color:#75715e"></span>
Read the <span style="color:#e6db74">`AGENTS.md`</span> and <span style="color:#e6db74">`README.md`</span> to get understanding of the project.

<span style="color:#75715e">## Task
</span><span style="color:#75715e"></span>
Add two new commands <span style="color:#e6db74">`create-prompt`</span> and <span style="color:#e6db74">`list-prompt`</span> to the <span style="color:#e6db74">`bin`</span> folder.

The first command asks for a title. The user is expected to enter something like <span style="color:#e6db74">`Title of the prompt`</span>.
Then the command creates a file <span style="color:#e6db74">`prompts/NN_title-of-the-prompt.md`</span> from a template <span style="color:#e6db74">`prompt.md.template`</span>. It writes the title of the prompt into the frontmatter.
Once the file is created it uses the <span style="color:#e6db74">`$EDITOR`</span> to open the file.
The number sequence starts at 01 and continues upwards.

Similar to <span style="color:#e6db74">`list-dotenv`</span> the <span style="color:#e6db74">`list-prompt`</span> commands lists all prompts. It shows a simple table:

<span style="color:#e6db74">```markdown
</span><span style="color:#e6db74"></span>| ID  | title               |
| --- | ------------------- |
| 08  | Add prompt commands |
<span style="color:#e6db74">```</span>

The title is retrieved from the frontmatter. The table width should be dynamic.

Add the new commands to the <span style="color:#e6db74">`library.md`</span> in the LLM section. Update the <span style="color:#e6db74">`task.template`</span> with <span style="color:#e6db74">`task update-template`</span>.

<span style="color:#75715e">## Worklog
</span><span style="color:#75715e"></span>
==Fill this in as you work on the task==

<span style="color:#75715e">## Summary
</span><span style="color:#75715e"></span>
==Fill this once you completed the task==
</code></pre>
</div>
<p>
  In order to execute the prompt I launch
  <code>
    opencode
  </code>
  in the root of the project and simply copy-paste the path to the prompt file. Open Code then starts working on the task.
</p>
<p>
  If it does not get the instructions, I either create a new session or give some manual inputs.
</p>
<h3 id="finish">
  Finish
</h3>
<p>
  After a few seconds the task has been completed. The worklog and summary section should completed by the agent.
</p>
<p>
  It is crucial to not blindly trust the completeness of your spec or the output of the LLM. So I check the
  <code>
    git diff
  </code>
  . If somethings seems off, I either update the prompt or make manual inputs.
</p>
<p>
  If I am happy with the result, the changes are staged
  <code>
    gaa
  </code>
  and I run
  <code>
    task update-changelog-with-llm
  </code>
  . This will update the
  <code>
    CHANGELOG.md
  </code>
  according to the
  <a href="https://keepachangelog.com/en/1.1.0/">
    keep a changelog
  </a>
  specification.
</p>
<p>
  To commit the changes I use
  <code>
    task commit-with-llm
  </code>
  . This command generates the commit message from the git diff according to the
  <a href="https://www.conventionalcommits.org/en/v1.0.0/">
    Conventional Commits
  </a>
  specification.
</p>
<p>
  And that’s it!
</p>
<h2 id="afterthoughts">
  Afterthoughts
</h2>
<p>
  This workflow works well for trivial and well defined tasks. Once you try more complex task and multiple agents, the quality of the project erodes pretty fast.
</p>
<blockquote>
  <p>
    Some researchers have demonstrated that LLMs can create entire browsers within a week. Browser engines are well specified, but their creation takes a very long time. The result met all requirements, but was very slow and inefficient.
  </p>
</blockquote>
<p>
  I believe that the AI hype is a challenge for the entire software industry. It’s not just salespeople who are hyping AI, but also engineers who should know better.
</p>
<p>
  Until the bubble pops, I’ll try my best.
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/large-language-model">
  Large Language Model
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/llm">
  llm
</a>
,
<a href="https://janikvonrotz.ch/tags/augmented">
  augmented
</a>
,
<a href="https://janikvonrotz.ch/tags/artifical">
  artifical
</a>
,
<a href="https://janikvonrotz.ch/tags/intelligence">
  intelligence
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2026-02-17-working-with-llm-agents.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2026%2f02%2f17%2fworking-with-llm-agents%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">5fffe4db64dc4a794ecb671f507f88af</guid>
    </item>
    <item>
      <title>Goodbye Soundcloud, Hello Bandcamp</title>
      <link>https://janikvonrotz.ch/2026/01/25/goodbye-soundcloud-hello-bandcamp/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/NewarkSchools0210.png"/>
<br/>
<small>
  3 min read
</small>
<h1>
  Goodbye Soundcloud, Hello Bandcamp
</h1>
<time>
  January 25, 2026
</time>
<p></p>
<p>
  This post is about enshittification and the decay of the creative web as we know it. It is also about owning and paying your fair share to music artists. I try to recall my last 20 years of listening to music and give some advice for the future.
</p>
<h2 id="2007---success-of-mp3">
  2007 - Success of .mp3
</h2>
<p>
  <strong>
    Setup
  </strong>
</p>
<p>
  Back in 2007 I had a Windows 7 computer and music library was stored on it. The library was synced with iTunes, Zune Player, SkyDrive (now OneDrive) or the Playstation Portable (PSP).
</p>
<p>
  <strong>
    Labor
  </strong>
</p>
<p>
  I put in a lot effort to keep the library well maintained. I updated metadata and added album covers.
</p>
<p>
  <strong>
    Industry
  </strong>
</p>
<p>
  Music was published by record labels. Artists depended on the record label and thus record labels had a lot of power.
</p>
<h2 id="2013---subscribe-to-listen">
  2013 - Subscribe to listen
</h2>
<p>
  <strong>
    Setup
  </strong>
</p>
<p>
  There internet bandwidth increased and music streaming became feasible. In 2013 Soundcloud was an underdog for listening to music. A lot of listeners had already ditched Apple music in favor of Spotify. On July 2nd 2013 I
  <a href="https://janikvonrotz.ch/2013/07/02/kinaj-01-cyber-corn-electro-house-progressive-house/">
    created my first playlist on Soundcloud
  </a>
  .
</p>
<p>
  <strong>
    Labor
  </strong>
</p>
<p>
  With access to an almost infinite music library, the work to maintain my personal music library became obsolete quite quickly. Instead, I created a lot of playlists. Discoverying music an see your taste change over time was fascinating.
</p>
<p>
  <strong>
    Industry
  </strong>
</p>
<p>
  From 2010 to 2020 the entire transition from owning a record to buying a subscription had happened.
</p>
<h2 id="2025---enter-the-enshittogenesis">
  2025 - Enter the enshittogenesis
</h2>
<p>
  <strong>
    Setup
  </strong>
</p>
<p>
  What is the state of Soundcloud? Since being the Underdog, Soundcloud has become a major player. They have tried various subscription models and rebranded themselfs many times.
</p>
<p>
  <strong>
    Labor
  </strong>
</p>
<p>
  If I recall correctly, these playlists held 10 to 12 songs. When I check them now, only 4 to 6 songs are left. About 50% of songs have been removed from my playlists.
</p>
<p>
  At least you are able to switch streaming platform. You can sync your likes follows, likes and playlists between the streaming providers (Spotify, Tidal, Apple Music or Soundcloud).
</p>
<p>
  <strong>
    Industry
  </strong>
</p>
<p>
  Spotify has changed the music industry… for the worse. They have the monopoly on music streaming and also decide how music is produced. Artists do not get their fair share.
</p>
<p>
  Soundcloud has the same problem as other music streaming providers. You cannot please your shareholders with a 13$ per month subscription and thus have to seek other revenue models.
</p>
<h2 id="2026---back-to-owning">
  2026 - Back to owning
</h2>
<p>
  <strong>
    Setup
  </strong>
</p>
<p>
  As the title of this post already tells, I cancelled my Soundcloud subscription.
</p>
<p>
  <img src="https://janikvonrotz.ch/images/soundcloud-we-are-sad.png" alt="soundcloud-we-are-sad"/>
</p>
<p>
  What really tipped my off was the fact, that even though I was a paying customer, Soundcloud tried to milk me for advertising. The Soundcloud app had become the most intrusive app and the number one on the adblock wall of shame.
</p>
<p>
  <img src="https://janikvonrotz.ch/images/soundcloud-tracking-blocked-leaks.png" alt="soundcloud-tracking-blocked-leaks"/>
</p>
<p>
  Just switching to another provider was not an option. The only option left was
  <a href="https://janikvonrotz.ch/2026/01/25/goodbye-soundcloud-hello-bandcamp/">
    Bandcamp
  </a>
  .
</p>
<p>
  <strong>
    Labor
  </strong>
</p>
<p>
  It is kind of going back to buying CDs and Vinyls. If you are truly a fan of music and want to support artists, then buying from then directly is the best way.
</p>
<p>
  <strong>
    Industry
  </strong>
</p>
<p>
  Bandcamp goes half-way. It is still a platform and can still be sold and remodeled. But until then, it seems like the best option.
</p>
<p>
  Also I appreciate their stance on AI:
  <a href="https://blog.bandcamp.com/2026/01/13/keeping-bandcamp-human/">
    https://blog.bandcamp.com/2026/01/13/keeping-bandcamp-human/
  </a>
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/music">
  Music
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/music">
  music
</a>
,
<a href="https://janikvonrotz.ch/tags/soundcloud">
  soundcloud
</a>
,
<a href="https://janikvonrotz.ch/tags/bandcamp">
  bandcamp
</a>
,
<a href="https://janikvonrotz.ch/tags/enshittification">
  enshittification
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2026-01-25-goodbye-soundcloud-hello-bandcamp.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2026%2f01%2f25%2fgoodbye-soundcloud-hello-bandcamp%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Sun, 25 Jan 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">18e18f666da3600124fed51ad248bed6</guid>
    </item>
    <item>
      <title>2025 Book List</title>
      <link>https://janikvonrotz.ch/2026/01/03/2025-book-list/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/books.jpg"/>
<br/>
<small>
  3 min read
</small>
<h1>
  2025 Book List
</h1>
<time>
  January 3, 2026
</time>
<p></p>
<p>
  In 2025 I read 9 books and 4 Mangas. As we move towards a more federated and decentralized internet, I now track my reading habits with
  <a href="https://joinbookwyrm.com/">
    Bookwyrm
  </a>
  . Please
  <a href="https://jointhefediverse.net/">
    join the fediverse
  </a>
  and
  <a href="https://bookwyrm.social/user/janikvonrotz">
    follow me
  </a>
  via Bookwyrm.
</p>
<p>
  My favorite book was
  <strong>
    Unsong
  </strong>
  by Scott Alexander.
</p>
<p>
  Here is the full 2025 book list:
</p>
<h2 id="books">
  Books
</h2>
<p>
  <img src="https://janikvonrotz.ch/images/I%20Who%20Have%20Never%20Known%20Men.png" alt=""/>
</p>
<p>
  Title: I Who Have Never Known Men
  <br/>
  Author: Jacqueline Harpman
  <br/>
  Comment: A lost story, that became relevant. Not sure why. Reading the book makes you curious to learn more about the author.
  <br/>
  Rating: 4/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/416044/s/i-who-have-never-known-men">
    https://bookwyrm.social/book/416044/s/i-who-have-never-known-men
  </a>
  <br/>
  Finished: Oct. 7, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/Die%20Verkrempelung%20der%20Welt.png" alt=""/>
</p>
<p>
  Title: Die Verkrempelung der Welt
  <br/>
  Author: Gabriel Yoran
  <br/>
  Comment: This books does a good job at explaining enshittification to non-tech people. The language is simple. I was very impressed by references to projects that I expected to be totally irrelevant outside the tech realm.
  <br/>
  Rating: 4/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/1987830/s/die-verkrempelung-der-welt">
    https://bookwyrm.social/book/1987830/s/die-verkrempelung-der-welt
  </a>
  <br/>
  Finished: Oct. 3, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/Unsong.png" alt=""/>
</p>
<p>
  Title: Unsong
  <br/>
  Author: Scott Alexander
  <br/>
  Comment: A story like now other. Hard to explain without sounding like a schizophrenic delulu. There are fighting scenes, but instead of swords or guns, they cite the bible and talmund. Everything is taken literally.
  <br/>
  Rating: 5/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/1793156/s/unsong">
    https://bookwyrm.social/book/1793156/s/unsong
  </a>
  <br/>
  Finished: Oct. 3, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/Dragons%20egg.png" alt=""/>
</p>
<p>
  Title: Dragon’s egg
  <br/>
  Author: Robert L. Forward
  <br/>
  Comment: Rock-solid sci-fi book. The story explores the idea that life can flourish under very different conditions to ours.
  <br/>
  Rating: 3/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/42702/s/dragons-egg">
    https://bookwyrm.social/book/42702/s/dragons-egg
  </a>
  <br/>
  Finished: Aug. 9, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/The%20Metamorphosis%20of%20Prime%20Intellect.png" alt=""/>
</p>
<p>
  Title: The Metamorphosis of Prime Intellect
  <br/>
  Author: Roger Williams
  <br/>
  Comment: Singularity puts everybody into a simulation. Death is defied, but not the humand condition.
  <br/>
  Rating: 3/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/199543/s/the-metamorphosis-of-prime-intellect">
    https://bookwyrm.social/book/199543/s/the-metamorphosis-of-prime-intellect
  </a>
  <br/>
  Finished: July 12, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/Lords%20of%20Uncreation.png" alt=""/>
</p>
<p>
  Title: Lords of Uncreation
  <br/>
  Series: The Final Architecture #3
  <br/>
  Author: Adrian Tchaikovsky
  <br/>
  Comment: The awaited and final book of the series. A space opera that delivers everything you wish for.
  <br/>
  Rating: 4/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/817598/s/lords-of-uncreation">
    https://bookwyrm.social/book/817598/s/lords-of-uncreation
  </a>
  <br/>
  Finished: April 10, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/Reinventing%20organizations.png" alt=""/>
</p>
<p>
  Title: Reinventing organizations
  <br/>
  Author: Frederic Laloux
  <br/>
  Comment: Frederic fundamentally changed my understanding of organisations are supposed to work. This book provides the answer to many problems employees face in hierarchical organisations.
  <br/>
  Rating: 5/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/1011193/s/reinventing-organizations">
    https://bookwyrm.social/book/1011193/s/reinventing-organizations
  </a>
  <br/>
  Finished: April 14, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/A%20Short%20Stay%20in%20Hell.png" alt=""/>
</p>
<p>
  Title: A Short Stay in Hell
  <br/>
  Author: Steven L. Peck
  <br/>
  Comment: Don’t underestimate the amount of pages and words it takes to fuck up your imagination. This story did a very good job of triggering the fear of endlessness and dullness.
  <br/>
  Rating: 5/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/44622/s/a-short-stay-in-hell">
    https://bookwyrm.social/book/44622/s/a-short-stay-in-hell
  </a>
  <br/>
  Finished: July 7, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/Survival%20of%20the%20Richest.png" alt=""/>
</p>
<p>
  Title: Survival of the Richest
  <br/>
  Author: Douglas Rushkoff
  <br/>
  Comment: If you already read and listen to Rushkoff, this book does offer nothing new.
  <br/>
  Rating: 3/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/book/550662/s/survival-of-the-richest">
    https://bookwyrm.social/book/550662/s/survival-of-the-richest
  </a>
  <br/>
  Finished: July 5, 2025
</p>
<h2 id="mangas">
  Mangas
</h2>
<p>
  <img src="https://janikvonrotz.ch/images/Dorohedoro.png" alt=""/>
</p>
<p>
  Title: Dorohedoro
  <br/>
  Author: Oda Eiichiro
  <br/>
  Comment: I love the dark atmosphere. Magic is not something beautiful. It smokey, stinky and killing. The story is entwined and well paced. Recommend to everyone.
  <br/>
  Rating: 5/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/series/by/294625?series_name=Dorohedoro">
    https://bookwyrm.social/series/by/294625?series_name=Dorohedoro
  </a>
  <br/>
  Finished: July 1, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/Delicious%20in%20Dungeon.png" alt=""/>
</p>
<p>
  Title: Delicious in Dungeon
  <br/>
  Author: Ryoko Kui
  <br/>
  Comment: The friend that recommended my this manga is a cook by profession. However, this manga is not only about cooking. It is funny dungeon crawler, but has the necessary spice to blend into well cooked story.
  <br/>
  Rating: 4/5
  <br/>
  Link:
  <a href="https://bookwyrm.social/series/by/742?series_name=Delicious%20in%20Dungeon">
    https://bookwyrm.social/series/by/742?series_name=Delicious%20in%20Dungeon
  </a>
  <br/>
  Finished: December 1, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/The%20Fable.png" alt=""/>
</p>
<p>
  Title: The Fable
  <br/>
  Author: Katsuhisa Minami
  <br/>
  Comment: The archetype of professional assassins trying the modest life, but then for various reasons are taken accountable for their past. The Fable is group is feared assassins, but all with peculiar personalities.
  <br/>
  Rating: 4/5
  <br/>
  Link:
  <a href="https://en.wikipedia.org/wiki/The_Fable">
    https://en.wikipedia.org/wiki/The_Fable
  </a>
  <br/>
  Finished: September 1, 2025
</p>
<p>
  <img src="https://janikvonrotz.ch/images/The%20Fable%20-%20The%20Second%20Contact.png" alt=""/>
</p>
<p>
  Title: The Fable: The Second Contact
  <br/>
  Author: Katsuhisa Minami
  <br/>
  Comment: The second part starts with COVID. A difficult time for manga artists and so we see some personal thoughts of the author. But then the story goes back into the well-known style of the first series.
  <br/>
  Rating: 4/5
  <br/>
  Link:
  <a href="https://en.wikipedia.org/wiki/The_Fable">
    https://en.wikipedia.org/wiki/The_Fable
  </a>
  <br/>
  Finished: October 1, 2025
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/blog">
  Blog
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/book">
  book
</a>
,
<a href="https://janikvonrotz.ch/tags/list">
  list
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2026-01-03-2025-book-list.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2026%2f01%2f03%2f2025-book-list%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">2a6d0a95f6730f4b6cb4aa306cb08830</guid>
    </item>
    <item>
      <title>Forgejo action to update Kubernetes deployment</title>
      <link>https://janikvonrotz.ch/2026/01/02/forgejo-action-to-update-kubernetes-deployment/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/forgejo-action-update-kubernetes-deployment.png"/>
<br/>
<small>
  3 min read
</small>
<h1>
  Forgejo action to update Kubernetes deployment
</h1>
<time>
  January 2, 2026
</time>
<p></p>
<p>
  In the
  <a href="https://janikvonrotz.ch/2025/12/31/deploy-forgejo-runner-to-kubernetes-cluster/">
    last post
  </a>
  I showed how you can build a Docker image in a Kubernetes cluster using with Forgejo runner. One missing step was the actual deployment of the new Docker image.
</p>
<p>
  In the context of Kubernetes to deploy means to update a deployment config and thus restart the life cycle of pods. There are many ways to do that. The simplest way is to kill a pod. If the
  <code>
    imagePullPolicy
  </code>
  of a container is set to
  <code>
    Always
  </code>
  , Kubernetes will pull the latest image before every container initialization. So whenever a pod is deleted, Kubernetes pulls the image and deploys a new pod.
</p>
<h2 id="deploy-kubeconfig">
  Deploy kubeconfig
</h2>
<p>
  We use this approach to make a deployment update. In order to access the deployment of Kubernetes cluster, a new service account is required. I created another Helm Chart that provides exactly this:
  <a href="https://kubernetes.build/deploymentUpdater/README.html">
    https://kubernetes.build/deploymentUpdater/README.html
  </a>
</p>
<p>
  Deploy the Chart and you’ll get a new account called
  <code>
    deploy
  </code>
  . Export the kubeconfig for this account like this:
  <a href="https://kubernetes.build/deploymentUpdater/README.html#forgejo-deployment-action">
    https://kubernetes.build/deploymentUpdater/README.html#forgejo-deployment-action
  </a>
</p>
<h2 id="codeberg-setup">
  Codeberg setup
</h2>
<p>
  Setup the kubeconfig as a secret for your organisation- or personal account. If you are using Codeberg, copen
  <em>
    Settings &gt; Actions &gt; Secrets
  </em>
  and click
  <em>
    Add secret
  </em>
  . Enter
  <code>
    KUBECONFIG_DEPLOY
  </code>
  as name enter the content of the kubeconfig.
</p>
<h2 id="forgejo-action">
  Forgejo action
</h2>
<p>
  We already reached the final step. For your repo we assume that your a build workflflow, f.g.
  <code>
    .forgejo/workflows/build.yml
  </code>
  . Rename the file to something like
  <code>
    .forgejo/workflows/build-and-deploy.yml
  </code>
  . In addition to the build step, we add a deploy step:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">  <span style="color:#f92672">deploy</span>:
    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Deploy to Kubernetes</span>
    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
    <span style="color:#f92672">container</span>: <span style="color:#ae81ff">catthehacker/ubuntu:act-latest</span>
    <span style="color:#f92672">needs</span>: <span style="color:#ae81ff">build</span>

    <span style="color:#f92672">steps</span>:
      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Checkout code</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Install kubectl</span>
        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">          curl -LO &#34;https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl&#34;
</span><span style="color:#e6db74">          chmod +x kubectl
</span><span style="color:#e6db74">          sudo mv kubectl /usr/local/bin/</span>          

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Create Kubeconfig for Deployment</span>
        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">          mkdir -p $HOME/.kube
</span><span style="color:#e6db74">          echo &#34;${{ secrets.KUBECONFIG_DEPLOY }}&#34; &gt; $HOME/.kube/config</span>          

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Verify kubectl version</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">kubectl version --client</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Deploy to Kubernetes</span>
        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">kubectl delete pods -l app=hugo -n &lt;namespace&gt;</span>
</code></pre>
</div>
<p>
  The deploy step depends on the build step (
  <code>
    needs: build
  </code>
  ). It installs
  <code>
    kubectl
  </code>
  and sets up the kubeconfig to access the cluster. It then deletes all pods for a namespace and a label. Update according to your configuration.
</p>
<p>
  Checkout the full reference of the
  <code>
    build-and-deploy.yml
  </code>
  :
  <a href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/branch/main/.forgejo/workflows/build-and-deploy.yml">
    https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/branch/main/.forgejo/workflows/build-and-deploy.yml
  </a>
</p>
<h2 id="run-the-action">
  Run the action
</h2>
<p>
  Commit and push the workflow file. The following should happen:
</p>
<ol>
  <li>
    Codeberg creates a new action run
  </li>
  <li>
    The Forgejo runner receives the task and runs the build step
  </li>
  <li>
    Once the build step is completed it starts the deploy step
  </li>
  <li>
    It installs
    <code>
      kubectl
    </code>
    and adds the kubeconfig
  </li>
  <li>
    Then it outputs the verion of kubectl
  </li>
  <li>
    Finally it deletes the pods matching the label and namespace
  </li>
  <li>
    Kubernetes will pull the image and deploy new pods
  </li>
  <li>
    Your application has been updated
  </li>
</ol>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/continuous-delivery">
  Continuous Delivery
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/kubernetes">
  kubernetes
</a>
,
<a href="https://janikvonrotz.ch/tags/forgejo">
  forgejo
</a>
,
<a href="https://janikvonrotz.ch/tags/runner">
  runner
</a>
,
<a href="https://janikvonrotz.ch/tags/action">
  action
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2026-01-02-forgejo-action-to-update-kubernetes-deployment.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2026%2f01%2f02%2fforgejo-action-to-update-kubernetes-deployment%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Fri, 02 Jan 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">31f38188b21ffe2ff315cfd84d4a0bac</guid>
    </item>
    <item>
      <title>Deploy Forgejo runner to Kubernetes cluster</title>
      <link>https://janikvonrotz.ch/2025/12/31/deploy-forgejo-runner-to-kubernetes-cluster/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/forgejo-runner.png"/>
<br/>
<small>
  3 min read
</small>
<h1>
  Deploy Forgejo runner to Kubernetes cluster
</h1>
<time>
  December 31, 2025
</time>
<p></p>
<p>
  In my post about
  <a href="https://janikvonrotz.ch/2025/08/20/migrate-from-github-to-codeberg/">
    migrating from GitHub to Codeberg
  </a>
  I was not able to find a suitable alternative for GitHub actions. With GitHub actions I built and published Docker images. Since the migration I was able find solution.
</p>
<p>
  The solution is a
  <a href="https://forgejo.org/docs/latest/admin/actions/runner-installation/">
    Forgejo runner
  </a>
  deployed to a private Kubernetes cluster. It is connected to Codeberg and runs my actions the same way as it did on GitHub. Let me walk you through the setup.
</p>
<h2 id="codeberg-setup">
  Codeberg setup
</h2>
<p>
  For the deployment of the Forgejo runner, I have created a Helm Chart:
  <a href="https://kubernetes.build/forgejoRunner/README.html">
    https://kubernetes.build/forgejoRunner/README.html
  </a>
  . If you are familiar with Kubernetes and Helm, the deployment is straightforward.
</p>
<p>
  The Helm Chart requires an instance token to register the runner. This token can be generated and copied from Codeberg. Open
  <a href="https://codeberg.org">
    https://codeberg.org
  </a>
  and click on your profile. Then navigate to
  <em>
    Settings &gt; Actions &gt; Runners
  </em>
  and click on
  <em>
    Create new runner
  </em>
  . Copy the registration token.
</p>
<h2 id="kubernetes-deployment">
  Kubernetes deployment
</h2>
<p>
  Once the runner is deployed, check if it shows up on Codeberg. Open
  <em>
    Settings &gt; Actions &gt; Runners
  </em>
  and you should see your registered runner.
</p>
<p>
  The Chart also creates a new cluster role called
  <code>
    buildx
  </code>
  . This role shall be used to access the cluster and build the Docker image in Kubernetes enviroment. Export the kubeconfig of this role like this:
  <a href="https://kubernetes.build/forgejoRunner/README.html#forgejo-buildx-action">
    https://kubernetes.build/forgejoRunner/README.html#forgejo-buildx-action
  </a>
</p>
<h2 id="forgejo-action">
  Forgejo action
</h2>
<p>
  The last step is the migration of the GitHub action and enabling the Codeberg repository to run actions.
</p>
<p>
  Let’s get started by enabling actions on the repo. Open the
  <em>
    Settings
  </em>
  page of your repo and click on
  <em>
    Units &gt; Overview
  </em>
  . Enable the
  <em>
    Actions
  </em>
  option and save the settings. A new tab
  <em>
    Actions
  </em>
  and a settings page are shown now.
</p>
<p>
  We assume that you have a GitHub action
  <code>
    .github/workflows/build.yml
  </code>
  in your repo. Rename the
  <code>
    .github
  </code>
  folder to
  <code>
    .forgejo
  </code>
  .
</p>
<p>
  A few modifications of the
  <code>
    build.yml
  </code>
  workflow are required to get the same results in the Forgejo runner environment.
</p>
<p>
  First we need to define the build environment. Update the
  <code>
    build.yml
  </code>
  with this
  <code>
    container
  </code>
  key:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">...
<span style="color:#f92672">jobs</span>:
  <span style="color:#f92672">build</span>:
	<span style="color:#ae81ff">...</span>
    <span style="color:#f92672">container</span>: <span style="color:#ae81ff">catthehacker/ubuntu:act-latest</span>
</code></pre>
</div>
<p>
  The
  <code>
    ubuntu:act-latest
  </code>
  Docker image replicates the GitHub build environment.
</p>
<p>
  Next we need to grant the Forgejo runner access to the Kubernetes environment.
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">	  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Checkout code</span>
		<span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>
	
	  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Create Kubeconfig for Buildx</span>
		<span style="color:#f92672">run</span>: <span style="color:#ae81ff">|</span>
		  <span style="color:#ae81ff">mkdir -p $HOME/.kube</span>
		  <span style="color:#ae81ff">echo &#34;${{ secrets.KUBECONFIG_BUILDX }}&#34; &gt; $HOME/.kube/config</span>
</code></pre>
</div>
<p>
  As you can see the the kubeconfig is loaded from a environment secret. Setup this secret in your organisation or personal account. Open
  <em>
    Settings &gt; Actions &gt; Secrets
  </em>
  and click
  <em>
    Add secret
  </em>
  . Enter
  <code>
    KUBECONFIG_BUILDX
  </code>
  as name enter the content of the kubeconfig from the
  <a href="https://janikvonrotz.ch/2025/12/31/deploy-forgejo-runner-to-kubernetes-cluster/#Codeberg%20setup">
    Codeberg setup
  </a>
  step.
</p>
<p>
  We are almost done. In order to build and publish a Docker image, these steps have to be added as well:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yaml" data-lang="yaml">      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Set up Docker Buildx</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">docker/setup-buildx-action@v3</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">driver</span>: <span style="color:#ae81ff">kubernetes</span>
          <span style="color:#f92672">driver-opts</span>: |<span style="color:#e6db74">
</span><span style="color:#e6db74">            </span>            <span style="color:#ae81ff">namespace=codeberg</span>

      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Login to Docker Registry</span>
        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">docker/login-action@v3</span>
        <span style="color:#f92672">with</span>:
          <span style="color:#f92672">username</span>: <span style="color:#ae81ff">janikvonrotz</span>
          <span style="color:#f92672">password</span>: <span style="color:#ae81ff">${{ secrets.DOCKER_PAT }}</span>
</code></pre>
</div>
<p>
  The
  <code>
    namespace=codeberg
  </code>
  definition must be match the namespace name of Forgejo runner deployed. Add the
  <code>
    DOCKER_PAT
  </code>
  as a secret to your account.
</p>
<p>
  Checkout the full reference of the
  <code>
    build.yml
  </code>
  :
  <a href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/branch/main/.forgejo/workflows/build-and-deploy.yml">
    https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/branch/main/.forgejo/workflows/build-and-deploy.yml
  </a>
</p>
<h2 id="run-the-action">
  Run the action
</h2>
<p>
  Everything is ready to run. Once you commit and push the new
  <code>
    .forgejo/workflows/build.yml
  </code>
  file, the following should happen:
</p>
<ol>
  <li>
    Codeberg creates a new action run
  </li>
  <li>
    The Forgejo runner receives the task and runs the build image
  </li>
  <li>
    In the build container the repository is cloned
  </li>
  <li>
    The kubeconfig is created and the Docker Buildx enviroment is prepared
  </li>
  <li>
    The build container builds the Docker image in the Kubernetes environment
  </li>
  <li>
    If successful, the image is pushed to the Docker registry
  </li>
</ol>
<h2 id="next-steps">
  Next steps
</h2>
<p>
  The image is now ready to deploy. How to trigger a Kubernetes deployment from a Forgejo action will be covered in another post.
</p>
<h2 id="credits">
  Credits
</h2>
<p>
  I give my thanks to
  <a href="https://www.tobru.ch/">
    Tobias Brunner
  </a>
  . He gave me the initial configs for the setup:
</p>
<p>
  Forgejo Runner Helm Chart:
  <a href="https://git.tbrnt.ch/tobru/gitops-zurrli/src/branch/main/apps/zurrli/forgejo-runner">
    https://git.tbrnt.ch/tobru/gitops-zurrli/src/branch/main/apps/zurrli/forgejo-runner
  </a>
</p>
<p>
  Build and Deploy action:
  <a href="https://git.tbrnt.ch/tobru/tobrublog/src/branch/main/.forgejo/workflows/build-deploy.yaml">
    https://git.tbrnt.ch/tobru/tobrublog/src/branch/main/.forgejo/workflows/build-deploy.yaml
  </a>
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/continuous-integration">
  Continuous Integration
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/codeberg">
  codeberg
</a>
,
<a href="https://janikvonrotz.ch/tags/github">
  github
</a>
,
<a href="https://janikvonrotz.ch/tags/forgejo">
  forgejo
</a>
,
<a href="https://janikvonrotz.ch/tags/migration">
  migration
</a>
,
<a href="https://janikvonrotz.ch/tags/kubernetes">
  kubernetes
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2025-12-31-deploy-forgejo-runner-to-kubernetes-cluster.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2025%2f12%2f31%2fdeploy-forgejo-runner-to-kubernetes-cluster%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Wed, 31 Dec 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">b00fb2b81b3d8d8655a975121f20158e</guid>
    </item>
    <item>
      <title>Too Big to Care</title>
      <link>https://janikvonrotz.ch/2025/12/08/too-big-to-care/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/ascii-emoji-dunno.png"/>
<br/>
<small>
  1 min read
</small>
<h1>
  Too Big to Care
</h1>
<time>
  December 8, 2025
</time>
<p></p>
<blockquote>
  <p>
    Wenn Hyperscaler zum Problem werden.
  </p>
</blockquote>
<p>
  Für einen Anlass des
  <a href="https://www.digital-cluster-uri.ch/">
    Digital Cluster Uri
  </a>
  durfte ich eine kurze Präsentation zum Digitale Souverenität halten. Die Präsentation fokussiert sich auf die sog. Hyperscaler und zeigt auf warum diese ein Problem sind.
</p>
<embed src="https://janikvonrotz.ch/documents/too-big-to-care.pdf" width="100%" height="1000px" type="application/pdf"/>
<p>
  <a href="https://janikvonrotz.ch/documents/too-big-to-care.pdf" target="_blank">
    Download the PDF
  </a>
  .
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/politics">
  Politics
</a>
,
<a href="https://janikvonrotz.ch/categories/tech">
  Tech
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/powerup">
  powerup
</a>
,
<a href="https://janikvonrotz.ch/tags/dependency">
  dependency
</a>
,
<a href="https://janikvonrotz.ch/tags/hyperscaler">
  hyperscaler
</a>
,
<a href="https://janikvonrotz.ch/tags/problem">
  problem
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2025-12-08-too-big-to-care.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2025%2f12%2f08%2ftoo-big-to-care%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Mon, 08 Dec 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">ac3bcc8d74eb72f32effabcb8fa34f5b</guid>
    </item>
    <item>
      <title>AI is not abstraction</title>
      <link>https://janikvonrotz.ch/2025/11/10/ai-is-not-abstraction/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/nuclear-code-repository.png"/>
<br/>
<small>
  3 min read
</small>
<h1>
  AI is not abstraction
</h1>
<time>
  November 10, 2025
</time>
<p></p>
<p>
  The concept of abstraction has been applied to software engineering. But it never made sense. Software is flexible. Software can be changed even after it has been put into production.
</p>
<h2 id="software-is-flexible-">
  Software is flexible 🌊
</h2>
<p>
  The layer of abstraction in software is always moving. The definition of software abstraction has nothing in common with the definition of abstraction of engineering a physical product.
</p>
<p>
  Software is fluid, always changing, often hard to reproduce, it is configurable, riddled with bugs and most of all - it has to be made sense of. As the code base changes you always have update your mental model of the how the software works.
</p>
<p>
  We intend to write more code than less. We tend to make software more complex than simple. We add more and more features than deprecating them. Everything is done under the umbrella of increasing productivity.
</p>
<p>
  However, developing software does not have to be more productive - it has to be contained.
</p>
<h2 id="the-bloat-is-real-">
  The bloat is real 🍔
</h2>
<p>
  Once you start engineering software with the help of AI you will experience a productivity gain. Not for the overall production of software, but for solving well defined problems in a well known domain. Problems such as bug fixing, commenting, documenting, boiler-plating and explaining are straightforward tasks for AIs.
</p>
<p>
  As a developer you get hooked on running agents in your code base. Solving multiple problems at once. Trying new strategies like git work trees to run multiple agents at the same time.
</p>
<p>
  The sense of productivity and squishing those bugs overwhelms you. This works until you hit the ceiling. The context window, the amount of token to burn through, the invoice, the dependency juggling, the agent getting lost, these are all hard limits that put a stop to the trip.
</p>
<p>
  But what is really happening here? Lets take a step back and have look. Actually you are not improving the software product, you are bloating the code base! It gets bigger and bigger. There are not limitations for adding new features. The computing resources are limitless. Of course this makes sense, you are not getting paid to write smart code, you are getting paid to produce new features.
</p>
<p>
  So this apparent level of abstraction and productivity gain is in reality just bloat. A waste of resources.
</p>
<h2 id="environmental-resources-">
  Environmental resources 🏭
</h2>
<p>
  Producing software with Big Tech services has now a direct link to environmental damage. It is well documented that their data centers are not sustainable and are not using sustainable energy sources. They have become an environmental hazard.
</p>
<p>
  In regards to the climate crisis you have a moral obligation to not use their data centers.
</p>
<p>
  So what if we treat AI as what it is? An environmental hazard.
</p>
<h2 id="safety-procedures-">
  Safety procedures ☢️
</h2>
<p>
  Read this part if you failed to contain the AI and a agent chain reactions has destroyed your code base.
</p>
<p>
  In case your code is radiating and has been polluted by AI-written code, it is time to run the contamination protocols and safety procedures.
</p>
<p>
  First, isolate the AI-written code. Containerize the application and sandbox the execution environment. Add warnings and comments that can be understood years after. Treat your software the same way as you would treat legacy systems.
</p>
<p>
  Once contained, bury the code in the deepest
  <code>
    /dev/null
  </code>
  you can find. Never touch it again, but also never forget about it. Future generations of coders might be able to untangle and decompose the mess that has been created.
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/software-development">
  Software development
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/llm">
  llm
</a>
,
<a href="https://janikvonrotz.ch/tags/artifical">
  artifical
</a>
,
<a href="https://janikvonrotz.ch/tags/intelligence">
  intelligence
</a>
,
<a href="https://janikvonrotz.ch/tags/bloat">
  bloat
</a>
,
<a href="https://janikvonrotz.ch/tags/abstraction">
  abstraction
</a>
,
<a href="https://janikvonrotz.ch/tags/environment">
  environment
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2025-11-10-ai-is-not-abstraction.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2025%2f11%2f10%2fai-is-not-abstraction%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Mon, 10 Nov 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">9e3ccbc5026c72b6b7e19fc8867674db</guid>
    </item>
    <item>
      <title>Disable dependabot alerts for all repos</title>
      <link>https://janikvonrotz.ch/2025/09/10/2025-08-09-10-disable-dependabot-alerts-for-all-repos/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/clippy-vs-code.png"/>
<br/>
<small>
  3 min read
</small>
<h1>
  Disable dependabot alerts for all repos
</h1>
<time>
  September 10, 2025
</time>
<p></p>
<p>
  It is well known that GitHub dependabot alerts and PRs are less than helpful. For hubbers the dependabot is very similar to what clippy was to the office users. It tries to help, but is very distracting for solving the actual problem.
</p>
<p>
  Disabling dependabot alerts for one repo is simple. Got to this page
  <code>
    https://github.com/$GITHUB_USERNAME/$REPO/settings/security_analysis
  </code>
  and click
  <em>
    disable
  </em>
  . But doing this for a 100 or 1000 repos is not feasible. We need a script to automate this process. Let me show you how.
</p>
<p>
  In order to run the scripts you need to create a personal access token to access the GitHub API. Create a token with read/write access to user and repo here:
  <a href="https://github.com/settings/tokens">
    https://github.com/settings/tokens
  </a>
</p>
<p>
  And then you are ready to configure and run the script. Simply change the
  <code>
    $GITHUB_USERNAME
  </code>
  and set the
  <code>
    $GITHUB_TOKEN
  </code>
  variables.
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/bin/bash
</span><span style="color:#75715e"></span>
GITHUB_USERNAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;janikvonrotz&#34;</span>
GITHUB_TOKEN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;*******&#34;</span>

GITHUB_TOTAL_REPOS<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;https://api.github.com/user&#34;</span> | jq <span style="color:#e6db74">&#39;.public_repos + .total_private_repos&#39;</span><span style="color:#66d9ef">)</span>
GITHUB_PAGINATION<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>
GITHUB_NEEDED_PAGES<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span> <span style="color:#f92672">(</span>GITHUB_TOTAL_REPOS <span style="color:#f92672">+</span> GITHUB_PAGINATION <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span><span style="color:#f92672">)</span> <span style="color:#f92672">/</span> GITHUB_PAGINATION <span style="color:#66d9ef">))</span>

echo <span style="color:#e6db74">&#34;Found </span>$GITHUB_TOTAL_REPOS<span style="color:#e6db74"> repos, processing over </span>$GITHUB_NEEDED_PAGES<span style="color:#e6db74"> page(s)...&#34;</span>

<span style="color:#66d9ef">for</span> <span style="color:#f92672">((</span> PAGE<span style="color:#f92672">=</span>1; PAGE&lt;<span style="color:#f92672">=</span>GITHUB_NEEDED_PAGES; PAGE++ <span style="color:#f92672">))</span>; <span style="color:#66d9ef">do</span>
    REPOS<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>        <span style="color:#e6db74">&#34;https://api.github.com/user/repos?per_page=</span>$GITHUB_PAGINATION<span style="color:#e6db74">&amp;page=</span>$PAGE<span style="color:#e6db74">&amp;type=owner&#34;</span><span style="color:#66d9ef">)</span>
    
    echo <span style="color:#e6db74">&#34;</span>$REPOS<span style="color:#e6db74">&#34;</span> | jq -r <span style="color:#e6db74">&#39;.[] | select(.owner.login == &#34;&#39;</span><span style="color:#e6db74">&#34;</span>$GITHUB_USERNAME<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">&#39;&#34;) | .full_name&#39;</span> | <span style="color:#66d9ef">while</span> read REPO_FULL; <span style="color:#66d9ef">do</span>
    
        echo <span style="color:#e6db74">&#34;Disabling dependabot for: </span>$REPO_FULL<span style="color:#e6db74">&#34;</span>
        
        <span style="color:#75715e"># Disable dependabot vulnerability alerts</span>
        curl -s -X DELETE <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Accept: application/vnd.github.v3+json&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            <span style="color:#e6db74">&#34;https://api.github.com/repos/</span>$REPO_FULL<span style="color:#e6db74">/vulnerability-alerts&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -w <span style="color:#e6db74">&#34; -&gt; Status: %{http_code}\n&#34;</span> -o /dev/null

        <span style="color:#75715e"># Disable dependabot automated security fixes (PRs)</span>
        curl -s -X DELETE <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Accept: application/vnd.github.v3+json&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            <span style="color:#e6db74">&#34;https://api.github.com/repos/</span>$REPO_FULL<span style="color:#e6db74">/automated-security-fixes&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -w <span style="color:#e6db74">&#34; -&gt; Status: %{http_code}\n&#34;</span> -o /dev/null
    <span style="color:#66d9ef">done</span>
<span style="color:#66d9ef">done</span>
</code></pre>
</div>
<p>
  The script will create list of repos connected to your account. Then it loops through the list disabling the alerts.
</p>
<p>
  The script above only works for personal accounts. If you want to disable the alerts for all repos of an organisation, use this script:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/bin/bash
</span><span style="color:#75715e"></span>
<span style="color:#75715e"># Configuration</span>
ORG_NAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Mint-System&#34;</span>
GITHUB_TOKEN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;*******&#34;</span>

GITHUB_TOTAL_REPOS<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#e6db74">&#34;https://api.github.com/orgs/</span>$ORG_NAME<span style="color:#e6db74">&#34;</span> | jq -r <span style="color:#e6db74">&#39;.public_repos + .total_private_repos&#39;</span><span style="color:#66d9ef">)</span>
GITHUB_PAGINATION<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>
GITHUB_NEEDED_PAGES<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span> <span style="color:#f92672">(</span>GITHUB_TOTAL_REPOS <span style="color:#f92672">+</span> GITHUB_PAGINATION <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span><span style="color:#f92672">)</span> <span style="color:#f92672">/</span> GITHUB_PAGINATION <span style="color:#66d9ef">))</span>

echo <span style="color:#e6db74">&#34;Found </span>$GITHUB_TOTAL_REPOS<span style="color:#e6db74"> repos in org &#39;</span>$ORG_NAME<span style="color:#e6db74">&#39;, processing over </span>$GITHUB_NEEDED_PAGES<span style="color:#e6db74"> page(s)...&#34;</span>

<span style="color:#75715e"># Loop through each page of organization repositories</span>
<span style="color:#66d9ef">for</span> <span style="color:#f92672">((</span> PAGE<span style="color:#f92672">=</span>1; PAGE&lt;<span style="color:#f92672">=</span>GITHUB_NEEDED_PAGES; PAGE++ <span style="color:#f92672">))</span>; <span style="color:#66d9ef">do</span>
    REPOS<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>        <span style="color:#e6db74">&#34;https://api.github.com/orgs/</span>$ORG_NAME<span style="color:#e6db74">/repos?per_page=</span>$GITHUB_PAGINATION<span style="color:#e6db74">&amp;page=</span>$PAGE<span style="color:#e6db74">&amp;type=public&#34;</span><span style="color:#66d9ef">)</span>

    echo <span style="color:#e6db74">&#34;</span>$REPOS<span style="color:#e6db74">&#34;</span> | jq -r <span style="color:#e6db74">&#39;.[] | .full_name&#39;</span> | <span style="color:#66d9ef">while</span> read REPO_FULL; <span style="color:#66d9ef">do</span>
    
        echo <span style="color:#e6db74">&#34;Disabling dependabot for: </span>$REPO_FULL<span style="color:#e6db74">&#34;</span>

        <span style="color:#75715e"># Disable dependabot vulnerability alerts</span>
        curl -s -X DELETE <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Accept: application/vnd.github.v3+json&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            <span style="color:#e6db74">&#34;https://api.github.com/repos/</span>$REPO_FULL<span style="color:#e6db74">/vulnerability-alerts&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -w <span style="color:#e6db74">&#34; -&gt; Status: %{http_code}\n&#34;</span> -o /dev/null

        <span style="color:#75715e"># Disable dependabot automated security fixes (PRs)</span>
        curl -s -X DELETE <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Accept: application/vnd.github.v3+json&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            <span style="color:#e6db74">&#34;https://api.github.com/repos/</span>$REPO_FULL<span style="color:#e6db74">/automated-security-fixes&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -w <span style="color:#e6db74">&#34; -&gt; Status: %{http_code}\n&#34;</span> -o /dev/null
    <span style="color:#66d9ef">done</span>
<span style="color:#66d9ef">done</span>
</code></pre>
</div>
<p>
  You can use the same access token. Simply set the org name and your are good to go.
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/software-development">
  Software development
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/noice">
  noice
</a>
,
<a href="https://janikvonrotz.ch/tags/cancelling">
  cancelling
</a>
,
<a href="https://janikvonrotz.ch/tags/github">
  github
</a>
,
<a href="https://janikvonrotz.ch/tags/microsoft">
  microsoft
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2025-08-09-10-disable-dependabot-alerts-for-all-repos.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2025%2f09%2f10%2f2025-08-09-10-disable-dependabot-alerts-for-all-repos%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Wed, 10 Sep 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">5d3ff06d7b4fcb8045af4d8b34a979f7</guid>
    </item>
    <item>
      <title>Migrate from Github to Codeberg</title>
      <link>https://janikvonrotz.ch/2025/08/20/migrate-from-github-to-codeberg/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/github-codeberg-migrate.png"/>
<br/>
<small>
  8 min read
</small>
<h1>
  Migrate from Github to Codeberg
</h1>
<time>
  August 20, 2025
</time>
<p></p>
<p>
  Since the
  <a href="https://arstechnica.com/gadgets/2025/08/github-will-be-folded-into-microsoft-proper-as-ceo-steps-down/">
    enshittification of GitHub
  </a>
  I decided to become a
  <em>
    Berger
  </em>
  instead of
  <em>
    Hubber
  </em>
  . Which I means that I wanted to move all my repos from github.com to codeberg.org.
</p>
<p>
  Running a migration script is easy. But of course there are many details to consider once the repos have been moved. In this post I’ll brief you on my experience and give you details on these challenges:
</p>
<ul>
  <li>
    GitHub-Integration with Vercel
  </li>
  <li>
    Update git repo origin
  </li>
  <li>
    Replace GitHub links in repos
  </li>
  <li>
    Migrate GitHub Actions
  </li>
  <li>
    Redirect people to Codeberg
  </li>
  <li>
    Archive GitHub repos
  </li>
  <li>
    Mirror to GitHub
  </li>
</ul>
<h2 id="running-the-migration">
  Running the migration
</h2>
<p>
  As mentioned running a migration script that copies the repos from GitHub to Codeberg is easy.
</p>
<p>
  The heavy work was done by
  <a href="https://github.com/LionyxML/migrate-github-to-codeberg">
    https://github.com/LionyxML/migrate-github-to-codeberg
  </a>
  . In order to run this script you need to create a token with read/write access to user, organisation and repo for Codeberg. Create the token here:
  <a href="https://codeberg.org/user/settings/applications">
    https://codeberg.org/user/settings/applications
  </a>
  Then you do the same for GitHub. Create a token with read/write access to user and repo here:
  <a href="https://github.com/settings/tokens">
    https://github.com/settings/tokens
  </a>
</p>
<p>
  Clone the miggation script and update the variables. Run the script
  <code>
    ./migrate_github_to_codeberg.sh
  </code>
  and you should get an output like this:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-txt" data-lang="txt">&gt;&gt;&gt; Migrating: Bundesverfassung (public)...
 Success!
&gt;&gt;&gt; Migrating: Hack4SocialGood (public)...
 Success!
&gt;&gt;&gt; Migrating: Quarto (public)...
 Success!
&gt;&gt;&gt; Migrating: WebPrototype (public)...
 Success!
&gt;&gt;&gt; Migrating: Website (public)...
 Success!
&gt;&gt;&gt; Migrating: raspi-and-friends (public)...
 Success!
</code></pre>
</div>
<p>
  One issue was that the script migrated all repos from all of connected organisations.
  I had to delete all repos in Codeberg. The following script helped doing so:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/bin/bash
</span><span style="color:#75715e"></span>
CODEBERG_USERNAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;janikvonrotz&#34;</span>
CODEBERG_TOKEN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;*******&#34;</span>

repos_response<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -f -X GET <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  https://codeberg.org/api/v1/users/$CODEBERG_USERNAME/repos <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  -H <span style="color:#e6db74">&#34;Authorization: token </span>$CODEBERG_TOKEN<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>

<span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> $? -eq <span style="color:#ae81ff">0</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
  repo_names<span style="color:#f92672">=(</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">&#34;</span>$repos_response<span style="color:#e6db74">&#34;</span> | jq -r <span style="color:#e6db74">&#39;.[] |.name&#39;</span><span style="color:#66d9ef">)</span><span style="color:#f92672">)</span>

  <span style="color:#66d9ef">for</span> repo_name in <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>repo_names[@]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>; <span style="color:#66d9ef">do</span>
    echo <span style="color:#e6db74">&#34;Deleting repository </span>$repo_name<span style="color:#e6db74">...&#34;</span>

    delete_response<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -f -w <span style="color:#e6db74">&#34;%{http_code}&#34;</span> -X DELETE <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>      https://codeberg.org/api/v1/repos/$CODEBERG_USERNAME/$repo_name <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>      -H <span style="color:#e6db74">&#34;Authorization: token </span>$CODEBERG_TOKEN<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>

    <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> $delete_response -eq <span style="color:#ae81ff">204</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
      echo <span style="color:#e6db74">&#34;Repository </span>$repo_name<span style="color:#e6db74"> deleted successfully.&#34;</span>
    <span style="color:#66d9ef">else</span>
      echo <span style="color:#e6db74">&#34;Failed to delete repository </span>$repo_name<span style="color:#e6db74">. Status code: </span>$delete_response<span style="color:#e6db74">&#34;</span>
    <span style="color:#66d9ef">fi</span>
  <span style="color:#66d9ef">done</span>
<span style="color:#66d9ef">else</span>
  echo <span style="color:#e6db74">&#34;Failed to retrieve repository list. Status code: </span>$?<span style="color:#e6db74">&#34;</span>
<span style="color:#66d9ef">fi</span>
</code></pre>
</div>
<p>
  I had to run the script multiple times because of the API paging.
</p>
<p>
  To ensure the migration is done for repos that are assigned tomy account, I had to set owners variable in the script:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">OWNERS<span style="color:#f92672">=(</span>
    <span style="color:#e6db74">&#34;janikvonrotz&#34;</span>
<span style="color:#f92672">)</span>
</code></pre>
</div>
<p>
  And with another run
  <code>
    ./migrate_github_to_codeberg.sh
  </code>
  the script copied all repos from GitHub to Codeberg.
</p>
<h2 id="github-integration-with-vercel">
  GitHub-Integration with Vercel
</h2>
<p>
  I use Vercel to build and publish my static website. If you use Netlify you probabley face the same problem. Vercel is tightly integrated with GitHub. At the time of writing this post there was no integration for Codeberg available. So it was either stick with GitHub or get rid of the integartion.
</p>
<p>
  I decided to get rid of it and uninstall the Vercel app on GitHub. You can access your GitHub apps here:
  <a href="https://github.com/settings/installations">
    https://github.com/settings/installations
  </a>
</p>
<p>
  This will cause the Vercel projects to be disconnected from the GitHub project and thus they will no longer be deployed automatically.
</p>
<p>
  To deploy the websites you can use the Vercel cli. It is simple as cake once you are looged in. Here is an example of such a deployment:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">
<span style="color:#f92672">[</span>main<span style="color:#f92672">][</span>~/taskfile.build<span style="color:#f92672">]</span>$ vercel --prod
Vercel CLI 44.7.3
🔍  Inspect: https://vercel.com/janik-vonrotz/taskfile-build/4zoKQnE7osV9udRnUyBYdX9EzNif <span style="color:#f92672">[</span>3s<span style="color:#f92672">]</span>
✅  Production: https://taskfile-build-5vtumwoja-janik-vonrotz.vercel.app <span style="color:#f92672">[</span>3s<span style="color:#f92672">]</span>
2025-08-20T11:20:17.987Z  Running build in Washington, D.C., USA <span style="color:#f92672">(</span>East<span style="color:#f92672">)</span> – iad1
2025-08-20T11:20:17.988Z  Build machine configuration: <span style="color:#ae81ff">2</span> cores, <span style="color:#ae81ff">8</span> GB
2025-08-20T11:20:18.006Z  Retrieving list of deployment files...
2025-08-20T11:20:18.518Z  Downloading <span style="color:#ae81ff">54</span> deployment files...
2025-08-20T11:20:19.233Z  Restored build cache from previous deployment <span style="color:#f92672">(</span>BuULWL9zESMSfPa8QYN5gyoGA4JP<span style="color:#f92672">)</span>
2025-08-20T11:20:21.264Z  Running <span style="color:#e6db74">&#34;vercel build&#34;</span>
2025-08-20T11:20:21.727Z  Vercel CLI 46.0.2
2025-08-20T11:20:22.364Z  Detected <span style="color:#e6db74">`</span>pnpm-lock.yaml<span style="color:#e6db74">`</span> <span style="color:#ae81ff">9</span> which may be generated by pnpm@9.x or pnpm@10.x
2025-08-20T11:20:22.365Z  Using pnpm@9.x based on project creation date
2025-08-20T11:20:22.365Z  To use pnpm@10.x, manually opt in using corepack <span style="color:#f92672">(</span>https://vercel.com/docs/deployments/configure-a-build#corepack<span style="color:#f92672">)</span>
2025-08-20T11:20:22.380Z  Installing dependencies...
2025-08-20T11:20:23.109Z  Lockfile is up to date, resolution step is skipped
2025-08-20T11:20:23.148Z  Already up to date
2025-08-20T11:20:23.992Z
2025-08-20T11:20:24.001Z  Done in 1.4s using pnpm v9.15.9
2025-08-20T11:20:26.202Z  <span style="color:#f92672">[</span>11ty<span style="color:#f92672">]</span> Writing ./_site/index.html from ./README.md <span style="color:#f92672">(</span>liquid<span style="color:#f92672">)</span>
2025-08-20T11:20:26.208Z  <span style="color:#f92672">[</span>11ty<span style="color:#f92672">]</span> Benchmark     73ms  19%     1× <span style="color:#f92672">(</span>Configuration<span style="color:#f92672">)</span> <span style="color:#e6db74">&#34;@11ty/eleventy/html-transformer&#34;</span> Transform
2025-08-20T11:20:26.208Z  <span style="color:#f92672">[</span>11ty<span style="color:#f92672">]</span> Copied <span style="color:#ae81ff">4</span> Wrote <span style="color:#ae81ff">1</span> file in 0.38 seconds <span style="color:#f92672">(</span>v3.0.0<span style="color:#f92672">)</span>
2025-08-20T11:20:26.303Z  Build Completed in /vercel/output <span style="color:#f92672">[</span>4s<span style="color:#f92672">]</span>
2025-08-20T11:20:26.399Z  Deploying outputs...

</code></pre>
</div>
<p>
  Of course it is possible to setup a CI job that installs the Vercel cli and runs the prod deployment.
</p>
<h2 id="update-git-repo-origin">
  Update git repo origin
</h2>
<p>
  For all the local git repos you need to update the remote. The local remote url will still point to github.com and needs to replaced with the coderberg.org url. The following script finds git repos in the home folder and upates the matching url:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/bin/bash
</span><span style="color:#75715e"></span>
OLD_URL<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;git@github.com:janikvonrotz/&#34;</span>
NEW_URL<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;git@codeberg.org:janikvonrotz/&#34;</span>

<span style="color:#66d9ef">for</span> REPO in <span style="color:#66d9ef">$(</span>find <span style="color:#e6db74">&#34;</span>$HOME<span style="color:#e6db74">&#34;</span> -maxdepth <span style="color:#ae81ff">2</span> -type d -name <span style="color:#e6db74">&#39;.git&#39;</span><span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
    DIR<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>dirname <span style="color:#e6db74">&#34;</span>$REPO<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
    cd <span style="color:#e6db74">&#34;</span>$DIR<span style="color:#e6db74">&#34;</span>
    CURRENT_URL<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>git config --get remote.origin.url<span style="color:#66d9ef">)</span>
	NEW_CURRENT_URL<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">&#34;</span>$CURRENT_URL<span style="color:#e6db74">&#34;</span> | sed <span style="color:#e6db74">&#34;s|</span>$OLD_URL<span style="color:#e6db74">|</span>$NEW_URL<span style="color:#e6db74">|&#34;</span><span style="color:#66d9ef">)</span>
    <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$NEW_CURRENT_URL<span style="color:#e6db74">&#34;</span> !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;</span>$CURRENT_URL<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
        git remote set-url origin <span style="color:#e6db74">&#34;</span>$NEW_CURRENT_URL<span style="color:#e6db74">&#34;</span>
        echo <span style="color:#e6db74">&#34;Updated origin URL for </span><span style="color:#66d9ef">$(</span>basename <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>pwd<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74"> to: </span>$NEW_CURRENT_URL<span style="color:#e6db74">&#34;</span>
    <span style="color:#66d9ef">fi</span>
<span style="color:#66d9ef">done</span>
</code></pre>
</div>
<p>
  Submodule links in the
  <code>
    .gitmodules
  </code>
  have to be updated manually.
</p>
<h2 id="replace-github-links-in-repos">
  Replace GitHub links in repos
</h2>
<p>
  Not only the git remote links to github.com, but also the content stored in the repo. I often add a git clone command to the usage section in the
  <code>
    README.md
  </code>
  . The clone url has to be updated.
</p>
<p>
  I was able to solve this issue with semi-automated approach. I created several search and replace commands that look for github.com link patterns. The search pattern considers external links to github.com that had to be preserved.
</p>
<p>
  On the command line I entered the repo and ran the replacement commands:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e"># 1. Fix github.com/blob → codeberg.org/src/branch</span>
rg <span style="color:#e6db74">&#39;github\.com(:|/)(janikvonrotz)/[^/]+/blob/(main|master)&#39;</span> -l | <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  xargs sed -i <span style="color:#e6db74">&#39;s|github\.com\(:\|/\)\(janikvonrotz\)/\([^/]\+\)/blob/\(main\|master\)\(/[^&#34;]*\)\?|codeberg.org/\2/\3/src/branch/\4\5|g&#39;</span>

<span style="color:#75715e"># 2. Fix github.com/tree → codeberg.org/src/branch</span>
rg <span style="color:#e6db74">&#39;github\.com(:|/)(janikvonrotz)/[^/]+/tree/(main|master)&#39;</span> -l | <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  xargs sed -i <span style="color:#e6db74">&#39;s|github\.com\(:\|/\)\(janikvonrotz\)/\([^/]\+\)/tree/\(main\|master\)\(/[^&#34;]*\)\?|codeberg.org/\2/\3/src/branch/\4\5|g&#39;</span>

<span style="color:#75715e"># 3. Fix raw.githubusercontent.com → codeberg.org/raw/branch</span>
rg <span style="color:#e6db74">&#39;raw\.githubusercontent\.com/(janikvonrotz)/[^/]+/(main|master)&#39;</span> -l | <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  xargs sed -i <span style="color:#e6db74">&#39;s|raw\.githubusercontent\.com/\(janikvonrotz\)/\([^/]\+\)/\(main\|master\)\(/[^&#34;]*\)\?|codeberg.org/\1/\2/raw/branch/\3\4|g&#39;</span>

<span style="color:#75715e"># 4. Fix bare repo URLs: github.com/user/repo → codeberg.org/user/repo</span>
rg <span style="color:#e6db74">&#39;github\.com(:|/)janikvonrotz/[^/&#34;?#]+&#39;</span> -l | <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  xargs sed -i <span style="color:#e6db74">&#39;s|github\.com\(:\|/\)\(janikvonrotz\)/\([^/&#34;?#]\+\)|codeberg.org/\2/\3|g&#39;</span>

<span style="color:#75715e"># 5. Fix user profile URLs</span>
rg <span style="color:#e6db74">&#39;https://github\.com/janikvonrotz\b&#39;</span> -l | <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  xargs sed -i <span style="color:#e6db74">&#39;s|https://github\.com/janikvonrotz|https://codeberg.org/janikvonrotz|g&#39;</span>

rg <span style="color:#e6db74">&#39;https://github\.com/jankvonrotz\b&#39;</span> -l | <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>  xargs sed -i <span style="color:#e6db74">&#39;s|https://github\.com/jankvonrotz|https://codeberg.org/jankvonrotz|g&#39;</span>
</code></pre>
</div>
<p>
  In some cases simply replacing a link was not possible. For example Vuepress linked by default to GitHub and I had to change the
  <code>
    .vuepress/config.js
  </code>
  manually:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-js" data-lang="js"><span style="color:#a6e22e">repo</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;https://codeberg.org/janikvonrotz/$REPO&#39;</span>,
<span style="color:#a6e22e">repoLabel</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Codeberg&#39;</span>,
<span style="color:#a6e22e">docsBranch</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;main&#39;</span>,
</code></pre>
</div>
<p>
  Nonetheless replacing the links was easier than expected.
</p>
<h2 id="migrate-github-actions">
  Migrate GitHub Actions
</h2>
<p>
  For my personal repos I didn’t run a a lot of GitHub Actions. One of the few was this action:
  <a href="https://github.com/janikvonrotz/janikvonrotz.ch/blob/main/.github/workflows/build.yml">
    https://github.com/janikvonrotz/janikvonrotz.ch/blob/main/.github/workflows/build.yml
  </a>
  <br/>
  It builds and pushes a Docker image to Docker registry.
</p>
<p>
  Codeberg offers two ways to run jobs.
  There is the Woodpecker CI:
  <a href="https://docs.codeberg.org/ci/#using-codeberg&#39;s-instance-of-woodpecker-ci">
    https://docs.codeberg.org/ci/#using-codeberg&#39;s-instance-of-woodpecker-ci
  </a>
  <br/>
  And there are Forgejo Actions:
  <a href="https://docs.codeberg.org/ci/actions/#installing-forgejo-runner">
    https://docs.codeberg.org/ci/actions/#installing-forgejo-runner
  </a>
</p>
<p>
  I decided to use Forgejo Action. First I enabled Forgejo Actions in the repos settings. Next I created the
  <code>
    DOCKER_PAT
  </code>
  secret in the user settings:
  <a href="https://codeberg.org/user/settings/actions/secrets">
    https://codeberg.org/user/settings/actions/secrets
  </a>
  .
</p>
<p>
  Forgejo Actions support the same YAML spec and thus I only need to rename the
  <code>
    .github
  </code>
  folder to
  <code>
    .forgejo
  </code>
  . I pushed the changes and the first run was created:
  <a href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/actions/runs/1">
    https://codeberg.org/janikvonrotz/janikvonrotz.ch/actions/runs/1
  </a>
</p>
<p>
  However the was waiting for the default Forgejo runner and it seemed not be meant for public use. So I decided to provide my own Forgejo runner.
</p>
<p>
  I created a Helm chart to deploy a Forgejo runner:
  <a href="https://kubernetes.build/forgejoRunner/README.html">
    https://kubernetes.build/forgejoRunner/README.html
  </a>
  <br/>
  Further I updated the
  <code>
    .forgejo/workflow/build.yml
  </code>
  to use the provided runner. The setup worked but it turned out that most of the CI dependencies are not in the YAML but on the runner. As I understand GitHub Action runners are actual virtual machines on Azure. Replicating these environments is not possible. Also building a multi-platform Docker image with Docker in Docker inside a Kubernetes cluster is not the best idea.
</p>
<p>
  I decided to put this issue on hold. As an alternative I setup a mirror from the Codeberg repo to GitHub (see section below).
</p>
<h2 id="redirect-people-to-codeberg">
  Redirect people to Codeberg
</h2>
<p>
  It is not possible to redirect repo visitors automatically from GithHub to Codeberg. I decided to update the repo description with a link to the new location. The following script walks through the GitHub repos and updates the description:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">CODEBERG_URL<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://codeberg.org/janikvonrotz/&#34;</span>
GITHUB_USERNAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;janikvonrotz&#34;</span>
GITHUB_TOKEN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;*******&#34;</span>

GITHUB_PAGINATION<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>
github_total_repos<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;https://api.github.com/user&#34;</span> | jq <span style="color:#e6db74">&#39;.public_repos +.total_private_repos&#39;</span><span style="color:#66d9ef">)</span>
github_needed_pages<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span> <span style="color:#f92672">(</span>$github_total_repos <span style="color:#f92672">+</span> $GITHUB_PAGINATION <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span><span style="color:#f92672">)</span> <span style="color:#f92672">/</span> $GITHUB_PAGINATION <span style="color:#66d9ef">))</span>

<span style="color:#66d9ef">for</span> <span style="color:#f92672">((</span>github_page_counter <span style="color:#f92672">=</span> 1; github_page_counter &lt;<span style="color:#f92672">=</span> github_needed_pages; github_page_counter++<span style="color:#f92672">))</span>; <span style="color:#66d9ef">do</span>
    repos<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;https://api.github.com/user/repos?per_page=</span><span style="color:#e6db74">${</span>GITHUB_PAGINATION<span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;page=</span><span style="color:#e6db74">${</span>github_page_counter<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
    <span style="color:#66d9ef">for</span> repo in <span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">&#34;</span>$repos<span style="color:#e6db74">&#34;</span> | jq -r <span style="color:#e6db74">&#39;.[] | select(.owner.login == &#34;&#39;</span><span style="color:#e6db74">&#34;</span>$GITHUB_USERNAME<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">&#39;&#34;) |.name&#39;</span><span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
        echo <span style="color:#e6db74">&#34;Update repo description for </span>$GITHUB_USERNAME<span style="color:#e6db74">/</span>$repo<span style="color:#e6db74">:&#34;</span>
        new_description<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;This repository has been moved to </span>$CODEBERG_URL$repo<span style="color:#e6db74">. Please visit the new location for the latest updates.&#34;</span>
        curl -X PATCH <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -H <span style="color:#e6db74">&#34;Content-Type: application/json&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            https://api.github.com/repos/$GITHUB_USERNAME/$repo <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>            -d <span style="color:#e6db74">&#34;{\&#34;description\&#34;:\&#34;</span>$new_description<span style="color:#e6db74">\&#34;}&#34;</span>
    <span style="color:#66d9ef">done</span>
<span style="color:#66d9ef">done</span>
</code></pre>
</div>
<h2 id="archive-github-repos">
  Archive GitHub repos
</h2>
<p>
  Archiving a repo on GitHub means that it is no longer maintained there. Also the archived repo becomes readonly. With the following script I archived all my GitHub repos:
</p>
<div class="highlight">
  <pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#75715e">#!/bin/bash
</span><span style="color:#75715e"></span>
ARCHIVE_MESSAGE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Repository migrated to Codeberg.&#34;</span>
GITHUB_USERNAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;janikvonrotz&#34;</span>
GITHUB_TOKEN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;*******&#34;</span>

GITHUB_PAGINATION<span style="color:#f92672">=</span><span style="color:#ae81ff">100</span>
github_total_repos<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;https://api.github.com/user&#34;</span> | jq <span style="color:#e6db74">&#39;.public_repos +.total_private_repos&#39;</span><span style="color:#66d9ef">)</span>
github_needed_pages<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span> <span style="color:#f92672">(</span>$github_total_repos <span style="color:#f92672">+</span> $GITHUB_PAGINATION <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span><span style="color:#f92672">)</span> <span style="color:#f92672">/</span> $GITHUB_PAGINATION <span style="color:#66d9ef">))</span>

<span style="color:#66d9ef">for</span> <span style="color:#f92672">((</span>github_page_counter <span style="color:#f92672">=</span> 1; github_page_counter &lt;<span style="color:#f92672">=</span> github_needed_pages; github_page_counter++<span style="color:#f92672">))</span>; <span style="color:#66d9ef">do</span>
    repos<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;https://api.github.com/user/repos?per_page=</span><span style="color:#e6db74">${</span>GITHUB_PAGINATION<span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;page=</span><span style="color:#e6db74">${</span>github_page_counter<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
    <span style="color:#66d9ef">for</span> repo in <span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">&#34;</span>$repos<span style="color:#e6db74">&#34;</span> | jq -r <span style="color:#e6db74">&#39;.[] | select(.owner.login == &#34;&#39;</span><span style="color:#e6db74">&#34;</span>$GITHUB_USERNAME<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">&#39;&#34;) |.name&#39;</span><span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
        echo <span style="color:#e6db74">&#34;Archive repo </span>$GITHUB_USERNAME<span style="color:#e6db74">/</span>$repo<span style="color:#e6db74">:&#34;</span>
        curl -X PATCH -H <span style="color:#e6db74">&#34;Authorization: token </span>$GITHUB_TOKEN<span style="color:#e6db74">&#34;</span> -H <span style="color:#e6db74">&#34;Content-Type: application/json&#34;</span> https://api.github.com/repos/$GITHUB_USERNAME/$repo -d <span style="color:#e6db74">&#34;{\&#34;archived\&#34;:true,\&#34;archive_message\&#34;:\&#34;</span>$ARCHIVE_MESSAGE<span style="color:#e6db74">\&#34;}&#34;</span>
    <span style="color:#66d9ef">done</span>
<span style="color:#66d9ef">done</span>
</code></pre>
</div>
<h2 id="mirror-to-github">
  Mirror to GitHub
</h2>
<p>
  Mirroring a repo to GitHub solved the problem I had with running the GitHub Actions in the Codeberg environment. It is possible to mirror a Codeberg repo to GitHub and thus you can trigger GitHub Actions with the push of commit.
</p>
<p>
  In the mirror settings of your repo, in my case it was
  <a href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/settings">
    https://codeberg.org/janikvonrotz/janikvonrotz.ch/settings
  </a>
  , you can setup a push url. Enter the same credentials as used in the migration script and ensure to tick the
  <em>
    push on commit
  </em>
  box.
</p>
<h2 id="summary-and-outlook">
  Summary and outlook
</h2>
<p>
  Not being able to run my own Forgejo runner was very frustrating. I think CI should not be that hard. I will try to setup a Forgejo runner on a bare metal vm and build my website image with it.
</p>
<p>
  Overall moving my personal repos from GitHub to Codeberg was easy. I did not consider to move the repos for my organisation yet. I think this will be a much more difficult challenge. The organisation repos are integrated deeply into many other projects. The best approach I can think of is mirroring the repos from GitHub to Codeberg and start the transition with one repo and move a long the linked repos.
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/software-development">
  Software development
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/github">
  github
</a>
,
<a href="https://janikvonrotz.ch/tags/codeberg">
  codeberg
</a>
,
<a href="https://janikvonrotz.ch/tags/migration">
  migration
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2025-08-20-migrate-from-github-to-codeberg.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2025%2f08%2f20%2fmigrate-from-github-to-codeberg%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">1b872b78da8754932c8f6a53b8cd62fd</guid>
    </item>
    <item>
      <title>Store Passkeys in KeePassXC</title>
      <link>https://janikvonrotz.ch/2025/07/04/store-passkeys-keepassxc/</link>
      <description><![CDATA[
<img src="https://janikvonrotz.ch/images/KeePassXC.png"/>
<br/>
<small>
  3 min read
</small>
<h1>
  Store Passkeys in KeePassXC
</h1>
<time>
  July 4, 2025
</time>
<p></p>
<hr/>
<p>
  The goal of Passkeys is to replace passwords.
</p>
<p>
  The idea is that instead of remembering a password and entering it to access your account, you own a device that generates a password for you.
</p>
<p>
  Remembering is replaced with Owning.
</p>
<p>
  In this post, I’ll give an example of such a device and how you can create and store a Passkey securely.
</p>
<h2 id="yubikey">
  YubiKey
</h2>
<p>
  A device that can be used as a Passkey is the
  <a href="https://en.wikipedia.org/wiki/YubiKey">
    YubiKey
  </a>
  . Setting it up is actually quite simple. In this example, we have an online account and are going to set up the Passkey. In the security settings of the online account, I click
  <em>
    Add Passkey
  </em>
  , and the browser prompts for an input:
</p>
<p>
  <img src="https://janikvonrotz.ch/images/browser%20prompt%20passkey.png" alt=""/>
</p>
<p>
  The YubiKey is plugged in, I touch the YubiKey, and the device is registered. That’s it. From now on, when I log into my account, as a login option, I can choose Passkey. The browser prompts for the input, I touch the YubiKey, and get logged in.
</p>
<p>
  However, at this point, you might ask: What happens when I lose the YubiKey? Can I make a backup of the key?
</p>
<p>
  The short answer is: You cannot create a backup of a YubiKey.
</p>
<p>
  So, the YubiKey might not be the best solution to use as a Passkey. Luckily, there are other providers and devices to manage a Passkey.
</p>
<h2 id="keepassxc">
  KeePassXC
</h2>
<p>
  Here, I will show you how you can create and store a Passkey with
  <a href="https://keepassxc.org/">
    KeePassXC
  </a>
  .
</p>
<ul>
  <li>
    Install KeePassXC
  </li>
  <li>
    Set up a password database
  </li>
  <li>
    In the settings, enable the browser integration and select your browser
  </li>
  <li>
    Install the KeePassXC browser extension
  </li>
  <li>
    Connect the extension to your KeePassXC database
  </li>
  <li>
    Enable the
    <em>
      Passkeys
    </em>
    option in the KeePassXC browser extension settings
  </li>
  <li>
    Optionally, set the
    <em>
      Default group for saving new passkeys
    </em>
    field
  </li>
</ul>
<p>
  <img src="https://janikvonrotz.ch/images/KeePassXC%20Browser%20Extension%20Passkeys.png" alt=""/>
</p>
<p>
  You may need to restart your browser for the changes to take effect. When you register a new Passkey for your account, the KeePassXC extension will open the KeePassXC database locally and show this dialog:
</p>
<p>
  <img src="https://janikvonrotz.ch/images/KeePassXC%20Passkey%20Register.png" alt=""/>
</p>
<p>
  You can click
  <em>
    Register
  </em>
  , and then you’ll find a Passkey entry in your KeePassXC database.
</p>
<p>
  Logging into your account with a Passkey is simple. Select the Passkey option, and the KeePassXC extension will find a matching entry and prompt to authenticate.
</p>
<p>
  <img src="https://janikvonrotz.ch/images/KeePassXC%20Authentication.png" alt=""/>
</p>
<p>
  Click
  <em>
    Authenticate
  </em>
  , and you should be logged in.
</p>
<p>
  The KeePassXC database can be backed up, and while Passkey entries can technically be exported, it’s crucial to understand the security implications before doing so.
</p>
<h2 id="recommendations">
  Recommendations
</h2>
<p>
  When registering a Passkey for your account, I recommend using both KeePassXC and YubiKey. The main reason is the mobile browser of your smartphone. Setting up the KeePassXC Passkey solution on your smartphone is currently not possible (as far as I know).
</p>
<p>
  The YubiKey can be plugged into the smartphone’s USB-C port and used to authenticate with any mobile browser. Here is a simple webpage that shows Passkey device and browser compatibility:
  <a href="https://www.passkeys.io/compatible-devices">
    https://www.passkeys.io/compatible-devices
  </a>
</p>
<p></p>
<strong>
  Categories:
</strong>
<a href="https://janikvonrotz.ch/categories/security">
  Security
</a>
<br/>
<strong>
  Tags:
</strong>
<a href="https://janikvonrotz.ch/tags/keepass">
  keepass
</a>
,
<a href="https://janikvonrotz.ch/tags/passkeys">
  passkeys
</a>
,
<a href="https://janikvonrotz.ch/tags/password">
  password
</a>
,
<a href="https://janikvonrotz.ch/tags/manage">
  manage
</a>
<br/>
<a target="_blank" href="https://codeberg.org/janikvonrotz/janikvonrotz.ch/src/content/post/2025-07-04-store-passkeys-keepassxc.md">
  Edit this page
</a>
<br/>
<a target="_blank" href="https://plausible.io/janikvonrotz.ch?page=%2f2025%2f07%2f04%2fstore-passkeys-keepassxc%2f">
  Show statistic for this page
</a>
]]></description>
      <pubDate>Fri, 04 Jul 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="false">e893a388831bd157def30e177051634b</guid>
    </item>
  </channel>
</rss>