Add wiki content (12 pages) — to be published once GitHub Wiki is enabled

This commit is contained in:
Hendrik 2026-05-01 21:10:00 +02:00
parent 91bb41ed05
commit 3b209db090
13 changed files with 1049 additions and 0 deletions

182
wiki/API.md Normal file
View file

@ -0,0 +1,182 @@
# API
REST-API unter `/api/v1/*`. Versioniert und stabil. JSON-only.
## Auth
Tokens in der UI unter **Einstellungen → API-Tokens** erstellen. Token wird nur einmalig angezeigt, danach nur als sha256-Hash in der DB.
Format: `nrx_<64-hex>`. Im Header senden:
```
Authorization: Bearer nrx_<your-token>
```
## Scopes
| Scope | Zugriff |
|---|---|
| `read:domains` | Domains lesen |
| `write:domains` | Domains anlegen / löschen |
| `read:analytics` | Aggregierte Statistiken |
| `read:hits` | Roh-Hits (nur ip_hash, kein Klartext-IP) |
Fehler-Format:
```json
{ "error": "forbidden", "code": "missing_scope", "required": "read:domains" }
```
## Endpoints
### `GET /api/v1/health`
Liveness-Check. Kein Token nötig.
```bash
curl https://admin.firma.de/api/v1/health
# {"ok":true,"ts":1714500000000}
```
### `GET /api/v1/version`
Aktuelle Version + Update-Status. Kein Token nötig.
```json
{
"current": "0.1.19",
"latest": "0.1.19",
"update_available": false,
"auto_update": false
}
```
### `GET /api/v1/domains`
Scope: `read:domains`
```json
{
"domains": [
{
"id": 1, "domain": "alt-firma.de", "status": "active",
"target_url": "https://www.firma.de", "redirect_code": 302,
"preserve_path": 1, "include_www": 1,
"total_hits": 142, "last_hit": 1714499000000
}
]
}
```
### `POST /api/v1/domains`
Scope: `write:domains`
Body:
```json
{
"domain": "alt-firma.de",
"target_url": "https://www.firma.de",
"redirect_code": 302,
"preserve_path": true,
"include_www": true
}
```
Response (`201`):
```json
{
"domain": { "id": 5, "status": "pending", ... },
"dns_records": [
{ "type": "A", "name": "alt-firma.de", "value": "203.0.113.42" },
{ "type": "A", "name": "www.alt-firma.de", "value": "203.0.113.42" }
]
}
```
### `GET /api/v1/domains/:id`
Scope: `read:domains`
### `DELETE /api/v1/domains/:id`
Scope: `write:domains`
### `GET /api/v1/domains/:id/stats?days=30`
Scope: `read:analytics`
```json
{
"domain_id": 1, "days": 30, "total": 412,
"daily": [{"day":"2026-04-01","hits":12}, ...],
"by_country": [{"country":"DE","hits":380}, ...]
}
```
### `GET /api/v1/analytics/summary?days=30`
Scope: `read:analytics`
```json
{
"days": 30, "total": 12480,
"daily": [...],
"top": [{"id":1,"domain":"alt-firma.de","hits":412}, ...],
"by_country": [...]
}
```
### `GET /api/v1/hits?domain_id=1&limit=100`
Scope: `read:hits`
```json
{
"hits": [
{
"id": 9001, "domain_id": 1, "ts": 1714499000000,
"ip_hash": "9f4a...", "country": "DE",
"user_agent": "Mozilla/5.0 ...", "referer": null, "path": "/"
}
]
}
```
`ip_hash` = `sha256(ip + täglicher Salt)`. Kein Klartext-IP wird gespeichert.
## Versionierung
`/api/v1` ist stabil. Breaking-Changes bekommen `/api/v2`. Deprecation-Hinweise im `Sunset`-Header.
## Rate-Limits
Aktuell kein hartes Limit. Empfehlung: max. 60 req/min pro Token.
## Beispiele
**Uptime-Check eines Tokens:**
```bash
curl -fsS -H "Authorization: Bearer $NRX" https://admin.firma.de/api/v1/health || echo "DOWN"
```
**Liste tote Domains (0 Hits / 90d):**
```bash
curl -s -H "Authorization: Bearer $NRX" \
"https://admin.firma.de/api/v1/analytics/summary?days=90" \
| jq '.top | map(select(.hits == 0))'
```
**Domain anlegen + DNS-Records anzeigen:**
```bash
curl -X POST -H "Authorization: Bearer $NRX" -H "Content-Type: application/json" \
-d '{"domain":"shop.alt.de","target_url":"https://shop.firma.de"}' \
https://admin.firma.de/api/v1/domains | jq '.dns_records'
```
→ Weiter mit [[Updates]]

View file

