Open registration let anyone with the URL create an account. Two
changes address that:
- REGISTRATION_CODE (.env, optional): when set, registration requires
entering it correctly. Empty/unset keeps registration open, so
existing installs are unaffected until configured.
- is_admin flag on User: the first account ever created on an install
becomes admin automatically (existing installs get their oldest
account promoted via the startup migration, so nobody is locked
out of user management after upgrading).
Admins get a new "Benutzerverwaltung" panel in Einstellungen listing
every account (email, link/prompt counts, join date) with a delete
button per account — deleting cascades to that user's links and
prompts via the existing relationship cascade. Deleting your own
account through this page is blocked (redirects with an error) to
avoid accidental admin lockout. Non-admins get a 403 on the
/settings/users routes.
Also fixes several pre-existing German pluralization bugs found while
writing the new counts ("2 Linke" -> "2 Links", "Kontoen"/"Konton" ->
"Konten") — irregular plurals need a full word swap, not a suffix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
210 lines
8.9 KiB
Markdown
210 lines
8.9 KiB
Markdown
# LinkVault
|
||
|
||
KI-gestützte Sammlung für **Links** und **Prompts**: eingeben → automatisch
|
||
**kategorisieren**, in **Kategorien / Hersteller / Tags** einsortieren und
|
||
schnell **wiederfinden**. Beide Sammlungen laufen in derselben App und
|
||
Datenbank, per Menü umschaltbar.
|
||
|
||
## Funktionen
|
||
|
||
- 🔗 **Link hinzufügen** – Seite wird geladen und der Textinhalt extrahiert.
|
||
- ✨ **Prompt hinzufügen** – eigener Bereich für KI-Prompts, per Kopf-Menü
|
||
zwischen „Links" und „Prompts" umschaltbar. Prompts werden ebenfalls
|
||
automatisch kategorisiert und getaggt, lassen sich per Kategorie/Volltext
|
||
durchsuchen und mit einem Klick in die Zwischenablage kopieren. Der
|
||
Prompt-Text unterstützt **Markdown** (Überschriften, Listen, Fett/Kursiv,
|
||
Codeblöcke, Zitate) – in der Karten-Vorschau wird das gerendert, beim
|
||
Kopieren landet der rohe Markdown-Text (für Struktur in der Ziel-KI) in
|
||
der Zwischenablage.
|
||
- 🤖 **Automatische KI-Analyse** (OpenAI) – Titel, Zusammenfassung/Kategorie
|
||
und Tags werden erzeugt. Bestehende Kategorien werden wiederverwendet,
|
||
damit die Sammlung konsistent bleibt.
|
||
- ✎ **Bearbeiten** – alle Felder lassen sich jederzeit manuell anpassen.
|
||
Alternativ kann die KI Links/Prompts **neu beschreiben bzw. neu
|
||
kategorisieren** lassen.
|
||
- 🗂️ **Filtern** nach Kategorie (Links zusätzlich nach Hersteller) über die
|
||
Seitenleiste.
|
||
- 🔍 **Suche** – klassische Textsuche, bei Links zusätzlich optionale
|
||
**semantische KI-Suche** (findet auch sinnverwandte Treffer via
|
||
Embeddings).
|
||
- ↕️ **Sortieren** nach Datum (neueste/älteste zuerst) oder Name (A–Z / Z–A).
|
||
- 🔖 **Merkliste** – Links lassen sich zur späteren Durchsicht markieren.
|
||
- 🕓 **Hinzufügedatum** wird pro Eintrag angezeigt.
|
||
- 📤 **Export/Import** (Einstellungen) – Links als CSV oder SQL-Dump
|
||
exportieren, SQL-Dump auch wieder importieren.
|
||
- 🌗 **Dark/Light-Modus** über den Umschalter im Kopfbereich.
|
||
- 👥 **Mehrbenutzer** – Registrierung/Login, jeder Nutzer sieht nur seine
|
||
eigenen Links und Prompts. Registrierung optional per Einladungscode
|
||
schützbar (`REGISTRATION_CODE` in der `.env`); das zuerst angelegte Konto
|
||
wird automatisch Admin und kann unter Einstellungen → Benutzerverwaltung
|
||
weitere Konten einsehen und löschen.
|
||
|
||
Die Fußzeile zeigt Autor, Version und den Hostnamen des Servers. Die Version
|
||
lässt sich über `APP_VERSION` in der `.env` setzen.
|
||
|
||
## Technik
|
||
|
||
- **Backend:** FastAPI (Python)
|
||
- **Frontend:** HTML + HTMX (kein Node.js nötig)
|
||
- **Datenbank:** SQLite (Standard, konfigurierbar über `DATABASE_URL`)
|
||
- **KI:** OpenAI (`gpt-4o-mini` + `text-embedding-3-small`, konfigurierbar)
|
||
|
||
## Schnellstart
|
||
|
||
```bash
|
||
./run.sh
|
||
```
|
||
|
||
Das Skript legt eine virtuelle Umgebung an, installiert die Abhängigkeiten,
|
||
erstellt bei Bedarf eine `.env` und startet den Server auf
|
||
http://127.0.0.1:8000.
|
||
|
||
Danach in `.env` den `OPENAI_API_KEY` eintragen und den Server neu starten.
|
||
|
||
> Ohne API-Key läuft die App trotzdem – Links werden dann nur ohne KI-Analyse
|
||
> gespeichert (Kategorie „Sonstiges", keine Zusammenfassung/semantische Suche).
|
||
|
||
### Manuell
|
||
|
||
```bash
|
||
python3 -m venv .venv
|
||
source .venv/bin/activate
|
||
pip install -r requirements.txt
|
||
cp .env.example .env # OPENAI_API_KEY eintragen
|
||
uvicorn app.main:app --reload --port 8000
|
||
```
|
||
|
||
## Projektstruktur
|
||
|
||
```
|
||
app/
|
||
main.py FastAPI-App, Routen (Links & Prompts), Auth-Sessions
|
||
config.py Konfiguration aus .env
|
||
database.py SQLAlchemy Engine/Session
|
||
models.py User-, Link- und Prompt-Modelle
|
||
security.py Passwort-Hashing (bcrypt)
|
||
scraper.py Seiteninhalt laden & extrahieren (nur Links)
|
||
ai.py OpenAI: Kategorisierung (Links & Prompts), Embeddings
|
||
search.py Semantische Suche (Kosinus-Ähnlichkeit, nur Links)
|
||
backup.py CSV-/SQL-Export und sicherer SQL-Import (nur Links)
|
||
mdrender.py Sicheres Markdown-Rendering für Prompt-Inhalte
|
||
templates/ Jinja2 + HTMX Oberfläche
|
||
```
|
||
|
||
## Proxmox
|
||
|
||
Für den Betrieb in einem Proxmox-VE-LXC-Container gibt es ein Helper-Script
|
||
(`scripts/proxmox/install-linkvault.sh`). Es legt automatisch einen
|
||
Debian-12-Container an und richtet LinkVault darin als systemd-Service ein.
|
||
|
||
### Installation
|
||
|
||
Auf dem **Proxmox-Host** (als root) ausführen:
|
||
|
||
```bash
|
||
bash <(curl -fsSL https://gitea.teamthiele.de/ethiele/linkvault/raw/branch/main/scripts/proxmox/install-linkvault.sh)
|
||
```
|
||
|
||
> Falls das Repo (wieder) auf privat gestellt wird, zusätzlich ein Gitea
|
||
> Access Token verwenden (**Einstellungen → Anwendungen → Neuen Token
|
||
> erzeugen**, Scope `read:repository` genügt):
|
||
> ```bash
|
||
> export GIT_TOKEN=<dein-token>
|
||
> bash <(curl -fsSL -H "Authorization: token ${GIT_TOKEN}" \
|
||
> https://gitea.teamthiele.de/ethiele/linkvault/raw/branch/main/scripts/proxmox/install-linkvault.sh)
|
||
> ```
|
||
> `GIT_TOKEN` wird automatisch an das Script weitergereicht und auch beim
|
||
> `git clone` innerhalb des LXC-Containers verwendet.
|
||
|
||
Alternativ, falls das Repo bereits lokal ausgecheckt ist:
|
||
|
||
```bash
|
||
bash scripts/proxmox/install-linkvault.sh
|
||
```
|
||
|
||
Das Script:
|
||
1. lädt bei Bedarf das Debian-12-LXC-Template herunter,
|
||
2. erstellt einen unprivilegierten LXC-Container,
|
||
3. installiert darin Python, klont das Repo nach `/opt/linkvault`,
|
||
4. legt ein venv an und installiert die Abhängigkeiten,
|
||
5. erzeugt eine `.env` mit zufälligem `SECRET_KEY`,
|
||
6. richtet LinkVault als systemd-Service (`linkvault.service`) ein und startet ihn.
|
||
|
||
Am Ende gibt das Script die URL des Containers sowie das generierte
|
||
root-Passwort aus.
|
||
|
||
### Konfiguration
|
||
|
||
Alle Einstellungen lassen sich per Umgebungsvariable vor dem Aufruf setzen
|
||
(Defaults siehe Kopf des Scripts):
|
||
|
||
| Variable | Bedeutung | Default |
|
||
|-------------------|-----------------------------------------|---------------------------------|
|
||
| `CTID` | Container-ID | nächste freie ID |
|
||
| `CT_HOSTNAME` | Hostname des Containers | `linkvault` |
|
||
| `CT_DISK_GB` | Größe der Root-Disk in GB | `8` |
|
||
| `CT_MEMORY_MB` | RAM in MB | `1024` |
|
||
| `CT_SWAP_MB` | Swap in MB | `512` |
|
||
| `CT_CORES` | CPU-Kerne | `2` |
|
||
| `CT_BRIDGE` | Netzwerk-Bridge | `vmbr0` |
|
||
| `CT_IP` | Statische IP (`192.168.1.50/24`) oder `dhcp` | `dhcp` |
|
||
| `CT_GW` | Gateway (nur bei statischer IP nötig) | – |
|
||
| `CT_STORAGE` | Storage für die Root-Disk | `local-lvm` |
|
||
| `TEMPLATE_STORAGE`| Storage für das LXC-Template | `local` |
|
||
| `CT_PASSWORD` | root-Passwort des Containers | zufällig generiert |
|
||
| `REPO_URL` | Git-Repo, das geklont wird | `.../ethiele/linkvault.git` |
|
||
| `APP_PORT` | Port, auf dem uvicorn lauscht | `8000` |
|
||
| `GIT_TOKEN` | Gitea Access Token (nur bei privatem Repo) | – |
|
||
| `TIMEZONE` | Zeitzone des Containers (`timedatectl`) | `Europe/Berlin` |
|
||
|
||
Beispiel mit statischer IP und fester Container-ID:
|
||
|
||
```bash
|
||
CTID=150 CT_HOSTNAME=linkvault CT_IP=192.168.1.50/24 CT_GW=192.168.1.1 \
|
||
bash <(curl -fsSL https://gitea.teamthiele.de/ethiele/linkvault/raw/branch/main/scripts/proxmox/install-linkvault.sh)
|
||
```
|
||
|
||
### Nach der Installation
|
||
|
||
In `/opt/linkvault/.env` **innerhalb des Containers** den `OPENAI_API_KEY`
|
||
eintragen (siehe [Schnellstart](#schnellstart) für weitere `.env`-Optionen),
|
||
danach den Dienst neu starten:
|
||
|
||
```bash
|
||
pct exec <CTID> -- systemctl restart linkvault
|
||
```
|
||
|
||
Nützliche Befehle:
|
||
|
||
```bash
|
||
pct exec <CTID> -- systemctl status linkvault # Status prüfen
|
||
pct exec <CTID> -- journalctl -u linkvault -f # Logs verfolgen
|
||
```
|
||
|
||
### Updaten
|
||
|
||
Wenn sich im Git-Repo etwas geändert hat, den Container mit
|
||
`scripts/proxmox/update-linkvault.sh` auf den neuesten Stand bringen. Das
|
||
Script zieht per `git pull`, aktualisiert die Python-Abhängigkeiten und
|
||
startet den systemd-Service neu. Auf dem **Proxmox-Host** ausführen:
|
||
|
||
```bash
|
||
CTID=113 bash <(curl -fsSL https://gitea.teamthiele.de/ethiele/linkvault/raw/branch/main/scripts/proxmox/update-linkvault.sh)
|
||
```
|
||
|
||
(`GIT_TOKEN` nur nötig, falls das Repo privat ist – siehe oben.)
|
||
|
||
Alternativ, falls das Repo bereits **auf dem Proxmox-Host selbst** per
|
||
`git clone` ausgecheckt wurde (z.B. unter `~/linkvault`) und du dich in
|
||
diesem Verzeichnis befindest:
|
||
|
||
```bash
|
||
CTID=113 bash scripts/proxmox/update-linkvault.sh
|
||
```
|
||
|
||
## Hinweise für den Produktivbetrieb
|
||
|
||
- Einen zufälligen `SECRET_KEY` in `.env` setzen.
|
||
- Hinter einem HTTPS-Reverse-Proxy betreiben (Session-Cookies).
|
||
- Für mehr Last statt SQLite z.B. PostgreSQL via `DATABASE_URL` verwenden.
|