diff --git a/LICENSE b/LICENSE
index 100a0a5..9e6581c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,26 +1,25 @@
-GNU AFFERO GENERAL PUBLIC LICENSE
-Version 3, 19 November 2007
+MIT License
----
+Copyright (c) Patrick Asmus (scriptos)
-Copyright ©
-
-Name: Patrick Asmus (scriptos)
-Email: support@techniverse.net
Website: https://www.patrick-asmus.de
Blog: https://www.cleveradmin.de
+Email: support@techniverse.net
----
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index dc14603..9cb6c9c 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,10 @@
-Name des Projekts
+Kutt Links Cleanup
- Kurzbeschreibung des Projekts/Anwendung, um die es geht
+ Bash-Script zum automatischen Aufräumen anonymer Links in einer selbst gehosteten Kutt-Instanz.
@@ -22,7 +22,84 @@
-CONTENT BEREICH
+## Was macht das Script?
+
+Anonyme Links (ohne Account erstellt) werden gelöscht, wenn sie beispielhaft seit **365 Tagen nicht mehr aufgerufen** wurden. Als Referenzdatum gilt:
+
+- Der **letzte Aufruf** (letzter Eintrag in der `visits`-Tabelle)
+- Falls der Link **nie aufgerufen** wurde: das **Erstelldatum**
+
+Links von eingeloggten Usern werden **nie** angefasst.
+
+## Voraussetzungen
+
+- Kutt läuft als Docker-Setup mit einem PostgreSQL-Container namens `kutt-db`
+- Der ausführende User hat Zugriff auf `docker exec`
+
+## Installation
+
+### 1. Script ablegen
+
+```bash
+mkdir -p /home/docker-projekte/kutt/scripts
+cp kutt-links-cleanup.sh /home/docker-projekte/kutt/scripts/
+chmod +x /home/docker-projekte/kutt/scripts/kutt-links-cleanup.sh
+```
+
+### 2. Cronjob einrichten
+
+Täglich um 03:00 Uhr ausführen:
+
+```bash
+crontab -e
+```
+
+Folgende Zeile hinzufügen:
+
+```
+0 3 * * * /home/docker-projekte/kutt/scripts/kutt-links-cleanup.sh >> /home/docker-projekte/kutt/scripts/cleanup.log 2>&1
+```
+
+## Verwendung
+
+### Dry Run (nur anzeigen, nichts löschen)
+
+```bash
+./kutt-links-cleanup.sh --dry-run
+```
+
+Zeigt alle Links an, die gelöscht werden würden — ohne etwas zu verändern.
+
+### Ausführen (Links löschen)
+
+```bash
+./kutt-links-cleanup.sh
+```
+
+### Log prüfen
+
+```bash
+cat /home/docker-projekte/kutt/scripts/cleanup.log
+```
+
+## Konfiguration
+
+Die Variablen stehen am Anfang des Scripts:
+
+| Variable | Standard | Beschreibung |
+|---|---|---|
+| `CONTAINER` | `kutt-db` | Name des PostgreSQL-Containers |
+| `DB_USER` | `kutt` | Datenbank-Benutzer |
+| `DB_NAME` | `kutt` | Datenbank-Name |
+| `MAX_AGE_DAYS` | `365` | Tage ohne Zugriff bis zur Löschung |
+
+## Technische Details
+
+- Gelöschte Links sind sofort aus der Kutt-Oberfläche verschwunden
+- Zugehörige Visit-Statistiken werden automatisch mitgelöscht (`ON DELETE CASCADE`)
+- Kutt nutzt Redis nur für Rate-Limiting, nicht als Link-Cache — es gibt kein Cache-Problem nach dem Löschen
+- Die freigewordenen Short-Link-Adressen können danach wieder vergeben werden
+
diff --git a/kutt-links-cleanup.sh b/kutt-links-cleanup.sh
new file mode 100644
index 0000000..62f9ac3
--- /dev/null
+++ b/kutt-links-cleanup.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+#
+# Löscht anonyme Kutt-Links die seit %MAX_AGE_DAYS% Tagen nicht mehr aufgerufen wurden.
+# Als Referenzdatum gilt der letzte Visit oder — falls nie aufgerufen — das Erstelldatum.
+#
+# Nutzung: Per Cron z.B. täglich ausführen.
+# 0 3 * * * /home/docker-projekte/kutt/scripts/kutt-links-cleanup.sh
+#
+
+set -euo pipefail
+
+CONTAINER="kutt-db"
+DB_USER="kutt"
+DB_NAME="kutt"
+MAX_AGE_DAYS=365
+DRY_RUN="${1:-}"
+
+SQL_SELECT="
+SELECT l.id, l.address, l.target,
+ COALESCE(MAX(v.created_at), l.created_at) AS last_activity
+FROM links l
+LEFT JOIN visits v ON v.link_id = l.id
+WHERE l.user_id IS NULL
+GROUP BY l.id
+HAVING COALESCE(MAX(v.created_at), l.created_at) < NOW() - INTERVAL '${MAX_AGE_DAYS} days'
+ORDER BY last_activity;
+"
+
+SQL_DELETE="
+DELETE FROM links
+WHERE id IN (
+ SELECT l.id
+ FROM links l
+ LEFT JOIN visits v ON v.link_id = l.id
+ WHERE l.user_id IS NULL
+ GROUP BY l.id
+ HAVING COALESCE(MAX(v.created_at), l.created_at) < NOW() - INTERVAL '${MAX_AGE_DAYS} days'
+);
+"
+
+echo "$(date '+%Y-%m-%d %H:%M:%S') — Cleanup anonymer Links (älter als ${MAX_AGE_DAYS} Tage ohne Zugriff)"
+
+if [ "$DRY_RUN" = "--dry-run" ]; then
+ echo "DRY RUN — es wird nichts gelöscht."
+ docker exec "$CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "$SQL_SELECT"
+else
+ CANDIDATES=$(docker exec "$CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -t -A -c "
+ SELECT COUNT(*) FROM links l
+ LEFT JOIN visits v ON v.link_id = l.id
+ WHERE l.user_id IS NULL
+ GROUP BY l.id
+ HAVING COALESCE(MAX(v.created_at), l.created_at) < NOW() - INTERVAL '${MAX_AGE_DAYS} days';
+ " | wc -l)
+
+ if [ "$CANDIDATES" -eq 0 ]; then
+ echo "Keine abgelaufenen anonymen Links gefunden."
+ exit 0
+ fi
+
+ echo "${CANDIDATES} Link(s) werden gelöscht..."
+ docker exec "$CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -c "$SQL_DELETE"
+ echo "Fertig."
+fi