- Go 85.6%
- HTML 6.4%
- CSS 4.6%
- JavaScript 2.1%
- Makefile 0.7%
- Andere 0.6%
|
|
||
|---|---|---|
| .config/systemd/user | ||
| .devcontainer | ||
| .forgejo/workflows | ||
| files/data | ||
| screenshots | ||
| static | ||
| templates | ||
| .dockerignore | ||
| .editorconfig | ||
| .env.example | ||
| .gitignore | ||
| api.go | ||
| api_test.go | ||
| auth.go | ||
| auth_test.go | ||
| docker-compose.yml | ||
| Dockerfile | ||
| Dockerfile.alpine | ||
| env.go | ||
| env_test.go | ||
| go.mod | ||
| handlers_admin.go | ||
| handlers_manage.go | ||
| handlers_poll.go | ||
| handlers_static.go | ||
| handlers_test.go | ||
| LICENSE | ||
| mail.go | ||
| mail_test.go | ||
| main.go | ||
| Makefile | ||
| password.go | ||
| password_test.go | ||
| poll.go | ||
| poll_test.go | ||
| proxy.go | ||
| proxy_test.go | ||
| ratelimit.go | ||
| ratelimit_test.go | ||
| README.md | ||
| server.go | ||
| statistik.go | ||
| statistik_test.go | ||
| util.go | ||
Der KonsensOmat
Fork-Hinweis: Dies ist ein Fork von OpenKunde/konsensomat (PHP). Diese Version wurde vollständig nach Go portiert und seitdem eigenständig weiterentwickelt.
KonsensOmat ist ein schlankes Open-Source-Tool zur Entscheidungsfindung nach dem Prinzip des Systemic Consensing (Systemisches Konsensieren, SK): https://en.wikipedia.org/wiki/Systemic_Consensing
Statt Mehrheiten zu zählen, wird der Widerstand gegenüber Vorschlägen bewertet. Die Option mit dem geringsten Gesamtwiderstand gilt als tragfähigste Lösung.
Hinweis: Das Tool ist derzeit nur auf Deutsch verfügbar.
Eigenschaften
- Bewertung nach dem Widerstandsprinzip (SK)
- Anonyme Teilnahme (Pseudonyme möglich)
- Keine Registrierung, keine Datenbank
- Speicherung als einfache Dateien
- Laufzeit pro Umfrage wählbar, automatische Löschung danach (Obergrenze konfigurierbar, Standard: 7 Tage)
- Optionaler Passwortschutz pro Umfrage
- Nur Erstellerin/Ersteller (und optional Admins) können eine Umfrage löschen – nicht jede Person mit dem Link
- Keine externen Dienste oder Tracker
- Light- und Dark-Mode (folgt der Systemeinstellung, manuell umschaltbar)
- Öffentliche Statistik-Seite (
/statistik) mit Aktivitäts-Heatmap der letzten 180 Tage - Rate-Limiting gegen wiederholtes Passwort-Raten (Umfrage- und Admin-Login)
- Sicherheits-Header (CSP, X-Frame-Options, HSTS, …) und CSRF-Schutz für alle Formulare
- Infoseiten zu systemischem Konsensieren, Impressum und Datenschutz
- Optionaler Mail-Versand: Einladungen, Wiederherstellungs-Link beim Erstellen, eigene Stimme später per Mail-Link anpassen (siehe Mail-Funktionen)
Voraussetzungen
- Go 1.26 oder höher
- Schreibrechte für
files/data
Lokale Nutzung
Repository klonen:
git clone https://github.com/rkl110/konsensomat.git
cd konsensomat
Go-Version prüfen:
go version
Lokalen Server starten:
go run .
Im Browser öffnen:
http://localhost:8080
Optional lässt sich die Konfiguration über Umgebungsvariablen anpassen:
KONSENSOMAT_ADDR=:8080 KONSENSOMAT_DATA_DIR=files/data KONSENSOMAT_EXPIRY_DAYS=14 go run .
| Variable | Standard | Bedeutung |
|---|---|---|
KONSENSOMAT_ADDR |
:8080 |
Adresse, auf der der Server lauscht |
KONSENSOMAT_DATA_DIR |
files/data |
Verzeichnis für die Umfrage-JSON-Dateien |
KONSENSOMAT_EXPIRY_DAYS |
7 |
Maximale und vorausgewählte Laufzeit einer Umfrage in Tagen (max. 365) |
KONSENSOMAT_ADMIN_PASSWORD |
(leer = aus) | Admin-Passwort für /admin (siehe Zugriffsschutz & Löschen) |
KONSENSOMAT_TRUSTED_PROXIES |
(leer) | Vertrauenswürdige Reverse-Proxy-IPs/-CIDRs (siehe Hinter einem Reverse-Proxy betreiben) |
KONSENSOMAT_BASE_URL |
(leer) | Basis-URL für Links in ausgehenden Mails, überschreibt die aus der Anfrage abgeleitete (siehe Mail-Funktionen) |
KONSENSOMAT_MAIL_ENABLED |
false |
Hauptschalter für den Mail-Versand (siehe Mail-Funktionen) |
KONSENSOMAT_MAIL_INVITE_ENABLED |
true |
Schalter nur für "Personen per Mail einladen" (siehe Mail-Funktionen) |
KONSENSOMAT_SMTP_HOST |
(leer) | SMTP-Host für den Mail-Versand |
KONSENSOMAT_SMTP_PORT |
587 |
Port des SMTP-Servers |
KONSENSOMAT_SMTP_FROM |
(leer) | Absenderadresse (erforderlich, sobald KONSENSOMAT_SMTP_HOST gesetzt ist) |
KONSENSOMAT_SMTP_FROM_NAME |
KonsensOmat |
Anzeigename im From:-Header |
KONSENSOMAT_SMTP_USERNAME |
(leer) | SMTP-Benutzername, falls der Server Authentifizierung verlangt |
KONSENSOMAT_SMTP_PASSWORD |
(leer) | SMTP-Passwort |
KONSENSOMAT_SMTP_TLS |
starttls |
starttls (opportunistisch) oder implicit (TLS von Anfang an, z.B. Port 465) |
Konfiguration über .env
Statt Umgebungsvariablen von Hand zu setzen, kann eine .env-Datei im Arbeitsverzeichnis abgelegt werden (siehe .env.example):
cp .env.example .env
KONSENSOMAT_ADDR=:8080
KONSENSOMAT_DATA_DIR=files/data
KONSENSOMAT_EXPIRY_DAYS=7
#KONSENSOMAT_ADMIN_PASSWORD=
#KONSENSOMAT_TRUSTED_PROXIES
Echte Umgebungsvariablen haben immer Vorrang vor der .env-Datei. Die .env selbst wird nicht versioniert (siehe .gitignore).
Tests ausführen
go test ./...
Mit Race-Detector (empfohlen, da Rate-Limiter und Statistik-Cache nebenläufig zugreifen):
go test -race ./...
Zugriffsschutz & Löschen
Löschrecht: Beim Erstellen wird automatisch ein Berechtigungs-Cookie gesetzt - nur damit lässt sich die eigene Umfrage löschen. Der bloße Link reicht dafür nicht, auch ohne Passwort. Ein optionales Passwort schützt zusätzlich das Ansehen/Abstimmen für alle anderen, gewährt aber kein Löschrecht. Löschen kann also immer nur die Erstellerin/der Ersteller selbst oder ein Admin - unabhängig vom Passwort.
Admin-Link teilen: Die Umfrageseite zeigt der Erstellerin/dem Ersteller einen Admin-Link mit eingebettetem Token. In einem anderen Browser geöffnet, gilt auch dieser fortan als Erstellerin - praktisch, um die Verwaltung mit Mitorganisatorinnen zu teilen, ohne das Passwort weiterzugeben. Wer den Link kennt, kann löschen und das Passwort ändern.
Nachträglich ändern: Wer verwalten darf, sieht einen einklappbaren Verwaltungsbereich zum Setzen/Ändern des Passworts, Korrigieren von Frage/Vorschlägen und Anpassen der Laufzeit (weiterhin begrenzt auf KONSENSOMAT_EXPIRY_DAYS).
Statistik-Seite: /statistik zeigt ohne Anmeldung nur aggregierte Zahlen (aktive/gültige/ablaufende Umfragen, Teilnehmerzahlen) und eine Aktivitäts-Heatmap der letzten 180 Tage - nie Inhalte oder Links einzelner Umfragen.
Admins: Mit gesetztem KONSENSOMAT_ADMIN_PASSWORD können sich Admins unter /admin anmelden und jede Umfrage einsehen, verwalten und löschen. Angemeldete Admins sehen auf /statistik zusätzlich alle aktiven Umfragen mit Verwaltungslink - gedacht als Moderations-Werkzeug (z.B. für missbräuchliche Umfragen), nicht als Nutzerkonto. Ohne Admin-Passwort komplett deaktiviert.
Laufzeit: Frei wählbar bis KONSENSOMAT_EXPIRY_DAYS (auch Vorauswahl); nach Ablauf wird automatisch gelöscht, unabhängig von Löschrecht oder Passwort.
Es gibt bewusst kein Nutzerkonto und keine Passwort-Wiederherstellung - gehen Cookie und Passwort verloren (und ist kein Admin konfiguriert), hilft nur noch die automatische Löschung nach Ablauf.
Brute-Force-Schutz: Falsche Passwort-Versuche pro Client-IP werden gezählt; nach 10 Fehlversuchen in 5 Minuten wird die IP für den jeweiligen Endpunkt vorübergehend gesperrt (HTTP 429). Erfolgreiche Versuche zählen nicht mit.
API
Die gesamte Anwendung lässt sich auch über eine JSON-API steuern, mit demselben Zugriffsmodell wie die Weboberfläche (siehe Zugriffsschutz & Löschen). Bewusst kein Endpunkt, der alle Umfragen auflistet, da Umfragen nur über ihren Link erreichbar sein sollen.
| Methode | Pfad | Beschreibung |
|---|---|---|
| POST | /api/polls |
Neue Umfrage erstellen |
| GET | /api/polls/{id} |
Umfrage inkl. Ergebnis abrufen |
| POST | /api/polls/{id}/votes |
Abstimmen |
| DELETE | /api/polls/{id} |
Umfrage löschen |
| GET | /api/stats |
Aggregierte Nutzungsstatistik |
Beispiel – Umfrage erstellen (optional mit password und durationDays, Standard/Maximum ist KONSENSOMAT_EXPIRY_DAYS):
curl -X POST http://localhost:8080/api/polls \
-H "Content-Type: application/json" \
-d '{"question":"Wohin geht die Firmenfeier?","options":["Strand","Berge","Stadt"],"password":"geheim","durationDays":3}'
Die Antwort enthält einmalig ein ownerToken – aufheben, denn nur damit (oder als Admin) lässt sich die Umfrage später wieder löschen:
{"id":"a1b2c3","question":"...", "ownerToken":"…", "hasPassword":true, "expiresAt":1234567890, "...": "..."}
Passwortgeschützte Umfragen brauchen bei GET/votes den Header X-Poll-Password: geheim oder ?password=geheim. Für DELETE reicht das Passwort nicht - dafür immer der Owner-Token (X-Owner-Token-Header oder ?owner=), oder ein Admin-Passwort (X-Admin-Password/?adminPassword=) für beliebige Umfragen.
Beispiel – abstimmen (ein Wert je Option, 0 = kein Widerstand … 4 = starker Widerstand; comments optional, wird nur bei Wert 4 angezeigt):
curl -X POST "http://localhost:8080/api/polls/<id>/votes?password=geheim" \
-H "Content-Type: application/json" \
-d '{"name":"Alice","votes":[0,4,2],"comments":["","Zu weit weg",""]}'
Beispiel – löschen mit Owner-Token:
curl -X DELETE "http://localhost:8080/api/polls/<id>?owner=<ownerToken>"
Alle Antworten sind JSON (Fehler als {"error": "..."}), mit Access-Control-Allow-Origin: * für Zugriff aus dem Browser von anderen Origins.
Mail-Funktionen
Mail-Versand ist optional und standardmäßig aus - aktiv erst mit KONSENSOMAT_MAIL_ENABLED=true und konfiguriertem KONSENSOMAT_SMTP_HOST. Der Hauptschalter ist bewusst von den SMTP-Werten getrennt, damit sich die Konfiguration vorbereiten und über einen Wert an-/abschalten lässt. Deaktiviert blenden sich alle zugehörigen Formulare aus, und die Routen lehnen Anfragen ab.
Kein externer Mail-Anbieter nötig: Versand per SMTP, wahlweise gegen ein lokales Mailsystem (meist ohne Auth) oder einen externen Smarthost/Relay (meist mit Auth+TLS) - nur unterschiedliche Ausprägungen derselben Konfiguration.
Wiederherstellungs-Mail beim Erstellen: Eine optionale eigene E-Mail-Adresse beim Erstellen schickt direkt danach Umfrage- und Verwaltungslink zu - ein Sicherheitsnetz, da es kein Nutzerkonto und keine Passwort-Wiederherstellung gibt. Die Adresse wird mit der Umfrage gespeichert (gleiche Lebensdauer), aktuell nur für mögliche künftige Erweiterungen (z.B. eine Ablauf-Erinnerung).
Basis-URL für Mail-Links: Standardmäßig aus Schema/Host der jeweiligen Anfrage abgeleitet; hinter Reverse-Proxy/Load-Balancer ggf. nicht korrekt - mit KONSENSOMAT_BASE_URL (z.B. https://umfrage.example.org) fest erzwingbar.
Einladung per Mail: Owner/Admin tragen im Verwaltungsbereich bis zu 20 Adressen (plus optionale Nachricht) ein; jede bekommt den Umfrage-Link, ohne dass die Adressen gespeichert werden. Da hierüber - anders als bei den beiden anderen Mail-Funktionen - beliebige fremde Adressen angeschrieben werden können, lässt sich das separat per KONSENSOMAT_MAIL_INVITE_ENABLED=false abschalten (Formular verschwindet, Route antwortet mit 503).
Eigene Stimme später per Mail-Link anpassen: Owner/Admin können erlauben, dass Abstimmende beim Abstimmen eine eigene Adresse angeben; sie bekommen dann einen individuellen Link, um ausschließlich ihre eigene Auswahl später zu ändern (kein Passwort/Owner-Token nötig). Adresse und Einstellung werden mit der Umfrage gespeichert; ohne Mail-Konfiguration bleibt das Feld unsichtbar bzw. wird eine trotzdem mitgeschickte Adresse ignoriert.
Alle drei Formulare sind gegen Missbrauch abgesichert: CSRF-Schutz, Beschränkung auf Verwaltungsberechtigte, ein eigenes Rate-Limit pro Client-IP für versendete Mails sowie - für die Bearbeitungslinks - konstante-Zeit-Tokenvergleich und Rate-Limiting falscher Tokens.
Deployment
Statisches Binary bauen (Templates und Assets werden per go:embed eingebettet):
go build -o konsensomat .
./konsensomat
Schreibrechte für files/data vergeben (bzw. KONSENSOMAT_DATA_DIR auf ein beschreibbares Verzeichnis setzen).
Logs
Loggt nur nach stdout (Betriebsmeldungen, eine Zeile pro Request mit Client-IP, Methode, Pfad, Status, Dauer) und stderr (Fehler), kein Log-File - trennbar/einsammelbar z.B. via docker compose logs -f.
Hinter einem Reverse-Proxy betreiben
KonsensOmat terminiert kein eigenes TLS und läuft hinter einem Reverse-Proxy (nginx, Caddy, Traefik, …), der HTTPS entgegennimmt und per HTTP mit den Headern X-Forwarded-Proto/X-Forwarded-For weiterleitet.
X-Forwarded-Proto: https wird automatisch erkannt (steuert u.a. das Secure-Attribut der Cookies) - hier ist keine Konfiguration nötig.
Für X-Forwarded-For (Client-IP für Rate-Limiting/Logs) muss der Proxy explizit als vertrauenswürdig eingetragen werden, per KONSENSOMAT_TRUSTED_PROXIES:
KONSENSOMAT_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8
Kommagetrennte IPs/CIDRs (IPv4/IPv6). Ohne Angabe (Standard) wird X-Forwarded-For ignoriert und immer die verbindende IP verwendet - der sichere Default, da der Header sonst von jedem frei setzbar wäre. Erst bei nachweislich vertrauenswürdigem Proxy wird die von ihm gesetzte IP übernommen (bei mehreren verketteten Proxies von rechts nach links die erste nicht selbst vertrauenswürdige Adresse).
Über Docker
cp .env.example .env # falls noch nicht geschehen
make docker-build # baut das Linux-Binary und das Docker-Image (konsensomat:latest)
docker compose up
docker-compose.yml bindet ./files/data ein (Umfragen überleben Container-Neustarts) und lädt die Konfiguration aus .env.
Das Standard-Dockerfile baut auf scratch (kein Betriebssystem, keine Shell, läuft nicht als root). Für einen HEALTHCHECK (z.B. Orchestrierung) die etwas größere Alpine-Variante nutzen:
make docker-build-alpine
Weitere make-Ziele (make ohne Argument bzw. ein Blick ins Makefile zeigen alle): build-windows, build-rpi64, build-rpi32, build-mac (macOS/Apple Silicon), test, run (= go run .) und clean.
Mit Podman (rootless) betreiben
Funktioniert unverändert auch mit podman compose up/podman-compose up - zwei rootless-spezifische Punkte müssen dafür stimmen:
-
SELinux: Das
:Zin dervolumes-Zeile relabelt das Verzeichnis automatisch für den Container - auf Nicht-SELinux-Hosts ein No-op. Bereits eingecheckt, nichts weiter zu tun. -
UID-Eigentümerschaft: Der Container läuft mit fester UID 65532 (nonroot), die rootless Podman auf eine eigene
/etc/subuid-UID abbildet - nicht deinen Host-User. Das gebindete./files/datagehört daher zunächst nicht dem Container. Einmalig beheben (und erneut nach Neuanlage vonfiles/data):podman unshare chown -R 65532:65532 files/dataNur innerhalb des rootless User-Namespace lässt sich auf diese UID chownen, daher der Umweg über
podman unsharestatt eines gewöhnlichenchownvom Host aus.
Neustart-sicher mit systemd (Podman, rootless)
podman compose up -d startet den Container nicht automatisch nach einem Reboot. Dafür liegt im Release-Archiv (Linux) unter .config/systemd/user/podman-compose@.service eine systemd-User-Unit als Vorlage (@-Template, %i = Instanzname).
Einrichtung (angenommen, das Deployment liegt in ~/konsensomat, also WorkingDirectory=%h/konsensomat):
mkdir -p ~/.config/systemd/user
cp .config/systemd/user/podman-compose@.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now podman-compose@konsensomat.service
Damit der Dienst auch ohne aktive Login-Session startet (z.B. direkt nach dem Booten), zusätzlich Lingering für den User aktivieren:
loginctl enable-linger $(whoami)
Der Instanzname nach dem @ (hier konsensomat) muss dem Verzeichnisnamen unter $HOME entsprechen, in dem docker-compose.yml und .env liegen - %i wird von systemd 1:1 in WorkingDirectory=%h/%i eingesetzt.
Screenshots
Abb. 1: Desktop-Ansicht
Abb. 2: Mobile-Ansicht
Lizenz
GNU Affero General Public License v3 (AGPLv3).
Siehe Datei LICENSE.

