Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ const staticPages = (await getCollection("staticPages"))
</Link>
<nav class="flex gap-1 text-base">
{
staticPages.map((page) => (
<Link href={`/${page.id}`} underline={false}>
{page.data.title.toLocaleLowerCase()}
</Link>
/* The separator belongs between the mapped links too. The hardcoded
spans below only ever separated the fixed entries, so with two
static pages the nav read "now uses / work / projects" and the
first two ran together. Emitting it before every entry except the
first keeps it correct for one page or five. */
staticPages.map((page, i) => (
<>
{i > 0 && <span>{`/`}</span>}
<Link href={`/${page.id}`} underline={false}>
{page.data.title.toLocaleLowerCase()}
</Link>
</>
))
}
<span>
Expand Down
2 changes: 1 addition & 1 deletion src/content/projects/farcaster-hackathons/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ repoURL: ""
July 2024

<a href="https://www.youtube.com/watch?v=Brm5Pr7Jkc4">
<img src="/images/projects/farcaster_hackathons/backdrop_datalatte.png" style="height: 40%; width: 40%; object-fit: contain;">
<img src="/images/projects/farcaster_hackathons/backdrop_datalatte.png" alt="Datalatte project backdrop — watch the demo video" style="height: 40%; width: 40%; object-fit: contain;">
</a>

Datalatte is a DataDAO managing a community knowledge graph that integrates public and private personal knowledge graphs as its core asset.
Expand Down
26 changes: 20 additions & 6 deletions src/content/staticPages/now.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@
title: "Now"
slug: "/now"
description: "What I'm working on right now."
date: "2026-06-13"
date: "2026-08-26"
---

I build **privacy-first apps**: tools that run on your own device, work without accounts or a backend, and only reach the cloud when *you* bring your own AI key. No telemetry, no data collection, no lock-in.
Two things have my attention: the infrastructure under a blockchain protocol, and a set of privacy-first apps that run on your own device.

Most of my time right now goes into [TobiBoard](/projects/TobiBoard), an on-device Android keyboard with optional bring-your-own-key AI voice-to-text and text fixing, and [TobiReader](/projects/TobiReader), a local-first RSS reader with on-device read-aloud and optional AI summaries. Around those I keep a small portfolio of shipped apps: a cross-platform screen recorder, macOS voice-to-text and menu-bar utilities, and a few AI-assisted side projects. The full list is on the [projects page](/projects).
## The day job

The thread through all of it: see how far useful, AI-flavoured software can go while keeping user data on the user's own hardware. In practice that's two patterns: run models locally where it's feasible (on-device speech and text-to-speech, local LLMs via Ollama on the desktop), and bring-your-own-key for the rest, so anything cloud-bound uses *your* API key and endpoint, never mine.
I'm a senior backend engineer at [Gnosis](https://www.gnosis.io), working on [Circles](https://aboutcircles.com). I own the backend services and the whole platform under them, from service code through deployment to monitoring: six hosts serving over 1.5 million requests a day, seven public services sitting at 99.99% uptime.

I also do some consulting through [Leinss Consulting](https://consulting.leinss.xyz), helping service companies put generative AI to work, but building my own apps is the main focus these days.
Most of what I've built there falls into three buckets. Four backend services written from the first commit, in TypeScript on PostgreSQL. Most of the protocol indexer, a C# plugin inside the execution client, whose query layer I rebuilt: p95 response times came down by between 5x and 141x depending on the call. And the platform itself, which moved off managed cloud onto bare metal, replacing Terraform and Kubernetes with thirty-eight Ansible roles that deploy blockchain nodes and PostgreSQL with automated failover, replication and pooling, host by host without downtime. Monthly cost fell by two thirds, and a merged change now reaches production in 10 to 20 minutes.

Based in Germany. This is a [now page](https://nownownow.com/about); last updated June 2026.
I care about the observability more than is probably normal. Two-tier health checks pull an unhealthy node out of DNS within 90 seconds, and a year of metrics means a regression is a question with an answer rather than an argument.

## The apps

Alongside that I build **privacy-first apps**: tools that run on your own device, work without accounts or a backend, and only reach the cloud when *you* bring your own AI key. No telemetry, no data collection, no lock-in.

Most of that time goes into [TobiBoard](/projects/TobiBoard), an on-device Android keyboard with optional bring-your-own-key AI voice-to-text and text fixing, and [TobiReader](/projects/TobiReader), a local-first RSS reader with on-device read-aloud and optional AI summaries. TobiBoard ships from my own [F-Droid repository](https://leinss.xyz/TobiBoard/repo). Around those sits a small portfolio: a cross-platform screen recorder, macOS voice-to-text and menu-bar utilities, and a few AI-assisted side projects. The full list is on the [projects page](/projects).

The thread through all of it is seeing how far useful, AI-flavoured software can go while keeping user data on the user's own hardware. In practice that's two patterns: run models locally where it's feasible, and bring-your-own-key for the rest, so anything cloud-bound uses *your* API key and endpoint, never mine.

## And some consulting

I also take on work through [Leinss Consulting](https://consulting.leinss.xyz), helping service companies put generative AI to work. Currently an e-commerce storefront with a back-office product management app, and dispatch and route planning for a fuel delivery business.

Based in Germany, relocating to Asia. This is a [now page](https://nownownow.com/about); last updated August 2026.
62 changes: 39 additions & 23 deletions src/content/staticPages/uses.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,62 @@
---
title: "Uses"
slug: "/uses"
description: "The hardware, tools and services I use to build my apps."
date: "2026-06-12"
description: "The hardware, tools and services I use to build my apps and run my infrastructure."
date: "2026-08-26"
---

A snapshot of what I build with. Inspired by [uses.tech](https://uses.tech). Everything here leans toward working offline and keeping data local, the same principle behind [my apps](/projects).
A snapshot of what I build with. Inspired by [uses.tech](https://uses.tech). The bias throughout is toward things that work offline, keep data local, and can be rebuilt from a file in a repository rather than a click in a dashboard.

## Hardware

- **Apple Silicon Mac**: main development machine, and where I test on-device LLMs locally.
- Android devices for testing [TobiBoard](/projects/TobiBoard) and [TobiReader](/projects/TobiReader).
<!-- TODO(Tobias): add exact Mac model + any other daily-driver gear (monitor, phone, keyboard) you want listed. -->
- **Apple Silicon Mac**: main development machine, and where I run local LLMs.
- **Android devices** for testing [TobiBoard](/projects/TobiBoard) and [TobiReader](/projects/TobiReader), driven over `adb` when a test needs to be repeatable.
- **A small homelab**: a NAS, a mini PC and a Raspberry Pi, which between them run everything in the self-hosted section below. All of it is configured by Ansible, none of it by hand.

## Editor & terminal
## Terminal

<!-- TODO(Tobias): confirm your editor (VS Code / Neovim / Android Studio / …) so this isn't a guess. -->
- **zsh** as the shell.
- **fnm** for Node version management (a single `.node-version` keeps local and CI in sync).
I live in the terminal, and most of the tooling is a replacement for something older that I got tired of.

- **zsh**, with `eza` for `ls`, `bat` for `cat`, `fd` for `find`, `ripgrep` for `grep`, `delta` for `diff`, `dust` for `du`, `duf` for `df` and `btop` for `htop`.
- **`jq` and `yq`** for anything shaped like JSON or YAML, **httpie** for HTTP by hand.
- **direnv** for per-project environment, with secrets resolved from the macOS Keychain rather than sitting in a `.envrc`.
- **chezmoi** for dotfiles, with **age** encrypting the parts that need it.
- **fnm** for Node versions, a single `.node-version` keeping local and CI in sync.
- **Claude Code** for day-to-day development.

## Web stack

This site, and most of my web work, runs on:
This site, and most of my web work:

- **Astro** + **MDX** for content-driven sites
- **Astro** and **MDX** for content-driven sites
- **Tailwind CSS** for styling
- **TypeScript**, linted with **ESLint**
- **pnpm** for packages, **Node 24**
- Deployed to **GitHub Pages**

## On-device & AI tooling
## Mobile and desktop

- **Ollama** for running local LLMs on the desktop (e.g. the auto-tagging in [taggr](/projects/taggr)).
- **On-device speech**: local transcription and text-to-speech for the mobile apps, so audio never leaves the device.
- **Bring-your-own-key** cloud models for the optional AI features in my apps: the key stays on the user's device, and calls go to the provider *you* choose.
- **Claude Code** for day-to-day development.
<!-- TODO(Tobias): if the on-device Gemma 3 1B / MediaPipe work is real and current, add it back here and in /now. -->
- **Kotlin** for Android, **Swift** for macOS, **TypeScript** where a thing should run everywhere.
- **A self-hosted F-Droid repository** plus GitHub Releases for distributing Android builds, so an install does not require a store account.

## On-device and AI tooling

- **Ollama** for local models on the desktop, including the auto-tagging in [taggr](/projects/taggr).
- **On-device speech**: transcription and text-to-speech that run on the phone, so audio never leaves it.
- **Bring-your-own-key** cloud models for the optional AI features in my apps. The key stays on the user's device and calls go to the provider *you* choose.

## Infrastructure and self-hosting

## Self-hosted services
The professional half of this list, and the part I would defend hardest in an interview.

I prefer to host the supporting infrastructure myself rather than hand data to third parties:
- **Ansible** for everything. If a box needs a change, it happens in a role and gets applied, not typed into a shell.
- **Docker** and **Traefik** for services and routing.
- **Prometheus, Grafana, Loki and Alertmanager** for metrics, logs and paging, with **Blackbox** for external probes and **cAdvisor** for container-level detail. An alert that cannot fire is worse than no alert, so the rules have their own unit tests that run in CI.
- **Tailscale** so nothing that does not need a public port has one.
- **Borg** for backups, on a tiered schedule, with restores actually exercised.
- **PostgreSQL** with replication, failover and pooling.
- **Gitea**, **FreshRSS**, **SearXNG**, **Navidrome**, **ArchiveBox** and **ntfy**, all self-hosted, because I would rather run a thing than be a product inside it.
- **Frigate** for camera detection and **ioBroker** for home automation, wired together so a detection reaches my phone.
- **umami** for privacy-friendly, cookie-free analytics, and **Listmonk** for the newsletter.

- **umami** for privacy-friendly, cookie-free analytics
- **Listmonk** for the newsletter
- A self-hosted **F-Droid** repository for distributing Android builds
Deliberately not listed: the identity, DNS-filtering and intrusion-detection pieces. They are the parts of a homelab where naming the software is a small favour to somebody scanning, and no favour at all to a reader.
4 changes: 2 additions & 2 deletions src/pages/[...slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ const { Content } = await render(post);
{readingTime(post.body ?? "")}
</div>
</div>
<div class="animate text-2xl font-semibold text-black dark:text-white">
<h1 class="animate text-2xl font-semibold text-black dark:text-white">
{post.data.title}
</div>
</h1>
</div>
<article class="longform animate">
<Content />
Expand Down
4 changes: 2 additions & 2 deletions src/pages/education/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ const education = await Promise.all(
<PageLayout title={EDUCATION.TITLE} description={EDUCATION.DESCRIPTION}>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
<h1 class="animate font-semibold text-black dark:text-white">
🎓 Education
</div>
</h1>
<ul class="flex flex-col space-y-8">
{
education.map((entry) => (
Expand Down
4 changes: 2 additions & 2 deletions src/pages/projects/[...slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ const appSchema = softwareApplicationSchema(project, Astro.site);
{readingTime(project.body ?? "")}
</div>
</div>
<div class="animate text-2xl font-semibold text-black dark:text-white">
<h1 class="animate text-2xl font-semibold text-black dark:text-white">
{project.data.title}
</div>
</h1>
{(project.data.demoURL || project.data.repoURL) && (
<nav class="animate flex gap-1">
{project.data.demoURL && (
Expand Down
4 changes: 2 additions & 2 deletions src/pages/social.astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ const BSKY_PROFILE_URL = `https://bsky.app/profile/${BSKY_HANDLE}`
>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
<h1 class="animate font-semibold text-black dark:text-white">
Social
</div>
</h1>

<div class="animate flex items-center gap-3 text-sm text-black/60 dark:text-white/60">
<a
Expand Down