@ -0,0 +1,73 @@
# Analytics & Reports
## Was wird getrackt?
Pro echtem Hit (siehe [[Bot Filter]]):
- Zeitstempel (ms)
- Domain-ID
- Land (ISO-2, falls GeoLite2 installiert)
- IP-Hash (sha256(ip + täglicher Salt) — DSGVO, kein Klartext)
- User-Agent (max 500 Zeichen)
- Referer (max 500 Zeichen)
- Pfad (max 500 Zeichen)
## Dashboard `/dashboard`
- Total / Aktiv / Wartend Domains
- Hits 24h + Eindeutige Besucher
- Top-10-Domains-Bar
- Daily-Hits-Line letzte 30 Tage
## Analytics `/analytics`
- Daily-Line letzte 30 Tage
- Top 10 Domains
- Top Länder (Pie)
- Tote Domains (aktiv aber 0 Hits / 90 Tage)
## Domain-Detail
`/domains/<id>` zeigt:
- Hits 24h / 30 Tage / Gesamt
- Eindeutige Besucher (30d, gesamt)
- Daily-Line der letzten 30 Tage **dieser Domain**
- DNS-Records-Übersicht (live, refreshbar)
- Sunset-Editor
## PDF-Report-Export
`/analytics` → "PDF Export" Button:
- 3 Vorlagen: **Minimal** / **Basic** / **Detailliert**
- Titel + Zeitraum (1365 Tage) frei wählbar
- Sektionen einzeln an/aus: Zusammenfassung, Daily-Chart, Top-Domains, Geo, Tote, Per-Domain-Detail, Letzte 200 Hits
Server-side Generation via headless Chromium (puppeteer-core). Direkt-Download als PDF. A4 Hochformat, NexRedirect-Branding, sauberes Page-Break-Verhalten.
Token-basierter Internal-Access (60s gültig, HMAC-signed) — Report-URL ist nicht öffentlich abrufbar.
## CSV-Export
- `/domains` → "CSV" lädt komplette Domain-Liste mit Hit-Counts
- `/analytics` → "Hits CSV" lädt letzte 30 Tage Hits (max 100k Zeilen)
CSV ist UTF-8 mit Komma-Trenner, RFC 4180-konform (Quotes für Sonderzeichen).
## Audit-Log `/audit`
500 letzte administrative Aktionen mit Zeit, Benutzer-Email, Aktion, Ziel, Details:
- domain.create / .update / .delete / .verify / .bulk_delete
- group.create / .update / .delete
- sunset.bulk
- (kann erweitert werden)
## Eindeutige Besucher
`COUNT(DISTINCT ip_hash)` über den Zeitraum. **Wichtig**: IP-Hash rotiert täglich (DSGVO-Anforderung). Gleicher User der heute und morgen kommt zählt als 2 verschiedene Besucher.
Innerhalb eines Tages ist die Zählung präzise. Über mehrere Tage ist die Besucher-Zahl eine **Obergrenze** (überschätzt).
→ Weiter mit [[Bot Filter]]

125
wiki/Architecture.md Normal file
View file

@ -0,0 +1,125 @@
# Architecture
## Komponenten
```
┌──────────────────────────────────────────────────────────────┐
│ Internet │
└──────────────────┬───────────────────────────────────────────┘
│ Port 80 / 443
┌──────────────────────────────────────────────────────────────┐
│ Caddy (auto-HTTPS via Let's Encrypt) │
│ │
│ /etc/caddy/Caddyfile (auto-generated) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ admin.example.de, server-ip → reverse_proxy :3000 │ │
│ │ alt-firma.de, www.alt-firma.de → reverse_proxy │ │
│ │ handle_errors { redir https://target.de/ 302 } │ │
│ │ ... │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────┬───────────────────────────────────────────┘
│ reverse_proxy localhost:3000
┌──────────────────────────────────────────────────────────────┐
│ server.ts (Custom Node Server, läuft via tsx) │
│ │
│ ① Host-Check │
│ ├─ Admin-Host (server-IP / base_domain) → Next.js │
│ ├─ Aktive Redirect-Domain → Hit loggen + 302 │
│ │ (Sunset → HTML-Notice) │
│ └─ Unbekannter Host → 404 "Domain not configured" │
│ │
│ ② Bei Admin-Host: │
│ Next.js handleRequest() — App Router │
│ ├─ middleware.ts (Edge): Auth-Check via JWT-Cookie │
│ ├─ /(app)/* — eingeloggte Routes │
│ ├─ /(auth)/login — NextAuth Credentials │
│ ├─ /(setup)/setup — First-Run-Wizard │
│ ├─ /api/v1/* — Public API (Bearer Token) │
│ ├─ /api/* — Admin-API (Session-Cookie) │
│ └─ /r/[token] — Internal Report-Page für PDF-Export │
└──────────────────┬───────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ better-sqlite3 │
│ /var/lib/corex-nexredirect/nexredirect.db │
│ Tables: users, domains, domain_groups, hits, settings, │
│ api_tokens, update_log, audit_log │
└──────────────────────────────────────────────────────────────┘
```
## Request-Flow für einen Domain-Hit
1. `GET https://alt-firma.de/foo` → DNS löst auf Server-IP
2. Caddy nimmt an, ACME hat schon Cert für `alt-firma.de`
3. Caddy `reverse_proxy localhost:3000`
4. server.ts `req.headers.host = "alt-firma.de"`
5. `isAdminHost("alt-firma.de")` → false (kein Match auf server_ip / base_domain / localhost)
6. `resolveHost("alt-firma.de")` → Resolved-Eintrag aus In-Memory-Cache (5s TTL)
7. Bot-Filter (`shouldRecord`) prüft Method, UA, Pfad, Browser-Signals, IP-Scan-Detector
8. Wenn echter Hit: `recordHit()` → Buffer (5s batch) → SQLite INSERT
9. Sunset aktiv? → HTML-Notice + `Cache-Control: no-store`
10. Sonst: 302 zum Target mit `no-store` Headers
## Request-Flow für Admin
1. `GET https://admin.example.de/dashboard` → Caddy → server.ts → isAdminHost = true → Next.js
2. `middleware.ts` checkt JWT-Session-Cookie
3. `(app)/layout.tsx` checkt `isSetupComplete()` + `getServerSession()` (force-dynamic)
4. Render Sidebar + Page
## Datenfluss bei Domain-Add
```
UI POST /api/domains
├─ Zod-Validation
├─ INSERT INTO domains (status='pending')
└─ Audit-Log
UI POST /api/domains/[id]/verify
├─ DNS-Lookup für domain + www-subdomain
├─ Vergleich mit getSetting('server_ip')
├─ Wenn Match: status='active', verified_at=now
├─ invalidateRedirectCache()
├─ Caddy: writeCaddyfile() + `caddy reload`
└─ Response { ok, caddy_reloaded }
```
## Update-Flow
```
UI Click "Update installieren"
└─ POST /api/update/apply (admin-only)
└─ exec(`sudo update.sh <tag>`)
├─ git checkout tag
├─ npm ci
├─ Prebuilt .next aus GitHub-Release-Asset (oder npm run build)
├─ ln -sf bin/nexredirect /usr/local/bin/
└─ ( sleep 2 && systemctl restart ) & disown
(Detached — script exitet, API kann response zurückgeben)
└─ UI polled /api/v1/health → window.location.reload()
```
## In-Memory-State
- **Redirect-Cache** (`lib/redirect-resolver.ts`): Map<host, ResolvedRedirect> mit 5s TTL. Invalidiert bei Domain/Group-Mutationen.
- **Hits-Buffer** (`lib/hits.ts`): Array von Pending Hits, alle 5s als Transaction in DB geschrieben.
- **Geo-Reader** (`lib/geo.ts`): MaxMind-mmdb-Reader, lazy load on first lookup.
- **IP-Scan-Tracker** (`lib/hits.ts`): Map<ip_hash, {paths: Set, firstSeen}> mit 30s Window.
Alle in-memory — bei Restart weg, kein Problem (Cache füllt sich neu).
## Sicherheit
- **NextAuth JWT** mit `NEXTAUTH_SECRET` (zufällig generiert beim Install, 30d Session)
- **bcryptjs** für Passwort-Hash (cost 12)
- **API-Tokens** als sha256-Hash in DB; Klartext nur einmalig bei Anlage angezeigt
- **HMAC-signierte Tokens** für PDF-Internal-Access (60s TTL)
- **DSGVO**: IP wird nie im Klartext gespeichert. `sha256(ip + daily_salt)`. Daily-Salt rotiert täglich → keine Re-Identifizierung über Tagesgrenzen.
- **CSRF**: NextAuth handhabt das für Auth-Routes. Admin-Mutations brauchen Session-Cookie. API-Routes brauchen Bearer-Token.
- **Bot-Filter** auch als implizite Schutzschicht gegen Scanner-Probing
- **No exposed admin paths**: alle Mutations brauchen Auth. `/api/v1/*` Bearer.
→ Weiter mit [[Troubleshooting]]

