Kommentarsysteme wie Disqus sind praktisch – aber sie laden Tracking, Werbung und externe Abhängigkeiten mit. Cactus Comments macht das anders: Kommentare landen direkt in Matrix-Räumen auf dem eigenen Homeserver.
Im letzten Artikel habe ich gezeigt, wie man Synapse mit Docker aufsetzt und einen eigenen Matrix-Homeserver betreibt. Heute bauen wir darauf auf: Mit Cactus Comments bekommt jeder Blogartikel seinen eigenen Matrix-Chatraum – Leser können Kommentare hinterlassen, ohne sich bei einem Drittanbieter registrieren zu müssen, und ich habe die volle Kontrolle über meine Daten.
Was ist Cactus Comments?
Cactus Comments ist ein föderiertes Kommentarsystem für das offene Web, das das Matrix-Protokoll als Backend nutzt. Das Prinzip ist elegant: Für jeden Blogartikel wird automatisch ein Matrix-Raum angelegt. Wer einen Kommentar schreiben möchte, meldet sich mit seinem Matrix-Account an – das kann ein Account auf matrix.org, meinem eigenen Server oder jedem anderen Matrix-Homeserver sein. Federation sei Dank.
Das System besteht aus zwei Teilen:
- Cactus Appservice – ein Python-Dienst, der als Matrix-Bot (hier:
@cactusbot) auf dem Homeserver läuft und die Räume verwaltet - Cactus Client – eine JavaScript/Elm-Webanwendung, die auf dem Blog eingebettet wird und das Kommentarfeld rendert
Voraussetzungen
- Ein laufender Synapse-Homeserver (meine Anleitung dazu: eigener Matrix-Homeserver mit Synapse)
- Docker und Docker Compose
- Node.js und npm (für den Client-Build)
- Eine Hugo-Website
Den Cactus Client bauen
Der Cactus Client wird nicht als fertige Bundle-Datei ausgeliefert – er muss selbst gebaut werden. Ich möchte außerdem eine eigene Kopie im lokalen Forgejo behalten, statt mich auf GitLab zu verlassen.
Repository klonen und auf Forgejo spiegeln:
git clone https://gitlab.com/cactus-comments/cactus-client.git
cd cactus-client
git remote rename origin gitlab
git remote add origin https://git.techlab.icu/sebastianzehner/cactus-client.git
git push origin --all
git push origin --tags
Build ausführen:
npm install
npm run build
Wenn du noch kein eigenes Forgejo hast, kannst du das Spiegeln überspringen.
Möglicher Fehler: Korruptes Elm-Paket
Beim ersten Build-Versuch ist bei mir folgender Fehler aufgetaucht:
🚨 CORRUPT PACKAGE DATA
I downloaded the source code for ryannhg/date-format 2.3.0 from:
https://github.com/ryannhg/date-format/zipball/2.3.0/
But it looks like the hash of the archive has changed since publication.
Das Paket ryannhg/date-format hat seit seiner Veröffentlichung einen geänderten Hash – ein bekanntes Problem bei Elm-Abhängigkeiten, wenn der Paketautor den Version-Tag nachträglich verschoben hat. Die Lösung: das Paket manuell herunterladen und an der richtigen Stelle ablegen.
cd ~/.elm/0.19.1/packages/ryannhg/date-format/2.3.0/
curl -L "https://github.com/ryannhg/date-format/zipball/2.3.0/" -o package.zip
unzip package.zip
mv ryan-haskell-date-format-b0e7928/* .
rm -rf ryan-haskell-date-format-b0e7928 package.zip
Danach nochmal bauen – diesmal erfolgreich:
✨ Built in 3.73s.
dist/cactus.js 155.95 KB
dist/style.css 6.96 KB
Den Appservice einrichten
Schritt 1: Tokens generieren
Der Appservice benötigt zwei zufällige Tokens für die Authentifizierung zwischen Synapse und Cactus:
cat /dev/urandom | tr -dc 'a-f0-9' | fold -w 64 | head -n 2
Die erste Ausgabezeile wird as_token, die zweite hs_token. Beide gut notieren.
Schritt 2: Registration-Datei für Synapse erstellen
nvim ~/docker/synapse/files/cactus.yaml
Inhalt:
id: "Cactus Comments"
url: "http://cactus:5000"
as_token: "YOUR_AS_TOKEN"
hs_token: "YOUR_HS_TOKEN"
sender_localpart: "cactusbot"
namespaces:
aliases:
- exclusive: true
regex: "#comments_.*"
Diese Datei teilt Synapse mit, dass es einen Appservice namens cactusbot gibt, der alle Raumaliase mit dem Präfix #comments_ verwaltet.
Schritt 3: homeserver.yaml ergänzen
nvim ~/docker/synapse/files/homeserver.yaml
Folgende Zeilen hinzufügen:
app_service_config_files:
- "/data/cactus.yaml"
allow_guest_access: true
use_appservice_legacy_authorization: true
enable_authenticated_media: false
public_baseurl: "https://matrix.your-domain.com"
Wichtig: Der Pfad
/data/cactus.yamlist der Pfad innerhalb des Synapse-Containers. Bei mir ist~/docker/synapse/files/als/datagemountet.
Sicherheitshinweis: Die Einstellungen
allow_guest_access: true,use_appservice_legacy_authorization: trueundenable_authenticated_media: falsesind Anforderungen des Cactus Appservice und lockern einige Sicherheitseinstellungen von Synapse. Wer das vermeiden möchte, müsste den Cactus Client entsprechend erweitern – das liegt jedoch außerhalb des Rahmens dieser Anleitung.
Schritt 4: Umgebungsvariablen für Cactus
nvim ~/docker-compose/synapse/cactus.env
Inhalt:
CACTUS_HS_TOKEN=YOUR_HS_TOKEN
CACTUS_AS_TOKEN=YOUR_AS_TOKEN
CACTUS_HOMESERVER_URL=http://synapse:8008
CACTUS_USER_ID=@cactusbot:matrix.your-domain.com
Schritt 5: Docker Compose erweitern
In der bestehenden docker-compose.yml für Synapse füge ich den Cactus-Service hinzu:
cactus:
image: cactuscomments/cactus-appservice:latest
container_name: cactus
env_file: cactus.env
restart: unless-stopped
networks:
- synapse
Cactus landet im synapse-Netzwerk, damit es den Synapse-Container direkt unter http://synapse:8008 erreichen kann.
Schritt 6: Starten
cd ~/docker-compose/synapse
docker compose down
docker compose up -d synapse
# wait for Synapse to become healthy
docker compose up -d cactus
Zur Kontrolle:
docker logs cactus --tail 50
docker logs synapse --tail 50
Die Website bei Cactus registrieren
Bevor Cactus Kommentarräume für meinen Blog anlegen kann, muss ich meine Website beim cactusbot registrieren. Das geht direkt über Element:
Einen neuen Chat mit @cactusbot:matrix.your-domain.com öffnen und eingeben:
register <websitename>
Wenn alles korrekt eingerichtet ist, antwortet der Bot mit einer Bestätigung. In den Container-Logs sieht der erfolgreiche Ablauf so aus:
INFO in app: Registration complete
INFO in app: Created site name='websitename' owner='@your_name:matrix.your-domain.com'
INFO in app: Power level changed, replicating room='#comments_websitename:matrix.your-domain.com'
Hugo Integration
Client-Dateien kopieren
cd ~/hugo/cactus-client
cp dist/cactus.js ~/hugo/blog/static/
cp dist/style.css ~/hugo/blog/static/cactus.css
Shortcode erstellen
nvim ~/hugo/blog/layouts/shortcodes/chat.html
Mein Shortcode lädt den Cactus Client und initialisiert den Kommentarbereich. Ich habe ihn außerdem an mein Catppuccin-Farbschema angepasst – sowohl für den hellen Latte- als auch den dunklen Mocha-Modus:
<script type="text/javascript" src="/cactus.js"></script>
<link rel="stylesheet" href="/cactus.css" type="text/css" />
<style>
/* Fix avatar image distortion */
.cactus-comment-avatar img {
max-width: unset;
width: 40px;
height: 40px;
object-fit: cover;
}
/* Catppuccin Latte (Light) */
:root[data-theme="light"] {
--cactus-text-color: #4c4f69;
--cactus-text-color--soft: #6c6f85;
--cactus-background-color: transparent;
--cactus-background-color--strong: #e6e9ef;
--cactus-border-color: #ccd0da;
--cactus-border-width: 1px;
--cactus-border-radius: 0.5em;
--cactus-box-shadow-color: rgba(30, 102, 245, 0.15);
--cactus-button-text-color: #4c4f69;
--cactus-button-color: #dce0e8;
--cactus-button-color--strong: #ccd0da;
--cactus-button-color--stronger: #bcc0cc;
--cactus-login-form-text-color: #4c4f69;
--cactus-error-color: #d20f39;
}
/* Catppuccin Mocha (Dark) */
:root[data-theme="dark"] {
--cactus-text-color: #cdd6f4;
--cactus-text-color--soft: #a6adc8;
--cactus-background-color: transparent;
--cactus-background-color--strong: #313244;
--cactus-border-color: #45475a;
--cactus-box-shadow-color: rgba(137, 180, 250, 0.18);
--cactus-button-text-color: #cdd6f4;
--cactus-button-color: #45475a;
--cactus-button-color--strong: #585b70;
--cactus-button-color--stronger: #6c7086;
--cactus-login-form-text-color: #cdd6f4;
--cactus-error-color: #f38ba8;
}
</style>
<br />
<div id="comment-section"></div>
<script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "https://matrix.your-domain.com",
serverName: "matrix.your-domain.com",
siteName: "websitename",
commentSectionId: "{{ index .Params 0 }}",
});
</script>
Alle verfügbaren Konfigurationsoptionen für initComments sind in der Cactus Client Dokumentation beschrieben.
Kommentarbereich in einen Beitrag einbinden
Ab sofort reicht eine einzige Zeile, um unter einem Artikel einen Kommentarbereich hinzuzufügen:
{{< chat cactus-comments >}}
Der Parameter cactus-comments ist der Name des Matrix-Raums für diesen Artikel. Jeder Raum bekommt automatisch den Alias #comments_websitename_cactus-comments:matrix.your-domain.com. Ich kann für jeden Artikel einen eigenen Raumnamen verwenden oder denselben für alle – das hängt davon ab, ob man Kommentare pro Artikel oder global zusammenführen möchte.
Änderungen veröffentlichen
git add layouts/shortcodes/chat.html static/cactus.css static/cactus.js
git commit -m "migrate Cactus Comments to self-hosted matrix.your-domain.com"
git push origin
Fazit
Was mich an Cactus Comments überzeugt: Es gibt keine externe Datenbank, kein Drittanbieter-Tracking, keine JavaScript-Payloads von fremden Domains.
Die Kommentare liegen als gewöhnliche Matrix-Events in meinem eigenen Synapse – gesichert mit meinem üblichen restic-Backup, versioniert, portierbar.
Gleichzeitig kann jeder, der einen Matrix-Account hat, sofort kommentieren – ganz egal, auf welchem Homeserver sein Account liegt. Und wer noch keinen Account hat, kann sich in wenigen Minuten einen auf matrix.org anlegen.
Das ist das Web, wie es sein sollte.
Fragen oder Anmerkungen? Schreib mir direkt über Matrix: @sebastian:matrix.techlab.icu – oder hinterlasse einfach unten einen Kommentar. Der landet dann auch prompt in meiner Matrix.
Liebe Grüße Sebastian