v2.0.12 — support channel: relay + hyprcrd plugin
Central support desk for the Oumta project. Tickets arrive through a
self-hostable support-relay (public form, SMTP out), hyprcrd pulls
them in as cards, replies go back out through the relay, and KB
fragments are plain hyprcrd cards so the board + search + tags
machinery handles them for free.
support-relay/ (standalone PHP/SQLite service)
- Public form + submit endpoint with IP rate-limit, honeypot, and an
email-matched reply path so users can follow up by typing their
OUMTA-n issue id — mismatched email falls back to a fresh ticket
so a hostile submitter can't hijack someone else's thread.
- One-shot setup wizard configures branding, issue-id prefix, public
URL, mail footer template, SMTP creds (stored AES-256-GCM at rest),
and generates the X-Support-Key API token shown exactly once.
- Hyprcrd-facing API: GET /api/hyprcrd/tickets?since=<iso>, POST
…/reply (sends via SMTP, flips status to answered), PATCH
…/status, DELETE, GET /ping, plus GET/POST /api/hyprcrd/settings
exposing brand/public_url/mail_footer for post-setup edits.
- Minimal dependency-free SMTP client covering STARTTLS/TLS/none,
AUTH LOGIN, RFC 2047 subject encoding, dot-stuffing. Extended to
emit multipart/alternative (plain+HTML) + multipart/related so
outbound mails carry the branded logo via Content-ID embedding;
plain-text fallback stays intact for older clients.
- Mail footer template supports {brand}, {issue_id}, {public_url}
placeholders. Lines with an empty placeholder are dropped, so the
"Reply via …" line vanishes cleanly when no public URL is set.
- Favicon on all public pages. Logo (SVG by default, PNG preferred
when both exist) picked up from logo2026col.* at send time.
- Docker: Dockerfile + docker-compose.yml for a stand-alone test
container, plus a support-relay service in the main compose
(profile "support") so hyprcrd can reach it at http://support-relay
without host-networking shenanigans.
public/plugins/support (hyprcrd client)
- Full-screen Support overlay: ticket list (status-filterable),
active ticket thread, reply composer with KB-fragment search +
click-to-insert, "Save as fragment" button, "Close ticket" shortcut.
- KB fragments are regular cards in a configurable bucket. The
composer just adds a fast search-and-insert path on top.
- Sync-tick hook auto-polls the relay alongside the existing CalDAV
+ relay-mail cycle.
- Settings tab in the main overlay: relay URL, API key, inbox / KB
bucket pickers, connection test, and a "Relay config" subsection
that round-trips to /api/hyprcrd/settings so the mail footer and
branding can be edited without touching the relay's DB.
- Local cache tables support_tickets + support_replies mirror the
relay's authoritative data keyed by relay_id.
Tooling
- dev.sh now opts into both relay + support profiles.
- dev-down.sh wipes support-relay volumes and bind-mount data for a
clean re-setup. Hyprcrd + relay data is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2d58cd993e
commit
7bce6a3a36
33 files changed
+2915
-10
No files matched your search
@@ -0,0 +1,197 @@
|
||||
# Support-Relay — Setup
|
||||
|
||||
Nimmt anonyme Support-Anfragen per Web-Formular entgegen, legt sie in
|
||||
einer lokalen SQLite-DB ab, versendet Bestätigungs- und Antwort-Mails
|
||||
per SMTP. Hyprcrd zieht sich die Tickets und antwortet durch diesen
|
||||
Relay hindurch. **Single-user** — ein Admin, ein API-Key.
|
||||
|
||||
## Lokal testen mit Docker (einfachster Weg)
|
||||
|
||||
```sh
|
||||
cd support-relay
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Danach `http://localhost:8090/` im Browser öffnen → Setup-Wizard. Kein
|
||||
Host-Setup nötig. Die DB und der master-key landen im lokalen
|
||||
`./data/`-Ordner und überleben `docker compose down`.
|
||||
|
||||
Zum Rücksetzen (neuer API-Key, leere DB):
|
||||
|
||||
```sh
|
||||
rm -f data/support.db data/support.db-* data/master.key
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
Stoppen: `docker compose down`.
|
||||
|
||||
---
|
||||
|
||||
## Was du für Production brauchst
|
||||
|
||||
- Eine Subdomain, z.B. `support.oumta.de`
|
||||
- PHP ≥ 8.1 mit `pdo_sqlite` und `openssl`
|
||||
- Apache mit `mod_rewrite` (oder nginx mit äquivalentem Rewrite)
|
||||
- Einen SMTP-Zugang (Mailserver, Mailtrap, Office365, was auch immer)
|
||||
|
||||
---
|
||||
|
||||
## 1. Upload
|
||||
|
||||
Den kompletten Ordner `support-relay/` als Document-Root der Subdomain
|
||||
hochladen. Die Struktur sollte am Host so aussehen:
|
||||
|
||||
```
|
||||
support.oumta.de/
|
||||
├── index.php
|
||||
├── .htaccess
|
||||
├── inc/
|
||||
├── ui/
|
||||
└── data/ ← muss vom Webserver schreibbar sein!
|
||||
```
|
||||
|
||||
**`data/` schreibbar machen** (SSH):
|
||||
|
||||
```sh
|
||||
chmod 755 data
|
||||
```
|
||||
|
||||
Der Webserver legt dort `support.db` und `master.key` an. `master.key`
|
||||
kriegt automatisch `chmod 600`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Erster Aufruf → Setup-Wizard
|
||||
|
||||
`https://support.oumta.de/` im Browser öffnen. Wenn `data/` schreibbar
|
||||
ist, erscheint automatisch der Setup-Wizard. Ausfüllen:
|
||||
|
||||
| Feld | Was eintragen |
|
||||
|---|---|
|
||||
| **Display name** | `Oumta Support` (wird Absendername, Subject-Präfix, Footer) |
|
||||
| **Issue-prefix** | `OUMTA` (Tickets heißen dann `OUMTA-1`, `OUMTA-2`, …) |
|
||||
| **Host** | z.B. `smtp.office365.com` / `smtp.mailtrap.io` / eigener Mailserver |
|
||||
| **Port** | `587` (STARTTLS — Standard) |
|
||||
| **Encryption** | `STARTTLS (587)` lassen, außer dein Server will was anderes |
|
||||
| **Username** | SMTP-User |
|
||||
| **Password** | SMTP-Passwort (wird verschlüsselt abgelegt) |
|
||||
| **From address** | z.B. `support@oumta.de` |
|
||||
| **From name** | `Oumta Support` |
|
||||
|
||||
**Speichern** drücken. Die nächste Seite zeigt einen langen **API-Key**.
|
||||
Kopieren und sicher aufheben — wird **exakt einmal** angezeigt.
|
||||
|
||||
Nach dem Setup ist diese Seite nicht mehr erreichbar. Stattdessen zeigt
|
||||
`/` jetzt das öffentliche Support-Formular.
|
||||
|
||||
---
|
||||
|
||||
## 3. Hyprcrd verbinden
|
||||
|
||||
In hyprcrd:
|
||||
|
||||
1. Zwei Buckets anlegen (falls noch nicht vorhanden):
|
||||
- **Support Inbox** — hier landen eingehende Tickets als Karten
|
||||
- **Support KB** — hier liegen deine wiederverwendbaren Antwort-Bausteine
|
||||
2. Settings (Zahnrad) öffnen → Tab **support**
|
||||
3. Eintragen:
|
||||
- **Relay URL**: `https://support.oumta.de`
|
||||
- **API key**: der gerade kopierte Key
|
||||
- **Inbox bucket**: `Support Inbox`
|
||||
- **KB bucket**: `Support KB`
|
||||
4. **Save** → **Test connection** → muss grün zurückkommen
|
||||
(`OK — relay says "Oumta Support"`)
|
||||
|
||||
---
|
||||
|
||||
## 4. Smoke-Test
|
||||
|
||||
**a) Ticket einreichen**
|
||||
|
||||
In einem Inkognito-Tab `https://support.oumta.de/` öffnen, eine
|
||||
Testanfrage absenden. Erwartung:
|
||||
|
||||
- Grüner Banner: `Received. Your issue id is OUMTA-1.`
|
||||
- Bestätigungsmail im Posteingang
|
||||
|
||||
**b) Ticket in hyprcrd**
|
||||
|
||||
Oben rechts den **Support**-Button im Header klicken → Overlay auf.
|
||||
Auf den Reload-Knopf (↻) klicken, dann erscheint das Ticket in der
|
||||
Liste. Im "Support Inbox"-Bucket liegt eine Karte mit derselben Info.
|
||||
|
||||
**c) Antworten**
|
||||
|
||||
Ticket anklicken → ins Textfeld tippen → **Send reply**. Erwartung:
|
||||
|
||||
- Status wechselt auf `answered`
|
||||
- Mail landet im Posteingang des Absenders
|
||||
|
||||
**d) Fragment speichern**
|
||||
|
||||
Nach dem Tippen **Save as fragment** → Titel vergeben → die Antwort
|
||||
taucht im KB-Bucket als Karte auf und wird ab sofort im Suchfeld
|
||||
über dem Composer gefunden.
|
||||
|
||||
---
|
||||
|
||||
## 5. Normaler Betrieb
|
||||
|
||||
- **Neue Tickets sehen**: Support-Overlay in hyprcrd. Pollt automatisch
|
||||
bei jedem Sync-Tick; manueller Pull per ↻-Knopf.
|
||||
- **Antworten**: aus dem Composer heraus, SMTP läuft über den Relay.
|
||||
- **Status setzen**: `open` → `answered` (automatisch bei Reply) →
|
||||
`closed` (manuell per Button).
|
||||
- **User antwortet auf eine Mail**: er muss seine **Issue-ID**
|
||||
(`OUMTA-n`) erneut ins Formular eintragen. Dann wird die Nachricht
|
||||
an das bestehende Ticket gehängt (nur wenn die Email matcht, sonst
|
||||
wird daraus ein neues Ticket).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Setup-Wizard kommt nicht**
|
||||
`data/` ist nicht schreibbar. → `chmod 755 data/` (oder 775) und neu
|
||||
laden.
|
||||
|
||||
**"Test connection" schlägt fehl**
|
||||
Relay-URL falsch (Trailing-Slash ist OK, aber HTTPS muss stimmen) oder
|
||||
API-Key nicht exakt übernommen.
|
||||
|
||||
**Confirmation-Mail kommt nicht**
|
||||
SMTP-Creds falsch oder Port blockiert. Direkt vom Host testen:
|
||||
|
||||
```sh
|
||||
php -r '
|
||||
require "inc/bootstrap.php"; require "inc/SmtpClient.php";
|
||||
$c = supportRelaySmtpConfig(Database::connect(), $MASTER_KEY);
|
||||
$r = (new SmtpClient($c))->send("du@example.com", $c["from_name"], $c["from"],
|
||||
"ping", ["Subject" => "SMTP ping"]);
|
||||
print_r($r);
|
||||
'
|
||||
```
|
||||
|
||||
Der `log`-Array zeigt die SMTP-Konversation.
|
||||
|
||||
**Alles kaputt, von vorn anfangen**
|
||||
`data/support.db` und `data/master.key` löschen. Nächster Aufruf
|
||||
startet wieder im Setup-Wizard — der API-Key ändert sich dann,
|
||||
musst du in hyprcrd neu eintragen.
|
||||
|
||||
---
|
||||
|
||||
## Dateien
|
||||
|
||||
| Pfad | Zweck |
|
||||
|---|---|
|
||||
| `index.php` | Entry-Point + Routing |
|
||||
| `inc/bootstrap.php` | DB-Schema, Settings, Helpers |
|
||||
| `inc/SmtpClient.php` | Minimaler SMTP-Client |
|
||||
| `inc/routes/*.php` | Public / Setup / Hyprcrd-API |
|
||||
| `ui/*.php` | Form, Setup-Wizard, Status-Seite |
|
||||
| `ui/style.css` | Styling |
|
||||
| `data/support.db` | SQLite DB (tickets, replies, settings) |
|
||||
| `data/master.key` | Verschlüsselt SMTP-Passwort (chmod 600) |
|
||||
|
||||
Gute Nacht 🌙
|
||||
Reference in new issue
Block a user