Unifi-Voucher-Tool/Readme.md
Friederich Loheide 5f7c503dff
Some checks are pending
CI / PHP Lint (push) Waiting to run
CI / PHP Lint-1 (push) Waiting to run
CI / Unit Tests & Static Analysis (push) Waiting to run
CI / PHP Lint (pull_request) Waiting to run
CI / PHP Lint-1 (pull_request) Waiting to run
CI / Unit Tests & Static Analysis (pull_request) Waiting to run
README: Projekt liegt auf Forgejo, nicht mehr auf GitHub
- CI-Badge von github.com entfernt (zeigte ins Leere); stattdessen ein
  statisches Test-Badge und ein Link zum Repository
- Clone-Befehl in der Installation auf git.loheide.cloud umgestellt
- neuer Abschnitt "Repository & Mitwirken" mit HTTPS-/SSH-Adressen,
  Hinweis auf Issues und Pull Requests sowie der tea-CLI
- vermerkt, dass .github/workflows von Forgejo Actions mitgelesen wird und
  docker-publish.yml (ghcr.io) noch aus der GitHub-Zeit stammt
- klargestellt, dass der Auto-Updater unabhängig davon über
  update.loheide.eu läuft
- Entwicklungs-Abschnitt nennt jetzt auch die CI-Prüfungen der Sprachdateien
- composer.json: homepage, authors und support-Links ergänzt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 13:58:16 +00:00

