From 490f334f8a862ca564d86970f9b70b474ddf8eb1 Mon Sep 17 00:00:00 2001 From: DerGrumpf Date: Thu, 21 May 2026 14:41:52 +0200 Subject: [PATCH] Added Website for amateurs to matrix --- nixos/roles/matrix/app.js | 121 ++++++ nixos/roles/matrix/index.html | 629 ++++++++++++++++++++++++++++++++ nixos/roles/matrix/maubot.nix | 2 + nixos/roles/matrix/register.php | 159 ++++++++ nixos/roles/matrix/style.css | 526 ++++++++++++++++++++++++++ nixos/roles/matrix/synapse.nix | 46 ++- 6 files changed, 1481 insertions(+), 2 deletions(-) create mode 100644 nixos/roles/matrix/app.js create mode 100644 nixos/roles/matrix/index.html create mode 100644 nixos/roles/matrix/register.php create mode 100644 nixos/roles/matrix/style.css diff --git a/nixos/roles/matrix/app.js b/nixos/roles/matrix/app.js new file mode 100644 index 0000000..3a3ddb0 --- /dev/null +++ b/nixos/roles/matrix/app.js @@ -0,0 +1,121 @@ +async function submitRegistration() { + const usernameInput = document.getElementById("reg-username"); + const emailInput = document.getElementById("reg-email"); + const btn = document.getElementById("reg-btn"); + const status = document.getElementById("reg-status"); + + const username = usernameInput.value.trim(); + const email = emailInput.value.trim(); + + // Validierung + status.className = "reg-status"; + status.style.display = "none"; + + if (!username || !email) { + status.className = "reg-status error"; + status.textContent = "⚠️ Bitte Benutzernamen und E-Mail-Adresse eingeben."; + return; + } + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + status.className = "reg-status error"; + status.textContent = "⚠️ Bitte eine gültige E-Mail-Adresse eingeben."; + return; + } + + btn.disabled = true; + btn.textContent = "Wird übertragen..."; + + try { + const res = await fetch("register.php", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + username, + email, + website: document.getElementById("reg-website").value, + }), + }); + + const data = await res.json().catch(() => ({})); + + if (res.ok && data.success) { + status.className = "reg-status success"; + status.innerHTML = + "✅ Anfrage erfolgreich gesendet!
" + + "Die Administration wurde benachrichtigt. Du erhältst deine Login-Daten in Kürze an die angegebene E-Mail-Adresse."; + usernameInput.value = ""; + emailInput.value = ""; + } else { + throw new Error( + data.error || + (res.status === 429 + ? "Zu viele Anfragen. Bitte warte einige Minuten." + : `HTTP ${res.status}`), + ); + } + } catch (e) { + status.className = "reg-status error"; + status.textContent = `❌ Übertragung fehlgeschlagen: ${e.message}`; + } finally { + btn.disabled = false; + btn.textContent = "Zugang anfordern"; + } +} + +// Enter-Taste in Feldern +document.addEventListener("DOMContentLoaded", () => { + ["reg-username", "reg-email"].forEach((id) => { + document.getElementById(id).addEventListener("keydown", (e) => { + if (e.key === "Enter") submitRegistration(); + }); + }); +}); + +const canvas = document.getElementById("matrix-bg"); +const ctx = canvas.getContext("2d"); + +function resizeCanvas() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; +} +resizeCanvas(); +window.addEventListener("resize", resizeCanvas); + +const katakana = + "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const alphabet = katakana.split(""); + +const fontSize = 16; +let columns = canvas.width / fontSize; + +const rainDrops = []; +for (let x = 0; x < columns; x++) { + rainDrops[x] = 1; +} + +window.addEventListener("resize", () => { + columns = canvas.width / fontSize; + for (let x = rainDrops.length; x < columns; x++) { + rainDrops[x] = 1; + } +}); + +function drawMatrixRain() { + ctx.fillStyle = "rgba(0, 0, 0, 0.05)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.fillStyle = "#0f0"; + ctx.font = fontSize + "px monospace"; + + for (let i = 0; i < rainDrops.length; i++) { + const text = alphabet[Math.floor(Math.random() * alphabet.length)]; + ctx.fillText(text, i * fontSize, rainDrops[i] * fontSize); + + if (rainDrops[i] * fontSize > canvas.height && Math.random() > 0.975) { + rainDrops[i] = 0; + } + rainDrops[i]++; + } +} + +setInterval(drawMatrixRain, 30); diff --git a/nixos/roles/matrix/index.html b/nixos/roles/matrix/index.html new file mode 100644 index 0000000..0d2c996 --- /dev/null +++ b/nixos/roles/matrix/index.html @@ -0,0 +1,629 @@ + + + + + + Matrix Guide - cyperpunk.de + + + + + + + + + + + + + + + + + + + + +
+ +
+

Matrix Guide

+

Systemstatus: Aktiv auf Host cyperpunk.de

+

Grid-Operator: @dergrumpf:cyperpunk.de

+
+ + +
+

+ 1. Identitäts-Allokation (Registrierung) +

+

+ Zugangsdaten für diesen Server werden exklusiv durch die + Administration autorisiert. Nutze das untenstehende Interface, um eine + Verbindung anzufordern. Die Übertragung erfolgt direkt in unseren + gesicherten internen Koordinationskanal. +

+ + +
+ + + + + + + + + + + + +
+
+
+ + +
+

+ 2. Protokoll für den initialen Verbindungsaufbau +

+ + + +

Schritt-für-Schritt-Protokoll:

+
    +
  1. + Wähle ein Zugangs-Terminal (Client) aus dem folgenden Abschnitt. +
  2. +
  3. + Der Zugriff ist entweder über klassische + User-Credentials oder über das + Single-Sign-On-Protokoll (SSO) möglich. +
  4. +
  5. + Bei der Nutzung von SSO wirst du automatisch zum zentralen + Identitäts-Node unter + auth.cyperpunk.de + (powered by Kanidm) umgeleitet. +
  6. +
  7. + Nach erfolgreicher Authentifizierung wird deine Session für das + Matrix-Grid freigeschaltet. +
  8. +
+
+ + +
+

3. Zugangs-Terminals initialisieren

+

+ Kopple dein System über eine unserer drei gehosteten + Web-Schnittstellen. Sobald dein Login-Protokoll abgeschlossen ist, + findest du den übergeordneten Hauptbereich (Community Space) im Grid + unter der Adresse #cyperpunk:cyperpunk.de. +

+ + + +

Welches Programm soll ich wählen?

+
+
+

Element

+

+ Der mächtige Allrounder. Sieht klassisch aus und + bietet absolut jede Funktion, die Matrix kann. Perfekt, wenn du + Räume selbst verwalten willst. + Unterstützt Sprach- und Videoanrufe direkt im + Browser. +

+
+
+

Cinny

+

+ Der moderne Chat. Ist extrem schnell, + übersichtlich und orientiert sich vom Design an Discord oder + Slack. Ideal, wenn du einfach nur bequem schreiben willst. + Unterstützt Sprach- und Videoanrufe direkt im + Browser. +

+
+
+

FluffyChat

+

+ Der einfache Messenger. Bunt, super unkompliziert + und perfekt für Einsteiger, die den Look von WhatsApp oder Signal + bevorzugen. + Achtung: + Diese Web-Version hat keinen Sprach-Chat. +

+
+
+
+ + +
+

4. Mobile App-Konfiguration

+

+ Möchtest du von unterwegs aus auf das Grid zugreifen? Lade dir einfach + eine passende App für dein Smartphone herunter: +

+ +
+ +
+
+ FluffyChat +

+ Bunt, einsteigerfreundlich und ideal für das Smartphone. +

+
+ +
+ + +
+
+ Element X +

+ Die App der nächsten Generation: Extrem schnell, modern und + sicher. +

+
+ +
+
+ +

+ Wichtig beim ersten App-Start: +

+
    +
  • Überspringe die automatische Registrierung in der App.
  • +
  • + Suche nach der Option "Heimserver ändern" (oder + Homeserver wechseln). +
  • +
  • + Trage dort manuell unsere Server-Adresse ein: + cyperpunk.de +
  • +
  • + Logge dich erst danach wie gewohnt mit deinen Zugangsdaten oder per + SSO ein. +
  • +
+
+ + +
+

+ 5. Sprachkanäle & Speicherplatz limits +

+

+ Unser Server stellt Kanäle bereit, mit denen du direkt über das + Internet mit anderen Mitgliedern sprechen oder Dateien austauschen + kannst. +

+ +
+
+

Anrufe in Gruppen

+

+ Wenn du mit mehreren Personen in einem Raum telefonierst, sorgt + unser Server im Hintergrund automatisch für eine flüssige, stabile + Audio- und Bildübertragung. +

+
+
+

Direktanrufe (1:1)

+

+ Telefonate mit nur einer Person werden sicher und direkt + aufgebaut. Ein integrierter Hilfsdienst sorgt dafür, dass die + Verbindung auch durch Firewalls hinweg klappt. +

+
+ +
+

+ Dateigröße & faires Teilen +

+

+ Das Hochladen von Bildern, Videos oder Dokumenten ist auf maximal + 50 MB pro Datei begrenzt. Da unser + Festplattenplatz auf dem Server nicht unendlich ist, gehe bitte + sparsam mit großen Dateien um. +

+
+
+
+ + +
+

+ 6. Server-Strukturen: Chats und Bereiche +

+

+ Damit alles ordentlich bleibt und du dich gut zurechtfindest, ist + Matrix logisch aufgeteilt. Es gibt im Grunde zwei Ebenen, um Gespräche + zu sortieren: +

+
    +
  1. + Spaces (Bereiche / Ordner): Das ist die + übergeordnete Klammer – wie ein kompletter Server bei Discord. Ein + Space sammelt verschiedene Chats an einem Ort, damit es + übersichtlich bleibt. +
  2. +
  3. + Rooms (Räume / Chats): Das sind die eigentlichen + Text- oder Sprachkanäle innerhalb eines Bereichs. Hier wird + geschrieben, telefoniert und Material geteilt. +
  4. +
+ +

Tipps für Admins und Moderatoren:

+
    +
  • + Wenn du eine neue Gruppe gründest, erstelle am besten + zuerst einen Space (Bereich). Danach packst du die + einzelnen Chats geordnet dort hinein. +
  • +
  • + Verwende klare, einfache Namen für deine Räume. Das macht es für + alle Nutzer leichter, die Übersicht in ihrer Seitenleiste zu + behalten. +
  • +
+
+ + +
+

7. Discord-zu-Matrix Referenz-Codex

+

+ Verwende diese Referenztabelle, um gewohnte Discord-Strukturen auf den + offenen Matrix-Standard zu mappen: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Discord-KonstruktMatrix-SpezifikationFunktion im Netz
Server / GildeSpace (Bereich) + Ein virtueller Container, der Räume und Entitäten unter einem + Thema bündelt. +
Text- / SprachkanalRoom (Raum) + Der eigentliche Endpunkt für Textnachrichten, Bilddaten oder + Telefonie. +
Direktnachricht (DM)Direkt-Chat + Ein kryptografisch isolierter 1:1-Kanal zwischen zwei Accounts. +
Rollen & RechtePower Levels + Ein numerischer Berechtigungswert (0 bis 100) zur Steuerung von + Moderations-Rechten. +
Nitro EmojisEmote-Packs + Serverweite Sticker- und Emoji-Bibliotheken – dezentral und + kostenfrei. +
+
+ + +
+

8. Aktive Automatisierungs-Bots (Maubot)

+

+ Das Maubot-Framework erlaubt das Einbinden von + automatisierten Skripten und Funktionen in das Raum-Netzwerk. Unsere + Kerninstanz operiert unter der ID: + @maubot:cyperpunk.de. +

+

+ Bedienung: Du kannst die Entität + in jeden beliebigen Raum einladen, um die geladenen + Module per Textbefehl zu aktivieren. Aktive Subroutinen: +

+ +
+ +
+

Urban Dictionary

+

+ Analysiert Slang-Begriffe, Definitionen und sprachliche Variablen + direkt im Raum-Feed.
Dokumentation +

+
+ +
+

RSS News-Feeds

+

+ Abonniert externe Informations-Feeds und speist Updates + vollautomatisch in den Chat-Stream ein.
Dokumentation +

+
+ +
+

Erinnerungs-Engine

+

+ Ermöglicht das Setzen von Timern, Cron-Triggern und + zeitgesteuerten Alarmen im Raum.
Dokumentation +

+
+ +
+

Wetter-Array

+

+ Fragt aktuelle Klimadaten und meteorologische Berichte für globale + Koordinaten ab.
Dokumentation +

+
+
+
+ + +
+

9. Discord Bridge Link

+

+ Unsere Brücken-Module spiegeln Daten und Signalkanal-Nachrichten + nahtlos zwischen Matrix und externen Discord-Gilden. +

+

+ Ident-ID der Discord-Bridge: + @discordbot:cyperpunk.de +

+ +

Befehlssatz:

+
    +
  • + Öffne eine Direktverbindung (DM) mit dem Bridge-Bot, um Befehle + direkt einzuspeisen. +
  • +
  • + Im privaten Kommunikationskanal versteht der Bot Befehle direkt über + das Text-Kommando help. +
  • +
  • + Innerhalb von geteilten Räumen muss das Präfix vorangestellt werden: + !discord help. +
  • +
+
+ +
+

FAQ – Häufige Fragen

+ +

Was ist Matrix?

+

+ Matrix ist ein offenes, dezentrales Kommunikationsprotokoll – ähnlich + wie E-Mail, aber für Echtzeit-Chat und Anrufe. Jeder kann seinen + eigenen Server betreiben, und Server können miteinander kommunizieren. +

+ +

Warum kein Discord?

+

+ Discord ist ein zentralisierter Dienst, der Nutzerdaten speichert und + auswerten kann. Matrix ist Open Source, selbst gehostet und + Ende-zu-Ende-verschlüsselt – du behältst die Kontrolle über deine + Daten. +

+ +

Kann ich meinen bestehenden Matrix-Account verwenden?

+

+ Ja. Du kannst von jedem Matrix-Server aus unseren Räumen beitreten – + einfach die Adresse @deinname:deinserver.de verwenden und + den Raum #cyperpunk:cyperpunk.de suchen. +

+ +

Wie bekomme ich einen Account auf diesem Server?

+

+ Fülle das Registrierungsformular oben aus. Die Administration prüft + deine Anfrage und schickt dir die Zugangsdaten per E-Mail. +

+ +

Sind meine Nachrichten verschlüsselt?

+

+ Ja. Dieser Server erzwingt Ende-zu-Ende-Verschlüsselung (E2EE) in + allen privaten Chats. Stelle sicher, dass du deinen + Sicherheitsschlüssel beim ersten Login sicherst. +

+ +

Was kostet das?

+

+ Nichts. Der Server wird privat betrieben und ist kostenlos nutzbar. +

+
+
+ + + + + + diff --git a/nixos/roles/matrix/maubot.nix b/nixos/roles/matrix/maubot.nix index 14f598b..00f980a 100644 --- a/nixos/roles/matrix/maubot.nix +++ b/nixos/roles/matrix/maubot.nix @@ -12,6 +12,8 @@ rss reminder urban + llm + wolframalpha ]; settings = { database = "postgresql:///maubot?host=/run/postgresql"; diff --git a/nixos/roles/matrix/register.php b/nixos/roles/matrix/register.php new file mode 100644 index 0000000..449cf54 --- /dev/null +++ b/nixos/roles/matrix/register.php @@ -0,0 +1,159 @@ + 'Method not allowed'])); +} + +header('Content-Type: application/json'); + +// --- Konfiguration --- +define('MATRIX_HOMESERVER', 'https://cyperpunk.de'); +define('MATRIX_ACCESS_TOKEN', 'syt_cmVnaXN0cmF0aW9uLWJvdA_tWjAfJOYDoJSuoCWoYIQ_4YuoMw'); +define('MATRIX_ROOM_ID', '!xBizjYatXLfpCorAXt:cyperpunk.de'); + +// Rate-Limit: max. Anfragen pro Zeitfenster +define('RATE_LIMIT_MAX', 5); // Anfragen +define('RATE_LIMIT_WINDOW', 300); // Sekunden (5 Minuten) +define('RATE_LIMIT_DIR', sys_get_temp_dir() . '/matrix_reg_rl'); + +// --- IP ermitteln (auch hinter Proxies) --- +function getClientIP(): string { + $headers = ['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR']; + foreach ($headers as $header) { + if (!empty($_SERVER[$header])) { + $ip = explode(',', $_SERVER[$header])[0]; + $ip = trim($ip); + if (filter_var($ip, FILTER_VALIDATE_IP)) { + return $ip; + } + } + } + return 'unknown'; +} + +// --- Rate-Limiting (dateibasiert) --- +function checkRateLimit(string $ip): bool { + $dir = RATE_LIMIT_DIR; + if (!is_dir($dir)) { + mkdir($dir, 0700, true); + } + + $file = $dir . '/' . hash('sha256', $ip) . '.json'; + $now = time(); + $data = ['count' => 0, 'window_start' => $now]; + + if (file_exists($file)) { + $data = json_decode(file_get_contents($file), true) ?? $data; + // Zeitfenster abgelaufen → zurücksetzen + if ($now - $data['window_start'] > RATE_LIMIT_WINDOW) { + $data = ['count' => 0, 'window_start' => $now]; + } + } + + if ($data['count'] >= RATE_LIMIT_MAX) { + return false; + } + + $data['count']++; + file_put_contents($file, json_encode($data), LOCK_EX); + return true; +} + +// --- Honeypot prüfen --- +$input = json_decode(file_get_contents('php://input'), true); + +if (!empty($input['website'])) { + // Bot hat das versteckte Feld ausgefüllt – still ablehnen + http_response_code(200); + exit(json_encode(['success' => true])); +} + +// --- Rate-Limit prüfen --- +$ip = getClientIP(); +if (!checkRateLimit($ip)) { + http_response_code(429); + exit(json_encode(['error' => 'Zu viele Anfragen. Bitte warte einige Minuten.'])); +} + +// --- Eingabe validieren --- +$username = isset($input['username']) ? trim($input['username']) : ''; +$email = isset($input['email']) ? trim($input['email']) : ''; + +if (empty($username) || empty($email)) { + http_response_code(400); + exit(json_encode(['error' => 'Benutzername und E-Mail sind erforderlich.'])); +} + +if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + http_response_code(400); + exit(json_encode(['error' => 'Ungültige E-Mail-Adresse.'])); +} + +// Eingaben bereinigen +$username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8'); +$email = htmlspecialchars($email, ENT_QUOTES, 'UTF-8'); + +// --- Nachricht zusammenstellen --- +$timestamp = date('d.m.Y H:i:s T'); +$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unbekannt'; + +$message = "🔐 Registration Request\n\n" + . "Username: {$username}\n" + . "E-Mail: {$email}\n" + . "IP: {$ip}\n" + . "Zeitstempel: {$timestamp}\n" + . "User-Agent: {$userAgent}"; + +$formattedMessage = "🔐 Registration Request

