The "Hinzufügen" buttons reserved layout space for their loading spinner even when idle, since .htmx-indicator only toggled opacity (not display), leaving a visible gap before the centered text. Switch to display:none/inline-flex so the spinner takes no space until an htmx request is in flight, and drop the static plus icon next to "Hinzufügen" so the idle button just shows centered text with the spinner appearing in its place while loading. Also adds the favicon as a small logo image in front of "LinkVault" in the shared topbar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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_CODEin 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
./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
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 <(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:repositorygenügt):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_TOKENwird automatisch an das Script weitergereicht und auch beimgit cloneinnerhalb des LXC-Containers verwendet.
Alternativ, falls das Repo bereits lokal ausgecheckt ist:
bash scripts/proxmox/install-linkvault.sh
Das Script:
- lädt bei Bedarf das Debian-12-LXC-Template herunter,
- erstellt einen unprivilegierten LXC-Container,
- installiert darin Python, klont das Repo nach
/opt/linkvault, - legt ein venv an und installiert die Abhängigkeiten,
- erzeugt eine
.envmit zufälligemSECRET_KEY, - 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:
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 für weitere .env-Optionen),
danach den Dienst neu starten:
pct exec <CTID> -- systemctl restart linkvault
Nützliche Befehle:
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:
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:
CTID=113 bash scripts/proxmox/update-linkvault.sh
Hinweise für den Produktivbetrieb
- Einen zufälligen
SECRET_KEYin.envsetzen. - Hinter einem HTTPS-Reverse-Proxy betreiben (Session-Cookies).
- Für mehr Last statt SQLite z.B. PostgreSQL via
DATABASE_URLverwenden.