591 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div align="center">
# 🎫 UniFi Voucher Management System
**Webbasiertes WLAN-Voucher-Management für UniFi OS** mit Multi-Site-Support, Benutzerverwaltung, Microsoft-365-Login und integriertem Auto-Updater.
Entwickelt von **[Loheide.eu](https://loheide.eu)**
![PHP](https://img.shields.io/badge/PHP-7.4%2B-777BB4?logo=php&logoColor=white)
![MySQL](https://img.shields.io/badge/MySQL-5.7%2B%20%2F%20MariaDB-4479A1?logo=mysql&logoColor=white)
![UniFi OS](https://img.shields.io/badge/UniFi%20OS-7.0%2B-0559C9?logo=ubiquiti&logoColor=white)
![License](https://img.shields.io/badge/Lizenz-MIT-green)
![Version](https://img.shields.io/badge/Version-2.6.0-blueviolet)
![Tests](https://img.shields.io/badge/Tests-PHPUnit%20%2B%20PHPStan-brightgreen)
[![Repository](https://img.shields.io/badge/Code-git.loheide.cloud-4b3ec4)](https://git.loheide.cloud/friloo/Unifi-Voucher-Tool)
</div>
---
<div align="center">
<img src="docs/screenshots/voucher-result.png" alt="Erstellter Voucher mit QR-Code" width="48%">
<img src="docs/screenshots/admin-dashboard.png" alt="Admin Dashboard" width="48%">
</div>
---
## ✨ Features
- 🎟️ **Voucher-Erstellung** mit sofortiger QR-Code-Anzeige, Druckvorlage und E-Mail-Versand
- 📦 **Bulk-Erstellung** bis zu 20 Vouchers auf einmal, inkl. Sammeldruck-Layout
- 🧩 **Voucher-Profile/Templates** vordefinierte Laufzeiten & Gerätelimits per Schnellauswahl
- 🏢 **Multi-Site-Support** beliebig viele UniFi-Standorte zentral verwalten
- 👥 **Benutzerverwaltung** mit granularer Site-Zugriffskontrolle
- 🔐 **Authentifizierung** via lokale Accounts **oder** Microsoft 365 OAuth
- 🔒 **2FA (TOTP)** optional, mit Recovery-Codes & erzwingbarer Admin-Pflicht
- 📈 **Reporting** Auswertungen mit Charts und CSV-/PDF-Export
- 🩺 **Health-Endpoint** (`/health.php`) für Monitoring/Uptime
- 🔑 **Passwort-Reset** per E-Mail (token-basiert, zeitlich begrenzt)
- 🚦 **Bandbreiten- & Datenlimits** pro Voucher/Profil (UniFi QoS)
- 🧰 **REST-API mit API-Schlüsseln** Scopes (read/write), Rate-Limit, OpenAPI-Spec
- 🪪 **Single Sign-On** via generisches OpenID Connect (zusätzlich zu M365)
- 📲 **SMS-Versand** der Codes (Twilio) · **CSV-Batch-Import** von Vouchern
- 🤖 **CAPTCHA** (Rechenaufgabe oder hCaptcha) im öffentlichen Modus
- 🗄️ **DB-gestützte Sessions** (opt-in) mit „überall abmelden"
- 🔔 **Webhooks** (Slack / Teams / generisch) für Erstellung, Ausfälle, neue Login-IP
- 🧹 **Auto-Cleanup & DSGVO** Aufbewahrungsfristen per Cron
- 💾 **Config-Backup & -Restore** (JSON Export/Import)
- 🐳 **Docker** Dockerfile + docker-compose (MariaDB)
- 🌍 **Öffentlicher Modus** optional ohne Login nutzbar (mit CSRF-Schutz & Throttle)
- 🎨 **Einheitliches Design-System** ein Stylesheet für Frontend, Login und Backend (Tokens, Komponenten, Light/Dark)
- 🏷️ **Login-Seite individualisierbar** Firmenname, Logo, Texte, Hintergrundbild bzw. Farbverlauf
- 🖌️ **Eigene Markenfarben** Akzentfarbe, Verlauf und Eckenradius wirken auf die gesamte Oberfläche
- ⬆️ **Bild-Upload** für Logo, Favicon und Login-Hintergrund (kein externes Hosting nötig)
- 🔒 **Keine externen CDNs** Schrift, Icons, Diagramme und Editor werden lokal ausgeliefert (DSGVO, Offline-Netze)
-**Barrierearm** Kontraste nach WCAG AA, Sprungmarke, aria-Beschriftungen, `prefers-reduced-motion`
- 📱 **Mobil nutzbar** Tabellen werden auf schmalen Geräten zu Karten
- 🌗 **Dark Mode** umschaltbar, Einstellung wird im Browser gespeichert
- 🌐 **Mehrsprachig** Deutsch / Englisch per Umschalter (`lang/`)
- 📱 **Responsive Admin-Layout** mit Hamburger-Menü & Sidebar-Overlay
- 📊 **Admin-Dashboard** mit Live-Statistiken und Sync-Funktion
- 📝 **Audit-Log** nachvollziehbare Protokollierung von Login & Änderungen (mit Filter)
- 📥 **CSV-Export** aller Vouchers pro Site
- 🔄 **Integrierter Auto-Updater** Updates & DB-Migrationen per Klick aus dem Admin-Bereich
- 🛡️ **Security-by-default**: CSRF-Schutz, bcrypt-Passwörter, Prepared Statements,
Login-Rate-Limiting, OAuth-State-Validierung, Verschlüsselung sensibler Daten
---
## 📸 Screenshots
> Alle Screenshots stammen aus der Oberfläche in Version 2.5.0 (neues Design-System).
### Anmeldung & Voucher-Erstellung
<div align="center">
<img src="docs/screenshots/login.png" alt="Anmeldung" width="48%">
<img src="docs/screenshots/login-branding.png" alt="Anmeldung mit eigenem Branding" width="48%">
</div>
<div align="center">
<img src="docs/screenshots/voucher-form.png" alt="Voucher erstellen" width="48%">
<img src="docs/screenshots/settings-login.png" alt="Einstellungen der Login-Seite" width="48%">
</div>
<div align="center">
<img src="docs/screenshots/voucher-result.png" alt="Voucher-Ergebnis mit QR-Code" width="48%">
<img src="docs/screenshots/bulk-vouchers.png" alt="Bulk-Voucher-Erstellung" width="48%">
</div>
### Administration
<div align="center">
<img src="docs/screenshots/admin-dashboard.png" alt="Dashboard" width="48%">
<img src="docs/screenshots/admin-dashboard-dark.png" alt="Dashboard im Dark Mode" width="48%">
</div>
<div align="center">
<img src="docs/screenshots/vouchers.png" alt="Live-Voucher-Verwaltung" width="48%">
<img src="docs/screenshots/settings.png" alt="Einstellungen" width="48%">
</div>
<div align="center">
<img src="docs/screenshots/settings-branding.png" alt="Markenfarben einstellen" width="48%">
<img src="docs/screenshots/mobile-vouchers.png" alt="Ansicht auf dem Smartphone" width="22%">
</div>
### REST-API, 2FA & Integrationen
<div align="center">
<img src="docs/screenshots/api-keys.png" alt="API-Schlüssel-Verwaltung" width="48%">
<img src="docs/screenshots/integrations.png" alt="Integration & Wartung" width="48%">
</div>
<div align="center">
<img src="docs/screenshots/two-factor.png" alt="Zwei-Faktor-Authentifizierung" width="60%">
</div>
### Updater & Wartungsmodus
<div align="center">
<img src="docs/screenshots/updater.png" alt="Updater Ausgangszustand" width="48%">
<img src="docs/screenshots/updater-available.png" alt="Auto-Updater mit verfügbarem Update" width="48%">
</div>
<div align="center">
<img src="docs/screenshots/maintenance.png" alt="Wartungsmodus" width="60%">
</div>
---
## 📋 Anforderungen
| Komponente | Version |
|---|---|
| PHP | 7.4+ (mit PDO, PDO_MySQL, cURL, mbstring, JSON; empfohlen: OpenSSL/libsodium, Zip) |
| Datenbank | MySQL 5.7+ / MariaDB 10.2+ |
| Webserver | Apache / Nginx |
| UniFi | **Network Application 7.0+ mit UniFi OS** (z. B. UDM, UDR, UniFi OS Server) |
---
## 🚀 Installation
```bash
git clone https://git.loheide.cloud/friloo/Unifi-Voucher-Tool.git
cd Unifi-Voucher-Tool
```
1. Dateien auf den Webserver hochladen
2. `https://ihre-domain.de/install.php` öffnen
3. Den **5-Schritte-Assistenten** durchlaufen:
1. Datenbank-Verbindungsdaten
2. Administrator-Account
3. Allgemeine Einstellungen (Titel, Logo, öffentlicher Zugriff)
4. Microsoft 365 Integration (optional)
5. Installation abschließen
4. `install.php` nach erfolgreicher Installation **löschen**
> 🔒 Der Installer erzeugt automatisch einen zufälligen `APP_KEY` (für die
> Verschlüsselung der UniFi-Passwörter) und eine `.htaccess`, die sensible
> Dateien sperrt. Ein erneuter Aufruf von `install.php?reinstall=1` ist nur
> für **angemeldete Administratoren** möglich.
### Sites konfigurieren
1. **Administration → Sites verwalten → Neue Site hinzufügen**
2. Felder ausfüllen:
- **Name:** Anzeigename (z. B. „Hauptgebäude")
- **Site ID:** UniFi Site ID (meist `default`)
- **Controller URL:** `https://unifi.example.com:11443`
- **Benutzername / Passwort:** UniFi Admin-Zugangsdaten
3. **Verbindung testen** klicken, dann speichern
### Voucher erstellen
1. Startseite öffnen (Login je nach Konfiguration optional)
2. Voucher-Name, Anzahl Geräte und Standort wählen
3. **Voucher erstellen** Code und QR-Code werden sofort angezeigt
4. Code per E-Mail senden oder ausdrucken
---
## 🔄 Auto-Updater
Das System bringt einen vollständig integrierten Updater mit, der Quellcode und
Datenbank-Migrationen über einen zentralen Update-Proxy nachzieht ganz ohne
SSH oder manuelles `git pull`.
```
[diese Instanz] ←HTTPS→ [Update-Proxy] ←pull→ [Git-Repo]
```
**Aufruf:** Administration → **System-Update** (`/admin/update.php`)
- 🔍 **„Auf Updates prüfen"** zeigt verfügbare Versionen inkl. Changelog
- ⬇️ **„Update installieren"** spielt das Update ein mit Wartungsseite,
geschützten Pfaden (`config.php`, Uploads, …), automatischen DB-Migrationen
und OPcache-Reset
- 🔀 **Channel-Auswahl** zwischen `stable` und `development`
- 📊 **Migrations-Status** in einem eigenen Tab inkl. Button **„Ausstehende
Migrationen ausführen"** (legt z. B. neue Tabellen für bestehende
Installationen an, ohne dass ein Code-Update nötig ist)
Während eines Updates wird die Anwendung kurz in den **Wartungsmodus** versetzt:
<div align="center">
<img src="docs/screenshots/maintenance.png" alt="Wartungsmodus" width="60%">
</div>
> Der Updater ist vollständig isoliert im Ordner [`updater/`](updater/) gekapselt.
> Details zur Architektur und eine Rückbau-Anleitung stehen in
> [`updater/README.md`](updater/README.md).
---
## ⚙️ Konfiguration
### `config.php`
Wird automatisch durch den Installer erstellt:
```php
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'unifi_voucher');
define('DB_USER', 'username');
define('DB_PASS', 'password');
define('APP_KEY', '...'); // Schlüssel für Verschlüsselung-at-rest
define('SESSION_LIFETIME', 3600); // Session-Timeout in Sekunden
date_default_timezone_set('Europe/Berlin');
```
### Microsoft 365 OAuth (optional)
1. Im [Azure Portal](https://portal.azure.com) eine App-Registrierung anlegen
2. Umleitungs-URI: `https://ihre-domain.de/m365_callback.php`
3. API-Berechtigungen: `openid`, `profile`, `email`, `User.Read`
4. Client ID, Client Secret und Tenant ID in **Administration → Einstellungen** eintragen
### Cron-Job (empfohlen)
Automatische Synchronisation alle 30 Minuten:
```bash
*/30 * * * * curl -s "https://ihre-domain.de/cron_sync.php?token=IHR_CRON_TOKEN"
```
Den Token finden Sie unter **Administration → Einstellungen → Cron**.
---
## 🎨 Design-System
Frontend, Login-Seiten, Installer, Updater und der gesamte Admin-Bereich nutzen ein
gemeinsames Stylesheet: **`assets/global.css`**.
- **Design-Tokens** (`:root` bzw. `[data-theme="dark"]`) für Flächen, Text, Linien,
Markenfarbe, Statusfarben, Radien, Schatten und Layout-Maße
- **Komponenten** darauf aufgebaut: Buttons, Formularfelder, Cards, Tabellen, Badges,
Alerts, Tabs, Pagination, Modals, Toasts, Statistik-Kacheln, Sidebar/Topbar
- **Dark Mode** ausschließlich über Tokens keine `!important`-Overrides mehr
- **Schriftart** Inter (via Google Fonts) mit System-Font-Fallback
### Markenfarben ohne Code
Unter **Administration → Einstellungen → Design** lassen sich Akzentfarbe
(hell und dunkel), Markenverlauf und Eckenradius setzen. Abgeleitete Töne
Hover, weiche Flächen, Rahmen, Fokusring berechnet das System per `color-mix`
aus der Grundfarbe; eine Farbe genügt also. Eine Live-Vorschau zeigt Button,
Badge, Chip und Logo-Kachel sofort im neuen Ton.
Die Werte landen als schlanker `:root`-Override im Seitenkopf und gelten überall,
auch auf Login-Seite, Installer und Updater. Wer lieber in CSS arbeitet, kann
dieselben Variablen weiterhin in `assets/global.css` überschreiben:
```css
:root {
--accent: #0f766e; /* Primärfarbe (Buttons, aktive Navigation) */
--brand-gradient: linear-gradient(135deg, #0f766e 0%, #0ea5e9 100%);
--r-lg: 14px; /* Eckenradius für Cards */
}
```
### Bilder hochladen
Logo, Favicon, Login-Logo und Login-Hintergrund lassen sich direkt hochladen
alternativ bleibt das URL-Feld bestehen. Die Dateien landen unter `uploads/`
(Docker: eigenes Volume, siehe unten). Erlaubt sind PNG, JPG, WEBP, GIF und SVG
bis 3 MB; SVGs werden vor dem Speichern von Skripten und externen Verweisen
befreit, und im Upload-Ordner sperrt eine `.htaccess` die PHP-Ausführung.
### Assets ohne Drittanbieter
Schrift (Inter), Icons (Font Awesome), Diagramme (Chart.js), QR-Codes und der
WYSIWYG-Editor (TinyMCE) liegen unter `assets/vendor/` und kommen vom eigenen
Server. Das hält Besucher-IPs bei Ihnen und die Oberfläche funktioniert auch
dort, wo das Netz keinen Weg nach außen hat. Details und Aktualisierungs-Hinweise:
[`assets/vendor/README.md`](assets/vendor/README.md).
Alle Asset-URLs tragen einen Versionsstempel (`?v=…`), damit Browser nach einem
Update nicht die alten Dateien aus dem Cache verwenden.
### Login-Seite individualisieren
Unter **Administration → Einstellungen → Login-Seite** lässt sich die Anmeldeseite
ohne Code-Änderung an das eigene Haus anpassen. Leere Felder verwenden jeweils den
Standardwert eine frische Installation sieht also unverändert aus.
| Einstellung | Wirkung |
|---|---|
| Linke Bildspalte anzeigen | Split-Screen an/aus. Aus = zentrierte Anmeldekarte |
| Firmenname | Name neben dem Logo bzw. in der Fußzeile (leer = Anwendungstitel) |
| Logo (URL) | Eigenes Logo in der Bildspalte (leer = allgemeines Logo) |
| Überschrift / Beschreibungstext | Claim in der Bildspalte |
| Stichpunkte | Liste mit Haken ein Stichpunkt pro Zeile, leer = keine Liste |
| Fußzeile | z. B. `© 2026 Muster GmbH · Datenschutz · Impressum` |
| Hintergrundbild (URL) | Formatfüllendes Bild der Bildspalte |
| Verlauf Start-/Endfarbe | Farbverlauf, wenn kein Bild gesetzt ist |
| Abdunklung (%) | Dunkle Ebene über dem Bild, damit der Text lesbar bleibt |
Über **„Vorschau öffnen"** lässt sich die Login-Seite als angemeldeter Administrator
ansehen (`login.php?preview=1`), ohne sich abzumelden.
---
## 🛡️ Sicherheit
Das Tool ist auf einen sicheren Standardbetrieb ausgelegt:
| Schutz | Umsetzung |
|---|---|
| **Passwörter** | bcrypt (`password_hash`) für Accounts |
| **UniFi-Passwörter** | Verschlüsselt-at-rest (AES-256-GCM / libsodium) über `APP_KEY` |
| **SQL-Injection** | Durchgängig Prepared Statements (PDO) |
| **CSRF** | Token für alle schreibenden Aktionen auch im öffentlichen Modus |
| **OAuth** | `state`-Validierung gegen Login-CSRF |
| **Brute-Force** | Login-Rate-Limiting + Throttle der öffentlichen Voucher-Erstellung |
| **Sessions** | HttpOnly, SameSite, strict mode + absolutes Timeout |
| **Fehler** | `display_errors` aus, `log_errors` an (kein Info-Leak) |
Mitgeliefert wird eine `.htaccess` im Projektstamm mit Sicherheits-Headern
(`X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`,
`Permissions-Policy` und einer Content-Security-Policy). Da alle Assets lokal
liegen, erlaubt die CSP nur noch die eigene Herkunft externe Verbindungen
bleiben lediglich für hCaptcha offen, falls es aktiviert wird. Ordner wie
`includes/`, `tools/`, `tests/` und `uploads/` schützen sich über eigene
`.htaccess`-Dateien.
> **Apache:** `AllowOverride All` muss für das Verzeichnis gesetzt sein, sonst
> werden die `.htaccess`-Dateien ignoriert. Das mitgelieferte Docker-Image
> erledigt das bereits.
Für **Nginx** entspricht das:
```nginx
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; frame-ancestors 'self'" always;
location ~ ^/(includes|tools|tests)/ { deny all; }
location ~ ^/updater/(storage|migrations)/ { deny all; }
location ~ ^/(config\.php|database\.sql)$ { deny all; }
location ^~ /uploads/ { location ~ \.php$ { deny all; } }
```
```sql
-- Dedizierter Datenbank-Benutzer mit minimalen Rechten
CREATE USER 'unifi_voucher'@'localhost' IDENTIFIED BY 'sicheres_passwort';
GRANT SELECT, INSERT, UPDATE, DELETE ON unifi_voucher.* TO 'unifi_voucher'@'localhost';
```
---
## 🩺 Problembehandlung
<details>
<summary><b>Login funktioniert nicht</b></summary>
- Datenbankverbindung und PHP-Session-Konfiguration prüfen
- Fehler werden ins PHP-Error-Log geschrieben (nicht mehr in den Browser)
</details>
<details>
<summary><b>UniFi-Verbindung schlägt fehl</b></summary>
- Controller-URL im Browser testen
- Port **11443** für UniFi OS verwenden (nicht 8443)
- Benutzername, Passwort und Site ID prüfen
- cURL-Extension muss aktiviert sein
</details>
<details>
<summary><b>UniFi OS: HTTP 404 oder 401</b></summary>
- Login-Endpunkt ist `/api/auth/login` (nicht `/api/login`)
- API-Pfade benötigen Präfix `/proxy/network/api/s/{site}/...`
- Älterer UniFi Network Controller (ohne UniFi OS) wird ab Version 2.1.0 nicht mehr unterstützt
</details>
<details>
<summary><b>Microsoft 365 Login funktioniert nicht</b></summary>
- Redirect URI in Azure AD prüfen
- Client ID, Secret und Tenant ID kontrollieren
- Diagnose unter `m365_debug.php` (nur als Admin erreichbar)
</details>
---
## 📡 API-Dokumentation (UniFi OS)
```http
# Login
POST /api/auth/login
Body: {"username": "admin", "password": "password"}
Response-Header: X-CSRF-Token: <token>
# Voucher erstellen
POST /proxy/network/api/s/{site_id}/cmd/hotspot
X-CSRF-Token: <token>
Body: {"cmd": "create-voucher", "expire": 480, "n": 1, "note": "Name", "quota": 1}
# Vouchers abrufen
GET /proxy/network/api/s/{site_id}/stat/voucher
# Voucher löschen
POST /proxy/network/api/s/{site_id}/cmd/hotspot
Body: {"cmd": "delete-voucher", "_id": "<voucher_id>"}
```
---
## 🧰 REST-API
Voucher lassen sich programmatisch erstellen (z. B. aus Buchungssystemen oder
Self-Service-Terminals). Schlüssel werden unter **Administration → API-Schlüssel**
verwaltet. Authentifizierung per `Authorization: Bearer <key>` oder `X-API-Key`.
```bash
# Voucher erstellen
curl -X POST https://ihre-domain.de/api/vouchers.php \
-H "Authorization: Bearer uvt_…" \
-H "Content-Type: application/json" \
-d '{"site_id":1,"name":"API Gast","max_uses":1,"expire_minutes":480,
"qos":{"down":10000,"up":2000,"quota_mb":500}}'
# Sites auflisten
curl https://ihre-domain.de/api/sites.php -H "X-API-Key: uvt_…"
# Voucher einer Site abrufen
curl "https://ihre-domain.de/api/vouchers.php?site_id=1" -H "X-API-Key: uvt_…"
```
| Methode | Endpunkt | Zweck |
|---|---|---|
| `POST` | `/api/vouchers.php` | Voucher erstellen (optional mit QoS-Limits) |
| `GET` | `/api/vouchers.php?site_id=<id>` | Voucher einer Site auflisten |
| `GET` | `/api/sites.php` | Aktive Sites auflisten |
## 🔒 2FA, Webhooks & Wartung
- **2FA:** Unter **Administration → Sicherheit (2FA)** aktivierbar QR-Code
scannen, Code bestätigen. Danach wird bei jeder Anmeldung ein Authenticator-
Code abgefragt.
- **Webhooks & Trusted-Proxy & Datenhaltung:** unter **Administration →
Integration & Wartung** konfigurierbar.
- **Auto-Cleanup (DSGVO):** täglicher Cron, löscht abgelaufene Voucher,
Audit-Log, Login-Versuche nach einstellbaren Fristen:
```bash
0 3 * * * curl -s "https://ihre-domain.de/cron_cleanup.php?token=IHR_CRON_TOKEN"
```
## 🐳 Docker
```bash
# APP_KEY erzeugen und in docker-compose.yml eintragen:
php -r 'echo base64_encode(random_bytes(32))."\n";'
docker compose up -d # App auf http://localhost:8080
```
Das Schema wird beim ersten Start automatisch in MariaDB geladen; danach den
Installer (`/install.php`) für den Admin-Account aufrufen oder Config per ENV
setzen (`DB_*`, `APP_KEY`).
Hochgeladene Logos und Hintergründe liegen im Volume `uploads` und überstehen
damit ein Image-Update. Bei eigener Apache-/Nginx-Installation muss `uploads/`
für den Webserver beschreibbar sein:
```bash
chown -R www-data:www-data uploads updater/storage
```
---
## 🧪 Entwicklung
Für Screenshots und einen schnellen Durchlauf aller Seiten gibt es eine
Demo-Instanz **ohne Datenbank** `Database` und `Auth` werden durch Stubs mit
festen Beispieldaten ersetzt:
```bash
python3 tools/demo/build.py /tmp/uvt-demo
php -S 127.0.0.1:8123 -t /tmp/uvt-demo &
# Bilder in docs/screenshots neu erzeugen (benötigt headless Chromium)
CHROME_BIN=/usr/bin/chromium python3 tools/screenshots.py
```
Details und die verfügbaren Demo-Zustände: [`tools/README.md`](tools/README.md).
Tests und statische Analyse:
```bash
composer install
vendor/bin/phpunit # 29 Tests (Crypto, TOTP, API-Keys, Upload, Ui)
vendor/bin/phpstan analyse # Level 5
```
Die Pipeline (`.github/workflows/ci.yml`) führt zusätzlich einen
Syntax-Check über alle PHP-Dateien aus und prüft, ob `lang/de.php` und
`lang/en.php` dieselben Schlüssel enthalten und jeder im Code verwendete
Schlüssel existiert. Dieselben Schritte lassen sich lokal ausführen.
---
## 📦 Repository & Mitwirken
Der Quellcode liegt auf der eigenen Forgejo-Instanz **nicht** auf GitHub:
**<https://git.loheide.cloud/friloo/Unifi-Voucher-Tool>**
```bash
# HTTPS
git clone https://git.loheide.cloud/friloo/Unifi-Voucher-Tool.git
# SSH (Port 2222)
git clone ssh://git@git.loheide.cloud:2222/friloo/Unifi-Voucher-Tool.git
```
Fehlerberichte und Änderungsvorschläge laufen über die **Issues** und **Pull
Requests** dort. Für die Kommandozeile eignet sich [`tea`](https://gitea.com/gitea/tea),
die Gitea-/Forgejo-CLI:
```bash
tea pr create # Pull Request öffnen
tea issues ls # offene Tickets ansehen
```
> Die Workflows unter `.github/workflows/` werden von Forgejo Actions
> mitgelesen; das Badge oben ist bewusst statisch, solange kein Runner
> registriert ist. `docker-publish.yml` veröffentlicht nach `ghcr.io` und
> stammt noch aus der GitHub-Zeit für den Forgejo-Betrieb entweder auf die
> eigene Registry umstellen oder entfernen.
Der **Auto-Updater** ist davon unabhängig: er zieht seine Pakete über
`update.loheide.eu` (Channels `stable` und `development`) und nicht direkt aus
dem Git-Hoster.
---
## 🗺️ Roadmap
- [x] Voucher-Templates (vordefinierte Laufzeiten)
- [x] Bulk-Voucher-Erstellung
- [x] Mehrsprachigkeit (DE/EN)
- [x] Dark Mode
- [x] Passwort-Reset
- [x] Audit-Log
- [x] Auto-Updater mit DB-Migrationen
- [x] REST-API mit API-Schlüsseln
- [x] 2FA (TOTP), Webhooks, Bandbreitenlimits
- [x] Docker-Container
- [x] Erweiterte Reporting-Funktionen (CSV/PDF) + Health-Endpoint
- [x] 2FA-Recovery-Codes, API-Scopes/Rate-Limit/OpenAPI, Test-Suite (PHPUnit/PHPStan)
- [x] Gemeinsames Design-System für Frontend, Login und Backend
- [x] Branding über die Oberfläche (Farben, Logo, Login-Seite)
- [x] Assets lokal ausliefern (keine Drittanbieter-CDNs)
- [x] Vollständige englische Übersetzung des Admin-Bereichs
---
<div align="center">
**Version 2.6.0** · Autor: **Friederich Loheide** · Lizenz: **MIT**
Entwickelt von **[Loheide.eu](https://loheide.eu)**
</div>