85
wiki/Bot-Filter.md Normal file
View file

@ -0,0 +1,85 @@
# Bot Filter
NexRedirect zählt nur "echte" Besucher — Scanner, Crawler und Monitoring-Tools werden serverseitig herausgefiltert. Kombination mehrerer Heuristiken in [`lib/hits.ts`](https://github.com/CoreXManagement/CoreX-NexRedirect/blob/main/lib/hits.ts):
## 1. HTTP-Method
`HEAD` und `OPTIONS` werden nie gezählt (Pre-Flight, Health-Checks).
## 2. Pfad-Pattern
Bekannte Scanner-Pfade werden ignoriert. Auszug:
```
/.env* /.git* /.DS_Store /.vscode /.svn /.hg /.idea
/wp-(admin|login|content|includes|json) /xmlrpc.php /wordpress/
/admin/ /administrator/ /phpmyadmin /pma/ /myadmin
/server-status /server-info /info.php /test.php
/login.action /console/ /manager/ /jenkins /jolokia
/actuator /telescope /horizon /debug /trace.axd /elmah.axd
/cgi-bin /about
/swagger* /api-docs* /api/swagger /v[23]/api-docs /v2/_catalog
/webjars/ /graphql* /api/gql /api/?$
/_next/ /@vite /__webpack
/ecp/ /owa/ /___proxy_subdomain
/(js|assets/js|static)/ /(css|assets/css)/
/bot-connect.* /config.json /composer.* /package.*
\.\./ %2e%2e /s/[0-9a-f]{20,}
rest_route=
/favicon.* /apple-touch-icon /robots.txt /sitemap* /ads.txt
/.well-known/ /browserconfig.xml
```
Komplett-Liste in `lib/hits.ts`.
## 3. User-Agent-Pattern
Bot-Keywords werden gematched:
```
bot, crawl, spider, slurp, curl, wget, httpclient, python-requests,
axios, node-fetch, monitor, uptime, pingdom, datadog, prometheus,
scanner, fetch, preview, whatsapp, telegrambot, facebookexternalhit,
linkedinbot, twitterbot, discordbot, skypeuripreview, mastodon,
matrix-bot, preconnect, dnsperf, sentry, newrelic, gtmetrix,
lighthouse, headlesschrome, phantomjs, puppeteer, playwright,
go-http-client, java/, okhttp, libwww, mechanize, nikto, sqlmap,
nmap, masscan, zgrab, nuclei, acunetix, netcraft, expanse, censys,
shodan, fuzz, burp, arachni, w3af, wpscan, gobuster, ffuf, dirb,
dirbuster
```
Plus:
- UA muss `Mozilla/` Prefix haben (echte Browser; Bots ohne Mozilla-Faked werden auch via UA-Keywords gefangen)
- UA muss mindestens 15 Zeichen lang sein
## 4. Browser-Signal-Heuristik
Echte Browser senden bei jeder Top-Level-Navigation:
- **`Sec-Fetch-Mode`** Header (Chrome/FF/Safari/Edge seit ~2020)
- **`Accept-Language`** Header (jeder Browser hat eine Spracheinstellung)
- **`Accept`** mit `text/html` (Browser fragen explizit nach HTML; Scanner senden `*/*`)
**Mindestens 2 von 3** müssen gesetzt sein. Plus: `Sec-Fetch-Dest` muss `document` / `empty` / `iframe` sein (filtert Image- / Script-Probes).
Das ist der wichtigste Filter — Scanner mit gefälschtem `Mozilla/5.0`-UA fallen hier durch.
## 5. Per-IP Scan-Detektor
In-Memory: gleiche IP-Hash, ≥5 verschiedene Pfade in 30s = Scanner. Alle weiteren Hits dieser IP im Zeitfenster werden verworfen.
## Ergebnis
Bei einem typischen Vanity-Redirect-Server:
- Vorher: ~80% der Hits sind Bots (vor allem nach DNS-Aktivierung kommen Scanner-Wellen)
- Nachher: <5% (vereinzelte Lighthouse / Pingdom etc.)
## False-Positives anzeigen
Bot-Hits werden gar nicht gespeichert — wenn du sie sehen willst, müsstest du temporär die Filter ausschalten. Es gibt keinen "ignored hits"-Counter.
Wenn ein Browser fälschlicherweise ausgefiltert wird (uralter Browser ohne Sec-Fetch?): bitte im GitHub-Issue melden mit User-Agent + Headers.
→ Weiter mit [[CLI]]

69
wiki/CLI.md Normal file
View file

@ -0,0 +1,69 @@
# CLI
Auf dem Server: `nexredirect <command>` (Symlink in `/usr/local/bin/`).
## Subcommands
```
status Service-Status (systemctl status)
start Service starten
stop Service stoppen
restart Service neu starten
logs [-n N] Logs streamen (default: -f live tail)
update [tag] Auf neueste Version (oder bestimmten Tag); skip wenn schon aktuell
update -f [tag] Update erzwingen auch wenn Version gleich
version Aktuelle + neueste Version (von GitHub)
caddy reload Caddyfile reload via Admin-API
caddy regen Caddyfile aus DB neu generieren + reloaden
caddy fix-perms /etc/caddy/Caddyfile dem Service-User übertragen
caddy show Aktuellen Caddyfile anzeigen
db SQLite-Shell auf der Datenbank öffnen
domains Aktive Domains listen
hits [N] Letzte N Hits (default 20)
tokens API-Tokens auflisten
backup [PATH] DB + Caddyfile sichern (default: /tmp/...)
uninstall Service + Files entfernen (DB bleibt)
help Hilfe
```
## Beispiele
```bash
# Status checken
nexredirect status
# Live-Logs
nexredirect logs
# Letzte 50 Hits
sudo nexredirect hits 50
# Update auf neueste Version
sudo nexredirect update
# Update auf bestimmten Tag (auch downgrade)
sudo nexredirect update v0.1.10
# Force-Update (gleiche Version neu installieren)
sudo nexredirect update -f
# DB anschauen
sudo nexredirect db
sqlite> SELECT id, domain, status FROM domains;
sqlite> .quit
# Caddyfile anzeigen
nexredirect caddy show
# Backup
sudo nexredirect backup
sudo nexredirect backup /backup/nexredirect-$(date +%F).tgz
```
## Service-User-Konzept
Service läuft als unprivileged user `nexredirect` (UID via `useradd --system`). Die meisten Commands brauchen `sudo` weil sie systemctl / DB-Schreibzugriff benötigen.
`sudo update.sh` ist über `/etc/sudoers.d/corex-nexredirect` als einzige Privileg-Eskalation erlaubt — vom Service-Process selbst aufrufbar wenn das UI "Update installieren" triggert.
→ Weiter mit [[API]]

43
wiki/DNS-Setup.md Normal file
View file

@ -0,0 +1,43 @@
# DNS Setup
Damit eine Domain auf NexRedirect zeigt, müssen DNS-Records auf die Server-IP zeigen.
## Records
Beim Hinzufügen einer Domain im UI bekommst du genau angezeigt was einzutragen ist. Standard:
| Type | Name | Value |
|---|---|---|
| A | @ | `<server-ipv4>` |
| AAAA | @ | `<server-ipv6>` _(optional)_ |
| A | www | `<server-ipv4>` |
`@` steht für die Root-Domain. Manche DNS-Provider erwarten stattdessen den vollen Domain-Namen oder ein leeres Feld — egal, alle drei Schreibweisen meinen dasselbe.
## Validierung
Nach dem Eintragen klick im UI auf **Prüfen**. Server fragt selbst per `dns.resolve()` ab und vergleicht mit der eigenen IP. Bei Match → Domain wird auf `active` gestellt → Caddy reload → Auto-HTTPS-Cert via Let's Encrypt.
DNS-Propagation kann ein paar Minuten dauern. Nochmal prüfen → meistens da.
## DNS-Records-Übersicht
In der Domain-Detail-Page gibt's eine Card "DNS-Records" — listet **alle** Records die für diese Domain veröffentlicht sind: A, AAAA, CNAME, MX, NS, TXT, SOA, CAA. A/AAAA-Werte die auf den Server zeigen werden grün markiert.
Refresh-Button löst neue Abfrage aus.
## Auto-HTTPS
Sobald eine Domain `active` ist, holt Caddy automatisch ein Let's-Encrypt-Cert. Erste HTTPS-Anfrage kann ~10s dauern, danach gecacht.
Voraussetzung: Server muss aus dem Internet auf Port 80 erreichbar sein (für ACME-Challenge).
## Häufige Probleme
**DNS validiert nicht**: A-Record zeigt auf falsche IP, oder TTL noch nicht abgelaufen. `dig +short example.de` und `dig +short example.de @8.8.8.8` vergleichen.
**HTTPS funktioniert nicht trotz active**: Caddy braucht eingehend Port 80 frei. Firewall prüfen. Logs: `nexredirect logs | grep -i caddy` oder `journalctl -u caddy -n 50`.
**Cert-Renewal-Errors**: Let's Encrypt Rate-Limit (50/Woche pro Domain). Bei vielen Subdomains: Wildcard-Cert nötig — wird aktuell nicht unterstützt, jede Subdomain braucht eigenen Cert.
→ Weiter mit [[Domain Management]]

52
wiki/Domain-Management.md Normal file
View file

@ -0,0 +1,52 @@
# Domain Management
## Domain hinzufügen
`/domains/new` öffnen. 3 Schritte:
1. **Domain & Ziel**: Domain-Name + Ziel-URL (oder Gruppe), Status-Code (302 empfohlen), Pfad-Preserve, www-Subdomain
2. **DNS-Records anzeigen**: A-Records eintragen wie angezeigt
3. **Validieren**: Klick → Server prüft → bei Erfolg ist die Domain sofort aktiv
## Status-Code 301 vs 302
**302 (Default)**: Browser cached den Redirect nicht. Jeder Aufruf hittet den Server, Analytics zählt sauber. Dafür minimaler SEO-Verlust.
**301**: Browser cached permanent. Folge-Aufrufe gehen direkt zum Ziel ohne deinen Server zu sehen — Hit-Counter bleibt bei 1, egal wie oft. NexRedirect setzt zwar `Cache-Control: no-store`, viele Browser ignorieren das aber bei 301.
**Empfehlung**: 302 außer bei expliziter SEO-Strategie.
## Pfad-Preserve
`alt-domain.de/foo/bar` → wenn aktiv: `https://ziel.de/foo/bar`. Wenn aus: alle Pfade landen auf `https://ziel.de/`.
Bei Domains die nur als Vanity-Redirect dienen → Aus. Bei echten Migrationen → An.
## www-Subdomain
Aktiviert hinzufügen: `www.example.de` zeigt aufs gleiche Ziel. Caddy holt dafür ein zweites Cert.
## Gruppen
Mehrere Domains zum gleichen Ziel = Gruppe.
`/groups` → "Neue Gruppe" → Name + Ziel-URL. Beim Domain-Anlegen unter "Ziel" → "Gruppe" wählen statt Einzel-URL. Vorteil: Ziel-URL einmal ändern → alle Domains in der Gruppe folgen.
Edit-Funktion über Bleistift-Icon. Löschen blockiert solange Domains die Gruppe nutzen.
## Bulk-Aktionen
Auf `/domains` Checkbox-Spalte links. Auswahl löst Selection-Bar oben aus:
- **Löschen** mehrere Domains (Bestätigung zeigt Hit-Count)
- **Sunset-Hinweis** für N Domains gleichzeitig konfigurieren (siehe [[Sunset Pages]])
## Domain bearbeiten
`/domains/<id>` → "Konfiguration"-Card hat ein inline Edit-Form: Ziel, Code, Gruppe, Pfad-Preserve, www. Speichern triggert automatisch Caddy-Reload.
## Domain löschen
Detail-Page → "Aktionen" → "Domain löschen". Confirm-Dialog warnt wie viele Hits mitgelöscht werden. Auch via [[CLI]]: `nexredirect db` und SQL.
→ Weiter mit [[Sunset Pages]]

43
wiki/Home.md Normal file
View file

@ -0,0 +1,43 @@
# CoreX NexRedirect
Self-hosted Domain-Redirect-Server mit Web-Admin-UI, Per-Domain-Analytics und One-Line Install.
## Was macht NexRedirect?
Du hast viele Domains, die nur auf eine andere Webseite weiterleiten sollen — und willst wissen, welche davon überhaupt noch genutzt werden? NexRedirect:
- nimmt alle deine Redirect-Domains entgegen (DNS auf den Server)
- leitet jeden Aufruf konfigurierbar zum Ziel weiter (302/301)
- protokolliert jeden echten Besuch (Bots gefiltert) mit Land, User-Agent, Referer
- zeigt dir im Web-UI welche Domains tatsächlich genutzt werden — und welche tot sind
## Schnellstart
```bash
curl -sSL https://raw.githubusercontent.com/CoreXManagement/CoreX-NexRedirect/main/scripts/install.sh | sudo bash
```
Setup-Wizard danach: `http://<server-ip>/setup`
→ Detail: [[Installation]]
## Themen
- [[Installation]] — Server aufsetzen
- [[DNS Setup]] — Domains korrekt verbinden
- [[Domain Management]] — Domains, Gruppen, Sunset-Pages
- [[Analytics & Reports]] — Hit-Tracking, PDF-Export, CSV-Export
- [[Bot Filter]] — Wie echte Besucher von Scannern unterschieden werden
- [[CLI]] — Server-CLI `nexredirect`
- [[API]] — REST-API mit Token-Auth
- [[Updates]] — Self-Update und Versions-Strategie
- [[Architecture]] — Wie alles zusammenhängt
- [[Troubleshooting]] — Häufige Probleme und Lösungen
## Stack
Next.js 15 + TypeScript + TailwindCSS + better-sqlite3 (eine Datei) + Caddy (Auto-HTTPS) + MaxMind GeoLite2 + Recharts. Läuft auf Debian/Ubuntu via systemd. Eine `.db`, eine `Caddyfile`, ein Node-Prozess.
## Lizenz
[MIT](https://github.com/CoreXManagement/CoreX-NexRedirect/blob/main/LICENSE) — viel Spaß damit.

95
wiki/Installation.md Normal file
View file

@ -0,0 +1,95 @@
# Installation
## Voraussetzungen
- Debian 11+ oder Ubuntu 20.04+ Server
- Root-Zugriff (sudo)
- Server-IP öffentlich erreichbar (Port 80 + 443 offen)
- Mindestens 1 GB RAM, 5 GB Disk
## One-Line Install
```bash
curl -sSL https://raw.githubusercontent.com/CoreXManagement/CoreX-NexRedirect/main/scripts/install.sh | sudo bash
```
Das Script:
1. Detect Debian/Ubuntu
2. `apt install`: curl, ca-certificates, gnupg, git, sqlite3, chromium, sudo
3. Caddy via offiziellem Repo
4. Node.js 20 via NodeSource
5. Holt neuestes Release (Tag), zieht Prebuilt `.next`-Tarball aus GitHub-Release
6. Legt Service-User `nexredirect` an, klont Repo nach `/opt/corex-nexredirect`
7. systemd Unit + sudoers für `update.sh`
8. Caddy Bootstrap-Config + reload
9. CLI-Symlink `/usr/local/bin/nexredirect`
10. Service start
Nach `Fertig!`-Meldung: Setup-Wizard unter `http://<server-ip>/setup` aufrufen, Admin-Account anlegen.
## Optional: GeoIP
Land pro Hit nur wenn MaxMind-DB installiert. Entweder beim Install:
```bash
sudo MAXMIND_LICENSE_KEY=xxx ... | sudo -E bash
```
…oder später im UI unter Einstellungen → GeoIP-Tracking → Account-ID + License-Key eintragen → Installieren.
[Lizenz-Key kostenlos hier](https://www.maxmind.com/en/geolite2/signup) generieren.
## Manueller Install
Wer das Curl-Pipe-Bash nicht mag:
```bash
sudo apt install -y caddy nodejs git sqlite3 chromium
sudo useradd --system --home /opt/corex-nexredirect --shell /usr/sbin/nologin nexredirect
sudo mkdir -p /opt/corex-nexredirect /var/lib/corex-nexredirect
sudo git clone https://github.com/CoreXManagement/CoreX-NexRedirect /opt/corex-nexredirect
sudo chown -R nexredirect:nexredirect /opt/corex-nexredirect /var/lib/corex-nexredirect
sudo -u nexredirect bash -c "cd /opt/corex-nexredirect && npm ci && npm run build"
sudo cp /opt/corex-nexredirect/systemd/corex-nexredirect.service /etc/systemd/system/
sudo chown nexredirect:nexredirect /etc/caddy/Caddyfile
sudo systemctl daemon-reload
sudo systemctl enable --now caddy corex-nexredirect
```
## Verzeichnisstruktur
| Pfad | Zweck |
|---|---|
| `/opt/corex-nexredirect` | Code (git checkout) |
| `/var/lib/corex-nexredirect/nexredirect.db` | SQLite (alle Daten) |
| `/var/lib/corex-nexredirect/GeoLite2-Country.mmdb` | Geo-DB (optional) |
| `/etc/caddy/Caddyfile` | Caddy-Config (auto-generated) |
| `/etc/systemd/system/corex-nexredirect.service` | systemd-Unit |
| `/etc/sudoers.d/corex-nexredirect` | Sudo-Privileg für `update.sh` |
| `/usr/local/bin/nexredirect` | CLI-Symlink |
## Backup
```bash
nexredirect backup [/path/zu.tar.gz]
```
Sichert `nexredirect.db` (+ WAL/SHM) und `Caddyfile`. Restore:
```bash
sudo systemctl stop corex-nexredirect caddy
sudo tar -xzf nexredirect-backup-XXX.tar.gz -C /
sudo chown nexredirect:nexredirect /var/lib/corex-nexredirect/*.db
sudo systemctl start caddy corex-nexredirect
```
## Deinstallation
```bash
sudo nexredirect uninstall
```
Entfernt Service, Files, Sudoers, CLI. **DB unter `/var/lib/corex-nexredirect/` bleibt erhalten** — separat löschen wenn gewollt.
→ Weiter mit [[DNS Setup]]

48
wiki/Sunset-Pages.md Normal file
View file

@ -0,0 +1,48 @@
# Sunset Pages
Statt sofortigem Redirect kann eine **Hinweisseite** kommen — schlichte weiße Seite mit Custom-Text und einem "Weiter"-Button. Klickt der User → echter Redirect.
## Wann brauchst du das?
- **Domain wird abgeschaltet**: "Diese Domain wird zum 31.12.2026 abgeschaltet. Bitte verwende ab sofort https://neue-domain.de"
- **Marken-Migration**: Hinweis dass die Firma umfirmiert hat
- **Information** über kommenden Besitzerwechsel
## Konfiguration per Domain
Domain-Detail-Page → Card "Abschaltungs-Hinweis" → Toggle aktivieren:
| Feld | Beispiel |
|---|---|
| Titel | Diese Domain wird abgeschaltet |
| Nachricht | Die Domain alt-firma.de wird zum 31.12.2026 abgeschaltet. Bitte verwenden Sie ab sofort https://neue-firma.de |
| Button-Text | Weiter |
| Abschaltdatum | 31.12.2026 _(optional, nur Anzeige)_ |
Speichern. Beim nächsten Aufruf der Domain bekommt der User die Notice statt Redirect. Klick auf "Weiter" → `?nr_continue=1` → echter Redirect.
## Bulk-Konfiguration
Auf `/domains` mehrere Domains markieren → Selection-Bar → "Sunset-Hinweis" → Form ausfüllen → "Auf N anwenden". Setzt für alle markierten Domains die gleiche Konfiguration.
Zum Deaktivieren für mehrere: Checkbox "Aktivieren" im Bulk-Dialog ausschalten und anwenden.
## Implementierung
Custom Server (`server.ts`) prüft beim Resolve eines Hosts:
1. Wenn Domain Sunset hat UND Request **kein** `?nr_continue=1` → HTML-Notice mit `Cache-Control: no-store`
2. Wenn `?nr_continue=1` → ganz normaler 302/301 zum Target
Hit wird beim ERSTEN Request gezählt (Notice-Render). Continue-Click zählt NICHT als zweiter Hit (würde sonst doppeln).
HTML-Page ist in [`lib/sunset-html.ts`](https://github.com/CoreXManagement/CoreX-NexRedirect/blob/main/lib/sunset-html.ts) — schwarz auf weiß, kein JS, kein externes Asset, kein Tracking. Funktioniert auch ohne JS-Browser.
## Wichtige Hinweise
- Sunset-Page wird **nicht** vom Browser gecacht (`Cache-Control: no-store`)
- Sunset-Page ist `noindex,nofollow` für Suchmaschinen
- Robots.txt etc. werden NICHT durch die Notice geleitet, gehen direkt zum Target
- Bei aktivem Sunset wird der Caddy-Fallback (bei App-Down: Direkt-Redirect via `handle_errors`) trotzdem ausgelöst — Notice nur wenn App läuft
→ Weiter mit [[Analytics & Reports]]

136
wiki/Troubleshooting.md Normal file
View file

@ -0,0 +1,136 @@
# Troubleshooting
## Service läuft nicht
```bash
nexredirect status
nexredirect logs -n 100
```
Häufigste Ursachen:
- **Port 3000 belegt**: anderer Service auf 3000? `ss -tlnp | grep 3000`
- **better-sqlite3 native module fehlt**: nach Update `sudo nexredirect update -f` zwingt npm ci
- **DB-Dateirechte**: `ls -la /var/lib/corex-nexredirect/`. Sollte `nexredirect:nexredirect` gehören
- **NEXTAUTH_SECRET nicht gesetzt**: `systemctl cat corex-nexredirect.service` prüfen
## /domains gibt "Application error"
Spalte fehlt (bei Update von alter Version). Logs prüfen:
```bash
nexredirect logs -n 50 | grep SqliteError
```
Falls `no such column`: Migration nicht durchgelaufen. v0.1.15+ hat Self-Healing (`PRAGMA table_info` Check). Update auf neueste Version + Restart:
```bash
sudo nexredirect update
```
## Domain-Add: DNS-Verify schlägt fehl
```bash
dig +short example.de @8.8.8.8
dig +short example.de @1.1.1.1
```
Output muss die Server-IP sein. Falls nicht:
- DNS noch nicht propagiert (TTL warten)
- A-Record falsch eingetragen (manche Provider erwarten leeres Name-Feld statt `@`)
- DNSSEC-Problem beim Registrar
Server-IP korrekt? `nexredirect db` und `SELECT * FROM settings WHERE key LIKE 'server_%';`
## HTTPS funktioniert nicht trotz active
Caddy braucht eingehend Port 80 für ACME-Challenge:
```bash
sudo nexredirect logs caddy 2>&1 | grep -i acme
journalctl -u caddy -n 50 | grep -i error
```
Häufigste Ursachen:
- Firewall blockt Port 80 (UFW / iptables)
- Cloudflare-Proxy davor → Caddy bekommt CF-IP, ACME schlägt fehl. Cloudflare auf "DNS-only" stellen
- Let's Encrypt Rate-Limit (50 Certs/Woche pro Domain)
## Caddyfile kann nicht geschrieben werden
Bei manueller Migration vor v0.1.9:
```bash
sudo nexredirect caddy fix-perms
sudo nexredirect caddy regen
```
Setzt Owner auf Service-User und regeneriert + reloaded.
## Update läuft, UI zeigt aber alte Version
Browser-Cache. Hard-Reload (Ctrl+Shift+R) oder Inkognito. v0.1.5+ hat Auto-Reload nach Update integriert.
## Hit-Counter steigt nicht
Wenn Domain `redirect_code = 301`:
Browser cached den Redirect, alle weiteren Aufrufe gehen direkt zum Ziel. NexRedirect setzt `Cache-Control: no-store`, viele Browser ignorieren das aber bei 301.
**Fix**: Auf 302 ändern. v0.1.7+ migriert beim ersten Boot automatisch.
## Hits werden gefiltert obwohl echte User
Modernere Filter (v0.1.19+) prüft Browser-Signal-Header. Sehr alte Browser ohne `Sec-Fetch-Mode` UND ohne `Accept-Language` werden gefiltert.
Im Hit-Log nachschauen: `nexredirect db` und `SELECT * FROM hits ORDER BY ts DESC LIMIT 50;`. Wenn der Browser geloggt wird, ist alles OK. Sonst Bot-Filter zu aggressiv.
## Update.sh: "dubious ownership"
Wenn `update.sh` als root aufgerufen wird aber Repo `nexredirect` gehört. Fix:
```bash
sudo nexredirect update
```
(CLI ruft update.sh über sudo auf und führt git als Service-User aus.) Bei Manuell:
```bash
sudo -u nexredirect git -C /opt/corex-nexredirect pull --ff-only
sudo /opt/corex-nexredirect/scripts/update.sh
```
## PDF-Download: "chrome_not_found"
Chromium fehlt. Beim Update wird's installiert; ältere Installs:
```bash
sudo apt install -y chromium
```
Pfad in der App ist `/usr/bin/chromium` oder `/usr/bin/chromium-browser`. Override via `NEXREDIRECT_CHROME_PATH` in der systemd-Unit-File.
## Logs zu Caddy-Reload-Fehlern
```bash
sudo nexredirect caddy show > /tmp/cur.caddyfile
caddy validate --config /tmp/cur.caddyfile
```
Validate-Output zeigt Syntax-Fehler. Bei Bedarf manuell editieren / Settings im UI korrigieren → `sudo nexredirect caddy regen`.
## Backup wiederherstellen
```bash
sudo systemctl stop corex-nexredirect caddy
sudo tar -xzf nexredirect-backup-XXX.tar.gz -C /
sudo chown nexredirect:nexredirect /var/lib/corex-nexredirect/*.db*
sudo systemctl start caddy corex-nexredirect
```
## Fragen / Bugs
GitHub Issues: https://github.com/CoreXManagement/CoreX-NexRedirect/issues
→ Zurück zu [[Home]]

78
wiki/Updates.md Normal file
View file

@ -0,0 +1,78 @@
# Updates
NexRedirect prüft alle 60 Minuten gegen die GitHub-Releases-API auf neue Versionen. **Keine Auto-Updates** außer aktiviert.
## Update-Verhalten
| Setting | Verhalten |
|---|---|
| Default | Stündlicher Check, Banner in der UI bei neuem Release. Nichts wird ohne Klick installiert. |
| `update_auto = true` | Bei jedem Check wird verfügbares Update sofort installiert. |
| `update_include_prereleases = true` | Auch Pre-Releases als Update angezeigt. |
## Manuell aktualisieren
**UI:**
1. Banner oben oder Settings → "Update v0.1.X installieren" klicken
2. Bestätigen
3. UI zeigt Spinner, polled `/api/v1/health`, lädt Seite neu sobald Server zurück
**CLI:**
```bash
sudo nexredirect update # auf neueste, skip wenn schon aktuell
sudo nexredirect update v0.1.10 # auf bestimmten Tag (auch downgrade)
sudo nexredirect update -f # erzwingen auch wenn gleiche Version
```
## Update-Mechanik
1. `update.sh` prüft latest Release-Tag (oder nimmt übergebenen Tag)
2. Skip wenn current === latest und kein `-f`
3. `git fetch --tags && git checkout <tag>` als Service-User
4. `npm ci`
5. **Prebuilt `.next`-Tarball** aus Release-Asset ziehen — spart ~25s gegenüber lokal bauen
6. Falls kein Asset (oder Download fehlschlägt): `npm run build` als Fallback
7. CLI-Symlink + Permissions aktualisieren
8. **Detached restart** in 2s (Hauptscript exitet zuerst sauber → API kann response zurückgeben → DANN restart)
## Auto-Update aktivieren
Settings → Toggle "Auto-Update aktivieren". Ab dann wird bei jedem stündlichen Check ein verfügbares Update direkt installiert.
**Empfehlung**: Nur in Test-Umgebungen. In Prod manuell prüfen, Release-Notes lesen.
## Rollback
```bash
sudo nexredirect update v0.1.10 # auf Vorgänger-Version
```
`update.sh` läuft `git checkout <tag>` egal ob vorwärts oder rückwärts. Schema-Migrationen sind additiv (`ALTER TABLE ADD COLUMN`) — Downgrade fragmenten ungenutzte Spalten ignorieren.
Falls Schema-Inkompatibilität: vorher Backup, ggf. DB händisch downgraden.
## Schema-Migrationen
Beim Start:
1. `ensureSchema(db)` legt fehlende Tabellen idempotent an (`CREATE TABLE IF NOT EXISTS`)
2. `runMigrations(db)` läuft definierte Schritte (Settings-Flag-basiert + Schema-Check)
Aktuelle Migrationen:
- `m_301_to_302`: alle existierenden 301-Codes auf 302 ändern (Browser-Cache-Fix)
- `sunset_config`-Spalte: Self-healing — prüft via `PRAGMA table_info` ob Spalte existiert, fügt hinzu wenn nicht
## Update-Log
Jeder Update-Versuch wird in der `update_log`-Tabelle protokolliert:
```sql
SELECT ts, from_version, to_version, status FROM update_log ORDER BY ts DESC LIMIT 10;
```
Status: `success` oder `failed` (Log-Auszug in der `log`-Spalte).
→ Weiter mit [[Architecture]]

20
wiki/_Sidebar.md Normal file
View file

@ -0,0 +1,20 @@
**CoreX NexRedirect**
- [[Home]]
- [[Installation]]
- [[DNS Setup]]
- [[Domain Management]]
- [[Sunset Pages]]
- [[Analytics & Reports]]
- [[Bot Filter]]
- [[CLI]]
- [[API]]
- [[Updates]]
- [[Architecture]]
- [[Troubleshooting]]
---
[Repo](https://github.com/CoreXManagement/CoreX-NexRedirect)
[Releases](https://github.com/CoreXManagement/CoreX-NexRedirect/releases)
[Issues](https://github.com/CoreXManagement/CoreX-NexRedirect/issues)