Maschinenraum
Die Rechner an Bord und selbstgebaute Werkzeuge.
Wie alles anfing
Mit meinem persönlichen ITler an meiner Seite und dem Wunsch, ein Game zu zocken, das es nicht für die Konsole gibt, entstand die Idee, einen eigenen Gaming-PC hochzuziehen. Wir machten uns auf die Suche und recherchierten nach geeigneten Komponenten. Viele Dinge davon wären bei mir untergegangen: Was muss das Motherboard können? Wie reagiert die CPU darauf, passt das zusammen? Was ist mit der GPU, verträgt die sich auch mit CPU und Motherboard? Welchen RAM braucht es? Auf welche Spezifikationen muss ich dabei achten? Wie viel Power braucht „der Gerät“ zum Schluss? Alles Dinge, die ich ohne ITler an meiner Seite nicht in Erwägung gezogen hätte. Und in Anbetracht der aktuellen PC-Lage haben wir keinen Deut zu spät damit begonnen, meinen PC zu konfigurieren.
Und dann: Welches OS soll drauf? Welches OS unterstützt das Zocken? Wo läuft Steam vernünftig? So viele Feinheiten, von denen ich keine Ahnung hatte, bis wir damit begonnen haben.
Übrigens: Das Spiel, um das es ging, habe ich immer noch nicht in meinem Besitz. Dafür unzählige andere ^.^, die so nie geplant waren :D
Schiffsregister
Die Rechner an Bord, eingetragen wie im Register der Sternenflotte.
Raumschiff der DG-Flotte
flux
Desktop
RX 7600 XT 16 GB · 64 GB RAM · CachyOS + KDE
Heimatwerft: Eigenbau – mein erster selbst gebauter PC
Flaggschiff und Main
Raumschiff der DG-Flotte
nx01
Dell Latitude E5550
Außeneinsätze
Raumschiff der DG-Flotte
Defiant
MacBook 2009
Debian · 32-bit-EFI
Heimatwerft: Utopia Planitia
Aus Sentimentalität im Dienst
Eigenbau-Tools
Jetzt, knapp ein Jahr nach der Anschaffung, erkunde ich immer noch Linux mit Hilfe meiner hauseigenen IT und Claude. Ist die IT gerade nicht abkömmlich, muss Claude herhalten und so manches Mal meine stumpfen Nachfragen ertragen, weil ich wieder irgendwas nicht begriffen habe. Vor allem, wenn es um das Terminal (fish) geht: nach wie vor ein Buch mit sieben Siegeln für mich. Ohne Claude würde ich da nicht zwingend rangehen, oder es würde viel, viel länger dauern.
So ist es nun, dass ich mit Hilfe von Claude kleinere Skripte habe, die Dinge zum Laufen und Leuchten bringen. Die totale Schischiness!
newsboard
Lokales Wetter- und News-Dashboard, alle 15 Minuten neu gebaut, ohne Konten und ohne API-Schlüssel.
Fürs Kopieren zu groß, deshalb als Paket. Eigenen Ort, Wetter und Quellen in sources.ini eintragen, dann ./install.sh.
newsboard.zip herunterladen ↓Ripple
Reaktiver Tastatur-Welleneffekt über OpenRGB, läuft als systemd-Dienst.
ripple.py211 Zeilen
Gebaut für die Turtle Beach Vulcan. Braucht OpenRGB mit eingeschaltetem SDK-Server sowie openrgb-python und evdev. Farben und Tempo stehen oben im Skript.
#!/usr/bin/env python3 """Ripple-FX fuer die Turtle Beach Vulcan ueber OpenRGB. Fassung 2.1 (2026-09-18) - Regenbogen: jeder Tastenanschlag erzeugt eine eigene Farbe - weichere, langsamere Welle - Workaround fuer KeyError: '' bei der Plugin-Abfrage (openrgb-python 0.3.6) - wartet beim Start auf den OpenRGB-SDK-Server und verbindet sich nach einem Absturz/Neustart von OpenRGB selbst neu (autostart-taugliche Fassung) """ import colorsys import math import selectors import string import time from openrgb import OpenRGBClient from openrgb.utils import RGBColor from evdev import InputDevice, list_devices, ecodes # --- Workaround --------------------------------------------------------- # OpenRGB 1.0 meldet fuer eines der installierten Plugins einen leeren # Namen; openrgb-python 0.3.6 scheitert daran mit "KeyError: ''", noch # bevor man an die Geraete kommt. Die Plugin-Liste brauchen wir nicht, # also Abfrage stilllegen. try: from openrgb import orgb as _orgb _orgb.OpenRGBClient.update_plugins = lambda self: None except Exception: pass # ----------------------------------------------------------------------- # ==== Zum Spielen: hier die Optik anpassen ==== BASE = (8, 0, 16) # Ruhefarbe (dunkles Violett). (0,0,0) = aus RAINBOW = True # True = jede Taste eine andere Farbe RIPPLE = (0, 200, 255) # Farbe des Tropfens, nur wenn RAINBOW = False SPEED = 7.0 # Ausbreitungsgeschwindigkeit (Zellen/Sek.) WIDTH = 1.0 # Dicke des Rings LIFETIME = 1.1 # Lebensdauer eines Tropfens (Sek.) FPS = 60 # ==== Verbindung / Autostart ==== KEYBOARD = "vulcan" # Suchbegriff im Geraetenamen RETRY_SEC = 3.0 # Wartezeit zwischen Verbindungsversuchen # ============================================== HUE_STEP = 0.618033988749895 # goldener Schnitt -> gut unterscheidbare Farben def log(msg): print(msg, flush=True) # --- evdev-Taste -> OpenRGB-Tastenname --- EV = {} for ch in string.ascii_uppercase: EV[f"KEY_{ch}"] = ch for d in "1234567890": EV[f"KEY_{d}"] = d for n in range(1, 13): EV[f"KEY_F{n}"] = f"F{n}" EV.update({ "KEY_ESC":"Escape","KEY_GRAVE":"`","KEY_TAB":"Tab","KEY_CAPSLOCK":"Caps Lock", "KEY_LEFTSHIFT":"Left Shift","KEY_LEFTCTRL":"Left Control","KEY_LEFTMETA":"Left Windows", "KEY_LEFTALT":"Left Alt","KEY_SPACE":"Space","KEY_RIGHTALT":"Right Alt","KEY_COMPOSE":"Menu", "KEY_MINUS":"-","KEY_EQUAL":"=","KEY_LEFTBRACE":"[","KEY_RIGHTBRACE":"]","KEY_SEMICOLON":";", "KEY_APOSTROPHE":"'","KEY_COMMA":",","KEY_DOT":".","KEY_SLASH":"/","KEY_BACKSPACE":"Backspace", "KEY_ENTER":"Enter (ISO)","KEY_RIGHTSHIFT":"Right Shift","KEY_RIGHTCTRL":"Right Control", "KEY_102ND":"\\ (ISO)","KEY_BACKSLASH":"#","KEY_NUMLOCK":"Num Lock","KEY_INSERT":"Insert", "KEY_DELETE":"Delete","KEY_HOME":"Home","KEY_END":"End","KEY_PAGEUP":"Page Up", "KEY_PAGEDOWN":"Page Down","KEY_UP":"Up Arrow","KEY_DOWN":"Down Arrow","KEY_LEFT":"Left Arrow", "KEY_RIGHT":"Right Arrow","KEY_SYSRQ":"Print Screen","KEY_SCROLLLOCK":"Scroll Lock", "KEY_PAUSE":"Pause/Break","KEY_KP7":"Number Pad 7","KEY_KP8":"Number Pad 8","KEY_KP9":"Number Pad 9", "KEY_KP4":"Number Pad 4","KEY_KP5":"Number Pad 5","KEY_KP6":"Number Pad 6","KEY_KP1":"Number Pad 1", "KEY_KP2":"Number Pad 2","KEY_KP3":"Number Pad 3","KEY_KP0":"Number Pad 0","KEY_KPDOT":"Number Pad .", "KEY_KPSLASH":"Number Pad /","KEY_KPASTERISK":"Number Pad *","KEY_KPMINUS":"Number Pad -", "KEY_KPPLUS":"Number Pad +","KEY_KPENTER":"Number Pad Enter","KEY_PREVIOUSSONG":"Media Previous", "KEY_PLAYPAUSE":"Media Play/Pause","KEY_NEXTSONG":"Media Next", }) def connect(): """Wartet auf OpenRGB, schaltet in Direct-Mode und liefert die Rasterkarte.""" while True: try: cli = OpenRGBClient(name="ripple") kb = next((d for d in cli.devices if KEYBOARD in d.name.lower()), None) if kb is None: log("OpenRGB erreichbar, aber keine Vulcan im Geraetebaum - warte ...") else: for m in kb.modes: if m.name.lower() == "direct": kb.set_mode(m) break zone = next(z for z in kb.zones if z.matrix_map) pos = {} for r, row in enumerate(zone.matrix_map): for c, idx in enumerate(row): if idx is not None: pos[idx] = (r, c) name_to_idx = {led.name.replace("Key: ", ""): i for i, led in enumerate(kb.leds)} log(f"Verbunden mit '{kb.name}' ({len(kb.leds)} LEDs).") return cli, kb, pos, name_to_idx except Exception as exc: log(f"Warte auf OpenRGB-SDK-Server ({type(exc).__name__}: {exc}) ...") time.sleep(RETRY_SEC) def open_inputs(): """Liefert die lesbaren Tastatur-Eingabegeraete.""" devs = [] denied = False for path in list_devices(): try: devs.append(InputDevice(path)) except PermissionError: denied = True kbs = [d for d in devs if ecodes.EV_KEY in d.capabilities() and KEYBOARD in d.name.lower()] if not kbs: # Fallback: irgendeine echte Tastatur kbs = [d for d in devs if ecodes.KEY_A in d.capabilities().get(ecodes.EV_KEY, [])] if not kbs: if denied: raise SystemExit( "Keine Tastatur lesbar - fehlt die Mitgliedschaft in der Gruppe 'input'?\n" " sudo usermod -aG input $USER (danach ab-/anmelden)") raise SystemExit("Kein Tastatur-Eingabegeraet gefunden.") return kbs def main(): cli, kb, pos, name_to_idx = connect() devs = open_inputs() log("Tastatur(en): " + ", ".join(d.name for d in devs)) sel = selectors.DefaultSelector() for d in devs: sel.register(d, selectors.EVENT_READ) def resolve(code): names = ecodes.KEY.get(code) for n in (names if isinstance(names, list) else [names]): suf = EV.get(n) if suf in name_to_idx: return name_to_idx[suf] return None n_leds = len(kb.leds) ripples = [] # [zeile, spalte, startzeit, (r,g,b)] hue = 0.0 frame_dt = 1.0 / FPS def next_color(): nonlocal hue if not RAINBOW: return RIPPLE hue = (hue + HUE_STEP) % 1.0 r, g, b = colorsys.hsv_to_rgb(hue, 1.0, 1.0) return (r * 255.0, g * 255.0, b * 255.0) def render(now): cols = [] for i in range(n_leds): r, c = pos.get(i, (None, None)) cr, cg, cb = BASE if r is not None: for orow, ocol, t0, col in ripples: age = now - t0 d = math.hypot(r - orow, c - ocol) - age * SPEED a = math.exp(-(d * d) / (2 * WIDTH * WIDTH)) * max(0.0, 1 - age / LIFETIME) cr += col[0] * a; cg += col[1] * a; cb += col[2] * a cols.append(RGBColor(min(255, int(cr)), min(255, int(cg)), min(255, int(cb)))) kb.set_colors(cols, fast=True) log("Ripple laeuft - tippen zum Testen, Strg+C zum Beenden.") render(time.time()) prev_active = False while True: for key, _ in sel.select(timeout=frame_dt): try: for ev in key.fileobj.read(): if ev.type == ecodes.EV_KEY and ev.value == 1: led = resolve(ev.code) if led in pos: ripples.append([pos[led][0], pos[led][1], time.time(), next_color()]) except (BlockingIOError, OSError): pass now = time.time() ripples[:] = [rp for rp in ripples if now - rp[2] < LIFETIME] active = bool(ripples) if active or prev_active: try: render(now) except Exception as exc: # OpenRGB weg (beendet/neu gestartet) -> neu verbinden log(f"Verbindung zu OpenRGB verloren ({type(exc).__name__}) - verbinde neu ...") try: cli.disconnect() except Exception: pass time.sleep(RETRY_SEC) cli, kb, pos, name_to_idx = connect() n_leds = len(kb.leds) ripples.clear() prev_active = active if __name__ == "__main__": try: main() except KeyboardInterrupt: log("\nBeendet.")ripple.service14 Zeilen
Als Benutzerdienst nach ~/.config/systemd/user/ legen, dann: systemctl --user enable --now ripple
[Unit] Description=Ripple-Tastatureffekt (OpenRGB / Turtle Beach Vulcan) After=graphical-session.target [Service] Type=simple # Pfad anpassen: dorthin, wo ripple.py bei dir liegt (%h = dein Home-Ordner) ExecStart=/usr/bin/python3 -u %h/.local/bin/ripple.py Restart=always RestartSec=5 Nice=5 [Install] WantedBy=default.targetripple_discover.py37 Zeilen
Helfer für andere Tastaturen: zeigt Geräte, Zonen und LED-Namen, die OpenRGB kennt.
#!/usr/bin/env python3 from openrgb import OpenRGBClient from openrgb.utils import DeviceType cli = OpenRGBClient(name="ripple-discover") print("== Alle Geräte ==") for i, d in enumerate(cli.devices): print(f" [{i}] {d.name} ({len(d.leds)} LEDs)") kb = None for d in cli.devices: if "vulcan" in d.name.lower(): kb = d break if kb is None: kbs = cli.get_devices_by_type(DeviceType.KEYBOARD) kb = kbs[0] if kbs else None if kb is None: print("\n!! Keine Tastatur gefunden.") raise SystemExit print(f"\n== Tastatur: {kb.name} ({len(kb.leds)} LEDs) ==") print("\n-- Zonen & Matrix --") for z in kb.zones: print(f"Zone '{z.name}' type={z.type.name} mat={z.mat_width}x{z.mat_height}") if z.matrix_map: for row in z.matrix_map: print(" ", row) else: print(" (keine Matrix-Karte)") print("\n-- LED-Namen --") for i, led in enumerate(kb.leds): print(f"{i:3d}: {led.name}")Noctalia-Dashboard
Der Caelestia-Look unter KDE Plasma.
noctalia-plasma-setup.sh209 Zeilen
Einrichtungsskript für CachyOS/Arch. Mit --lite als Sparvariante für schwache Grafik.
#!/usr/bin/env bash # # noctalia-plasma-setup.sh # Setzt eine Quickshell-artige Desktop-Shell (Noctalia) neben KDE Plasma 6 / KWin Wayland auf, # als KWin-taugliche Annaeherung an den Caelestia-Look (Hyprland). # # Fassung 1.1 - 2026-09-04 # Aenderung ggue. 1.0: pipewire / pipewire-pulse aus REPO_PKGS entfernt. # Grund: auf CachyOS ist das ein CachyOS-eigener Build (z.B. 1:1.6.8-1.2), # den gst-plugin-pipewire fest voraussetzt. Die Standard-[extra]-Version # (z.B. 1:1.6.8-1) ist aelter -> pacman verweigert den Downgrade-Konflikt. # Beide Pakete sind auf einem laufenden Plasma/CachyOS-System ohnehin # schon vorhanden, Noctalia braucht sie nicht neu installiert. # Zielsysteme: CachyOS / Arch (flux = full, nx01 = lite) # # Das Skript installiert NUR. Es aendert von sich aus KEINE Plasma-Konfiguration, # entfernt keine Panels und aktiviert keinen Autostart, solange du es nicht sagst. set -euo pipefail MODE="full" DO_AUTOSTART=0 DRY_RUN=0 usage() { cat <<'USAGE' Verwendung: ./noctalia-plasma-setup.sh [OPTIONEN] --lite Sparvariante fuer schwache GPUs (nx01): kein cava/Visualizer, keine Blur-Extras, keine schweren Schriftpakete. --autostart Legt zusaetzlich einen Autostart-Eintrag an (~/.config/autostart). Standard: aus. Erst manuell testen, dann aktivieren. --dry-run Zeigt nur, was passieren wuerde. -h, --help Diese Hilfe. USAGE } while [[ $# -gt 0 ]]; do case "$1" in --lite) MODE="lite"; shift ;; --autostart) DO_AUTOSTART=1; shift ;; --dry-run) DRY_RUN=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "Unbekannte Option: $1" >&2; usage; exit 1 ;; esac done say() { printf '\n\033[1;35m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; } die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; } run() { if [[ $DRY_RUN -eq 1 ]]; then printf ' (dry-run) %s\n' "$*" else "$@" fi } # ---------------------------------------------------------------- Vorpruefung say "Vorpruefung" command -v pacman >/dev/null 2>&1 || die "Kein pacman gefunden. Das Skript ist fuer Arch/CachyOS." if [[ $EUID -eq 0 ]]; then die "Bitte NICHT als root starten. Das Skript ruft sudo selbst auf, wo noetig." fi PLASMA_VER="$(plasmashell --version 2>/dev/null | awk '{print $NF}' || true)" if [[ -z "$PLASMA_VER" ]]; then warn "plasmashell nicht gefunden - laeuft hier wirklich Plasma?" else echo " Plasma: $PLASMA_VER" case "$PLASMA_VER" in 6.*) ;; *) warn "Noctalia braucht Plasma 6. Gefunden: $PLASMA_VER" ;; esac fi if [[ "${XDG_SESSION_TYPE:-}" != "wayland" ]]; then warn "Aktuelle Session ist '${XDG_SESSION_TYPE:-unbekannt}', nicht wayland." warn "Die Shell braucht wlr-layer-shell und laeuft nur unter Plasma (Wayland)." warn "Installieren geht trotzdem - vor dem Start in die Wayland-Session wechseln." fi echo " Modus: $MODE" # ------------------------------------------------------------- AUR-Helfer AUR="" for h in paru yay; do if command -v "$h" >/dev/null 2>&1; then AUR="$h"; break; fi done [[ -n "$AUR" ]] || die "Kein AUR-Helfer (paru/yay) gefunden. CachyOS hat normalerweise paru: sudo pacman -S paru" echo " AUR-Helfer: $AUR" # ------------------------------------------------------- Pakete aus den Repos say "Repo-Pakete installieren" # quickshell liegt seit 0.3.x in [extra] - kein AUR noetig. # pipewire/pipewire-pulse bewusst NICHT hier drin (siehe Kommentar im Kopf). REPO_PKGS=( quickshell networkmanager upower brightnessctl wl-clipboard qt6-tools python ) if [[ "$MODE" == "full" ]]; then REPO_PKGS+=( cava ddcutil libqalculate inter-font ttf-jetbrains-mono-nerd ) fi run sudo pacman -S --needed --noconfirm "${REPO_PKGS[@]}" # -------------------------------------------------------------- Noctalia (AUR) say "Noctalia aus dem AUR installieren (noctalia-git, upstream-gepflegt)" run "$AUR" -S --needed --noconfirm noctalia-git # ---------------------------------------------------- Binary ermitteln, nicht raten say "Startbefehl ermitteln" NOCTALIA_BIN="" if [[ $DRY_RUN -eq 0 ]]; then while IFS= read -r p; do [[ -x "$p" ]] && { NOCTALIA_BIN="$p"; break; } done < <(pacman -Qlq noctalia-git 2>/dev/null | grep -E '^/usr/bin/' || true) fi if [[ -z "$NOCTALIA_BIN" && $DRY_RUN -eq 0 ]]; then warn "Konnte den Startbefehl nicht automatisch finden." warn "Selbst nachsehen: pacman -Qlq noctalia-git | grep /usr/bin/" NOCTALIA_BIN="noctalia-shell" # Rueckfallname, bitte pruefen else echo " Startbefehl: ${NOCTALIA_BIN:-<dry-run>}" fi # ------------------------------------------------------------------ Autostart if [[ $DO_AUTOSTART -eq 1 ]]; then say "Autostart-Eintrag anlegen" AUTOSTART_DIR="$HOME/.config/autostart" run mkdir -p "$AUTOSTART_DIR" if [[ $DRY_RUN -eq 0 ]]; then cat > "$AUTOSTART_DIR/noctalia.desktop" <<DESKTOP [Desktop Entry] Type=Application Name=Noctalia Shell Comment=Quickshell-Shell neben Plasma (Caelestia-Annaeherung) Exec=$NOCTALIA_BIN X-KDE-autostart-phase=2 X-GNOME-Autostart-enabled=true DESKTOP echo " geschrieben: $AUTOSTART_DIR/noctalia.desktop" else printf ' (dry-run) schreibe %s/noctalia.desktop\n' "$AUTOSTART_DIR" fi else say "Autostart uebersprungen (kein --autostart)" fi # --------------------------------------------------------------------- Ausgabe say "Fertig. Naechste Schritte von Hand:" cat <<NEXT 1) TESTEN, bevor irgendetwas dauerhaft wird: $NOCTALIA_BIN In einem Terminal starten, anschauen, mit Strg+C wieder beenden. 2) Plasma-Panel loswerden (reversibel, KEIN plasmashell killen): Rechtsklick aufs Panel -> "Kontrollleiste anzeigen" -> "Kontrollleiste entfernen" Zurueck: Rechtsklick auf den Desktop -> "Kontrollleiste hinzufuegen" -> "Standard-Kontrollleiste" 3) Farben: Noctalia erzeugt das Farbschema aus dem Wallpaper (Material Design 3). Genau daher kommt der Pastell-Ton im Caelestia-Video - nicht aus einem festen Theme. Also: erst Wallpaper setzen, dann Palette generieren lassen. Generator "tonal-spot" oder "soft" kommt dem Video am naechsten. 4) Was Plasma weiter uebernimmt (unter KWin fehlen die noetigen wlroots-Protokolle): Screenshots -> Spectacle Zwischenablage -> Klipper Nachtfarben -> Systemeinstellungen Sperrbildschirm -> Plasma-Sperrbildschirm 5) Rueckbau komplett: rm -f ~/.config/autostart/noctalia.desktop $AUR -Rns noctalia-git Plasma-Panel wie unter 2) wieder hinzufuegen NEXT if [[ "$MODE" == "lite" ]]; then cat <<'LITE' nx01-Hinweise (Broadwell-iGPU): - In den Systemeinstellungen -> Fensterverhalten -> Arbeitsflaecheneffekte: "Weichzeichnen" und "Hintergrundkontrast" AUS. - Systemeinstellungen -> Barrierefreiheit bzw. Animationsgeschwindigkeit: Animationen auf "Sofort" oder stark verkuerzt. - In Noctalia: Visualizer/cava aus, Desktop-Widgets sparsam, Wallpaper statisch (kein Video/Slideshow), Blur in der Shell reduzieren. LITE fiLCD-Display
Die kleine Thermalright-Anzeige unter Linux zum Leben erweckt. Eher exotisch: Für Linux gibt es keine Herstellersoftware, es läuft über ein Community-Tool.


