" + . "Username: {$username}
" + . "E-Mail: {$email}
" + . "IP: {$ip}
" + . "Zeitstempel: {$timestamp}
" + . "User-Agent: {$userAgent}"; + +// --- An Matrix senden --- +$txnId = 'reg_' . time() . '_' . bin2hex(random_bytes(8)); +$url = MATRIX_HOMESERVER + . '/_matrix/client/v3/rooms/' + . urlencode(MATRIX_ROOM_ID) + . '/send/m.room.message/' + . $txnId; + +$payload = json_encode([ + 'msgtype' => 'm.text', + 'body' => $message, + 'format' => 'org.matrix.custom.html', + 'formatted_body' => $formattedMessage, +]); + +$ch = curl_init($url); +curl_setopt_array($ch, [ + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_POSTFIELDS => $payload, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ' . MATRIX_ACCESS_TOKEN, + 'Content-Type: application/json', + ], + CURLOPT_TIMEOUT => 10, +]); + +$response = curl_exec($ch); +$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); +$curlError = curl_error($ch); +curl_close($ch); + +if ($curlError) { + http_response_code(502); + exit(json_encode(['error' => 'Verbindung zum Matrix-Server fehlgeschlagen.'])); +} + +if ($httpCode >= 200 && $httpCode < 300) { + http_response_code(200); + exit(json_encode(['success' => true])); +} else { + $matrixError = json_decode($response, true); + http_response_code(502); + exit(json_encode(['error' => 'Matrix-Fehler: ' . ($matrixError['error'] ?? "HTTP {$httpCode}")])); +} diff --git a/nixos/roles/matrix/style.css b/nixos/roles/matrix/style.css new file mode 100644 index 0000000..c60f86e --- /dev/null +++ b/nixos/roles/matrix/style.css @@ -0,0 +1,526 @@ +/* Basis-Styles & Reset */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: #000; + color: #00ff41; /* Klassisches Matrix-Grün */ + font-family: "Courier New", Courier, monospace; + overflow-x: hidden; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* Skip-to-content Link für Tastaturnavigation */ +.skip-link { + position: absolute; + top: -100%; + left: 0; + background: #00ff41; + color: #000; + padding: 10px 20px; + font-weight: bold; + text-decoration: none; + z-index: 9999; + border-radius: 0 0 4px 0; +} + +.skip-link:focus { + top: 0; +} + +/* Matrix-Regen Canvas Hintergrund */ +#matrix-bg { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + opacity: 0.12; /* Gedimmter Hintergrund für optimale Lesbarkeit */ +} + +/* Hauptcontainer */ +.container { + max-width: 950px; + margin: 0 auto; + padding: 40px 20px; + position: relative; + z-index: 1; + flex: 1; +} + +/* Header / Titel */ +header { + text-align: center; + margin-bottom: 50px; +} + +h1 { + font-size: 3rem; + text-transform: uppercase; + letter-spacing: 4px; + color: #fff; + text-shadow: + 0 0 10px #00ff41, + 0 0 20px #00ff41, + 0 0 30px #00ff41; + margin-bottom: 10px; + animation: glitch 1s linear infinite alternate; +} + +.subtitle { + font-size: 1.1rem; + color: #00ff41; + text-transform: uppercase; + letter-spacing: 2px; + color: #00e63a; + margin-bottom: 15px; +} + +.admin-tag { + display: inline-block; + background: rgba(0, 255, 65, 0.1); + border: 1px dashed #00ff41; + padding: 6px 12px; + font-size: 0.9rem; + color: #fff; + border-radius: 4px; +} + +/* Sektionen-Styling */ +section { + margin-bottom: 60px; + background: rgba(0, 5, 0, 0.85); + border: 1px solid #00ff41; + border-radius: 6px; + padding: 30px; + box-shadow: 0 0 15px rgba(0, 255, 65, 0.1); +} + +h2 { + font-size: 1.6rem; + border-bottom: 2px solid #00ff41; + padding-bottom: 8px; + margin-bottom: 25px; + text-transform: uppercase; + letter-spacing: 2px; + color: #fff; + text-shadow: 0 0 5px #00ff41; +} + +h3 { + font-size: 1.2rem; + color: #fff; + margin-top: 25px; + margin-bottom: 10px; + text-transform: uppercase; +} + +p { + font-size: 0.95rem; + line-height: 1.6; + color: #a3ffa3; + margin-bottom: 15px; +} + +ol, +ul { + margin-left: 20px; + margin-bottom: 15px; + color: #a3ffa3; +} + +li { + margin-bottom: 8px; + line-height: 1.5; +} + +strong { + color: #fff; + text-shadow: 0 0 2px #00ff41; +} + +a { + color: #fff; + text-decoration: underline; +} +a:hover { + color: #00ff41; +} + +/* --- Linktree-Clients & App-Links --- */ +.linktree-container { + max-width: 600px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + margin-bottom: 30px; +} + +.linktree-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + width: 100%; + padding: 16px 20px; + background: transparent; + border: 2px solid #00ff41; + color: #00ff41; + text-decoration: none; + font-size: 1.1rem; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 2px; + border-radius: 4px; + transition: all 0.3s ease; + box-shadow: inset 0 0 0 0 rgba(0, 255, 65, 0.2); +} + +.linktree-btn img { + height: 24px; + width: auto; + object-fit: contain; + filter: invert(53%) sepia(93%) saturate(1353%) hue-rotate(87deg) + brightness(119%) contrast(119%); + transition: all 0.3s ease; +} + +.linktree-btn:hover { + background: #00ff41; + color: #000; + box-shadow: 0 0 15px #00ff41; + cursor: pointer; +} + +.linktree-btn:hover img { + filter: invert(0%) brightness(0%); +} + +/* Mobile Download Bereich */ +.app-download-box { + background: rgba(0, 20, 0, 0.4); + border: 1px dashed #00ff41; + border-radius: 4px; + padding: 20px; + margin-top: 20px; +} + +.app-row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 15px; + padding: 15px 0; +} + +.app-row:not(:last-child) { + border-bottom: 1px solid rgba(0, 255, 65, 0.2); +} + +.app-info { + flex: 1; + min-width: 200px; +} + +.app-name { + font-weight: bold; + color: #fff; + font-size: 1.1rem; +} + +.app-buttons { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.app-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border: 1px solid #00ff41; + color: #00ff41; + text-decoration: none; + font-size: 0.85rem; + border-radius: 4px; + text-transform: uppercase; + transition: all 0.2s ease; +} + +.app-btn:hover { + background: rgba(0, 255, 65, 0.2); + color: #fff; + box-shadow: 0 0 8px rgba(0, 255, 65, 0.5); +} + +/* --- Karten-Raster (Cards Grid) --- */ +.cards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; + margin-top: 15px; +} + +.card { + background: rgba(0, 0, 0, 0.9); + border: 1px solid #00ff41; + border-radius: 4px; + padding: 20px; + transition: all 0.3s ease; +} + +.card:hover { + transform: translateY(-3px); + box-shadow: 0 0 15px rgba(0, 255, 65, 0.4); + border-color: #fff; +} + +.card.full-width-card { + grid-column: 1 / -1; +} + +.card-title { + font-size: 1.15rem; + color: #fff; + margin-bottom: 10px; + text-transform: uppercase; + letter-spacing: 1px; + border-bottom: 1px dashed #00ff41; + padding-bottom: 5px; +} + +.card-text { + font-size: 0.9rem; + line-height: 1.5; + color: #a3ffa3; + margin-bottom: 0; +} + +/* --- Registrierungs-Widget --- */ +.reg-form { + padding: 10px 0; + margin-top: 15px; +} + +.reg-form label { + display: block; + font-size: 0.85rem; + color: #00ff41; + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 6px; +} + +.reg-form input { + width: 100%; + background: rgba(0, 0, 0, 0.8); + border: 1px solid #00ff41; + color: #fff; + font-family: "Courier New", Courier, monospace; + font-size: 1rem; + padding: 10px 14px; + border-radius: 3px; + margin-bottom: 20px; + outline: none; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.reg-form input:focus { + border-color: #fff; + box-shadow: 0 0 8px rgba(0, 255, 65, 0.4); +} + +.reg-submit { + width: 100%; + padding: 14px; + background: transparent; + border: 2px solid #00ff41; + color: #00ff41; + font-family: "Courier New", Courier, monospace; + font-size: 1rem; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 2px; + border-radius: 3px; + cursor: pointer; + transition: all 0.3s ease; +} + +.reg-submit:hover:not(:disabled) { + background: #00ff41; + color: #000; + box-shadow: 0 0 15px #00ff41; +} + +.reg-submit:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.reg-status { + margin-top: 20px; + padding: 15px; + border-radius: 3px; + font-size: 0.9rem; + display: none; +} + +.reg-status.success { + display: block; + background: rgba(0, 255, 65, 0.1); + border: 1px solid #00ff41; + color: #a3ffa3; +} + +.reg-status.error { + display: block; + background: rgba(255, 0, 0, 0.1); + border: 1px solid #ff0000; + color: #ff9999; +} + +/* --- Warnungen / Alerts --- */ +.matrix-alert { + background: rgba(255, 0, 0, 0.1); + border: 1px solid #ff0000; + color: #ff9999; + padding: 20px; + border-radius: 4px; + margin-bottom: 25px; +} +.matrix-alert h3 { + color: #ff3333; + margin-bottom: 8px; + margin-top: 0; +} + +/* --- Codex Tabellen-Styling --- */ +.codex-table { + width: 100%; + border-collapse: collapse; + margin-top: 15px; + font-size: 0.95rem; +} + +.codex-table th, +.codex-table td { + border: 1px solid #00ff41; + padding: 12px; + text-align: left; +} + +.codex-table th { + background: rgba(0, 255, 65, 0.15); + color: #fff; + text-transform: uppercase; +} + +.codex-table tr:nth-child(even) { + background: rgba(0, 255, 65, 0.03); +} + +/* --- Footer --- */ +footer { + text-align: center; + padding: 20px; + background: rgba(0, 5, 0, 0.9); + border-top: 1px dashed #00ff41; + font-size: 0.8rem; + color: #00ff41; + color: rgba(0, 255, 65, 0.7); + position: relative; + z-index: 1; + letter-spacing: 1px; +} + +/* Fokus-Styles für Tastaturnavigation */ +a:focus-visible, +button:focus-visible, +input:focus-visible { + outline: 2px solid #fff; + outline-offset: 3px; + box-shadow: 0 0 0 4px rgba(0, 255, 65, 0.4); +} + +/* Opacity-fix: sicherstellen dass gedimmter Text ausreichend Kontrast hat */ +.app-info p { + opacity: 1; + color: #a3ffa3; +} + +/* Glitch Animations-Effekt */ +@keyframes glitch { + 0% { + text-shadow: + 2px 2px 0px #ff0000, + -2px -2px 0px #0000ff; + } + 50% { + text-shadow: + -2px 2px 0px #00ff41, + 2px -2px 0px #ff0000; + } + 100% { + text-shadow: + 2px -2px 0px #0000ff, + -2px 2px 0px #00ff41; + } +} + +/* Animationen deaktivieren für Nutzer die das bevorzugen */ +@media (prefers-reduced-motion: reduce) { + h1 { + animation: none; + text-shadow: 0 0 10px #00ff41; + } + #matrix-bg { + display: none; + } + *, + *::before, + *::after { + transition: none !important; + } +} + +@media (max-width: 600px) { + h1 { + font-size: 2.2rem; + } + h2 { + font-size: 1.3rem; + } + section { + padding: 15px; + } + .codex-table th, + .codex-table td { + padding: 8px; + font-size: 0.85rem; + } + .app-row { + flex-direction: column; + align-items: flex-start; + } + .app-buttons { + width: 100%; + } + .app-btn { + flex: 1; + justify-content: center; + } +} diff --git a/nixos/roles/matrix/synapse.nix b/nixos/roles/matrix/synapse.nix index e33019f..598e455 100644 --- a/nixos/roles/matrix/synapse.nix +++ b/nixos/roles/matrix/synapse.nix @@ -26,6 +26,11 @@ let restrictBaseUrl = [ "https://matrix.cyperpunk.de" ]; loginFlows = [ "password" ]; }; + + matrixIndexHtml = pkgs.writeText "matrix-index.html" (builtins.readFile ./index.html); + matrixRegisterPhp = pkgs.writeText "matrix-register.php" (builtins.readFile ./register.php); + matrixStyleCss = pkgs.writeText "matrix-style.css" (builtins.readFile ./style.css); + matrixAppJs = pkgs.writeText "matrix-app.js" (builtins.readFile ./app.js); in { sops.secrets = { @@ -44,6 +49,29 @@ in }; }; + systemd.tmpfiles.rules = [ + "d /var/www/matrix 0755 nginx nginx -" + "L+ /var/www/matrix/index.html 0644 nginx nginx - ${matrixIndexHtml}" + "L+ /var/www/matrix/register.php 0644 nginx nginx - ${matrixRegisterPhp}" + "L+ /var/www/matrix/style.css 0644 nginx nginx - ${matrixStyleCss}" + "L+ /var/www/matrix/app.js 0644 nginx nginx - ${matrixAppJs}" + ]; + + services.phpfpm.pools.matrix = { + user = "nginx"; + group = "nginx"; + settings = { + "listen.owner" = "nginx"; + "listen.group" = "nginx"; + "pm" = "dynamic"; + "pm.max_children" = 10; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 1; + "pm.max_spare_servers" = 3; + }; + phpPackage = pkgs.php.withExtensions ({ enabled, all }: enabled ++ [ all.curl ]); + }; + services = { matrix-synapse = { enable = true; @@ -174,6 +202,22 @@ in proxy_set_header Connection "upgrade"; ''; }; + "/" = { + root = "/var/www/matrix"; + extraConfig = '' + index index.html; + try_files $uri $uri/ =404; + ''; + }; + "~ \\.php$" = { + root = "/var/www/matrix"; + extraConfig = '' + fastcgi_pass unix:${config.services.phpfpm.pools.matrix.socket}; + fastcgi_index index.php; + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + ''; + }; }; }; }; @@ -188,14 +232,12 @@ in LC_COLLATE = "C" LC_CTYPE = "C"; ''; - settings = { wal_level = "replica"; max_wal_senders = 5; wal_keep_size = "512MB"; listen_addresses = lib.mkForce "127.0.0.1,100.109.10.91"; }; - authentication = lib.mkAfter '' host replication replicator 100.0.0.0/8 scram-sha-256 '';