Umlaut and German-title proxy for Sonarr / Radarr / Lidarr / Readarr.
Deutsch · English
# UmlautAdaptarrEX
> **AI Disclaimer:** This project was built with the help of AI, but not "vibe coded". I have been a software developer for over 11 years and security is a high priority.
Full rewrite of the original .NET tool on **Next.js + Fastify + Prisma + SQLite**.
UmlautAdaptarrEX presents itself to the \*arrs as an indexer, sits between the \*arrs and the real indexers, and
corrects both searches and results so that releases with umlauts or German titles are reliably found, downloaded and
imported.
## Which problems does it solve?
- Releases with umlauts are often not found or imported correctly by the \*arrs (searching for `o` instead of `ö`,
missing mapping at the indexer).
- Sonarr & Radarr expect the English title from TheTVDB / TMDB. For German productions or translations this leads to
errors like `Found matching series/movie via grab history, but release was matched to series by ID`.
- Bad release naming (e.g. missing `GERMAN` tag) is optionally corrected so the \*arrs detect it properly.
## Features
| Feature | Status |
| --------------------------------------------------------------------------------------------------------------------------------------- | :----: |
|
Sonarr support | ✓ |
|
Radarr support (native, via `alternateTitles` + optional TMDB) | ✓ |
|
Lidarr support | ✓ |
|
Readarr support | ✓ |
|
Prowlarr & NZB Hydra support | ✓ |
| **Prowlarr indexer-patch dialog**: select indexers, auto-tag them & switch from `https` to `http` | ✓ |
| Newznab (Usenet) & Torznab (Torrent) support | ✓ |
| Multiple instances per \*arr type (e.g. 2× Sonarr) | ✓ |
| Detection of releases with German title & TVDB alias | ✓ |
| Correct search and detection of titles with umlauts | ✓ |
| Renaming of releases with bad naming (optional) | ✓ |
| **Web UI** (setup wizard, login, dashboard, instances, sync runs, request & rename history) | ✓ |
| **Persistent SQLite database**, no cache loss after restart | ✓ |
| **Live logs** via WebSocket | ✓ |
| **Multiple title providers** with configurable order: pcjones-API, TVDB, TMDB | ✓ |
| **Language plugins**: German umlauts (default), Swedish umlauts, French accents | ✓ |
| **i18n**: German + English | ✓ |
> **Information about Radarr:**
>
> - A TMDB / TVDB key is required for Radarr to work.
> - A TMDB / TVDB key is required for the plugins to work.
## Language Plugins
Language plugins control how titles are normalized and which spelling variants are sent to the indexer. They can be
enabled individually during the setup wizard (step "Plugins") or later under **Settings → Plugins**. Several plugins
can run at the same time, e.g. when a library contains both German and French titles.
| Plugin | Language | Default | Behavior |
| ------------------- | :------: | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **German umlauts** | `de` | ✓ | Latin variants (`ä → ae`, `ö → oe`, `ü → ue`, `ß → ss`) and no-dots variants (`ä → a`, …); strips articles `Der/Die/Das/The/An/A`. |
| **Swedish umlauts** | `sv` | ◯ | Swedish romanization: `Å → A` or `AA`, `Ä → A` or `AE`, `Ö → O` or `OE` (preserves case). |
| **French accents** | `fr` | ◯ | Removes accents (`é → e`, `à → a`, `ç → c`, …) and expands ligatures (`æ → ae`, `œ → oe`); strips articles `Le/La/Les/Un/Une/Des/Du/De`. |
Each plugin generates multiple variation maps so that releases with mixed spellings (e.g. `Brueckenkopf` vs.
`Brückenkopf` vs. `Brueckenkopf`) are still reliably detected. Audio libraries (Lidarr) additionally use a
"strip-all" path that removes the diacritic letter entirely.
## Installation
Three ways to start the image. Whichever variant you pick: after the first start, open
`http://:5007` and the setup wizard will walk you through account creation, mode, plugins, Prowlarr and proxy
configuration.
### Variant 1: Docker Compose (recommended)
The repository contains two compose files:
| File | Image source | When to use? |
| ---------------------------- | -------------------------------------------- | -------------------------------------------------------- |
| `docker-compose.yml` | Local build (`build: .`) | You cloned the repository and want to build from source. |
| `docker-compose.release.yml` | `lexfi/umlautadaptarrex:latest` (Docker Hub) | Fastest way, no repo checkout needed. |
Note: when the container runs as root (the default), the image fixes permissions on the `/data` volume
automatically at startup (default `PUID=1000`, `PGID=1000`). A manual `chown` is no longer needed. root
is used **only** for this one-time `chown`: the entrypoint fixes ownership and then switches to
`PUID:PGID` via `gosu`, so the application process (`node start.mjs`) never runs as root. If you want
files under `./data` to be owned by a different host user, set `PUID`/`PGID` as env variables (see
comments in the respective compose file).
Unprivileged operation: the image can also be started directly as a non-root user
(`docker run --user 1000:1000`, a `user:` entry in the compose file, Kubernetes `runAsUser`, or the
TrueNAS app's `run_as` field). In that case the entrypoint skips `chown`/`gosu` and runs directly under
the given UID/GID, with no need for `CHOWN`/`SETUID`/`SETGID` capabilities. Prerequisite: the `/data`
volume is already owned by that UID/GID (chown it manually or via the TrueNAS ACL).
1. Start the container. Either with the image from Docker Hub:
```sh
curl -O https://raw.githubusercontent.com/xpsony/UmlautAdaptarrEX/main/docker-compose.release.yml
docker compose -f docker-compose.release.yml up -d
```
or as a local build (requires a repo checkout):
```sh
docker compose up -d
```
2. Open the web UI: [http://localhost:5007](http://localhost:5007).
Follow logs: `docker compose -f docker-compose.release.yml logs -f umlautadaptarrex` (or without `-f
docker-compose.release.yml` for the local build). Stop: `docker compose ... down`.
Update from Docker Hub: `docker compose -f docker-compose.release.yml pull && docker compose -f
docker-compose.release.yml up -d`. Update for local build: `docker compose build --pull && docker
compose up -d`.
### Variant 2: `docker run` (without Compose)
Sufficient if you do not want to clone the repository and just want to run the prebuilt image:
```sh
docker run -d \
--name umlautadaptarrex \
--restart unless-stopped \
-p 5005:5005 \
-p 5006:5006 \
-p 5007:5007 \
-v /srv/umlautadaptarrex/data:/data \
-e TZ=Europe/Berlin \
lexfi/umlautadaptarrex:latest
```
The directory `/srv/umlautadaptarrex/data` is created automatically on first start and the entrypoint will chown it
to `PUID:PGID` (default `1000:1000`). A manual `chown` is not needed.
Optional additional `-e` flags:
- `PUID=1000` / `PGID=1000` (UID and GID under which the app process runs. Files under `./data` will be owned by
these IDs). Only takes effect when the container starts as root; with `--user`, `PUID`/`PGID` are
ignored and the app runs directly under the given UID/GID.
- `LOG_LEVEL=info` (Pino level: `trace`, `debug`, `info`, `warn`, `error`, `fatal`).
Update: `docker pull lexfi/umlautadaptarrex:latest && docker rm -f umlautadaptarrex` and re-run the command above.
The `data/` volume is preserved.
### Variant 3: Unraid Template
The Unraid template for UmlautAdaptarrEX is now officially available in the Community Applications (CA)
store: in Unraid, just open **Apps**, search for "UmlautAdaptarrEX" and install it directly, no template
URL required. The template is maintained in a separate repository:
[xpsony/UmlautAdaptarrEX-Unraid-Template](https://github.com/xpsony/UmlautAdaptarrEX-Unraid-Template).
Installation instructions, template URL, and field defaults (ports, PUID/PGID, appdata path) are
documented in the template repo's README.
### Variant 4: TrueNAS App
UmlautAdaptarrEX is available as a community app in the TrueNAS app catalog:
[apps.truenas.com/catalog/umlautadaptarrex_community](https://apps.truenas.com/catalog/umlautadaptarrex_community/).
In the TrueNAS UI, go to **Apps → Discover Apps**, search for "UmlautAdaptarrEX" and install it.
The app is maintained by [xopez](https://github.com/xopez), many thanks for that.
### Variant 5: Bare metal / without Docker
Works on any Linux or macOS host with Node `>= 24` and `pnpm 11.11.0`. The supervisor in
[`start.mjs`](start.mjs) handles migration, Fastify (port 5005 + TCP proxy 5006) and Next.js (port
5007); no reverse proxy is required.
```sh
git clone https://github.com/xpsony/UmlautAdaptarrEX.git
cd UmlautAdaptarrEX
pnpm install --frozen-lockfile
pnpm prod # build:prod -> prisma migrate deploy -> start:prod
```
The individual steps as separate scripts (e.g. for CI):
| Script | What it does |
| -------------------- | ------------------------------------------------------------------------------- |
| `pnpm build:prod` | Builds Next.js (standalone) + Fastify bundle (tsup) with `NODE_ENV=production`. |
| `pnpm prisma:deploy` | Applies migrations idempotently to the SQLite DB. |
| `pnpm start:prod` | Starts the supervisor (migrate -> Fastify -> Next.js child) with prod env. |
Persistence: the `data/` directory (SQLite) stays in the repo working tree. For "survives a reboot",
a systemd unit ships under [`deploy/umlautadaptarrex.service`](deploy/umlautadaptarrex.service),
including examples for user, `WorkingDirectory` and hardening (`ProtectSystem`, `ReadWritePaths`).
Short version of the install:
```sh
sudo cp deploy/umlautadaptarrex.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now umlautadaptarrex
journalctl -u umlautadaptarrex -f
```
### Variant 6: Proxmox VE (LXC, community script)
> **Works, but currently not accepted into the Proxmox Helper Scripts.** The script follows the
> [community-scripts](https://community-scripts.org/docs/ct/readme) (ProxmoxVED) format and is ready to use.
> Due to the project's current regulations it cannot be included in the official Proxmox Helper Scripts repo
> at the moment, so it is provided self-hosted from this fork.
A single command, run on the **Proxmox VE host shell**, creates an LXC container and installs
UmlautAdaptarrEX inside it (self-hosted from this fork, no ProxmoxVED clone required):
```bash
bash -c "$(curl -fsSL https://raw.githubusercontent.com/xpsony/UmlautAdaptarrEX/main/proxmox/community-scripts/ct/umlautadaptarrex.sh)"
```
What the script does:
- Creates a Debian 13 LXC (2 vCPU, 2048 MB RAM for the build, 6 GB disk).
- Installs Node.js 26 + pnpm (via npm), fetches the latest release of `xpsony/UmlautAdaptarrEX`
and runs `pnpm build:prod` + `pnpm prisma:deploy`.
- Prompts for the three service ports during install (pre-filled with the defaults, press Enter to accept):
- **5007** — web UI + setup wizard (`http://:5007/setup`)
- **5005** — public API + indexer routes for the \*arrs
- **5006** — Prowlarr TCP proxy (basic auth, set during setup)
- Runs the app as a systemd service (`umlautadaptarrex`). The SQLite DB lives at
`/opt/umlautadaptarrex/data/` and is preserved across updates.
After it finishes, open the setup in your browser: `http://:5007/setup`.
Changing ports later: edit `/opt/umlautadaptarrex/.env` (`UMLAUTADAPTARREX_WEBUI_PORT` /
`_LEGACYAPI_PORT` / `_PROXY_PORT`) and run `systemctl restart umlautadaptarrex`. An LXC has its own IP,
so there is no host-side port mapping. Details and maintenance notes are in
[`proxmox/community-scripts/README.md`](proxmox/community-scripts/README.md).
## Ports
| Port | Service | Purpose |
| ---- | -------------- | ------------------------------------------------------------------------------- |
| 5005 | Fastify | Public API, legacy routes (`///api`), WebSocket logs (`/ws/logs`) |
| 5006 | TCP HTTP proxy | Prowlarr indexer proxy with HTTPS CONNECT tunneling |
| 5007 | Next.js | Web UI |
Ports can be set via environment variables (precedence: branded variable > DB > default):
| Port | Environment variable | Fallback |
| ---- | --------------------------------- | ------------------------ |
| 5005 | `UMLAUTADAPTARREX_LEGACYAPI_PORT` | `5005` |
| 5006 | `UMLAUTADAPTARREX_PROXY_PORT` | `Setting.proxyPort` (DB) |
| 5007 | `UMLAUTADAPTARREX_WEBUI_PORT` | `5007` |
`UMLAUTADAPTARREX_PROXY_PORT` overrides the value stored in the database at every start; when set, the proxy-port field under Settings → Advanced is shown read-only. Each variable sets both the in-container bind port and the published host port (the compose mapping uses the same value on both sides). See `.env.example` for a template.
The `data/` DB is mounted into the container and contains the entire configuration.
## Architecture
How UmlautAdaptarrEX sits between the \*arrs, Prowlarr and the indexers.
### Mode 1: Prowlarr indexer proxy (recommended, port 5006)
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Sonarr │ │ Radarr │ │ Lidarr │ │ Readarr │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
│ Newznab/Torznab API (with *arr API key)│
└─────────────┼─────────────┼─────────────┘
▼
┌───────────┐
│ Prowlarr │ Indexer manager
└─────┬─────┘
│ HTTP (indexer scheme changed from https → http)
│ HTTP proxy: indexer proxies → "UmlautAdaptarrEX"
▼
┌──────────────────────────────────┐
│ UmlautAdaptarrEX │
│ ───────────────────────────── │
│ :5006 TCP proxy (Basic auth) │◀── HTTP CONNECT tunnel for https targets
│ :5005 Fastify API + legacy │
│ :5007 Web UI (Next.js) │
│ │
│ Pipeline per request: │
│ 1. Parse URL (t=search/...) │
│ 2. Title lookup via providers │
│ (pcjones │ TVDB │ TMDB │ │
│ db-cache, order │
│ configurable) │
│ 3. Expand query with title │
│ variants (umlauts, aliases, │
│ language plugin maps) │
│ 4. Request to real indexer │
│ 5. Rewrite XML response │
│ (title fix, rename, tags) │
│ │
│ Persistence: SQLite (Prisma) │
│ • Settings, instances, apiKey │
│ • Title cache, sync runs │
│ • Request & rename history │
└──────────────┬───────────────────┘
│ HTTPS (outbound)
▼
┌────────────┐
│ Indexer │ Newznab/Torznab,
│ (Usenet/ │ NZB Hydra, ...
│ Torrent) │
└────────────┘
```
### Mode 2: Direct as indexer (without Prowlarr proxy, port 5005)
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Sonarr │ │ Radarr │ │ Lidarr │ ...
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ Indexer URL entered as: │
│ http://:5005//
└───────────────┼───────────────┘
▼
┌──────────────────────────────────┐
│ UmlautAdaptarrEX │
│ Legacy route │
│ ///api?t=... │
│ (same pipeline as above) │
└──────────────┬───────────────────┘
│ HTTPS
▼
┌────────────┐
│ Indexer │
└────────────┘
```
Key points:
- **5006** is the only port Prowlarr talks to directly (HTTP proxy with Basic auth, default user `UmlautAdaptarr`).
- **5005** carries both the admin API and the legacy route `///api` for direct mode.
- **5007** is only the UI; it reverse-proxies `/api/*` to 5005 at runtime (`src/proxy.ts`).
## Configuration in Prowlarr (recommended)
Recommended method, because there is no speed loss with multiple indexers.
1. Start UmlautAdaptarrEX and walk through the setup in the web UI (create Sonarr/Radarr/Lidarr/Readarr instances).
2. In Prowlarr: **Settings → Indexers → Indexer Proxies → Add (HTTP)**
- Name: `UmlautAdaptarrEX HTTP Proxy`
- Host: container name (`umlautadaptarrex`) or host IP
- Port: `5006`
- Tag: `umlautadaptarrex`
- Username/Password: use the credentials set in the setup wizard (step "Proxy"). The default user is
`UmlautAdaptarr` and the password is auto-generated. Both values are visible at any time under **Settings →
Proxy** in the web UI. If UmlautAdaptarrEX creates the Prowlarr indexer proxy configuration automatically
(setup wizard, step "Prowlarr install"), the credentials are stored directly.
3. For all indexers that should use the proxy:
- Add tag `umlautadaptarrex`
- **Change the URL scheme from `https` to `http`**, only then can UmlautAdaptarrEX intercept the requests
locally. Outgoing requests to the indexer remain `https`, of course.
**The indexer-patch dialog is faster:** instead of touching each indexer in Prowlarr by hand,
UmlautAdaptarrEX lists your Prowlarr indexers and performs both steps (set the tag + `https`→`http`)
for the indexers you select. The dialog is part of the setup wizard and is available any time under
**Settings → Prowlarr → "Patch indexers"**. "Select all" is the default; de-selecting an
already-patched indexer reverts both the tag and the scheme. The dialog also explains why the switch
is needed and that the connection to the indexer on the internet stays `https` (no unencrypted
traffic leaves your system).
4. Run **Test All Indexers**. If any `https` URLs remain, a warning appears in the live logs.
## Configuration without Prowlarr proxy
> Note: not yet tested
With only a few indexers or without Prowlarr, enter the API URL per indexer directly in
Sonarr/Radarr/Lidarr/Readarr:
```
http://:5005//
```
The API key is set as usual. The `apiKey` for UmlautAdaptarrEX is created in the web UI.
The full HTTP API (admin, auth, legacy, WebSocket, TCP proxy) is documented in [docs/api.md](docs/api.md).
The release-rename pipeline is described in [docs/renaming.md](docs/renaming.md). For users who want to fork the
project and re-flag it under their own GitHub owner / Docker Hub namespace, the guide is in
[docs/forking.md](docs/forking.md), including `scripts/rebrand.sh` for the static defaults and the three runtime
levers (`DOCKERHUB_IMAGE`, `UMLAUTADAPTARREX_IMAGE`, `NEXT_PUBLIC_GITHUB_OWNER` / `NEXT_PUBLIC_GITHUB_REPO`).
## Local Development
```sh
pnpm install
cp .env.example .env
pnpm prisma:migrate
pnpm dev
# Web UI: http://localhost:5007
# Fastify API: http://localhost:5005
# Prowlarr proxy: tcp://localhost:5006
```
### Development Container (VS Code)
For a reproducible dev environment, you can use the included devcontainer:
1. Open the repository in VS Code.
2. Run `Dev Containers: Reopen in Container`.
3. After the container has started: `pnpm dev`.
The container ships Node 26 (dev and production image) + pnpm 11.11.0, forwards ports `5005/5006/5007` and sets recommended VS Code
extensions/settings for TypeScript, Next.js, Prisma, Tailwind, ESLint/Prettier, Vitest and Playwright.
After `pnpm dev` you reach the UI on port `5007` and the API on port `5005` (directly or via VS Code port
forwarding). The TCP proxy is reachable on port `5006`.
### Tests
```sh
pnpm test # vitest (unit + integration)
pnpm test:e2e # playwright (baseURL = http://localhost:5005)
pnpm typecheck
pnpm lint
```
## Project Structure
```
src/
├─ app/ # Next.js App Router (web UI)
├─ components/ # React + shadcn UI
├─ server/ # Fastify gateway, TCP proxy, sync workers, logging
├─ domain/ # Framework-free core
│ ├─ normalization/ # Title normalization
│ ├─ variations/ # Title variants
│ ├─ matching/ # Release matching
│ ├─ plugins/ # Language plugins (DE umlauts, SE umlauts, FR accents)
│ └─ xml/ # Newznab/Torznab XML rewriting
├─ providers/ # External title providers (pcjones, TVDB, TMDB, db-cache)
├─ arr/ # Sonarr/Radarr/Lidarr/Readarr/Prowlarr clients
├─ schemas/ # Zod schemas (shared client/server)
├─ messages/ # i18n (de.json, en.json)
└─ lib/ # db, auth, secrets, legacy-env, i18n, utils
```
## Stack
- Node 24+ (production image: Node 26) / TypeScript / pnpm 11
- Next.js 16 / React 19 / Tailwind 4 / shadcn (new-york)
- Fastify 5 / Prisma 7 / SQLite (better-sqlite3)
- Zod 4 / next-intl / @tanstack/react-query
- Vitest 4 / Playwright
## Contact & Support
- GitHub issues for bug reports and feature requests
- [UsenetDE Discord](https://discord.gg/src6zcH4rr) → `#umlautadaptarr`
## Credits
Based on the idea and logic of [PCJones/UmlautAdaptarr](https://github.com/PCJones/UmlautAdaptarr).
Thanks to [xopez](https://github.com/xopez) for the TrueNAS community app.
## Disclaimer
UmlautAdaptarrEX is a technical compatibility proxy. The software does not download any content itself, does not
circumvent any technical protection measures (DRM) and does not establish connections to indexers that were not
previously configured in the \*arrs or in Prowlarr.
The project is intended exclusively for use with legal sources, such as your own backup copies, regularly subscribed
Usenet or tracker services, public-domain works, and content with explicit licensing by the rights holder.
Responsibility for lawful use of the \*arrs and the connected indexers lies entirely with the respective operator.
The authors assume no liability for any unintended or unlawful use. In Germany this includes the Copyright Act
(UrhG); comparable rules exist in other countries. The software is provided "as is", without any warranty (see MIT
license).
## License
MIT