# Küpper Blog > Tech Blog - DevOps, Kubernetes, Self-Hosted Author: Rüdiger Küpper Site: https://blog.kuepper.nrw Dies ist der konsolidierte Volltext aller Blogposts (neueste zuerst). Eine knappe Übersicht steht unter https://blog.kuepper.nrw/llms.txt. ## Headscale: Dein eigener Tailscale-Control-Server zum Selbsthosten - URL: https://blog.kuepper.nrw/posts/2026-05-30-headscale-tailscale-control-server-selfhosted/ - Datum: 2026-05-30 - Autor: ruediger - Tags: SelfHosted, vpn, tailscale, networking, go - Kategorien: SelfHosted, Netzwerk Tailscale hat das Aufsetzen eines privaten Mesh-Netzwerks fast schon trivial gemacht: WireGuard unter der Haube, NAT-Traversal, das ganze Schlüsselgefummel verschwindet hinter einem Login. Der Haken aus Self-Hoster-Sicht: Der Koordinationsserver, der sogenannte Control Server, läuft in Tailscales Cloud und ist als einziger Teil des Stacks nicht quelloffen. Genau diese Lücke schließt **Headscale**, eine in Go geschriebene, selbst gehostete Implementierung dieses Control Servers. Das Projekt steht unter BSD-3-Clause-Lizenz, hat auf GitHub rund 36.000 Sterne und liegt aktuell bei Version 0.28.0. ## Kurz zur Einordnung: Was macht ein Control Server überhaupt? Tailscale ist im Kern ein [modernes VPN auf WireGuard-Basis](https://tailscale.com/), das ein Overlay-Netzwerk zwischen den Rechnern eines Netzwerks aufspannt und dabei NAT-Traversal nutzt. Bei Tailscale ist tatsächlich fast alles Open Source, mit Ausnahme der GUI-Clients für Windows und macOS/iOS sowie eben des Control Servers. Dieser Control Server ist die zentrale Vermittlungsstelle. Er tauscht die öffentlichen WireGuard-Schlüssel der Knoten im Netzwerk aus, vergibt die IP-Adressen der Clients, zieht die Grenzen zwischen den Nutzern, ermöglicht das Teilen von Maschinen und gibt die angekündigten Routen der Knoten bekannt. Das eigentliche VPN läuft danach direkt zwischen den Geräten (Peer-to-Peer), der Control Server koordiniert nur, er ist kein Datendurchlauf. Genau diese Koordinationsrolle übernimmt Headscale auf der eigenen Infrastruktur. ## Designziel: bewusst schlank Headscale verfolgt ein klar umrissenes Ziel: eine quelloffene Alternative zum Tailscale-Control-Server für Self-Hoster, Enthusiasten und Hobbyisten. Der Fokus liegt ausdrücklich auf einem schmalen Funktionsumfang, konkret auf einem *einzelnen* Tailnet, das für den persönlichen Gebrauch oder eine kleine Open-Source-Organisation gedacht ist. Wer also ein vielmandantenfähiges Enterprise-System mit Dutzenden getrennter Netzwerke sucht, ist hier falsch; wer sein Homelab, seine Server und ein paar Clients in ein privates Mesh holen will, genau richtig. Sympathisch transparent: Einer der aktiven Maintainer ist bei Tailscale angestellt und darf Arbeitszeit ins Projekt stecken, dessen Beiträge werden aber von anderen Maintainern gegengeprüft. Mit Tailscale Inc. als Unternehmen ist das Projekt offiziell nicht verbunden. ## Was Headscale kann Der Funktionsumfang deckt die „Base”-Features von Tailscale weitgehend ab. Die wichtigsten Punkte: **Knoten-Registrierung**, sowohl per Web-Authentifizierung als auch über vorab generierte Pre-Auth-Keys, was sich gut für automatisiertes Provisioning eignet. **DNS** MagicDNS, globale und eingeschränkte Nameserver (Split DNS) sowie Search Domains. Eine Headscale-exklusive Zugabe sind zusätzliche, frei definierbare DNS-Records. **Routing** Subnet Router, um ganze Netzsegmente ins Tailnet zu holen, und Exit Nodes, um den gesamten Datenverkehr über einen bestimmten Knoten zu leiten. Beide lassen sich über Auto-Approvers automatisch freigeben. **Zugriffssteuerung (ACLs)** verwaltbar über die API, inklusive einiger Autogroups (etwa `autogroup:internet`, `autogroup:member`, `autogroup:tagged`) und Unterstützung für **Tailscale SSH**. **Single Sign-On via OpenID Connect** Knoten lassen sich über einen OIDC-Provider registrieren und Nutzerprofile von dort übernehmen. Eine Einschränkung, die man kennen sollte: OIDC-Gruppen lassen sich derzeit nicht in ACLs verwenden. **Weiteres** Taildrop für Dateiübertragung, Tags, Dual Stack (IPv4 und IPv6), Ephemeral Nodes für kurzlebige Geräte und ein eingebetteter DERP-Server als Fallback-Relay, wenn keine direkte Verbindung zustande kommt. Was (noch) **fehlt**, sollte man ebenfalls auf dem Schirm haben: Funnel, Serve und Network Flow Logs sind nicht implementiert, entsprechende Issues sind aber offen. ## Betrieb: ein eigenwilliger Hinweis Ein Punkt, der erfahrene Self-Hoster zunächst stutzen lässt: Die Maintainer schreiben im README ausdrücklich, dass sie den Einsatz von Reverse Proxies und Containern für den Betrieb von Headscale **weder unterstützen noch empfehlen**. Das heißt nicht, dass es nicht geht, die Dokumentation enthält durchaus eine Container-Anleitung, sondern dass die offizielle Unterstützung sich auf den direkten Betrieb des Binaries konzentriert. Wer Probleme meldet, sollte das also idealerweise mit einem nativen Setup reproduzieren. Für NixOS-Anwender liegt dem Projekt zudem ein eigenes Modul im Verzeichnis `nix/` bei. Die Installation selbst ist dank Go-Binary unkompliziert. Headscale wird über eine zentrale `config.yaml` konfiguriert; ein gut kommentiertes `config-example.yaml` liegt im Repository. Wichtig dabei: Bei den Beispielkonfigurationen immer denselben Git-Tag wie die eingesetzte Version verwenden, da der `main`-Branch bereits unveröffentlichte Änderungen enthalten kann. Sind Server und Konfiguration einsatzbereit, verbinden sich die ganz normalen, offiziellen Tailscale-Clients, man weist sie lediglich per `--login-server` auf die eigene Headscale-Instanz statt auf die Tailscale-Cloud: ```bash tailscale up --login-server https://headscale.example.com ``` Die Verwaltung von Nutzern, Knoten und Pre-Auth-Keys läuft anschließend über die `headscale`-CLI auf dem Server, etwa: ```bash # Einen Nutzer anlegen headscale users create homelab # Einen Pre-Auth-Key für automatisierte Registrierung erzeugen headscale preauthkeys create --user homelab --reusable --expiration 24h # Alle registrierten Knoten auflisten headscale nodes list ``` ## Warum das gerade fürs Homelab interessant ist Der Reiz liegt in der Kombination zweier Dinge: Man behält den Komfort des Tailscale-Ökosystems, die ausgereiften Clients über alle Plattformen, MagicDNS, das mühelose Peer-to-Peer-Mesh, und entzieht gleichzeitig die zentrale Koordination der fremden Cloud. Für alle, die ihre Geräte, Server und Dienste ohnehin schon konsequent selbst betreiben, schließt sich damit eine der letzten Abhängigkeiten. Das eigene Tailnet, der eigene Control Server, volle Datenhoheit über die Metadaten des Netzwerks. Wer bislang vor dem rohen WireGuard-Schlüsselmanagement zurückgeschreckt ist, aber Tailscale aus Prinzip nicht in die Cloud auslagern wollte, findet in Headscale den pragmatischen Mittelweg. Ein klar umrissenes Werkzeug, das genau eine Sache tut, und das richtig. *Repository: [github.com/juanfont/headscale](https://github.com/juanfont/headscale) · Dokumentation: [headscale.net](https://headscale.net/stable/) · Lizenz: BSD-3-Clause* --- ## LangExtract: strukturierte Daten aus Texten ziehen - URL: https://blog.kuepper.nrw/posts/2026-05-30-LangExtract-strukturierte-daten-aus-texten-ziehen/ - Datum: 2026-05-30 - Autor: ruediger - Tags: python, llm, nlp, gemini, ollama, open-source - Kategorien: AI, ML, Documents Wer schon einmal versucht hat, aus Arztbriefen, Vertragstexten, Support-Tickets oder Logfiles verlässlich strukturierte Daten zu gewinnen, kennt das Dilemma: Reguläre Ausdrücke sind schnell am Ende, klassische NLP-Pipelines brauchen Trainingsdaten, und wirft man das Ganze einfach in ein LLM, bekommt man zwar JSON zurück, aber ohne jede Garantie, dass die extrahierten Werte auch wirklich im Originaltext stehen. Genau hier setzt **LangExtract** an, eine Python-Bibliothek von Google, die im Mai mit Version 1.5.0 erschienen ist und auf GitHub bereits über 36.000 Sterne gesammelt hat. ## Was LangExtract macht LangExtract extrahiert strukturierte Informationen aus unstrukturiertem Text auf Basis von Anweisungen, die man selbst definiert. Man beschreibt in einem Prompt, was man haben möchte, liefert ein, zwei hochwertige Beispiele mit, und das Tool kümmert sich um den Rest: Chunking langer Dokumente, parallele Verarbeitung, Schema-Erzwingung und am Ende eine interaktive Visualisierung. Der entscheidende Unterschied zu einem nackten LLM-Aufruf liegt im **Source Grounding**. Jede einzelne Extraktion wird auf ihre exakte Position im Quelltext zurückgeführt. Man weiß also nicht nur, *dass* das Modell „Ibuprofen 400 mg” gefunden hat, sondern auch *wo* genau im Dokument dieser Wert steht. Das ist kein Komfort-Feature, sondern in regulierten Umfeldern, Medizin, Recht, Compliance, die Grundvoraussetzung dafür, dass man dem Ergebnis überhaupt trauen darf. ## Die Kernideen im Überblick Sieben Eigenschaften machen das Tool aus, und sie greifen sauber ineinander: **Präzises Source Grounding.** Jede Extraktion wird auf ihren Ursprung im Text gemappt, was visuelles Highlighting und damit echte Nachvollziehbarkeit ermöglicht. Findet das Modell etwas, das sich nicht im Quelltext lokalisieren lässt, bekommt die Extraktion ein `char_interval = None`, solche Halluzinationen lassen sich also gezielt herausfiltern. **Zuverlässige strukturierte Ausgaben.** Das Ausgabeschema wird aus den mitgelieferten Few-Shot-Beispielen abgeleitet und konsistent durchgehalten. Bei unterstützten Modellen wie Gemini sorgt Controlled Generation dafür, dass die Struktur garantiert eingehalten wird. **Optimiert für lange Dokumente.** Das berüchtigte „Needle in a Haystack”-Problem großer Texte wird über eine Kombination aus Text-Chunking, paralleler Verarbeitung und mehreren Extraktions-Durchläufen angegangen. Mehr Durchläufe bedeuten höheren Recall. **Interaktive Visualisierung.** Aus den Ergebnissen generiert LangExtract eine eigenständige HTML-Datei, in der sich auch tausende extrahierte Entitäten in ihrem ursprünglichen Kontext durchstöbern lassen. **Flexible Modellwahl.** Von Cloud-Modellen der Gemini-Familie über OpenAI bis zu lokalen Open-Source-Modellen via Ollama ist alles dabei, und über ein Plugin-System lassen sich eigene Provider ergänzen. **Domänenunabhängig und ohne Fine-Tuning.** Eine neue Extraktionsaufgabe definiert man mit wenigen Beispielen. Es braucht kein Training, kein angepasstes Modell. ## Quick Start Die API ist angenehm schlank. Man definiert Prompt und Beispiele, ruft `lx.extract` auf – fertig. Hier das kanonische Beispiel aus der Dokumentation, das Figuren, Emotionen und Beziehungen aus einem Shakespeare-Zitat zieht: ```python import langextract as lx import textwrap prompt = textwrap.dedent("""\ Extract characters, emotions, and relationships in order of appearance. Use exact text for extractions. Do not paraphrase or overlap entities. Provide meaningful attributes for each entity to add context.""") examples = [ lx.data.ExampleData( text="ROMEO. But soft! What light through yonder window breaks? It is the east, and Juliet is the sun.", extractions=[ lx.data.Extraction( extraction_class="character", extraction_text="ROMEO", attributes={"emotional_state": "wonder"} ), lx.data.Extraction( extraction_class="emotion", extraction_text="But soft!", attributes={"feeling": "gentle awe"} ), ] ) ] result = lx.extract( text_or_documents="Lady Juliet gazed longingly at the stars, her heart aching for Romeo", prompt_description=prompt, examples=examples, model_id="gemini-3.5-flash", ) ``` Wichtig ist dabei: Die Beispiele steuern das Modellverhalten maßgeblich. Der `extraction_text` sollte möglichst wortgetreu aus dem Beispieltext stammen und nicht umschrieben werden, LangExtract warnt standardmäßig, wenn die Beispiele diesem Muster nicht folgen. Für lange Texte skaliert man über zusätzliche Parameter. Das Tool kann komplette Dokumente direkt von einer URL verarbeiten: ```python result = lx.extract( text_or_documents="https://www.gutenberg.org/files/1513/1513-0.txt", prompt_description=prompt, examples=examples, model_id="gemini-3.5-flash", extraction_passes=3, # mehr Durchläufe = höherer Recall max_workers=20, # parallele Verarbeitung max_char_buffer=1000 # kleinere Kontexte = höhere Genauigkeit ) ``` Damit lässt sich der komplette „Romeo und Julia”-Text (knapp 148.000 Zeichen) verarbeiten und in hunderte Entitäten zerlegen. ## Lokal betreiben mit Ollama Für mich als Homelab-Betreiber ist der interessanteste Punkt, dass LangExtract komplett ohne Cloud und API-Key auskommt. Lokale Inferenz läuft über Ollama: ```python import langextract as lx result = lx.extract( text_or_documents=input_text, prompt_description=prompt, examples=examples, model_id="gemma2:2b", # wählt automatisch den Ollama-Provider model_url="http://localhost:11434", ) ``` Das Setup ist überschaubar: Ollama von [ollama.com](https://ollama.com/) installieren, `ollama pull gemma2:2b`, dann `ollama serve`. Damit verlässt kein einziges Byte des zu verarbeitenden Textes die eigene Infrastruktur, für sensible Daten, etwa im medizinischen oder juristischen Kontext, ist das ein gewichtiges Argument. Wer mag, kann das Ganze auch im eigenen Cluster containerisieren; ein Dockerfile liegt dem Projekt bei. ## Wofür man es einsetzt Die mitgelieferten Beispiele zeigen die Bandbreite deutlich. Neben der literarischen Spielerei mit Shakespeare liegt der Fokus klar auf strukturierter Informationsextraktion aus Fachtexten: das Herausziehen von Medikamentennamen, Dosierungen und Verabreichungswegen aus klinischen Notizen etwa, inklusive der Beziehungen zwischen diesen Entitäten. Ein Live-Demo namens RadExtract auf HuggingFace Spaces zeigt, wie sich Radiologie-Berichte automatisch strukturieren lassen. Übertragen auf den Alltag heißt das: überall dort, wo strukturierte Felder aus Fließtext gewonnen werden müssen und Nachvollziehbarkeit zählt. Vertragsanalyse, Extraktion aus Behördenschreiben, Aufbereitung von Support-Tickets, Auswertung von Forschungsliteratur – die Aufgabe definiert man jeweils über ein paar Beispiele neu, das Werkzeug bleibt dasselbe. ## Einordnung LangExtract ist ausdrücklich **kein** offiziell unterstütztes Google-Produkt, sondern steht unter Apache-2.0-Lizenz als Open Source bereit. Für gesundheitsbezogene Anwendungen gelten zusätzlich die Health-AI-Developer-Foundations-Nutzungsbedingungen. Man sollte sich außerdem bewusst sein, dass die Qualität der Extraktion immer vom gewählten Modell, der Komplexität der Aufgabe und der Klarheit von Prompt und Beispielen abhängt – das Tool nimmt einem die sorgfältige Formulierung der Aufgabe nicht ab. Was es aber liefert, ist die fehlende Schicht zwischen „LLM gibt irgendwas zurück” und „verlässlich weiterverarbeitbare Daten”: ein erzwungenes Schema, nachprüfbare Quellenbezüge und eine Visualisierung, mit der man Ergebnisse tatsächlich kontrollieren kann. Für jeden, der LLMs über die Spielerei hinaus produktiv für Extraktionsaufgaben einsetzen will, lohnt sich ein genauer Blick. ----- *Repository: [github.com/google/langextract](https://github.com/google/langextract) · Installation: `pip install langextract`* --- ## A public server where you can play classic text adventure games - URL: https://blog.kuepper.nrw/posts/2026-05-30-retro-text-adventures-im-terminal/ - Datum: 2026-05-30 - Autor: ruediger - Tags: games, terminal - Kategorien: Games A public server where you can play classic text adventure games for free. Just open up a Terminal window and enter: ssh -p 3517 dgl@games.dgrlabs.co Have fun! --- ## flux9s: Ein K9s für FluxCD im Terminal - URL: https://blog.kuepper.nrw/posts/2026-05-19-flux9s-ein-k9s-fuer-fluxcd-im-terminal/ - Datum: 2026-05-19 - Autor: ruediger - Tags: FluxCD, GitOps, Kubernetes, Rust, TUI - Kategorien: DevOps, Kubernetes Wer FluxCD im Alltag nutzt, kennt das Spiel: Man wechselt ständig zwischen `flux get kustomizations`, `flux get sources git`, `kubectl describe` und vielleicht noch einem `kubectl get events`, um zu verstehen, warum ein Deployment gerade nicht durchläuft. K9s hat das für klassische Kubernetes-Ressourcen längst gelöst, aber für die Flux-CRDs fehlte bisher das passende Pendant. Genau diese Lücke schließt **[flux9s](https://github.com/dgunzy/flux9s)**: Ein K9s-inspiriertes Terminal-UI speziell für FluxCD. Ich bin durch Zufall darauf gestoßen und finde das Projekt interessant genug, um es hier kurz vorzustellen. ## Was macht flux9s? flux9s ist ein in **Rust** geschriebenes TUI (Terminal User Interface), das die wichtigsten Flux-Ressourcen in Echtzeit anzeigt und bedienbar macht. Unter der Haube nutzt es die Kubernetes Watch API über [kube-rs](https://github.com/kube-rs/kube), die CRD-Typen werden mit [kopium](https://github.com/kube-rs/kopium) generiert. Die Feature-Liste deckt das ab, was man im Alltag tatsächlich braucht: - **Echtzeit-Monitoring** der Flux Custom Resources – Kustomizations, GitRepositories, HelmReleases, OCIRepositories, Buckets und mehr - **Unified View oder typspezifisch** – entweder alles auf einen Blick oder gefiltert nach Ressourcentyp - **Aktionen direkt aus der Oberfläche**: Suspend, Resume, Reconcile, Reconcile-with-Source, Delete - **YAML-Inspektion** der vollständigen Manifeste mit einem Tastendruck - **Graph-Ansicht** der Abhängigkeiten zwischen Ressourcen – sehr praktisch, um zu sehen, welche Kustomization gerade welche HelmRelease managed - **Reconciliation-History** für Kustomizations, HelmReleases, FluxInstance und ResourceSet - **Trace** der Ownership-Kette einer Ressource - **Favoriten**, Namespace-Switching, Context-Switching - **17 eingebaute Themes** plus die Möglichkeit, eigene Skins zu importieren (das Format ist an K9s angelehnt, aber nicht 1:1 kompatibel) ## Was mir besonders gefällt Zwei Details fallen positiv auf: **Readonly per Default.** flux9s startet im Readonly-Modus. Wer eine destruktive Aktion ausführen will, muss das explizit aktivieren, entweder dauerhaft per `flux9s config set readOnly false` oder pro Session mit `:readonly`. Für ein Tool, das produktive Cluster im Blick hat, ist das genau die richtige Default-Einstellung. Ein versehentliches `Ctrl+d` auf der Root-Kustomization will man nun wirklich nicht erleben. **Vertraute Keybindings.** Wer K9s gewohnt ist, findet sich sofort zurecht: `j`/`k` zum Navigieren, `:` für den Command-Mode, `/` zum Filtern, `y` für YAML, `d` für Describe. Hinzu kommen Flux-spezifische Bindings wie `s` (suspend), `r` (resume), `R` (reconcile) und `W` (reconcile with source). ## Installation Auf macOS und Linux geht es am bequemsten per Homebrew: ```bash brew install dgunzy/tap/flux9s ``` Alternativ über `cargo`: ```bash cargo install flux9s # oder mit cargo-binstall ohne Compile-Schritt: cargo binstall flux9s ``` Vorgebaute Binaries für Linux, macOS (Intel & Apple Silicon) und Windows gibt es außerdem auf der [Releases-Seite](https://github.com/dgunzy/flux9s/releases). ## Erster Start Vorausgesetzt der `kubeconfig` zeigt auf einen Cluster mit installiertem Flux, reicht ein simples: ```bash flux9s ``` Standardmäßig wird der Namespace `flux-system` beobachtet. Mit `:ns all` sieht man alle Namespaces, mit `:ns ` wechselt man gezielt. `:ctx` öffnet ein interaktives Menü zum Context-Switch, sehr angenehm, wenn man mehrere Cluster gleichzeitig betreut. ## Einordnung flux9s ist klar Version 0.9.x, also noch kein „rock solid" 1.0-Release. Der Autor weist im README ehrlich darauf hin, dass das Scaffold mit KI-Unterstützung entstanden ist und um Issues/PRs gebeten wird. Trotzdem: Für ein junges Projekt fühlt sich die Bedienung schon sehr rund an, und der Funktionsumfang deckt die wichtigsten Workflows ab. Wer FluxCD ernsthaft im Einsatz hat – sei es im Homelab oder produktiv – sollte flux9s zumindest mal ausprobieren. Bei mir wandert es jedenfalls neben K9s in den Werkzeugkasten. Für die schnelle Diagnose „warum reconciled das gerade nicht?" deutlich angenehmer, als sich durch mehrere `flux get`- und `kubectl describe`-Aufrufe zu hangeln. ## Links - Repository: [github.com/dgunzy/flux9s](https://github.com/dgunzy/flux9s) - Projektseite: [flux9s.ca](https://flux9s.ca) - K9s (das Original): [github.com/derailed/k9s](https://github.com/derailed/k9s) - FluxCD: [fluxcd.io](https://fluxcd.io) --- ## KI-Agents ohne Handbremse mit Code Container - URL: https://blog.kuepper.nrw/posts/2026-05-08-ki-agents-ohne-handbremse-mit-code-container/ - Datum: 2026-05-08 - Autor: ruediger - Tags: macOS, Open-Source, Productivity, KI-Agent - Kategorien: Tools Projekt öffnen und dem KI-Agent Root-Zugriff geben, volle Dateiberechtigungen und uneingeschränkten Netzwerkzugriff. Keine Bestätigungen. Keine Berechtigungsabfragen. Keine Überwachung. Ich weiß, das klingt verrückt. Aber mit Code Container ist es tatsächlich vollkommen sicher. Anstatt herumzusitzen und jeden Prompt wie ein Aufpasser zu überwachen, nur für den Fall, dass sich ein abtrünniges `rm -rf` durchschmuggelt, nutze ich Code Container, um jedes Projekt in einen isolierten Container zu mounten, in dem ich meinen KI-Agent mit vollen Berechtigungen frei laufen lassen kann. Code Container ist vollständig Open Source. Du kannst Container mit einem einzigen Befehl installieren: `npm install -g code-container` \*Hinweis: Code Container geht davon aus, dass dein KI-Agent in gutem Glauben agiert. Code Container kann nicht vor Prompt-Injections schützen. ## Do you want to proceed? Do you want to proceed? Do you want to proceed? Vor ein paar Monaten. Ich analysierte ein Repo. Ich wollte, dass Claude Code das Frontend analysiert, während ich das Backend analysiere. Parallele Arbeit, maximale Effizienz. Die Parallelität hielt etwa 5 Sekunden lang, bevor Claude-Code mich unterbrach: „Do you want to proceed?" Klar. „Do you want to proceed?" Klar. „Do you want to proceed?" Klar. Ich wurde ständig von Claude-Code für Berechtigungen unterbrochen und kam überhaupt nicht zum Arbeiten. Aber ich wollte Claude-Code auch keine vollen Berechtigungen geben, falls es mein gesamtes System zerlegt. Es ging entweder darum, Claude zu babysitten oder russisches Roulette mit meiner Festplatte zu spielen. Hier kommt **Code Container** ins Spiel, ein ultra-leichtgewichtiger Container für jedes Projekt, der deinen KI-Agent frei laufen lässt mit einer Startzeit von 300ms, gemeinsam genutzten Konfigurationen und Konversationen sowie vollständiger Systemisolation. ## Code Container bauen Die Idee ist simpel. Ein Projektverzeichnis in einen Docker-Container mounten, alle Tools, die der KI-Agent braucht, vorinstallieren und ihn völlig uneingeschränkt laufen lassen. Wenn er etwas löscht, dann löscht er es innerhalb des Containers. Worst Case? Ich entferne den Container und starte einen neuen. Meine eigentliche Maschine bleibt unangetastet. Nur ein einfaches Shell-Skript, das einen Docker-Container hochfährt, das Projektverzeichnis und meinen `~/.claude`-Ordner mountet und dich mit einsatzbereitem Claude Code reinwirft. ## Wie es funktioniert Das gesamte Projekt besteht nur aus einem Shell-Skript und einem Dockerfile. Wenn du `container` aus einem beliebigen Projektverzeichnis ausführst, passiert Folgendes: - Es erstellt oder setzt einen Container fort, der genau auf dieses Projekt zugeschnitten ist, benannt `container-{project-name}-{path-hash}` - Es mountet dein Projektverzeichnis in den Container unter `/root/{project-name}` - Es mountet deine KI-Agent-Konfigurationsdateien in den Container (`~/.codex`, `~/.config/opencode` usw.) - Es wirft dich in eine Bash-Shell mit einsatzbereitem OpenCode, Codex und Claude-Code. Wenn du beendest, stoppt der Container automatisch. Das nächste Mal, wenn du `container` im selben Projekt ausführst, ist alles genau dort, wo du es verlassen hast: installierte Pakete, Shell-Historie, alles bleibt pro Container erhalten. Der KI-Agent-State wie KI-Agent-Konfigurationen und Konversationshistorien werden über alle deine Container hinweg geteilt, sodass dein KI-Agent sofort dort weitermacht, wo er aufgehört hat unabhängig davon, in welchem Projekt du dich befindest. SSH-Keys und Git-Config könnten read-only vom Host gemountet, sodass Commits und Pushes wie gewohnt funktionieren. Das mache ich nicht, weil ich commits dann doch lieber kontrolliert von ausserhalb des Containers machen möchte. container ist außerdem extrem leichtgewichtig. Einen neuen Container hochzufahren dauert etwa eine Sekunde. Einen bestehenden fortzusetzen geht sofort. ## Mein Workflow Ich nutze OpenCode mit GLM 4.7 für die meisten Aufgaben und wenn ich etwas Kompliziertes erledigen muss wechsel ich zu Codex. Die Routine ist kinderleicht: in ein Projekt `cd`'en, `container` ausführen, `opencode` starten und dann parallel meine eigene Arbeit machen. Innerhalb des Containers gebe ich dem KI-Agent volle Berechtigungen, um zu tun, was immer er will. Volle Berechtigungen. Keine Unterbrechungen. Ich kann sogar mehrere Container für dasselbe Projekt hochfahren und so meine Produktivität enorm steigern, ohne mir Sorgen machen zu müssen, dass meinem System etwas passiert. Ehrlich gesagt führe ich meinen KI-Agent nie mehr außerhalb eines Containers aus. Es ist so schnell und nahtlos, dass es keinen Grund dafür gibt. Ich hatte eigentlich vor, das für mich zu behalten; es war zu einem so natürlichen Teil meines Workflows geworden, dass ich vergessen hatte, dass das nicht jeder so macht. Dann fragte ein Freund, ob er es ausprobieren dürfe, und ich dachte: Warum nicht das Ganze einfach Open Source stellen? Also hier sind wir. ## Erste Schritte mit container Das Setup dauert je nach System etwa 5 Minuten: ```bash # container-Befehl global installieren npm install -g code-container # Deine KI-Agent-Konfigurationen nach ~/.code-container kopieren container init # Image bauen (~5 min, einmalig) container build ``` Dann, aus jedem beliebigen Projekt: ```bash cd /path/to/your/project container ``` Keine Config-Dateien. Kein Setup pro Projekt. Einfach `container`, und du bist drin. Code Container ist vollständig Open Source: https://github.com/kevinMEH/code-container Wenn man zusätzliche Entwicklungstools oder Pakete (GCC, Go, Rust) hinzufügen möchtest, kan man einfach das `Dockerfile` anpasst. Mounting-Points hinzufügen, Einfach Mounts in `~/.code-container/MOUNTS.txt` eintragen. --- ## Thaw: Der Open-Source-Nachfolger von Ice für eine aufgeräumte macOS-Menüleiste - URL: https://blog.kuepper.nrw/posts/2026-05-04-Thaw-Der-Open-Source-Nachfolger-von-Ice-fuer-eine-aufgeraeumte-macOS-menueleiste/ - Datum: 2026-05-04 - Autor: ruediger - Tags: macOS, Open-Source, Productivity, Tools - Kategorien: Tools Wer auf macOS schon mal ernsthaft mit der Menüleiste gearbeitet hat, kennt das Drama der letzten zwei Jahre. **Bartender** — über Jahre der unangefochtene Platzhirsch — wurde 2024 still und heimlich an die Applause Group verkauft, samt unerklärter Analytics-Integration, die Nutzer erst nach einem Update bemerkten. Die Community lief in Scharen zu **Ice** über, einer freien Open-Source-Alternative von Jordan Baird. Doch auch dieser Hafen erwies sich als nicht ganz krisensicher: Ice hat seit Anfang 2026 keine stabile Version mehr gesehen, der Entwickler ist beruflich anderweitig eingebunden — und macOS 26 Tahoe wartet nicht. Genau hier setzt **Thaw** an. ## Was ist Thaw? Thaw ist ein **Fork von Ice**, gestartet von [stonerl](https://github.com/stonerl) mit dem erklärten Ziel, das Projekt am Leben zu halten: Bugs fixen, Kompatibilität mit aktuellen macOS-Versionen sicherstellen und die offene Roadmap von Ice abarbeiten. Lizenz ist **GPL-3.0**, der Code zu 100 % in Swift, und mit über 2.600 Stars sowie 40 Mitwirkenden hat sich das Projekt in kurzer Zeit etabliert. Die Voraussetzung ist klar definiert: **macOS 14 oder neuer**. Thaw nutzt System-APIs, die erst ab Sonoma verfügbar sind — Support für ältere Versionen ist nicht geplant. ## Was kann Thaw? Auf den ersten Blick: das, was alle in dieser Klasse können — Menüleisten-Icons verstecken, zeigen, sortieren. Im Detail aber deutlich mehr als die meisten Konkurrenten: **Item-Management** - Drei Sektionen: sichtbar, versteckt und „always-hidden" - Reveal nach Wahl: Hover, Klick auf leere Stelle, Scrollen oder Wischen - Automatisches Wieder-Verstecken nach konfigurierbarem Timer - Drag-and-Drop-Layout-Editor - Profile mit eigenen Layouts (etwa „Arbeit", „Privat", „Fokus") - Item-Suche per Spotlight-ähnlichem Panel - Eigene Spacer und Item-Gruppen **Notch-Freundlichkeit** Die separate **Thaw Bar** zeigt versteckte Items in einer eigenen Leiste unterhalb der Menüleiste — perfekt für MacBooks mit Notch, wo Items sonst dauerhaft hinter der Aussparung verschwinden würden. **Aussehen** - Tinting (solid und Gradient) - Schatten, Border, abgerundete oder geteilte Menüleiste - Hintergrund hinter der Menüleiste entfernen - Abgerundete Bildschirmecken - Separate Settings für Light/Dark Mode **Hotkeys & Integration** Eigene Tastenkombinationen für so ziemlich alles: Sektionen togglen, Suchpanel öffnen, Auto-Rehide pausieren, einzelne Items kurz anzeigen. In aktuellen Releases kommt zusätzlich eine **Focus-Filter-Integration** dazu — pro macOS-Fokusmodus ein anderes Profil, mit automatischer Rückkehr beim Verlassen des Modus. ## Installation Zwei Wege, beide schmerzfrei: ```bash # Stable brew install thaw # Beta (oder Stable, je nachdem was neuer ist) brew install thaw@beta ``` Alternativ das Zip von der [Release-Seite](https://github.com/stonerl/Thaw/releases/latest) ziehen und nach `/Applications` schieben. ## Warum Thaw und nicht Ice? Ehrliche Antwort: Wenn Ice bei dir läuft und du auf macOS 14 oder 15 bist, kannst du es weiternutzen. Aber: - **Ice ist seit Februar 2026 in der Schwebe**, der letzte stabile Release liegt entsprechend weit zurück. - **Tahoe-Kompatibilität** gibt es bei Ice nur als Dev-Beta, bei Thaw als stabile Releases. - **Aktive Bug-Pflege** — die Release-Notes der letzten Versionen lesen sich wie eine Sammlung der Schmerzpunkte, die Ice-Nutzer kannten: Multi-Icon-Apps, die nach jedem Reboot durcheinandergerieten, Position-Persistence über Neustarts, sauberes Handling von Apps mit gleichen Icons. Thaw ist im Grunde Ice in „aktiv gepflegt" — und genau das ist gerade das einzige, was zählt. ## Wermutstropfen - **macOS 14+ Pflicht** — wer noch auf Ventura oder älter klebt, schaut in die Röhre. - **Wie jeder Menüleisten-Manager** braucht Thaw bestimmte System-Berechtigungen (Bedienungshilfen, optional Bildschirmaufnahme für Tooltips/Suche). Das ist technisch unvermeidbar, aber muss man wissen. - **Kein App-Store-Release** — Updates laufen über die eingebaute Auto-Update-Funktion oder Homebrew. ## Mein Fazit Thaw ist gerade die pragmatischste Wahl für die macOS-Menüleiste. Open Source, GPL-3.0, keine Subscription, keine Analytics-Überraschungen, und ein Maintainer, der erkennbar Lust auf das Projekt hat. Wer Bartender den Rücken kehren will und mit der Stagnation von Ice hadert, findet hier den natürlichen Nachfolger — mit dem Bonus, dass die ursprüngliche Ice-Roadmap weiter abgearbeitet wird. Für mein eigenes Setup — mehrere Kontexte, Notch-MacBook, viele Homelab-Icons (Docker, k9s, Tailscale & Co.) — ist gerade die Kombination aus Profilen und Focus-Filter-Integration der Knaller. Ein Fokusmodus „Arbeit" mit allen DevOps-Tools sichtbar, „Privat" mit reduziertem Set, automatisches Umschalten beim Wechsel: genau der Workflow, den ich mir seit Jahren bastle. Wer Thaw ausprobiert, sollte den Maintainer übrigens auch [auf Patreon unterstützen](https://patreon.com/stonerl) oder über GitHub Sponsors. Solche Projekte funktionieren nur, wenn die Pflege jemandem Spaß macht — und wenn jemand sie bezahlt. --- **Links** - Repository: - Releases: - Discord: - Lizenz: GPL-3.0 --- ## Lokale PKI im Homelab: step-certificates in Kubernetes einrichten - URL: https://blog.kuepper.nrw/posts/2026-04-14-lokale-pki-im-homelab-step-certificates-in-kubernetes-einrichten/ - Datum: 2026-04-14 - Autor: ruediger - Tags: kubernetes, cert-manager, mkcert, tls, local-dev - Kategorien: Internet *Wer ein Kubernetes-Homelab betreibt, kennt das Problem: Selbstsignierte Zertifikate erzeugen Browser-Warnungen, Let's Encrypt funktioniert nur mit öffentlich erreichbaren Domains, und `curl -k` fühlt sich einfach falsch an. Die Lösung ist eine eigene Certificate Authority – und mit [step-certificates](https://smallstep.com/docs/step-ca/) von Smallstep lässt sich das erstaunlich elegant in Kubernetes integrieren.* ## Was wir bauen Am Ende dieser Anleitung läuft in deinem Cluster: - Eine **step-certificates CA** als Kubernetes-StatefulSet - Ein **StepClusterIssuer** für cert-manager, der automatisch TLS-Zertifikate ausstellt - Wildcard-Zertifikate für eine lokale Domain (in meinem Fall `*.r.k`) - Pi-hole als DNS-Resolver für die lokale Domain ## Voraussetzungen - Ein laufender Kubernetes-Cluster - `cert-manager` installiert (inklusive CRDs) - `cert-manager-step-issuer` installiert - Helm 3 - Pi-hole (oder ein anderer DNS-Resolver, der eigene Records unterstützt) --- ## Schritt 1: CA-Passwort vorbereiten Das CA-Passwort wird base64-kodiert an Helm übergeben: ```bash echo -n "secret_ca_password" | base64 > password.txt ``` > **Hinweis:** In einer Produktionsumgebung würde man dieses Secret natürlich über SOPS/Age oder einen externen Secret-Manager verwalten, nicht direkt in einer Datei. --- ## Schritt 2: step-certificates per Helm installieren Die Installation erfolgt über das offizielle Smallstep Helm Chart. Wichtig sind vor allem die DNS-SANs für den CA-Server selbst: ```bash helm install \ --set ca.dns="step-certificates.step-certificates.svc.cluster.local\,127.0.0.1\,ca.kuepper.nrw" \ --set service.targetPort=9000 \ --set inject.secrets.provisioner_password=$(cat password.txt) \ --set inject.secrets.ca_password=$(cat password.txt) \ --set issuer.dnsNames="*.home.kuepper.nrw" \ step-certificates smallstep/step-certificates \ --create-namespace \ --namespace step-certificates ``` Die wichtigsten Parameter erklärt: | Parameter | Bedeutung | |---|---| | `ca.dns` | SANs (Subject Alternative Names) für das CA-Zertifikat selbst | | `service.targetPort` | Port, auf dem step-ca lauscht | | `inject.secrets.ca_password` | Passwort zum Entschlüsseln des CA-Schlüssels | | `inject.secrets.provisioner_password` | Passwort für den ACME/JWK-Provisioner | | `issuer.dnsNames` | Für welche Domains der Provisioner Zertifikate ausstellen darf | --- ## Schritt 3: Generierte Passwörter auslesen step-certificates legt beim ersten Start automatisch Kubernetes Secrets an. Die tatsächlich verwendeten Passwörter lassen sich so auslesen: ```bash # CA-Passwort kubectl get -n step-certificates \ -o jsonpath='{.data.password}' \ secret/step-certificates-ca-password | base64 --decode # Provisioner-Passwort kubectl get -n step-certificates \ -o jsonpath='{.data.password}' \ secret/step-certificates-provisioner-password | base64 --decode ``` Diese Passwörter brauchen wir später für die cert-manager Konfiguration. --- ## Schritt 4: CA-Fingerprint und Root-Zertifikat sichern Der CA-Fingerprint ist notwendig, damit Clients der CA vertrauen können. Er wird im Initialisierungs-Job ausgegeben: ```bash kubectl -n step-certificates logs job.batch/step-certificates # CA URL: https://step-certificates.step-certificates.svc.cluster.local # CA Fingerprint: fe4459d7b7d2e0e8c3d2e49ace4fbd60b3cc0f8a310a31f5ce97751143338fed ``` Nachdem wir Fingerprint und Logs gesichert haben, kann der Job gelöscht werden: ```bash kubectl -n step-certificates delete job.batch/step-certificates ``` Das Root-Zertifikat exportieren wir direkt aus dem laufenden Pod und kodieren es base64 für die spätere Verwendung in Kubernetes Manifests: ```bash kubectl -n step-certificates exec -it step-certificates-0 -- step ca root | base64 -w0 ``` Die Ausgabe (ein langer base64-String, der mit `LS0tLS1CRUdJTi...` beginnt) wird später als `caBundle` im StepClusterIssuer benötigt. --- ## Schritt 5: Provisioner-Konfiguration auslesen Für den cert-manager StepClusterIssuer brauchen wir die `kid` (Key ID) des Provisioners: ```bash # CA-URL prüfen kubectl -n step-certificates get configmap step-certificates-config \ -o jsonpath="{.data['defaults\.json']}" | jq -r '."ca-url"' # https://step-certificates.step-certificates.svc.cluster.local # kid des ersten Provisioners kubectl -n step-certificates get \ -o jsonpath="{.data['ca\.json']}" \ configmaps/step-certificates-config | jq .authority.provisioners[0].key.kid # "yBkH6Chw8aECj1Itrb80X0oUR5MLQeftGw9bStcue" # Name des Provisioners kubectl -n step-certificates get \ -o jsonpath="{.data['ca\.json']}" \ configmaps/step-certificates-config | jq .authority.provisioners[0].name # "admin" ``` --- ## Schritt 6: StepClusterIssuer für cert-manager konfigurieren Jetzt verbinden wir step-certificates mit cert-manager. Der StepClusterIssuer ist cluster-weit gültig und kann in jedem Namespace Zertifikate ausstellen: ```yaml cat <" provisioner: name: "admin" kid: "yBkH6Chw8aECj1Itrb80X0oUR5MLQeftGw9bStcue" passwordRef: name: "step-certificates-provisioner-password" key: "password" namespace: "step-certificates" enableCertificateChain: true EOF ``` Der `caBundle` ist der base64-kodierte Inhalt des Root-Zertifikats aus Schritt 4. --- ## Schritt 7: DNS-Auflösung mit Pi-hole einrichten Damit alle Geräte im lokalen Netzwerk die Domain `*.r.k` auflösen können, fügen wir einen DNS-Record in Pi-hole hinzu. Die kurze TLD `.r.k` ist dabei bewusst gewählt – kurz zu tippen und klar als lokale Domain erkennbar: ```bash pihole-FTL --config misc.dnsmasq_lines '["address=/.r.k/10.0.2.211"]' systemctl restart pihole-FTL ``` Der Wildcard-Record `*.r.k` zeigt auf `10.0.2.211`, die IP des Traefik-Ingress-Controllers. --- ## Schritt 8: Ingress mit automatischem TLS Jetzt der entscheidende Test: ein Ingress, der automatisch ein gültiges Wildcard-Zertifikat bekommt. Die Annotation `cert-manager.io/issuer-kind: StepClusterIssuer` weist cert-manager an, den Step-Issuer zu verwenden: ```yaml cat < **Hinweis:** Der Private Key der CA (`rootCA-key.pem`) ist sensitiv. Für > produktionsähnliche Umgebungen empfiehlt sich die Verschlüsselung mit SOPS/Age > vor dem Einchecken in ein Git-Repository. ## Schritt 4: ClusterIssuer anlegen Mit dem Secret im Cluster kann jetzt ein `ClusterIssuer` erstellt werden, der cert-manager mitteilt, wie Zertifikate ausgestellt werden sollen: ``` cat <=3.0.0 <4.0.0" sourceRef: kind: HelmRepository name: seaweedfs namespace: seaweedfs interval: 24h install: remediation: retries: 3 upgrade: cleanupOnFail: true remediation: strategy: rollback retries: 3 postRenderers: - kustomize: patches: - target: kind: StatefulSet name: seaweedfs-volume patch: | - op: replace path: /spec/template/spec/containers/0/command value: - /bin/sh - -ec - | exec /usr/bin/weed \ -logtostderr=true \ -v=1 \ volume \ -port=8080 \ -metricsPort=9327 \ -dir /data1 \ -dir.idx=/idx \ -max 50 \ -ip.bind=0.0.0.0 \ -readMode=proxy \ -minFreeSpacePercent=7 \ "-ip=${POD_NAME}.${SEAWEEDFS_FULLNAME}-volume.seaweedfs" \ -compactionMBps=50 \ "-mserver=${SEAWEEDFS_FULLNAME}-master-0.${SEAWEEDFS_FULLNAME}-master.seaweedfs:9333" values: global: # Replication-Faktor – auf 1 setzen für Single-Node replicationPlacment: "000" # StorageClass – anpassen an dein Cluster (z.B. "local-path" für K3s) storageClass: "local-path" # Logs loggingLevel: 1 # ServiceMonitor für kube-prometheus-stack enableReplication: false monitoring: enabled: false # auf true setzen wenn kube-prometheus-stack läuft master: enabled: true replicas: 1 # für HA: 3 (braucht ungerade Anzahl) port: 9333 grpcPort: 19333 affinity: "" nodeSelector: {} # Persistenz für Metadaten data: type: "persistentVolumeClaim" size: "1Gi" resources: | requests: cpu: 50m memory: 64Mi limits: memory: 256Mi # Garbage Collection: täglich um 4 Uhr config: |- [master.maintenance] scripts = "ec.encode -fullPercent=95 -quietFor=1h" sleep_minutes = 17 volume: enabled: true replicas: 1 port: 8080 grpcPort: 18080 affinity: "" nodeSelector: {} maxVolumes: 100 extraArgs: "-volumeSizeLimitMB=1024" # Tatsächlicher Datenspeicher – hier großzügig sein data: type: "persistentVolumeClaim" size: "50Gi" # anpassen! idx: type: "persistentVolumeClaim" size: "2Gi" resources: | requests: cpu: 50m memory: 128Mi limits: memory: 512Mi filer: enabled: true replicas: 1 port: 8888 grpcPort: 18888 affinity: "" nodeSelector: {} # Filer-Metadaten (Verzeichnisstruktur etc.) data: type: "persistentVolumeClaim" size: "2Gi" resources: | requests: cpu: 50m memory: 64Mi limits: memory: 256Mi # S3 über den Filer aktivieren s3: enabled: true port: 8333 httpsPort: 0 # Auth-Config wird per Secret/ConfigMap gemountet (siehe s3-config.yaml) enableAuth: true existingConfigSecret: seaweedfs-s3-config # Filer-Config: SQLite reicht für kleine Setups, Postgres für Prod config: |- [leveldb2] enabled = true dir = "/data/filerldb2" s3: enabled: false # S3 läuft über den Filer (filer.s3.enabled), nicht als eigener Pod # Ingress für S3-Endpoint und Filer-UI # Anpassen an dein Traefik-Setup ingress: enabled: false # manuell unten via extra-Manifest # Monitoring via ServiceMonitor (wenn kube-prometheus-stack aktiv) serviceMonitor: enabled: false ``` # FluxCD Kustomization: ## File: namespace.yaml ```yaml apiVersion: v1 kind: Namespace metadata: name: seaweedfs labels: environment: production app: seaweedfs ``` ## File: ingress.yaml ```yaml --- # S3-Endpoint (für aws-cli, SDKs, etc.) # endpoint-url: https://s3.example.com apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: seaweedfs-s3 # namespace: seaweedfs annotations: # Großes Body-Limit für Uploads traefik.ingress.kubernetes.io/router.entrypoints: websecure nginx.ingress.kubernetes.io/proxy-body-size: "0" cert-manager.io/cluster-issuer: "cf-letsencrypt-prod" traefik.ingress.kubernetes.io/router.middlewares: "kube-system-redirect-scheme@kubernetescrd" # Traefik: body-size über Middleware setzen, falls nötig spec: ingressClassName: traefik rules: - host: s3fs.example.com # <-- anpassen http: paths: - path: / pathType: Prefix backend: service: name: seaweedfs-filer port: number: 8333 tls: - hosts: - s3fs.example.com # <-- anpassen secretName: seaweedfs-s3-tls --- # Filer-UI (optional, eher zum Debuggen) apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: seaweedfs-filer-ui # namespace: seaweedfs annotations: traefik.ingress.kubernetes.io/router.entrypoints: websecure cert-manager.io/cluster-issuer: "cf-letsencrypt-prod" traefik.ingress.kubernetes.io/router.middlewares: "kube-system-redirect-scheme@kubernetescrd,seaweedfs-seaweedfs-filer-auth@kubernetescrd" # UI absichern – z.B. mit Traefik BasicAuth-Middleware # traefik.ingress.kubernetes.io/router.middlewares: seaweedfs-auth@kubernetescrd spec: ingressClassName: traefik rules: - host: seaweedfs.example.com # <-- anpassen http: paths: - path: / pathType: Prefix backend: service: name: seaweedfs-filer port: number: 8888 tls: - hosts: - seaweedfs.example.com # <-- anpassen secretName: seaweedfs-filer-tls ``` ## File: secret.yaml ```yaml --- # Dieses Secret enthält die S3-Authentifizierungskonfiguration. # In Produktion: per SOPS verschlüsseln oder per ExternalSecret aus Vault/ESO holen. # # Format: AWS-S3-kompatible identity.json # Doku: https://github.com/seaweedfs/seaweedfs/wiki/Amazon-S3-API apiVersion: v1 kind: Secret metadata: name: seaweedfs-s3-config type: Opaque stringData: # Dateiname muss exakt "config.json" sein seaweedfs_s3_config: | { "identities": [ { "name": "admin", "credentials": [ { "accessKey": "XXXXXXXXXXXXXXXXXXXX", "secretKey": "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" } ], "actions": [ "Admin", "Read", "Write", "List", "Tagging" ] }, { "name": "app-images", "credentials": [ { "accessKey": "AAAAAAAAAAAAAAAAAAAA", "secretKey": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" } ], "actions": [ "Read:app-images", "Write:app-images", "List:app-images", "Tagging:app-images" ] }, { "name": "anonymous", "credentials": [], "actions": [ "Read:app-images", ] } ] } ``` ## File: middleware.yaml ```yaml apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: seaweedfs-filer-auth spec: basicAuth: secret: seaweedfs-filer-auth removeHeader: true ``` ## File: http-auth-secret.yaml ```yaml apiVersion: v1 kind: Secret metadata: name: seaweedfs-filer-auth type: Opaque stringData: # Format: "user:htpasswd-hash" – hier Beispiel mit admin/changeme # Unbedingt ersetzen! Hash generieren wie oben beschrieben. # podman run --rm httpd:alpine htpasswd -nbB admin 'DEIN_PASSWORT' users: "admin:$2y$05$uM7NqmPwxn4egdGuthLu8.n2/Q4GLdbEvAApzebATfMB/Fl3Ms97S" ``` ## File: kustomization.yaml ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - namespace.yaml - ingress.yaml - secret.yaml - middleware.yaml - http-auth-secret.yaml ``` ## Prod Cluster kustomization.yaml ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../base patches: [] ``` File: clusters/prod/flux-system/kustomization/seaweedfs.yaml ```yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: seaweedfs namespace: flux-system spec: interval: 10m prune: true path: ./apps/seaweedfs/prod sourceRef: kind: GitRepository name: flux-system targetNamespace: seaweedfs decryption: provider: sops secretRef: name: sops-age ``` --- ## Von 7 Minuten auf 13 Sekunden: Warum ich mein eigenes CI/CD-Tool gebaut habe - URL: https://blog.kuepper.nrw/posts/2026-03-03-von-7-minuten-auf-13-sekunden-warum-ich-mein-eigenes-cicd-Tool-gebaut-habe/ - Datum: 2026-03-03 - Autor: ruediger - Tags: CI/CD, Automation, Github, cictl - Kategorien: cicd # Von 7 Minuten auf 13 Sekunden: Warum ich mein eigenes CI/CD-Tool gebaut habe GitHub Actions ist ein fantastisches Tool. Die Integration in GitHub ist nahtlos, die Marketplace-Actions decken fast jeden Use Case ab, und für die meisten Projekte reicht es vollkommen aus. Trotzdem habe ich mein eigenes CI/CD-Tool geschrieben. Nicht weil GitHub Actions schlecht ist — sondern weil es für meine Anforderungen zu langsam war. ## Das Problem mit GitHub Actions Wer schon mal eine GitHub Actions Pipeline optimiert hat, kennt das Gefühl: Man cached Dependencies, parallelisiert Jobs, nutzt kleinere Base-Images — und am Ende wartet man trotzdem Minuten, bis der Build durch ist. Bei manchen Projekten sah das so aus: Ursprüngliche Build-Zeiten von 30-40 Minuten. Nach intensiver Optimierung — Docker Layer Caching, Dependency Caching, Build-Matrix-Tuning — konnte ich die Zeiten auf 5-7 Minuten drücken. Klingt nach einem Erfolg, fühlt sich aber trotzdem langsam an, wenn man gerade schnell einen Fix deployen will. Das Grundproblem dabei ist architekturbedingt und lässt sich nicht wegoptimieren: **Jeder Build startet bei Null.** GitHub Actions provisioniert für jeden Workflow-Run eine frische VM oder einen Container. Das bedeutet: Go muss installiert werden. Node muss installiert werden. Docker muss gestartet werden. Dependencies müssen heruntergeladen werden. Jedes. Einzelne. Mal. Caching hilft, aber das Entpacken und Wiederherstellen eines Caches kostet ebenfalls Zeit. **Self-Hosted Runner helfen kaum.** Man könnte denken, dass eigene Runner das Problem lösen. Tun sie aber nur bedingt. Die Actions selbst installieren trotzdem Tools, laden Dependencies, und der Overhead des Runners bleibt. **Lokales Testen ist nicht möglich.** Will man eine Pipeline lokal testen, braucht man Third-Party-Tools wie `act`, die GitHub Actions nur annähernd nachbilden. Die Pipeline-Syntax ist an GitHub gebunden — man kann sie nicht einfach auf der eigenen Maschine ausführen. ## Der Ansatz: Was wäre, wenn die Tools schon da wären? Die Idee hinter cictl ist simpel: Wenn der größte Zeitfresser das Installieren von Tools und das Bootstrapping der Umgebung ist, dann eliminiere ich genau das. cictl ist ein leichtgewichtiges CI/CD-Tool, das als dauerhafter Runner läuft. Die Tools — Go, Node, Docker, Podman — sind vorinstalliert und sofort verfügbar. Kein Download, kein Entpacken, kein Setup. Der Build startet und die erste Zeile Code kompiliert sofort. Das Ergebnis war sofort spürbar: Dieselbe Pipeline, die bei GitHub Actions nach Optimierung 5-7 Minuten brauchte, lief mit cictl in 30-50 Sekunden. Nach weiteren Anpassungen bin ich jetzt bei **13-16 Sekunden** für einen kompletten Build inklusive Container-Image und Push in die Registry. ## Wo die Zeit gespart wird Die Zeitersparnis kommt aus mehreren Faktoren: **Kein Tool-Setup.** Go, Docker, Node — alles ist bereits installiert und konfiguriert. Das Docker-Image, in dem der Runner läuft, enthält genau die Tools, die für die jeweiligen Projekte gebraucht werden. Nicht mehr, nicht weniger. **Persistente Umgebung.** Der Runner läuft dauerhaft. Go-Module sind im Cache, Docker-Layer sind lokal vorhanden, npm-Packages müssen nicht jedes Mal neu heruntergeladen werden. Die Umgebung wärmt sich mit der Zeit auf. **Kein Orchestrierungs-Overhead.** Es gibt keine VM-Provisionierung, kein Container-Scheduling für den Runner selbst, keinen Checkout-Action-Overhead. Ein Git-Webhook kommt rein, der Code wird gepullt, der Build startet. **Schlanke Pipeline-Definition.** Keine marketplace Actions mit eigenem Setup. Die `.cictl.yaml` beschreibt direkt, was passieren soll: ```yaml name: my-app version: strategy: semver source: conventional-commits build: container: image: ghcr.io/org/my-app tags: - "v{{.Version}}" - latest push: true on: push: branches: - main jobs: test: name: Tests runs-on: local steps: - name: Test run: go test ./... lint: name: Lint runs-on: local steps: - name: Vet run: go vet ./... ``` Kein `actions/checkout@v4`, kein `actions/setup-go@v5`, kein `docker/login-action@v3`. Nur die eigentlichen Build-Schritte. ## Setup und Einrichtung cictl lässt sich auf zwei Arten betreiben: lokal auf dem eigenen Rechner oder als Runner im Kubernetes-Cluster. ### Lokale Ausführung Pipeline lokal ausführen — etwas, das mit GitHub Actions schlicht nicht geht: ```bash cictl run -f .cictl.yaml ``` Das war's. cictl liest die Pipeline, löst die Version auf, führt die Jobs aus. Auf der eigenen Maschine, mit den lokal installierten Tools. ### Runner im Cluster Für den dauerhaften Betrieb wird der Runner mit einer Konfigurationsdatei gestartet: ```yaml repos: - url: https://github.com/org/repo.git branch: main webhook: enabled: true listen: ":8080" secret: "${WEBHOOK_SECRET}" store: type: redis ``` Der Runner überwacht die konfigurierten Repositories, reagiert auf GitHub/GitLab-Webhooks, und stellt ein Web-Dashboard mit Live-Build-Logs bereit. Die Builds laufen automatisch — Push auf `main`, Webhook kommt rein, Build startet, Image wird gebaut und gepusht. Mehrere Runner-Instanzen teilen sich den State über Redis, inklusive Session-Daten für die optionale OIDC-Authentifizierung des Dashboards. ### Versionierung inklusive Ein Feature, das ich nicht mehr missen möchte: Automatische semantische Versionierung über Conventional Commits. Kein manuelles Tag-Setzen, kein Versions-Bumping in Dateien. cictl liest die Commit-History, bestimmt die nächste Version, taggt das Repository und injiziert die Version als Variable in den Build. Ein `feat:` Commit bumpt die Minor-Version, ein `fix:` die Patch-Version, ein Breaking Change die Major-Version. Das passiert automatisch, bei jedem Build. ## Fazit GitHub Actions bleibt ein gutes Tool für viele Teams und Projekte. Aber wenn Build-Geschwindigkeit wirklich zählt — wenn der Unterschied zwischen 7 Minuten und 13 Sekunden den Entwicklungsflow verändert — dann lohnt es sich, die CI/CD-Pipeline näher an die eigene Infrastruktur zu bringen. cictl ist kein GitHub-Actions-Ersatz für alle. Es ist ein spezialisiertes Tool für Teams und Entwickler, die maximale Build-Performance wollen und bereit sind, ihre Runner selbst zu betreiben. Dafür bekommt man Builds, die sich anfühlen wie lokale Ausführung — weil sie es im Grunde auch sind. | | GitHub Actions (optimiert) | cictl Runner | |---|---|---| | Blog-Build | 5-7 min | 13-16 sek | | Tool-Installation | Jedes Mal | Einmalig vorinstalliert | | Lokale Ausführung | Nicht nativ | `cictl run` | | Setup-Overhead | VM/Container pro Run | Dauerhafter Runner | --- ## Kubernetes freediskspace failed troubleshooting - URL: https://blog.kuepper.nrw/posts/2026-03-03-kubernetes-freediskspace-failed-troubleshooting/ - Datum: 2026-03-03 - Autor: ruediger - Tags: Kubernetes, Troubleshooting - Kategorien: Kubernetes # Kubernetes: FreeDiskSpaceFailed – Kubelet kann Images nicht bereinigen ## Symptom Im Event-Log eines Kubernetes-Nodes erscheint folgende Warnung wiederholt: ``` Warning FreeDiskSpaceFailed kubelet (combined from similar events): Failed to garbage collect required amount of images. Attempted to free 2015210700 bytes, but only found 0 bytes eligible to free. ``` Die Meldung tritt auf, obwohl auf dem Node noch ausreichend Speicherplatz vorhanden ist (z. B. 25 % frei). --- ## Ursache Der Kubelet-Prozess führt regelmäßig eine Image Garbage Collection durch, um ungenutzten Speicher auf der Image-Partition freizugeben. Dabei gelten zwei zentrale Schwellenwerte: - **imageGCHighThresholdPercent** (Standard: 85 %): Ab dieser Belegung startet die Bereinigung. - **imageGCLowThresholdPercent** (Standard: 80 %): Bis zu diesem Wert wird versucht, Speicher freizuräumen. Die Fehlermeldung entsteht, wenn Kubelet versucht, Speicher freizugeben, aber **keine löschbaren Images findet**. Ein Image gilt nur dann als löschbar, wenn es von keinem Container referenziert wird – auch nicht von gestoppten (Exited) Containern. ### Häufigste Ursachen im Detail **1. Exited Container referenzieren Images** Gestoppte Container bleiben auf dem Node bestehen und halten eine Referenz auf ihr Image. Solange der Container existiert, ist das zugehörige Image vor dem Garbage Collector geschützt. Bei Nodes mit vielen Workloads sammeln sich über Wochen Dutzende gestoppter Container an. **2. Kubelet Container-GC ist nicht aggressiv genug konfiguriert** Standardmäßig ist die Container-GC bei einigen Distributionen (z. B. K3s) konservativ eingestellt. Alte Container werden nicht automatisch entfernt, was dazu führt, dass auch deren Images nicht bereinigt werden können. **3. Viele verschiedene Workloads auf einem Node** Wenn nahezu alle vorhandenen Images von laufenden Pods referenziert werden, gibt es schlicht nichts zu löschen. Der GC meldet trotzdem einen Fehler, weil er sein Ziel (Speicher unter den Low-Threshold bringen) nicht erreicht. **4. Dangling Images und Build-Artefakte** Untagged Images (``) aus fehlgeschlagenen oder abgebrochenen Pulls belegen Speicher, werden aber vom normalen GC-Zyklus nicht immer erfasst. **5. Logs und Volumes belegen Speicherplatz** Wenn Container-Logs oder emptyDir-Volumes die Disk füllen, kann die Gesamtbelegung den Threshold überschreiten. Der Image-GC kann aber nur Images löschen, keine Logs – daher findet er nichts zum Freigeben. --- ## Diagnose Alle folgenden Befehle werden direkt auf dem betroffenen Node ausgeführt. ### 1. Gestoppte Container prüfen ```bash crictl ps -a --state exited ``` Zeigt alle gestoppten Container mit Alter und zugehörigem Pod. Wenn hier Einträge stehen, die Wochen oder Monate alt sind, ist das die wahrscheinlichste Ursache. ### 2. Image-Belegung ermitteln ```bash crictl imagefsinfo crictl images | sort -k3 -h ``` Zeigt die Gesamtbelegung der Image-Partition und listet alle Images sortiert nach Größe. ### 3. Untagged/Dangling Images identifizieren ```bash crictl images | grep "" ``` Diese Images haben keinen Tag mehr und sind Kandidaten für die Bereinigung. ### 4. Speicherverbrauch nach Verzeichnis prüfen ```bash du -sh /var/lib/containerd/* 2>/dev/null du -sh /var/lib/rancher/* 2>/dev/null du -sh /var/lib/kubelet/* du -sh /var/log/pods/* | sort -h | tail -20 ``` Damit lässt sich feststellen, ob die Belegung tatsächlich von Images stammt oder ob Logs oder andere Daten den Speicher belegen. ### 5. Kubelet-GC-Konfiguration prüfen ```bash cat /var/lib/kubelet/config.yaml | grep -A5 -i "image\|container\|eviction" ``` --- ## Lösung ### Sofortmaßnahme: Gestoppte Container und ungenutzte Images entfernen ```bash # Alle gestoppten Container entfernen crictl rm $(crictl ps -a -q --state exited) # Nicht mehr referenzierte Images bereinigen crictl rmi --prune ``` Nach dem Entfernen der gestoppten Container verlieren deren Images ihre Referenz und werden durch `crictl rmi --prune` oder den nächsten GC-Zyklus automatisch gelöscht. ### Dauerhafte Lösung: Kubelet Container-GC konfigurieren Die Kubelet-Konfiguration steuert, wie aggressiv alte Container entfernt werden. **Für Standard-Kubernetes** (`/var/lib/kubelet/config.yaml`): ```yaml imageGCHighThresholdPercent: 80 imageGCLowThresholdPercent: 70 imageMinimumGCAge: 2m evictionHard: imagefs.available: "15%" ``` **Für K3s** (`/etc/rancher/k3s/config.yaml`): ```yaml kubelet-arg: - "maximum-dead-containers-per-container=1" - "maximum-dead-containers=20" - "minimum-container-ttl-duration=1m" ``` Nach der Änderung den Kubelet- bzw. K3s-Dienst neu starten: ```bash # K3s sudo systemctl restart k3s # Server-Node sudo systemctl restart k3s-agent # Agent-Node # Standard-Kubernetes sudo systemctl restart kubelet ``` ### Optional: Regelmäßige Bereinigung per CronJob Als zusätzliche Absicherung kann ein systemd-Timer oder Kubernetes-CronJob gestoppte Container und ungenutzte Images regelmäßig entfernen: ```bash # /etc/cron.d/container-cleanup 0 3 * * * root crictl rm $(crictl ps -a -q --state exited) 2>/dev/null; crictl rmi --prune 2>/dev/null ``` Alternativ als Kubernetes DaemonSet, das auf allen Nodes läuft und regelmäßig aufräumt. --- ## Zusammenfassung | Schritt | Aktion | Befehl | |---------|--------|--------| | Diagnose | Gestoppte Container auflisten | `crictl ps -a --state exited` | | Diagnose | Image-Belegung prüfen | `crictl imagefsinfo` | | Diagnose | Speicherverbrauch prüfen | `du -sh /var/lib/containerd/*` | | Sofort | Container aufräumen | `crictl rm $(crictl ps -a -q --state exited)` | | Sofort | Images aufräumen | `crictl rmi --prune` | | Dauerhaft | Kubelet-GC konfigurieren | `maximum-dead-containers-per-container=1` | | Optional | Cron-basierte Bereinigung | Cron-Job oder DaemonSet einrichten | --- ## Verwandte Themen - Kubernetes Dokumentation: [Garbage Collection](https://kubernetes.io/docs/concepts/architecture/garbage-collection/) - Kubelet Konfiguration: [Kubelet Configuration (v1beta1)](https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/) --- ## Shell Tools - URL: https://blog.kuepper.nrw/posts/2026-03-02-shell-tools/ - Datum: 2026-03-02 - Autor: ruediger - Tags: MacOs, Shell, Tools, Terminal - Kategorien: MacOS, Terminal # ripgrep Ersetze `grep` durch `ripgrep (rg)` — Suche in Millisekunden `grep` ist zuverlässig, aber langsam bei großen Projekten. `ripgrep`: Ein Rust-basiertes Such-Tool, das bis zu 100× schneller als `grep` ist. Alter Weg: ``` grep -r "main" . ``` Neuer Weg: ``` rg main ``` Warum es rockt: * Durchsucht 10.000+ Dateien in Millisekunden * Ignoriert `node_modules/` und `.git/` automatisch * Schön farbige Ergebnisse * Regex- und Dateityp-Filterung: `rg --type js "function"` Beispiel-Geschwindigkeitstest: * `grep`: 3,8 Sekunden * `rg`: 0,07 Sekunden Installation: ``` sudo apt install ripgrep # oder brew install ripgrep ``` 💡 Profi-Tipp: Integriere `rg` mit `fzf` für eine blitzschnelle Fuzzy-Dateisuche über Projekte hinweg. # Ersetze `cat` durch `bat` — Syntax-Highlighting für die Seele `cat` ist gut für schnelle Dateiansichten, aber reiner Text ist nicht ideal beim Lesen von Code. `bat` löst das elegant mit Syntax-Highlighting, Git-Integration und Zeilennummern. Alter Weg: ``` cat app.js ``` Neuer Weg: ``` bat app.js ``` Warum du es lieben wirst: * Syntax-Highlighting für über 100 Sprachen * Zeigt Zeilennummern und Git-Diff-Informationen * Farbige, leicht lesbare Ausgabe Beispiel: ``` 1 │ function sayHello() { 2 │ console.log("Hello, World!"); 3 │ } ``` Installation: ``` sudo apt install bat # oder brew install bat ``` Auch wenn es etwas langsamer als `cat` ist, machen die Klarheit und Lesbarkeit das jedes Mal wett. --- ## QuillDrop - Mein eigenes Blog-CMS ist fertig - URL: https://blog.kuepper.nrw/posts/2026-02-19-quilldrop-mein-eigenes-blog-cms/ - Datum: 2026-02-19 - Autor: ruediger - Tags: Go, Blog, CMS, QuillDrop, Open-Source, Self-Hosted - Kategorien: Internet, Projekte # QuillDrop - Mein eigenes Blog-CMS ist fertig ![QuillDrop](/static/icons/favicon.png) Jekyll, Octopress, Hugo, InkProject und dann noch mein eigenes CMS in Ruby - die Liste der Systeme, auf denen dieser Blog im Laufe der Jahre gelaufen ist, ist lang. Jetzt kommt ein neues dazu, aber dieses Mal ist es anders. Dieses Mal habe ich das Ding komplett selbst geschrieben: **QuillDrop**. ## Warum schon wieder ein neues CMS? Die Kurzfassung: Weil keines genau das gemacht hat, was ich wollte - und gleichzeitig nichts, was ich nicht wollte. Hugo ist großartig, aber die Template-Sprache ist eine Welt für sich und das Debugging bei Problemen kann schnell frustrierend werden. Mein vorheriges Ruby-basiertes CMS hat funktioniert, war aber mit der Zeit zu komplex geworden. Was ich wollte, war simpel: Markdown-Dateien schreiben, ein Binary starten, fertig. Keine Node-Module, keine Build-Pipeline, keine externe Datenbank. Also habe ich QuillDrop gebaut. ## Was ist QuillDrop? QuillDrop ist ein Blog-CMS, geschrieben in Go. Ein einzelnes Binary, das zwei Dinge kann: - **`quilldrop serve`** startet einen lokalen HTTP-Server zum Entwickeln und Vorschauen - **`quilldrop generate`** generiert eine komplett statische Website für das Deployment Das war es. Keine Plugins, keine Themes zum Installieren, kein Package-Manager. Die Templates sind direkt im Binary eingebettet (`go:embed`), das CSS ist Vanilla CSS, das JavaScript ist Vanilla JavaScript. Die einzigen Go-Dependencies sind Goldmark für Markdown-Rendering und ein YAML-Parser für die Konfiguration. ## Die komplette Migration Der Blog ist jetzt vollständig migriert. Alle Posts, alle statischen Seiten, alle Bilder, alle Funktionen die vorher schon da waren - alles läuft jetzt auf QuillDrop. Und das sind nicht wenige: ### Posts und Inhalte Alle bestehenden Markdown-Posts wurden 1:1 übernommen. Das YAML-Frontmatter ist kompatibel mit Hugo, sodass die Migration im Grunde nur ein Kopieren der Dateien war. Cover-Bilder, Tags, Kategorien, Entwürfe - alles wird unterstützt. ### Statische Seiten Seiten wie "Über mich" und die Projektseiten (VM-Tracker, QuillDrop selbst) liegen als Markdown im `sites/`-Verzeichnis. Verschachtelte Verzeichnisse werden automatisch erkannt. ### Navigation mit Dropdown-Menüs Die komplette Navigation wird über die `config.yaml` konfiguriert. Dropdown-Menüs für Unterseiten wie bei "Projekte" sind kein Problem - einfach `children` definieren und fertig. ### Tags und Kategorien Tags hatte das vorherige System auch, Kategorien sind neu dazugekommen. Beides wird aus dem Frontmatter gelesen und bekommt eigene Übersichtsseiten unter `/tags/` und `/categories/`. ### Dark/Light Theme Der Dark Mode ist Standard, aber es gibt einen Toggle für ein helles Theme. Die Auswahl wird im Browser gespeichert und bleibt nach einem Reload erhalten. ### Pagination Bei mittlerweile über 80 Posts ist eine Pagination essentiell. QuillDrop zeigt eine intelligente Seitennummerierung mit Ellipsis: `1 ... 10 11 12 13 14 ... 23`. Die erste Seite wird SEO-freundlich von `/page/1/` auf `/` umgeleitet. ### RSS Feed Der RSS Feed liegt unter `/index.xml` - die gleiche URL wie vorher, damit bestehende Abonnenten nichts umstellen müssen. ### Volltextsuche Das war eines der Features, die ich vom vorherigen Blog unbedingt mitnehmen wollte. Die Suche funktioniert komplett client-seitig: Beim Generieren wird eine `search-index.json` erstellt, die dann im Browser durchsucht wird. Lazy Loading sorgt dafür, dass der Index erst geladen wird, wenn die Suche geöffnet wird. Unterstützt werden mehrere Suchbegriffe (UND-Verknüpfung), und mit `Ctrl+K` kann man die Suche direkt öffnen. ### Inhaltsverzeichnis Posts mit `toc: true` im Frontmatter bekommen ein automatisch generiertes Inhaltsverzeichnis. Das TOC erkennt H1-, H2- und H3-Überschriften und rückt relativ ein. ### Artikel-Navigation Am Ende jedes Posts gibt es jetzt Links zum neueren und älteren Artikel. Beim neuesten Post wird nur "Älterer Artikel" angezeigt, beim ältesten nur "Neuerer Artikel". ## Technik unter der Haube Der Stack ist bewusst minimal gehalten: - **Go** mit der Standard Library für HTTP-Server und Templates - **Goldmark** für Markdown-Rendering mit GFM, Emoji-Support und Syntax-Highlighting über Chroma - **Vanilla CSS** mit Custom Properties für das Theming - **Vanilla JavaScript** für Theme-Toggle, Suche und TOC-Generierung - **Keine Datenbank** - das Dateisystem ist die einzige Datenquelle Die gesamte Konfiguration läuft über eine einzige `config.yaml`. Keine versteckten Konfigurationsdateien, keine Environment-Variables die man vergessen könnte. ## Dual-Mode: Entwicklung und Produktion Für die lokale Entwicklung starte ich `quilldrop serve` und sehe sofort jede Änderung im Browser. Für das Deployment auf den Webserver läuft `quilldrop generate` und spuckt statische HTML-Dateien aus, die direkt auf Nginx, Apache oder einem CDN liegen können. Das ist das Schöne an einem Static Site Generator: Die fertige Seite ist nur noch HTML, CSS und ein bisschen JavaScript. Kein PHP, kein Ruby, kein Node.js auf dem Server. Einfach Dateien ausliefern. ## Fazit Nach Jahren mit verschiedenen CMS-Systemen läuft der Blog jetzt auf meiner eigenen Software. Alles ist migriert, alle Funktionen sind da und das System macht genau das, was es soll - nicht mehr und nicht weniger. Der Code ist Open Source und auf [GitHub](https://github.com/9it-full-service/quilldrop) [^verfügbar]. Wer einen schnellen, minimalistischen Blog ohne Overhead sucht, kann sich QuillDrop gerne anschauen. > Write. Save. Published. [^verfügbar]: ~Noch nicht, das kommt die Tage noch. Es gibt ein Update, so bald das Repo öffentlich ist.~ Ist jetzt verfügbar. --- ## VM-Tracker - Client und API-server jetzt Self-Hosted - URL: https://blog.kuepper.nrw/posts/2025-11-06-vm-tracker-client-und-api-server-jetzt-self-hosted/ - Datum: 2025-11-06 - Aktualisiert: 2025-11-06 - Autor: ruediger - Tags: Qemu, VM, Overview, Self-Hosted - Kategorien: Internet # :satellite: VM-Tracker - Downloads für Self-Hosted Installationen verfügbar :sunglasses: Im [letzten Post](/posts/2025-11-01-vm-tracker-automatische-registration-und-monitoring-system/) hatte ich den VM-Tracker schon einmal vorgestellt und in den letzten Tagen noch selbst einiges getestet. Jetzt ist alles online und kann installiert werden. So das jeder den VM-Tracker selbst betreiben kann und die eigenen RaspberryPi, VMs, Dedicated-Server, NAS-Systeme oder Shellys Tracken kann. Ja, auch Shellys können sich registrieren und so Überwacht werden. Gehe zu Settings → Scripts → Add Script und erstelle: *Den Domainnamen noch anpassen!* ```javascript function registerDevice() { // Device Info abrufen Shelly.call("Shelly.GetDeviceInfo", {}, function(deviceInfo) { let hostname = deviceInfo.id || "unknown-shelly"; // z.B. "shellyplus1pm-a1b2c3d4" Shelly.call("Shelly.GetStatus", {}, function(result) { let ip_address = null; let interface_name = null; if (result.eth && result.eth.ip) { ip_address = result.eth.ip; interface_name = "eth0"; } else if (result.wifi && result.wifi.sta_ip) { ip_address = result.wifi.sta_ip; interface_name = "wlan0"; } if (!ip_address) { print("Keine IP gefunden!"); return; } Shelly.call( "HTTP.POST", { url: "https://vm-tracker.example.com/api/register", content_type: "application/json", body: JSON.stringify({ hostname: hostname, ip_address: ip_address, interface: interface_name }) }, function(result, error_code, error_message) { if (error_code === 0) { print("✓ Registriert: ", hostname, " - ", ip_address); } else { print("✗ Fehler: ", error_message); } } ); }); }); } // Bei Netzwerk-Verbindung ausführen Shelly.addEventHandler(function(event) { if (event.component === "wifi" && event.info.status === "got ip") { print("WiFi verbunden, registriere..."); Timer.set(2000, false, registerDevice); // 2s Verzögerung } if (event.component === "eth" && event.info.status === "up") { print("Ethernet verbunden, registriere..."); Timer.set(2000, false, registerDevice); } }); // Zusätzlich alle 10 Minuten Timer.set(30000, true, registerDevice); ``` Das Script kann man einfach auf jeden Shelly erstellen und sie melden sich dann einfach an. ![VM-Tracker registrierter Shelly](https://s3fs.kuepper.nrw/blog-images/posts/2025/11/vm-tracker-shelly.webp) # VM-Tracker Server installieren. Damit man selbst einen Endpunkt für die Clients, unter einer eigenen Domain hat, kann der API-Server jetzt auch Self-Hosted betrieben werden. Anleitungen gibt es für: * [Binary Installation](https://vm-tracker.kuepper.nrw/docs/de/api/installation_binary/) * [Docker](https://vm-tracker.kuepper.nrw/docs/de/api/installation_docker/) * [Kubernetes](https://vm-tracker.kuepper.nrw/docs/de/api/installation_kubernetes/) * [Helm](https://vm-tracker.kuepper.nrw/docs/de/api/installation_helm/) * [Kustomization](https://vm-tracker.kuepper.nrw/docs/de/api/installation_kustomization/) * [FluxCD Kustomization](https://vm-tracker.kuepper.nrw/docs/de/api/installation_fluxcd_kustomization/) * [FluxCD Helm Release](https://vm-tracker.kuepper.nrw/docs/de/api/installation_fluxcd_helm_release/) Bei allen Installationen kann man diese ENV-Variabeln setzen: * API_BASE_URL=https://vm-tracker.example.com * BASE_URL=https://vm-tracker.example.com Diese werden benutzt für z.B. Ingress, um die API über die Domain erreichbar zu machen. Ausserdem wird damit das Installations-Skript erstellt, damit die Clients sich mit der richtigen API verbinden. Dadurch kann der Client schnell und unkompliziert auf allen Sytemen installiert werden. # VM-Tracker Client installieren. Welche Möglichkeiten bei der Client Installation zur Verfügung stehen ist hier beschrieben: * [Binary](https://vm-tracker.kuepper.nrw/docs/de/client/installation_binary/) * [Skript](https://vm-tracker.kuepper.nrw/docs/de/client/installation_script/) * [Systemd](https://vm-tracker.kuepper.nrw/docs/de/client/installation_systemd/) * [Cloud-Init](https://vm-tracker.kuepper.nrw/docs/de/client/installation_cloud_init/) * [Shelly Script](https://vm-tracker.kuepper.nrw/docs/de/client/installation_shelly/) Die komplette Dokumentation in deutsch ist [hier](https://vm-tracker.kuepper.nrw/docs/de/) und die englische [hier](https://vm-tracker.kuepper.nrw/docs/en/). ## VM-Manager und Vm-Tracker in Aktion --- ## VM-Tracker - Automatische Registration and Monitoring-System - URL: https://blog.kuepper.nrw/posts/2025-11-01-vm-tracker-automatische-registration-und-monitoring-system/ - Datum: 2025-11-01 - Aktualisiert: 2025-11-10 - Autor: ruediger - Tags: Qemu, VM, Overview - Kategorien: Internet # :satellite: VM-Tracker - das GetHomepage für Server und VMs :sunglasses: ## VM-Manager - VM mit Cloud-Init Templates erstellen Um Qemu-VMs schnell aufsetzen zu können habe ich ein Tool geschrieben mit dem ich schnell neue Server für verschiedene Zwecke erstellen kann. Da ich für Kubernetes, GitHub Runner, Nginx Webserver und andere Server in dem Tool Templates habe sind neue Server schnell aufgesetzt. Die Templates können erweitert werden oder neue erstellt werden. Wenn Server erstellt werden wird immer eine Serielle-Console erstellt und über ein Webinterface erreichbar gemacht. Das ist natürlich nur intern erreichbar und wenn ich von unterwegs einen Server starte weiß ich die IP erst einmal nicht. Ich kann natürlich über VPN zugreifen, aber manchmal will man einfach etwas starten und dann einfach drauf zugreifen oder wenn es extern erreichbar sein soll, einfach die IP per Port-Forward freigeben. Der VM-Manger wird demnächst Online gestellt und kann dann von jedem genutzt werden. ## VM-Tracker für bessere Rechner, Server und VM Übersicht :monocle_face: Damit ich sehe welche VMs aktuell auf den Rechnern hier gestartet sind habe ich ein Tool geschrieben das: * Einfach das default Netzwerkinterface sucht, * Die IP-Adresse ausliest * Und dann mit dem Hostname an eine API sendet. Die API merkt sich alle Server die sich melden. Ein Healthcheck-Tracker prüft die Meldungen alle 30 Sekunden und meldet sich ein Server 60 Sekunden nicht wird er als Offline angezeigt. Die Server können auch aus der List gelöscht werden. Offline Server verschwinden komplett, sie melden sich ja nicht mehr. Server die Online sind melden sich ja regelmässig und werden nach kurzer Zeit wieder auftauchen. Das Webinterface zeigt alle Server in einer schönen Übersicht an: ![VM-Tracker Overview](https://s3fs.kuepper.nrw/blog-images/posts/2025/11/vm-tracker-overview.webp) ## Installation Update: Links zu den Installationsanleitungen sind in diesem [Blogpost](/posts/2025-11-06-vm-tracker-client-und-api-server-jetzt-self-hosted/) Den VM-Tracker-Client kann man einfach auf den Server kopieren und ausführen. Die Binaries sind für verschiedene Betriebsysteme und Architekturen verfügbar: + vm-tracker-client-386 + vm-tracker-client-amd64 + vm-tracker-client-arm64 + vm-tracker-client-armv6 + vm-tracker-client-armv7 + vm-tracker-client-darwin + vm-tracker-client-darwin-amd64 + vm-tracker-client-darwin-arm64 + vm-tracker-client-freebsd-amd64 + vm-tracker-client-windows-386.exe + vm-tracker-client-windows-amd64.exe VM-Tracker Client Ausgeführt: ```bash ./vm-tracker-client-darwin-arm64 -api https://vm-tracker.kuepper.nrw -interface en0 -interval 30 2025/11/01 09:58:48 VM Tracker Client starting... 2025/11/01 09:58:48 API Server: https://vm-tracker.kuepper.nrw 2025/11/01 09:58:48 Interface: en0 2025/11/01 09:58:48 Update interval: 30s 2025/11/01 09:58:48 Successfully registered with API server (hostname: mbair-rk.local, IP: 10.0.2.206) ``` Das Interface automatisch setzen lassen: MacOS: ```bash INTERFACE=$(route get 1.1.1.1 | awk '/interface:/ {print $2; exit}') ./vm-tracker-client-darwin-arm64 -api https://vm-tracker.kuepper.nrw -interface ${INTERFACE} -interval 30 ``` Linux: ```bash INTERFACE=$(route get 1.1.1.1 | awk '/dev/ {print $5; exit}') ./vm-tracker-client-darwin-arm64 -api https://vm-tracker.kuepper.nrw -interface ${INTERFACE} -interval 30 ``` Damit man aber nicht alles manuell kopieren und ausführen muss gibt es auch ein Script für die Installation. Die Installation kann dann einfach schnell per SSH ausgeführt werden oder so wie bei mir in den Templates von meinem VM-Tool eingefügt werden. Manuell ausführen: ```bash wget -O- https://vm-tracker.kuepper.nrw/download/install-tracker.sh | bash ``` Automatisiert in Cloud-Init: ```yaml { title = "cloudinit-templates/nginx/user-data/user-data.yaml" } ... runcmd: - wget -O- https://vm-tracker.kuepper.nrw/download/install-tracker.sh | bash - systemctl enable qemu-guest-agent - systemctl start qemu-guest-agent - echo "Cloud-init Konfiguration abgeschlossen" > /var/log/cloudinit-done.log ... ``` Damit wird der VM-Tracker Client automatisch installiert. Es wird das Binary für die richtige Zielarchitektur heruntergeladen und an die richtige Stelle kopiert. Zusätzlich wird ein Systemd Service angelegt, aktiviert und gestartet. > Hinweis: Das Installationsscript funktinoiert aktuell nur auf Linus Systemen. MacOS und Windows müssen noch integriert werden. So bald auch MacOS hinzugefügt ist und weitere Änderungen gemacht sind wird der VM-Tracker auch Public gestellt. Das ist dann aber auch nicht nur der Client, auch die API mit dem Webinterface wird verfügbar sein. Dann kann jeder seinen eigenen Tracker betreiben und so schnell einen Überblick über seine Infrastruktur erhalten, Probleme mit Servern oder VMs sehen. ## VM-Manager und Vm-Tracker in Aktion Es sind zwei Webserver vorhanden, web001 und web002. Jetzt werden 2 weitere benötigt. Die Konfiguration dafür ist schon vorbereitet und die VMs können erstellt werden. Nach dem Start wird dann mit Cloud-Init komplett konfiguriert und auch der VM-Tracker gestartet. So sieht man dann auch nach kurzer Zeit die neuen Server in der Übersicht des VM-Trackers. --- ## k3s System-Upgrade-Controller Fail, Restore in 5 Minuten mit FluxCD - URL: https://blog.kuepper.nrw/posts/2025-10-23-k3s-system-upgrade-controller-fail-restore-in-5-minuten-mit-fluxcd/ - Datum: 2025-10-23 - Aktualisiert: 2025-10-23 - Autor: ruediger - Tags: Kubernetes, k3s, fluxcd, Recovery, GitOps - Kategorien: Kubernetes Wenn der k3s System Upgrade Controller eine Recovery-Übung erzwingt: So wie vorletzte Woche, als ich eigentlich einfach nur ein Upgrade von k3s auf die aktuellste Version machen wollte. Nur ist das irgendwie etwas schiefgelaufen. Alle Services liefen noch, die Container waren alle noch da und alles war erreichbar. Das Einzige, was nicht gestartet ist, war k3s. Kurzes Checken der Logfiles zeigte, dass die Node-IPs nicht zu denen in der Cluster-Config passten. Es wurde versucht, mit der Loadbalancer-IP zu verbinden. Ändern in der Datenbank wollte irgendwie nicht funktionieren. Loadbalancer-IP kurz getrennt, sodass nur noch die Node-IP auf den einzelnen Nodes zu sehen war. Auch damit wollte k3s nicht starten. Also den System-Upgrade-Controller einfach mal zurückrollen lassen. Was dann auch geklappt hat. Alles wieder ok, k3s wieder gestartet und nutzbar. Nur waren im Log noch ein paar Sachen, die etwas stutzig machten. Nichts Kritisches, aber es waren selbst noch diese Einträge zu sehen, nachdem ich eine der Applications, für die Fehler angezeigt wurden, auf 0 skaliert und sogar komplett entfernt hatte. Der Cluster lief danach noch ein paar Tage ohne Probleme. Und da ich mit FluxCD jetzt alles in einem GitOps-GitHub-Repository habe, war eh schon länger geplant, auch meinen privaten Production-Kubernetes-Cluster neu aufzusetzen. Das ist mit den Clustern labor01, dev und stage schon mehrfach gemacht worden. Der Prod-Cluster war zwar schon die ganze Zeit mit im Repo und alle Komponenten migriert, aber der Teufel liegt ja immer im Detail. Habe ich wirklich an alles gedacht und ist wirklich alles im Git? Außerdem: Was ist mit den Daten, die nicht auf dem NFS-Server liegen? Die Daten der Volumes liegen hauptsächlich auf NFS-Shares, außer dem vom Garage S3 Storage. Dafür habe ich dann auch gleich noch ein Backup- und Restore-Tool erstellt. Dazu die Tage noch mehr. Nachdem alle Daten aus dem Garage auch noch gesichert wurden, war die Zeit für das Löschen des Kubernetes-Clusters gekommen. Gefolgt von Server Create plus Cloud-Init. Damit wurde alles wieder erstellt. Das Cloud-Init installiert dann alles, was benötigt wird: k3s, helm, k9s, FluxCD. FluxCD wird dann per Bootstrap automatisch mit dem GitOps-Repo verbunden und installiert dann alle Ressourcen. Nach etwa 3-4 Minuten ist der Cluster wieder komplett gestartet und alles wieder installiert und aktiv. Während der Cert-Manager mit Cloudflare DNS API sich kurz noch um die Zertifikate gekümmert hat, habe ich mich kurz Garage gewidmet. Den Garage-INIT-Job getriggert, der kümmert sich darum, dass sich alle Garage-Nodes gegenseitig kennen. Anschließend das Restore für Garage ausgeführt und alle Buckets mit Daten und die Secrets waren wieder da. Das Ganze war schon sehr gut und war genau so, wie es sein soll. Und ja, es gab 2-3 Kleinigkeiten, die waren noch nicht im Git. Die wurden dann auch gleich noch erledigt. So wird dann bei der nächsten Installation direkt alles fertig sein. Ich kann auch den Cluster aufsetzen, aber beim ersten Start werden die Zertifikate noch gar nicht generiert. So kann ich erst einmal alles prüfen und ein paar Tests machen. Wenn alles ok ist, wird der Cert-Manager aktiv geschaltet und das Loadbalancer-Target auf den neuen geändert. So ist der Cluster innerhalb ein paar Sekunden ausgetauscht. Dass der System-Upgrade-Controller fehlgeschlagen ist, war ärgerlich, aber das war auch irgendwie gut so. Eine Recovery war schon länger geplant, da es bei Test-Clustern im HomeLab und auch für die anderen Stages immer gut lief. Es sind immer ein paar Sachen aufgefallen und behoben worden. Da habe ich aber auch eine Neuinstallation einfach mal so zwischendurch gemacht. Beim Production-Cluster war immer etwas Respekt davor. Aber jetzt musste es einfach gemacht werden. Es lief zwar alles, aber es waren Fehler zu sehen, bei denen ich nicht wusste, woher sie kommen und wieso. Was macht das sonst noch im Hintergrund – könnte es irgendwann doch den ganzen Cluster einfach zerreißen und alles wäre offline? --- ## Der ultimative Guide zum caffeinate-Befehl auf dem Mac - URL: https://blog.kuepper.nrw/posts/2025-10-18-der-ultimative-guide-zum-caffeinate-befehl-auf-dem-mac/ - Datum: 2025-10-18 - Aktualisiert: 2025-10-18 - Autor: ruediger - Tags: Mac, caffeinate - Kategorien: Mac # Schluss mit ungewolltem Schlafmodus: Der ultimative Guide zum caffeinate-Befehl auf dem Mac ## Die Situation kennt jeder Mac-Nutzer Du lädst gerade eine große Datei herunter, renderst ein Video oder führst ein wichtiges Backup durch – und plötzlich: Der Mac geht in den Schlafmodus. Der Download bricht ab, das Rendering stoppt, und du musst von vorne beginnen. Frustrierend, oder? Sicher, du könntest jedes Mal in die Systemeinstellungen gehen und die Energiesparoptionen anpassen. Aber mal ehrlich: Wer denkt daran, das danach wieder zurückzustellen? Und wer möchte ständig in den Einstellungen herumklicken? Die Lösung liegt näher, als du denkst – und sie heißt **caffeinate**. ## Was ist caffeinate überhaupt? Der Name ist Programm: caffeinate (vom englischen "to caffeinate" – mit Koffein versorgen) hält deinen Mac wach, als hättest du ihm einen doppelten Espresso spendiert. Es ist ein in macOS integriertes Kommandozeilentool, das seit OS X 10.8 Mountain Lion standardmäßig dabei ist. Das Schöne daran: Du musst nichts installieren, keine zusätzliche Software kaufen oder komplizierte Konfigurationen vornehmen. caffeinate ist bereits da und wartet nur darauf, genutzt zu werden. ## Die Basics: So einfach geht's ### Der Schnellstart Öffne das Terminal (zu finden über Spotlight mit ⌘ + Leertaste, dann "Terminal" eingeben) und tippe: ```bash caffeinate ``` Das war's schon! Dein Mac bleibt nun wach, bis du: - Das Terminal-Fenster schließt - Den Befehl mit `Ctrl + C` beendest - Den Mac neu startest ### Zeit ist Geld: Der Timer-Modus Manchmal weißt du genau, wie lange dein Mac wach bleiben soll. Hier kommt der `-t` Parameter ins Spiel: ```bash caffeinate -t 3600 ``` Diese Zeile hält deinen Mac für genau 3600 Sekunden (= 1 Stunde) wach. Danach kehrt er automatisch zu den normalen Energiespareinstellungen zurück. Praktisch, nicht wahr? Weitere Beispiele: - 30 Minuten: `caffeinate -t 1800` - 2 Stunden: `caffeinate -t 7200` - 8 Stunden (eine Arbeitstag): `caffeinate -t 28800` ## Die Profi-Optionen: Was caffeinate noch kann ### Die verschiedenen Modi caffeinate bietet verschiedene Flags, mit denen du genau steuern kannst, *wie* dein Mac wach bleibt: **`-d` (Display)**: Verhindert, dass das Display schlafen geht ```bash caffeinate -d ``` Perfekt für Präsentationen oder wenn du ein Tutorial-Video anschaust. **`-i` (Idle)**: Verhindert den Idle-Sleep (System bleibt aktiv) ```bash caffeinate -i ``` Ideal für Server-Anwendungen oder lange Berechnungen. **`-m` (Disk)**: Verhindert, dass die Festplatte in den Ruhezustand geht ```bash caffeinate -m ``` Nützlich bei kontinuierlichen Lese-/Schreibvorgängen. **`-s` (System)**: Hält das System wach, auch wenn das Display aus ist ```bash caffeinate -s ``` Gut für Downloads über Nacht. **`-u` (User)**: Simuliert Benutzeraktivität ```bash caffeinate -u -t 10 ``` Dieser spezielle Modus simuliert für 10 Sekunden Benutzeraktivität und kann helfen, wenn andere Modi nicht greifen. ### Kombinationen für maximale Kontrolle Du kannst mehrere Flags kombinieren: ```bash caffeinate -dims -t 3600 ``` Dies hält Display, System und Festplatte für eine Stunde wach und verhindert den Idle-Sleep. ## Praktische Anwendungsfälle ### 1. Downloads über Nacht ```bash caffeinate -s curl -O https://example.com/grossedatei.zip ``` caffeinate läuft hier nur so lange, wie der Download dauert. ### 2. Backup-Prozesse ```bash caffeinate -i rsync -av /Quelle/ /Ziel/ ``` Stellt sicher, dass dein Backup vollständig durchläuft. ### 3. Video-Rendering ```bash caffeinate -di ffmpeg -i input.mov output.mp4 ``` Hält Display und System während der Videokonvertierung wach. ### 4. Präsentationsmodus ```bash caffeinate -d -t 5400 ``` 90 Minuten lang bleibt das Display an – perfekt für längere Meetings. ### 5. Software-Updates ```bash caffeinate -i softwareupdate -ia ``` Installiert alle verfügbaren Updates, ohne dass der Mac zwischendurch einschläft. ## Pro-Tipps für Power-User ### Tipp 1: Alias erstellen Füge diese Zeilen zu deiner `~/.zshrc` oder `~/.bash_profile` hinzu: ```bash alias awake="caffeinate -d" alias awake1h="caffeinate -d -t 3600" alias awake2h="caffeinate -d -t 7200" ``` Nun kannst du einfach `awake1h` tippen für eine Stunde Wachzeit. ### Tipp 2: Mit Assertion-Namen arbeiten Du kannst deinen caffeinate-Sessions Namen geben: ```bash caffeinate -i -w $$ & ``` Dies erstellt eine Assertion, die mit deiner aktuellen Shell-Session verknüpft ist. ### Tipp 3: Status überprüfen Willst du wissen, welche Prozesse deinen Mac wach halten? ```bash pmset -g assertions ``` Dieser Befehl zeigt dir alle aktiven "Wachhalter" an. ### Tipp 4: caffeinate im Hintergrund ```bash caffeinate -i & ``` Das `&` am Ende lässt caffeinate im Hintergrund laufen. Du kannst das Terminal weiter nutzen. Mit `fg` holst du es wieder in den Vordergrund, um es mit Ctrl+C zu beenden. ### Tipp 5: Integration in Skripte ```bash #!/bin/bash # Mein Backup-Skript caffeinate -i bash << 'EOF' echo "Backup startet..." rsync -av ~/Documents/ /Volumes/Backup/Documents/ echo "Backup abgeschlossen!" EOF ``` ## Troubleshooting: Wenn's mal nicht klappt **Problem**: caffeinate scheint nicht zu funktionieren - **Lösung**: Überprüfe mit `pmset -g` deine Energieeinstellungen. Manche Unternehmens-Policies können caffeinate überschreiben. **Problem**: Terminal schließt sich versehentlich - **Lösung**: Nutze `nohup caffeinate &` um caffeinate auch nach dem Schließen des Terminals weiterlaufen zu lassen. **Problem**: Unsicher, ob caffeinate läuft - **Lösung**: `pgrep caffeinate` zeigt dir die Prozess-ID, wenn caffeinate aktiv ist. ## Alternativen zu caffeinate Falls du eine GUI bevorzugst, gibt es auch Apps: - **Amphetamine** (kostenlos im App Store) - **Caffeine** (klassische Menubar-App) - **KeepingYouAwake** (Open Source Alternative) Aber ehrlich: Warum eine zusätzliche App installieren, wenn caffeinate schon da ist? ## Fazit: Ein unterschätztes Power-Tool caffeinate ist eines dieser Tools, von dem viele Mac-Nutzer nie erfahren – dabei kann es den Arbeitsalltag erheblich erleichtern. Keine abgebrochenen Downloads mehr, keine unterbrochenen Backups, keine schwarzen Bildschirme während wichtiger Präsentationen. Das Beste daran: Es ist bereits auf deinem Mac installiert, komplett kostenlos und unglaublich einfach zu bedienen. Ein simples `caffeinate` im Terminal, und schon bleibt dein Mac so lange wach, wie du es brauchst. Also, das nächste Mal, wenn du deinen Mac für eine wichtige Aufgabe wach halten musst, denk an caffeinate. Dein digitaler Espresso wartet schon im Terminal auf dich. --- **Bonus-Einzeiler für die Kommandozeile:** ```bash echo "☕ Mac stays awake!" && caffeinate -d -t 3600 && echo "💤 Back to sleep mode!" ``` Happy Caffeinating! ☕🖥️ --- ## Debugging von Distroless-Containern in Kubernetes - URL: https://blog.kuepper.nrw/posts/2025-10-17-debugging-von-distroless-containern-in-kubernetes/ - Datum: 2025-10-17 - Aktualisiert: 2025-10-17 - Autor: ruediger - Tags: Kubernetes, Debugging, Distroless-Containern - Kategorien: Kubernetes Distroless Container Images sind schnell, sicher und perfekt für die Produktion, können aber beim Debugging zum Albtraum werden. In diesem Blog erfährst Du, warum Distroless Container Images so leistungsfähig sind, warum ihr Debugging schwierig ist und wie Ephemeral Containers in Kubernetes das Live-Debugging wieder einfach machen, ohne Ihre Anwendung neu zu starten. # Was sind Distroless Images? Distroless-Container sind Docker-Images, die keine Linux-Distribution (wie Ubuntu oder Alpine) enthalten. Sie beinhalten nur: * Ihre Anwendung * Deren Laufzeitumgebung (z.B. Java, Node.js, Go) # Was fehlt darin? * Keine Shell (sh, bash) * Kein Paketmanager (apt, apk) * Keine Debug-Tools (curl, ping, ps) # Warum Distroless Images verwenden? Distroless Images sind für Sicherheit, Performance und Einfachheit optimiert und werden aufgrund folgender Vorteile für den Einsatz in Produktionsumgebungen empfohlen: * Schnellere Downloads und Deployments durch kleinere Docker-Image-Größe, wodurch Cloud-Infrastrukturkosten sowohl für Netzwerk als auch Storage gespart werden * Reduzierte Angriffsfläche durch weniger Binaries und Bibliotheken * Kein Shell-Zugriff bedeutet, dass niemand per exec in den Container gelangen kann # Das Debugging-Dilemma Stellen Dir sich vor, Deine Node.js-App läuft in einem Distroless Container Image und wirft 500-Fehler. Um dies zu debuggen, haben Du versucht: ```bash kubectl exec -it my-production-application -- sh ``` erhälst jedoch folgende Fehlermeldung: ```bash error: unable to upgrade connection: container not found or shell not available ``` Warum? Weil es keine Shell im Container gibt! Daher können Sie weder `ps`, `curl`, `netstat` noch andere nützliche Tools ausführen, um den Produktionsfehler zu debuggen. ## Lösung: Ephemeral Containers in Kubernetes Ephemeral Containers in Kubernetes ermöglichen es Dir, einen temporären Debug-Container in einen laufenden Pod zu injizieren, ohne Deine Anwendung zu stoppen, neu zu erstellen oder neu zu starten, um den Fehler im Anwendungs-Pod zu debuggen. ### Schritte: 1. Erstelle Dir einen Beispiel-Anwendungs-Pod mit dem Distroless Container Image ```yaml apiVersion: v1 kind: Pod metadata: name: my-app spec: containers: — name: app image: gcr.io/distroless/static command: ["sleep", "3600"] ``` ```bash kubectl apply -f my-app.yaml ``` 2. Debug-Container injizieren ```yaml kubectl debug -it my-app \ --target=app \ --image=busybox \ --name=debugger ``` Jetzt befindest Du Dich im `busybox`-Container, der die Netzwerk-, Prozess- und IPC-Namespaces mit Ihr Deinem Anwendungs-Pod teilt, was Dir hilft, Dir Anwendungsprobleme zu debuggen. ```text ps # Prozesse des nginx-Container-Namespace auflisten ls / # Dateisystem durchsuchen cat /etc/hosts # Netzwerkkonfiguration anzeigen ping 8.8.8.8 # Ausgehende Verbindung testen wget # Falls unterstützt, Downloads testen ``` Und Deine Anwendung läuft ohne Neustart weiter. --- ## FluxCD Globale Variabeln für GetHomepage - URL: https://blog.kuepper.nrw/posts/2025-10-17-fluxcd-globale-vars-fuer-gethomepage/ - Datum: 2025-10-16 - Aktualisiert: 2025-10-16 - Autor: ruediger - Tags: Kubernetes, FluxCD, GlobalVars, GetHomepage - Kategorien: Kubernetes Damit GetHomepage Services anzeigt und in Gruppen zuordnet kann man im Ingress dafür Annotations setzen. Änderungen sollen nicht zu Aufwendig sein, daher habe ich sie als Variabeln in FluxCD gesetzt. Meine Defaults sind enabled, group, pod_selector und icon. Die werden immer gesetzt. Weitere werden in den Applications nachher noch bei Bedarf per Patch hinzugefügt. Oder die Defaults überschrieben. Aber erst einmal der Reige nach. Ein Ingress Object sieht erst einmal so aus. der Clusterissuer und die Middleware gehören nicht zu GetHomepage, werden aber auch gleich mit gemacht. So können diese auch gleich schnell geändert werden wenn sich z.B. der ClusterIssuer ändert. ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: clusterissuer: "cf-letsencrypt-prod" middlewares: "kube-system-redirect-scheme@kubernetescrd" gethomepage_enabled: '"true"' gethomepage_group: "Default" gethomepage_pod_selector: "''" gethomepage_icon: "homepage" ... ``` Diese Annotations, eher die Values werden einfach in einer ConfigMap gespeichert. Auf diese wird später im Ingress verwiesen. ```yaml apiVersion: v1 kind: ConfigMap metadata: name: global-vars namespace: flux-system data: clusterissuer: "cf-letsencrypt-prod" middlewares: "kube-system-redirect-scheme@kubernetescrd" gethomepage_enabled: '"true"' gethomepage_group: "Default" gethomepage_pod_selector: "''" gethomepage_icon: "homepage" ``` Damit die Configmap genutzt wir muss noch die kustomization.yaml in clusters/prod/flux-system/ angepasst werden. ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - gotk-components.yaml - gotk-sync.yaml - gitops-repos.yaml - global_vars.yaml ... ``` Jetzt müssen wir nur noch die einzelnen Applications anpassen. Sie liegen bei mir im GitOps Repository in /apps//base/ingress.yaml ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: blog annotations: cert-manager.io/cluster-issuer: ${clusterissuer} traefik.ingress.kubernetes.io/router.middlewares: ${middlewares} gethomepage.dev/enabled: ${gethomepage_enabled} gethomepage.dev/group: ${gethomepage_group} gethomepage.dev/pod-selector: ${gethomepage_pod_selector} spec: ingressClassName: traefik rules: ... ``` In der Kustomization der Application muss jetzt noch noch die global_vars.yaml in der App hinzugefügt werden. ```yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: blog namespace: flux-system spec: interval: 2m prune: false path: ./apps/blog/prod sourceRef: kind: GitRepository name: flux-system targetNamespace: blog decryption: provider: sops secretRef: name: sops-age postBuild: substituteFrom: - kind: ConfigMap name: global-vars substitute: ingress_host: blog.kuepper.nrw tls_secret_name: blog.kuepper.nrw-tls middlewares: "kube-system-redirect-scheme@kubernetescrd,blog-redirect-blog-feed@kubernetescrd" ``` Die ConfigMap `global_vars` wird mit substituteFrom geladen und die Variabeln sind verfügbar. In diesem Fall wird die Middleware auch gleich überschrieben. Denn das Blog ein paar Redirects, die aktiviert werden müssen. Wenn jetzt alles ins Repo gepushed wird, werden nach kurzer Zeit die Annotations in dem Ingress auftauchen. Die Seite wird dann in GetHomepage angezeigt in der Gruppe Default. Möchte man jetzt ein paar Sachen Ändern oder erweitern geht man in die Kustomization der Application um sie anzupassen. Ich will beim Blog die Description, die URL für den link und das Icon anpassen. ```yaml ... patches: - patch: |- - op: add path: /metadata/annotations/gethomepage.dev~1description value: 'Blog Küpper' - op: add path: /metadata/annotations/gethomepage.dev~1href value: 'https://blog.kuepper.nrw' - op: add path: /metadata/annotations/gethomepage.dev~1icon value: 'mdi-web' target: kind: Ingress name: blog postBuild: ... ``` Möchte man die Group `Default` mit `Homepage` überschrieben fügt man einfach den entsprechenden Patch dazu: ```yaml - op: add path: /metadata/annotations/gethomepage.dev~1group value: 'Homepages' ``` Alles eingecheckt und FluxCD hat es ausgerollt, sieht der fertige Ingress im Cluster so aus: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: cert-manager.io/cluster-issuer: cf-letsencrypt-prod gethomepage.dev/description: Blog Küpper gethomepage.dev/enabled: "true" gethomepage.dev/group: Homepage gethomepage.dev/href: https://blog.kuepper.nrw gethomepage.dev/icon: mdi-web gethomepage.dev/pod-selector: "" traefik.ingress.kubernetes.io/router.middlewares: kube-system-redirect-scheme@kubernetescrd,blog-redirect-blog-feed@kubernetescrd labels: ... ``` Die Variabeln können auch auf mehrere Dateien aufgeteilt werden. Je nachdem wofür sie genutzt werden hat man sie dann auch suaber getrennt. Es ist auch möglich Variabeln die nichz alle User eines Repos sehen sollen aus einem anderen Repo zu laden. Als Beispiel wäre der ClusterIssuer, der nicht angepasst werden darf. Oder Daten die z.B. für einen SecretStore, DNS Api Token für external DNS oder den Certmanager. Die sollten zwar eh mit Age oder anderen Tools verschlüsselt sein, aber angenommen das wäre nicht der Fall, würde man sie einfach an einem anderen Ort ablegen. ```yaml apiVersion: source.toolkit.fluxcd.io/v1 kind: GitRepository metadata: name: flux-vars-repo namespace: flux-system spec: interval: 5m url: ssh://git@github.com/username/gitops-vars ref: branch: main secretRef: name: gitops-testapps-auth ``` Das ganze muss natürlich noch genutzt werden: ```yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: flux-vars namespace: flux-system spec: interval: 10m sourceRef: kind: GitRepository name: flux-vars-repo path: ./production prune: true ``` ```yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: globalVars namespace: flux-system spec: interval: 10m prune: true path: ./vars sourceRef: kind: GitRepository name: flux-vars-repo targetNamespace: flux-system ``` In dem repo `github.com/username/gitops-vars` legt man den Ordner `vars` an und legt dort einfach eine kustomization.yaml an mit folgendem Inhalt: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - global_vars.yaml namespace: flux-system ``` Die `global_vars.yaml` kopiert man jetzt ins neue Repo unter `vars/global_vars.yaml` und checkt alles ein. FluxCD fügt nach dem Reconcil das Git Repo hinzu, legt entsprechend alles auf dem Cluster an und die Variabeln kommen aus einem externem Repo. Im Grunde das gleiche wie auch alle anderen Sachen in FluxCD von externen Git Repos genutzt werden können. Das ist ja das schöne an FluxCD. Man kann den Code so anpassen wie man es benötigt. Gerade wenn man immer die gleichen Teile auf mehreren Clustern oder Umgebungen wieder verwendet ist eine Trennung diese Teile sinnvoll. Man muss sie nur einmal anfassen und sie werden auf allen Clustern angewendet. Oder wenn man Infrastruktur, Tools von anderen Sachen trennen möchte. Wenn z.B. Entwickler Ihre Sachen selbst machen sollen, dann haben sie für Ihr Zeug ein eigenes Repo und können dort alles machen was sie wollen. --- ## Wasser predigen, Wein trinken: Telekoms Mailserver-Doppelmoral - URL: https://blog.kuepper.nrw/posts/2025-08-15-wasser-predigen-wein-trinken-telekoms-mailserver-doppelmoral/ - Datum: 2025-08-15 - Aktualisiert: 2025-08-15 - Autor: ruediger - Tags: Telekom, mailserver, reputation, fail - Kategorien: Mailserver Und die Telekom E-Mail Engineers können anscheinend nicht mal `dig` bedienen. Wenn ich Spam oder Attacken auf meine Mailserver feststelle, dann ist das Erste, was ich mache, natürlich blocken. Anschließend wird geguckt, woher der Mailserver kommt, also Provider, Serverbesitzer, Domainbesitzer. Oft kommt es ja von einer Domain, auf der auch eine Internetseite betrieben wird. Dann kann man halt über das Impressum gehen. Wenn das aber nicht so einfach zu finden ist, dann geht man halt über [postmaster@domain.tld](mailto:postmaster@domain.tld), die sollten immer erreichbar sein. Reagiert da keiner, hilft ein `dig soa domain.tld` und man schreibt den Zonemaster an, der ja wissen sollte, welchem Kunden der MX gehört. Bei großen Providern gibt es eine Abuse-Abteilung, die man kontaktieren kann. Jetzt meinte die Telekom vorletzte Woche, eine Mail von mir abzuweisen. Ich maile nicht oft an t-online.de-Adressen, weil die meisten Gott sei Dank keine mehr haben. Aber es kam halt mal wieder vor. ```bash <[**********@t-online.de](mailto:S-W-B@t-online.de)>: host [mx03.t-online.de](http://mx03.t-online.de/)[194.25.134.73] refused to talk to    me: 554 IP=23.88.46.137 - Dialup/transient IP not allowed. Use a    mailgateway or contact [toda@rx.t-online.de](mailto:toda@rx.t-online.de) if obsolete. (DIAL) Reporting-MTA: dns; [mail01.9it.de](http://mail01.9it.de/) X-Postfix-Queue-ID: B72DF44A35 X-Postfix-Sender: rfc822; [ruediger@kuepper.nrw](mailto:ruediger@kuepper.nrw) Arrival-Date: Tue, 12 Aug 2025 09:12:53 +0000 (UTC) Final-Recipient: rfc822; [**********@t-online.de](mailto:**********@t-online.de) Original-Recipient: rfc822;[**********@t-online.de](mailto:**********@t-online.de) Action: failed Status: 4.0.0 Remote-MTA: dns; [mx03.t-online.de](http://mx03.t-online.de/) Diagnostic-Code: smtp; 554 IP=23.88.46.137 - Dialup/transient IP not allowed.    Use a mailgateway or contact [toda@rx.t-online.de](mailto:toda@rx.t-online.de) if obsolete. (DIAL) ``` Der MX mx03.t-online.de meint also, es ist eine Dialup-IP, also von einem Einwahl-DSL-, Kabel- oder Glasfaseranschluss. Nein, ist sie nicht. Es ist eine IP bei einem großen deutschen Hostinganbieter. Ich habe dann erst einmal selbst ein paar Sachen geprüft. Hätte ich nicht machen brauchen, weil ich wusste schon vorher, dass alles passt. * Vom Mailserver passte im DNS der A-Record, * bei der IP die zurückgeliefert wurde passte der PTR genau auf den A-Record vom Mailserver. * TXT-Records für SPF: Correct. * DKIM: Correct * DMARC: Correct Die Telekom verwies in der Antwort auf: postmaster.t-online.de Alles, was sie verlangten, war ok, außer dass keine Kontaktdaten hinterlegt sind. Was man aber mit `dig SOA 9it.de` oder einer einfachen Mail an [postmaster@9it.de](mailto:postmaster@9it.de) hätte gar nicht benötigt. Damit aber bald wieder, wenn doch mal wieder nötig, an eine t-online.de-Mailadresse gemailt werden könnte, habe ich die Mailadressen auf die Seite gepackt. War auch nicht genug, also ist da jetzt ein Link zu einem Impressum. Lustigerweise meinte einer der Engineers der Telekom zu bemängeln, dass es ja auch nicht bei der mail01.9it.de gegeben sei. Jetzt mal im Ernst. mail01.9it.de und andere Hosts von mir sind reine Mailserver. Genau so betreibt es auch die Telekom und alle anderen Provider. Reine Mailserver, da ist kein Webserver, kein DNS-Server drauf. Sie machen reine Mailserver. Sollen jetzt mal alle Mailadmins sich einfach mal genau so verhalten und es genau so begründen? Dann kann die Telekom die nächsten Wochen gerne auch mal damit rumschlagen, um ihre Mails loszuwerden. ```bash mail03:/etc/nginx# dig +short MX rx.t-online.de 10 rx.t-online.de. mail03:/etc/nginx# dig +short MX t-online.de 10 mx01.t-online.de. 10 mx02.t-online.de. 10 mx03.t-online.de. 10 mx00.t-online.de. mail03:/etc/nginx# dig +short MX telekom.de 100 mailin12.telekom.de. 100 mailout32.telekom.de. 100 mailin42.telekom.de. 100 mailin32.telekom.de. 100 mailin22.telekom.de. ``` Alle Hostnames für diese MX sind alle nicht per HTTP erreichbar. Nimmt man sich jetzt einfach mal eine Mail der Telekom: ```bash 2025-07-24T08:50:26.239134+00:00 localhost postfix/smtpd[1207115]: 3A5444167F: client=awmail151.telekom.de[194.25.225.223] 2025-07-24T08:50:26.277338+00:00 localhost postfix/cleanup[1207118]: 3A5444167F: message-id=<-2014779266.1753347025112.JavaMail.rechnung-online@telekom.de> 2025-07-24T08:50:26.591807+00:00 localhost postfix/qmgr[960119]: 3A5444167F: from=, size=162701, nrcpt=1 (queue active) 2025-07-24T08:50:26.640058+00:00 localhost postfix/lmtp[1207119]: 3A5444167F: to=<********@9it-server.de>, relay=mail01.9it.de[private/dovecot-lmtp], delay=0.43, delays=0.38/0/0.02/0.03, dsn=2.0.0, status=sent (250 2.0.0 <=<********@9it-server.de> oGl1JNLzgWhpaxIA0J78UA Saved) 2025-07-24T08:50:26.640423+00:00 localhost postfix/qmgr[960119]: 3A5444167F: ``` Man kann Glück haben und man erreicht durch Zufall den richtigen Mitarbeiter, der es an die richtige Abteilung leitet. Denn man könnte ja dabei einfach auf telekom.de gehen, das Impressum anklicken und sein Glück versuchen. Denn die Kontaktdaten dort sind alles Kontaktdaten, Formulare und Chats, die für Endkunden ausgelegt sind. Kommt da mal mit Mailserver-Logs und Hinweisen zu Spam-Mails oder sonstigen Themen, die technisch sind und nichts mit "Mein DSL ist kaputt" zu tun haben. Eine direkte Kontaktmöglichkeit zu den E-Mail-Engineers findet man auf der Seite nirgendwo. Andersherum ballern hier auch täglich sehr viele Mails auf meine Mailserver ein, die über Telekom-Infrastruktur versendet werden, die unerwünscht sind und sogar reine Spam-Mails sind. Dabei ist ein Marketinganbieter, bei dem die Telekom auch Technologiepartner ist. Aber auch Versender, die die Infrastruktur der Telekom nutzen. Die Domains, die genutzt werden, sind oft überhaupt nicht erreichbar. Da muss auch jeder andere Mailadmin auch mal `host` und `dig` anwerfen, um herauszufinden, dass ein MX bei der Telekom betrieben wird. Der Mailserver, bzw. die IP, soll jetzt wieder resettet werden. `Wir werden veranlassen, dass die Reputation dieser IP-Adresse bei unserem System resettet wird.` Interessant war in einer vorherigen Mail dieser Satz: `Von der genannten IP-Adresse war lange Zeit keine Aktivität bei uns feststellbar. Aus Sicherheitsgründen nehmen unsere Systeme von solchen IPs erst nach Prüfung und Reset der Reputation E-Mails entgegen.` Richtig, es werden kaum Mails an Telekom-Mailadressen versendet. Das sollte ja ein Zeichen dafür sein, dass meine MX sauber sind und die Reputation sollte gut sein. Heißt das jetzt, ich muss einfach regelmäßig Mails an die Telekom-Adressen schicken, um nicht wieder aus deren Liste zu fliegen und ich mich somit wieder unnötig mit der Telekom auseinandersetzen muss? Ok, als Kunde der Telekom kann man sich ja eine Mailadresse anlegen und einfach jeden Tag eine sinnlose Mail hinschicken lassen. Obwohl ich ja kurz überlegt hatte, einfach alles, was an Schrott von der Telekom kommt und von meinen Mailservern geblockt oder als Spam erkannt wird, einfach jedes Mal bei der Telekom selbst zu reporten. Die Auswertungen laufen eh automatisiert und es wäre nur eine kleine Erweiterung der Tools, damit es einfach an [abuse@telekom.de](mailto:abuse@telekom.de) geht. Aber sehr wahrscheinlich würde dann wieder jemand bei der Telekom auf seinem hohen Ross einfach nach ein paar Tagen alles löschen und einfach die IP komplett auf eine Blacklist packen. Selbst Spammern eine Plattform liefern und dann so reagieren, aber wenn andere eigentlich alles richtig machen, kommen sie mit ihren Reputationslisten und wird bestraft, wenn man eben keine Mails und somit Spam einliefert. Das ist das gleiche Verhalten, wie es Jahre lang von Hotmail.com betrieben wurde. Jeden Tag sind von denen Millionen von Spam-Mails eingetroffen, aber selbst aufführen, als wäre man die Internet-Polizei und man kann anderen vorschreiben, was zu machen ist. Kriegt erst einmal alle eure eigenen Sachen in den Griff. Dann können wir gerne über Mailsecurity reden. --- ## UniFi Network 9.3: Was ist neu und lohnt sich das Update? - URL: https://blog.kuepper.nrw/posts/2025-07-16-unifi-network-3.9/ - Datum: 2025-07-16 - Aktualisiert: 2025-07-16 - Autor: ruediger - Tags: Unifi, UDM, Network, Update - Kategorien: Network ![Links of the week](https://s3fs.kuepper.nrw/blog-images/posts/2025/07/unifi-dashboard.webp) Ubiquiti hat UniFi Network 9.3 veröffentlicht und ich hab mir die neuen Features mal genauer angeschaut. Spoiler: Es gibt einige wirklich nützliche Verbesserungen, auch wenn nicht alles revolutionär ist. ## Die neue Client-Tabelle - endlich übersichtlich Das erste, was mir aufgefallen ist: Die Client-Tabelle wurde komplett überarbeitet. Wer schon mal versucht hat, in einem größeren Netzwerk einen bestimmten Client zu finden, weiß wie nervig das bisher war. **Was sich geändert hat:** - Filtern nach Broadcast-Typ, Access Point, Funkband, WiFi-Generation oder Herstellern - Echtzeitaktualisierungen (endlich!) - Deutlich schneller, auch bei vielen Clients Besonders praktisch finde ich die Herstellerfilterung. Wenn du wissen willst, welche Apple-Geräte gerade im Netz sind oder alle Samsung-Smartphones auf einmal anzeigen möchtest, geht das jetzt mit einem Klick. Die Performance ist tatsächlich spürbar besser geworden. Selbst in meinem Testnetzwerk mit über 200 Clients lädt die Tabelle schnell und reagiert flüssig. Das war früher definitiv ein Schwachpunkt. ## DHCP Manager - überfällige Verbesserung Der neue DHCP Manager ist ehrlich gesagt längst überfällig gewesen. Bisher musstest du für jeden VLAN separat schauen, welche IP-Adressen vergeben sind. Jetzt siehst du alles zentral an einem Ort. **Was mir gut gefällt:** - Alle aktiven Leases in einer Übersicht - Einfache Verwaltung von statischen Zuweisungen - Funktioniert auch bei mehreren Netzwerken problemlos Ich nutze das hauptsächlich, um zu schauen, welche Geräte welche IPs haben und um bei Bedarf statische Zuweisungen zu machen. Geht jetzt deutlich schneller als vorher. ## Alarm Manager - nützlich, aber nicht perfekt Der neue Alarm Manager ist eine interessante Ergänzung. Du kannst jetzt spezifische Geräte überwachen und dir Benachrichtigungen schicken lassen, wenn bestimmte Bedingungen erfüllt sind. **Was funktioniert gut:** - Überwachung einzelner Geräte mit eigenen Regeln - Verschiedene Benachrichtigungsarten (E-Mail, etc.) - Export zu externen Systemen möglich **Wo noch Luft nach oben ist:** Die Konfiguration ist etwas umständlich und die Dokumentation könnte besser sein. Für einfache Anwendungsfälle (Server offline, hohe Bandbreitennutzung) funktioniert es aber gut. ## System-Logs - deutlich besser geworden Die System-Logs wurden komplett überarbeitet und sind jetzt wesentlich brauchbarer. Früher war das oft ein Krampf, relevante Informationen zu finden. **Verbesserungen:** - Viel mehr Details zu Ereignissen - Bessere Suchfunktion - CEF-Format für SIEM-Integration (falls du sowas nutzt) Besonders für die Fehlersuche ist das eine echte Verbesserung. Die Logs sind jetzt strukturierter und man findet schneller, was man sucht. ## Content-Filterung - endlich granular Die Content-Filterung wurde erweitert und ist jetzt deutlich flexibler. Du kannst pro Netzwerksegment verschiedene Filter erstellen und sogar Zeitpläne definieren. **Neue Möglichkeiten:** - Unbegrenzte Filter pro Netzwerk - Integrierte Ad-Blocking-Funktion - Zeitbasierte Regeln Das ist besonders praktisch, wenn du unterschiedliche Benutzergruppen hast. Gäste-WLAN kann andere Regeln haben als das Mitarbeiter-Netzwerk, und das lässt sich jetzt viel einfacher umsetzen. ## CyberSecure Protection - Marketing oder Mehrwert? Ubiquiti bewirbt die erweiterten Sicherheitsfeatures ziemlich stark. Die Realität ist: Es gibt tatsächlich Verbesserungen, aber die Grundfunktionen waren auch vorher schon solide. **Was neu ist:** - Proofpoint IDS/IPS Integration - Cloudflare-basierte Echtzeit-Filterung - Kontinuierliche Threat-Updates Ob das in der Praxis einen großen Unterschied macht, hängt stark von deinem Anwendungsfall ab. Für die meisten kleineren Netzwerke ist es nice-to-have, aber kein Game-Changer. ## Multi-WAN Support - für die, die es brauchen Die Multi-WAN-Funktionen wurden erweitert. Du kannst jetzt SLAs definieren und intelligenteres Load-Balancing betreiben. **Neue Features:** - Anpassbare SLAs für verschiedene Uplinks - Policy-basiertes Routing - Bessere Failover-Mechanismen Das ist hauptsächlich für Leute interessant, die mehrere Internetverbindungen haben. Für normale Setups mit einem Provider ist das weniger relevant. ## Mein Fazit nach dem Testen UniFi Network 9.3 ist ein solides Update mit einigen wirklich nützlichen Verbesserungen. Die überarbeitete Client-Tabelle allein macht das Update schon lohnenswert, wenn du regelmäßig mit der Verwaltung zu tun hast. **Was mir besonders gut gefällt:** - Client-Tabelle ist endlich brauchbar - DHCP Manager spart Zeit - System-Logs sind deutlich informativer **Was noch verbesserungswürdig ist:** - Alarm Manager könnte einfacher zu konfigurieren sein - Manche Features fühlen sich noch etwas "beta" an - Dokumentation ist teilweise lückenhaft **Solltest du updaten?** Ja, aber nicht sofort. Ich würde empfehlen, noch ein paar Wochen zu warten, bis die ersten Kinderkrankheiten ausgebügelt sind. Dann ist es definitiv ein lohnenswertes Update. Aber die meisten die ich kenne werden es eh jetzt installieren oder schon installiert haben. Die meisten Verbesserungen sind praktische Alltagserleichterungen und keine revolutionären Neuerungen. Aber genau das macht ein gutes Update aus - es macht die tägliche Arbeit einfacher, ohne dabei neue Probleme zu schaffen. Übrigens: Das Update ist kostenlos und funktioniert mit der bestehenden Hardware. Keine versteckten Kosten oder Abo-Fallen, was bei Ubiquiti auch nicht anders zu erwarten war. Was denkst du über die neuen Features? Hast du schon Erfahrungen mit 9.3 gemacht? Lass es mich in den Kommentaren wissen! --- ## Links der Woche KW 28 - URL: https://blog.kuepper.nrw/posts/2025-07-14-links-der-woche-kw28/ - Datum: 2025-07-14 - Aktualisiert: 2025-07-14 - Autor: ruediger - Tags: Links - Kategorien: Links ![Links of the week](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/links-of-the-week.webp) ## Quick Reference - cheatsheets and quick references Quick Reference Hier sind einige Spickzettel und Schnellreferenzen, die von Open-Source-Engeln beigesteuert wurden. Link: [Quick Reference](https://quickref.me/) ## CodeImage - Code-Screenshots Ein Tool, um deine Code-Screenshots zu verschönern. Verwalte deine Code-Snippets sofort, mache sie schön lesbar und teile sie mit allen. Link: [CodeImage](https://codeimage.dev/) ## DevDocs DevDocs kombiniert mehrere API-Dokumentationen in einer schnellen, organisierten und durchsuchbaren Benutzeroberfläche. Link: [DevDocs](https://devdocs.io/) --- ## Der Mutt Mailclient wird 30 oder mehr Jahre alt - URL: https://blog.kuepper.nrw/posts/2025-07-11-mutt-mailclient-wird-30-Jahre-oder-mehr/ - Datum: 2025-07-11 - Aktualisiert: 2025-07-11 - Autor: ruediger - Tags: Mail, Mutt, IMAP, Client - Kategorien: Software ![Happy Birthday Mutt Mail Client](https://s3fs.kuepper.nrw/blog-images/posts/2025/07/mutt-30-years.webp) ## Mutt Mail client Da ich vorhin den Mailclient Mutt in einem Artikel erwähnt habe und wissen wollte, wie alt Mutt überhaupt ist, habe ich etwas im Internet recherchiert. Wann wurde es veröffentlicht, wann gestartet? 1995 wurde Mutt veröffentlicht. Also ist Mutt jetzt offiziell 30 Jahre alt, wenn man nach dem Release-Jahr geht. Genau eingrenzen lässt es sich nicht, da nirgendwo ein genaues Datum steht. Auch keine Ankündigung zu finden. Oder das Internet hat tatsächlich mal etwas vergessen. Interessant war jedoch die Reise durch das Git-Repository, denn dort kann man immer noch sehen, wie die Reise begonnen hat: Vom ersten initialen Commit auf Github kann man jede Änderungen und Verbesserung seit dem anschauen. Ich meine vorher war es in einem SVN Repo. Das habe ich aber nicht mehr gefunden. Auch keine Hinweise in Mailinglisten oder auf Internetseiten warun dazu zu finden. ```text #### [Initial revision](https://github.com/muttmua/mutt/commit/1a5381e07e97fe482c2b3a7c75f99938f0b105d4 "Initial revision") ![author](https://github.githubassets.com/images/gravatars/gravatar-user-420.png?size=32) Thomas Roessler committed on Jun 8, 1998 ``` Und die README.md ist auch noch sehr übersichtlich: ```markdown README for mutt-0.90i ======================= Installation instructions are detailed in ''INSTALL''. The user manual is in doc/manual.txt. PGP users please read doc/pgp-Notes.txt before proceeding. For more information, see the Mutt home page, http://www.cs.hmc.edu/~me/mutt/index.html. The primary distribution point for Mutt is ftp://ftp.cs.hmc.edu/pub/me/mutt. See the home page for mirror sites. Michael Elkins , January 22, 1998 Thomas Roessler , February 3, 1998 ``` ## Mutt kann 1998 schon gpg/sMime Wenn man das aus dem Jahr 1998 liest, fragt man sich: Warum ist das für Apple, Microsoft und alle anderen heute immer noch so schwer? ```text - Other than multipart/mixed and PGP/MIME, Mutt should allow the user to specify what to do with other types of multipart messages (i.e., so a user can deal with S/MIME messages reasonably) ... ``` Ja, Mutt konnte damals schon mit Verschlüsselung und Signieren umgehen. Das Internet wäre so viel besser, wenn das Standard in allen Clients werden würde. Jetzt komme bitte keiner mit "Aber das ist doch alles viel zu kompliziert". Kompliziert ist irgendwie alles. Passwörter landen in einer Notiz-App, Word oder Excel. Mehrfaktor-Authentifizierung für Logins oder Passkeys sind zu kompliziert. Hört auf, ihr seid einfach nur zu faul und zu bequem. Dafür gibt es Apps, die euch das alles abnehmen. Nur komisch finde ich immer, wenn genau das "zu kompliziert" von genau den gleichen Leuten kommt, die Dinge einrichten und installieren, die aus meiner Sicht schon sehr hart an kompliziert sind. Da geht es dann um Accounts, Software, Lizenzen, die über dubiose Wege und mit viel Gefrickel eingerichtet werden müssen, und ich denke nur: "Respekt, aber Passwortmanager und Authenticator-App sind zu schwer." ## Wieder mehr Mutt benutzen Aber zurück zu Mutt. Aktuell nutze ich Mutt nicht mehr so oft. Aber das wird sich bald wieder ändern. Gerade jetzt zum 30. Geburtstag wird Mutt einfach mal wieder reaktiviert. Gerade mit vielen Mails in vielen Ordnern ist Mutt einfach unschlagbar. Kein Scrollen durch Ordnerlisten für verschiedene Accounts. Einfach eine Taste jede halbe Stunde drücken und man sieht, in welchen Ordnern neue Mails sind. Man sieht nicht nur, in welchen Ordnern neue Mails sind – Mutt springt einfach mit einer Taste zum nächsten Ordner mit neuen Mails. Mutt ist so mächtig. Und das vermisse ich auch irgendwie bei allen Mail-Clients, die es aktuell so gibt. Einfach mit einem Shortcut alle Mails aus der Inbox verschieben, die mit GitHub-PRs zu tun haben. Oder alle Incident-Mails. Oder man markiert mal eben per Regex alle Mails außer den 2-3, die man noch benötigt, und verschiebt dann einen Batch mit 10 oder mehr Mails. ## Meine Mutt Konfiguration Hier einmal meine Konfiguration für 2 IMAP-Mailaccounts, sehr abgespeckt. Das Switchen zwischen den Accounts geht mit den Tasten 1 und 2. Falls die Sidebar mal nicht aktualisieren sollte: einfach 0 drücken. ### Installation ```bash brew install mutt # mutt_dotlock(1) has been installed, but does not have the permissions to lock # spool files in /var/mail. To grant the necessary permissions, run: sudo chgrp mail /opt/homebrew/Cellar/mutt/2.2.14/bin/mutt_dotlock sudo chmod g+s /opt/homebrew/Cellar/mutt/2.2.14/bin/mutt_dotlock # Alternatively, you may configure `spoolfile` in your .muttrc to a file inside # your home directory. ``` ### .muttrc Config File ```bash cat < ~/.muttrc folder-hook 'account.com.example.user1' 'source ~/.mutt/account.com.example.user1' folder-hook 'account.com.example.user2' 'source ~/.mutt/account.com.example.user2' # Default account source ~/.mutt/account.com.example.user1 macro index 1 'source ~/.mutt/account.com.example.user1exec imap-logout-allunset sidebar_visibleset sidebar_visible=yes=INBOX' "Switch to User 1 account" macro index 2 'source ~/.mutt/account.com.example.user2exec imap-logout-allunset sidebar_visibleset sidebar_visible=yes=INBOX' "Switch to User 2 account" macro index 0 'unset sidebar_visibleset sidebar_visible=yesset sidebar_width=25' "Full sidebar reset" # Fetch mail shortcut bind index G imap-fetch-mail set mail_check = 60 # Prüfe alle 60 Sekunden set imap_check_subscribed = yes # Nur abonnierte Ordner prüfen set imap_list_subscribed = yes # Nur abonnierte Ordner anzeigen set imap_passive = no set imap_idle = yes set mail_check_stats = yes set mail_check_stats_interval = 60 # Seitenleiste mit Ordnern (optional): set sidebar_format = "%B%?F? [%F]?%* %?N?%N/?%S" set folder_format = "%2C %t %N %F %2l %-8.8u %-8.8g %8s %d %f" set sort_browser = alpha set sidebar_visible = yes set sidebar_width = 25 set sidebar_short_path = yes # folder indent wer auch dort einrückungen möchte # set sidebar_folder_indent = yes # set sidebar_delim_chars = "/." set sidebar_new_mail_only = no set sidebar_next_new_wrap = yes bind index,pager K sidebar-prev # K = Nach oben bind index,pager J sidebar-next # J = Nach unten bind index,pager L sidebar-open # L = Öffnen bind index,pager ü sidebar-page-up # ü = Seite hoch bind index,pager ö sidebar-page-down # ü = Seite runter bind index,pager B sidebar-toggle-visible # Alle Header erstmal ausblenden ignore * # Nur wichtige Header anzeigen (in dieser Reihenfolge) unignore from: to: cc: bcc: date: subject: reply-to: unignore organization: user-agent: x-mailer: unignore list-id: x-mailing-list: # Header-Reihenfolge festlegen hdr_order from: to: cc: bcc: subject: date: reply-to: organization: # === PAGER-KONFIGURATION (E-Mail-Ansicht) === # Anzahl der Index-Zeilen über der E-Mail anzeigen set pager_index_lines = 10 # Zeigt 10 E-Mails über der aktuellen # Pager-Verhalten set pager_stop = yes # Stoppe am Ende der Nachricht set pager_context = 3 # 3 Zeilen Kontext beim Scrollen set menu_scroll = yes # Scrollen statt Seitenweise set smart_wrap = yes # Intelligenter Zeilenumbruch set markers = no # Keine '+' Zeichen bei umgebrochenen Zeilen set pager_index_lines = 10 # Aliases / Addressbook set alias_file = ~/.mutt/aliases set sort_alias = alias set reverse_alias = yes set reverse_name = yes set reverse_realname = yes source ~/.mutt/aliases set alias_format = "%4n %2f %t %-15a %-25r" set charset = "utf-8" set send_charset = "utf-8" set assumed_charset = "utf-8" # Für bessere Anzeige beim E-Mail schreiben: set compose_format = "-- Mutt: Compose [Approx. msg size: %l Atts: %a]%>-" # === SCHÖNERE E-MAIL-LISTE === # Index-Format (E-Mail-Liste) anpassen set index_format = "%4C %Z %{%b %d} %-15.15L (%?l?%4l&%4c?) %s" EOF ``` ### Accounts anlegen ```bash mkdir -p ~/.mutt/cache/com.example.user1/{bodies,header} mkdir -p ~/.mutt/cache/com.example.user2/{bodies,header} ``` #### User 1: Datei: ~/.mutt/account.com.example.user1 ```bash cat < .mutt/account.com.example.user1 unmailboxes * set ssl_starttls=yes set ssl_force_tls=yes set from = 'user1@example.com' set use_from = yes set imap_user = 'user1@example.com' set imap_pass = 'yourpass' set smtp_pass = 'yourpass' set realname='Bob Mayer' set folder = "imaps://mail.example.com:993" set spoolfile = "+INBOX" set postponed = "+Drafts" set record = "+Sent Messages" set trash = "+Deleted Messages" set header_cache = "~/.mutt/cache/com.example.user1/headers" set message_cachedir = "~/.mutt/cache/com.example.user1/bodies" set certificate_file = "~/.mutt/certificates" set smtp_url = 'smtp://user1@mail.example.com:587/' set smtp_authenticators = "plain" set move = no set imap_keepalive = 900 set ssl_use_sslv3 = no set ssl_use_tlsv1 = no set ssl_use_tlsv1_1 = no set ssl_use_tlsv1_2 = yes set ssl_use_tlsv1_3 = yes set ssl_verify_dates = yes set ssl_verify_host = no EOF ``` #### User 2: Datei: Datei: ~/.mutt/ ```bash cat < .mutt/account.com.example.user1 unmailboxes * set ssl_starttls=yes set ssl_force_tls=yes set from = 'user2@example.com' set use_from = yes set imap_user = 'user2@example.com' set imap_pass = 'yourpass' set smtp_pass = 'yourpass' set realname='Bob Mayer' set folder = "imaps://mail.example.com:993" set spoolfile = "+INBOX" set postponed = "+Drafts" set record = "+Sent Messages" set trash = "+Deleted Messages" set header_cache = "~/.mutt/cache/com.example.user2/headers" set message_cachedir = "~/.mutt/cache/com.example.user2/bodies" set certificate_file = "~/.mutt/certificates" set smtp_url = 'smtp://user2@mail.example.com:587/' set smtp_authenticators = "plain" set move = no set imap_keepalive = 900 set ssl_use_sslv3 = no set ssl_use_tlsv1 = no set ssl_use_tlsv1_1 = no set ssl_use_tlsv1_2 = yes set ssl_use_tlsv1_3 = yes set ssl_verify_dates = yes set ssl_verify_host = no EOF ``` ### Mutt Alias / Adressbuch Datei Alias / Adressbuch: ```bash ~/.mutt/aliases alias bob "Bob Mayer" alias Alice "Alice Müller" ``` --- ## RANT - Produktivität verbrennen mit Microsoft - URL: https://blog.kuepper.nrw/posts/2025-07-10-rant-produktivitaet-verbrennen-mit-microsoft/ - Datum: 2025-07-10 - Aktualisiert: 2025-07-10 - Autor: ruediger - Tags: Microsoft, Teams, Sharepoint, Drive, Outlook - Kategorien: Microsoft ![RANT - Produktivität verbrennen mit Microsoft](https://s3fs.kuepper.nrw/blog-images/posts/2025/07/microsoft-rant.webp) Visual Studio Code, Azure Cloud – alles Dinge, die von Microsoft sind und dabei auch noch gut funktionieren. Ja, bei der Azure Cloud kann man auch an einigen Stellen fluchen und fragen warum. Aber das kann man bei jedem Hyperscaler, die tun sich da alle nichts. Was sie anbieten funktioniert, und jeder davon hat "historisch" gewachsene Strukturen, die sehr wahrscheinlich auch die Anbieter selbst gerne anders machen würden. Aber darum geht es nicht. Was mich seit einiger Zeit wirklich nervt, und damit bin ich nicht alleine, sind diese ganzen Tools wie Outlook, SharePoint (oder war es jetzt OneDrive?) und Teams. ## Teams – Das Produktivitätsgrab Teams ist das Schlimmste, was Entscheider wählen können. Ja, Corporate, ihr habt ja alles von Microsoft und da will man keinen Zoo verwalten. Die, die das entscheiden, haben eh nachher nichts mehr damit zu tun. Sie müssen damit nachher auch kaum arbeiten oder es gar verwalten und pflegen. Sie benutzen es einfach, wie sie es benutzen, und wissen oft gar nicht, was andere in der Firma benötigen und was es leisten muss. Und weil dann interne IT-Abteilungen auch gerne per zentraler Klick-UI stundenlang alle Einstellungen vornehmen, wissen sie auch nicht, dass andere Teams komplett anders arbeiten. ### Code-Blöcke? Fehlanzeige! Schon mal in einem Meeting gesessen, bei dem es gerade um ein Deployment geht und Ansible, Terraform, ein Shell Script o.ä. einen Fehler wirft? In Slack oder anderen Programmen kann man Code-Blöcke auswählen oder per Markdown einfach starten, Code reinkopieren und abfeuern. Das Ganze ist dann sauber formatiert und jeder kann es schnell lesen – oft ist ein Problem schnell von jemandem erkannt. Teams: Kein Code-Block, kein Markdown. Da kann man die Shell-Ausgabe reinkopieren und es sieht aus wie reingerotzt. Selbst Newlines werden entfernt. Wenn man das da nicht rauskopiert und in anderen Tools dann irgendwie ansatzweise lesbar macht – was auch unnötig Zeit kostet –, ist das Lesen ungemein schwierig bis unmöglich. ### Wo ist die spontane Kommunikation? In Slack, Discord und einer Menge anderer Programme kann man sich Huddles, Working Threads oder wie sie sonst so heißen anlegen. Pro Team, pro Projekt, team-/projektübergreifend, sodass sich alle, die es müssen, schnell finden. Aber man kann auch selbst schnell mit 2-3 Personen spontan einen Chat oder Video-Call-Raum aufmachen. Man sieht, wer drin ist, wer davon online ist, und kann auch mal spontan fragen. In Teams sieht man davon nichts. Keine Channels, keine Rooms, nicht wer online ist. Ich will nicht erst einmal aus irgendwelchen Chats raus und nach Personen suchen. Ich will auch gar nicht überlegen, welche Person ich aus irgendeinem Team ansprechen müsste. Weil man oft gar nicht so schnell weiß, wer überhaupt alles in einem bestimmten Team ist. Man möchte höchstens auf den Channel gehen, der für die Kommunikation eingerichtet ist, und da dann sofort die Mitgliederliste sehen. Selbst wenn der Großteil davon auf "Offline" oder "Abwesend" ist – wenn da nur eine Person anwesend ist, schreibt man in den Chat oder die Person direkt an, um Hilfe oder Infos zu bekommen. Und selbst wenn alle offline sind, bekommen sie umgehend Benachrichtigungen. ### Benachrichtigungen? Glückssache! Sehr oft erleben wir es bei Teams: 1. Überhaupt keine Benachrichtigungen kommen – gerade wenn man Teams geschlossen hat. 2. Es werden E-Mails versendet. Die kommen dann aber 5-8 Stunden nachdem irgendjemand eine Nachricht in Teams gesendet hat. Bei Teams mal eben mit einer oder gar mehreren Personen chatten oder einen Call machen – wieso geht das nicht mal eben spontan? Und damit meine ich nicht "auf neue Nachricht klicken und Leute zusammensuchen". Wieso gibt es keine Channels, die immer verfügbar sind? Wir kennen es von Slack. Dort können Huddles angelegt werden, und wenn man etwas Bestimmtes macht, was für andere interessant sein könnte, geht man da rein. Andere werden kurz benachrichtigt und können dazukommen. Oder auch nur, weil man gerade im Homeoffice Tasks macht, die auch mit einer lockeren Unterhaltung funktionieren. ## Der Kommunikationskollaps Genau so entstehen teamintern und teamübergreifend Austausch, der wichtig für jede Firma ist. Gerade in großen Unternehmen fördert man damit viel mehr, als manchem bewusst ist. Und das wird oft in vielen Unternehmen komplett zerstört. Erst kürzlich in einem Projekt erlebt. Dort war Slack im Einsatz und wurde mit der Begründung "Muss wegen Corporate-Blabla jetzt Teams sein" abgelöst. Wenn das Corporate wäre, dann würde man diesen Schritt nicht gehen. Das ist Ignoranz, Faulheit, Bequemlichkeit und einige andere Sachen, die dazu führen. Und wozu führt das Ganze? Die Kommunikation ist innerhalb von kürzester Zeit auf nahezu NULL gesunken. Calls oder einfach nur Unterhaltungen, also Austausch zu Projektthemen oder auch Zwischenmenschliches sind komplett weg. ## SharePoint/OneDrive – Das Mysterium SharePoint oder OneDrive – keine Ahnung, wann und wo manche Dokumente überhaupt wegen welcher Bedingungen landen. Und wenn ich mir so manche IT-Admin-Gruppen im Netz und Social Media angucke, wissen das sehr oft so manche IT-Admins auch nicht so recht. Das ist ein Mysterium, welches sehr oft nur Verzweiflung bringt. Und auch hier, wie auch bei Teams, ist das Teilen von Informationen nicht immer sehr einfach. Gerade im Company-Umfeld kommt es nicht selten vor, dass man mit mehreren Firmen an etwas arbeitet und zentral eine Stelle haben möchte, in der alle gemeinsam auf Dokumente zugreifen können. Wenn nicht schon durch die interne IT und irgendwelche Policies komplett vernagelt und somit überhaupt nicht möglich, scheitert es selbst dann, wenn man es nicht selbst komplett schlecht konfiguriert hat. Schon mal versucht, eine Connection zwischen zwei Teams von zwei Firmen zu verknüpfen oder User, die nicht im Tenant sind... ach, lassen wir das. Es ist grausam. Bei uns sollte ein Werksstudent Zugriff auf einen Ordner bekommen. Erst wollte es überhaupt nicht, dann klappte es dann doch, und zwei Tage später war die komplette Freigabe wieder weg. Keiner wusste wieso, und es war auch nicht ersichtlich warum. ## Das wahre Problem: Versteckte Kosten Da werden Stunden verbrannt, und das nicht gerade wenige. Alles in allem verbrennt das Zeug so viel Geld – da frage ich mich: Wieso höre ich immer nur "Aber das alles kostet doch nur 5€ pro User und da ist alles drin"? Ja, da ist alles drin. Aber auch eine Menge Zeit, die tagtäglich in zig Firmen einfach so verbrannt wird. Und es ist nicht nur die Zeit, die verbrennt. Da verbrennt Geld, da verbrennt gute Laune, da verbrennt Zwischenmenschlichkeit. Die Teambuilding-Fahne wird von vielen Entscheidern immer wieder hochgehalten. Die gleichen Entscheider, die dann diesen ganzen Mist einführen, durchdrücken und meistens auf berechtigte Hinweise NULL reagieren. ## Outlook – Wenn die Suche versagt Selbst wenn man Probleme und Funktionsmängel aufzeigt, werden sie sehr wahrscheinlich kurz gesehen und man will sie ernsthaft beachten. Wenn das per E-Mail gesendet wurde, dann ist es sehr wahrscheinlich weg. Selbst wenn man sich daran erinnert und sich ernsthaft damit auseinandersetzen möchte – die E-Mail wird man sehr wahrscheinlich nicht im Postfach finden. Erst kürzlich gehabt: Man wusste genau, um was es in der E-Mail ging, und beide haben in ihren Postfächern nach diesen E-Mails gesucht. Und da man wusste, um was es ging, und auch dabei um ein Thema ging, bei dem man gezielt nach bestimmten Begriffen suchen konnte... die Suche von Outlook hat bei beiden keine einzige E-Mail gefunden. Durch Zufall hat einer dann die E-Mail bei sich gefunden, die E-Mail-Adresse vom Systemaccount per Chat gesendet und siehe da – mit der konnte Outlook dann auch mal diese E-Mails bei allen finden. Selbst das 30 Jahre alle Mutt findet meine Mails schneller und kann noch viele Sachen einfach viel besser als Outlook. Wie zum Beispiel der Shortcut: "Suche alle Mails die mit Tickets zutun haben, bei denen eine Mail mit Closed reinkam und verschiebe alle dazu gehörigen in einen bestimmten Ordner" Der Code dazu sieht zwar anders aus als hier beschrieben, aber mit einem Tastendruck unnötig und erledigt Mails aus der Inbox zubekommen hat was. Oder was meint Ihr wieso bei mir Zero-Inbox überhaupt funktioniert? ;-) --- *Fazit: 5€ pro User? Die versteckten Kosten durch verlorene Produktivität, Frustration und kaputte Teamdynamik sind um ein Vielfaches höher.* --- ## Von Linkwarden zu Karakeep: Warum weniger manchmal mehr ist - URL: https://blog.kuepper.nrw/posts/2025-07-08-von-linkwarden-nach-karakeep-migriert/ - Datum: 2025-07-08 - Aktualisiert: 2025-07-08 - Autor: ruediger - Tags: bookmarks, linkwarden, karakeep, kuberentes - Kategorien: Bookmarks ![Von Linkwarden zu Karakeep: Warum weniger manchmal mehr ist](https://s3fs.kuepper.nrw/blog-images/posts/2025/07/linkwarden-karakeep.webp) Als jemand, der seine Links und Bookmarks professionell organisiert, bin ich kürzlich von Linkwarden zu Karakeep gewechselt. Nach einigen Monaten mit dem neuen Setup kann ich sagen: Es war die richtige Entscheidung. Hier meine Erfahrungen und warum dieser Wechsel für mich so erfolgreich war. ## Die Ausgangssituation mit Linkwarden Linkwarden ist zweifellos ein mächtiges Tool. Die Möglichkeit, vollständige Screenshots und Snapshots von Webseiten zu speichern, klingt zunächst sehr verlockend. In der Praxis stellte ich jedoch fest, dass ich diese Funktion kaum nutzte. Stattdessen wurde sie zu einem unnötigen Overhead, der Ressourcen verbrauchte, ohne mir einen echten Mehrwert zu bieten. Das größere Problem lag jedoch in der Infrastruktur: Linkwarden benötigt eine PostgreSQL-Datenbank, was bedeutete, dass ich mich nicht nur um die Anwendung selbst kümmern musste, sondern auch um die Wartung, Updates und Backups der Datenbank. In meinen Kubernetes-Clustern war das ein zusätzlicher Komplexitätsgrad, den ich gerne vermeiden wollte. ## Warum Karakeep die bessere Wahl war ### Schlankheit und Performance Der Wechsel zu Karakeep brachte sofort spürbare Verbesserungen mit sich. Die Anwendung ist deutlich schlanker und läuft erheblich schneller in meinen Kubernetes-Clustern. Ohne die Notwendigkeit für umfangreiche Screenshot-Funktionen und komplexe Archivierungslogik startet Karakeep schneller und verbraucht weniger Speicher. ### Einfachere Datenhaltung Einer der größten Vorteile: Karakeep verzichtet auf PostgreSQL. Stattdessen nutzt es eine einfachere Datenhaltung, die über ein Data Directory läuft. Dieses Directory kann ich einfach über Persistent Volume Claims (PVC) und Persistent Volumes (PV) in Kubernetes mounten. Die Datensicherung wird dadurch zu einem Kinderspiel – ich muss nur noch das Storage-Backend regelmäßig sichern, anstatt mich um komplexe Datenbank-Backups zu kümmern. ### Ressourceneffizienz zahlt sich aus Durch die gesparten Ressourcen konnte ich eine Funktion aktivieren, die ich bei Linkwarden vermisst hatte: automatisches AI-Tagging. Da Karakeep weniger CPU und RAM benötigt, blieb genug Headroom für diese intelligente Funktion, die meine Links automatisch kategorisiert und verschlagwortet. Das spart mir viel manuelle Arbeit und macht die Organisation meiner Bookmarks noch effizienter. ## Praktische Vorteile im Kubernetes-Setup ### Vereinfachte Deployments Mein Kubernetes-Deployment für Karakeep ist deutlich einfacher geworden: - Nur ein Container statt separater Datenbank-Pods - Einfachere Konfiguration ohne Datenbank-Credentials - Weniger Netzwerk-Komplexität zwischen Services ### Bessere Skalierbarkeit Ohne den Overhead einer externen Datenbank skaliert Karakeep besser in meiner Container-Umgebung. Die Anwendung startet schneller und reagiert flüssiger, besonders bei mehreren parallelen Instanzen. ### Backup-Strategie Die Backup-Strategie ist jetzt viel einfacher: Anstatt Datenbank-Dumps zu erstellen und zu verwalten, sichere ich einfach das Storage-Backend, auf dem das Data Directory liegt. Das ist wartungsärmer und weniger fehleranfällig. ## Fazit: Weniger ist mehr Der Wechsel von Linkwarden zu Karakeep hat mir gezeigt, dass es nicht immer die Feature-reichste Lösung sein muss. Oft ist es besser, ein Tool zu wählen, das genau das macht, was man braucht – und das gut und effizient. Karakeep bietet mir alles, was ich für die Link-Organisation benötige, ohne unnötigen Ballast. Die gesparten Ressourcen kann ich für Features investieren, die mir wirklich helfen, wie das automatische AI-Tagging. Und die vereinfachte Infrastruktur bedeutet weniger Wartungsaufwand und mehr Zeit für die wichtigen Dinge. Für alle, die ihre Bookmarks in Kubernetes betreiben und dabei Wert auf Effizienz und Einfachheit legen, kann ich Karakeep wärmstens empfehlen. Manchmal ist weniger wirklich mehr. --- ## Automatische Bereinigung der GitHub Container Registry mit einer eigenen Action - URL: https://blog.kuepper.nrw/posts/2025-07-05-automatische-bereinigung-der-github-container-registry-mit-einer-action/ - Datum: 2025-07-05 - Aktualisiert: 2025-07-05 - Autor: ruediger - Tags: github, ghcr, container, docker, images, registry, cleanup - Kategorien: Development ![ghcr.io container registry cleanup](https://s3fs.kuepper.nrw/blog-images/posts/2025/07/container-registry-cleanup.webp) Die GitHub Container Registry (ghcr.io) ist ein praktischer Service zum Hosten von Docker Images direkt bei GitHub. Bei aktiver Entwicklung sammeln sich jedoch schnell hunderte oder sogar tausende alte Container-Versionen an, die wertvollen Speicherplatz verbrauchen und die Übersicht erschweren. In diesem Artikel zeige ich, wie ich eine GitHub Action entwickelt habe, die automatisch alte Container Images bereinigt, dabei aber wichtige Versionen schützt. ## Das Problem: Explodierender Container-Speicher Bei einem meiner Projekte hatte sich die Anzahl der Container-Versionen auf über 1000 angehäuft: ``` ✓ Total versions: 1041 ``` Jeder Push in verschiedene Branches erzeugte neue Images mit Tags wie: - `v1.9.64-dev.5` - `v1.9.64-stage.2` - `v1.9.63-develop.1` - Untagged Versionen von gescheiterten Builds Während aktuelle Production-Tags wie `latest` oder `v1.9.66` natürlich erhalten bleiben sollen, können alte Development- und Staging-Versionen problemlos gelöscht werden. ## Die Lösung: Container Registry Cleanup Action Ich habe eine GitHub Action entwickelt, die diese Aufgabe automatisiert. Die Action berücksichtigt dabei mehrere wichtige Aspekte: ### ✨ Hauptfunktionen - **🗑️ Zeitbasierte Bereinigung**: Löscht Container-Versionen, die älter als X Tage sind - **🛡️ Tag-Schutz**: Wichtige Tags wie `latest`, `main` oder Release-Versionen bleiben erhalten - **📊 Mindestanzahl**: Behält immer eine konfigurierbare Mindestanzahl alter Versionen - **⚡ Batch-Verarbeitung**: Löscht pro Lauf nur eine begrenzte Anzahl, um API-Limits zu respektieren - **🔒 Sichere Löschung**: Detaillierte Logs und mehrfache Sicherheitsprüfungen ### 🔧 Konfigurierbare Parameter Die Action bietet umfangreiche Konfigurationsmöglichkeiten: | Parameter | Beschreibung | Standard | Beispiel | |-----------|--------------|----------|----------| | `package-name` | Name des Container-Packages | *erforderlich* | `my-app` | | `token` | GitHub Token mit `packages:write` | *erforderlich* | `${{ secrets.PAT_TOKEN }}` | | `days-old` | Lösche Versionen älter als X Tage | `21` | `14` | | `min-versions-to-keep` | Mindestanzahl alter Versionen behalten | `3` | `5` | | `max-versions-per-run` | Maximale Löschungen pro Lauf | `10` | `20` | | `protected-tags` | Regex-Pattern für geschützte Tags | `latest\|main\|master\|develop\|dev` | `latest\|stable` | | `delete-untagged-only` | Nur untagged Versionen löschen | `false` | `true` | ## 🛡️ Intelligenter Tag-Schutz Das Herzstück der Action ist der intelligente Tag-Schutz. Über das `protected-tags` Parameter können Sie mit Regex-Patterns definieren, welche Tags niemals gelöscht werden sollen: ### Standard-Schutz ```yaml protected-tags: 'latest|main|master|develop|dev' ``` ### Erweiteter Schutz für Release-Versionen ```yaml protected-tags: 'latest|main|v[0-9]+\\.[0-9]+\\.[0-9]+$' ``` Dies schützt Semantic Versioning Tags wie `v1.2.3`, aber nicht `v1.2.3-beta.1`. ### Minimaler Schutz ```yaml protected-tags: 'latest|production' ``` ## 📝 Praktische Anwendungsbeispiele ### Basis-Setup für tägliche Bereinigung ```yaml name: Container Registry Cleanup on: schedule: - cron: '0 2 * * *' # Täglich um 2 Uhr workflow_dispatch: # Manueller Start möglich jobs: cleanup: runs-on: ubuntu-latest steps: - name: Cleanup old container images uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} days-old: 21 min-versions-to-keep: 3 ``` ### Aggressive Bereinigung für Development ```yaml - name: Cleanup development images uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} days-old: 7 # Nur 1 Woche behalten min-versions-to-keep: 1 protected-tags: 'latest|production' max-versions-per-run: 50 ``` ### Sichere Bereinigung (nur untagged) ```yaml - name: Safe cleanup - untagged only uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} delete-untagged-only: true ``` ### Mehrere Packages bereinigen ```yaml jobs: cleanup: runs-on: ubuntu-latest strategy: matrix: package: ['frontend', 'backend', 'worker', 'database'] steps: - name: Cleanup ${{ matrix.package }} uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: ${{ matrix.package }} token: ${{ secrets.PAT_TOKEN }} ``` ## 🔑 Token-Setup Die Action benötigt einen Personal Access Token (PAT) mit entsprechenden Berechtigungen: ### 1. PAT erstellen 1. GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) 2. Erforderliche Scopes auswählen: - `read:packages` - `write:packages` - `delete:packages` ### 2. Token als Repository Secret hinzufügen 1. Repository → Settings → Secrets and variables → Actions 2. New repository secret: `PAT_TOKEN` 3. Token-Wert einfügen ## 📊 Praktische Ergebnisse Bei meinem Projekt mit über 1000 Container-Versionen: **Vor der Bereinigung:** ``` ✓ Total versions: 1041 Old versions found: 293 ``` **Nach mehreren Läufen:** ``` ✓ Total versions: 751 Successfully deleted: 20 versions (untagged + old tagged) ``` Die Action löscht systematisch alte Development-Tags wie: - `v1.0.233-amd64` ✅ gelöscht - `v1.0.232-stage.2` ✅ gelöscht - `v1.9.15-develop.1` ✅ gelöscht - `latest` ❌ geschützt - `v1.9.66` ❌ geschützt (aktuell) ## 🚀 Automatisierung und Best Practices ### Empfohlene Scheduler-Konfiguration ```yaml on: schedule: - cron: '0 2 * * 0' # Sonntags um 2 Uhr (wöchentlich) workflow_dispatch: ``` ### Monitoring mit Outputs ```yaml - name: Cleanup old images id: cleanup uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} - name: Report results run: | echo "Deleted: ${{ steps.cleanup.outputs.deleted-count }} versions" echo "Total versions: ${{ steps.cleanup.outputs.total-versions }}" echo "Old versions found: ${{ steps.cleanup.outputs.old-versions }}" ``` ### Staging/Production Unterscheidung ```yaml # Staging - aggressiv - name: Cleanup staging images if: github.ref == 'refs/heads/develop' uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app-staging' token: ${{ secrets.PAT_TOKEN }} days-old: 3 min-versions-to-keep: 1 # Production - konservativ - name: Cleanup production images if: github.ref == 'refs/heads/main' uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} days-old: 30 min-versions-to-keep: 10 protected-tags: 'latest|stable|v[0-9]+\\.[0-9]+\\.[0-9]+$' ``` ## 🔍 Troubleshooting ### Häufige Probleme **403 Forbidden**: - Prüfen Sie die PAT-Berechtigungen - Stellen Sie sicher, dass das Package zugänglich ist **Keine Versionen gelöscht**: - `min-versions-to-keep` könnte zu hoch sein - Alle Versionen könnten durch `protected-tags` geschützt sein - Keine Versionen älter als `days-old` vorhanden ### Debug-Modus ```yaml - name: Debug package info run: | gh api /user/packages/container/my-app | jq gh api /user/packages/container/my-app/versions | jq '.[0:3]' env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} ``` ## 💡 Fazit Die Container Registry Cleanup Action automatisiert eine zeitaufwändige Maintenance-Aufgabe und bietet dabei maximale Flexibilität und Sicherheit. Durch die intelligente Tag-Filterung bleiben wichtige Versionen erhalten, während alte Development- und Staging-Versionen systematisch entfernt werden. **Vorteile:** - ✅ Reduzierter Storage-Verbrauch - ✅ Bessere Übersicht in der Registry - ✅ Automatisierte Wartung - ✅ Schutz wichtiger Versionen - ✅ Konfigurierbar für verschiedene Szenarien Die Action ist Open Source verfügbar und kann direkt über den GitHub Marketplace eingebunden werden. --- ## 📚 Vollständige README Hier die komplette Dokumentation der Action: --- # Container Registry Cleanup Action A GitHub Action to automatically cleanup old container images from GitHub Container Registry (ghcr.io). ## Features - 🗑️ Delete old container images based on age - 🛡️ Protect important tags (latest, main, master, etc.) - 📊 Keep minimum number of versions - ⚡ Configurable batch processing - 🔒 Safe deletion with detailed logging - 🎯 Support for both tagged and untagged versions ## Usage ### Basic Example ```yaml name: Cleanup Container Registry on: schedule: - cron: '0 2 * * *' # Daily at 2 AM workflow_dispatch: jobs: cleanup: runs-on: ubuntu-latest steps: - name: Cleanup old container images uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} days-old: 21 min-versions-to-keep: 3 ``` ### Advanced Example ```yaml name: Advanced Container Cleanup on: workflow_dispatch: jobs: cleanup: runs-on: ubuntu-latest steps: - name: Cleanup multiple packages uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} days-old: 14 min-versions-to-keep: 5 max-versions-per-run: 20 protected-tags: 'latest|stable|v[0-9]+\\.[0-9]+\\.[0-9]+' delete-untagged-only: false ``` ### Multiple Packages ```yaml jobs: cleanup: runs-on: ubuntu-latest strategy: matrix: package: ['frontend', 'backend', 'worker'] steps: - name: Cleanup ${{ matrix.package }} uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: ${{ matrix.package }} token: ${{ secrets.PAT_TOKEN }} ``` ## Inputs | Input | Description | Required | Default | |-------|-------------|----------|---------| | `package-name` | Name of the container package to cleanup | Yes | - | | `token` | GitHub token with `packages:write` permission | Yes | - | | `days-old` | Delete versions older than this many days | No | `21` | | `min-versions-to-keep` | Minimum number of old versions to keep | No | `3` | | `max-versions-per-run` | Maximum versions to delete per run | No | `10` | | `protected-tags` | Regex pattern for protected tags (pipe-separated) | No | `latest\|main\|master\|develop\|dev` | | `delete-untagged-only` | Only delete untagged versions | No | `false` | ## Outputs | Output | Description | |--------|-------------| | `deleted-count` | Number of versions deleted | | `total-versions` | Total versions found | | `old-versions` | Number of old versions found | ## Token Setup 1. Create a Personal Access Token (PAT): - Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) - Select scopes: `read:packages`, `write:packages`, `delete:packages` 2. Add the token as a repository secret: - Repository → Settings → Secrets and variables → Actions - Name: `PAT_TOKEN` - Value: Your created token ## Protected Tags By default, these tags are protected and won't be deleted: - `latest` - `main` - `master` - `develop` - `dev` You can customize this with the `protected-tags` input using regex patterns. ## Safety Features - **Minimum versions**: Always keeps a minimum number of old versions - **Batch processing**: Limits deletions per run to avoid overwhelming the API - **Protected tags**: Prevents deletion of important tags - **Detailed logging**: Shows exactly what's being deleted and why - **Dry-run capability**: Set `max-versions-per-run: 0` to see what would be deleted ## Examples by Use Case ### Conservative Cleanup (Untagged Only) ```yaml - uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} delete-untagged-only: true ``` ### Aggressive Cleanup (Keep Only Latest Releases) ```yaml - uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} days-old: 7 min-versions-to-keep: 1 protected-tags: 'latest|v[0-9]+\\.[0-9]+\\.[0-9]+' ``` ### Large Repository Cleanup ```yaml - uses: ruedigerp/container-registry-cleanup@v1.1 with: package-name: 'my-app' token: ${{ secrets.PAT_TOKEN }} max-versions-per-run: 50 days-old: 30 ``` ## Troubleshooting ### Common Issues 1. **403 Forbidden**: Check that your PAT has the correct permissions 2. **Package not found**: Ensure the package name is correct and accessible 3. **No versions deleted**: Check that versions exist and meet the age criteria ### Debug Mode Add this step before the cleanup to debug: ```yaml - name: Debug package info run: | gh api /user/packages/container/my-app | jq gh api /user/packages/container/my-app/versions | jq '.[0:3]' env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} ``` ## License MIT License - see [LICENSE](https://github.com/ruedigerp/container-registry-cleanup/blob/main/LICENSE) file for details. ## Contributing Contributions welcome! Please read [CONTRIBUTING](https://github.com/ruedigerp/container-registry-cleanup) for guidelines. --- ## Schnell vorkonfigurierte VMs mit QEMU erstellen - URL: https://blog.kuepper.nrw/posts/2025-07-05-schnell-vorkonfigurierte-vms-mit-qemu-erstellen/ - Datum: 2025-07-05 - Aktualisiert: 2025-07-05 - Autor: ruediger - Tags: VM, server, cloudinit - Kategorien: DevOps ![VM mit Qemu und Cloudinit erstellen](https://s3fs.kuepper.nrw/blog-images/posts/2025/07/BC13E6DB-AC08-4445-A5E3-65CA6C1A2EE4.webp) Wer kennt das nicht? Man braucht mal eben eine saubere Testumgebung, will ein neues Tool ausprobieren oder ein Kubernetes-Cluster aufsetzen. Normalerweise bedeutet das: VM aufsetzen, OS installieren, Updates fahren, Tools installieren – und schon sind ein paar Stunden weg. Mit QEMU und Cloud-Init geht das deutlich eleganter. Einmal konfiguriert, startet man eine vollständig vorkonfigurierte VM in wenigen Minuten. Hier zeige ich, wie das geht. ## Warum QEMU und Cloud-Init? QEMU ist ein mächtiger Virtualisierer, der auf praktisch allen Plattformen läuft. Cloud-Init ist das Schweizer Taschenmesser für VM-Konfiguration – es kann beim ersten Boot automatisch User anlegen, SSH-Keys installieren, Software nachinstallieren und sogar komplette Skripte ausführen. Die Kombination macht’s möglich: VM starten, kurz warten, fertig konfigurierte Umgebung nutzen. ## QEMU installieren **macOS (mit Homebrew):** ```bash brew install qemu ``` **Ubuntu/Debian:** ```bash sudo apt update sudo apt install qemu-system-aarch64 qemu-utils ``` **CentOS/RHEL/Fedora:** ```bash sudo dnf install qemu-system-aarch64 qemu-img ``` ## Das Base-Image besorgen Wir verwenden ein fertiges Debian ARM64 Cloud-Image als Basis: ```bash wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-arm64.qcow2 ``` ## Working Copy erstellen Das Original-Image behalten wir als Template und erstellen eine Arbeitskopie: ```bash cp debian-12-generic-arm64.qcow2 debian-testserver.qcow2 qemu-img resize debian-testserver.qcow2 20G ``` So kann man das Original-Image immer wieder für neue VMs verwenden. ## Cloud-Init konfigurieren Cloud-Init braucht drei Dateien, die wir in einem eigenen Ordner sammeln: ```bash mkdir cloud-init-data cd cloud-init-data/ ``` ### user-data - Das Herzstück Hier passiert die ganze Magie. Diese Datei definiert, wie die VM aussehen soll: ```yaml cat < user-data #cloud-config # User anlegen users: - name: debian plain_text_passwd: testpass lock_passwd: false sudo: ALL=(ALL) NOPASSWD:ALL shell: /bin/bash ssh_authorized_keys: - ssh-ed25519 AAAAC3...DeinSSHKey...7iFVL # SSH mit Passwort erlauben ssh_pwauth: true # Debug-Modus für Troubleshooting debug: true # Standard-Pakete installieren packages: - htop - curl - git - vim - wget # System updaten package_update: true package_upgrade: true # Installationsskript vorbereiten write_files: - path: /tmp/install-k3s.sh permissions: '0755' content: | #!/bin/bash set -e echo "K3s wird installiert..." curl -sfL https://get.k3s.io | sh -s - server --cluster-init echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" >> /root/.bashrc echo "k9s wird installiert..." wget https://github.com/derailed/k9s/releases/download/v0.32.7/k9s_Linux_arm64.tar.gz tar xzf k9s_Linux_arm64.tar.gz mv k9s /usr/local/bin/ echo "Helm wird installiert..." curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh echo "FluxCD CLI wird installiert..." curl -s https://fluxcd.io/install.sh | sudo bash echo "kubectl wird installiert..." curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl" sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl # Praktische Aliases echo 'alias k="kubectl"' >> /root/.bashrc echo 'alias kgp="kubectl get pods"' >> /root/.bashrc # Warten bis Kubernetes läuft echo "Warte auf Kubernetes API..." while ! kubectl cluster-info &>/dev/null; do echo "Kubernetes startet noch..." sleep 10 done echo "K3s Installation abgeschlossen!" # Skript nach der Installation ausführen runcmd: - /tmp/install-k3s.sh EOF ``` ### meta-data - Server-Metadaten ```bash cat < meta-data instance-id: server-001 local-hostname: testserver EOF ``` ### network-config - Netzwerk festlegen ```bash cat < network-config version: 2 ethernets: enp0s1: addresses: [192.168.74.10/24] gateway4: 192.168.74.1 nameservers: addresses: [8.8.8.8, 1.1.1.1] dhcp4: false EOF ``` ## Cloud-Init ISO erstellen Aus den drei Dateien basteln wir ein ISO-Image: ### macOS ```bash cd .. hdiutil makehybrid -iso -joliet -default-volume-name "cidata" -o seed.iso cloud-init-data/ ``` ### Linux ```bash cd .. # Mit genisoimage (meist vorinstalliert) genisoimage -output seed.iso -volid cidata -joliet -rock cloud-init-data/ # Oder mit xorriso xorriso -as mkisofs -V cidata -o seed.iso cloud-init-data/ ``` ## VM starten Jetzt kommt der spannende Teil – die VM starten: ```bash qemu-system-aarch64 \ -name "testserver" \ -machine type=virt,accel=hvf \ -cpu cortex-a72 \ -smp cores=4,threads=1 \ -m 4G \ -drive file=debian-testserver.qcow2,if=virtio,index=0,media=disk,format=qcow2 \ -drive file=seed.iso,if=virtio,index=1,media=cdrom \ -netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \ -device virtio-net-pci,netdev=net0 \ -bios /opt/homebrew/share/qemu/edk2-aarch64-code.fd \ -nographic \ -serial mon:stdio ``` **Was passiert hier?** - `-accel=hvf`: Hardware-Beschleunigung (macOS), unter Linux nimmt man `kvm` - `-m 4G`: 4 GB RAM - `-smp cores=4`: 4 CPU-Kerne - `hostfwd=tcp::2222-:22`: SSH über Port 2222 erreichbar - `hostfwd=tcp::8080-:80`: HTTP über Port 8080 erreichbar - `-nographic`: Läuft in der Konsole ## Netzwerk anpassen Je nach Einsatzzweck kann man verschiedene Netzwerk-Modi nutzen: ### User-Modus (Standard) ```bash -netdev user,id=net0,hostfwd=tcp::2222-:22 ``` - Einfachste Variante - VM kann ins Internet, ist aber von außen nicht direkt erreichbar - Perfekt für Tests ### Bridge-Modus ```bash -netdev bridge,id=net0,br=br0 ``` - VM bekommt IP aus dem Host-Netzwerk - Direkte Kommunikation möglich - Braucht Bridge-Setup auf dem Host ### Host-Only ```bash -netdev socket,id=net0,listen=:1234 ``` - VM nur vom Host erreichbar - Maximale Isolation - Gut für Sicherheitstests ## Nach dem Start ### Per SSH einloggen ```bash ssh -p 2222 debian@localhost ``` ### Direkte Konsole nutzen Falls SSH mal nicht klappt: - `Ctrl+A, C` für QEMU-Monitor - `info network` zeigt Netzwerk-Status - `Ctrl+A, X` beendet QEMU ## Wenn’s nicht läuft ### Cloud-Init checken ```bash # In der VM sudo cloud-init status --wait sudo cloud-init logs ``` ### Netzwerk prüfen ```bash # In der VM ip addr show ping google.com ``` ### Services testen ```bash # K3s Status sudo systemctl status k3s kubectl get nodes ``` ## Erweiterte Tricks ### Mehrere VMs parallel Einfach verschiedene Cloud-Init-Konfigurationen erstellen: ```bash # Zweite VM cp debian-12-generic-arm64.qcow2 debian-testserver-2.qcow2 # Neue cloud-init-data-2/ mit angepassten Einstellungen # Andere Ports verwenden: 2223, 8081, etc. ``` ### Automatisierung Ein kleines Skript macht das Leben leichter: ```bash #!/bin/bash # vm-create.sh VM_NAME=$1 SSH_PORT=$2 HTTP_PORT=$3 echo "Erstelle VM: $VM_NAME" cp debian-12-generic-arm64.qcow2 $VM_NAME.qcow2 # Cloud-Init anpassen mkdir cloud-init-$VM_NAME sed "s/testserver/$VM_NAME/g" cloud-init-data/meta-data > cloud-init-$VM_NAME/meta-data cp cloud-init-data/user-data cloud-init-$VM_NAME/ cp cloud-init-data/network-config cloud-init-$VM_NAME/ # ISO erstellen hdiutil makehybrid -iso -joliet -default-volume-name "cidata" -o $VM_NAME-seed.iso cloud-init-$VM_NAME/ echo "VM $VM_NAME ist bereit!" ``` ## Fazit Mit dieser Methode hat man in wenigen Minuten eine vollständig konfigurierte VM am Start. Das Setup ist einmal Arbeit, aber dann kann man beliebig viele VMs aus dem Template erstellen. Perfekt für: - Schnelle Entwicklungsumgebungen - CI/CD-Testing - Kubernetes-Experimente - Sicherheitstests - Schulungen Die ganze Konfiguration lässt sich versionieren und an verschiedene Projekte anpassen. Einmal erstellt, hat man immer eine saubere Testumgebung parat – ohne stundenlanges Setup. --- ## Links der Woche KW 25 - URL: https://blog.kuepper.nrw/posts/2025-06-26-links-der-woche-kw25/ - Datum: 2025-06-26 - Aktualisiert: 2025-06-26 - Autor: ruediger - Tags: Links - Kategorien: Links ![Links of the week](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/links-of-the-week.webp) # Kubernetes Context und Namespace in tmux Staus Link: [Kube tmux](https://github.com/jonmosco/kube-tmux) kube-tmux: Kubernetes-Kontext und Namespace-Status für tmux Ein Skript, das es dir ermöglicht, den aktuellen Kubernetes-Kontext und Namespace, der in kubectl konfiguriert ist, zu deiner tmux-Statuszeile hinzuzufügen. Inspiriert von kube-ps1, ist dies eine Portierung für tmux, die alle Funktionen enthält, die kube-ps1 effizient machen, und bringt sie in die tmux-Statuszeile. # Beliebige Inhalte in deine macOS-Menüleiste einfügen Link: [xbar App](https://xbarapp.com) xbar (der BitBar-Neustart) ermöglicht es dir, die Ausgabe von jedem Skript/Programm in deine macOS-Menüleiste zu setzen. Github Repo: [xbar](https://github.com/matryer/xbar) # karakeep - The Bookmark Everything App (self-hosted) Ich selbst benutze Linkwarden für meine Bookmarks. Bin aber die Tage auf diese App gestossen und werde es auch mal installieren und testen. Eine selbst-hostbare App zum Speichern von allem (Links, Notizen und Bilder) mit KI-basierter automatischer Verschlagwortung und Volltextsuche Du kannst die Demo unter https://try.karakeep.app aufrufen. Melde dich mit folgenden Zugangsdaten an: ``` E-Mail: demo@karakeep.app Passwort: demodemo ``` Die Demo ist mit einigen Inhalten gefüllt, aber im Nur-Lese-Modus, um Missbrauch zu verhindern. # Kommentar System for static pages (self-hosted) Wo wir schon bei Self-Hosted sind, da mache ich auch mal auf was eigenes aufmerksam. Wie ja [hier](https://blog.kuepper.nrw/posts/2025-06-25-2025-06-20-blog-kommentar-system-self-hosted.html) schon beschrieben habe ich das Kommentarsystem fertig und online gestellt. Der Code und die Dokumentation ist auf [Github Repository](https://github.com/ruedigerp/comments). --- ## Mein Blog Kommentarsystem Self-Hosted - URL: https://blog.kuepper.nrw/posts/2025-06-20-blog-kommentar-system-self-hosted/ - Datum: 2025-06-25 - Aktualisiert: 2025-06-25 - Autor: ruediger - Tags: Kommentare, Blog, Static, InkProjek, Jekyll, Hugo, Gatsby - Kategorien: Development ![Automatisierte Kubernetes Volume-Backups](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/comments.webp) # TLDR; [Github Repo](https://github.com/ruedigerp/comments) # Ein eigenes Kommentarsystem für statische Blogs: Warum ich auf Go setze Statische Website-Generatoren wie Hugo, Jekyll oder Gatsby erfreuen sich großer Beliebtheit - und das zu Recht. Sie bieten schnelle Ladezeiten, hohe Sicherheit und einfaches Hosting. Doch wenn es um interaktive Funktionen wie Kommentare geht, stoßen sie schnell an ihre Grenzen. Während WordPress-Nutzer auf bewährte Plugins zurückgreifen können, müssen Betreiber statischer Blogs oft auf externe Anbieter wie Disqus ausweichen. ## Das Problem mit externen Kommentaranbietern Externe Kommentarsysteme wie Disqus bringen durchaus Vorteile mit sich: Sie sind schnell eingerichtet, bieten umfangreiche Funktionen und kümmern sich um Spam-Schutz und Moderation. Dennoch haben sie entscheidende Nachteile: **Datenschutz und Kontrolle:** Externe Anbieter sammeln oft umfangreiche Nutzerdaten und zeigen Werbung an. Als Website-Betreiber hat man wenig Kontrolle über diese Aspekte und muss sich auf die Datenschutzrichtlinien Dritter verlassen. **Abhängigkeiten:** Was passiert, wenn der Anbieter seinen Service einstellt oder die Preise drastisch erhöht? Alle Kommentare könnten verloren gehen. **Design-Integration:** Oft lassen sich externe Kommentarsysteme nur begrenzt an das eigene Website-Design anpassen. ## Die Lösung: Ein eigenes Kommentarsystem in Go Aus diesen Gründen habe ich mich entschieden, ein eigenes Kommentarsystem zu entwickeln. Die Wahl fiel auf Go als Backend-Sprache, da sie sich hervorragend für Web-Services eignet und sowohl performant als auch ressourcenschonend ist. ### Architektur und Design-Entscheidungen Das System basiert auf einer klaren Trennung zwischen dem statischen Blog und der Kommentarfunktionalität: **Separate Domain:** Das Kommentarsystem läuft unter einer eigenen Domain, getrennt vom Hauptblog. Diese Architektur bietet mehrere Vorteile: Bessere Skalierbarkeit, einfachere Wartung und die Möglichkeit, das System für mehrere Websites zu nutzen. **Go-Backend:** Der Server wurde in Go implementiert und stellt eine REST-API zur Verfügung. Go eignet sich perfekt für solche Aufgaben - es ist schnell, hat eine hervorragende Standard-Bibliothek für Web-Services und benötigt nur minimale Systemressourcen. **JavaScript-Frontend:** Die Integration in den Blog erfolgt über ein JavaScript-Widget, das die Kommentare dynamisch lädt und darstellt. Dies ermöglicht eine nahtlose Integration in jedes statische Website-System. ### Integration in das Blog-System Die Einbindung könnte nicht einfacher sein. Im Blog-Theme wird im Header einfach das JavaScriipt vom Kommentar Server geladen und lediglich ein `
`-Container mit einer eindeutigen ID platziert: ```html ... ```html # Date + Title
# or Title
# or URL Path
``` Mit Blog-ID bei der Nutzung auf mehreren Seite (siehe oben): ```html # Blogname + Date + Title
# or Blogname + Title
# or Blogname + URL Path
``` oder: ```html # Blog-ID + Date + Title
# or Blog-ID + Title
# or Blog-ID + URL Path
``` Das JavaScript-Widget erkennt diesen Container automatisch und lädt die entsprechenden Kommentare. Dabei kann die Integration sowohl post-spezifisch als auch global im Theme erfolgen - je nach gewünschter Flexibilität. Wenn Kommentare global in allen Artikeln aktivieren möchte, kann man auch einfach folgenden Code an der entsprechenden Stelle im Theme einfügen. Das `{{.Link}}` ist hier speziefisch für [InkPaper, a static blog generator](https://github.com/InkProject/ink) . Je nach eingesetztem CMS muss diese Variable angepasst werden. ```html ``` Oder auch hier wieder mit eine Blog-ID: ```html ``` Kommentar Form: ![1](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/comments-1.png) ### Technische Vorteile **Performance:** Da das System speziell für die eigenen Anforderungen entwickelt wurde, ist es schlank und schnell. Keine unnötigen Features bedeuten weniger Code und bessere Performance. **Datenschutz:** Alle Daten bleiben unter eigener Kontrolle. Es werden nur die notwendigen Informationen gespeichert, und die Einhaltung der DSGVO liegt in den eigenen Händen. **Anpassbarkeit:** Das Design lässt sich vollständig an die Website anpassen. CSS-Styles können frei definiert werden, ohne auf die Vorgaben eines externen Anbieters angewiesen zu sein. **Skalierbarkeit:** Go ist bekannt für seine hervorragende Concurrent-Performance. Das System kann problemlos viele gleichzeitige Anfragen verarbeiten. ## Funktionsumfang und Features Das selbst entwickelte System muss nicht weniger können als kommerzielle Alternativen. Typische Features umfassen: - **Moderation:** Administrative Oberfläche zur Verwaltung und Freischaltung von Kommentaren Adminpanel: ![2](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/comments-2.png) ### ToDos: * **Spam-Schutz:** Implementierung eigener Spam-Filter oder Integration bestehender Lösungen * **Antwort-Funktionen:** Verschachtelte Kommentare und Antworten auf bestehende Beiträge * **Benachrichtigungen:** E-Mail-Benachrichtigungen bei neuen Kommentaren * **Rate-Limiting:** Schutz vor Spam durch Begrenzung der Kommentar-Frequenz ## Deployment und Betrieb Ein weiterer Vorteil von Go ist die einfache Deployment-Strategie. Go-Programme werden zu einzelnen, ausführbaren Dateien kompiliert, die keine zusätzlichen Dependencies benötigen. Das macht das Deployment auf jedem Server unkompliziert. Das System kann auf einem einfachen VPS betrieben werden und benötigt nur minimale Ressourcen. Für die Datenspeicherung wir ValKey (Redis) eingesetzt. Im Github Repository kann jederzeit das aktuelle [Release](https://github.com/ruedigerp/comments/releases) herunterladen werden. Die Version wird auch im Helm Chart immer aktualisiert, das das ein `helm upgrade ...` ausreicht für ein update oder bei FluxCD einfach automatisch per ImageUpdateAutomation. ## Dokumentation und Installation Installation mit einem Binary, als Docker Container, Helm, FluxCD ist unter den folgenden Links beschrieben. Genau so wie die API Dokumentation und hilfreiche Redis Befehle, falls man mal etwas debuggen möchte. * Installation: [Install doc](https://github.com/ruedigerp/comments/blob/main/docs/README.md) * Docker: [Docker-compose](https://github.com/ruedigerp/comments/blob/main/docs/docker-compose/README.md) * Kuberenetes (Noch in Arbeit) * Helm: [helm](https://github.com/ruedigerp/comments/blob/main/docs/helm/README.md) * FluxCD: [FluxCD Installation](https://github.com/ruedigerp/comments/blob/main/docs/fluxcd/) * API: [API Docs](https://github.com/ruedigerp/comments/blob/main/docs/api/README.md) * Redis: [Redis Commands](https://github.com/ruedigerp/comments/blob/main/docs/redis/README.md) ## Fazit Die Entwicklung eines eigenen Kommentarsystems mag zunächst nach Mehraufwand aussehen, bietet aber langfristig entscheidende Vorteile. Vollständige Kontrolle über Daten und Funktionalität, bessere Performance und die Unabhängigkeit von externen Anbietern rechtfertigen den initialen Entwicklungsaufwand. Go erweist sich dabei als ideale Wahl für das Backend - die Sprache ist nicht nur performant und ressourcenschonend, sondern auch gut zu lernen und zu warten. In Kombination mit einem flexiblen JavaScript-Frontend entsteht so ein System, das sowohl technisch überzeugt als auch den eigenen Anforderungen perfekt entspricht. Für Betreiber statischer Blogs, die Wert auf Datenschutz, Performance und Kontrolle legen, ist ein eigenes Kommentarsystem eine durchaus lohnenswerte Alternative zu externen Lösungen. --- ## AWS EKS kubectl in Web-Console - URL: https://blog.kuepper.nrw/posts/2025-06-23-aws-console-eks-kubectl-in-console/ - Datum: 2025-06-23 - Aktualisiert: 2025-06-23 - Autor: ruediger - Tags: aws, eks, kubernetes, kubeconfig, console - Kategorien: Kubernetes ![Automatisierte Kubernetes Volume-Backups](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/aws-els-console-kubeconfig.webp) Da ich gerade genau das einmal wieder machen musste, nicht direkt an die KubeConfig gekommen bin, habe ich kurz über die Shell in der AWS Console nachgeschaut. Und da man so etwas immer wieder vergisst und suchen muss wie man es machen, einfach mal hier kurz festgehalten. In der Shell ist erst einmal keine `./kube/config` vorhanden. Diese kann man sich aber schnell in die Shell importieren. ```bash aws eks update-kubeconfig --region eu-central-1 --name my-cluster ``` Anschliessend kann man direkt mit kubectl auf den Cluster zugreifen. --- ## Git Merge Script für Deployment Workflow - URL: https://blog.kuepper.nrw/posts/2025-06-19-git-merge-script/ - Datum: 2025-06-19 - Aktualisiert: 2025-06-19 - Autor: ruediger - Tags: Git, Merge, Workflow, Merge. Braches - Kategorien: Development ![Automatisierte Kubernetes Volume-Backups](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/git-merge-script.webp) # Git Merge Automation Script Ein Bash-Script zur Automatisierung von Git-Merge-Workflows über GitHub Pull Requests. ## 🎯 Zweck Dieses Script automatisiert den gesamten Merge-Prozess zwischen verschiedenen Branches in einem Git-Repository und nutzt dabei die GitHub CLI für Pull Request Management. Es ist speziell für Teams entwickelt, die einen strukturierten Deployment-Workflow verwenden. ## 📋 Voraussetzungen - **Git** installiert und konfiguriert - **GitHub CLI (gh)** installiert und authentifiziert - Berechtigung zum Erstellen und Mergen von Pull Requests im Repository - Bash-Shell (Linux/macOS/WSL) ### GitHub CLI Installation ```bash # macOS (Homebrew) brew install gh # Ubuntu/Debian sudo apt install gh # Windows (Chocolatey) choco install gh ``` ### GitHub CLI Authentifizierung ```bash gh auth login ``` ## 🚀 Installation * Script herunterladen und ausführbar machen: ```bash chmod +x merge.sh ``` * Optional: In ein Verzeichnis im PATH verschieben: ```bash sudo mv merge.sh /usr/local/bin/merge ``` ## 📖 Verwendung ### Grundlegende Syntax ```bash ./merge.sh ``` ### Verfügbare Commands #### 1. `dev-stage` - Development zu Staging Merged den `dev` Branch in den `stage` Branch. ```bash ./merge.sh dev-stage ``` **Was passiert:** - Erstellt einen Pull Request von `dev` → `stage` - PR-Titel: "Deploy dev to stage - YYYY-MM-DD" - Merged den PR automatisch nach erfolgreichen Checks #### 2. `stage-main` - Staging zu Production Merged den `stage` Branch in den `main` Branch (Production). ```bash ./merge.sh stage-main ``` **Was passiert:** - Erstellt einen Pull Request von `stage` → `main` - PR-Titel: "Deploy stage to main - YYYY-MM-DD" - Merged den PR automatisch für Production-Release #### 3. `back-merge` - Rück-Synchronisation Synchronisiert den `main` Branch zurück zu `stage` und `dev` nach einem Production-Release. ```bash ./merge.sh back-merge ``` **Was passiert:** 1. Holt den neuesten Tag (Release-Version) 2. Erstellt PR: `main` → `stage` (Back-merge) 3. Erstellt PR: `main` → `dev` (Back-merge) 4. Beide PRs werden mit `[skip ci]` Flag gemerged #### 4. `anywhere` - Flexibler Merge Merged einen beliebigen Branch in einen anderen. ```bash ./merge.sh anywhere ``` **Beispiele:** ```bash ./merge.sh anywhere stage dev # dev → stage ./merge.sh anywhere main hotfix # hotfix → main ./merge.sh anywhere feature dev # dev → feature ``` ## 🔄 Typischer Workflow ### Standard Development Cycle ```bash # 1. Development → Staging ./merge.sh dev-stage # 2. Testing auf Staging... # 3. Staging → Production ./merge.sh stage-main # 4. Back-merge nach Release ./merge.sh back-merge ``` ### Hotfix Workflow ```bash # 1. Hotfix direkt zu Production ./merge.sh anywhere main hotfix-branch # 2. Back-merge ./merge.sh back-merge ``` ## 🎛️ Branch-Struktur Das Script ist für folgende Branch-Struktur optimiert: ``` main (Production) ├── stage (Staging/Testing) └── dev (Development) ├── feature/xyz ├── bugfix/abc └── hotfix/urgent ``` ## ⚙️ Funktionsweise ### Pull Request Erstellung - **Automatische Titel**: Datum-basierte PR-Titel - **Beschreibungen**: Vordefinierte, aussagekräftige Beschreibungen - **Auto-Merge**: PRs werden automatisch gemerged wenn alle Checks bestehen ### Back-Merge Besonderheiten - **Tag-Detection**: Automatische Erkennung des neuesten Release-Tags - **CI Skip**: Back-Merges verwenden `[skip ci]` um unnötige Builds zu vermeiden - **Synchronisation**: Stellt sicher, dass alle Branches auf dem gleichen Stand sind ## 🛠️ Fehlerbehebung ### Häufige Probleme #### GitHub CLI nicht authentifiziert ```bash gh auth status gh auth login ``` #### Merge-Konflikte - Das Script stoppt bei Konflikten - Löse Konflikte manuell im GitHub Web-Interface - Oder löse sie lokal und push erneut #### Fehlende Berechtigung ```bash # Prüfe Repository-Berechtigung gh repo view ``` #### Branch existiert nicht ```bash # Verfügbare Branches anzeigen git branch -a ``` ### Debug-Modus Für detaillierte Ausgabe: ```bash bash -x ./merge.sh dev-stage ``` ## 🔒 Sicherheitshinweise - **Branch Protection**: Aktiviere Branch Protection Rules für `main` und `stage` - **Required Reviews**: Konfiguriere erforderliche Code-Reviews - **Status Checks**: Stelle sicher, dass CI/CD-Checks aktiviert sind - **Auto-Merge**: Funktioniert nur wenn alle konfigurierten Checks bestehen ## 📝 Anpassungen ### Custom Branch Namen Passe die Branch-Namen im Script an deine Naming-Convention an: ```bash # Ändere im Script: --base stage # zu deinem Staging-Branch --head dev # zu deinem Development-Branch ``` ### Custom PR-Titel Ändere die PR-Titel-Templates: ```bash --title "Deploy dev to stage - $(date +%Y-%m-%d)" ``` ### Custom Commit Messages Ändere die Merge-Commit-Messages: ```bash --subject "Release stage" ``` ## 🤝 Best Practices 1. **Teste immer auf Staging** bevor du zu Production merged 2. **Führe Back-Merges regelmäßig durch** um Branches synchron zu halten 3. **Prüfe CI/CD-Status** vor dem Merge 4. **Verwende aussagekräftige Commit-Messages** in deinen Feature-Branches 5. **Erstelle Tags** für Production-Releases ## 📊 Monitoring ### PR-Status prüfen ```bash # Alle offenen PRs anzeigen gh pr list # Specific PR-Status gh pr view ``` ### Recent Merges ```bash # Letzten 5 Commits auf main git log --oneline -5 main # Tags anzeigen git tag --sort=-version:refname | head -5 ``` ## 🆘 Support Bei Problemen: 1. Prüfe die Logs des Scripts 2. Verifiziere GitHub CLI Authentication 3. Prüfe Repository-Berechtigungen --- ## Automatisierte Kubernetes Volume-Backups - URL: https://blog.kuepper.nrw/posts/2025-06-17-Automatisierte-Kubernetes-Volume-Backups/ - Datum: 2025-06-18 - Aktualisiert: 2025-06-18 - Autor: ruediger - Tags: Kubernetes, Volumes, Backup - Kategorien: Kubernetes ![Automatisierte Kubernetes Volume-Backups](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/k8s-pv-backup.webp) Ich betreibe meine Kubernetes Cluster bei Hetzner und habe in den Projekten zusätzlichen einen NFS-Server, der in Kubernetes die Volumes mit dem `nfs-subdir-external-provisioner` bereitstellt. Das gleiche Setup habe ich auch zuhause in meheren VMs. So das ich auch dort das gleiche Setup habe und lokale Services oder auch zum testen vorhanden habe wie im Live-Betrieb. Ich kann so zwischen Dev, Stage und Prod Daten bei Hetzner schnell hin und her kopieren. Oder über VPN auch mal die Daten nach Hause ziehen um Migrationen oder Änderungen zu testen. Daten können dann so auch schnell wieder hergestellt werden. Durch dieses Setup kann ich auch jeden Cluster einfach neu aufsetzen, mit FluxCD alle Kubernetes Resourcen installieren lassen und die Daten kommen von den NFS-Servern. Diese Daten kann ich an andere Orte synchronisieren und habe damit auch das Backup. Trotzdem sichere ich die Daten für die jeweiligen Custer noch auf den jeweiligen Kubernetes Clustern noch einmal auf den NFS-Servern. Das Backup ist für schnelles Recovery und dann im Einsatz wenn etwas getestet oder aktualisiert wird. Geht einmal etwas schief, kann ich die Daten direkt zurück spielen. # Der Backup Job Das mache ich mit einem Backup Cronjob den ich vorher einmal manuell triggern kann. Er sichert dann alle PV Daten in einem Backup-Job. Da ich NFS mit dem `nfs-subdir-external-provisioner` nutze und die PV/PVC mit dem AccessMode ReadWriteMany eingerichtet sind können diese Volumes einfach vom Backup-Job gemounted werden. # Der Backup Job mit ReadWriteOnce Das Problem sind immer Volumes mit dem AccessMode ReadWriteOnce. Das Volume kann nicht von anderen Pods gemounted werden. Um diese Volumes zu sichern muss * Der Pod der Application über das Deployment, Statefulset oder Daemonset einmal herunter skaliert werden. * Ein Pod gestartet werden, der das Volume und das Backupziel mounted. * Das Backup erstellt werden. * Backup Pod stoppen. * Deployment, Statefulset oder Daemonset wieder hoch skalieren. Das kann man für 1-2 Deyployments manuell machen. Aber wenn im Cluster viele Services laufen wird diese Aufgabe recht langwierig. Das möchte man weg automatisieren. # Mein k8s-pv-backup Tool Ich möchte mich um das Backup nicht kümmern. Auch möchte ich nicht nach jeder Installation von neuen Services auch noch die Backup Config anpassen müssen. Genau das gleiche, wenn ein Service nicht mehr benutzt wird und vom Cluster entfernt wurde. k8s-pv-backup läuft als Cronjob im Cluster und prüft als erstes alle PV im Cluster und sucht dazu das passende PVC. Dabei wird auch noch der AccessMode geprüft. Anhand des AccessModes wird entschieden ob ein Deployment, Statefulset oder Daemonset auf 0 skaliert werden muss oder nicht (ReadWriteMany). Dann wird erst einmal RBAC entsprechend konfiguriert und ein PVC in dem Namespace des zu sichernden Service erstellt, mit dem PV der Application. Jetzt wird noch ein Job für das eigentliche Backup angelegt, der dann mit dem neuen PVC und dem Backup-NFS PVC das Backup erstellt. Ist der Job fertig wird für diesen einen Service das RBAC und PVC wieder gelöscht und der Job für den nächste Service wird angelegt. RBAC/PVC erstellen, Backup erstellen und aufräumen. Beim herunter und rauf skalieren der Deployment, Statefulset oder Daemonset wird immer auf den Wert gesetzt der vorher gesetzt gewesen ist. So hat man komplett automatisiert ein Backup. Zero-Downtime bei ReadWriteMany und kurze Downtime beim ReadWriteOnce. # k8s-pv-backup benutzen Installation einfach per Helm. Es kann aber auch lokal ausgeführt werden. ```bash helm repo add ruedigerp https://ruedigerp.github.io/helm-charts/ helm repo update ruedigerp helm upgrade --install k8s-pv-backup ruedigerp/k8s-pv-backup --namespace backup-system --create-namespace --wait -f values.yaml ``` Beispiel values.yaml ```yaml nfs_server: "10.0.10.7" nfs_path: "/srv/nfs/k8s-pv/production/k8s-backup" storage_class: "nfs-client" ``` `nfs_server` und `nfs_path` entsprechend anpassen. `storage_class` ist aktuell für "nfs-client" ausgelegt. Das wird sich in Zukunft aber noch ändern, da natürlich auch andere Backupziele möglich sein sollen. --- ## FluxCD Image Update mit Pull Request - URL: https://blog.kuepper.nrw/posts/2025-06-16-fluxcd-image-update-mit-pull-request/ - Datum: 2025-06-16 - Aktualisiert: 2025-06-16 - Autor: ruediger - Tags: Kubernetes, FluxCD, ImageUpdateAutomation, GitOps - Kategorien: Kubernetes ![FluxCD Image Update Automation](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/fluxcd-image-update-automation.webp) In meinem vorherigen Artikel zu [FluxCD und Image Update Automation](https://blog.kuepper.nrw/posts/2025-06-15-fluxcd-image-update-automation) habe ich die direkte Implementierung von automatischen Container-Image-Updates demonstriert. Während diese Lösung für Development-Umgebungen praktikabel ist, erfordert eine Production-Umgebung typischerweise einen kontrollierten Review-Prozess vor dem Deployment. # Problem der direkten Automation Die ursprüngliche Konfiguration pushed Image-Updates direkt auf den main Branch, wodurch Änderungen sofort ohne manuellen Review-Prozess deployed werden. Für kritische Production-Workloads ist dieser Ansatz oft zu risikoreich. Lösung: Pull Request-basierter Workflow Durch eine geringfügige Modifikation der ImageUpdateAutomation-Konfiguration lässt sich ein Pull Request-basierter Approval-Workflow implementieren. Der entscheidende Parameter ist die Änderung des Target-Branch von main zu einem dedizierten Update-Branch: ```yaml # Ursprünglich: direkter Push auf main push: branch: main # Modifiziert: Push auf separaten Branch für PR-Workflow push: branch: flux-updates-source ``` # Technische Implementierung ## Personal Access Token konfigurieren: * GitHub Settings → Developer settings → Personal access tokens → Tokens (classic) * Erforderliche Scopes: `repo` (Full control) und `workflow` (Update workflows) * Token als Repository Secret `PAT_TOKEN` hinterlegen ## GitHub Actions Workflow erstellen: * Datei: .github/workflows/create-pr-on-flux-updates.yml ```yaml name: Create PR for Flux Image Updates on: push: branches: - flux-updates-source permissions: contents: write pull-requests: write jobs: create-pr: runs-on: ubuntu-latest steps: - name: Checkout flux-updates-source uses: actions/checkout@v4 with: ref: flux-updates-source fetch-depth: 0 token: ${{ secrets.PAT_TOKEN }} - name: Create and push PR branch run: | # Erstelle einen neuen Branch basierend auf flux-updates-source git checkout -b flux-image-updates-$(date +%Y%m%d-%H%M%S) # Push den neuen Branch git push origin HEAD # Speichere Branch-Name für nächsten Step echo "PR_BRANCH=$(git branch --show-current)" >> $GITHUB_ENV - name: Create Pull Request uses: actions/github-script@v7 with: github-token: ${{ secrets.PAT_TOKEN }} script: | const { data: pullRequest } = await github.rest.pulls.create({ owner: context.repo.owner, repo: context.repo.repo, title: '🤖 Automated Image Update', head: process.env.PR_BRANCH, base: 'main', body: `## Automated Image Update This PR was automatically created by Flux CD image automation. **Changes:** - Updated container images in production environment **Source:** flux-updates-source **Timestamp:** ${new Date().toISOString()}`, }); // Labels hinzufügen await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: pullRequest.number, labels: ['automated', 'flux', 'image-update'] }); console.log(`Pull Request created: ${pullRequest.html_url}`); ``` ## Automatisierter PR-Erstellungsprozess: Der Workflow reagiert auf Push-Events zum `flux-updates-source` Branch und erstellt automatisch einen Pull Request mit den Image-Updates. Dies ermöglicht Code-Review, automatisierte Tests und kontrollierte Deployment-Zyklen. ## Branch-Management: Optional kann automatisches Branch-Cleanup nach PR-Merge in den Repository-Settings aktiviert werden (Settings → General → "Automatically delete head branches"). Diese Implementierung kombiniert die Effizienz der automatischen Image-Erkennung mit den Governance-Anforderungen von Production-Umgebungen und bietet dabei vollständige Traceability über Git-History und PR-Metadaten. --- ## Moderne Blog-Entwicklung: Ein durchgängiger Git-Workflow mit automatisierter Versionierung und Deployment - URL: https://blog.kuepper.nrw/posts/2025-06-15-git-workflow-mit-automatisierter-versionierung-und-deployment/ - Datum: 2025-06-15 - Aktualisiert: 2025-06-15 - Autor: ruediger - Tags: Git, GitHub, Workflow - Kategorien: Git ![Links of the week](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/IMG_0971.webp) # Moderne Blog-Entwicklung: Ein durchgängiger Git-Workflow mit automatisierter Versionierung und Deployment In der modernen Softwareentwicklung ist ein sauberer Deployment-Workflow essentiell für die Qualitätssicherung und effiziente Zusammenarbeit. In diesem Artikel stelle ich meinen bewährten Workflow für die Blog-Entwicklung vor, der drei Stages nutzt und durch automatisierte Versionierung sowie GitOps-Prinzipien unterstützt wird. ## Die drei Stages: Dev, Stage und Prod Mein Blog-Setup basiert auf drei klar getrennten Umgebungen: - **Dev**: Die Entwicklungsumgebung für neue Features und Experimente - **Stage**: Die Staging-Umgebung für finale Tests unter produktionsähnlichen Bedingungen - **Prod**: Die Live-Produktionsumgebung für Endnutzer Diese Trennung spiegelt sich direkt in der Git-Branch-Struktur wider. Ich verwende entsprechend benannte Branches `dev`, `stage` und `main` (für Prod), wobei `main` als Hauptbranch fungiert. ## Automatisierte Versionierung mit Semantic Versioning Ein Kernstück meines Workflows ist die automatisierte Versionierung durch GitHub Actions. Dabei nutze ich Semantic Versioning (SemVer) in Kombination mit aussagekräftigen Commit-Messages. ### Der Entwicklungsprozess Alle Änderungen beginnen im `dev`-Branch. Wenn ich neue Features entwickle oder Bugs behebe, verwende ich spezifische Präfixe in meinen Commit-Messages: ```bash git commit -m "fix: Behebung des Responsive-Problems im Header" # oder git commit -m "feat: Neue Kommentarfunktion hinzugefügt" ``` Diese strukturierten Commit-Messages sind nicht nur für Menschen lesbar, sondern triggern auch automatisch die Versionierung in meinen GitHub Actions. Jeder Commit mit `fix:` oder `feat:` erstellt automatisch einen neuen Git-Tag im Format `v1.9.40-dev.1`, `v1.9.40-dev.2`, usw. ### Von Dev zu Stage: Der erste Qualitätsfilter Sobald alle Entwicklungsarbeiten in `dev` abgeschlossen und getestet sind, erstelle ich einen Pull Request von `dev` nach `stage`. Dieser Merge-Vorgang ist ein bewusster Schritt, der signalisiert: “Diese Änderungen sind bereit für die finale Testphase.” Der Merge nach `stage` triggert automatisch die Erstellung eines neuen Tags im Format `v1.9.40-stage.1`. Falls weitere Änderungen aus `dev` folgen, werden diese als `v1.9.40-stage.2`, `v1.9.40-stage.3` usw. versioniert. ### Der finale Schritt: Stage zu Prod Nach erfolgreichen Tests in der Staging-Umgebung erfolgt der finale Merge von `stage` nach `prod` (main). Dieser Schritt erstellt die finale Produktionsversion, zum Beispiel `v1.9.41` - eine saubere, produktionsreife Versionsnummer ohne Zusätze. ### Backward-Merge: Der Kreis schließt sich Ein oft übersehener, aber wichtiger Schritt ist der Backward-Merge. Nach dem Release führe ich einen Merge von `prod` zurück nach `stage` und `dev` durch. Dies stellt sicher, dass alle Branches synchron bleiben und eventuelle Hotfixes oder produktionsrelevante Anpassungen in alle Umgebungen übernommen werden. Nach diesem Backward-Merge beginnt der Zyklus von neuem, jetzt mit der Basis-Version `v1.9.41-dev.1`. ## Docker-Images und Container-Orchestrierung Parallel zur Git-Versionierung erstellen meine GitHub Actions automatisch Docker-Images für jede Stage. Diese Images werden entsprechend der Branch-Namen getaggt: - `myblog:v1.9.40-dev.1` für Entwicklungsversionen - `myblog:v1.9.40-stage.1` für Staging-Versionen - `myblog:v1.9.41` für Produktionsversionen Diese konsistente Tagging-Strategie ermöglicht es, jederzeit nachzuvollziehen, welche Version in welcher Umgebung läuft. ## GitOps mit FluxCD: Automatisierte Deployments Der finale Baustein meines Workflows ist die automatisierte Deployment-Pipeline mit FluxCD. Durch ImageUpdateAutomations überwacht FluxCD kontinuierlich meine Container-Registry auf neue Images. Sobald ein neues Image für eine Stage verfügbar ist, aktualisiert FluxCD automatisch die entsprechenden Deployments im Kubernetes-Cluster. Dies bedeutet: - Neue Dev-Images werden automatisch in die Entwicklungsumgebung deployed - Stage-Images landen automatisch in der Staging-Umgebung - Produktions-Images werden nach erfolgreichem Merge automatisch live geschaltet ## Vorteile dieses Workflows ### Nachvollziehbarkeit Jede Änderung ist durch die semantische Versionierung klar nachverfolgbar. Ein Blick auf die Git-Tags zeigt sofort, welche Version welche Features oder Fixes enthält. ### Qualitätssicherung Der mehrstufige Prozess stellt sicher, dass nur durchgetestete Änderungen in die Produktion gelangen. Die Staging-Umgebung fungiert als wichtige Barriere vor dem Live-System. ### Automatisierung reduziert Fehler Durch die Automatisierung von Versionierung und Deployment werden menschliche Fehler minimiert. Vergessene Tags oder manuelle Deployment-Fehler gehören der Vergangenheit an. ### Schnelle Iteration Entwickler können sich auf die eigentliche Arbeit konzentrieren, während der Workflow im Hintergrund für konsistente Deployments sorgt. ### Rollback-Fähigkeit Durch die klare Versionierung ist es jederzeit möglich, zu einer vorherigen Version zurückzukehren, falls Probleme auftreten. ## Fazit Dieser Workflow hat sich in meiner Blog-Entwicklung als äußerst effizient erwiesen. Die Kombination aus strukturierten Git-Branches, automatisierter semantischer Versionierung und GitOps-Prinzipien schafft einen robusten, nachvollziehbaren und wartungsarmen Entwicklungsprozess. Der Schlüssel liegt in der Konsistenz: Jeder Schritt folgt klaren Regeln, jede Version ist eindeutig identifizierbar, und jede Umgebung spiegelt exakt den gewünschten Zustand wider. So kann ich mich auf das Wesentliche konzentrieren - das Erstellen großartiger Inhalte für meinen Blog. --- ## Links der Woche KW 24 - URL: https://blog.kuepper.nrw/posts/2025-06-15-links-der-woche-kw24/ - Datum: 2025-06-15 - Aktualisiert: 2025-06-15 - Autor: ruediger - Tags: Links - Kategorien: Links ![Links of the week](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/links-of-the-week.webp) # Tailscale Your legacy VPN belongs in the past Link: [Tailscale](https://tailscale.com) Tailscale ist ein modernes VPN-System, das auf WireGuard basiert und Geräte automatisch über ein privates Netzwerk verbindet. Es erstellt ein verschlüsseltes Mesh-Netzwerk zwischen deinen Geräten - egal ob Computer, Smartphones oder Server - ohne komplizierte manuelle Konfiguration. Die Hauptvorteile: Einfache Installation über Apps, automatische Peer-to-Peer-Verbindungen (wo möglich), Zero-Trust-Sicherheitsmodell und zentrale Verwaltung über eine Web-Oberfläche. Du meldest dich einfach mit deinem Google/Microsoft/GitHub-Account an, installierst die App auf deinen Geräten, und sie können sich sofort sicher miteinander verbinden - auch wenn sie sich in verschiedenen Netzwerken befinden. Besonders praktisch für Homelab-Setups, Remote-Arbeit oder den sicheren Zugriff auf eigene Dienste von unterwegs. # Kubernetes Cluster API Link: [Kubernetes Cluster API](https://cluster-api.sigs.k8s.io/introduction) Kubernetes Cluster API ist ein deklaratives Tool zur Verwaltung von Kubernetes-Clustern als Code. Es behandelt Cluster-Infrastruktur wie normale Kubernetes-Ressourcen - du definierst Cluster in YAML-Manifesten und die Cluster API erstellt, skaliert und verwaltet sie automatisch. Kernkonzept: Management-Cluster verwaltet Workload-Cluster. Du beschreibst gewünschte Cluster-Konfiguration (Node-Anzahl, VM-Größen, Kubernetes-Version) und die API sorgt für die Umsetzung. Provider-Support: Funktioniert mit AWS, Azure, GCP, vSphere, OpenStack und vielen anderen Infrastrukturen über spezielle Provider. Hauptvorteile: Einheitliche API für alle Cloud-Provider, GitOps-Integration, automatische Lifecycle-Verwaltung (Updates, Patches), und Cluster-Templates für Standardisierung. Besonders nützlich für Multi-Cloud-Umgebungen oder wenn du viele Kubernetes-Cluster automatisiert verwalten möchtest. # CyberChef - Das Cyber-Schweizer-Taschenmesser > **Hinweis:** Vergleichbar mit [IT-Tools](https://it-tools.tech) / [IT-Tools Github](https://github.com/CorentinTh/it-tools) Link: [CyberChef Github](https://github.com/gchq/CyberChef) Demo: [Demo Link](https://gchq.github.io/CyberChef/) CyberChef ist eine einfache, intuitive Web-App für die Durchführung aller Arten von "Cyber"-Operationen innerhalb eines Webbrowsers. Diese Operationen umfassen einfache Kodierungen wie XOR und Base64, komplexere Verschlüsselungen wie AES, DES und Blowfish, das Erstellen von Binär- und Hex-Dumps, Komprimierung und Dekomprimierung von Daten, Berechnung von Hashes und Prüfsummen, IPv6- und X.509-Parsing, Änderung von Zeichenkodierungen und vieles mehr. Das Tool ist darauf ausgelegt, sowohl technischen als auch nicht-technischen Analysten zu ermöglichen, Daten auf komplexe Weise zu manipulieren, ohne sich mit komplexen Tools oder Algorithmen auseinandersetzen zu müssen. Es wurde von einem Analysten in seiner 10%-Innovationszeit über mehrere Jahre hinweg konzipiert, entworfen, entwickelt und schrittweise verbessert. --- ## SOPS-verschlüsselte Kubernetes Secrets - URL: https://blog.kuepper.nrw/posts/2025-06-15-verschluesselte-Secrets-mit-SOPS/ - Datum: 2025-06-15 - Autor: ruediger - Tags: Kubernetes, Secrets, encrypt, SOPS, GitOps - Kategorien: Kubernetes ![SOPS Secret Encryption](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/sops-secret-encryption.webp) # Überblick Bei der Verwaltung von Kubernetes Secrets ohne Verwendung eines Secret Vaults (wie HashiCorp Vault oder Azure Key Vault) ist die Verschlüsselung der Secrets vor der Speicherung im Git Repository essentiell. SOPS (Secrets OPerationS) bietet hierfür eine robuste Lösung. # Verschlüsselungsansatz Da Kubernetes Secrets ihre Metadaten (apiVersion, kind, metadata, type) für die korrekte Funktionalität benötigen, erfolgt die Verschlüsselung selektiv nur auf die sensitiven Datenblöcke: * data: Base64-kodierte Secret-Werte * stringData: Klartext Secret-Werte (werden automatisch zu data konvertiert) Die Kubernetes-Metadaten bleiben unverschlüsselt, damit das YAML weiterhin als gültiges Kubernetes-Manifest erkannt wird. # SOPS-Verschlüsselungskommando ```bash sops --encrypt --in-place --encrypted-regex '^(data|stringData)$' \ --age path/to/docker-secret.yaml ``` ## Parameter-Erklärung: * --encrypt: Verschlüsselungsmodus * --in-place: Überschreibt die ursprüngliche Datei * --encrypted-regex '^(data|stringData)$': Regulärer Ausdruck zur Bestimmung der zu verschlüsselnden Felder * --age : Verwendung des AGE-Verschlüsselungsschlüssels # Struktur eines verschlüsselten Docker Registry Secrets ```yaml apiVersion: v1 data: .dockerconfigjson: ENC[AES256_GCM,data:EXAMPLE_ENCRYPTED_DATA_PLACEHOLDER_DO_NOT_USE_IN_PRODUCTION,iv:EXAMPLE_IV_PLACEHOLDER,tag:EXAMPLE_TAG_PLACEHOLDER,type:str] kind: Secret metadata: name: my-docker-registry-secret namespace: my-namespace type: kubernetes.io/dockerconfigjson sops: age: - recipient: age1example123456789abcdefghijklmnopqrstuvwxyz0123456789abcdef enc: | -----BEGIN AGE ENCRYPTED FILE----- EXAMPLE_AGE_ENCRYPTED_CONTENT_PLACEHOLDER DO_NOT_USE_THIS_IN_PRODUCTION THIS_IS_ONLY_AN_EXAMPLE -----END AGE ENCRYPTED FILE----- lastmodified: "2025-01-15T10:30:00Z" mac: ENC[AES256_GCM,data:EXAMPLE_MAC_PLACEHOLDER_FOR_DEMONSTRATION_PURPOSES_ONLY,iv:EXAMPLE_MAC_IV,tag:EXAMPLE_MAC_TAG,type:str] encrypted_regex: ^(data|stringData)$ version: 3.10.2 ``` ## SOPS-Metadaten Erklärung Nach der Verschlüsselung fügt SOPS automatisch einen sops-Block hinzu: AGE-Verschlüsselung * recipient: Der öffentliche AGE-Schlüssel für die Verschlüsselung * enc: Der verschlüsselte Master-Schlüssel im AGE-Format ## Integritätssicherung * mac: Message Authentication Code zur Verifikation der Datenintegrität * lastmodified: Zeitstempel der letzten Änderung * encrypted_regex: Bestätigung des verwendeten Verschlüsselungsmusters * version: SOPS-Version # Vorteile dieses Ansatzes 1. GitOps-Kompatibilität: Secrets können sicher im Git Repository gespeichert werden 2. Kubernetes-Kompatibilität: Metadaten bleiben lesbar für Kubernetes 3. Selektive Verschlüsselung: Nur sensitive Daten werden verschlüsselt 4. Audit-Trail: Git-History für Secret-Änderungen verfügbar 5. Schlüsselrotation: AGE-Schlüssel können rotiert werden # Entschlüsselung und Anwendung ## Entschlüsselung zur Ansicht ```bash sops --decrypt path/to/docker-secret.yaml ``` ## Direkte Anwendung auf Kubernetes ```bash sops --decrypt path/to/docker-secret.yaml | kubectl apply -f - ``` # Sicherheitshinweise * AGE-Private-Keys müssen sicher außerhalb des Repositories gespeichert werden * Regelmäßige Rotation der Verschlüsselungsschlüssel * Verwendung unterschiedlicher Schlüssel für verschiedene Umgebungen (Dev/Staging/Prod) * Backup-Strategie für Verschlüsselungsschlüssel implementieren --- ## Vereinfachte GitOps-Pipeline mit FluxCD und automatischen Image-Updates - URL: https://blog.kuepper.nrw/posts/2025-06-15-fluxcd-image-update-automation/ - Datum: 2025-06-15 - Aktualisiert: 2025-06-15 - Autor: ruediger - Tags: Kubernetes, FluxCD, ImageUpdateAutomation, GitOps - Kategorien: Kubernetes ![FluxCD Image Update Automation](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/fluxcd-image-update-automation.webp) Traditionelle Build-Pipelines sind oft komplex und fehleranfällig. Sie müssen nicht nur den Anwendungscode verarbeiten, sondern auch Helm Charts verwalten, semantische Versionierung durchführen und das finale Deployment orchestrieren. Wenn dabei ein Schritt fehlschlägt – beispielsweise ein fehlerhafter Helm Chart – bricht die gesamte Pipeline ab, obwohl das Docker Image bereits erfolgreich erstellt wurde. Besonders bei größeren Projekten mit längeren Build-Zeiten ist das frustrierend und ineffizient. Die Lösung liegt in der Trennung von Build- und Deployment-Prozessen durch eine GitOps-Architektur. Anstatt alles in einer monolithischen Pipeline zu erledigen, beschränke ich die Build-Pipeline auf das Wesentliche: semantische Versionierung, Docker Image-Erstellung und Push in die Registry. Das Deployment wird komplett an FluxCD delegiert, das kontinuierlich das GitOps-Repository überwacht und automatisch neue Image-Versionen erkennt und ausrollt. Diese Architektur bietet mehrere Vorteile: Build-Pipelines werden einfacher und stabiler, Deployments erfolgen vollautomatisch ohne manuelle Eingriffe, und durch die stage-spezifische Konfiguration können verschiedene Umgebungen (dev, stage, prod) mit unterschiedlichen Image-Tags versorgt werden. FluxCD übernimmt dabei nicht nur das Deployment, sondern auch die automatische Aktualisierung der Image-Tags im GitOps-Repository. Build Pipeline sehen oft wie folgt aus: ![Build Pipeline sehen oft wie folgt aus](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/build-pipeline.png) Diese Piplelines müssen nicht nur den Code selbst auschecken, sie müssen zusätzlich auch noch den HelmChart auschecken könne. Liegen diese wo anders, benötigt man hier wieder einen Token für Pull und Push. Tritt beim Pull oder des Push des HelmCharts ein Problem auf, oder ist der HelmChart fehlerhaft, dann bricht die komplette Pipeline ab. Gerade bei großen Projekten, bei denen ein Build etwas länger dauert ist das sehr ärgerlich. Da das Image eigentlich schon fertig für das Deployment ist, aber ein Fehler im Chart das verhindert. Meine Pipeline macht nur noch SemVer und erstellt das Docker Image und push es in die Docker Registry. Sonst wird da nicht mehr gemacht. Der Rest wird an anderer Stelle erledigt. Ich installiere meine Kubernetes Cluster mit Cloudinit und mache so direkt ein `flux bootstrap`, damit alles in den Clustern installiert wird. ```bash # ... # other stuff: install kubernete, helm, k9s .... # ... # FluxCD Bootstrap export GITHUB_TOKEN="${github_token}" export KUBECONFIG=/etc/rancher/k3s/k3s.yaml # Warten bis Kubernetes API verfügbar ist while ! kubectl cluster-info &>/dev/null; do echo "Waiting for Kubernetes API..." sleep 10 done # FluxCD Bootstrap ausführen flux bootstrap github \ --owner=ruedigerp \ --repository=fluxcd \ --branch=main \ --path=clusters/production \ --personal \ --components-extra=image-reflector-controller,image-automation-controller ``` Im Repo `gitops` ist für jeden Cluster ein Verzeichnis für die jeweile Konfigurationen. Damit werden Helm Chart Repos, Helm Charts, Applications und weitere Stage spezifische Konfigurationen gemacht. Im repo ist auch ein Verzeichnis `/apps/, in dem sind Applications die installiert werden sollen. Wie zum Beispiel mein Blog. ```bash ❯ tree apps/blog apps/blog ├── base │   ├── deployment.yaml │   ├── ingress-http.yaml │   ├── ingress-https.yaml │   ├── kustomization.yaml │   └── service.yaml ├── dev │   ├── deployment-patch.yaml │   └── kustomization.yaml ├── prod │   ├── deployment-patch.yaml │   └── kustomization.yaml └── stage ├── deployment-patch.yaml └── kustomization.yaml ``` Im Base Verzeichnis sind alle Kubernetes YAML-Files die für die Application benötigt werden. Das Deployment enthält wie üblich ein Docker Image: ```bash grep image apps/blog/base/deployment.yaml image: ghcr.io/ruedigerp/ink-blog.kuepper.nrw:v0.0.1-develop.1 ``` Hier ist es egal welche Version eingetragen ist, da der Image Name und Tag je nach Stage ersetzt werden. Denn im Apps Verzeichnis sind für jeden Cluster/Stage ein weiteres Verzeichnis, welches dann die Konfigurationen enthält, die auf dem Ziel Cluster genutzt werden sollen. Wie auch das Patch für die Images. ```bash ❯ grep image apps/blog/prod/deployment-patch.yaml image: ghcr.io/ruedigerp/ink-blog.kuepper.nrw:v1.9.31 # {"$imagepolicy": "blog:blog-policy"} ❯ grep image apps/blog/stage/deployment-patch.yaml image: ghcr.io/ruedigerp/ink-blog.kuepper.nrw:v1.9.32-stage.1 # {"$imagepolicy": "blog:blog-policy"} ❯ grep image apps/blog/dev/deployment-patch.yaml image: ghcr.io/ruedigerp/ink-blog.kuepper.nrw:v1.9.32-develop.2 # {"$imagepolicy": "blog:blog-policy"} ``` Aufgerufen werden sie durch die jeweiligen kustomization.yaml Files in den Cluster/Stage Verzeichnissen. Hier das Beispiel von `prod`: ```yaml ❯ cat apps/blog/prod/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../base patches: - path: deployment-patch.yaml namespace: blog ``` Das sagt Flux an der Stelle es soll alles aus dem Verzeichnis `../base` anwenden, und das Patch-File für das Deployment anwenden. Hier können auch noch weitere Patches für alles mögliche, wie beispielsweise Änderungen am Ingress, Service, Secrets usw. Der Patch für das Deployment sieht wie folgt aus: ```yaml ❯ cat apps/blog/prod/deployment-patch.yaml # apps/blog/stage/deployment-patch.yaml apiVersion: apps/v1 kind: Deployment metadata: name: blog namespace: blog spec: template: spec: containers: - name: blog image: ghcr.io/ruedigerp/ink-blog.kuepper.nrw:v1.9.31 # {"$imagepolicy": "blog:blog-policy"} ``` Der Patch nimmt das Deployment `blog` im Namespace `blog` und ersetzt das image welches in `base/deployment.yaml` gesetzt ist. Wichtig ist in der `deployment.yaml` und `deployment-patch-yaml` der Kommentar hinter dem Image: `# {"$imagepolicy": "blog:blog-policy"}`. Damit weiß ImageUpdateAutomoation von FluxCD welches Image er ersetzten soll und mit welcher Policy. In diesem Fall `blog-policy` im Namespace `blog`. ```yaml # clusters/production/flux-system/kustomizations/private/blog.yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: blog namespace: flux-system spec: dependsOn: - name: infrastructure interval: 5m prune: false path: ./apps/blog/prod sourceRef: kind: GitRepository name: flux-system targetNamespace: blog decryption: provider: sops secretRef: name: sops-age # weitere Sachen wie Patch der Replica, ingress Domains usw. # ... ``` Über weitere Kustomizations lade ich im Verzeichnis `clusters/production` noch viele weitere Sachen wie Helm Repos, Helm Charts, weitere Apps und auch die Konfigurationen der imageUpdates. ```bash tree clusters/production/infrastructure/image-updater/ clusters/production/infrastructure/image-updater/ ├── blog │   ├── image-policy.yaml │   ├── image-repository.yaml │   ├── image-update-automation.yaml │   └── kustomization.yaml ├── image-write-repo.yaml ├── kustomization.yaml ├── other-app │   ├── image-policy.yaml │   ├── image-repository.yaml │   ├── image-update-automation.yaml │   └── kustomization.yaml └── second-other-app ├── image-policy.yaml ├── image-repository.yaml ├── image-update-automation.yaml └── kustomization.yaml ``` Die Image-Update-Automation benötigt für das prüfen auf neue Docker Images, das prüfen ob der Image-Tag für die Stage passt und die Automation selbst drei Konfigurationen. Das Image Repo konfigurieren: ```yaml # clusters/production/infrastructure/image-updater/blog/image-repository.yaml apiVersion: image.toolkit.fluxcd.io/v1beta2 kind: ImageRepository metadata: name: blog-repo namespace: blog spec: image: ghcr.io/ruedigerp/ink-blog.kuepper.nrw interval: 10m provider: generic ``` Damit wird alle 10 Minuten geprüft, ob es ein neues Image gibt. Und die Policy kann da überprüfen welcher Tag auf die entsprechende Stage passt. ```yaml # clusters/production/infrastructure/image-updater/blog/image-policy.yaml apiVersion: image.toolkit.fluxcd.io/v1beta2 kind: ImagePolicy metadata: name: blog-policy namespace: blog # Gleicher Namespace spec: imageRepositoryRef: name: blog-repo # Muss mit ImageRepository.name übereinstimmen filterTags: pattern: '^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' policy: semver: range: '>=1.0.0' ``` Der `filterTag` ist entsprechend der Stages jeweils angepasst. Dafür liegt entsprechend des Clusters, bzw. der Stage, in `/cluster/{prod,stage,dev}/infrastructure/image-updater/blog/image-policy.yaml` die passende Policy. Stage: ```yaml ... filterTags: pattern: '^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' policy: semver: range: '>=1.0.0-stage' ... ``` Dev: ```yaml filterTags: pattern: '^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' policy: semver: range: '>=1.0.0-dev' ``` Die Dateien sind ansonsten identisch. Wenn es einen neuen Tag gibt, wird diese Änderung mit der `ImageUpdateAutomation` vorgenommen und ins GitOps Repo commited. ```yaml # clusters/production/infrastructure/image-updater/blog/image-update-automation.yaml apiVersion: image.toolkit.fluxcd.io/v1beta2 kind: ImageUpdateAutomation metadata: name: blog-automation namespace: blog # Gleicher Namespace spec: sourceRef: kind: GitRepository name: flux-system-write namespace: flux-system git: checkout: ref: branch: main commit: author: email: fluxcdbot@users.noreply.github.com name: fluxcdbot messageTemplate: | Automated image update Automation name: {{ .AutomationObject }} Images: {{- range .Updated.Images }} - {{.}} {{- end }} push: branch: main interval: 1m update: path: "./apps/blog/prod" # Anpassen an deinen Pfad strategy: Setters ``` Damit wird dann in `./apps/blog/prod` das image im `deployment-patch.yaml` geändert und ins Git Repo gepusched. Der Patch ändert dann beim `reconcile` der Kustomization das Deployment und applied es in den Cluster. Ich muss daher keine Änderungen im GitOps Repo machen. Das passiert alles automatisch. Wenn man nach einem Docker Image Build nicht automatisch ausrollen möchte, kann man auch über einen PR die Änderungen machen lassen. So kann man neue Versionen erst ausrollen wenn man es möchte und hat mehr Kontrolle. --- ## FIX: Kubernetes Longhorn löschen hängt in Terminating fest - URL: https://blog.kuepper.nrw/posts/2025-06-14-kubernetes-longhorn-loeschen/ - Datum: 2025-06-14 - Autor: ruediger - Tags: Kubernetes, Longhorn, uninstall - Kategorien: Kubernetes, Login ![Longhorn Namespace und CRDs unlöschbar](https://s3fs.kuepper.nrw/blog-images/posts/2025/06/fix-problem.webp) ### Longhorn-Deinstallationsproblem: Persistierende CRDs und Namespace Bei einem Test von Longhorn als Kubernetes-Storage-Lösung traten Performance-Probleme auf, die zu einer erheblichen Cluster-Verlangsamung führten. Nach der Entscheidung zur Deinstallation wurden zwar Pods und Deployments erfolgreich entfernt, jedoch blieben Custom Resource Definitions (CRDs) und der zugehörige Namespace bestehen. ### Problemanalyse: Namespace `longhorn` verbleibt im Status `Terminating` CRDs lassen sich nicht über Standard-Löschbefehle entfernen Namespace-Löschung wird durch bestehende CRDs blockiert ### Ursache: Longhorn-CRDs enthalten Finalizers, die eine automatische Bereinigung verhindern. Dies ist ein dokumentiertes Problem bei der Longhorn-Deinstallation. ### Lösungsansatz: Entfernung der Finalizers aus allen Longhorn-CRDs Forcierte Löschung der CRDs Bereinigung des Namespace ### Technische Umsetzung: ```bash kubectl get crd | grep longhorn | awk '{print $1}' | xargs -I {} kubectl patch crd {} -p '{"metadata":{"finalizers":[]}}' --type=merge customresourcedefinition.apiextensions.k8s.io/backups.longhorn.io patched customresourcedefinition.apiextensions.k8s.io/engineimages.longhorn.io patched customresourcedefinition.apiextensions.k8s.io/engines.longhorn.io patched customresourcedefinition.apiextensions.k8s.io/nodes.longhorn.io patched customresourcedefinition.apiextensions.k8s.io/replicas.longhorn.io patched customresourcedefinition.apiextensions.k8s.io/sharemanagers.longhorn.io patched customresourcedefinition.apiextensions.k8s.io/snapshots.longhorn.io patched customresourcedefinition.apiextensions.k8s.io/volumeattachments.longhorn.io patched customresourcedefinition.apiextensions.k8s.io/volumes.longhorn.io patched ``` Und jetzt noch den Namespace `longhorn` ```bash kubectl patch namespace longhorn -p '{"metadata":{"finalizers":[]}}' --type=merge namespace/foo patched kubectl delete ns longhorn ❯ kubectl get namespace | grep longhorn ❯ ``` Diese Vorgehensweise löst das persistierende Deinstallationsproblem und ermöglicht eine vollständige Bereinigung der Longhorn-Komponenten. Der Longhorn Namespace ist Geschichte. --- ## Dex SSO mit Traefik und forward-auth - URL: https://blog.kuepper.nrw/posts/2025-04-29-dex-traefik-forward-auth/ - Datum: 2025-04-29 - Autor: ruediger - Tags: SSO, Login, Traefik, Forward-Auth - Kategorien: SSO, Login ![Dex SSO-Login](https://s3fs.kuepper.nrw/blog-images/posts/2025/04/dex-sso.webp) Ich benutze Dex für den SSO Login auf mehreren Seiten von mir. Ich habe E-Mail Logins und Github aktuell bei mir aktiviert. Alle anderen Möglichkeiten die Dex bietet, wie LinkedIn, Facebook, Google uns viele mehr habe ich auch alle einmal getestet. Für die Nutzung von GitHub muss im Github Account eine [OAuth App](https://github.com/settings/developers) angelegt werden. Damit bekommen wir eine Client-ID und ein Client-Secret. Die beiden trägt man in die `values.yaml` für den Dex Helm Chart ein. helm repo add dex https://charts.dexidp.io helm repo update dex Hier eine Beispiel `values.yaml` in der man Domains, Client-ID und Secret noch anpassen muss. Beispiel values.yaml für Dex: ingress: enabled: true className: traefik annotations: cert-manager.io/cluster-issuer: letsencrypt-cluster-issuer traefik.ingress.kubernetes.io/router.entrypoints: websecure traefik.ingress.kubernetes.io/router.tls: "true" hosts: - host: dex.example.com paths: - path: / pathType: ImplementationSpecific tls: - secretName: dex.example.com-cert hosts: - dex.example.com config: connectors: - config: clientID: 5J3FvbjYDQm4XeofPKtc clientSecret: iLuvTwVgbtrcLftWjqy2cIWVb0PVRGZG6F39XCEi loadAllGroups: true orgs: - name: exmaple-Org redirectURI: https://dex.example/callback teamNameField: slug useLoginAsID: true id: github name: GitHub type: github enablePasswordDB: true frontend: issuer: kuepper.nrw logoURL: https://blog.example.com/images/avatar.png theme: dark issuer: https://dex.example.com logger: level: debug oauth2: alwaysShowLoginScreen: true skipApprovalScreen: true staticClients: - id: github name: Traefik Forward Auth OIDC Dex App public: false redirectURIs: - https://login.example.com/_oauth - https://dex.example.com/_oauth - https://intern.example.com/_oauth secret: iLuvTwVgbtrcLftWjqy2cIWVb0PVRGZG6F39XCEi staticPasswords: - email: user1@example.com hash: $2y$10$D6QwNsN5j7hWOHOEPsXMcu77FJOT1Ae2bkfsGKjZLyx26kwZ9UA.S userID: 08a8684b-db88-4b73-90a9-3cd1661f5466 username: user1 storage: config: inCluster: true type: kubernetes Mit dieser values.yaml wird jetzt erst einmal Dex installiert. helm upgrade -i dex dex/dex -n dex -f values.yaml --create-namespace --namespace dex --wait Damit nachher auch Seiten umgeleitet werden können brauchen wir noch einen forward-auth Service im Cluster. Der kümmert sich um die Umleitung auf dex und den Callback nach dem Login. Hier hatte ich mir oauth2-proxy einmal angeguckt. Der muss aber dann in jedem Cluster eingerichtet und Konfiguriert werden. Ausserdem muss er in jeden Namespace rein damit es auch mit allen Services funktioniert. forward-auth ist da viel kleiner und einfacher zu konfigurieren. helm repo add kuepper https://helm.9it.eu/ helm repo update kuepper Hier eine Beispiel values.yaml für den forward-auth: deployment: args: - --secret=iLuvTwVgbtrcLftWjqy2cIWVb0PVRGZG6F39XCEi - --auth-host=login.example.com - --cookie-domain=example.com - --default-provider=oidc - --providers.oidc.issuer-url=https://dex.example.com - --providers.oidc.client-id=github - --providers.oidc.client-secret=iLuvTwVgbtrcLftWjqy2cIWVb0PVRGZG6F39XCEi ingress: domains: - name: auth.example.com tls: true servicename: forward-auth port: 4181 path: / pathtype: Prefix Die values.yaml hat noch mehr Values die angepasse werden können. Ein Liste mit allen Möglichkeiten kann mit `helm values ...` abgerufen werden: helm show values kuepper/traefik-forward-auth Wenn alles installiert ist kann man einen Service, der mit einem Login versehen werden soll einfach per Treafik Ingress Annotation auf den forward-auth und so auf Dex umleiten. In einem Ingress einfach folgende Annotaions hinzufügen oder einen Middleware Eintrag erweitern: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: traefik.ingress.kubernetes.io/router.middlewares: kube-system-forward-auth-dex@kubernetescrd name: gethomepage namespace: gethomepage ... Ruft man jetzt die Seite auf sollte ein Redirect auf Dex erfolgen. Dann kann direkt der Login mit github getestet werden. --- ## Delta Chat mit eigenem Mailserver - URL: https://blog.kuepper.nrw/posts/2025-03-31-deltachat-mit-eigenem-mailserver copy/ - Datum: 2025-03-31 - Autor: ruediger - Tags: DeltaChat, decentralized, secure, messenger, app - Kategorien: Messenger, decentralized ![Delta Chat](https://s3fs.kuepper.nrw/blog-images/posts/2025/03/deltachat.webp) [Alexander Lehmann](https://www.threads.net/@alexlehm) hat auf Threads über [Delta Chat](https://delta.chat/de/) [gepostet](https://www.threads.net/@alexlehm/post/DH0m29TtqrT). Ich dachte natürlich: "Noch ein Messenger?". Aber den Link musste ich anklicken und wenigstens einmal gucken. Ein dezentraler Messenger und sicher soll er sein. Das ist Threema, Singal und Co auch. Aber man kann sich bei einem Chatmail-Server von Delta Chat anmelden oder seinen eigenen Mailserver benutzen. Und da wurde es dann interessant. Ich habe einfach mal Mailaccounts von 2 Domains von mir in der App eingerichtet. Ich habe erst einmal einen Account direkt in der App angelegt und dabei aber den Button nicht gefunden (eher übersehen), mit dem man mit einem eigenen Mailserver verbinden kann. ![Anderen Server verwenden](https://s3fs.kuepper.nrw/blog-images/posts/2025/03/deltachat_own_server.png) Beim anlegen ist ganz unten der Punkt "Anderen Server verwenden". Auf diesen geklickt und anschliessend auf "Klassischen E-Mail-Login", erscheinen weitere Einstellungen zum Mailserver und Mailaccount. ![Delta Chat Mail Settings](https://s3fs.kuepper.nrw/blog-images/posts/2025/03/deltachat-mail-settings.png) Alle Daten vom Mailaccount eingetragen und ich könnte zwischen beiden Account Nachrichten senden. Die Nachrichten landeten im Posteingang und in den Chats in Delta Chat. Die Nachrichten wollte ich aber nicht im Posteingang haben, sondern direkt in den Ordner "DeltaChat" verschieben. Das macht es übersichtlicher und in den Settings von DeltaChat kann man auch einstellen, das nur Nachrichten aus diesem Ordner beachtet werden. Hier meine Sieve Mailfilter Rule: # rule:[deltachat] if anyof (header :contains "Chat-Version" "1.0") { fileinto :create "DeltaChat"; stop; } --- ## HomeLab mit mehreren Clustern hinter einer IP - URL: https://blog.kuepper.nrw/posts/2025-03-17-kubernetes-homelab-haproxy/ - Datum: 2025-03-16 - Autor: ruediger - Tags: HomeLab, Kubernetes, NFS, HAProxy - Kategorien: Kubernetes ![HAProxy](https://s3fs.kuepper.nrw/blog-images/posts/2025/03/haproxy.webp) # Homelab Übersicht Mein kleines Kubernetes Cluster Setup sieht aktuell wie folgt aus.
graph TB;
    Glasfaser-->UDM-Pro;
    UDM-Pro-->MacMini;
    MacMini-->Multipass-VM1;
    MacMini-->Multipass-VM2;
    MacMini-->Multipass-VM3;
    MacMini-->Multipass-VM4;
    MacMini-->Multipass-VM5;
    MacMini-->Multipass-VM6;
    MacMini-->Multipass-VM7;
    MacMini-->Multipass-VM8;
    MacMini-->Multipass-VM9;
    Multipass-VM7-->HaProxy
    HaProxy-->K8s-Controlplane1;
    HaProxy-->K8s-Controlplane2;
    HaProxy-->K8s-Controlplane3;
    HaProxy-->K8s-Worker1;
    HaProxy-->K8s-Worker2;
    HaProxy-->K8s-Worker3;

    subgraph Cluster01;
        subgraph Controlplanes;
            Multipass-VM1-->K8s-Controlplane1;
            Multipass-VM2-->K8s-Controlplane2;
            Multipass-VM3-->K8s-Controlplane3;
        end;
        subgraph Worker;
            Multipass-VM4-->K8s-Worker1;
            Multipass-VM5-->K8s-Worker2;
            Multipass-VM6-->K8s-Worker3;
        end;
    end;

    subgraph Cluster02;
        Multipass-VM8-->K8sdev-Controlplane1;
        Multipass-VM9-->K8sdev-Worker1;
    end;

    NFS-Server-->K8s-Controlplane1;
    NFS-Server-->K8s-Controlplane2;
    NFS-Server-->K8s-Controlplane3;
    NFS-Server-->K8s-Worker1;
    NFS-Server-->K8s-Worker2;
    NFS-Server-->K8s-Worker3;

    NFS-Server-->K8sdev-Controlplane1;
    NFS-Server-->K8sdev-Worker1;
Alle VMs sind auf einem MacMini mit Multipass virtualisiert. Cluster01 hat 3 Controlplanes und 3 Worker. Der Cluster02 ist für DEV und Tests, hat aktuell nur ein Controlplane und einen Worker. Beide Cluster können aber bei Bedarf jederzeit vergrössert werden. Aktuell sind sogar noch 3 weitere Cluster aktiv, da aktuell viel getestet und ausprobiert wird. Auf der Multipass VM 07 ist ein HA-Proxy installiert, der sich um die Loadbalancing Frontends und Backends kümmert. Hier ist auch je Cluster eine VIP für die Kubernetes API. Über die kann die Kubernetes API über eine IP angesprochen werden und landet dann auf eines der Controlplanes.
graph TD;
    subgraph API-k8s;
        HaProxyAPI[HAProxy, VIP: 192.168.67.200, Port 6443]-->K8s-Controlplane1;
        HaProxyAPI[HAProxy, VIP: 192.168.67.200, Port 6443]-->K8s-Controlplane2;
        HaProxyAPI[HAProxy, VIP: 192.168.67.200, Port 6443]-->K8s-Controlplane3;
    end;
Das gleiche gilt für die HTTP Zugriffe über Port 80, hier wird aber eine andere VIP genommen. Der Grund ist, das hier über Metallb andere oder auch weitere IPS für LoadbalancerIP oder einem weiteren IngressController im Kubernetes genutzt werden könnten. Aber auch für Migrationen des kompletten Clusters oder einzelner Services auf andere Cluster.
graph TD;
    subgraph Ingress-http;
        HaProxyhttp[HAProxy, VIP: 192.168.67.17, Port 80]-->K8s-Controlplane1-http;
        HaProxyhttp[HAProxy, VIP: 192.168.67.17, Port 80]-->K8s-Controlplane2-http;
        HaProxyhttp[HAProxy, VIP: 192.168.67.17, Port 80]-->K8s-Controlplane3-http;
        HaProxyhttp[HAProxy, VIP: 192.168.67.17, Port 80]-->K8s-Worker1-http;
        HaProxyhttp[HAProxy, VIP: 192.168.67.17, Port 80]-->K8s-Worker2-http;
        HaProxyhttp[HAProxy, VIP: 192.168.67.17, Port 80]-->K8s-Worker3-http;
    end;
Auch für https sind im HA Proxy die Controlplanes und Worker des Clusters eingetragen. Die IP 192.168.67.17 ist die gleiche, da ich hier keine Trennung vornehme. Es währe aber möglich HTTPs Traffik nur auf bestimmte Nodes zu leiten oder gar schon vorne am HA-Proxy auf eine dedizierte IP. Da kommt es immer auf das Setup und den Service an den man bereitstellen möchte. Da Option besteht aber und man könnte auf stärkere Hardware TLS laufen lassen und auf den schwachen Nodes HTTP-Only.
graph TD;
    subgraph Ingress-https;
        HaProxyhttps[HAProxy, VIP: 192.168.67.17, Port 443]-->K8s-Controlplane1-https;
        HaProxyhttps[HAProxy, VIP: 192.168.67.17, Port 443]-->K8s-Controlplane2-https;
        HaProxyhttps[HAProxy, VIP: 192.168.67.17, Port 443]-->K8s-Controlplane3-https;
        HaProxyhttps[HAProxy, VIP: 192.168.67.17, Port 443]-->K8s-Worker1-https;
        HaProxyhttps[HAProxy, VIP: 192.168.67.17, Port 443]-->K8s-Worker2-https;
        HaProxyhttps[HAProxy, VIP: 192.168.67.17, Port 443]-->K8s-Worker3-https;
    end;
# VIP IPs mit Keepalived Die VIPs werden auf der VM07 (HAProxy) per keepdalived hochgefahren. ``` global_defs { enable_script_security script_user root } vrrp_script chk_haproxy { script 'killall -0 haproxy' interval 2 } vrrp_instance cluster01-api-vip { interface enp0s1 state MASTER priority 200 virtual_router_id 51 virtual_ipaddress { 192.168.64.200/24 } } vrrp_instance cluster01-ingress-vip { interface enp0s1 state MASTER priority 200 virtual_router_id 52 virtual_ipaddress { 192.168.67.17/24 } } ``` Die Instancen werden einfach hinzugefügt und dabei die `virtual_router_id` hochgezählt. Die müssen eindeutig sein. Nach einem Restart von Keepalived sind nach ein paar Sekunden die zusätzlichen IPs auf dem Interface hochgefahren: ``` systemctl restart keepalived.service 2: enp0s1: mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:73:a6:5c brd ff:ff:ff:ff:ff:ff inet 192.168.64.92/24 metric 100 brd 192.168.64.255 scope global dynamic enp0s1 valid_lft 2061sec preferred_lft 2061sec inet 192.168.67.17/24 scope global enp0s1 valid_lft forever preferred_lft forever inet 192.168.64.200/24 scope global secondary enp0s1 valid_lft forever preferred_lft forever ``` # HAProxy Konfiguration Jetzt kann der HAProxy konfiguriert werden und die einzelnen Services auf die VIPs gebunden werden. ## Global und Default Der Global und Default Block in der haproxy.cfg sieht ao aus: ``` global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy # stats socket /run/haproxy/admin.sock mode 660 level admin stats socket /var/run/haproxy.sock mode 600 level admin stats timeout 30s user haproxy group haproxy daemon # Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private # See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets defaults log global mode http option httplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http ``` ## Das erste Frontend Anschliessend werden die Frontends konfiguiert. Frontends sind bei HAProxy die Services, die auf einer IP auf einem bestimmten Port listenen und auf ein Backend verweisen. Zu Backends dann weiter unten mehr. Als erstes das Frontend für die Kubernetes API auf Port 6443. ``` frontend k3s-api-frontend bind 192.168.64.200:6443 mode tcp option tcplog default_backend k3s-backend ``` ## Das erste Backend Das Backend dazu enthält jetzt aber nicht nur einen Eintrag, sondern bekommt alle Controlplanes des Clusters. ``` backend k3s-backend mode tcp option tcp-check balance roundrobin default-server inter 10s downinter 5s server controlplane1 192.168.64.91:6443 check server controlplane2 192.168.64.92:6443 check server controlplane3 192.168.64.93:6443 check ``` Damit sind die drei Controlplanes eingetragen und ein Aufruf auf `192.168.64.200:6443` gibt die Ausgabe der Kubernetes API zurück. Diese IP und den Port kann auch so in die Kubeconfig eingetragen werden. Das hat den Vorteil das wir jederzeit einen oder mehrere Controlplanes offline nehmen können und weiter auf die API zugreifen können. ## HTTP/HTTPS Frontend/Backend Das Frontend für HTTP und HTTPs sieht genau so aus. Hier einmal wie es für einen Cluster sein könnte. Anschliessend aber mit weiteren Optionen, da wir ein Frontend haben wollen, welches auf mehrere Backends zeigt. Wir wollen ja extern nur eine IP benutzen. Gerade an einem Anschluss fürs HomeLab mit DSL oder Glasfaser hat man ja nur eine IP. Und IPv6 halten ja zu viele noch für Teufelswerk. ``` frontend k8s_http_lb_frontend bind 192.168.64.17:80 mode http option httplog log-format "%ci:%cp [%t] %ft %b %s %TR/%Tw/%Tc/%Tr/%Tt %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r" default_backend k8s_http_lb_backend frontend k8s_https_lb_frontend bind 192.168.64.17:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 mode http option httplog log-format "%ci:%cp [%t] %ft %b %s %TR/%Tw/%Tc/%Tr/%Tt %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r" default_backend k8s_https_lb_backend ``` Und die Backends dazu: ``` backend k8s_http_lb_backend mode tcp option tcp-check balance roundrobin default-server inter 10s downinter 5s server controlplane1 192.168.64.91:80 check server controlplane2 192.168.64.92:80 check server controlplane3 192.168.64.93:80 check server worker1 192.168.64.94:80 check server worker2 192.168.64.95:80 check server worker3 192.168.64.96:80 check backend k8s_https_lb_backend mode http balance roundrobin default-server inter 10s downinter 5s server controlplane1 192.168.64.91:443 check server controlplane2 192.168.64.92:443 check server controlplane3 192.168.64.93:443 check server worker1 192.168.64.94:443 check server worker2 192.168.64.95:443 check server worker3 192.168.64.96:443 check ``` Damit wäre das ganze lauffähig und wir hätten die API, HTTP und HTTPs Ingress erreichbar. ## Weitere Backends Aber es sind ja mehrere Cluster und die externe IP am Anschluss soll ja auf den HAProxy geleitet werden und dann weiter an den entsprechenden Cluster. Cluster01 hat die Domain k8s01.example.net und entsprechende Subdomains web1.k8s01.example.net, web2.k8s01.example.net usw. Cluster01 DEV hat die Domain k8s01-dev.example.net und entsprechende Subdomains web1.k8s01-dev.example.net, web2.k8s01-dev.example.net usw. Cluster02 DEV hat die Domain k8s02-dev.example.net und entsprechende Subdomains web1.k8s02-dev.example.net, web2.k8s02-dev.example.net usw. Das ganze kann beliebig weiter getrieben werden und weitere Cluster hinzugüfgt werden. ## Match Domains Damit das ganze funktioniert müssen wir als erstes das Frontent für HTTP und HTTPs erweitern. ``` frontend k8s_http_lb_frontend bind 192.168.64.17:80 mode http option httplog log-format "%ci:%cp [%t] %ft %b %s %TR/%Tw/%Tc/%Tr/%Tt %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r" acl is_k8s01_dev hdr(host) -m reg ^[a-zA-Z0-9-]+\.k8s01-dev\.example\.net$ acl is_k8s02_dev hdr(host) -m reg ^[a-zA-Z0-9-]+\.k8s02-dev\.example\.net$ use_backend http_k8s01_dev_backend if is_k8s01_dev use_backend http_k8s02_dev_backend if is_k8s02_dev default_backend k8s_http_lb_backend frontend k8s_https_lb_frontend bind 192.168.64.17:443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 mode http option httplog log-format "%ci:%cp [%t] %ft %b %s %TR/%Tw/%Tc/%Tr/%Tt %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r" acl is_k8s01_dev hdr(host) -m reg ^[a-zA-Z0-9-]+\.k8s01-dev\.example\.net$ acl is_k8s01_dev req.ssl_sni -m reg ^[a-zA-Z0-9-]+\.k8s01-dev\.example\.net$ acl is_k8s02_dev hdr(host) -m reg ^[a-zA-Z0-9-]+\.k8s02-dev\.example\.net$ acl is_k8s02_dev req.ssl_sni -m reg ^[a-zA-Z0-9-]+\.k8s02-dev\.example\.net$ use_backend https_k8s01_dev_backend if is_k8s01_dev use_backend https_k8s02_dev_backend if is_k8s02_dev default_backend k8s_https_lb_backend ``` ## Die zusätzlichen Backends Die beiden Backends der weiteren Cluster anlegen: ``` backend http_k8s01_dev_backend mode tcp option tcp-check balance roundrobin default-server inter 10s downinter 5s server controlplane1 192.168.64.100:80 check server worker1 192.168.64.101:80 check backend https_k8s01_dev_backend mode http balance roundrobin default-server inter 10s downinter 5s server controlplane1 192.168.64.100:443 check server worker1 192.168.64.101:443 check backend http_k8s02_dev_backend mode tcp option tcp-check balance roundrobin default-server inter 10s downinter 5s server controlplane1 192.168.64.110:80 check server worker1 192.168.64.111:80 check backend https_k8s02_dev_backend mode http balance roundrobin default-server inter 10s downinter 5s server controlplane1 192.168.64.110:443 check server worker1 192.168.64.111:443 check ``` Damit würde das ganze jetzt mit dem Production, Dev 1 und Dev 2 funktionieren und die Requests werden je nach Domain auf den richtigen Cluster geleitet. # TLS-Termination mit Letsencrypt Wer genau gegeuckt hat sieht in den Frontend Blöcken bei der Option `bind` beim Frontent für https `ssl crt /etc/haproxy/certs/ alpn h2,http/1.1`. Der HAProxy macht die TLS-termination, also auch das handling der HTTPs Verbindungen, inklusive der Zertifikaten. Ich habe meine Domains bei Cloudflare und habe dort einen API-Key angelegt, mit dem ich DNS Zonen lesen und bearbeiten kann. Cludflare DNS auch aus dem Grund, weil man so auch Wildcard Zertifikate erstellen kann, was die ganze Sache sehr viel einfacher macht. Man muss nicht für jeden neuen Host-Eintrag ein neues Zertifikat erstellen. acme.sh installieren: ``` curl https://get.acme.sh | sh -s email=youraddress@example.com ``` Anscliessend noch den API-Key von Cloudflare mit dem passenden Account in die Env Vars per expose packen: ``` export CF_Key="763eac4f1bcebd8b5c95e9fc50d010b4" export CF_Email="alice@example.com" ``` Die benötigten Zertifikate erstellen: ``` acme.sh --issue --dns dns_cf -d k8s01.example.net -d '*.k8s01.example.net' --server letsencrypt acme.sh --issue --dns dns_cf -d k8s01-dev.example.net -d '*.k8s01-dev.example.net' --server letsencrypt acme.sh --issue --dns dns_cf -d k8s02-dev.example.net -d '*.k8s02-dev.example.net' --server letsencrypt ``` Die Zertifikate sind damit auch erledigt und man muss diese jetzt nur noch für HaProxy bereitstellen: ``` cat /root/.acme.sh/k8s01.example.net_ecc/fullchain.cer /root/.acme.sh/k8s01.example.net_ecc/k8s01.example.net.key > /etc/haproxy/certs/k8s01.example.net.pem cat /root/.acme.sh/k8s01-dev.example.net_ecc/fullchain.cer /root/.acme.sh/k8s01-dev.example.net_ecc/k8s01-dev.example.net.key > /etc/haproxy/certs/k8s01-dev.example.net.pem cat /root/.acme.sh/k8s02-dev.example.net_ecc/fullchain.cer /root/.acme.sh/k8s02-dev.example.net_ecc/k8s02-dev.example.net.key > /etc/haproxy/certs/k8s02-dev.example.net.pem ``` Wenn man noch mehr Cluster und Domains hat muss man die beiden letzten Schritte auch für diese Domains ausführen. `systemctl restart haproxy.service` und prüfen ob alles ok ist. Dazu kann man auch die Stats Option im HAProxy aktivieren und auch da mit prüfen ob alle Backend eingetragen sind. # HAProxy Stats ``` listen stats bind *:9000 stats enable stats uri /stats stats refresh 5s stats realm HomeLab\ Statistics stats auth youruser:yoursecretpassword ``` Die IP mit Port 9000 im Browser öffnen und die Stats Seite von HAProxy zeigt alle Frontends und Backends an. # Trotz TLS in HAProxy kann Cert-Manager genutzt werden Hier wird das SSL-Offloading vom HAProxy gemacht. Das ist nötig, damit HAProxy auch in die Verbindungen gucken kann und anhad ` Host` im Header unterscheiden kann, zu welchem Backend der Request gesendet werden muss. Ich teste und nutze in Kubernetes auch den Cert-Manager. Das möchte ich auch weiter nutzen. Das ist aber auch kein Problem, da ein Ingress mit TLS, über den Issuer/ClusterIssuer genau so den Cert-Manager triggert wie bis her. Der legt dann den `http-solver` an und den passenden Ingress, der über den Pfad auf den `http-solver` zeigt. Der Request geht dann ganz normal an das externe Interface, zum MacMini, HAProxy und wird dann per HTTP an den richtigen Cluster geleitet. Sollte ich also den HAProxy für Wartungen offline nehmen müssen, dann ist für mich aktuell nur der Production Cluster relevant. Und dafür kann ich dann einfach in der UDM-Pro einfach auch direkt auf den Cluster leiten lassen. Die Zertifikate sind dort dann auch schon vorhanden und es gibt keine Probleme oder Ausfälle. # Weitere Anregungen zu HAProxy Wer nur einen Cluster betreibt muss den SSL Part nicht machen. In dem Fall kann man auch weiter alle Zertifikate nur im Kubernetes mit dem Cert-Manager erstellen und stellt die Verbindungen im Backend, bei der Option `mode`, einfach auf `tcp`. Das `httplog` muss dann auch entfernt werden. Für Wartungen an Kubernetes Nodes können diese auch einfach ausgetragen werden oder hinter dem `check` trägt man einfach `disabled` zusätzlich ein und macht einen reload. Das setzt setzt den Server auf Maintainance. HAProxy ist sehr mächtig. Zum Beispiel wird hier einfach nur Roundrobin genutzt. Es Loadbalancer nach roundrobin, leastconn, source IP oder weiteren Loadbalancing-Algorithmen genutzt werden. Genau so kann Sticky Sessions mit Sticky Tables genutzt werden. Die Beispiele oben leiten bestimmte Sub-Sub-Domains auf Backends um, da auf die Subdomain gematched wird. Wer jetzt überlegt: > "Ja, aber was mache ich wenn ich alle Domains umziehen möchte, aber ich kann nicht alle auf einmal umziehen?" Das geht auch sehr einfach und war auch am Anfang meine Überlegung es komplett so zu machen. Dann hätte ich aber jeden Hostname (Alos Sub-Sub-Domain) beim anlegen händisch im HAProxy hinzufügen müssen. Das will ich nicht, da ich einfach schnell Dinge im Kubernetes anlegen will und sie dann sofort funktionieren. Aber man kann anstatt einem Regex auch mit Domainlisten arbeiten. Oder auch HAProxy Maps einsetzen: Dafür eine Datei anlegen `/etc/haproxy/maps/hosts.map` ``` #domainname backendname nginx.k8s02-dev.example.net https_k8s02_dev_backend nginx2.k8s02-dev.example.net https_k8s02_dev_backend nginx.k8s01-dev.example.net https_k8s01_dev_backend ngin2.k8s01-dev.example.net https_k8s01_dev_backend nginx.k8s01.example.net https_k8s01_backend # [...] api.k8s01.example.net https_k8s01_backend ``` Die Frontend Konfiguration dafür würde dann so ausehen: ``` frontend default bind :80 use_backend %[req.hdr(host),lower,map_dom(/etc/haproxy/maps/hosts.map,be_default)] ``` Plus ggf. die IP fürs Bind, Loadbalancing-Algorithmen und weitere Optionen. Domain und Backend Listen können auch im Zusammenspiel mit ALCs genutzt werden. Es können auch bestimmte Routen auf andere Backend umgeleitet werden: `/etc/haproxy/maps/routes.map` ``` /api be_api /login be_auth ``` Frontend: ``` frontend www bind :80 use_backend %[path,map_beg(/etc/haproxy/maps/routes.map,be_default)] ``` Man sieht, HAProxy ist sehr mächtig und kann sehr viel. Was heute nur noch auf HAProxy Installationen zum einsatz kommt, die man eigentlich abschalten sollte, ist die Weiche für Desktop und Mobile Webseiten. Ja das haben einige früher so gemacht und ich kenne noch ein paar die das immer noch einsetzen müssen, da deren CMS weiter betrieben werden muss, weil keiner die Eier hat auch mal alten Schrott abzuschalten. ;-) --- ## Multipass K3S VM mit Cloud-init - URL: https://blog.kuepper.nrw/posts/2025-02-03-multipass-k3s-vm-mit-cloud-init/ - Datum: 2025-02-03 - Autor: ruediger - Tags: Kubernetes, k3s, HomeLab, multipass, Cloud-init - Kategorien: Kubernetes ![k3s k9s](https://s3fs.kuepper.nrw/blog-images/posts/2024/12/tmux.webp) # Multipass installieren ❯ brew install multipass # Cloud-Init Yaml erstellen cloud-init.yaml packages: # - traceroute # - frr runcmd: - export HOME='/home/ubuntu' - export USER='ubuntu' - cd $HOME - curl -sfL https://get.k3s.io | sh -s - server --cluster-init --disable=servicelb --tls-san=192.168.64.251 --disable=traefik - echo "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml" > /root/.bashrc - wget https://github.com/derailed/k9s/releases/download/v0.32.7/k9s_Linux_arm64.tar.gz; tar xzf k9s_Linux_arm64.tar.gz; mv k9s /usr/local/bin/; - curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3; chmod 700 get_helm.sh; ./get_helm.sh # VM erstellen ❯ multipass launch -c 1 -m 1G --disk 10G --network en0 --name controlplane-test --cloud-init cloud-init.yaml ❯ multipass list Name State IPv4 Image cloud-init-test Running 192.168.64.45 Ubuntu 24.04 LTS 10.0.2.109 10.42.0.0 10.42.0.1 # In VM einloggen ❯ multipass shell controlplane-test Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-51-generic aarch64) Last login: Mon Feb 3 20:26:31 2025 from 192.168.64.1 ubuntu@controlplane-test:~$ sudo su - root@controlplane-test:~# kubectl get nodes NAME STATUS ROLES AGE VERSION controlplane-test Ready control-plane,etcd,master 7m v1.31.5+k3s1 root@controlplane-test:~# kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-ccb96694c-knm8m 1/1 Running 0 6m58s kube-system local-path-provisioner-5cf85fd84d-h9rjg 1/1 Running 2 (6m49s ago) 6m58s kube-system metrics-server-5985cbc9d7-cv25j 1/1 Running 2 (6m49s ago) 6m58s root@controlplane-test:~# # VM löschen ❯ multipass delete controlplane-test ❯ multipass purge --- ## Kubernetres Ingress DNS PiHole - URL: https://blog.kuepper.nrw/posts/2025-01-19-kubernetres-ingress-dns-pihole copy/ - Datum: 2025-01-19 - Autor: ruediger - Tags: Kubernetes, HomeLab, external-DNS, PiHole - Kategorien: Kubernetes ![piHole local dns](https://s3fs.kuepper.nrw/blog-images/posts/2025/01/pihole-local-dns.webp) ## Lokales Lab auf dem Mac mit DNS Recordes Ich habe auf dem Mac Mini mit Multipass mehrere Server, die mit Kubernetes mehrere Dienste bereitstellen. Damit diese auch per Domain angesprochen werden benutze ich die lokalen DNS Records im PiHole. Wenn ich einen Service per Ingress oder Service bereitstelle wird automatisch ein DNS Record angelegt. Dazu benutze ich External-DNS. Ich kann damit entweder im PiHole die DNS Records anlegen oder bei Cloudflare in einer Zone. External-DNS kann aber auch mit [sehr vielen](https://github.com/kubernetes-sigs/external-dns?tab=readme-ov-file#the-latest-release) anderen DNS-Providern genutzt werden. In diesem Post beschreibe ich das einrichten im PiHole. ## Konfiguration Namespace anlegen: kubectl create ns external-dns Secret anlegen: kubectl create secret generic pihole-password --from-literal EXTERNAL_DNS_PIHOLE_PASSWORD=YourSecret -n external-dns YourSecret ist das Passwort für den Login in Deinem PiHole. pihole.yaml --- apiVersion: v1 kind: ServiceAccount metadata: name: external-dns namespace: external-dns --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: external-dns namespace: external-dns rules: - apiGroups: [""] resources: ["services","endpoints","pods"] verbs: ["get","watch","list"] - apiGroups: ["extensions","networking.k8s.io"] resources: ["ingresses"] verbs: ["get","watch","list"] - apiGroups: [""] resources: ["nodes"] verbs: ["list","watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: external-dns-viewer namespace: external-dns roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: external-dns subjects: - kind: ServiceAccount name: external-dns namespace: external-dns --- apiVersion: apps/v1 kind: Deployment metadata: name: external-dns namespace: external-dns spec: strategy: type: Recreate selector: matchLabels: app: external-dns template: metadata: labels: app: external-dns spec: serviceAccountName: external-dns containers: - name: external-dns image: registry.k8s.io/external-dns/external-dns:v0.14.1 # If authentication is disabled and/or you didn't create # a secret, you can remove this block. envFrom: - secretRef: # Change this if you gave the secret a different name name: pihole-password args: - --source=service - --source=ingress - --domain-filter=kuepper.lab # Pihole only supports A/CNAME records so there is no mechanism to track ownership. # You don't need to set this flag, but if you leave it unset, you will receive warning # logs when ExternalDNS attempts to create TXT records. - --registry=noop # IMPORTANT: If you have records that you manage manually in Pi-hole, set # the policy to upsert-only so they do not get deleted. - --policy=upsert-only - --provider=pihole # Change this to the actual address of your Pi-hole web server - --pihole-server=http://10.0.2.240 securityContext: fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes token files In dem `pihole.yaml` muss die IP für den piHole angepasst werden: - --pihole-server=http://10.0.2.240 Zusätzlich sollte die Domain angepasst werden. Die IP 10.0.2.240 mit der IP Deines PiHole ersetzen, da Du sehr wahrscheinlich nicht `kuepper.lab` benutzen willst. - --domain-filter=yourdomain.tld Das yaml-File in Deinem Kubernetes installieren und schon werden die Records automatisch angelegt. kubectl apply -f pihole.yaml ## Ingress anlegen und Tests ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ink-blog namespace: ink spec: ingressClassName: traefik rules: - host: ink.kuepper.lab http: paths: - backend: service: name: ink-blog port: number: 80 path: / pathType: Prefix Den Ingress in Deinem Cluster anlegen: kubectl apply -f ingress.yaml Im Logfile vom external DNS sollte 1-2 Minuten nach dem apply folgende zeile im Log auftauchen: external-dns-786bc76c79-hbw84 time="2025-01-18T23:39:56Z" level=info msg="add ink.kuepper.lab IN A -> 192.168.64.6" Die IP `192.168.64.6` ist die IP von meinem Cluster und der Domainname (fqdn) ink.kuepper.lab wird im PiHole eingetragen. ![piHole local dns](https://s3fs.kuepper.nrw/blog-images/posts/2025/01/pihole-local-dns.png) Überprüfen wir das anschliessend, wird der hostname richtige aufgelöst: host ink.kuepper.lab ink.kuepper.lab has address 192.168.64.6 Dieses Setup ist für lokale Tests, kann aber auch für Services genutzt werden die extern erreichbar sind, so das die benötigten DNS Record automatisch bei Cloudflare oder anderen Providern eingerichtet werden. Dabei kann jeder Service auch Proxied bei Cloudflare eingerichtet werden. Also auch it SSL Zertifikaten. Im Free Account für Cloudflare können keine sub-sub Domains also xzy.foobar.yourdomain.tld mit einem Zertifikat ausgestattet werden. Diese kann man dann einfach ohne Cloudflare Proxy einrichten und dann lokal per Cert-Manager und Letsencrypt einfach mit Zertifikate ausstatten. Das beschreibe ich dann in einem weiterem Post. In diesem werde ich dann zeigen wie man das Zertifikat pro Domain oder einfach ein Wildcard Zertifikat erstellt. Je nachdem was man benötigt und wie Dein Setup ist. --- ## tmux Sessions mit tmuxifier verwalten - URL: https://blog.kuepper.nrw/posts/2024-12-18-tmux-sessions-mit-tmuxifier/ - Datum: 2024-12-18 - Autor: ruediger - Tags: MacOS, iTerm, tmux, tmuxifier - Kategorien: MacOS ![iTerm zsh](https://s3fs.kuepper.nrw/blog-images/posts/2024/12/tmux.webp) ## tmux Session mit Tmuxifier Wer kennt es nicht: Ein Kollege kommt an den Schreibtisch und beschreibt ein Problem auf einem Cluster. Man öffnet ein Terminal und 4–5 Tabs, da man auf 2–3 Servern per SSH zugreifen muss, sowie 2–3 Shells für das Config-Verzeichnis und z. B. für Tests mit Curl. Allein die Logins, Verzeichniswechsel und das Öffnen der Konfigurationen zum Vergleichen dauert, bis man endlich loslegen kann. Ich benutze im Terminal Tmux und dafür das Tool Tmuxifier, um diese Schritte zu automatisieren. [Tmuxify](https://github.com/jimeh/tmuxifier) dein Tmux! Erstelle, bearbeite, verwalte und lade komplexe Tmux-Konfigurationen für Sitzungen, Fenster und Panes mit Leichtigkeit. Kurz gesagt, Tmuxifier ermöglicht es dir, „Layout“-Dateien einfach zu erstellen, zu bearbeiten und zu laden. Diese Dateien sind einfache Shell-Skripte, in denen du den Tmux-Befehl sowie von Tmuxifier bereitgestellte Hilfsbefehle verwendest, um Tmux-Sitzungen und Fenster zu verwalten. ### Fenster-Layouts Fenster-Layouts erstellen ein neues Tmux-Fenster, wobei optional der Fenstertitel und das Root-Verzeichnis festgelegt werden können, in dem sich alle Shells standardmäßig befinden. Sie ermöglichen es dir, ein Fenster einfach in spezifisch dimensionierte Panes zu unterteilen und es nach deinen Wünschen anzupassen. Du kannst ein Fenster-Layout direkt in deiner aktuellen Tmux-Sitzung laden oder es in ein Sitzungs-Layout integrieren, sodass das Fenster zusammen mit der Sitzung erstellt wird. ### Sitzungs-Layouts Sitzungs-Layouts erstellen eine neue Tmux-Sitzung und legen dabei optional einen Sitzungstitel und ein Root-Verzeichnis fest, in dem sich alle Shells der Sitzung standardmäßig befinden. Fenster können der Sitzung entweder durch das Laden bestehender Fenster-Layouts hinzugefügt oder direkt innerhalb der Sitzungs-Layout-Datei definiert werden. ## Tmuxifier installieren Installation von Tmuxifier mit homebrew: git clone https://github.com/jimeh/tmuxifier.git ~/.tmuxifier ## Konfiguration Als Erstes erstellt man eine neue Session, die man in Zukunft einfach mit load-session immer wieder aufrufen kann: tmuxifier create-session example Dadurch wird die Datei `.tmuxifier/layouts/example.session.sh` angelegt. Diese wird automatisch geöffnet und kann anschließend bearbeitet werden. session_root "~/Code/example-cluster/" if initialize_session "example"; then # Create a new window inline within session layout definition. new_window "cluster1" new_window "cluster1" new_window "deploy" new_window "services" new_window "shell # Select the default active window on session creation. select_window 0 run_cmd "KUBECONFIG=/Users/rk/Code/example-cluster/cluster1-kubeconfig.yaml" run_cmd "cluster1" select_window 1 run_cmd "KUBECONFIG=/Users/rk/Code/example-cluster/cluster2-kubeconfig.yaml" run_cmd "cluster1" select_window 2 run_cmd "cd deployments; nvim " select_window 3 run_cmd "cd services; nvim " select_window 4 run_cmd "cd deployments" select window 0 fi # Finalize session creation and switch/attach to it. finalize_and_go_to_session Muss ich auf die beiden Cluster und die entsprechenden Verzeichnisse zugreifen, um etwas zu debuggen, auszurollen usw., kann ich einfach eine neue Tmux-Session erstellen, die dann 4 Tmux-Fenster öffnet. In den ersten beiden Fenstern sind die beiden Kubernetes-Cluster mit k9s geöffnet. Im 3. und 4. Fenster befinden sich die Deployments (Helm-Charts) bzw. Services (Code und Docker-Skripte) und sind direkt in nvim geöffnet. Das 5. Fenster ist eine Shell, die direkt im Verzeichnis deployments startet, um schnell Pakete erstellen oder Deployments durchführen zu können. Die Session starte ich dann mit: tmuxifier load-session example Nach 1–2 Sekunden ist in Tmux eine neue Session mit dem Namen Example geöffnet – mit 5 Fenstern, die genau so gestartet und geöffnet wurden, wie in der Tmuxifier-Konfiguration angegeben. Falls dich jemand fragt, warum ich die kubeconfig.yaml-Dateien pro Cluster habe und nicht in der globalen Benutzer-Kubeconfig-Context-Datei: Ich benutze ein Tool (direnv) für Environment-Variablen (Env-Vars) in der Shell, das in Verzeichnissen die entsprechenden Env-Vars setzt, die dort benötigt werden. Das werde ich in einem anderen Post genauer beschreiben. Öffnet man jetzt die Session- und Fensterliste, sieht man die Session mit allen Fenstern und kann diese auswählen. Bei mir gibt es aktuell drei Sessions mit mehreren Fenstern. Die Session aus der Konfiguration ist die Session `example` mit ihren fünf Tmux-Fenstern. (1) + dev-cluster: 2 windows (2) - example: 4 windows (3) ├─> 0: cluster1* (4) ├─> 1: cluster1 (5) ├─> 2: deploy (6) └─> 3: services (7) └─> 3: shell (8) + prod-cluster: 3 windows ### Tmuxifier erleichtert wiederkehrende Sessions Die Aufgaben und die damit einhergehenden, immer wieder gleichen Schritte, die man täglich ausführt, lassen sich mit Tmuxifier schnell und einfach automatisieren. Die Konfiguration ist recht simpel, und wenn man – wie ich – täglich auf mehreren Servern und Clustern arbeitet, kann man sich hierdurch einige Tasks erheblich erleichtern. Ich habe eine Konfiguration für einen Test- und einen Produktions-Cluster erstellt, sodass ich schnell die wichtigsten Dinge geöffnet habe. Zusätzlich nutze ich das oben beschriebene Beispiel für meinen Blog und andere Tools auf meinen privaten Kubernetes-Clustern. Dadurch kann ich mein Blog und andere Projekte schnell aktualisieren. Für Migrationen erstelle ich ebenfalls eine Tmuxifier-Konfiguration. In den Tagen vor der Migration bin ich häufig auf den entsprechenden Servern eingeloggt, um alles vorzubereiten – oft auch mit Migrationstests, die den eigentlichen Migrationstag simulieren. Die Tmux-Fenster und Sessions kann ich nach der Vorbereitung schließen und dann am Tag der Migration einfach die Session in Tmux neu erstellen lassen. --- ## neovim - URL: https://blog.kuepper.nrw/posts/2024-12-17-neovim/ - Datum: 2024-12-17 - Autor: ruediger - Tags: MacOS, iTerm, neovim - Kategorien: MacOS git clone https://github.com/jimeh/tmuxifier.git ~/.tmuxifier ![iTerm zsh](https://s3fs.kuepper.nrw/blog-images/posts/2024/12/neovim.webp) ## neovim anstatt vim Seit Langem höre und lese ich immer wieder von Neovim und wie begeistert die Leute davon sind. Neovim ist eigentlich wie früher Emacs – es ist ein eigenes Betriebssystem. ;) In den letzten Monaten fiel dann wieder vermehrt der Name Neovim bei Kollegen und ITlern, mit denen man zusammenarbeitet. Die Konfigurationen, die ich so im Netz und von anderen gesehen habe, waren oft sehr groß. Damit war irgendwie alles möglich, aber die Konfiguration zu durchsteigen und zu verstehen, das ist oft fast unmöglich. Durch Zufall bin ich im Netz dann über einen Post und kurz danach über ein Video von jemandem gestolpert, in dem er Neovim Schritt für Schritt einrichtet. Dabei hält er die Konfiguration, Tools und Co. sehr klein bzw. setzt nur wenige ein. Nur das Wichtigste wird verwendet und hauptsächlich so beschrieben, dass man erst einmal versteht, wie man Neovim und 1–2 Plugins einrichtet, um dann viele weitere Plugins installieren, verwalten und konfigurieren zu können. ## neovim installieren Installation von neovim mit homebrew brew install nvim ## Konfiguration Alle Dateien meiner Config sind in einem [Github Repo](https://github.com/ruedigerp/neovim-config) veröffentlicht. Wer direkt mit Neovim loslegen möchte, kann es einfach klonen und Neovim starten: git clone https://github.com/ruedigerp/neovim-config ~/.config/nvim Wer sich alle Plugins genauer anschauen, konfigurieren und verstehen möchte, kann einfach ab hier Schritt für Schritt durchgehen und nach jedem Schritt/Plugin testen. Die Konfiguration von Neovim liegt im Home-Verzeichnis in `.config/nvim`. Als Erstes habe ich eine Datei `init.lua` erstellt und Lazy konfiguriert. Normalerweise sind in der Datei noch andere Sachen, wie z. B. die Leader-Taste, die auf Space gesetzt ist. Das konfiguriere ich später aber in einer anderen Datei. Hier wird jetzt erst einmal nur Lazy konfiguriert, und anschließend legen wir noch im Verzeichnis lua weitere Dateien und Ordner an, in denen dann die anderen Sachen konfiguriert werden. local lazypath = vim. fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim. loop.fs_stat(lazypath) then vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath, }) end vim.opt.rtp:prepend(lazypath) local opt = {} require("vim-options") require("lazy").setup("plugins") Als nächstes wird im Verzeichnis `.config/vim/lua/` die Datei `vim-options.lua` angelegt: # Tab, Space und Co vim.cmd("set expandtab") vim.cmd("set tabstop=2") vim.cmd("set softtabstop=2") vim.cmd("set shiftwidth=2") vim.g.mapleader = " " # Wechseln links, rechts, hoch und runter mit ctl + hjkl # Hier kommt nachher noch ein Plugin zum Einsatz vim.keymap.set( 'n', '', ':wincmd k') vim.keymap.set( 'n', '', ':wincmd j') vim.keymap.set( 'n', '', ':wincmd h') vim.keymap.set( 'n', '', ':wincd l') # Search und Zeilennummern vim.keymap.set( 'n', 'h', ':nohlsearch') vim.wo.number = true ## Plugins in neovim ### nvim-tmux-navigator Wie im Kommentar oben erwähnt, benutze ich ein paar Plugins – darunter auch eins, um aus Neovim mit den oben konfigurierten Keymaps nicht nur innerhalb von Neovim wechseln zu können, sondern auch in andere Fenster innerhalb einer Tmux-Session. Aktuell könnte man zwar schon mit Tmux mehrere Fenster erstellen und mit der gleichen Konfiguration zwischen den Tmux-Fenstern hin- und herspringen. Ist jedoch in einem Fenster Neovim geöffnet, kommt man nicht mehr zurück in andere Tmux-Fenster. Also fangen wir mal an, die Plugins zu konfigurieren, und nehmen uns direkt dieses Plugin vor. Im `lua`-Ordner erstellen wir einen Ordner `plugins` und darin die Datei `nvim-tmux-navigator.lua`: [christoomey/vim-tmux-navigator](https://github.com/christoomey/vim-tmux-navigator) return { "christoomey/vim-tmux-navigator", vim.keymap.set('n', 'C-h', ':TmuxNavigateLeft'), vim.keymap.set('n', 'C-j', ':TmuxNavigateDown'), vim.keymap.set('n', 'C-k', ':TmuxNavigateUp'), vim.keymap.set('n', 'C-l', ':TmuxNavigateRight'), } Öffnet man jetzt Neovim (`nvim`), sollte als Erstes ein Fenster von `Lazy` zu sehen sein, und alle konfigurierten Plugins werden installiert. Es sollte also jetzt zu sehen sein, dass ein Repo geklont wird und `vim-tmux-navigator` installiert ist. Ist Neovim nun in einer Tmux-Session mit mehreren Fenstern geöffnet, kann man mit Ctrl + h, j, k, l durch alle Fenster navigieren – egal, ob es ein Neovim- oder ein Tmux-Fenster ist. ### Weitere Puglins konfigurieren Bei allen Plugins kann nach dem Anlegen der Datei und dem Kopieren der Konfiguration Neovim gespeichert, geschlossen und neu geöffnet werden. Dann wird das jeweilige Plugin installiert. Wer nicht alles Schritt für Schritt machen möchte, kann einfach alles anlegen und Neovim erst zum Schluss neu starten. Lazy kümmert sich dann darum, alle Plugins zu installieren. Falls jedoch etwas nicht funktioniert, wird es schwieriger, den Fehler zu beheben. Daher würde ich empfehlen, erst einmal alles einzeln zu machen und dabei zu testen. Dann erst das nächste Plugin einrichten. Daher richten wir jetzt als Erstes die Sidebar für Dateien ein. Man kann dann einfach in das Verzeichnis `cd ~/.config/nvim` wechseln und dort `nvim` starten. Wenn `neo-tree` installiert ist, kann man mit `Ctrl + n` die Dateiliste anzeigen und schnell zwischen den Dateien wechseln. ### Dateien in der Sidebar [Neo-tree](nvim-neo-tree/neo-tree.nvim) ist ein Neovim-Plugin, mit dem du das Dateisystem und andere baumartige Strukturen in jedem gewünschten Stil durchsuchen kannst, einschließlich Seitenleisten, schwebender Fenster, Netrw-Split-Style oder sogar alle gleichzeitig! return { "nvim-neo-tree/neo-tree.nvim", branch = "v3.x", dependencies = { "nvim-lua/plenary.nvim", "nvim-tree/nvim-web-devicons", "MunifTanjim/nui.nvim", }, config = function() require("neo-tree").setup({ filesystem = { filtered_items = { visible = true, -- Zeige standardmäßig alle ausgeblendeten Dateien hide_dotfiles = false, -- Zeige .dotfiles wie .gitignore hide_gitignored = false, -- Zeige Dateien, die in der .gitignore stehen hide_by_name = { -- Optional: spezifische Dateien oder Ordner ausblenden -- "node_modules", "dist" }, }, }, }) -- Keymap für Neo-Tree vim.keymap.set('n', '', ':Neotree filesystem reveal right', {} ) end } ### Colorschema mit catppuccin [catppuccin/nvim](https://github.com/catppuccin/nvim) Funktionen • Unterstützt sowohl Vim als auch Neovim (benötigt Neovim >= 0.8 oder Vim >= 9, kompiliert mit Lua >= 5.1) • Hochgradig konfigurierbar mit 4 verschiedenen Varianten und der Möglichkeit, eigene zu erstellen! • Benutzerkonfiguration kompilieren für schnellste Startzeit • Integrationen mit LSP, Treesitter und zahlreichen Plugins • Unterstützung für viele andere Anwendungen return { "catppuccin/nvim", -- lazy = false, name = "catppuccin", priority = 1000, config = function() -- require("catppuccin").setup() vim.cmd.colorscheme "catppuccin" end } ### Dashboard beim starten [nvimdev/dashboard-nvim](https://github.com/nvimdev/dashboard-nvim) Fancy and Blazing Fast start screen plugin of neovim return { 'nvimdev/dashboard-nvim', event = 'VimEnter', config = function() require('dashboard').setup { -- config } end, dependencies = { {'nvim-tree/nvim-web-devicons'}} } ### Neovim statusline A blazing fast and easy to configure Neovim statusline written in Lua. [nvim-lualine/lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) return { "nvim-lualine/lualine.nvim", config = function() require('lualine').setup({ options = { theme = 'dracula' } }) end } ### Fuzzy-Finder [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) ist ein hochgradig erweiterbarer Fuzzy-Finder für Listen. Es basiert auf den neuesten großartigen Funktionen des Neovim-Kerns. Telescope ist auf Modularität ausgerichtet und ermöglicht eine einfache Anpassung. return { { 'nvim-telescope/telescope.nvim', tag = '0.1.8', dependencies = { 'nvim-lua/plenary.nvim' }, config = function() local builtin = require("telescope.builtin") local telescope = require("telescope") -- Telescope setup telescope.setup({ defaults = { hidden = true, -- Aktiviert die Anzeige von versteckten Dateien file_ignore_patterns = { "%.git/" }, respect_gitignore = true, } }) -- Keybindings vim.keymap.set('n', '', function() builtin.find_files({ hidden = true }) -- Versteckte Dateien für `find_files` anzeigen end, {}) -- vim.keymap.set('n', 'fg', builtin.live_grep, {}) vim.keymap.set('n', 'fg', function() builtin.live_grep({ no_ignore = false }) -- Respektiere .gitignore end, {}) end }, { "nvim-telescope/telescope-ui-select.nvim", config = function() require("telescope").setup ({ extensions = { ["ui-select"] = { require("telescope.themes").get_dropdown { } } } }) require("telescope").load_extension("ui-select") end }, } ### Treesitter Das Ziel von [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) ist es, sowohl eine einfache und benutzerfreundliche Schnittstelle für Tree-sitter in Neovim bereitzustellen als auch grundlegende Funktionen wie etwa Syntaxhervorhebung darauf basierend anzubieten. return { "nvim-treesitter/nvim-treesitter", build = ":TSUpdate", config = function() local config = require ("nvim-treesitter.configs") config. setup ({ ensure_installed = {"lua", "javascript", "go", "dockerfile", "helm", "ssh_config", "terraform", "tmux", "toml", "yaml"}, highlight = { enable = true }, indent = { enable = true }, }) end } ### LSP und Vim-Tests Ich habe noch zwei weitere Plugins. Und zwar `vim-test`, um Code-Tests auszuführen, die man direkt in Neovim starten kann. Dabei wird jedoch ein Buffer in Neovim geöffnet, und wenn man größere Ausgaben hat, sieht man diese zwar, aber man kann sie nicht in den Hintergrund schieben, den Code anschauen und dann wieder zur Ausgabe zurückwechseln. Die Ausgabe des Tests ist dann weg. `vim-test` sorgt dafür, dass ein zweites Fenster in der Tmux-Session geöffnet wird, welches offen bleibt und zwischen dem Code- und dem Test-Fenster gewechselt werden kann. So können alle Fehler nach und nach behoben werden. Das zweite Plugin ist für LSP: `nvim-lspconfig`. `nvim-lspconfig` ist ein „datenbasiertes“ Repository, das grundlegende, standardmäßige Neovim-LSP-Client-Konfigurationen für verschiedene `LSP-Server` bereitstellt. So werden Fehler im Code direkt angezeigt, wie man es von Visual Studio und Co. kennt. Auch das Springen zu Definitionen, Anzeigen von Hilfen usw. ist damit möglich. Die beiden Plugins werde ich später noch einmal ausführlicher beschreiben, da ich sie aktuell selbst noch teste und teilweise noch einrichte und konfiguriere. Für LSP müssen lokal passende LSP-Server für die entsprechenden Programmiersprachen installiert sein. Das wird dann besser ein eigener Post. --- ## Meine tmux konfiguration - URL: https://blog.kuepper.nrw/posts/2024-12-15-tmux-configuration/ - Datum: 2024-12-15 - Autor: ruediger - Tags: MacOS, iTerm, tmux - Kategorien: MacOS ![iTerm zsh](https://s3fs.kuepper.nrw/blog-images/posts/2024/12/tmux.webp) ## tmux installieren Installation von tmux mit homebrew brew install tmux ## Konfiguration Damit Änderungen in der `tmux.conf` für alle Sessions, Windows und Panes neugeladen werden können wird als ersten `bind r ` konfiguriert: unbind r bind r source-file ~/.tmux.conf Der Standard Prefix ist default auf `control + b` und ich finde ihn auf `control +s` angenehmer. Was auch an meiner Zeit mit `screen` liegt. set -g prefix C-s Aktivieren der Mouse, damit auch die Panes mit der Maus angepasst werden können: set -g mouse on Um mit den Tasten `control + h,j,k,l` durch die Panes springen zu können: setw -g mode-keys vi bind-key h select-pane -L bind-key j select-pane -D bind-key k select-pane -U bind-key l select-pane -R Für die Plugins in tmux benutze ich den tmux plugin manager - tpm. set -g @plugin 'tmux-plugins/tpm' Für die Statusleiste hatte ich Dracula getestet, dann aber `tmux2k` gefunden. Das Plugin wird einfach mit folgender Zeile aktiviert. set -g @plugin '2kabhishek/tmux2k' Die Konfiguration für tmux2k: set -g @tmux2k-theme 'onedark icons' set -g @tmux2k-icons-only true set -g @tmux2k-left-plugins "git cpu-usage ram-usage" set -g @tmux2k-right-plugins "battery cpu ram git time" set -g @tmux2k-network-name "en0" set -g @tmux2k-show-powerline true set -g @tmux2k-show-fahrenheit false set -g @tmux2k-military-time true set -g @tmux2k-border-contrast true # available colors: white, gray, dark_gray, light_purple, dark_purple, cyan, green, orange, red, pink, yellow # set -g @tmux2k-[plugin-name]-colors "[background] [foreground]" set -g @tmux2k-cpu-usage-colors "blue dark_gray" # it can accept `session`, `rocket`, `window`, or any character. set -g @tmux2k-show-left-icon "" # update powerline symbols set -g @tmux2k-show-left-sep "" set -g @tmux2k-show-right-sep "" # change refresh rate set -g @tmux2k-refresh-rate 5 Die Statusbar habe ich für Tmux gerne oben, da meine ZSH Statusbar immer unten ist wirkt das aufgräumter. Ausserdem sehe ich so auch oben immer die wichtigen Infos, wie welches Window aktiv ist, Git Branch usw. set -g status-position top Jetzt noch tpm aktivieren und schon kann man einen Reload der Konfiguration machen: run '~/.tmux/plugins/tpm/tpm' Wenn noch keine tmux Session vorhanden, einfach eine neue aufmachen. Ist schon eine vorhanden kann man jetzt mit `control + r` die Konfiguration laden und anschliessend noch tpm die Plugins installieren lassen mit `control + I`. Die Plugins werden installiert und die Statusleiste sollte jetzt aktiv und zu sehen sein. Die komplette Konfiguration: unbind r bind r source-file ~/.tmux.conf set -g prefix C-s set -g mouse on setw -g mode-keys vi bind-key h select-pane -L bind-key j select-pane -D bind-key k select-pane -U bind-key l select-pane -R set -g @plugin 'tmux-plugins/tpm' set -g @plugin '2kabhishek/tmux2k' set -g @tmux2k-theme 'onedark icons' set -g @tmux2k-icons-only true set -g @tmux2k-left-plugins "git cpu-usage ram-usage" set -g @tmux2k-right-plugins "battery cpu ram git time" set -g @tmux2k-network-name "en0" set -g @tmux2k-show-powerline true set -g @tmux2k-show-fahrenheit false set -g @tmux2k-military-time true set -g @tmux2k-border-contrast true # available colors: white, gray, dark_gray, light_purple, dark_purple, cyan, green, orange, red, pink, yellow # set -g @tmux2k-[plugin-name]-colors "[background] [foreground]" set -g @tmux2k-cpu-usage-colors "blue dark_gray" # it can accept `session`, `rocket`, `window`, or any character. set -g @tmux2k-show-left-icon "" # update powerline symbols set -g @tmux2k-show-left-sep "" set -g @tmux2k-show-right-sep "" # change refresh rate set -g @tmux2k-refresh-rate 5 set -g status-position top run '~/.tmux/plugins/tpm/tpm' --- ## Nice Terminal und zsh Konfiguration - URL: https://blog.kuepper.nrw/posts/2024-12-15-nice-zsh-config/ - Datum: 2024-12-15 - Autor: ruediger - Tags: MacOS, iTerm, zsh, nerdfonts - Kategorien: MacOS ![iTerm zsh](https://s3fs.kuepper.nrw/blog-images/posts/2024/12/zsh.png) ## iTerm2 installieren und konfigurieren Als erstes ersetzen wir den MacOS Terminal und installieren iterm2 brew install iterm2 Für iTerm2 benutze ich das Gruvebox Colorscheme: curl -so gruvebox.itermcolors https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/GruvboxDark.itermcolors Das Color-Scheme importiert man in den Settings von iTerm2 unter Profiles -> Colors -> Color Presets (unten rechts). ![Import colorscheme](https://s3fs.kuepper.nrw/blog-images/posts/2024/12/iterm-colors.png) Anschliessend kann man dort dann "gruvebox" auswählen. Als nächstes brauchen wir noch Fonts. Die bekommt man auf Nerdfonts.com. Ich benutze den Font "Nerd Hack Fonts": curl -sLo Hack.zip https://github.com/ryanoasis/nerd-fonts/releases/download/v3.3.0/Hack.zip Die Datei entpacken und den Font einfach mit Doppelklick öffnen. Unter MacOS ist damit die Font Installation schon erledigt. Den Font kann an dann in den Settings von iTerm2 direkt auswählen und aktivieren. Profiles -> Text -> Font. ## ZSH konfigurieren Wer noch kein ZSH installiert hat kann ZSH mit `brew install zsh` installieren. oh-my-zsh managed die ZSH Konfiguration und bringt eine Menge an Plugins, Themes und noch mehr mit um ZSH so anpassen zu können wir man es benötigt. Egal Aussehen oder Funktionen. sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" Das powerlevel10k Plugin installieren: git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k In der .zshrc kann man das Theme auf anschliessend auf Powerlevel10k setzen: ZSH_THEME="powerlevel10k/powerlevel10k" Jetzt noch etwas die zsh Shell pimpen. Dazu werden jetzt noch zsh-syntax-highlighting und zsh-autosuggestions installiert. brew install zsh-autosuggestions brew install zsh-syntax-highlighting Beide aktiveren, in dem man einfach folgende Zeilen in der .zshrc am Ende hinzufügt: source $(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh In der Datei `.zshrc` ist ziemlich am Anfang eine Variable `plugins`, bei dieser fügt man jetzt dir beiden Plugins hinzu. Bei mir sieht die Zeile so aus: plugins=(command-not-found web-search git git-prompt systemadmin taskwarrior svn perl macos screen terminitor lol emoji-clock themes history zsh-syntax-highlighting zsh-autosuggestions) Damit die File- und Ordnerliste so aussehen wir oben im Screenshot benutze ich colorls, welches mit Ruby gem installiert wird. sudo gem install colorls Ggf. muss vorher noch ein `gem install public_suffix -v 5.1.1` ausgeführt werden. Das wird aber auch in dem Fall beim install von colorls angezeigt. Damit ist dann auch `colorls` als Befehl nutzbar. Wenn man jetzt aber nicht immer colorls eingeben möchte, kann man sich einfach in der `.zshrc` einfach einen Alias dafür anlegen: alias ls="colorls" Jetzt kann man entweder einen neuen Termin öffnen oder man gibt einfach `source ~/.zshrc` ein um die Konfiguration einlesen zu lassen. ## Noch mehr mit Powerlevel10k ![zsh powerlevel10k](https://s3fs.kuepper.nrw/blog-images/posts/2024/12/p10k.png) Hier sieht man meinen kompletten ZSH Prompt. Hier werden neben der Uhrzeit, Batterie, interne und externe IP, Akku jede Menge Sachen angezeigt. Wenn ich in einem Verzeichnis mit z.B. mit einer Kubeconfig bin und/oder einem Git Repository, wird man der Kuberentes Context, Git Branch usw. angezeigt. Die Konfig ist etwas größer, daher habe ich sie in einen [GitHub Gist](https://gist.github.com/ruedigerp/670abd2bfecba6c4cd481b7cf4352570) hinterlegt. Die Powerlevel10k Konfiguration habe ich in der Datei `~/.p10k.zsh` gespeichert. Und in der `.zshrc` wird dann anschliessend noch eingefügt: # Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc. # Initialization code that may require console input (password prompts, [y/n] # confirmations, etc.) must go above this block; everything else may go below. if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" fi source $(brew --prefix)/share/powerlevel10k/powerlevel9k.zsh-theme # To customize prompt, run `p10k configure` or edit ~/.p10k.zsh. [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh # typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet typeset -g POWERLEVEL9K_INSTANT_PROMPT=off Jetzt noch mal einen neuen Terminal öffnen oder wieder ein `source ~/.zshrc`. --- ## Statische Webseiten in Kubernetes - URL: https://blog.kuepper.nrw/posts/2024-09-18-statische-webseiten-in-kubernetes/ - Datum: 2024-09-18 - Autor: ruediger - Tags: Homepage, CMS, Blog, Kubernetes - Kategorien: Internet ![Blog and CMS](https://s3fs.kuepper.nrw/blog-images/posts/2024/02/cms.webp) Ich habe mehrere Seiten und benutze dafür Hugo, InkProject und ein eigenes in Go geschriebenes CMS. Der Content wird in allen drei mit Markdown Files erstellt. Mit den drei Systemen wird dann die jeweilige Seite statisch generiert. ## Github/Gitlab Pipelines? Benutze ich Github oder Gitlab Pipelines für das generieren? Nein. Der Content, die CMS Config, Themes usw. liegen alle in Github. Aber Github Actions oder Pipelines benutze ich bei den Seiten nicht mehr. Das habe ich über Jahre so gemacht, aber der Aufwand ist mir einfach zu groß. Und das veröffentlichen, also das generieren der Seiten, erstellen der Images und Deployment der Images in Kubernetes dauert einfach zu lange. ### Aufwand und Zeit. Damit meine ich das Hugo, InkProject und mein eigens CMS bei aktualisieren neu zu kompilieren, Docker Images zu bauen und so bei Updates der Systeme mit der neuen Version die Seiten erstellen zu können. Auch das generieren der Seiten und das anschliessende erstellen der Images dauert einfach sehr lange. Da jeder Step ein Docker Image hoch fährt, seine Aufgabe erledigt und anschliessende die fertigen Daten weiter verarbeitet. Images, da ich nur noch Multi-Arch Images baue, in die Registry pushen und mit dem fertigen Image den Helm Chart mit der neuen Image Version erstellt und deployed. Das dauerte ja nach Seite 3-5 Minuten. Was bei kleinen Änderungen wie Typos einfach nervig war. ## Wie ich jetzt Seiten aktualisiere Jede Seite ist lokal auf dem Rechner und ich füge neue Markdown Files für neuen Content dazu. Ich kann bei allen drei Systemen einfach ` build` oder ` docker.yaml.tmp; mv docker.yaml.tmp docker.yaml VERSION=$(perl -pe 's/(version: )(v\d\.)(\d\.)(\d+)/$2.$3.($4)/ge' docker.yaml) docker buildx build --no-cache -t ${REGISTRY}/${USERNAME}/${IMAGE}:${VERSION}-arm64 . docker buildx build --no-cache --platform linux/arm64 -t ${REGISTRY}/${USERNAME}/${IMAGE}:${VERSION}-arm64-linux . docker buildx build --no-cache --platform linux/amd64 -t ${REGISTRY}/${USERNAME}/${IMAGE}:${VERSION}-amd64 . docker push ${REGISTRY}/${USERNAME}/${IMAGE}:${VERSION}-arm64 docker push ${REGISTRY}/${USERNAME}/${IMAGE}:${VERSION}-arm64-linux docker push ${REGISTRY}/${USERNAME}/${IMAGE}:${VERSION}-amd64 docker manifest create ${REGISTRY}/${USERNAME}/${IMAGE}:$VERSION \ --amend ${REGISTRY}/${USERNAME}/${IMAGE}:$VERSION-amd64 \ --amend ${REGISTRY}/${USERNAME}/${IMAGE}:$VERSION-arm64-linux \ --amend ${REGISTRY}/${USERNAME}/${IMAGE}:$VERSION-arm64 docker manifest push ${REGISTRY}/${USERNAME}/${IMAGE}:$VERSION docker manifest create ${REGISTRY}/${USERNAME}/${IMAGE}:latest \ --amend ${REGISTRY}/${USERNAME}/${IMAGE}:$VERSION-amd64 \ --amend ${REGISTRY}/${USERNAME}/${IMAGE}:$VERSION-arm64-linux \ --amend ${REGISTRY}/${USERNAME}/${IMAGE}:$VERSION-arm64 docker manifest push ${REGISTRY}/${USERNAME}/${IMAGE}:latest ``` ## Helm-Chart Der Helm-Chart kümmert sich um das Deployment in den bzw. die Kubernetes Cluster. Das Deployment, Service, Ingress, TLS-Cert, Traefik Middlewares und vielem mehr. Änderungen an den Workloads können so schnell gemacht werden und Rollback ist schnell möglich. Auch hier wird, wie beim Docker Image, die Version immer hoch gezählt. Der aktuelle Image-Tag wird aus dem Verzeichnis des CMS ausgelesen und als Value beim Installaieren bzw. beim Upgrade des Helm-Charts im Cluster übergeben. Damit ich beim Rollout des Helm-Charts nicht zusätzlich immer erst in das CMS Verzeichnis wechseln muss, um ein neues Image bei Änderungen zu generieren, kann ich einfach beim Aufrufe vom Script `package.sh` einfach ein `docker` mit anhängen. So bin ich flexible und kann auch nur Änderungen am Helm-Chart ausrollen, ohne immer wieder neue Docker Images zu erstellen. ``` #!/bin/bash HOMEPAGEURL="blog.example.net" HELMREPO="charts.mydomain.de" CHARTNAME="myblog" CHARTUSER="bob" CHARTPASSWORD="mysecret" if [ "$1" = "docker" ]; then cd ../../services/${HOMEPAGEURL}/ ./build.sh cd - fi perl -pe 's/(version: )(v\d\.)(\d\.)(\d+)/$1 . $2.$3.($4 + 1)/ge' helm/Chart.yaml > helm/Chart.yaml.tmp; mv helm/Chart.yaml.tmp helm/Chart.yaml TAG=$(grep -a -m1 'version: ' ../../services/${HOMEPAGEURL}/docker.yaml | cut -d " " -f2) helm package helm helm cm-push -u ${CHARTUSER} -p ${CHARTPASSWORD} $(ls -tr -1 *.tgz | tail -n 1) ${HELMREPO} helm repo update helm upgrade ${CHARTNAME} ${HELMREPO}/${CHARTNAME} --namespace ${CHARTNAME} --set tag=${TAG} ``` --- ## Eine simple Homepage würde schon mal reichen - URL: https://blog.kuepper.nrw/posts/2024-01-16-eine-simple-homepage-wuerde-schon-mal-reichen/ - Datum: 2024-01-16 - Autor: ruediger - Tags: Firma, Business, Homepage, SEO - Kategorien: Internet ![Young Seo & Marketing Casper](https://s3fs.kuepper.nrw/blog-images/posts/2024/01/young-seo-casper.webp) Wenn dir jemand sagt, wie wichtig eine eigene Homepage ist, hat er eigentlich ganz recht. Aber was brauchst du wirklich, um im Internet präsent zu sein? Nicht viel. Es muss nicht unbedingt HTML und CSS im Texteditor sein, es sei denn, das ist dein Beruf oder Hobby und du kennst dich damit bestens aus. Selbst dann wirst du wahrscheinlich zu einem CMS wie WordPress, Drupal oder Typo3 greifen. Oder du entscheidest dich für einen der vielen Anbieter, die dir einen kompletten Website-Baukasten zur Verfügung stellen. Egal für was du dich entscheidest, prüfe mal die Exportmöglichkeiten in den Einstellungen oder Plugins. Denn es ist unwahrscheinlich, dass du beim ersten Mal das perfekte CMS oder den perfekten Baukasten erwischst. Manchmal passt es, weil es einfach genug für deine Bedürfnisse ist. Aber oft kommt der Punkt, an dem du wächst und mehr brauchst, wie zum Beispiel einen Shop oder Medienbereitstellung. Dann willst du vielleicht doch zu einem anderen System wechseln. Such dir aus, was zu dir passt. Lass dich nicht beeinflussen oder abzocken von irgendeinem jungen Business-Typen, mit schicken Instagram-Profil und auf Threads von ihrem sechsstelligen Marketing-Business prahlen. Natürlich kannst du dir Infos und Tipps holen, aber wenn du nur eine einfache Homepage willst, dann mach das auf deine Weise. Du kannst jemanden für das Design dazunehmen, aber letztendlich ist es deine Entscheidung. Wichtig ist: Du willst ein Design, und womit du es umsetzt, ist egal. Ein Webdesigner, der nur für WordPress arbeitet, ist kein richtiger Webdesigner. Ein guter Designer liefert dir etwas, das du in das System deiner Wahl einfügen kannst. Wenn du Drupal willst und jemand versucht, dir WordPress aufzuschwatzen, sag nein und such dir lieber jemand anderen – und umgekehrt. SEO ist wichtig, aber man muss es nicht übertreiben. Es frisst Zeit und kostet Geld. Du hast dich für ein System entschieden und hast das Webdesign gestalten lassen. Der Webdesigner, der ja ein Profi sein sollte, kennt sich mit SEO aus und hat die grundlegenden Sachen wie Absätze, Titel, Keywords und Überschriften im Design integriert. Wenn der SEO-Typ dir was anderes erzählt, stimmt entweder mit dem Webdesigner oder dem SEO-Typen was nicht. Spar dir zuerst das SEO und teures Webdesign. Meistens bringt es dir anfangs nichts. Du willst ein Business aufbauen und brauchst erstmal Kunden. Konzentriere dich darauf, ob du es alleine schaffst und ob die Kunden überhaupt wollen, was du anbietest. Du hast keine Zeit, dich mit Webdesign, CMS, Templates, Plugins und Hosting zu beschäftigen. Du willst dein Ding machen und das für deine Kunden. Und dann sind da noch Bankgeschäfte, Rechnungen und das Finanzamt. Die wichtigsten Dinge brauchst du: Ja, auch deine Homepage. Aber da machst du erstmal das, was dir passt und womit du klar kommst. Sichere dir deinen Namen als Domain und leg los. Denn was du wirklich brauchst, ist erstmal eine Homepage, die zeigt, was du machst und wer du bist. Wichtig sind auch die Kontaktdaten. Der Rest kommt später, wenn du Zeit und Geld dafür hast. Dann kannst du Leute suchen, die den Job für dich erledigen. Weil du dann die Ruhe dafür hast und dich nicht von Möchtegern-Experten abzocken lässt, die am Anfang deine Zeitknappheit ausnutzen. Eine Domain, eine kleine Webseite mit kurzen Infos und Kontaktdaten reichen aus. Kunden suchen oft lokal und kennen vielleicht sogar deinen Namen, dann landen sie eh auf deiner Seite, wo sie alle aktuellen Informationen finden. Der einzige Nachteil ohne eigene Homepage ist, dass du dich vielleicht in irgendwelchen Portalen selbst eintragen musst oder von anderen eingetragen wirst. Andere Seiten greifen diese Daten ab und zeigen sie in ihrem Verzeichnis an. Kunden landen dort über Google. Ändert sich deine Telefonnummer, E-Mail-Adresse oder Anschrift, wird sie dort oft nie geändert. Auf Deiner Homepage schon. Da gucken die Leute drauf, also nutze sie genau dafür. Also, erstelle deine eigene Homepage. Das gewählte System ist zunächst zweitrangig. Plane nur dein weiteres Wachstum mit ein und vermeide unnötigen Schnickschnack. Starte mit deiner Homepage wie mit deinem Business: Klein! Und wachse langsam mit deinem Business und deiner Webseite. --- ## AdBlock mit Pi-Hole ist Selbstverteidigung - URL: https://blog.kuepper.nrw/posts/2024-01-14-AdBlock-mit-Pi-Hole-ist-selbstverteidigung/ - Datum: 2024-01-14 - Autor: ruediger - Tags: RaspberryPi, Pi-Hole - Kategorien: Internet ![Pi-Hole](https://s3fs.kuepper.nrw/blog-images/posts/2024/01/adblocker-pihole-protection.webp) Pi-hole ist eine Netzwerk-basierte Anwendung zur Werbe- und Tracking-Blockierung. Es funktioniert, indem es als DNS-Server in Ihrem Netzwerk agiert und unerwünschte Inhalte, insbesondere Werbung und Tracker, filtert, bevor sie auf Ihre Geräte geladen werden. Hier sind die grundlegenden Schritte, um Pi-hole einzusetzen: 1. **Hardware-Voraussetzungen**: Pi-hole kann auf Hardware wie einem Raspberry Pi, einem anderen Einplatinencomputer oder sogar auf einem alten Computer installiert werden. 2. **Betriebssystem**: Du musst ein kompatibles Betriebssystem auf Deiner Hardware installieren, oft ist dies eine Linux-Distribution. 3. **Installation von Pi-hole**: Die Installation erfolgt normalerweise über eine einfache Befehlszeile. Pi-hole bietet auf ihrer Website eine offizielle Installationsanleitung. 4. **Konfiguration Ihres Netzwerks**: Nach der Installation müssen Sie Ihren Router so konfigurieren, dass er Pi-hole als primären DNS-Server verwendet. Dadurch wird der gesamte Datenverkehr Ihres Netzwerks durch Pi-hole geleitet. 5. **Verwaltung und Wartung**: Pi-hole bietet ein Web-Dashboard, über das Sie die Einstellungen verwalten, Statistiken einsehen und die Blockierlisten anpassen können. 6. **Regelmäßige Updates**: Es ist wichtig, Pi-hole regelmäßig zu aktualisieren, um sicherzustellen, dass es effektiv bleibt und Sicherheitsrisiken minimiert werden. Pi-hole ist besonders beliebt für seine Effizienz bei der Reduzierung von Werbung auf allen Geräten im Netzwerk, seine relativ einfache Einrichtung und geringe Hardwareanforderungen. Es bietet auch einen erhöhten Datenschutz, da es hilft, Online-Tracking zu reduzieren. # Pi-Hole installieren Die Installation von Pi-hole auf einem Raspberry Pi ist ein relativ einfacher Prozess. Hier sind die grundlegenden Schritte: 1. **Raspberry Pi vorbereiten**: - Besorgen Dir einen Raspberry Pi und stellen sicher, dass Du eine SD-Karte, ein Netzteil und eine Netzwerkverbindung (entweder über Ethernet oder WLAN) hast. - Installiere ein Betriebssystem auf Deinem Raspberry Pi, üblicherweise wird Raspbian verwendet, das auf Debian basiert. 2. **Betriebssystem einrichten**: - Schreibe das Betriebssystem-Image auf die SD-Karte. Dazu kannst Du Software wie BalenaEtcher verwenden. - Lege die SD-Karte in den Raspberry Pi ein und starte ihn. - Führe die grundlegende Konfiguration durch, wie das Einrichten einer Netzwerkverbindung und das Ändern des Standardpassworts. 3. **Pi-hole installieren**: - Öffne einen Terminal auf dem Raspberry Pi. - Führe den folgenden Befehl aus: `curl -sSL https://install.pi-hole.net | bash` - Dieses Kommando lädt das Installationsskript von Pi-hole herunter und führt es aus. 4. **Pi-hole Konfiguration**: - Während der Installation wirst Du durch verschiedene Konfigurationsoptionen geführt, einschließlich der Auswahl eines DNS-Servers, der Einstellung von IPv4 oder IPv6 und der Festlegung von Filterlisten. - Nach Abschluss der Konfiguration zeigt das Installationsprogramm die IP-Adresse des Pi-hole-Servers sowie das Passwort für das Web-Administrationsinterface an. 5. **Router-Konfiguration**: - Um Pi-hole für Dein gesamtes Netzwerk zu verwenden, muss der Raspberry Pi als primären DNS-Server in Deinem Router eingerichtet werden. - Melde Dich sich bei Deinem Router an und suche die DNS-Einstellungen. Hier trägst Du die IP-Adresse des Raspberry Pi ein. 6. **Überprüfen und verwalten**: - Nachdem Du Pi-hole als DNS-Server eingestellt hast, kannst Du auf das Pi-hole-Administrations-Dashboard zugreifen, indem Du die IP-Adresse des Raspberry Pi in einem Webbrowser eingibst und Dich anmeldest. - Überprüfe, ob die Werbeblockierung funktioniert und passe bei Bedarf die Einstellungen im Dashboard an. Vergewissere dich, dass regelmäßige Updates für sowohl das Betriebssystem als auch Pi-hole durchgeführt werden, um Sicherheit und Funktionalität zu gewährleisten. # Sperrelisten hizufügen und verwalten Um weitere Listen in Pi-hole hinzuzufügen, kkannst Du das Web-Administrationsinterface nutzen. Hier sind die Schritte, um zusätzliche Blocklisten hinzuzufügen: 1. **Pi-hole Webinterface öffnen**: - Gebe die IP-Adresse Deines Raspberry Pi in einem Webbrowser ein, um auf das Pi-hole Dashboard zuzugreifen. - Melde Dich an, falls erforderlich. 2. **Zum Blocklisten-Management navigieren**: - Im Dashboard findest Du einen Abschnitt oder eine Registerkarte namens "Group Management" oder "Gruppenverwaltung". - Klicke darauf und wähle dann „Adlists“ oder „Blocklisten“. 3. **Neue Listen hinzufügen**: - Hier kannst Du die URLs der zusätzlichen Blocklisten eingeben. Blocklisten sind in der Regel als URL zu einer Liste im Internet verfügbar, die von verschiedenen Quellen gepflegt werden. - Gebe die URL der gewünschten Liste ein und klicke auf „Add“ oder „Hinzufügen“. 4. **Änderungen anwenden**: - Nachdem Du neue Listen hinzugefügt hast, ist es wichtig, die Gravity-Datenbank zu aktualisieren, damit die Änderungen wirksam werden. - Dies können Du tun, indem Du im Hauptdashboard auf „Tools“ und dann auf „Update Gravity“ klickst. 5. **Überprüfen und Testen**: - Überprüfe nach dem Aktualisieren der Gravity-Datenbank, ob die neuen Listen korrekt hinzugefügt wurden und funktionieren. - Du kkannst dies testen, indem Du auf Websites zugreifst, die normalerweise Werbung anzeigen, und überprüfen, ob diese Werbung nun blockiert wird. 6. **Regelmäßige Wartung**: - Beachte, dass einige Listen möglicherweise regelmäßig aktualisiert werden müssen, um effektiv zu bleiben. - Pi-hole führt standardmäßig regelmäßige Updates durch, aber Du kannst dies auch manuell im Webinterface unter „Tools“ > „Update Gravity“ durchführen. Denke, dass das Hinzufügen vieler Listen die Leistung Deines Pi-hole beeinträchtigen kann, besonders wenn Dein Raspberry Pi über begrenzte Ressourcen verfügt. Es ist auch möglich, dass zu viele Listen zu falsch-positiven Ergebnissen führen, bei denen legitime Websites fälschlicherweise blockiert werden. Es ist daher wichtig, ein Gleichgewicht zu finden und Listen auszuwählen, die Deinen spezifischen Anforderungen und Präferenzen entsprechen. # Listen mit denen gute Erfahrungen gemacht wurden Ende 2022 habe ich von [Frank Plaschke](https://about.me/Frank.Plaschke) eine Seite geschickt bekommen. Auf [Firebog.net](https://firebog.net) sind eine Menge Sperrlisten für Pi-Hole gelistet, die Pi-Hole bei ihm sehr effizient gemacht haben. Ich selbst habe sie bei mir auch eingetragen und Pi-Hole wurde sehr viel besser. --- ## Sudo ohne Passwort in Gaming Tutorials: Warum das ein No-Go ist - URL: https://blog.kuepper.nrw/posts/2024-01-12-sudo-ohne-passwort-in-gaming-tutorials-warum-das-ein-no-go-ist/ - Datum: 2024-01-12 - Autor: ruediger - Tags: Linux, Security, Gaming, sudo - Kategorien: Linux ![Static Weblog](https://s3fs.kuepper.nrw/blog-images/posts/2024/01/gaming-howto-sudo.webp) Hey Gamer-Community! Habt ihr schon mal Tutorials für eure Linux-Gaming-Setups gecheckt und gesehen, wie oft da einfach sudo Befehle ohne Passwortabfrage genutzt werden? Es sieht super easy aus, aber was, wenn ich euch sage, dass das eigentlich mega unsicher ist? Lasst uns darüber reden, warum das ein echtes Sicherheits-No-Go ist. # Sudo-Easy-Mode: Bequem aber gefährlich Sudo in Linux ist wie ein Master-Key für euren Computer. Wenn ihr sudo vor einen Befehl setzt, sagt ihr im Grunde: "Hey, führe das als Superuser aus!" Aber wenn ihr das ohne Passwort macht (die 'NOPASSWD'-Option), dann ist das, als würdet ihr eure Haustür offen lassen. Jeder, der vorbeikommt, könnte reinspazieren! # Das Sicherheitsproblem: Easy Access für Bad Guys Stellt euch vor, ihr ladet euch was für euer Game runter, und ein Hacker hat sich darin versteckt. Mit sudo ohne Passwort könnte der alles auf eurem System machen – wie in einem Game mit Cheat-Codes, nur dass ihr die Verlierer seid. # Real-Talk: Beispiele aus dem echten Leben Es gab schon Fälle, wo solche Sicherheitslücken zu echten Desastern geführt haben – von geklauten Infos bis hin zu komplett übernommenen Systemen. Nicht cool! # Wie man’s besser macht: Sicherheit first! Also, Leute, lasst uns smart sein. Benutzt sudo mit Passwort. Ja, es dauert ein paar Sekunden länger, aber euer System bleibt sicher. Ändert auch regelmäßig eure Passwörter und bleibt wachsam, wenn ihr Tutorials folgt. # Fazit Gaming auf Linux ist super, aber Sicherheit ist wie bei jedem guten Game: Man muss die Regeln kennen und sich dran halten. sudo ohne Passwort? Lasst das lieber sein. Sicher sein im Internet ist wie ein unsichtbarer Rüstungsboost – ihr seht es nicht direkt, aber es rettet euch vor den fiesen Überraschungen. Bleibt sicher, zockt cool! 🎮🔐✨ --- ## Warp Terminal - URL: https://blog.kuepper.nrw/posts/2024-01-04-Warp-Editor/ - Datum: 2024-01-04 - Autor: ruediger - Tags: Internet, Develop, Terminal - Kategorien: Internet, Develop, Terminal [Englisch Version](/posts/2024-01-04-warp-editor-en.html) ![Warp Editor](https://s3fs.kuepper.nrw/blog-images/posts/2024/01/warp-editor.webp) Vor den Feiertagen, bin ich auf einen schicken Terminal-Ersatz gestossen. Wave ist sehr schick und bietet viele Funktionen. Leider hatte er einen Nachteil. ZSH kann er nicht, daher waren wir in der Firma alle etwas entäuscht. Aber heute kam mein Kollege Jan mit einer neuen Alternative um die Ecke, von dem er begeistert im Develop Channel in Slack berichtete. Inklusive eines Links, also mussten wir natürlich direkt nachsehen. Yep, das sah auch sehr schick aus. Also direkt mal getestet. Es geht um den [Warp Terminal](https://www.warp.dev), der zsh unterstützt und zudem über gute Features verfügt. * Schreiben mit einem IDE-Stil-Editor * Einfaches Navigieren durch Ausgaben * Befehle speichern, um sie später wiederzuverwenden * Warp-KI um Erklärungen oder Debugging bitten * Tastenkombinationen und Startkonfigurationen anpassen * Aus vorgeladenen Themen auswählen oder eigene erstellen Installation per Binary Download oder per Homebrew: brew install --cask warp Danach wird nach dem Öffnen ein Login bzw. die Registrierung angezeigt. Wer möchte kann die Onboarding Survey machen. Neben Einstellungen zum Aussehen hat er eine sehr große Command Palette, AI Support und Session Management. Sessions können gespeichert werden und wieder hergestellt werden. Auch die Settings können gespeichert werden. So kann man sich auch für verschiedene Anwendungsfälle das Setup extra zusammen stellen. In der Dev-Umgebung ist alles grün und wenn man in Prod arbeitet, ist alles rot, sodass man weiß, wann man vorsichtig sein sollte. Warp ist sehr mächtig und heute wurde erst einmal reingeguckt und erste Einstellungen gemacht. Es wird bestimmt ein Follow-Up-Post dazu kommen, dann auch mit ein paar Tipps und mehr Infos zu einzelnen Funktionen. --- ## Kubernetes Network Policies - URL: https://blog.kuepper.nrw/posts/2023-09-21-kubernetes-network-policies/ - Datum: 2023-09-21 - Autor: ruediger - Tags: Kubernetes, NetworkPolicy - Kategorien: Internet [Englisch Version](/posts/2023-09-21-kubernetes-network-policies-en.html) ![Github Actions](https://s3fs.kuepper.nrw/blog-images/posts/2023/09/kubernetes-netpol.webp) ## Einleitung In der Welt der Container-Orchestrierung spielt Kubernetes eine führende Rolle bei der Verwaltung und Automatisierung von containerisierten Anwendungen. Eine der Schlüsselkomponenten in Kubernetes ist die NetworkPolicy, ein wichtiges Werkzeug zur Kontrolle der Kommunikation zwischen Pods. In diesem Artikel werden wir uns mit den Grundlagen von NetworkPolicies befassen und wie sie zur Sicherung von Kubernetes-Clustern eingesetzt werden können. ## Grundlagen von NetworkPolicies NetworkPolicies sind spezifische Objekte in Kubernetes, die festlegen, wie Gruppen von Pods miteinander und mit anderen Netzwerkendpunkten kommunizieren dürfen. Sie verwenden Labels, um Pods zu identifizieren und Regeln zu definieren, die den Datenverkehr zwischen diesen Pods steuern. ## Arten von NetworkPolicies 1. Eingehende (Ingress) Regeln: * Diese Regeln steuern den eingehenden Datenverkehr zu Pods. * Sie können spezifizieren, welche Quellen (IP-Adressen oder Pods) erlaubt sind, auf einen Pod zuzugreifen. 2. Ausgehende (Egress) Regeln: * Diese Regeln steuern den ausgehenden Datenverkehr von Pods. * Sie können festlegen, zu welchen Zielen (IP-Adressen oder Pods) ein Pod kommunizieren darf. ## Erstellung einer NetworkPolicy Um eine NetworkPolicy zu erstellen, definiert man eine YAML-Datei mit den gewünschten Regeln und wendet sie auf den Kubernetes-Cluster an. Hier ist ein einfaches Beispiel für eine Ingress NetworkPolicy: ``` apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: example-networkpolicy namespace: default spec: podSelector: matchLabels: app: myapp policyTypes: - Ingress ingress: - from: - ipBlock: cidr: 172.17.0.0/16 ``` Diese Policy erlaubt den eingehenden Datenverkehr zu Pods mit dem Label app: myapp nur aus dem IP-Bereich 172.17.0.0/16. ## Best Practices 1. Gewähren Sie nur die minimal notwendigen Berechtigungen. * Verweigern Sie standardmäßig alle Verbindungen und erlauben Sie nur spezifische. 2. Explizite Egress-Regeln: * Definieren Sie klare Egress-Regeln, um den ausgehenden Datenverkehr zu kontrollieren. 3. Verwendung von Labels: * Verwenden Sie Labels zur Identifizierung von Pods, um die Verwaltung von NetworkPolicies zu erleichtern. ## Fazit NetworkPolicies sind ein unverzichtbares Werkzeug zur Sicherung von Kubernetes-Clustern. Sie ermöglichen feingranulare Kontrolle über die Kommunikation zwischen Pods und helfen, das Prinzip der geringsten Privilegien durchzusetzen. Durch den effektiven Einsatz von NetworkPolicies können Organisationen ihre Anwendungen in Kubernetes sicher und zuverlässig betreiben. --- ## Github workflow trigger in other repository - URL: https://blog.kuepper.nrw/posts/2023-09-21-github-workflow-trigger-in-other-repository/ - Datum: 2023-09-21 - Autor: ruediger - Tags: GitHub, Action, Workflow, Trigger - Kategorien: Internet [English Version](/posts/2023-09-21-github-workflow-trigger-in-other-repository-en.html) ![Github Actions](https://s3fs.kuepper.nrw/blog-images/posts/github-actions-banner.webp) ## Eine Github Action in einem anderem Repository triggern Im ersten Repository wird ein Workflow `trigger_on_push.yaml` erstellt, der in einem anderen Repo einen Worklow `trigger_prod.yaml` triggert. Dieser kann sogar in einem anderen GitHub Account liegen. Man benötigt nur einen gültigen Token. Aber wieso über eine dazwischen geschaltete Action? Ganz einfach, das 2. Repository bekommt keine Pushes, ausser es würde sich etwas in diesem Repository ändern. Da aber die Änderungen nur im 1. Repository gemacht werden und das zweite nur die Software für das Generieren der Page und der Docker Images enthält, wird sich da nichts ändern. Also kann Semantic Release auch keine Version hochzählen. Daher die Trigger Action im 2. Repo. Die ändert nur eine Datei im Repo und pushed es in sich selbst. Danach Triggert sie die eigentliche Build Action `main.yaml`. Damit ist eine Änderung vorhanden und Semantic Release findet Commits, die dann in dieses Release mit rein kommen. Repo 1 Workflow `trigger_on_push.yaml`: ``` name: Build Develop on: push: branches: - develop jobs: trigger-workflow: runs-on: ubuntu-latest steps: - name: Trigger build Repo uses: convictional/trigger-workflow-and-wait@v1.6.1 with: owner: ruedigerp repo: ink-blog github_token: ${{ secrets.GH_TOKEN }} workflow_file_name: trigger_prod.yaml ref: develop wait_interval: 10 propagate_failure: true trigger_workflow: true wait_workflow: true ``` Im 2. Repo `trigger_prod.yaml` ``` name: Trigger Build Prod on: workflow_dispatch: inputs: workflow_02: description: 'ًWorkflow 2 which will be triggered' required: true default: 'trigger-workflow' workflow2_github_account: description: 'GitHub Account Owner' required: true default: 'youraccount' workflow2_repo_github: description: 'repo-name' required: true ref: main default: 'repo1' jobs: trigger-build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: fetch-depth: 0 - name: add Trigger page run: | ls -la echo "build $(date)" > buildit.txt ls -la git remote -v export GH_TOKEN="${{ secrets.DOCKHUB_TOKEN }}" git config --global user.email "your@main.com" git config --global user.name "Your Name" git add . git commit -m "fix: new build $(date)" git push trigger-workflow: runs-on: ubuntu-latest steps: - name: Trigger build Repo uses: convictional/trigger-workflow-and-wait@v1.6.1 with: owner: ruedigerp repo: repo2 github_token: ${{ secrets.DOCKERHUB_TOKEN }} workflow_file_name: main.yaml ref: main wait_interval: 10 propagate_failure: true trigger_workflow: true wait_workflow: true ``` Die `main.yaml` in Repo2: ``` name: Build Prod on: push: branches: - 'main' workflow_dispatch: inputs: workflow_02: description: 'ًWorkflow 2 which will be triggered' required: true default: 'trigger-workflow' workflow2_github_account: description: 'GitHub Account Owner' required: true default: 'youraccount' workflow2_repo_github: description: 'repo-name' required: true default: 'repo2' env: IMAGE_NAME: ghcr.io/youraccount/imagename DOCKERFILE: Dockerfile jobs: prepare: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: fetch-depth: 0 - name: Create Sematic Release Version env: GITHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} run: | export HOME=/tmp git config --global user.email "your@main.com" git config --global user.name "youraccount" git config --global credential.helper cache npx semantic-release build-amd64: needs: [ prepare ] runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v2 with: fetch-depth: 0 - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: "ghcr.io" username: ${{ secrets.GH_USERNAME }} password: ${{ secrets.GH_TOKEN }} - name: Build and push AMD64 image run: | # ... # build stuff # ... docker build -f ${{ env.DOCKERFILE }} --build-arg GOOS=linux --build-arg GOARCH=amd64 --build-arg NEXT_VERSION="$VERSION" --build-arg BUILD_TIMESTAMP="$BUILD_TIMESTAMP" --build-arg GIT_BRANCH="$GIT_BRANCH" --build-arg COMMIT_HASH="$COMMIT_HASH" -t ${{ env.IMAGE_NAME }}:$VERSION-amd64 -t ${{ env.IMAGE_NAME }}:latest-amd64 . docker push ${{ env.IMAGE_NAME }}:$VERSION-amd64 # ... merge-images: # ... other-cool-stuff: # ... ``` --- ## GitHub Actions: Das Triggern von Anderen Actions - URL: https://blog.kuepper.nrw/posts/2023-09-21-github-actions-das-triggern-von-anderen-actions/ - Datum: 2023-09-21 - Autor: ruediger - Tags: GitHub, Action, Workflow, Trigger - Kategorien: Internet [English Version](/posts/2023-09-21-github-actions-das-triggern-von-anderen-actions-en.html) ![Github Actions](https://s3fs.kuepper.nrw/blog-images/posts/github-actions-banner.webp) ## Einleitung GitHub Actions ist ein CI/CD-Service (Continuous Integration/Continuous Deployment) von GitHub, der es Entwicklern ermöglicht, Automatisierungsworkflows direkt in ihren GitHub-Repositories zu erstellen und zu teilen. Eine häufige Anforderung ist das Triggern einer Action aus einer anderen Action heraus. In diesem Artikel werden wir untersuchen, wie man GitHub Actions effektiv verkettet und so komplexere Automatisierungsprozesse erstellt. ## Grundlagen von GitHub Actions GitHub Actions basieren auf Workflows, die in YAML-Dateien innerhalb des .github/workflows-Verzeichnisses eines Repositories definiert sind. Ein Workflow besteht aus einem oder mehreren Jobs, und jeder Job besteht aus Schritten, die Befehle oder Actions sind. ## Triggern von Anderen Actions Um eine GitHub Action aus einer anderen Action heraus zu triggern, gibt es verschiedene Ansätze: Man kann ein Repository Dispatch Event von einer Action auslösen, um einen Workflow in demselben oder einem anderen Repository zu starten. Dies erfordert ein Personal Access Token, um das Event zu authentifizieren. 1. Workflow Dispatch Event: * Mit dem Workflow Dispatch Event kann man manuell einen Workflow starten. * Es kann auch durch einen API-Aufruf ausgelöst werden, was es von einer anderen Action aus startbar macht. 2. Scheduled Workflows: * Workflows können so konfiguriert werden, dass sie zu bestimmten Zeiten ausgeführt werden. * Dies kann genutzt werden, um nachfolgende Workflows zu triggern. ## Beispiel: Repository Dispatch Event Hier ist ein einfaches Beispiel, wie man ein Repository Dispatch Event verwendet, um einen Workflow zu triggern: ``` # .github/workflows/triggering-workflow.yml name: Triggering Workflow on: [push] jobs: trigger: runs-on: ubuntu-latest steps: - name: Trigger Another Workflow run: | curl -XPOST -u "USERNAME:${{ secrets.PAT }}" \ -H "Accept: application/vnd.github.everest-preview+json" \ -H "Content-Type: application/json" \ https://api.github.com/repos/OWNER/REPO/dispatches \ --data '{"event_type": "triggered"}' ``` ``` # .github/workflows/triggered-workflow.yml name: Triggered Workflow on: repository_dispatch: types: [triggered] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v2 # Weitere Schritte... ``` In diesem Beispiel wird durch einen push Event der triggering-workflow.yml Workflow gestartet, der dann das triggered-workflow.yml durch ein Repository Dispatch Event auslöst. ### Fazit Das Triggern von GitHub Actions aus anderen Actions heraus ermöglicht die Erstellung von komplexen und flexiblen Automatisierungsworkflows. Durch die Verwendung von Methoden wie dem Repository Dispatch Event oder dem Workflow Dispatch Event können Entwickler ihre CI/CD-Prozesse effizient gestalten und optimieren. --- ## InkProject Blog Github Workflow - URL: https://blog.kuepper.nrw/posts/2023-09-19-inkproject-blog-github-workflow/ - Datum: 2023-09-19 - Autor: ruediger - Tags: InkProjekt, Blog, GitHub, Action, Workflow, Trigger - Kategorien: Internet [English Version](/posts/2023-09-19-inkproject-blog-github-workflow-en.html) ## Bey Bey Hugo, welcome Ink Dieses Blog wird nicht mehr durch Hugo generiert. Der Hugo Static Content Generator war jetzt lange im Einsatz, doch seit ein paar Wochen habe ich mir alternativen angeguckt. Dabei bin ich mehrere durchgegangen und bin schließlich bei InkProjekt geblieben. Nach den ersten Tests wurden dann auch nach und nach immer mehr Posts umgezogen. Dabei wurden dann auch gleich die ersten Schritte in Richtung Docker und Kubernetes gemacht. Also für Ink und dieses Blog. Docker und Kubernetes mache ich ja jetzt schom ein paar Jahre. ## Schreiben, publish, build und deploy Die Reihenfolge ist klar, wie immer halt. Ein neuer Post wird geschrieben, oder wie jetzt in Ink kopiert, anschliessend `ìnk publish` ausgeführt. Alle Files aus dem `public` nach `html` kopiert, so das ein Dockerfile für Nginx das html Verzeichnis mit dem neuen Content bespielen kann. Docker Push in die Docker Registry, gefolgt von einem `kubectl set image...`. Die neue Version war online. Das waren erst einmal noch manuelle Schritt. Ein paar Shell Scripte. ## GitHub Actions Nachdem das alles wunderbar lief war es an der Zeit alles automatisiert zu erledigen. Post oder Page anlegen, git add,commit,push und GitHub Action sollten sich um das zusammen bauen der Page, das Builden von Docker Images für alle Plattformen und den HelmChart kümmern. Das war alles erst einmal in einem Git Repo, da kam das als ersten der Helm Chart wieder raus in ein eigens. Der Workflow kümmerte sich nach dem Push dann um folgende Schritt: 1 repo Checkout 2 Semantic Release Version hoch zählen 3 Version in GitHub Env speichern 4 Page im ink publish erstellen 5 Seiten in Docker Images für alle Plattformen packen 6 Docker Image zu einem MultiPlatform Image zusammenfügen 7 Docker Image in die Registrie pushen 8 Helm Chart Repo auschecken 9 Mit der Semantiv Version Helm Chart erstellen 10 Helm Chart ins Helm Chart Museum pushen 11 Helm Chart in den Kubernetes Cluster deployen ## Da alles perfekt lieft, alles auseinander reißen Da alles, bis auf der Helm Chart, in einem Repository gewesen ist konnte man Teile davon schlecht für andere Seiten oder Blogs wiederverwenden. Also wurde als erstes der Content in ein eigenes GitHub Repo gesteckt. Beim InkProject befindet sich in Source nicht nur die Markdown Files mit dem Content, auch die Images sind dort drin. Die wurden da auch direkt raus und auch in ein eigenes Repo kopiert. so das man wirklich nur Markdown Dateien für den Blog Content in einem Repo hat. Themes, die haben noch ein Helm Repository bekommen. Genau so wie die grundsätzlichen Teile von InkProject. Die Binary Files, ja Files, da ich direkt arm, amd usw dazu gepackt habe, falls die GitHub RUnner mal auf anderen Plattformen laufen, genau so wie die Config. ## Der GitHub Workflow konnte dann angepasst werden. Der Workflow wird jetzt im Repo `ink-blog` gestartet. Dieser zieht sich nach einander die Repositories: * ink-content * ink-images * ink-theme * ink-html Die werden in den Ordnern `source`, `source/images`, `theme` und `html` abgelegt und somit ist die Ordner-Struktur von InkProject wie sie ein muss. Im nächsten Schritt wird der Blog erstellt: `ink publish` und dann die Dateien nach `html` kopiert. das Verzeichnis `html` ist ein GitHub Repo, da wird dann rein gewechselt und ein git add,commit,push gemacht. Damit habe ich dann schon einmal den neuen Content in einem eigenen Repo. Der Rest wird dann schon mal wieder im Workflow weggeschmissen und im nächsten Schritt werden jetzt die Docker Images für mehere Plattformen erstellt. Dazu wird dann das Repo `ink-html` benötigt und daher in den Steps ausgecheckt. Alle Docker Images werden wieder zu einem MultiPlatform Image zusammengefügt und in die Docker Registry geladen. Bei den ganzen Schritten natürlich wieder Semantoc Release, also Docker Images und Helm Chart automatisch mit der richtigen Version versehen. Der Helm Chart wird zum Schluß noch ausgecheckt und mit der Semantic Version erstellt, das Image über die `values.yaml` gesetzt. HelmChart ins Chart Museum und der Helm Chart in den Kubernetes Cluster installieren. ## Workflow die 3. Der Workflow ist gut und man könnte ihn so lassen. Da ich aber den Content in einem eigenen Repo pflege, müsste man je immmer den Content erstellen oder ändern, dann pushen. Dann das gleiche beim `ink-blog` Repo auch noch machen, damit die Pipeline getriggert wird. Nö, das geht auch anders. ## Content mit eigener Action Die Action in dem Content Repo macht nicht viel. Sie baut nicht mal irgend etwas zusammen. Sie triggert einfach nur die andere Pipeline im Reop `ink-blog`, welches dann alles weitere macht. Content triggert ink-blog, ink-blog checked `ink-content` und weitere Repos aus, baut alles zusammen, erstellt Images und Helm Chart und deployed alles in Kubernetes. So einfach ist das. Der Vorteil ist: Ich kann Content auch mal eben vom Handy aus erstellen und anpassen. Ich kann das ganze auch noch erweitern für andere Blogs und Homepages. Dann wird nur noch der Content für die einzelnen Seiten hinterlegt, der Build mit anderen Daten kommt immer aus den gleichen Repos. Das ist noch nicht eingerichtet, aber die Grundlage dafür ist schon mal angelegt. --- ## Deploy Minecraft Server on Kubernetes Cluster - URL: https://blog.kuepper.nrw/posts/2023-09-11-Bedrock-Minecraft-on-kubernetes/ - Datum: 2023-09-11 - Aktualisiert: 2023-09-11 - Autor: ruediger - Tags: Kubernetes, Minecraft, Bedrock - Kategorien: Internet # Create Kubernetes YAML for * Namespace * Configmap * PersistentVolumeClaim * Loadbalancer Service * StatefulSet ``` cat > minecraft.yaml < When creating a PVC without specifying a StorageClass, the PV is not created if no StorageClass is selected as default in the cluster. The PVC then remains in a pending state indefinitely. > Retrieve the list of available StorageClass using kubectl and search for the desired StorageClass from the list. ``` kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE csi-disk everest-csi-provisioner Delete Immediate true 66d csi-disk-topology everest-csi-provisioner Delete WaitForFirstConsumer true 66d ... ``` Mit dem Namen der StorageClass ein Kubectl Path ausführen und dabei dann `is-default-class` auf `true` setzen. > Execute a kubectl path with the name of the StorageClass and set is-default-class to true. ``` kubectl patch storageclass csi-disk -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' storageclass.storage.k8s.io/csi-disk patched ``` Die StorageClass wird anschliessend als Default StorageClass angezeigt. > The StorageClass will then be displayed as the default StorageClass. ``` kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE csi-disk (default) everest-csi-provisioner Delete Immediate true 66d csi-disk-topology everest-csi-provisioner Delete WaitForFirstConsumer true 66d ... ``` --- ## Nginx Google Fonts Filtern - URL: https://blog.kuepper.nrw/posts/2022-09-25-nginx-googlefonts-filtern/ - Datum: 2022-09-25 - Autor: ruediger - Tags: Nginx, GoogleFonts, Internet - Kategorien: Technik ### Abmahnwelle wegen Google Fonts? Seit ein paar Tagen finden einige wieder Briefe im Briefkasten wegen einer Abmahnwelle zu Google Fonts. Dieses mal lässt sich sogar ein Anwalt vor den Karren spannen und er versendet aktuell wohl tausende Abmahnungen. Laut Post einer Kanzlei sind dort alleine am Freitag vorletzter Woche 300 dieser Schreiben eingetroffen die man bearbeitet. Wer noch Google Fonts direkt von den Google Server einbunden hat, sollte diese lokal speichern und direkt selbst ausliefern. ### Was machen wenn Theme Updates Fonts wieder einbinden? Es gibt z.B. Wordpress Themes bei denen man die Fonts zwar anpassen kann, aber nach einem Update des Themes wird wieder Google Fonts von den Servern bei Google geladen. Anderes Problem sind Personen die nicht wissen was sie machen müssen. Wer, egal wieso, die Nutzung von Google Fonts von Google Servern unterbinden möchte und selbst einen Nginx Webserver einsetzt, der kann das sehr leicht für alle Seiten vom Nginx ereldigen lassen. In der Config vom Nginx einfach folgende Zeile mit eintragen. Wir ersetzen einfach die Domain für die Google Fonts gegen: `https://fonts.googleapis.com`. ``` sub_filter 'https://fonts.googleapis.com' 'https://disable-google-fonts'; ``` Die Zeile könnte man jetzt auch gut dafür nutzen um alle Seiten zu finden die noch Google Fonts einbinden. ``` sub_filter 'https://fonts.googleapis.com' '/googlefonts/'; ``` Damit wird der Font nicht auf dem Sevrer gefunden der die Seite ausliefert. Dafür kann man dann aber einfach in den Logfiles alles Seiten nach `/googlefonts/` suchen und hat dann schnell alle Seiten und Stellen wo man noch anpassen müsste. ``` grep '/googlefonts/' /path/to/logfiles/*.log ``` Sollten Einträge vorhanden sein weiß man schnell wo noch Google Fonts eingebunden sind. --- ## Dovecot Sieve Delimiters Rules - URL: https://blog.kuepper.nrw/posts/2022-09-24-dovecot-sieve-delimiters-rules/ - Datum: 2022-09-24 - Autor: ruediger - Tags: Dovecot, Sieve, Delimiters, Rules - Kategorien: Technik ### Was sind Delimiter Mailadressen? Ich benutze seit einigen Jahren für manche Mailaccounts Delimiter im Local-Part der Adressen. So kann ich für bestimmte Seite immer, ohne erst einen Account oder eine Weiterleitung einrichten zu müssen, extra E-Mailadressen benutzen. Die E-Mailadressen sind immer nach folgendem Schema aufgebaut. username+order1.order2@domain.de ordner1 ist dabei dann z.B. shops, social usw. ordner2 ist z.B. bei shops dann amazon, ebay usw. So könnte es dann folgende E-Mailadressen für die einzelnen Seiten geben: * username+shops.amazon@domain.de * username+shops.ebay@domain.de * username+social.facebook@domain.de * username+social.instagram@domain.de * username+social.twitter@domain.de So hat jeder Shop und jedes soziale Netzwerk und viele weitere Portale und Services immer eine eigene E-Mailadresse. ### Automatische Sortierung Die Mail können anschliessend per Filterregeln natürlich passend einsortiert werden. Wer seine Domains und Mailadressen auf einem Server mit Sieve als Filter, kann diese Mailadressen auch automatisch in die Ordner ablegen lassen. Dafür gibt es einen Ordner im Postfach `Autosort` in dem dann alle Mails in die entsprechenden Ordnern speichert. ``` .Autosort.shops.amazon .Autosort.shops.ebay .Autosort.social.facebook .Autosort.social.instagram .Autosort.social.twitter ``` Im Mailclient wird dann unter dem Ordner Autosort die Ordner shops und social angelegt und dort drin dann Amazon und Ebay, bzw. Facebook, Instagram und Twitter angelegt, in denen dann die Mails jeweils einsortiert werden. ![Autosort Folders Mailpostfach](https://s3fs.kuepper.nrw/blog-images/posts/folders.webp) ### Sieve Filterregeln Damit das ganze funktioniert muss natürlich auch ein passender Filter im Sieve hinterlegt werden. ``` require ["reject","fileinto","imap4flags","body","vacation","copy","variables","regex","envelope"]; # rule:[autodelemiter] if header :regex "Delivered-To" "username\\+([^.]*)\\.?([^.]*)\\.?([^.]*)\\.?([^.]*)\\.?([^.]*)@.*$" { if string :is "${1}" "" {} else { set :lower "part1" "${1}"; } if string :is "${2}" "" {} else { set :lower "part2" "${2}"; } if string :is "${3}" "" {} else { set :lower "part3" "${3}"; } if string :is "${4}" "" {} else { set :lower "part4" "${4}"; } if string :is "${5}" "" {} else { set :lower "part5" "${5}"; } if string :is "${6}" "" {} else { set :lower "part6" "${6}"; } set "targetfolder" ""; if string :is "${part1}" "" {} else { set "targetfolder" "${part1}"; } if string :is "${2}" "" {} else { set "targetfolder" "${targetfolder}/${part2}"; } if string :is "${3}" "" {} else { set "targetfolder" "${targetfolder}/${part3}"; } if string :is "${4}" "" {} else { set "targetfolder" "${targetfolder}/${part4}"; } if string :is "${5}" "" {} else { set "targetfolder" "${targetfolder}/${part5}"; } if string :is "${6}" "" {} else { set "targetfolder" "${targetfolder}/${part6}"; } fileinto "Autosort/${targetfolder}"; } ``` Damit können die Mailadressen einfach angegeben werden und sie werden automatisch sortiert. Das ganze ist hier für bis zu 6 Ordnern ausgelegt. Damit wäre also folgende Mailadresse möglich `username+ordner1.ordner2.order3.order4.order5.order6@domain.tld` möglich. Die Ordner dazu hätten dann folgende Struktur im Postfach: ``` Autosort +-ordner1 +-ordner2 +-order3 +-order4 +-order5 +-order6 ``` Ich selbst habe bis jetzt glaube ich maximal 4 Ordner benutzt. Aber so ist halt noch Luft nach oben. ;-) Das ist der Grund wieso ich 2789 Ordner in meinen Mailpostfächern habe. Und 97% habe ich nicht einmal selbst angelegt. Das passiert alles automatisch und die die Mails landen auch noch automatisch in diesen Ordnern. So sind auch nur die wichtigsten E-Mail in der Inbox. ### Wieso Delimiter Adresse noch sinnvoll sind Es kam in den letzten Jahren mehrfach zu Datenschutzvorfällen bei einigen Anbietern und Portalen. Werden solche E-Mailadresse durch einen Hack einer Seite abgezogen und eine Spamwelle kommt, schaltet man diese einzelne Delimiter Mailadresse einfach mit einer Filterregel ab. Also einfach löschen. In ein paar Fällen war den Betreibern auch nicht bewusst das Daten von dem Portal abgezogen wurden. Durch die Erklärung und teils sogar recht kryptischen Mailadressen von mir war ihnen schnell klar das diese Spam/Pishing E-Mails nicht durch Zufall bei mir angekommen sind. Es wurde dann überprüft und durch den Hinweis konnten manche Vorfälle dann auch bestätigt und andere Benutzer direkt gewarnt werden. --- ## Unifi UDM PRO IPSec dynamische WAN-IP - URL: https://blog.kuepper.nrw/posts/2022-08-23-unifi-udm-pro-ipsec-dynamic-wan-ip/ - Datum: 2022-09-23 - Aktualisiert: 2022-09-23 - Autor: ruediger - Tags: ubiquiti, unifi, udmpro, VPN, Site2Site, WAN, IP - Kategorien: Technik IPSec Site2Site Tunnel mit der Unifi Dream Machine Pro ist sehr einfach eingerichtet. Bei einer statischen WAN-IP hat man auch keine Probleme damit. Wer aber eine UDM Pro mit einer dynamischen IP-Adresse benutzt wird schnell merken wenn der Tunnel nicht läuft, wenn durch einen Reconnect am Anschluss eine neue IP gesetzt ist. Die UDM ändert leider nicht die Config automatisch ab. Aber auch auf der Gegenseite muss die Config angepasst werden. Das ganze erledige ich jetzt mit Ansible. Ändert sich die IP wird die aktuelle IP per Ninja Template in die neue Config gegossen und auf den Remote-IP-Server kopiert. Für die UDM kommt auch ein Script zum Einsatz. Es wird per Ansible kopiert und anschliessend auch ausgeführt. Der Ansible Task für den Remote IPSec-Server holt sich erst einmal die aktuelle IP. Damit wird dann das Template gefüllt, also an der Stelle für die LeftID (rightid=...) und auf den Remote Server kopiert. ``` --- - name: Get JSON from the Interwebs uri: url="https://ip.tytik.cloud/json" return_content=yes method="GET" body_format="json" register: json_response delegate_to: localhost - name: set ip var set_fact: ip="{{ (json_response.content|from_json)['data'] }}" - name: ipsec.conf template: src: ipsec.conf.j2 dest: /etc/ipsec.conf owner: root group: root mode: 0640 ``` Bei der UDM Pro wird ein Shell Script kopiert, welches auch die aktuelle IP Adresse per Curl abruft und dann per sed die alte IP gegen die neue ersetzt. Anschliessend wird IPSec neugestartet und der Tunnel aufgebaut. ``` --- - name: Copy file with owner and permissions copy: src: ip_wan_ip.sh dest: /root/ip_wan_ip.sh owner: root group: root mode: '0544' - name: Change Remote ip command: /root/ip_wan_ip.sh ``` Da die IP bei `rightid` gesetzt ist und bei `right` der Subdomain und dieser auf die dynamische IP Zeigt, wird die Subdomain auch direkt noch aktualisert. Dazu habe ich ein Update Script, mit dem im BIND DNS Server der A-Record für diese Subdomain aktualisiert wird. ``` - name: Change Domain Record command: /root/nsupdate.sh -z domain.tld -t A -h ipsec -v {{ ip }} delegate_to: localhost ``` Damit kann regelmässig überprüft werden ob sich die IP geändert hat und es werden auf allen Seiten die nötigen Änderungen gemacht um den Tunnel wieder aufzubauen. Das umständliche DNS Update und das herumgeklicke in der UDM Pro Weboberfläche fällt weg. Ausserdem kann das dann auch automatisiert gemacht werden und man muss sich auch nicht mehr drum kümmern. Die Scripte und Ansible Roles sind fertig. Da noch ein paar Sachen auf das Setup hier angepasst sind und an mehreren Stellen etwas angepasst werden müsste, ist der Code noch nicht im Git. Das werde ich noch nachholen und ein Update hier hinterlassen. So das es von jedem genutzt werden kann und nur an einer oder zwei Stellen dann die IPs, Hostnames, User usw. eingetragen werden müssen. --- ## Fremde Geräte im Wifi - URL: https://blog.kuepper.nrw/posts/2022-07-04-Fremde-Geräte-im-Wifi/ - Datum: 2022-07-04 - Autor: ruediger - Tags: Router, Wifi, WLAN - Kategorien: Internet Fremde Geräte im WLAN? Chip.de, Telekom.de, pcwelt.de und ihre Tipps beim Thema WLAN. Es ist zum Haare raufen. Bei manchen Artikeln fast man sich nur noch an den Kopf und will eine Aspirin einwerfen. Ja, nutzt jemand das WiFi unberechtigt kann es zu einer langsamen Internetverbindung führen. Oder im schlimmsten Fall zu rechtlichen Problemen kommen. Dann, wenn jemand illegales Zeug mit dem Internetanschluss macht. Aber was bitte werden in den Artikeln für Tipps gegeben? Es fängt damit an, daß man vielleicht vermutet jemand ist im WiFi und dann wird anhand vom Router erklärt wie man so einen Benutzer dort findet. Es stimmt das sehr viele wahrscheinlich eine Fritzbox haben. Also wird es anhand der Weboberfläche der Fritzbox erklärt. Nur was machen bitte alle Kunden der Telekom, die einen Speedport Router haben. Oder Netgear, TP-Link usw.? Also, hat man den Verdacht, es befindet sich ein unbefugter Benutzer in WiFi, dann ist die Lösung sehr kurz und Knapp: Änder Dein WiFi-Passwort. Punkt. Damit wäre jeder Artikel in 2-3 Sätzen schon erledigt. Aber das wichtigste fehlt in allen dieser Artikel. Keiner geht drauf ein was man sonst noch machen könnte. Es wird eigentlich in allen immer nur geraten den MAC-Adressen Filter zu aktivieren. Damit, mit den Mac-Filter, dann auch viel Spaß. Denn z.B. iOS/iPadOS (Apple) und auch einige Andoid Geräte haben die Möglichkeit die MAC-Adressen zu verschleiern. Bei Apple heißt es in den WLAN Einstellungen „Private WLAN-Adresse“. Das ist eigentlich als Standard immer aktiv. Was heißt das genau? Jedes Gerät hat bei Bluetooth und Netzwerkschnittstellen (Kabel oder WiFi) immer eine weltweit eindeutige MAC-Adresse, über die es identifizierbar ist. Im Heimnetzwerk und in Firmen bekommt jedes Gerät meistens seine IP beim Verbinden von einem DHCP-Server zugewiesen. Dazu sendet das Gerät die Anfrage für eine IP ins Netzwerk. In dieser Anfrage ist die Mac-Adresse enthalten. Die IP-Adressen werden aus einem Pool genommen und entsprechend eine freie an das Gerät vergeben. Verlässt man das Netzwerk oder schaltet das Gerät aus, geht dann nach kurzer Zeit wieder in Netzwerk, bekommt man meistens die gleiche IP zugewiesen. Oft kann man sich trotz DHCP in den meisten Routern auch immer die gleiche IP Adresse geben lassen, indem man dem Gerät im Router die IP fest zuweisen kann. Dabei wird immer die MAC-Adresse herangezogen. Wir erinnern uns an die Anfrage mit der Mac-Adresse weiter oben. Jetzt aktivieren wir aber an einem iPhone „Private WLAN-Adresse“ und erhalten damit jedes mal sogar eine andere MAC-Adresse. Entsprechend ist das Gerät auch noch für den Router und dem darauf laufenden DHCP-Server ein neues Gerät. Also bekommen wir jedesmal eine andere IP-Adresse im Netzwerk. Und jetzt schlägt dann der „Pro-Tipp“ großer IT Bereiche mancher Portale zu. Die Empfehlung den MAC-Adressenfilter zu aktivieren. Da sitzt jetzt der nicht Internet-Pro an seinem iPhone und macht sich Sorgen um sein WiFi. Irgend etwas stimmt damit nicht. Kommt jetzt auf einen der Artikel und in ihm kommt etwas Angst auf beim lesen. „Es könnte jemand Dein Netzwerk benutzen“. MAC-Adressenfilter aktivieren, super wird gemacht. Verstanden wozu und was er damit macht, keine Ahnung. Wird schon richtig sein. Es funktioniert auch prima, da der Filter aktiviert werden kann und sein iPhone, so wie das Gerät der Holden auch schön in der Liste eingetragen sind. Bis er dann sein Gerät einige Zeit nicht mehr an hatte. Es lag aus auf dem Tisch. iPhone entsperren und surf….. Es bleibt offline, keine App kann sich verbinden, keine Internetseite funktioniert mehr. Die Frau kann aber noch alles machen, sie war die ganze Zeit am Telefon. Würde man jetzt mit dem noch funktionierenden Gerät auf dem Router noch mal den MAC-Adressenfilter überprüfen, würde man sehen: * Sein Gerät ist offline * Ein neues Gerät versucht zu verbinden, wird aber geblockt. * Das „neue“ Gerät ist aber sein Gerät. Da „Private WLAN-Adresse“ aktiv ist, hat er jedesmal eine neue MAC-Adresse. Daher ist es für den Router und seinen Mac-Adressenfilter auch jedesmal ein neues Gerät. Im Normalfall geht der unbedarfte Benutzer nicht hin und deaktiviert den Filter jetzt oder fügt das „neue“ Gerät im Filter hinzu. Am besten deaktiviert er „Private WLAN-Adresse“ im iPhone. Nein, es klappt bei Ihr und nicht mehr bei ihm. Also scheint wohl irgend etwas am WLAN zu sein. Also: „ich starte mal den Router neu, vielleicht geht es ja dann wieder.“ Der Supergau tritt ein. Ab jetzt, also der Fall wie er heutzutage oft üblich ist, wenn es nur noch Smartphone und/oder Tablets im Haushalt gibt, kommt kein Gerät mehr in das WiFi rein. Alle haben die Einstellung für die „Private WLAN-Adresse“ aktiviert. Der MAC-Adressenfilter ist aktiv und sagt jetzt zu jedem Gerät: „ich kenn Dich nicht, du kommst hier nicht rein. Und jetzt wird es für die beiden spaßig. Die kommen in Ihr WiFi nicht mehr rein. Sie können jetzt auch nicht mehr den Filter deaktivieren. Sie müssen das mit einem Laptop machen der mit einem Kabel an den Router angeschlossen wird. Und selbst wenn sie den haben wird das für die meisten ein schwieriges Unterfangen. Denn oft wissen sie nicht mal die IP von ihrem Router. Und alles nur weil man Probleme mit dem WiFi hatte. Auf ein paar Seiten nach der Lösung geguckt hat und dabei dann die Panik wegen fremden Nutzern aufkam. Dann der Tipp mit dem Mac-Adressenfilter, der heutzutage nicht nur unwillkommene Benutzer aussperrt, sondern direkt alle. Auch die, die rein dürfen, besser gesagt bis dahin durften. Sollte ein WiFi langsam sein oder es öfters zu Abbrüchen kommen, ist es in den aller meisten Fällen kein fremder Benutzer. Das wird in sehr, wirklich sehr wenigen Fällen die Ursache sein. In den meisten Fällen sind es bescheidene WiFi Router, die von der Hardware und Software nicht die tollsten Geräte sind. Auch immer störend, gerade in Städten, sind viele andere WiFi-Netzwerke, die sich die Frequenzen teilen und sich so gegenseitig stören. Oder andere Geräte in der eigenen Wohnung und sehr oft der falsche Standort. Oft reicht es den Router nur etwas anders zu positionieren. Die meisten haben gefühlt ihre Router auf Hüfthöhe auf der Komode oder im schlimmsten Fall dahinter. Sieht ja nicht toll aus die Box. Einfach mal nach oben an die Wand hängen. Wirkt oft Wunder. Aber was wäre wenn man sich jetzt einmal wirklich nicht sicher ist, ob nicht doch jemand einfach das WiFi mit benutzt? Wie oben beschrieben hilft da einfach am besten das WiFi Passwort zu ändern. Wer möchte kann sich ja gerne die verbundene Geräte einmal auf dem Router angucken. Vielleicht hat ja doch jemand das WiFi Passwort erraten oder woher sich immer. Aber wenn man sich unsicher ist, wie bei allen anderen Sachen auch: Passwort ändern. Alles andere ist unnötig und kann zu mehr Problemen führen. Wer das Passwort ändert und nicht auf anderen Geräten mühsam eingeben möchte: * iPhone und iPad können WiFi Passwörter teilen. Ein anderes Gerät will verbinden und ein anderen, welches schon damit verbunden ist kriegt eine Meldung und kann es dann einfach per antippen teilen. * Falls andere Geräte sich verbinden sollen kann man auch einfach einen QR-Code für WiFi-Name und WiFi-Passwort erstellen. Bei der Wahl des WiFi-Passworts nicht sparsam sein. Es sollte schon sehr sicher sein. Man gibt es ja auch nicht jeden Tag ein. Es wird ja auch gespeichert. Also ruhig auch mal nach Monaten oder 1-2 Jahren auch mal wieder ändern. Gerade wenn man einen Router zum ersten Mal eingerichtet hat, ändern. Oft sind die WiFi-Passwörter recht einfach. Teilweise haben manche Hersteller einfach die MAC-Adresse des Routers genommen. Oder die MAC-Adresse mit eigenen Tools in eine WiFi-Passwort umgewandelt. Was aber unsicher war und so von fremden schnell selbst berechnet werden konnte. Also immer ruhig das WiFi-Passwort selbst einmal ändern. --- ## Smarthome ioBroker Zigbee - URL: https://blog.kuepper.nrw/posts/2022-07-02-Smarthome-ioBroker-Zigbee/ - Datum: 2022-07-02 - Autor: ruediger - Tags: ZigBee, Gateway, RaspbeeII, Raspberry, Phoscon, ioBroker - Kategorien: Internet ## Smarthome Das Smarthome wurde eine zeitlang mit FHEM, HA-Bridge bzw. HomeBridge betrieben. Es lief auch recht lange sehr gut. Irgend wann wollte aber HA-Bridge nicht mehr funktionieren. Alle versuche, selbst komplette Neuinstallation konnte HA-Bridge nicht mehr zum laufen bewegen. HA-Bridge ist dann erst einmal rausgeflogen und die meisten Sachen wurde einige Zeit direkt über den Echo direkt angebunden. ![ioBroker HABPanel Keller](https://s3fs.kuepper.nrw/blog-images/posts/ioBroker-keller.webp) Funktioniert auch gut, aber man muss alles mögliche, was vorher über HA-Bridge oder FHEM gemacht wurde über Routinen erstellen. Kann man machen, ist dann aber oft sehr sperrig. Viele der vorher gemachten Dinge können aber überhaupt nicht abgebildet werden. Vor allem nicht so schnell und einfach wie vorher. In der Alexa App mal eine kleine Routine anlegen ok. Aber z.B. bestimmte Zustände von Lichtern, Steckdosen, Multimedia Geräten zum Beispiel in Abhängkeit von 1-n an-/abwesenden Personen, ist sehr aufwändig bis gar nicht möglich. ## HomeAssistant kennengelernt Vor ein paar Monaten habe ich dann bei einem Bekannten ein HomeAssistant (HA) migriert und konnte da mal ein wenig reingucken. Das hat mich natürlich wieder getriggert. Also habe ich mir HA einmal etwas angeguckt. Da ich aber hier keinen großen Server hinstellen möchte für ein "bisschen" SmartHome und HA offiziell nicht mit Docker supported wird, hatte sich das leider erledigt. Also noch einmal nach HA-Bridge und anderen Alternativen geschaut. Dabei dann auch mal einen genaueren Blick auf ioBroker geworfen. Die Installation war schon mal sehr simple und schnell erledigt. ![ioBroker HABPanel Wohnzimmer](https://s3fs.kuepper.nrw/blog-images/posts/ioBroker-wohnzimmer.webp) ## ioBroker installieren Das Raspbian auf eine SD-Karte schmeißen und anschliessend ioBroker installieren: curl -sL https://iobroker.net/install.sh | bash - Dieser Befehl startet die gesamte ioBroker-Installation. Am Ende der Installation wird dir die URL angezeigt, wie der ioBroker zu erreichen ist. Aufrufen von ioBroker über die Web-Oberfläche Gehe an deinen PC/Mac und öffne die Adresse, die am Ende des Setups zu sehen war. Folge den Anweisungen im ioBroker (Lizenzbestimmungen, Grundeinstellungen) Passwortänderung des Benutzers “pi” Das war es dann schon. Im ioBroker kann man sich dann erst einmal umschauen und sich einen Überlick verschaffen was ioBroker überhaupt alles kann. Und das ist eine Menge. Auch hier wie immer erst einmal alles möglich getestet und ausprobiert. ## ioBroker Adapter Bei ioBroker nennen sich zusätzliche Tools und Anbindungen an z.B. Alexa, VW-Coonect usw. Adapter. Für mich natürlich interessant Alexa, VW-Connect, Adapter für die Visualisierung und viele andere mehr. ### Smarthome: Alexa2 Adapter. Der Alexa2 Adapter sorgt nicht nur dafür um Smarte Geräte im ioBroker hinzuzufügen und sie dann auf den Echo Devices zu finden. Man bekommt auch sehr viel Information über alle möglichen Alexa Geräte und über Geräte die noch über eine Alexa, bzw. den eingebauten Hub verbunden sind. Man kann auch Sprachausgaben an Echo Geräte schicken. Und nicht nur Ankündigungen die erst durch "Alexa, habe ich neue Benachrichtigungen" ausgegeben werden. Sie können auch direkt ausgegegeben werden. Anwendung dafür wäre z.B. eine smarte Türklingel, die ab 20 Uhr keine Töne mehr von sich gibt, sondern nur noch im Wohnzimmer eine Sprachausgabe macht. Oder per Sprache und/oder Textausgabe auf einem FireTV. ### VW-Connect In der Fülle an Adapter ist mir dann der VW-Connect Adapter aufgefallen. Den habe ich direkt mal installiert und getestet. Da ich einen Skoda mit Skoda Connect und war sehr überrascht was man mit diesem Adapter alles machen kann. Von KM-Stand (Gesamt, letzte Fahrt), Verbrauch, bis zum Status aller Fenster/Türen (offen, geschlossen, verriegelt). Und vieles andere mehr. In Verbindung mit Datenpunkten und eGraph können so auch schöne Graphen zu allen möglichen Daten erstellt werden. Dazu aber mal in einem anderen Beitrag mehr Info. ### Weitere Adapter Alle hier aktuell installierten Adapter einmal in einer Liste. Wie schon oben bei egraph geschrieben, werde ich in anderen Beiträgen bestimmt noch mal genauer auf einzelne Adapter eingehen. Die Liste erhält man mit: `iobroker list adapters` * admin * alexa2 * backitup * cloud * daswetter * deconz * devices * discovery * dwd * echarts * firetv * habpanel * history * hue * hue-extended * icons-open-icon-library-png: * iot * javascript * net-tools * openweathermap * pi-hole * ping * smartthings * socketio * synology * tado * time-switch * unifi * vis * vis-weather * vw-connect * web * ws * yahka ### Wichtigsten Adapter Die für mich erst einmal wichtigsten Adapter sind Alexa2, deConz ZigBee, HABpanel, Philips Hue-Bridge und Extended, Javascript, Pi-Hole, Samsung Smartthings, Tado, Unifiy Network. Damit konnten dann alle Smarthome Geräte eingebunden, gesteuert und mit den JavaScript Adapter umfangreich programmiert werden. Die Adapter DeConz und habpanel sind für das im [letzten Artikel](/posts/2022-06-29-zigbee-gateway-raspbee-ii/) erwähnte ZigBee Gateway und für die Viualisierung der einzelnen Räume auf dem jeweiligen Tablet an der Wand. Zu den Tablets in den Räumen mit der jeweiligen Ansicht der Geräte und einer View für Multimedia werde ich auch noch einen Artikel schreiben. ![ioBroker HABPanel Küche](https://s3fs.kuepper.nrw/blog-images/posts/ioBroker-kueche.webp) --- ## DrayTek 167 UDM Pro IPv6 - URL: https://blog.kuepper.nrw/posts/2022-06-27-DayTrek-167-udm-pro-ipv6/ - Datum: 2022-06-27 - Autor: ruediger - Tags: Ubiquiti, Unfiy, UDM, DreamMachine, DrayTek, Vigor, 167, IPv6 - Kategorien: Internet Der etwas längere Weg zu IPv6 am Draytek Vigor 167 und der Ubiquiti DreamMachine Pro. ![Ubiquiti DreamMachine Pro](https://s3fs.kuepper.nrw/blog-images/posts/unifiy-pro.webp) [Der Chris](https://twitter.com/lelei) hatte mich letztes Jahr auf die neue Firmware hingewiesen, in der IPv6 funktionieren soll. Bei ihm hatte es auch an seinem Anschluss auch sofort geklappt. Hier funktionierte es aber überhaupt nicht. Selbst alles mögliche aus Foren hatte keinen Erfolg. IPv6 Prefix setzen, WAN/Lan IPv6 Firewall regeln und vieles mehr. Selbst VLAN7 in der DreamMachine ausschalten und von dem Vigor übernehmen lassen, oder anders herum. Nichts hat funktioniert. Immer wieder nach neuem Update der UDM Firmware habe ich dann getestet und probiert. Bis dann in einem Forum in einem Beitrag auf ein Firmware Update für das DrayTek Vigor 176 Modem gibt, welche das Problem beheben sollte. Und siehe da, mit der neuen Firmware Version ist jetzt auch eine IPv6 Adresse verfügbar. Aber man sieht sie nicht im Webinterface der DreamMachine. Aber die Seite [ipv6-test.com](https://ipv6-test.com) zeigt IPv4 und IPv6 an. IPv6-Test.com zeigt aber erst einmal einen Score mit 18 von 20 an. Weil ICMP blockiert wird. Das kann behoben werden indem man Firewall Rules dafür anlegt. Type: Internet v6 Local IPv6 Protocol: ICMPv6 IPv6 ICMP Type Name: Any Action: Accept Type: Internet v6 Local IPv6 Protocol: IPv6-ICMP Match all protocols except for this: enable Zusätzlich kann man jetzt auch die IPv6 Prefixe bei den internen Netzen hinzufügen. IPv6 Interface Type: Prefix Delegation Router Advertisement (RA): Enable RA Priority: high DHCPv6 Range start: ::2 DHCPv6 Range stop: ::7d1 DHCPv6/RDNSS DNS Control: auto Diese Einstellungen einfach bei allen internen Netzen hinzufügen und jedes Netz bekommt seinen eigenen IPv6 Prefix. Damit haben alle Clients IPv6 Adressen bekommen. Der nächste Schritt war dann auch für IPv4 DHCP über die DreamMachine zu machen. Das wurde bis jetzt immer von einem der beiden Ubiquiti EgdeRouterX SPF erledigt. Netzbereiche eingetragen aktiviert und die DHCP Services im EgdeRouterX deaktiviert. Und dann auch gleich die interfaces deaktiviert. Jetzt macht die DreamMachine DHCP, IPv6 und alles was sie auch schon vorher gemacht hatte. DreamMachine, ein Träumchen. ;-) --- ## Safari: Nervige Cookiebanner automatisch schliessen - URL: https://blog.kuepper.nrw/posts/2021-10-21-safari-nervige-cookiebanner-automatisch-schliessen/ - Datum: 2021-10-21 - Autor: ruediger - Tags: iOS, MacOS, Safari, Erweiterung, Consent, Cookiebanner, Cookie - Kategorien: Internet Beim surfen werden wir ja den ganzen Tag von nervigen DSGVO-Banner, Cookiebanner, Consentbanner belästigt und klicken sie weg. Oft werden Voreinstellungen gesetzt, die mehr erlauben als wir möchten. Zusätzlich werden diese Banner optisch so gestaltet, damit wir ja "Alles akzeptieren", oft grün, anklicken. Der Button "Nur notwendige" ist oft grau. Mit der Safari Erweiterung "Super Agent for Safari" [Mac App Store)](https://apps.apple.com/de/app/super-agent-for-safari/id1568262835) (Universal App für MacOS und iOS) kann man einfach diese nervigen Banner automatisch schliessen lassen. In den Cookie Settings von Super Agent kann man einstellen welche Cookies akzeptiert bzw. nicht akzeptiert werden dürfen. Im Dashboard wird angezeigt wie viele Seiten man schon besucht hat, wie viele Klicks gespart wurden und wieviel Zeit dadurch gewonnen wurde. ### Gallerie ![Super Agent Dashboard](https://s3fs.kuepper.nrw/blog-images/galleries/2021/10/safari-nervige-cookiebanner-automatisch-schliessen/01-super-agent-dashboard.webp) ![Super Agent Cookie Settings](https://s3fs.kuepper.nrw/blog-images/galleries/2021/10/safari-nervige-cookiebanner-automatisch-schliessen/02-super-agent-cookie-settings.webp) --- ## Rasbian ohne Monitor und Tastatur installieren - URL: https://blog.kuepper.nrw/posts/2019-09-03-Rasbian-ohne-Monitor-tastatur-installieren/ - Datum: 2019-09-03 - Aktualisiert: 2019-09-03 - Autor: ruediger - Tags: RaspberryPi, Raspbian, Installation, WiFi, Headless, silentinstall - Kategorien: Internet Ich bestelle immer RaspberryPi Bundles wie das hier [UCreate Raspberry Pi 3 Model B+ Desktop Starter Kit (16 GB, schwarz)](https://www.amazon.de/gp/product/B07BNPZVR7/). Eigentlich immer mit einem vorinstallierten Rasbian. Dieses mal war aber kein Rasbian vorinstalliert sondern Noobs auf der SD-Card. Nur stecke ich immer ein Netzwerkkabel an und Strom, warte kurz bis der RaspberryPi im Netz ist und logge mich dann per SSH dauf dem RaspberryPi ein. Das einzige was ich sonst immer mache ist im ROOT auf der SD-Card eine leere Datei `ssh` anlegen. Um jetzt Headless zu installieren geht man wie folgt vor: * In das Verzeichnis `/os` wechseln und alle Distributions Verzeichnisse löschen die man nicht benötigt. In diesem Fall bleibt dann nur das Verzeichnis `/os/Rasbian_Full` übrig. * Im Root Verzeichnis die Datei `recovery.cmdline` öffnen und an das Ende `silentinstall` anhängen. * Im Root Verzeichnis: `touch ssh` Vorher: ``` runinstaller quiet vt.cur_default=1 coherent_pool=6M elevator=deadline ``` Nachher: ``` runinstaller quiet vt.cur_default=1 coherent_pool=6M elevator=deadline silentinstall ``` SD-Card einstecken und Strom anschliessen. Dann ist Kaffee holen angesagt. Die Installation dauert ein paar Minuten. Wenn der RaspberryPi fertig ist sollte er wie gewohnt im Netz auftauchen und man kann sich drauf einloggen. # Kleines WLAN Problem Dieser RaspberryPi soll später mobil per WiFi über einen LTE Router ins Internet und per Kabel soll ein Kassendrucker angeschlossen werden. Da wpa_supplicant noch nicht konfiguriert war, aber schon gestartet ist hatte das noch kurz Probleme gemacht. Die Datei `/etc/wpa_supplicant/wpa_supplicant.conf` wurde angepasst: ``` cat /etc/wpa_supplicant/wpa_supplicant.conf ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev ap_scan=1 update_config=1 network={ ssid="Section3" psk="1234123412341234" } ``` Kurzer Test mit laufendem `tail -f /var/log/message &` hat erst einmal überhaupt nicht funktioniert. ``` iw wlan0 info Interface wlan0 ifindex 3 wdev 0x1 addr b8:27:eb:87:71:dc type managed wiphy 0 channel 11 (2462 MHz), width: 40 MHz, center1: 2452 MHz txpower 31.00 dBm ``` Läuft doch. Aber wieso klappt die Verbindung nicht? Ein Scan mit `iwlist wlan0 scan` hat auch geklappt, die gewohnt lange Liste der SSID hier wird ausgegeben. ``` # > wpa_supplicant -i wlan0 -Dnl80211 -c /etc/wpa_supplicant/wpa_supplicant.conf Successfully initialized wpa_supplicant Failed to create interface p2p-dev-wlan0: -16 (Device or resource busy) nl80211: Failed to create a P2P Device interface p2p-dev-wlan0 P2P: Failed to enable P2P Device interface Sep 3 20:27:17 raspberrypi kernel: [ 2046.293855] brcmfmac: brcmf_cfg80211_add_iface: iface validation failed: err=-16 Sep 3 20:27:17 raspberrypi kernel: [ 2046.293855] brcmfmac: brcmf_cfg80211_add_iface: iface validation failed: err=-16 wlan0: Trying to associate with 90:de:d0:d0:f1:98 (SSID='Section3' freq=2462 MHz) wlan0: Associated with 90:de:d0:d0:f1:98 wlan0: CTRL-EVENT-DISCONNECTED bssid=90:de:d0:d0:f1:98 reason=0 locally_generated=1 wlan0: WPA: 4-Way Handshake failed - pre-shared key may be incorrect wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=0 ssid="Section3" auth_failures=1 duration=10 reason=WRONG_KEY wlan0: CTRL-EVENT-REGDOM-CHANGE init=CORE type=WORLD wlan0: CTRL-EVENT-REGDOM-CHANGE init=USER type=COUNTRY alpha2=US wlan0: CTRL-EVENT-SSID-REENABLED id=0 ssid="Section3" wlan0: Trying to associate with 90:de:d0:d0:f1:98 (SSID='Section3' freq=2462 MHz) Sep 3 20:27:31 raspberrypi kernel: [ 2060.046763] brcmfmac: brcmf_cfg80211_escan: Connecting: status (3) Sep 3 20:27:31 raspberrypi kernel: [ 2060.046777] brcmfmac: brcmf_cfg80211_scan: scan error (-11) Sep 3 20:27:31 raspberrypi kernel: [ 2060.046763] brcmfmac: brcmf_cfg80211_escan: Connecting: status (3) Sep 3 20:27:31 raspberrypi kernel: [ 2060.046777] brcmfmac: brcmf_cfg80211_scan: scan error (-11) ``` Alles noch mal überprüft. Die SSID stimmt, PSK stimmt, PSK auch noch mal verschlüsselt hinterlegt. Keine Verbindung mit dem WiFi möglich. Dann mal checken ob vielleicht noch etwas läuft: ``` #> ps fauxww | grep wpa root 468 0.0 0.3 10156 2864 ? Ss 19:53 0:02 wpa_supplicant -B -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext root 2454 0.0 0.0 4372 572 pts/0 S+ 20:38 0:00 \_ grep wpa ``` Ah, ok. Da ist noch ein alter wpa_supplicant gestartet, der den Treiber falsch hatte. Der wird dann einfach mal gekillt. ``` # > kill -9 468 ``` Check ob er wirklich beendet ist: ``` # > ps fauxww | grep wpa root 2456 0.0 0.0 4372 564 pts/0 S+ 20:38 0:00 \_ grep wpa ``` Alles klar und jetzt wpa_supplicant noch mal starten. Und siehe da, jetzt klappt es. ``` # > wpa_supplicant -i wlan0 -Dnl80211 -c /etc/wpa_supplicant/wpa_supplicant.conf Successfully initialized wpa_supplicant wlan0: Trying to associate with 90:de:d0:d0:f1:98 (SSID='Section3' freq=2462 MHz) wlan0: Associated with 90:de:d0:d0:f1:98 wlan0: WPA: Key negotiation completed with 90:de:d0:d0:f1:98 [PTK=CCMP GTK=TKIP] wlan0: CTRL-EVENT-CONNECTED - Connection to 90:de:d0:d0:f1:98 completed [id=0 id_str=] Sep 3 20:38:28 raspberrypi kernel: [ 2717.234843] IPv6: ADDRCONF(NETDEV_CHANGE): wlan0: link becomes ready Sep 3 20:38:28 raspberrypi dhcpcd[414]: wlan0: carrier acquired Sep 3 20:38:28 raspberrypi dhcpcd[414]: wlan0: IAID eb:87:71:dc Sep 3 20:38:28 raspberrypi dhcpcd[414]: wlan0: adding address fe80::997b:1be8:e6e0:f5c8 Sep 3 20:38:28 raspberrypi dhcpcd[414]: wlan0: soliciting a DHCP lease Sep 3 20:38:29 raspberrypi dhcpcd[414]: wlan0: soliciting an IPv6 router Sep 3 20:38:29 raspberrypi dhcpcd[414]: wlan0: offered 10.0.2.125 from 10.0.2.1 Sep 3 20:38:29 raspberrypi dhcpcd[414]: wlan0: probing address 10.0.2.125/24 Sep 3 20:38:30 raspberrypi avahi-daemon[318]: Joining mDNS multicast group on interface wlan0.IPv6 with address fe80::997b:1be8:e6e0:f5c8. Sep 3 20:38:30 raspberrypi avahi-daemon[318]: New relevant interface wlan0.IPv6 for mDNS. Sep 3 20:38:30 raspberrypi avahi-daemon[318]: Registering new address record for fe80::997b:1be8:e6e0:f5c8 on wlan0.*. Sep 3 20:38:34 raspberrypi dhcpcd[414]: wlan0: leased 10.0.2.125 for 3600 seconds Sep 3 20:38:34 raspberrypi avahi-daemon[318]: Joining mDNS multicast group on interface wlan0.IPv4 with address 10.0.2.125. Sep 3 20:38:34 raspberrypi avahi-daemon[318]: New relevant interface wlan0.IPv4 for mDNS. Sep 3 20:38:34 raspberrypi dhcpcd[414]: wlan0: adding route to 10.0.2.0/24 Sep 3 20:38:34 raspberrypi avahi-daemon[318]: Registering new address record for 10.0.2.125 on wlan0.IPv4. Sep 3 20:38:34 raspberrypi dhcpcd[414]: wlan0: adding default route via 10.0.2.1 ``` Damit das ganze auch noch nach dem Start funktioniert auch noch systemd konfiguriert. `vim /lib/systemd/system/wpa_supplicant@wlan0.service` ``` [Unit] Description=WPA-Supplicant-Daemon (wlan0) Requires=sys-subsystem-net-devices-wlan0.device BindsTo=sys-subsystem-net-devices-wlan0.device After=sys-subsystem-net-devices-wlan0.device Before=network.target Wants=network.target [Service] Type=simple RemainAfterExit=yes ExecStart=/sbin/wpa_supplicant -qq -c/etc/wpa_supplicant/wpa_supplicant.conf -Dnl80211 -iwlan0 Restart=on-failure [Install] Alias=multi-user.target.wants/wpa_supplicant@wlan0.service ``` Und anschliessend aktivieren: ``` # > systemctl daemon-reload # > systemctl enable wpa_supplicant@wlan0.service # > systemctl start wpa_supplicant@wlan0.service # > reboot ``` Das Netzwerkkabel entfernt und der RaspberryPi kam wieder über WiFi online. Per SSH eingeloggt und den Rest gemacht. User pi deaktiviert, neuen User angelegt und weitere Software installiert. Das was man halt so üblicherweise macht um den PI Safe und fertig zu bekommen. Um das WiFi Password nicht im Klartext in der Konfiguration zu haben: ``` # > wpa_passphrase "WLAN-NAME" "123412341234" | grep -v "#" network={ ssid="WLAN-NAME" psk=c6e5b342b0bc6fe1aff18ee420ee5adbed3e8bfa1a7d5da9a7c7585fe0446fc0 } # also: # > wpa_passphrase "WLAN-NAME" "123412341234" | grep -v "#" >> \ /etc/wpa_supplicant/wpa_supplicant.conf ``` Wer WiFi schon bei der Headless Installation konfigurieren möchte lege die Datei `wpa_supplicant.conf` einfach in das `/boot` Verzeichnis der SD-Card vor dem ersten Start. ``` ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev ap_scan=1 update_config=1 country=DE network={ ssid="WLAN-NAME" psk=c6e5b342b0bc6fe1aff18ee420ee5adbed3e8bfa1a7d5da9a7c7585fe0446fc0 } ``` Dann ist WiFi auch nach der Installation sofort fertig konfiguriert. # Power Management ausschalten Da der RaspberryPi das WiFi Interface nicht abschalten soll muss noch das Power-Management ausgeschaltet werden. ``` # > iwconfig wlan0 | grep Power Bit Rate=135 Mb/s Tx-Power=31 dBm Power Management:on ``` Dazu in der Datei `/etc/rc.local` einfach vor dem `exit 0;` folgendes hinzufügen: ``` iwconfig wlan0 power off ``` Nach dem Reboot sollte auch das Power-Management ausgeschaltet sein: ``` > # iwconfig wlan0 | grep Power Bit Rate=135 Mb/s Tx-Power=31 dBm Power Management:off ``` --- ## Caddyserver als nginx Alternative - URL: https://blog.kuepper.nrw/posts/2019-08-29-caddyserver-als-nginx-alternative/ - Datum: 2019-08-29 - Aktualisiert: 2019-08-29 - Autor: ruediger - Tags: caddy, webserver, nginx, alternative - Kategorien: Internet Ich benutze sehr gerne den Nginx Webserver und das wird auch so bleiben. Aber für keine Projekte habe ich jetzt einen Webserver gefunden der einfach und schnell benutzt werden kann. Die rede ist vom [Caddyserver](https://caddyserver.com). Das nette ist: * Konfigfile mit 3 Zeilen und läuft * Läuft auf allen Plattformen * Macht auf wunsch auch Letsencrypt SSL Certs On-The-Fly Gerade für Testseiten bietet sich der kleine Webserver an. Er kann lokal einfach gestartet werden und man kann seine Seiten testen. Auf der [Downloadseite](https://caddyserver.com/download) kann der Caddyserver für alle Plattformen heruntergeladen werden. Dabei kann man auch PlugIns für sehr viele DNS Anbieter hinzufügen, um DNS Einträge für das generieren der SSL Certs zu erstellen. Die Liste enthält alle möglichen Anbieter wie Cloudflare, Route53 (AWS), Azure, DYN usw. In der PlugIn Liste sind sehr viele Plugins z.B. Proxy, HTTP-Auth, Geo-IP, IP-Filter und viele andere mehr. Für Docker gibt es ein fertiges [Image](https://hub.docker.com/r/abiosoft/caddy/). Das configfile ist wie gesagt sehr simple: ``` test1.homepage.net browse tls off ``` Möchte man PHP-FPM benutzen reichen 2 weitere Zeilen: ``` fastcgi / 127.0.0.1:9000 php # php variant on startup php-fpm7 # php variant only ``` Die zweite Zeile kümmert sich sogar gleich darum das php-fpm7 vor dem Start des Webserver mit gestartet wird. Hier kann man beliebige so genannte `RUN COMMANDS` ausführen um Dienste oder Befehle auszuführen die vor dem Start benötigt werden. Ich werde den Webserver in nächster Zeit noch weiter testen. Hauptsächlich erst einmal für Testumgebungen. Mal sehen wie er sich so macht. --- ## WiFi Hotspot und Hugo Grid Theme - URL: https://blog.kuepper.nrw/posts/2019-08-18-wifi-hotspot-und-hugo-grid-theme/ - Datum: 2019-08-28 - Aktualisiert: 2019-08-28 - Autor: ruediger - Tags: Router, Hotspot, Chillispot, Hugo, CMS, Theme, Grid, Responsive - Kategorien: Internet # EdgeRouterX und Chillispot Captive Portal Auf dem EdgeRouterX [^1] habe ich zum testen die Tage auch einmal [Chillispot Captive Portal](http://www.chillispot.org) installiert. Auf dem WiFi AccessPoint eine neue SSID mit neuem VLAN angelegt, SwitchPort getagged und auf dem EdgeRouterX das VLAN angelegt mit den passenden Config für Chillispot. Für die Authentifizierung habe ich [Hotspotsystem.com](https://www.hotspotsystem.com) genommen. Die Einrichtung und das aktivieren ist recht simple. Und wenn man neben dem Gast WiFi noch ein Pay-WiFi anbieten möchte kann man das damit sehr gut umsetzen. # Hugo CMS Grid Theme Zwischen dem ganzen Netzwerkkram [^2], [^3], [^4], [^5] die letzten Tage habe ich mich mal an ein eigenes Theme für dieses Blog gemacht. Das ist nicht so mein Ding. Nicht so wie Netzwerk und Serverkram. Aber so ein HTML5 Grid werde ich ja wohl hinbekommen. Hauptsächlich sollte es immer auf mobilen Geräten immer passen. Keine Bilder die zu groß sind und Dinge, wie Sidebar sollen einfach wo anders hin verschwinden wo sie erst einmal nicht stören. Oder manche Sachen in der mobilen Ansicht einfach komplett verschwinden. Was auf mobile passt, das passt auch auf dem Desktop. Also Mobile-First. An ein paar Ecken muss ich noch ran. Aber für einen Tag HTML- und CSS-Session schon mal nicht schlecht. Jetzt noch mal in Ruhe etwas aufräumen und dann weiter verbessern und verschönern. [^1]: EdgeRouterX [Ubiquiti](https://www.ui.com/edgemax/edgerouter-x-sfp/) [^2]: [Netzwerk Umbaulobar](https://blog.kuepper.nrw/2019/08/09/netzwerk-umbaulabor/) [^3]: [Netzwerkumbau VLANs und Firewall](https://blog.kuepper.nrw/2019/08/09/netzwerkumbau-vlans-und-firewall/) [^4]: [VLANs und Firewall](https://blog.kuepper.nrw/2019/08/12/vlans-und-firewall/) [^5]: [Networkserver of Death](https://blog.kuepper.nrw/2019/08/18/networkserver-of-death/) --- ## tmux-xpanes - Ultimate terminal divider powered by tmux - URL: https://blog.kuepper.nrw/posts/2019-08-28-tmux-xpanes-ultimate-terminal-divider-powered-by-tmux/ - Datum: 2019-08-28 - Aktualisiert: 2019-08-28 - Autor: ruediger - Tags: Terminal, tmux, tmux-xpanes - Kategorien: Internet Als ich noch eine Linux Workstation hatte, habe ich eine lange Zeit [Terminator](https://gnometerminator.blogspot.com) benutzt. Bei diesem Terminal-Multiplexer kann man sehr gut und schnell per Shortcut aktivieren das Befehle in allen Fenstern ausgeführt werden. Nach dem Wechsel auf den Mac habe ich immer eine brauchbare Alternative gesucht. Jetzt bin ich über [tmux-xpanes](https://github.com/greymd/tmux-xpanes) gestolpert. ![xpanes Ultimate terminal divider powered by tmux](https://s3fs.kuepper.nrw/blog-images/posts/2019/08/28/movie_v4.gif) Mit [csshx](http://macappstore.org/csshx/) bin ich nie richtig warm geworden. Bei [iterm2](https://iterm2.com) den ich benutze geht das auch mit `send input to all tabs` . Aber irgendwie war das auch immer nicht so super. Mit [tmux](https://github.com/tmux/tmux) kann man das aktivieren mit `:setw synchronize-panes` und wieder ausschalten mit `:setw synchronize-panes off` [tmux-xpanes](https://github.com/greymd/tmux-xpanes) macht es aber irgend wie schicker. Alleine schon der Aufruf ist schon mal sehr cool. ``` xpanes --log=~/log --ssh user1@host1 user2@host2 user2@host3 docker ps -q | xpanes -s -c "docker exec -it {} sh" ``` Weitere Beispiele sind auf der Github Seite von [tmux-xpanes](https://github.com/greymd/tmux-xpanes) beschrieben. # Installieren Mac: ``` brew install tmux-xpanes ``` CentOS, RHEL: ``` yum install \ https://github.com/greymd/tmux-xpanes/releases/download/v4.1.1/tmux-xpanes_v4.1.1.rpm ``` Ubuntu/Debian: ``` sudo apt install software-properties-common sudo add-apt-repository ppa:greymd/tmux-xpanes sudo apt update sudo apt install tmux-xpanes ``` --- ## Blog Theme noch einmal angepasst - URL: https://blog.kuepper.nrw/posts/2019-08-20-blog-theme-noch-einmal-angepasst/ - Datum: 2019-08-20 - Aktualisiert: 2019-08-20 - Autor: ruediger - Tags: Hugo, CMS, Theme - Kategorien: Technik Auch heute wurde noch etwas weiter am Theme geschraubt. Es waren noch ein paar Baustellen was die Darstellung von Bildern betrifft. Jetzt passt erst einmal alles wie gewünscht. Und gerade wurde auch ein großer Test mit Apple iPhones (4s bis XR), iPads (Alle von 1er bis Pro), Samsumg Phones und Tables (Alles mögliche), LG, Nexus (6,9), HTC One, Sony, Kindle usw. Eigentlich alles was man so draussen erwarten kann. Passt alles und die nächste Zeit jetzt nur noch Kleinigkeiten verbessern. Da wären z.B. die Codeblöcke. Die gefallen mir noch nicht so gut, da ich aktuell keinen Hintergrund setzen kann. Code der auf der Seite angezeigt wird passt von der Breite zwar jetzt, aber die Box im Hintergrund geht nur ca. 60-70 der verfügbaren Breite. Sieht dann halt doof aus wenn der Code dann drüber hinaus geht. Bilder waren heute mittag noch mal nervig. Alles hat gepasst und ein Ende war in Sicht. Bis ich dann auch wieder alle anderen Browser mit getestet habe. Und der Firefox hat alles komplett ~~beschis~~ zerschossen angezeigt. Die Bilder liegen jetzt in einem CDN und können da auch schnell deployed werden. Die fliegen auch noch aus dem Hugo Ordner raus. Die kommen ja erst später in die Seite. Das Theme und der restliche Content sind im Git auch schon getrennt, so das alles einzeln angefasst werden kann und erst in der Gitlab Pipeline wird alles zusammen gesteckt und die Seite generiert. Für die Bilder habe ich jetzt ein kleines Script was die Bilder verkleinert. Da wird noch was verbessert. Da jetzt das Theme fertig ist kann ich auch mal geziehlt mit den Grössen gucken. Die Bilder dann damit auch in die passende Grösse automatisch anpassen und ins CDN deployen, fertig. --- ## Networkserver of Death - URL: https://blog.kuepper.nrw/posts/2019-08-18-networkserver-of-death/ - Datum: 2019-08-18 - Aktualisiert: 2019-08-18 - Autor: ruediger - Tags: Storage, NAS, Router, UPnP, AVM, Fritzbox - Kategorien: Technik # Server immer wieder von extern nicht mehr erreichbar Für das Projekt [Essenz - Rock Dein Block](https://www.beone-projects.com/projekte/essenz/ "Rock Dein Block Projekseite") haben wir hier auf einem Synology NAS mehrere Dienste laufen damit sich die Coaches und Teilnehmer austauschen können. Es gab immer mal wieder Probleme mit dem Zugriffen auf das NAS. Und das Monitoring (Icinga) hat auch immer wieder alamiert. Nach dem umfangreichem Netzwerkumbau in den letzten Tagen hatte ich gestern Abend teilweise die Änderungen am Netz als Ursache nicht mehr ausschliessen können. Vorteil der Änderungen war aber ,durch die strickte Trennung mit VLANs und den Routern zwischen den Netzen, kann man jetzt gezielt nach Fehlern suchen. Es ist nicht mehr alles eine große Suppe IP Traffic von über 90 Geräten. Das ganze könnte aber auch die Ursache des ganzen Übels sein. Daher habe ich heute die komplette Netzwerkkonfiguration noch mal auf links gedreht. Auch weil, immer wenn das Problem aufgetaucht ist, der Traffik zwischen den Netzen und Switchen auf einmal massiv angestiegen ist. So stark das ich das eigentiche Problem da noch gar nicht auf dem Schirm hatte. Dazu war der Traffic auf den Ports viel zu hoch, als das man auf UPnP kommen könnte. Der Traffik blieb selbst wenn dann das Portforwarding komplett ausgestiegen ist und sogar aus den Routern auf einmal verschwunden ist. Manuell gesetzt, Problem taucht auf, Portforwarding nicht mehr in der Konfiguration. Und selbst dann blieb der Traffic intern weiter sehr hoch. Da geht man von einem Problem in der Netzwerkkonfiguration oder an den Geräten im Netz aus. # Das NAS ist im Netzwerk noch mal umgezogen Um einige Sachen ausschliessen zu können ist das NAS noch mal umgezogen. Also es blieb da stehen wo es ist, aber es wurden an den Switchen ein paar Ports umkonfiguriert, so das es so gesehen wieder direkt hinter den Routern angeordnet war. Das Problem war ... immer noch vorhanden. Um das Problem an einem der Switche auszuschliessen wurde ein frischer Ubiquiti EdgeRouterX SPF aus dem Karton genommen und das NAS da angeschlossen. Da zum Switch, an dem das NAS eigentlich hängt, 4 Leitungen als LAG aus der anderen Etage kommen um 4 GigaBit/s hochschieben zu können, einfach eine davon gezogen und auch an den EdgeRouterX geklemmt. Die andere Seite unten vom Switch auch gezogen und direkt auf den Router zum Internet geklemmt. > Internetrouter -> EdgeRouterX -> NAS Portforwarding konfiguriert und getestet. Online war das NAS wieder auch wieder ohne Probleme. So bald wieder Requests rein kommen ist das Portforwarding weg und auf dem EdgeRouterX sieht man massig Traffik. Also alles andere im Netzwerk kann jetzt schon einmal ausgeschlossen werden. Aber woher kommt es und was ist die Ursache? # DNS gändert und Ruhe Nach langem Suchen nach der Ursache und nicht wirklich der Ursache näher kommend noch mal überlegt was nicht ausgeschlossen wurde. Das war zu dem Zeitpunkt noch der Zugriff von außen auf das NAS. Die Domain dafür wird bestimmt schon bei einigen BadBoys SkriptKiddies rumgeistern. Also Attacken kann man nicht ausschliessen. Also DNS Record löschen und auf `127.0.0.1` setzen. ``` dnsmngt -d -z domain.de -s subdomain dnsmngt -a -z domain.de -s subdomain -i 127.0.0.1 ``` Danach war auch erst einmal alles ruhig und es gab auch keine Probleme mehr im Netzwerk. Das ganze wieder zurück und den DNS Record auf den alten Wert setzen. Kurze Zeit später war das Problem wieder da. Portforwarding zeigt den Server dahinter wieder als Offline an. Netzwerktraffik ging wieder hoch. Es besteht weiter. Jetzt eben eine neue Subdomain anlegen und damit testen. ``` dnsmngt -a -z domain.de -s testsubdomain -i 111.222.333.444 ``` Portforwarding wieder konfiguriert. Aufgerufen und es ist alles damit online. ein wenig getestet und auch jetzt wieder alles weg. Traffik ... ihr wisst schon. Bber die neue Subdomain kann keiner kennen. Dazu ist sie noch zu frisch. Es muss also noch etwas anderes sein. Um zu prüfen ob die Fritzbox vielleicht mal wieder eine kaputte Konfiguration hat musste sie ausgeschlossen werden. Weil die Box Resetten, komplett auf Werkseinstellung und neu einrichten wäre nicht so der Aufwand. Aber wieso wenn es nicht nötig ist. # Weg über den 2. Anschluss testen Wir haben ja zwei Anschlüsse und daher auch zwei Fritzboxen. Auf der anderen Fritzbox das Portforwarding eingerichtet. Andere Fritzbox, anderer Anschluss. Wenn es da auch so ist, dann ist es nicht die Fritzbox. Damit das ganze überhaupt funktioniert musste das NAS einfach nur als Standard Gateway die andere Fritzbox IP bekommen. Parallel wurden ein paar Geräte in anderen Etage dazu angehalten den anderen Anschluss zu benutzen. So wurden alle anderen Geräte nicht gestört und über die Leitung ging nur noch das NAS online. Default Routen werden hier auf den Routern eh immer beide gesetzt. Aber so das immer das andere Gateway als Fallback dient wenn ein Anschluss gestört ist. So kann man auch mal eine Fritzbox vom Netz nehmen, ohne das groß jemand etwas merkt. NAS online, alles ok. Doch auch da dann wieder Probleme. Da sogar so heftig, das die Fritzbox sogar komplett jedesmal das DSL verloren hat. Das ganze konnte ich 5 mal reproduzieren. # UPnP noch mal gechecked In den Fritzboxen noch einmal überprüft ob das UPnP aus ist. Bei dem Gerät war es auch nicht an. Das wurde jetzt schon zig mal überprüft, aber trotzdem noch mal gucken kann ja nicht schaden. Auf dem NAS sollte es auch nicht an sein, das wird nicht benutzt und soll auch nicht aktiv sein. Aber ein Blick in die Config zeigt: da ist etwas konfiguriert ist. Also noch mal checken ob das auch aktiv ist. Und siehe da, der Mist ist da aktiv. Der ganze Aufriss, auf den Cisco Switchen alles möglich checken und tracen, auf den anderen Switche, auf dem EdgeRoutern, auf den AccessPoints, auf allen möglichen Geräten, weil das komplette Netzwerk auf einmal Traffic machte, der weder von innen nach draussen, noch von aussen nach drinnen so massiv kommen konnte. Tcpdump, traceroutes, mtr usw. um das Problem zu finden. Und dann ist es dieses fucking UPnP. Portforwarding aktiviert, NAS wieder online. Also Tcpdump noch mal gezielt an einigen Punkten im Netzwerk auf UPnP Zeug angesetzt. ``` tcpdump -i eth0 udp and port 1900 and dst 239.255.255.250 -s0 -w UPnP.pcap ``` Das Netzwerk war die ganze Zeit ruhig. Bis man die App am Smartphone aus dem mobilen Netz aufruft und Daten vom NAS abruft und kopiert. Einfach auf das iPhone kopieren oder selbst auf dem NAS an einen anderen Ort kopieren. In dem Moment brach jedesmal die Multicast Hölle aus. Das NAS hat jedesmal die Router zugebommt mit Multicast Paketen bis die eine das Portforwarding eingestellt hatte. Oder bei der anderen ja sogar so weit das DSL komplett ausgestiegen ist. Der Router musste jedesmal DSL neu synchronisieren. # WTF UPnP für internen Kram wie Media Server, Media Printer, Netzlaufwerke usw. ist ja toll und schön. Das benutzen wir hier mit einigen Geräten auch. Der Octagon z.B. stellt Streaming, Mediaplayer für Fotos und Videos damit bereit. Aber an den Router soll so ein Mist bitte nicht. Das wurde 2013 schon sehr gut gezeigt. Als man ca. 50 Millionen Geräte im Netz gefunden hatte, die darüber von aussen übernommen werden konnten. Das, also UPnP, ist wieder so ein tolles Beispiel für: `Dem Benutzer alles abnehmen, weil es so für ihn einfacher ist.` Ja einfach, aber macht es nur schlimmer. Die Leute stellen sich Geräte zuhause hin die dann mal eben so das komplette Leben (Daten auf den Geräten) frei ins Internet stellen. Weil irgend ein Gerät meint dem Router über UPnP zu sagen: `Mach mal Port auf und schick alle rein zu mir, ich habe die Daten.` > Ja, kannste schon so machen, ist dann halt kacke! Wieso versucht man den Leuten immer alles abzunehmen? Wer ein Geräte hinter seinem "sicheren" Internetanschluss freigeben möchte, der soll das gefälligst selbst einrichten. Und ja dazu gehört auch: `Beschäftige Dich mit dem Scheiss`. Man sollte zumindest ein wenig verstehen was man da macht. Vor allem was man da mit seinen Daten im schlimmsten Fall machen. Oder man macht sich wenigstens kurz Gedanken darüber ob man das auch wirklich so will und nicht besser noch andere Sicherheitsmassnahmen dazu schaltet. # Aber auch die Hersteller könnten mal ... 1. Wieso ballert da eine Synology einfach so massiv Multicast ins Netzwerk und zwar so das man an alles mögliche denkt. Nur nicht an UPnP. Leute, wir haben hier zwischen den Wohnungen jeweils 2x1 GB im LAG. Und wir hier in unserer Wohnung nach oben ins Büro 4x1 GB im LAG. Und der UPnP-Traffik war zwischen dem ganzen Streaming und Backups die in der Zeit liefen zu sehen. Also es war nicht der UPnP-Traffik selbst zusehen. Danach wurde ja noch nicht geziehlt geguckt. Der Traffik war halt so viel das er in den Graphen und Statistiken nur auffallen konnte. Und wenn etwas, wie in dem Fall UPnP, nicht funktioniert, wo war da eine Mail oder eine Meldung in der Oberfläche? Jeder Mist wird einem zig mal benachrichtigt, aber das nicht? Aber dafür das Netz mal komplett zu müllen. Ausserdem kann man einen Service auch sagen er soll ruhig sein wenn etwas nicht klappt und nicht einfach noch lauter ins Netz brüllen lassen. Meinem Sohn sage ich auch: > Es bringt nichts wenn Du lauter wirst, dadurch höre ich Dir nicht besser zu wenn ich Dir nicht gerade zuhören kann. Warte kurz und sei bitte kurz leise. Und ja, wenn er dann nicht hört bekommt auch mein Sohn ein `FIN-RST` von mir und darf sich erst einmal abkühlen gehen. 2. AVM macht ja eigentlich immer einen guten Job. Aber UPnP deaktiveren haben sie ja jetzt so gut versteckt und ist auch nicht mehr so gut zu erkennen ob es aktiviert ist oder nicht. Das könnte bitte besser sein. Und leider kann man auch nie gut erkennen, was, wann, wie schief läuft oder gelaufen ist. Man kann auch nicht so gut auf den Dingern gucken was in so einer Situation gerade passiert. Da wären ein wenig mehr Infos schon nicht schlecht. Ausserdem wäre eine Funktion in der Fritzbox nicht schlecht, die einfach mal sagt: `Device XY, du kommst hier gerade mit ein paar tausend UPnP Zeug rein, das ist hier nicht, geh sterben` und dann strickt wegblockt. Fertig. Dazu dann noch in der Oberfläche eine Meldung, das da gerade ein Device scheisse baut. # UPnP wird jetzt weggeblockt Nachdem jetzt alles wieder zurück gebaut wurde. Also eher zurück-zurück ;-). Wird jetzt die Firewall zwischen den VLANS und zu den Routern noch um ein UPnP DROP erweitert. Das ein Gerät meint es darf sich hier alles erlauben soll ja nicht noch einmal vorkommen. Ausserdem will ich sehen wenn so etwas noch einmal passiert und dann auch benachrichtigt werden. UPnP vom Synology aus sollte eigentlich ausgeschaltet gewesen sein. Ich kann mir auch nicht vorstellen das irgend ein Paket, was installiert wurde, das selbstständig gemacht hat. Das ist eigentlich nicht Synology-Style Das jemand beim Einrichten eines Services gedacht hat man muss das in den Einstellungen auch noch unter "Routerkonfigurieren" aktivieren, will ich nicht ausschliessen. Ist schon heftig, wenn so etwas solche Auswirkungen hat. Und wird so auch nicht mehr passieren. Dafür wird es schöne Rules in den Firewall geben. --- ## Octagon SF4008 Receiver - URL: https://blog.kuepper.nrw/posts/2019-08-13-Octagon-SF4008-Receiver/ - Datum: 2019-08-13 - Aktualisiert: 2019-08-13 - Autor: ruediger - Tags: Octagon, Satelit, Kabel, DVB-T, Receiver, DVB-S2, DVB-C, TV - Kategorien: Technik # Unser Octagon Receiver ![Octagon SF4008 Receiver](https://s3fs.kuepper.nrw/blog-images/posts/sf4008.webp) Wir haben seit über einem Jahr einen Ocatgon SF4008 Receiver an der Sat-Anlage. Da ich immer wieder Aussagen oder Fragen höre wie: * "Ach, das kann der Receiver?" * "Wie, das machst Du auch damit?" * uva. werde ich mal versuchen zusammen zufassen wieso der Octagon Receiver ausgewählt wurde. Auswahlkriterien waren: * Preis * Sat und/oder Kabel und/oder DVB-T * Aufnahmemöglichkeit auf Festplatte * Alternativ oder parallel Aufnahme auf einem Netzwerklaufwerk * Individuell anpassbar (installierte Funktionen/Oberfläche) * Den HD+-Dreck brauchen wir nicht (wäre aber möglich) * Auch noch Updates wenn das Gerät älter ist Die Liste wurde im Kopf noch viel länger was die gewünschten Funktionen waren. Um das schon mal vorweg zu nehmen. Diese Liste im Kopf konnte der Ocatgon eigentlich komplett erfüllen. # Preis und Zukunft Der Octagon SF4008, andere Modelle oder auch von anderen Herstellern, die in diese Richtung gehen, sind auf dem ersten Blick für viele teuer. Aber ganz wichtig ist dabei zu beachten das es eben kein MediaMarkt 49,- € Receiver ist, sondern schon einiges mehr kann, besser gessagt zu einem Gerät gemacht werden kann was alles mögliche machen kann. Wir setzen ihn aktuell nur für Sat TV ein. Sollten die Kabel-TV Anbieter endlich mal ihren Schrott besser machen könnten wir aber auch schnell um Kabel erweitern. Das wäre nur eine weitere Karte in den freien Slot stecken, Kabel rein und fertig. Die aktuelle Karte im Slot 1 kann Dual Sat (DVB-S2), heisst zwei Kabel von der SChüssel bzw. der Weiche können einfach in das Gerät gesteckt werden und Bild-in-Bild oder Aufnahme und parallel gucken sind kein Problem. Würden wir uns mal für DVB-T entscheiden könnten wir aber auch eine Karte für DVB-T reinstecken. Vorteil: Egal was die nächsten Jahre passiert: Wir müssen kein neues Gerät kaufen und auch nicht viel neu einrichten, nur weil sich der Anschluß / die Quelle des TV Programms ändert. # Unser Octagon ist immer noch ohne Festplatte Es liegen zwar noch genug Festplatten herum die aus anderen Geräten noch übrig waren. Trotzdem hat der Octagon immer noch keine Festplatte eingebaut. Der Octagon ist hier im Netzwerk per Kabel angeschlossen und hat auch Zugriff auf mehrere Freigaben auf einem Synology NAS. Der Ocatagon kann z.B. auf ein Homeverzeichnis von einem User zugreifen, auf das ich auch von meinem Rechner schnell Zugriff habe und kann Daten für den Ocatgon dort ablegen oder Aufnahmen abrufen. Aufnahmen kann man so auch auf den Rechner laden und sie unterwegs gucken. Oder wir kopieren Fotos und Videos auf das NAS, um sie anderen auf dem großen TV zu zeigen. Ja, das könnten wir auch vom iPhone, iPad oder Rechner auch direkt über den AppleTV machen. Oder dem FireTV. Aber wir möchten das auch mal von unterwegs aus machen können. Oder wir lassen über Streaming ein oder mehrere Videos auf mehreren Geräten gleichzeitig abspielen. Zig Leute zu besuch und alle hängen um ein kleines Gerät und veruchen zu gucken? Nö, hier kann jeder einfach auf seinem Gerät mitgucken. Das ganze funktioniert auch mit dem TV-Programm. Sogar von unterwegs. Möchte man etwas nicht unbedingt am Fernseher gucken, sondern einen Bericht/Doku im Sommer auf dem Balkon oder unterwegs Live gucken. Auch kein Problem, einfach streamen. Das geht auch parellel zum TV gucken. Brauchen wir auch nicht so oft, aber immer wenn wir mal was im TV gucken und jemand aus der Family kurz nach oben kommt, schellt und mal eben was durch den Dokumenten-Scanner schicken will, weil er es per Mail versenden möchte wird es gerne benutzt. iPhone oder iPad in die Hand, Streaming des aktuellen Senders auf das iPad und so nichts verpassen. Die anderen können ja auch ganz normal am TV weitergucken. # Anpassen wie es einem gefällt Beim anpassen von Funktionen, Oberfläche oder gar Senderlisten ist alles mögliche machbar. Alleine schon das Senderlisten anpassen war wichtig. Wer schon mal Unitymedia / KabelBW hatte und da alle paar Monate eine neue Senderliste untergejubelt bekommen hat, der wird wissen wie es dazu kommt das man für den Anbieter nur noch hass empfinden kann. Stundenlanges hin- und herschieben von Sendern mit einer sehr schlechten Bedieneroberfläche. Beim Octagon kann man das über eine viel bessere Oberfläche viel schneller erledigen. Oder man lädt sich die Aktuelleliste per Sat oder Kabel, je nach Anschluss vom Anbieter. Dann lädt man eine Datei vom Ocatgon herunter. Diese Datei öffnet man in einem Texteditor. Schiebt alles so zurecht wie man die Sender haben möchte und kopiert die Datei wieder auf den Ocatgon. Die Datei wird eingelesen und schon ist die Senderliste wie gewünscht. Wer jetzt nicht unbedingt immer neue Sender unbedingt sofort haben muss, der kann sich ein paat Zeilen Script schreiben, welche die gewünscht Senderliste aus einer neuen Anbieterliste zusammenbaut und alle restlichen neuen Sender einfach ans Ende packt. Die kann man dann auch schnell über das Webinterface verschieben. Eine andere Möglichkeit ist eine Liste von jemanden anderem zu benutzen. Das mache ich bei unserem Ocatgon so. Ich habe die Liste seit der Ersteinrichtung nicht mehr angepackt. Der hat eine Webadresse zu einer Liste bekommen und überprüft regelmässig ob sich etwas an den Sendern verändert hat. Die neue Liste wird dann heruntergeladen und installiert. Da kümmert sich jemand anderes drum und der macht es richtig gut. Wir haben noch nie mitbekommen das ein Sender weg ist weil auf einem Transponder sich eteas geändert hat. Da müsste ich mal nachgucken wer das ist, mich mal bedanken und ggf. mal mit einer Kleinigkeit erkenntlich zeigen. Der Typ macht das als Hobby und in einer Qualität, Top Arbeit. Auch die Oberfläche ist anpassbar und erweiterbar bis ins kleinste Detail. Es gibt zig vorgefertigte Designs, die per Paketverwaltung einfach installiert werden. Aktivieren und gucken ob es gefällt. Man kann aber auch einfach noch ein paar andere installieren und zwischen den Oberflächen wechseln. Gerade am Anfang sehr nett um zusehen was man eigentlich wie haben will. # Bildergalerie von der Oberfläche ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-24-23.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-24-34.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-24-53.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-25-01.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-25-38.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-25-50.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-25-57.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-26-55.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-29-31.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-29-44.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-31-23.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-31-50.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-32-01.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-32-09.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-32-35.webp) ![](https://s3fs.kuepper.nrw/blog-images/galleries/2019/08/Octagon-SF4008-Receiver/screen-19-33-40.webp) Für die Bilder habe ich einfach ein paar Sender, Menüs und Plugins aufgerufen und dann am Rechner einen einfachen Befehl ausgeführt. ``` curl -s -u Benutzer:Passwort "http://10.0.2.105/grab?format=png&r=1280&&filename=screen1.png" > screen-$(date "+%H-%M-%S").png ``` Das erstellt mir einfach einen Screenshot vom Bild was der Octagon in dem Moment auf dem TV anzeigt. Die Bilder werden mit dem Name `screen---.png` gespeichert. Dabei sind `r=1280` sind 720p und `r=1920` wären 1080p. # PlugIns für alles Es gibt zum Beispiel: * Mediacenter Fotos/Videos von der Festplatte oder Netzwerklaufwerk abspielen * Filebrowser * IMDB für Infos zu Filmen/Serien * Moviecutter (Videos/Aufnahmen schneiden) * OpenWebIf (Webinterface um per Browser auf den Ocatgon zugreifen zu können) * Youtube * ZDF-Mediathek Die Liste ist sehr lang. Komplett verfügbar sind über 4000 Pakete. Davon sind natürlich einige Linux System Tools und Librarys. Aktuell haben ich insgesamt 534 installiert. WakeOnLan, Fritzbox, Speedtest uvm. Fritzbox ist dafür installiert um Anrufe auf dem Festnetz mit einer kleinen Infobox kurz auf dem TV anzuzeigen. Kein störendes klingeln mehr wenn man eigentlich nur TV gucken will. # Wir steuern auch per Sprache Da wir auch das Webinterface installiert haben können wir per Api jeden Befehl der Fernbedienung auch von einem Rechner aus ausführen. Durch eine Einstellung wurde der Untertitel immer angezeigt. Welche Taste das ausschaltet haben wir immer vergessen. Irgend wann habe ich einfach die Videotext-Taste so belegt das der Untertitle an- bzw ausgeschaltet wurde. Das hat Linda dann aber auch immer wieder vergessen. Also habe ich die Api vom Webinterface dazu benutzt mit HTTP Requests zusammen zu bauen die das einfach über die API machen. Die HTTP Aufrufe habe ich in HomeBridge geworfen und jetzt kann man einfach den Untertitle einfach per Sprache ausschalten: > Alexa, schalte Untertitel aus. Das kann der Octagon von sich aus so nicht. Dazu ist HomeBridge und Amazon Alexa oder AppleTV mit Siri nötig. Beim schrieben denke ich gerade: > Ich will das nicht so zu 100% sagen, weil irgendwo im Netz hat jemand das bestimmt auch schon mit dem Octagon genau so gemacht.` Da hier ein HomeBridge und FHEM Server für unser SmartHome vorhanden ist macht der das einfach für uns so möglich. # Der Octagon macht vieles einfach entspannter * Sat, Kabel, DVB-T auswechselbar oder kombinierbar. * Viele Möglichkeiten zum Erweitern von Funktionen * Er wird noch sehr lange Updates bekommen. # Was der Octagon hier nie machen wird Wir werden den Octagon niemals mit einer HD+ Karte betreiben. ARD/ZDF sind eh alle HD und die privaten sind es nicht wert da Geld für mehr Pixeln einzuwerfen. Ich sage da immer: > Hätte man bei der Einführung von Farbfernsehen damals Geld verlangt wäre die Entwicklung stehen geblieben und wir hätten heute noch Schwarz/Weiß` Ausserdem kann ein Sender sehr viel einschränken wenn er möchte. Er kann jeder Zeit sagen das Aufnahmen nicht mehr oder für bestimmte Sendungen/Filme nicht mehr erkaubt ist. Oder Aufnahmen sind erlaubt, aber man darf nicht mehr vorspulen. Das sind einfach zu viele unberechenbare Einschränkungen, die einfach nur unverschämt sind. # Updates der Firmware und Oberfläche Bei den üblichen SAT-/Kabel-/DVB-T-Receivern hat man immer das Problem das sie nach ein paar Jahren kein Update mehr bekommen. Ausserdem ist das Update bei den meisten Geräten immer fast unmöglich gewesen. Die Boxen haben zwar USB, serielle Schnittstelle, aber wer hat heute noch die passenden Geräte um damit ein binary des Updates auf das Gerät zu schieben? Viele der Gerate haben zwar noch funktioniert, wurden aber in die Tonne geschmissen weil Änderungen in der Infrastruktur nicht auf den Geräten angekommem sind. Da sind Anbieter und Hersteller eigentlich in der Pflicht. Umweltschutz interessiert da wohl keinen. Und so wurden jahrelang Millionen von Geräten sinnlos in den Müll geworfen. Der Octagon hat Netzwerk per Kabel. Könnte aber auch per USB WiFi Adapter per WLAN ins heimische Netzwerk gebracht werden. Andere Modelle haben sogar WLAN direkt dabei. # Octagon mit WLAN, da geht noch mehr ;-) Wer zuhause bescheidenes WLAN hat, da der Anschluss mal wieder im Flur oder gar in einer anderen Etage ist. Einfach mal dran denken: Auf dem Octagon läuft ein Linux. Wenn der Octagon mit WiFi per Kabel ins Netzwerk gebracht werden kann: * Octagon per Kabel ins Netzwerk * WiFi vom Octagon als AccessPoint konfigurieren * Weiterleitung zwischen WLAN und Kabel aktivieren * Andere Geräte können sich per Wifi jetzt auch mit dem Ocatgon verbinden und kommen dann über den ins Netz. So viel zu den Möglichkeiten die so ein Gerät mitbringt. --- ## VLANS und Firewall - URL: https://blog.kuepper.nrw/posts/2019-08-12-vlans-und-firewall/ - Datum: 2019-08-12 - Aktualisiert: 2019-08-12 - Autor: ruediger - Tags: Netzwerk, IP, VPN, VLAN, IPSec, OpenVPN - Kategorien: Technik Wenn man das komplette Netzwerk endlich mal umkrämpelt und in den Zustand bringt den man schon lange haben wollte sind manche Fails beim Einrichten auch etwas gutes um zu sehen das alles funktioniert. Damit auch eine Kommunikation zwischen den Netzen funktioniert muss zwischen den VLANS geroutet werden. Damit dann aber nicht alles mit jedem telefonieren kann braucht es dann auch eine(n) ~~Firewall~~ Paketfilter. Gestern sind hier alle Smarthome Geräte in ein neues VLAN umgezogen. Beim einrichten wollte direkt zu Beginn der Tradfri Gateway nicht mit dem neuen Netz funktionieren. WiFi A soll VLAN 10 sein. WiFi B soll Vlan 20 sein. Wenn man aber die VLANs vertauscht und dem AP die WiFi SSID mit dem falschem VLAN konfiguriert klappt das auch nicht nicht so wie man es will. Als die 1. Etage umgezogen ist wurden auf dem Cisco Switch dort alle Ports mit dem VLAN versorgt und den UpLink das VLAN getagged. Wollte auch erst einmal nicht. Man sollte auch auf dem anderen Switch auf dem UpLink Port zur 1. Etage das VLAN mit auf den Port schmeissen. Und zack, die ersten Geräte tauchen auch schon auf. Oh ein Firmware Update verfügbar für den Switch da unten, also das auch noch direkt mit drauf auf den Switch. Dann muss der ja eh einen Restart machen und das könnte für die Geräte die noch nichts mitbekommen haben reichen um die Netzwerkports in den Geräte dazu zu veranlassen sich einmal auf down zu setzen und wenn die Switch Ports wieder hoch kommen das Netzwerk wieder up. 3 Etagen umgezogen und sauber getrennt. Fehlt nur noch eine. Die darf aber vielleicht noch 1-2 Tage warten. In der Zwischenzeit wird die Firewall noch etwas feinjustiert. Wir haben 5 Amazon Echo/EchoDot, von denen 4 ohne Probleme funktioniert haben. Nur der Echo in der Küche. Der ist einer der ersten Generation und irgend etwas können die neuen, was das alte Schätzcken nicht kann. Denn der Echo konnte heute morgen Streamen, Radio Essen und EinsLive liefen ohne Probleme. Linda wollte aber vorhin bei der Hausarbeit in der Küche Hörspiele hören. iPhone auf und mit dem Echo verbinden und es passierte nichts. Erst dachte ich, ok das iPhone ist ja noch im alten Netz, ziehste sie eben um. QRCode gescanned fertig. 30 Sekunden später steht sie wieder da: `Geht immer noch nicht.` Alle anderen Echo Devices ohne Probleme. Amazon Music App auf, Hörspiel an und Echo auswählen klappt. Nur der in der Küche nicht. Der Echo wurde gerade etwas länger bespasst: * Es wurde alles mögliche gemacht. * Er wurde ins alte Media WiFi zurück gepackt. * Das iPhone hinterher. * Echo zwischen den WiFi Netzen hin und her. * iPhone per Bluetooth mit dem Echo gekoppelt. * iPhone wieder getrennt. * Alle Geräte gelöscht. * Echo komplett zurückgesetzt. * Echo lange vom Strom. Es wurde wirklich alles ausprobiert. Das komische war es gab bei einigen der Aktionen oben aus der Liste Situationen bei denen es z.B. bei mir kurz klappte und wenn wir Lindas iPhone verbunden haben ging wieder nichts. Zwischendurch klappte auch mal kurz wieder an ihrem iPhone. Dann wieder nicht. Die ganze Situation war sehr strange, da alles was man Alexa gefragt hat, wie Wetter, Verkehr usw. klappt. Also nur alles wo Alexa Text aus dem Netz abfragt und als Sprache aus gibt. "Alexa, spiele Musik.". Kurze Zeit später sagt Alexa auch dabei sie kann die Musik nicht abspielen. Also Online ist sie, aber kann nicht alles machen. Noch mal alles gechecked, AccessPoint, Switche, Ports, VLANs, IPs, Gateway, aber alles hat gepasst. Und mit anderen Echos klappt es ja auch. Also noch mal Firewall, tcpdump und Log auf dem EdgeRouter checken. Die Default Policy hatte Log an und hat das Logfile bei der Masse an Daten schon dicht gemacht. Da war erst einmal nichts mehr zu sehen, da die Platte voll war. Wenn vorher eine Rule etwas blockt konnte man das in dem Moment nicht sehen. Ein `echo "" > /var/log/messages` und noch mal getestet. `[2OG-50-A]IN=eth0 OUT=eth1.2 MAC=78:8a:20:07:7a:94:38:10:d5:76:31:46:08:00 SRC=192.168.176.2 DST=10.0.2.115 LEN=540 TOS=0x00 PREC=0x00 TTL=62 ID=23432 PROTO=UDP SPT=53 DPT=53080 LEN=520` Ok, DNS kommt nicht durch. Das wird dann mal freigeschaltet. Weiter Logfile geguckt und noch weiter Ports und Protokolle freigegeben. Ist zwar aufwändig bis alles läuft, aber ich will das so haben. Das die anderen Echos das Problem nicht hatten werde ich mir die Tage noch genauer angucken. Alles wird geblockt, alles freigegeben was man braucht und der Rest der dann noch fehlt fällt auf wenn es nicht klappt. Es gibt Leute die meinen alles offen und dann nur das verbieten was man nicht haben will. Bei vielen Sachen weiß man aber nicht welche Protokolle und Ports es benutzt. Das kann man nicht sicher machen, weil man nicht alles kennt was irgendwann mal durchs Netz schwirrt. Die vertauschten VLANS am AccessPoint und die fehlenden Firewallfreischaltungen haben gezeigt: `Alles richtig gemacht und genau so sollte es werden`. Das fühlt sich so einfach einfach richtig an. --- ## WiFi QRCode mit qrencode erstellen - URL: https://blog.kuepper.nrw/posts/2019-08-10-WiFi-QRCode-mit-qrencode-erstellen/ - Datum: 2019-08-10 - Aktualisiert: 2019-08-10 - Autor: ruediger - Tags: QRCode, qrencode, brew, apt, WLAN, WiFi - Kategorien: Technik # QRCode für WiFi Zugänge erstellen Um ohne langes tippen in das WiFi Netz zu kommen erstelle ich immer QRCodes. Ich erstelle sie mit qrencode. ``` # Debian apt install -y qrencode # MacOS brew install qrencode ``` Anschliessend kann man sehr einfach einen WiFi QRCode erstellen: ``` qrencode -o wifi-zugang.png "WIFI:S:UnserWlan;T:WPA2;P:strengeheim12345;;" --dpi=300 -s 100 ``` ![Der QRCode für das WiFi, generiert mit qrencode](https://s3fs.kuepper.nrw/blog-images/posts/wifi-zugang.png) So kann für neue WiFi Netze schnell der Zugang weitergegeben werden. Wir benutzen das zuhause regelmässig für die WiFi-Gastzugänge, da auch da regelmässig die Passwörter geändert werden. # Kann auch Adressdaten in einen QRCode packen Addresse.txt ``` BEGIN:VCARD VERSION:3.0 N:Nachname, Vorname ORG:nachname.de TITLE:Webmaster und Author EMAIL;TYPE=PREF,INTERNET: info@nachname.de END:VCARD ``` ``` qrencode -o vcard-low.png < adresse.txt ``` ![VCard QRCode](https://s3fs.kuepper.nrw/blog-images/posts/vcard-low.png) ``` qrencode -l H -o vcard.png < adresse.txt ``` ![VCard mit mehr Fehlerkorrektur](https://s3fs.kuepper.nrw/blog-images/posts/vcard.png) --- ## Netzwerkumbau VLANs und Firewall - URL: https://blog.kuepper.nrw/posts/2019-08-09-Netzwerkumbau-VLANs-und-Firewall/ - Datum: 2019-08-09 - Aktualisiert: 2019-08-09 - Autor: ruediger - Tags: Netzwerk, Internet, TCP, IP, VLAN, Switch, Router, Routing, Security - Kategorien: Internet # Dann wollen wir mal Oder auch erst einmal nicht. Ich wusste vom letzten Test die IP vom Ubiquiti EdgeRouterX nicht mehr. Dann halt mal wieder IPv6 zur Hilfe nehmen :smirk: Ich habe ja eine Liste alle bekannten Adressen in dem Segment, also einfach `ping6` auf das interface `en0` und `ff02::1` und wir sollten die IPv6 Adresse von dem Teil haben. ``` pi01:~# ping6 -I en0 ff02::1 16 bytes from fe80::7a8a:20ff:fe07:7a94%en0, icmp_seq=17 hlim=64 time=6.148 ms 16 bytes from fe80::7a8a:20ff:fe07:7a94%en0, icmp_seq=18 hlim=64 time=11.023 ms 16 bytes from fe80::7a8a:20ff:fe07:7a94%en0, icmp_seq=19 hlim=64 time=3.578 ms ``` Bingo, wir haben die IPv6 Adresse und so auch die Mac Adresse von dem Teil. Jetzt noch die ARP Table checken und wir sollten die IP haben. ``` pi01:~# arp -a -n | grep "78:8a:20:07:7a:94" pi01:~# arp -a -n | grep "78:8a" ? (192.168.179.204) auf 78:8a:20:07:7a:95 [ether] auf eth0 ``` Okay die Mac-Adresse passte dann nicht so ganz, aber da IPv6 von einem anderen Interface kommt, als nachher die IP-Adresse, einfach mit weniger String der Mac Adresse noch mal ARP fragen und zack da ist sie. Also Netzwerkkabel von dem Teil raus und den Ping starten, um zu checken ob es wirklich der Ubiquiti EdgeRouterX ist. ``` pi01:~# ping 192.168.179.204 PING 192.168.179.204 (192.168.179.204) 56(84) bytes of data. From 192.168.176.2 icmp_seq=1 Destination Host Unreachable From 192.168.176.2 icmp_seq=2 Destination Host Unreachable ... 64 bytes from 192.168.179.204: icmp_req=19 ttl=64 time=1992 ms 64 bytes from 192.168.179.204: icmp_req=20 ttl=64 time=991 ms 64 bytes from 192.168.179.204: icmp_req=21 ttl=64 time=0.858 ms ``` Alles klar, der Abend war gerettet und nach dem einloggen erst mal checken was ich da vor ein paar Monaten verbrochen habe. Mit der Konfig noch mal etwas ausprobiert und dann noch mal von vorne. # Dann wird jetzt konfiguriert Mit der vorhandenen Konfiguration weiter gemacht und auf den Interfaces `eth1 (VLAN2)`, `eth2 (3)`, `eth3 (4)` und `eth4 (5)` die VLANs konfiguriert. IP-Adressen jeweils aus einem Netz drauf gelegt und in jedem VLAN einen DHCP Server spendiert. VLAN 2 auf dem Cisco Switch auf `gi8` und `gi10` getagged. Auf `gi10` hängt ein Accesspoint der das VLAN braucht, da er eine eigene SSID zum testen für die neuen Netze hat. Das iPhone ins neue WiFi geworfen. Es gekommt eine IP wie gewünscht. Nur war trotzdem alles irgendwie hakelig. Routing, Firewall usw. alles gechecked und ausprobiert. So will man das nicht haben. Da ich mir eh gerade, durch ein blöden Konfigurationsfehler, die IP weg gezogen hatte und ich resetten musste, konnte ich auch gleich noch mal was anders konfigurieren. Anstatt auf dem EdgeRouterX auf den Interfaces, jetzt auf dem EdgeRouterX Switch die VLANs eingerichtet. Um es kurz zu machen das wollte so gar nicht. Trotz der ganzen Anleitungen im Netz und genauen Beschreibungen das andere es so einsetzen. Es wollte einfach nicht. Also noch einmal von vorne wie am Anfang schon. Nur dieses mal nach einem Reset des Routers. Ich hatte eh schon das Gefühl das ich da mal was drauf gemacht hatte was jetzt störte. VLANs und VLAN-Interfaces anlegen, IPs setzen, Routing usw. und jetzt klappt es. An eth0 ist der Uplink zum Switch, hinter dem dann auch irgendwo das DSL Modem hängt. Switche, DSL und noch 1-2 andere Sachen werden in dem alten Netz bleiben. Das wird jetzt das Management Netz. Alle anderen VLANs bekommen jetzt eigene IP-Bereiche, inklusive DHCP Server. Zwischen den Netzen Inter-VLAN-Routing und Firewall macht alles dicht bis auf das was durch soll. Gerade der Punkt war wichtig, da wir bei uns den einzigen Drucker im Haus haben den alle benutzen. Der kann jetzt auch weiter freigegegen werden. Genau wie unsere Synology. In die andere Richtung müssen wir auch mal auf andere Geräte in den anderen Wohnungen zugreifen. Daher müssen einzelne Geräte freigeschaltet werden können. # So sieht die Konfiguration bis jetzt aus Auf dem Ubiquiti sind die Interfaces jetzt eingerichtet. ![Ubiquiti Interfaces](https://s3fs.kuepper.nrw/blog-images/posts/2019-08-10-Netzwerkumbau-VLANs-und-Firewall-1.webp) Auf dem AccessPoint ist eine Test-SSID im Vlan '2' und auf dem Port am Switch hat der AccessPoint auch das VLAN getagged bekommen. Der Sitch an dem das ganze Zeug häng ist ein Cisco SG300-10 10-Port Gigabit Managed Switch. Der hat jetzt erst einmal 4 neue VLANs bekommen. Auf dem Switch sieht die Config jetzt also für die VLANs so aus: ``` sw-02-1#show vlan tag 2 Created by: D-Default, S-Static, G-GVRP, R-Radius Assigned VLAN, V-Voice VLAN Vlan Name Tagged Ports UnTagged Ports Created by ---- ----------------- ------------------ ------------------ ---------------- 2 VLAN2.OG gi8,gi10 gi2 S sw-02-1#show vlan tag 3 Created by: D-Default, S-Static, G-GVRP, R-Radius Assigned VLAN, V-Voice VLAN Vlan Name Tagged Ports UnTagged Ports Created by ---- ----------------- ------------------ ------------------ ---------------- 3 VLAN1.OG gi4,gi10 S sw-02-1#show vlan tag 4 Created by: D-Default, S-Static, G-GVRP, R-Radius Assigned VLAN, V-Voice VLAN Vlan Name Tagged Ports UnTagged Ports Created by ---- ----------------- ------------------ ------------------ ---------------- 4 VLANEG S sw-02-1#show vlan tag 5 Created by: D-Default, S-Static, G-GVRP, R-Radius Assigned VLAN, V-Voice VLAN Vlan Name Tagged Ports UnTagged Ports Created by ---- ----------------- ------------------ ------------------ ---------------- 5 Server S ``` Hauptsächlich ist `VLAN 2` jetzt konfiguriert. Ist zwar auch noch nicht komplett fertig, aber dafür muss ich mir nachher erst einmal genau angucken welcher Port wo verkabelt ist und wie sich die beiden 16 Port Switche verhalten. Die beiden sind keine Cisco Switche sondern TP-Link Managed Switche. Ich weiß nicht wieso managed, weil naja die verrichten ihren Dienst, aber so viel können die auch nicht und das Webinterface ist einfach nur mies. Hätte ich das vorher gewusst wären das auch Cisco Switche geworden. Sollten die mich nachher ärgern fliegen die raus und werden heute noch gegen Cisco Switche ersetzt. Dann heisst es dann wenigstens auch dabei: "On, Plug , Config und fertig." Die TP-Link-Switche könnten noch ein Problem werden. Ich müsste eigentlich Tagged zu denen rübber und die VLANs dann UnTagged auf die Ports geben. Das war letzten schon nicht so erfolgreich. # Die anderen Etagen sind dann auch noch dran Das wird sehr schnell gehen. VLANs an den Uplink mit anlegen und dann alle Ports die dort für die die Etage sind mit dem VLAN Tag versehen. Auf dem AccessPoint das VLAN setzen und einfach nur alle Geräte kurz vor die Tür setzen damit sie wieder neu reinkommen. Danach sind sie in einer frisch renovierten Wohnung ... ähm ... Netzwerkumgebung. Dann das gleiche noch auf der letzten Etage und dann ist das Thema durch. # Das war ein Urlaubsprojekt für die nächsten 3 Wochen Tja, erster Abend und eigentlich schon fertig. Der Rest ist jetzt nur noch stumpfes Port konfigurieren. Aber es gibt ja noch genug für die 3 Wochen. Zum Beispiel da wir 2 DSL Anschlüsse haben auch jetzt wieder das Wechseln nach Last oder bei Ausfällen auf den anderen DSL Anschuss. Zutun gibt es immer etwas :laughing:. --- ## Netzwerk Umbaulabor - URL: https://blog.kuepper.nrw/posts/2019-08-09-Netzwerk-Umbaulabor/ - Datum: 2019-08-09 - Aktualisiert: 2019-08-09 - Autor: ruediger - Tags: Internet, Netzwerk, Router, VLAN, IP - Kategorien: Internet # Testlabor für den Netzwerkumbau In den nächsten 3 Wochen ist Urlaub angesagt. Das heisst jetzt für die Family: Es wird umfangreiche Änderungen am Netzwerk geben. Das ist schon länger geplant, aber die Zeit ist immer so eine Sache. Das Netzwerk hatte ich ja schon angesprochen. 4 Etagen, 3 Wohnungen, 5 Switche, 3 WiFi AccessPoints, jede Menge Smarthome, smarte Geräte und Zeug drum her rum. # Ubiquiti Edge Router X als Testlabor Ich habe mit einem der beiden Router schon ein paar Sachen gemacht. Jetzt wird auch der wieder zum testen genommen. Ein zweiter liegt auch schon etwas länger bereit zum Einsatz daneben. Die beiden werden jetzt dafür genommen sie als Gateways zum Internet und für die Interne Netztrennung einzusetzen. Im ersten Schritt kommen beide erst einmal an den Switch ins vorhandene Netz. Auf der anderen Seite kommen dann die ersten Geräte zum testen. Getrennt vom Rest und damit wird dann alles so vorbereitet wie es nachher sein soll. Funktioniert das wird ein zweites VLAN erstellt und so das nächste Etagen Netz getestet. Kommt das ins Netz und kann man bei Bedarf einzelne Geräte mit anderen im anderen Netz verbinden wird noch das 3. Netz genau so vorbereitet. # Edge Router vom Labor hinten nach ganz vorne stellen. Wenn die Punkte oben alle rund laufen werden die beiden bzw. einer davon vom Testlabor nach vorne hinter die Fritzbox gepackt. Ok, das passiert nicht per Kabel, es wird eher an den Switchports anders konfiguriert. Ziel ist das alles vorzubereiten und dann an einem Tag einfach per Schalter umzustellen. Es soll keiner im Haus davon etwas bemerken. Die IPs werden sich ändern, aber das im Hintergrund. Die Geräte per Kabel bekommen an allen Switchen kurz ein Port Down und Up. Die sollten das alle mitbekommen und das Interface neu konfigurieren. Sie sind dann in einem anderen VLAN und bekommen die neuen IPs. Geräte per WiFi werden ganze einfach neu bespasst. Alle AccessPoints werden einfach kurz resettet, Clients werden neu verbunden und danach auch mit anderer Netzwerkkonfiguration wieder online kommen. # So der Plan Wir werden sehen wir schnell das alles klappt und wann alles fertig ist. Erst mal im kleinen testen und dann weiter machen. Ich werde dann mal die nächsten Tage und Wochen berichten. --- ## Apple Mac Address Spoofing cool und uncool - URL: https://blog.kuepper.nrw/posts/2019-08-07-Apple-Mac-Address-spoofing-cool-and-uncool/ - Datum: 2019-08-07 - Aktualisiert: 2019-08-07 - Autor: ruediger - Tags: Apple, AppleTV, TimeCapsule, Network, VLAN, ARP, TCP, IP, Spoofing - Kategorien: Technik # Alarm aus der 1. Etage Kriege ich gerade aus der 1. Etage hier im Haus eine iMessage: > Hast du eine Ahnung warum das Internet so langsam ist? Streaming über Lan stoppt nach einer halben Sekunde. Kurz gegen gechecked und Youtube auf dem iPhone hat keine Probleme. Also selbst mal den AppleTV anschmeissen und da überprüfen. ARD Mediathek ok, Youtube OK. FireTV mit Netflix und Co getesten. Selbst FireTV und DreamTV den Stream Haus intern vom Octagon kann ohne Probleme HD abrufen. Kein ruckeln, keine Abbrüche. # Kurzes debugging Kurzer Blick auf dem RaspberryPI der DHCP gemacht und da viele DHCP Requests von dem AppleTV gesehen der als Problemgerät gemeldet wurde. Mutt auf dem RaspberryPI aufgemacht und nach ArpWatch Mails geschaut. Treffer, sehr viele Mails mit "flip flop" Meldungen passend zum Gerät. ``` 60114 N + Aug 07 Arpwatch pi01.9 ( 10) flip flop (appletv-01-2.intern.pretzlaff.co) eth0 60115 N + Aug 07 Arpwatch pi01.9 ( 10) flip flop (appletv-01-2.intern.pretzlaff.co) eth0 60116 + Aug 07 Arpwatch pi01.9 ( 10) flip flop (appletv-01-2.intern.pretzlaff.co) eth0 ``` Dann pingen wir mal das Gerät über die IP an: ``` ping 192.168.178.108 PING 192.168.178.108 (192.168.178.108) 56(84) bytes of data. From 192.168.176.106: icmp_seq=1 Redirect Host(New nexthop: 192.168.178.108) From 192.168.176.106: icmp_seq=2 Redirect Host(New nexthop: 192.168.178.108) ``` # Erste Analyse Sieht komisch aus und mir fällt auch prompt etwas ein was die Ursache sein kann. Mac-Address Spoofing der Apple TimeCapsule Geräten, wenn sie andere Apple Geräte offline gehen sehen oder meinen die Geräte sind offline. Eurer AppleTV ist ausgeschaltet und trotzdem zeigt ein iPhone, iPad oder Mac das Gerät trotzdem sofort an wenn Ihr auf AirPlay geht? Genau das ist gemeint. Apple Geräte wie die TimeCapsule oder AppleTV gucken welche anderen Geräte noch im Netzwerk sind und geht eines der Geräte offline, zum Beispiel in den Standby, melden sie sich im Netzwerk ab und ein anderes Gerät wie die TimeCapsule nimmt sich auf sein Netzwerkinterface die Mac-Adresse des Gerätes welches in Standby gegangen ist. Dadurch sieht das Gerät immer noch als online im Netzwerk aus. Das gleiche wird auch so beim Mac / Macbook gemacht. Fragen jetzt andere Geräte im Netzwerk eines der Geräte im Standby an antwortet die TimeCapsule auch brav und sendet im Hintergrund ein Datenpaket an das Standby Gerät, welches dadurch aufgeweckt wird. Praktisch wenn man Back to my Mac, VPN oder einfach lokal mal auf einen Rechner zugreifen will. Oder wenn man AirPlay machen möchte. Die Geräte wachen wie von selbst auf. # Kann aber auch mal doof sein Zurück zum Problem. Checken wir mal den Arp Cache: ``` arp -a| grep 78:ca:39:ff:ee:3c ... appletv-01-2.intern.pretzlaff.co (192.168.178.108) auf 78:ca:39:ff:ee:3c [ether] auf eth0 appletv-0-1.intern.pretzlaff.co (192.168.177.3) auf 78:ca:39:ff:ee:3c [ether] auf eth0 tc02.intern.pretzlaff.co (192.168.176.106) auf 78:ca:39:ff:ee:3c [ether] auf eth0 ... ``` Das ganze noch mal mit -n um schneller und übersichtlicher die Ergebnisse zu sehen. Denn im Netzwerk sind 92 Geräte zu dem Zeitpunkt aktiv gewesen und ohne -n dauert das schon etwas länger. ``` arp -a -n | grep 78:ca:39:ff:ee:3c ? (192.168.176.106) auf 78:ca:39:ff:ee:3c [ether] auf eth0 ? (192.168.178.108) auf 78:ca:39:ff:ee:3c [ether] auf eth0 ? (192.168.177.3) auf 78:ca:39:ff:ee:3c [ether] auf eth0 ``` Zm Vergleich die Mac Adressen die eigentlich hinter IP `192.168.178.108` und `192.168.177.3` stecken: ``` arp -a -n | grep 192.168.178.108 ? (192.168.178.108) auf 34:c0:59:31:c7:92 [ether] auf eth0 arp -a | grep 192.168.178.108 appletv-01-2.intern.pretzlaff.co (192.168.178.108) auf 34:c0:59:31:c7:92 [ether] auf eth0 arp -a -n | grep 192.168.177.3 ? (192.168.177.3) auf 34:c0:59:31:fe:46 [ether] auf eth0 arp -a | grep 192.168.177.3 appletv-0-1.intern.pretzlaff.co (192.168.177.3) auf 34:c0:59:31:fe:46 [ether] auf eth0 ``` Man sieht hier das die IPs bei anderen Geräten benutzt werden. Aber eben genau die Geräte haben aber jetzt die Mac Adresse `78:ca:39:ff:ee:3c`. # Ursache ist eigentlich eine Super Funktion Aber hier macht sie gerade ein Problem. Apple Geräte wie die TimeCapsule bekommen mit wenn sich andere Apple Geräte im Netz befinden. Geht jetzt der betroffene AppleTV in den Standby nimmt sich die TimeCapsule die Mac Adresse vom AppleTV und simuliert die Funktionen des AppleTV. Für andere Clients wirkt es als wäre der AppleTV online. AirPlay zeigt den AppleTV als verfügbar an und man kann diesen auswählen. Verbindet sich ein Client jetzt mit dem AppleTV wird er per WakeOnLan Magic Paket aufgeweckt und die TimeCapsule entfernt die Mac Addresse wieder von seinem Netzwerk Interface. Der AppleTV übernimmt sie wieder und bekommt seine IP. > The sleep proxy service responds to address resolution protocol requests on behalf of the low-power-mode device > When a sleep proxy sees an IPv4 ARP or IPv6 ND Request for one of the sleeping device's addresses, it answers on behalf of the sleeping device, without waking it up, giving its own MAC address as the current (temporary) owner of that address. Wir haben hier mehrere TimeCapsule und 4 aktive AppleTV. Der 5te ist aktuell nicht in Benutzung. Das Verhalten war aber jetzt auch nur von der TimeCapsule bekannt. Die beiden AppleTV die hier aber auch die Mac Adresse übernommen hatten waren AppleTV der neuesten Generation. Es scheint also so zu sein das jetzt auch der AppleTV als Bonjour Sleep Proxy eingesetzt wird. Das würde auch die sporadischen komischen Netzwerkprobleme erklären die immer mal wieder im Haus aufkommen. TimeCapsule und AppleTV kümmern sich also drum das iTunes Sharing, File Sharing, Druckerfreigaben auch dann erreichbar sind, bzw. die entsprechenden Geräte auch im Standby angesprochen werden können. Und ja, da Apple sich immer mehr aus dem Routermarkt verabschiedet ist die Funktion in den AppleTV bzw. TVOS gewandert. > Mit der Funktion „Ruhezustand bei Bedarf beenden“ (auf Ihrem Mac) und mit Bonjour Sleep Proxy (durch ein AirPort-Gerät oder Apple TV bereitgestellt) können Sie Energie sparen und Kosten senken, während gleichzeitig der Zugriff auf alle freigegebenen Dienste sichergestellt bleibt. Zudem können Sie über Zugang zu meinem Mac auch aus der Ferne über das Internet auf die freigegebenen Dienste zugreifen. Die Funktion „Ruhezustand bei Bedarf beenden“ wird zusammen mit Bonjour Sleep Proxy auf Ihrer AirPort-Basisstation, Time Capsule oder Apple TV (wenn sich keine AirPort-Basisstation oder Time Capsule im Netzwerk befindet) ausgeführt. Hinweis: Apple TV agiert auch im Ruhemodus als Bonjour Sleep Proxy. # Apple hat wohl nicht an die Masse an Geräte gedacht Wie erwähnt haben wir mehrere AppleTV, TimeCapsule und eine grössere Menge MacOS und iOS Geräten im Hause. Da kommt bei 3 Haushalten schon was zusammen. Jede Wohnung bzw. Etage hat hier einen eigenen Switch und AccessPoint. Per Vlans getrennt. Bis auf das Zeug was Media angeht. Die AppleTV und TimeCapsule Geräte sind in einem Netz zusammen. Jetzt geht der AppleTV von meinem Bruder offline und die TimeCapsules meinen jetzt beide sie müssten im Netz behaupten sie seien der AppleTV. Die anderen AppleTV behaupten das gleiche. Der AppleTV wird dann wieder aufgeweckt und dann fängt die ARP schlacht im Netzwerk an. Eines der Geräte hat das Datenpacket für den AppleTV bekommen, sagt dem Apple TV bescheid und lässt die Mac Adresse wieder frei, was dann aber für eines der anderen TimeCapsules oder AppleTV als "Da geht ein Device offline ich mach mal Bonjour Sleep Proxy", fragt beim DHCP nach IP und schreit das dann auch so ins Netz damit alle bescheid wissen. Der eigentliche AppleTV ist irritiert und meldet sich im Netz "Ey bin doch online, ich brauche eine IP." Jetzt fängt das gleiche von vorne an. Mac Adresse wird vom AppleTV aktiviert und ins Netz geblasen und von einem anderen Device freigegeben und das nächste meint da geht was offline und macht einen auf Bonjour Sleep Proxy. usw. usw. # Ok, im Urlaub auch Media-Netze mit Vlans trennen Wir haben hier zuhause 4 Etagen. EG und 1. OG sind jeweils 1 Wohnung und wir hier oben haben 2. und 3. Etage. Zwischen den einzelnen Wohungen sind immer 2 Verbindungen, die als LAG geschaltet sind. Also 1 <-> 2 <-> 3 und noch mal 1 <-> 3 Etagen. Im schlimmsten Fall könnte man also 4 Gigabit Bandbreite benutzen. Alles mit Cisco Switchen. Sollte das mal nicht mehr ausreichen gehen wir einfach über Glasfaser und könnten auf 10 Gigabit oder mit LAG auf 20 Gigabit. Die AccessPoints haben mehrere SSID konfiguriert, die aber auf allen Etagen. So das man hier von einer Wohnung in die nächste gehen kann, Verbindungen bestehen bleiben und man überall sein WiFi Netz hat. Zusätzlich noch Media WiFi und Gäste WiFi. Alles schön getrennt mit VLANs. Bis auf das Media-Netz. Da wird dann ab nächster Woche dran geschraubt und auch da alles schön getrennt. Dann kommen auch die beiden Ubiquiti Edge Router X zum Einsatz. Ich werde auch mal im Detail den Aufbau des Netzes hier beschreiben. Das steht schon länger auf er ToDo und wird auch mal Zeit. --- ## Recent Posts Widget - URL: https://blog.kuepper.nrw/posts/2019-08-05-recent-posts-widget/ - Datum: 2019-08-05 - Aktualisiert: 2019-08-05 - Autor: ruediger - Tags: Hugo, CMS, Sidebar, Widgets - Kategorien: Technik ### Recent Posts Widget für die Sidebar In meinen Blogs hatte ich immer ein Widgets für die letzte Posts. Das habe ich gerade auch für das Hugo CMS erstellt. Im Theme unter Layouts -> Partials -> Widgets habe ich eine Datei `lastposts.html` erstellt: ```
``` Die Zeilen 1, 9-12 und 16 sind markiert. Das sind Zeilen die das Widget steuern bzw. die Anzahl an konfigurierten letzten Posts ausgeben. * Zeile 1 überprüft ob das Widget aktiv ist. * Zeile 9 setzt die Variable $count auf die konfiguriere Anzahl der posts * Zeile 10 bis 12 durchläuft die letzte $count Posts und zeigt sie an. Das funktioniert auf der Startseite gut, aber in den Artikeln bleibt die Liste leer. Also wurde jetzt noch folgender Code ersetzt: ``` {{ $count := .Site.Params.widgets.recent_posts }} {{ range first $count .Pages }}
  • {{ .Name }}
  • {{ end }} ``` Durch: ``` {{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }} {{ range first $count $pages }}
  • {{ .Name }}
  • {{ end }} ``` Jetzt wird das Widget auf allen Seiten befüllt. ### Konfiguration des Widget Das Widget kann in der `config.toml` konfiguriert werden. ``` ... # Enable and disable widgets for the right sidebar [params.widgets] categories = true tags = true search = true recent_posts = 10 ... ``` Das Widget kann mit `false` deaktiviert werden. Die Anzahl kann frei gewählt werden. --- ## Eigene Shortcodes Hugo CMS - URL: https://blog.kuepper.nrw/posts/2019-08-04-shortcodes-hugo-cms/ - Datum: 2019-08-04 - Aktualisiert: 2019-08-04 - Autor: ruediger - Tags: Webpage, CMS, Hugo, Generator, Pipeline, Deploymend, Automatisierung - Kategorien: Technik ### Shortcode erstellen Um Bilder in Seiten oder einem Blogartikel schnell einfügen zu können habe ich mit im Hugo CMS einen eigenen Shortcut erstellt. Ich habe immer das originale Bild und ein kleiner gerechnetes Bild. Diese werden im Ordner `/static/img/posts/` gespeichert. Datei im Ordner: `/layouts/shortcodes/postimage.html` ```

    {{ index .Params 0 }}

    ``` In Seiten/Artikeln kann ich so jetzt einfach mitfolgendem Code einfach Bilder einbinden: ``` {{ < postimage "title" "image-original.webp" "Image-thumbnail.webp" >}} ``` Die Bilder liegen aktuell noch alle in `/static/img/posts/`. Das werde ich aber noch ändern und auch den Shortcode anpassen. Die Originale bleiben in `/static/img/posts/` aber die Thumbnails werden aber in `/static/img/posts/thumbs` landen. Denn ich möchte die Bilder auch noch automatisch generieren lassen ohne großen Aufwand. Aktuell wird jedes Bild einzeln verkleinert. ``` nconvert -resize 800 -o DB-Wifi-1-800.png DB-Wifi-1.png ``` Das will ich gerne in folgendes ändern: ``` for FILE in $(find ${IMAGEDIR} -type f -maxdepth 1) do nconvert -resize 800 -o ${IMAGEDIR}/thumbs/${FILE} ${FILE} done ``` Für das Image Rezise habe ich jetzt ein Docker Image erstellt. Dockerfile ``` FROM alpine:edge MAINTAINER "Rüdiger Küpper " RUN apk update && apk add imagemagick bash COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] ``` Entrypint.sh ``` #!/bin/bash cd /posts for FILE in $(find . ! -name "*.svg" -type f -maxdepth 1); do convert -resize 800 ${FILE} thumbs/${FILE}; done ``` Im Ordner `/static/img/imagesresize.sh`: ``` #!/bin/bash docker run -it -v $(pwd)/posts:/posts imageresize ``` Ein `./imagereszie.sh` im Ordner `/static/img/` generiert jetzt alle Bilder als Thumbnails in fester Breite neu und legt sie im Ordner `thumbs` ab. Den Shortcode habe ich jetzt noch wie folgt angepasst: ```

    {{ index .Params 0 }}

    ``` ### Shortcode für Soundcloud Um meine oder von anderen Soundcloud Tracks einzubinden habe ich mir auch einen Shortcode geschrieben. Datei im Ordner: `/layouts/shortcodes/soundcloud.html` ```

    {{ index .Params 1 }}

    ``` Eingebunden wird das ganze dann in Artikel oder Seiten mit: ``` {{ < soundcloud 235962771 "Rock Solo Guitar - New Version" >}} ``` ### Shortcode für Soundcloud Alben ```

    {{ index .Params 1 }}

    ``` Eingebunden wird das ganze dann in Artikel oder Seiten mit: ``` {{ < soundcloudalbum 298199575 "clockopera - Veen Album 2017" >}} ``` --- ## Wireguard VPN - URL: https://blog.kuepper.nrw/posts/2019-08-03-wireguard-vpn/ - Datum: 2019-08-03 - Aktualisiert: 2019-08-03 - Autor: ruediger - Tags: Netzwerk, IP, IPSec, VPN, Tunnel, Routing, Internet - Kategorien: Internet ### WireGuard VPN Seit Jahren benutze ich OpenVPN, das wird auch so bleiben. Gerade für mobile Geräte ist aber auch Racoon für IPSec im Einsatz. IPSec aus einem Grund: Authentifizieren mit Zertifikaten. Denn OnDemand Verbindungen zu bestimmten Zielen im internen Netz bekommt man auf dem iPhone nur per Profilen hin wenn man Zertifikate benutzt. Also habe ich meine eigene CA mit der ich die Benutzer mit den entsprechenden Zertifikaten verwalten kann. Funktioniert auch sehr gut, ist aber auch etwas aufwändiger. Da ich privat und auch mit Arbeitskollegen Server, Rechner und VPN Verbindungen betreibe um Daten auszutauschen ist der Aufwand mit IPSec immer etwas mehr. Daher wird da meistens OpenVPN genommen. Hat aber wieder das Problem das OnDemand und manche Client Konfiguration komplizierter ist. ### WireGuard - VPN in einfach Seit längerem schwirrt im Netz WireGuard herum. Ich hatte mir das auch schon vor ein paar Monaten angeguckt. Damals zum testen auf einem RaspberryPI und auch als Docker Container. Die Tests waren nicht so erfolgreich. Da sich ein Kollege jetzt auch mit WireGuard beschäftigt hat kam das Thema auch bei mir wieder auf. Und siehe da! Es hat sich einiges getan und die Entwicklung hat große Schritte gemacht. Der Server läuft und die Konfiguration ist simple. Und das trifft auch auf die für die Clients zu. Auf beiden Seiten wird ein Schlüsselpaar generiert. Jetzt werden einige sagen: aber das kann man in der Familie gar nicht benutzen. Das kapiert doch keiner. Doch kann man. Ich hatte vorher auch die Zertifikate, ob OpenVPN oder IPSEC, generiert. Das Schlüsselpaar wird auch einfach von mir generiert und damit die Konfiguration erstellt. ### Konfiguration in 3 Minuten Die Konfiguration besteht immer aus 2 Teilen. Einmal das Interface und dem Peer, dem Client. Beim Server sind es halt mehrere Peers. Für den Anfang erst einmal nur Server und einen Client. Dann hat man jeweils 3-5 Zeilen und das war es. Jetzt hat man eine simple Datei mit ca 10 Zeilen und kann sie an den Benutzer verteilen. Jetzt kann man die Datei client.conf schicken. Oder man schickt die Datei einfach einmal durch qrencode und erhält einen QRCode. ### Client installiert und QRCode scannen Der Benutzer installiert sich jetzt einfach nur noch die WireGuard App. Nach dem öffnen Tunnel hinzufügen und einmal auf QRCode scannen tippen. Den QRCode einscannen und schon ist alles fertig. Der Client kann sich sofort verbinden und bei Bedarf auch einstellen das in bestimmten Wifi Netzen und/oder mobil immer das VPN aufgebaut werden soll. ### Schritt für schritt Konfiguration Eine Anleitung zu WireGuard wird es die Tage noch geben. Dann wird es auch Config Beispiele geben. Auch wie das mit dem Routing in das Netz zuhause oder zu anderen geht. Wir benutzen in unserem kleinem Mesh Netz für jeden Teilnehmer eigene Netze und Routen diese mit Hilfe von BGP. Viele Sachen davon waren immer kompliziert. Mit WireGuard ist vieles aber einfacher geworden und es kann jeder jetzt Schell und einfach sichere Verbindungen aufbauen. --- ## Quagga BGP prefix-list - URL: https://blog.kuepper.nrw/posts/2019-08-02-quagga-bgp-prefix-list/ - Datum: 2019-08-03 - Aktualisiert: 2019-08-03 - Autor: ruediger - Tags: Netzwerk, IP, BGP, Routing, Internet - Kategorien: Internet *Notiz an mich, um nich noch einmal suchen zu müssen* :point_up: Um im BGP manche Netze nicht zu erlauben: ``` router bgp 65001 bgp router-id 10.10.10.1 network 10.101.0.0/16 neighbor 10.11.0.1 remote-as 65002 neighbor 10.11.0.1 description 65002 neighbor 10.11.0.1 prefix-list icvpn4 in neighbor 10.11.0.1 prefix-list icvpn4 out ! ip prefix-list icvpn4 description *** ICVPN prefix-list for internal and public IP address space *** ip prefix-list icvpn4 seq 20 deny 10.101.0.0/16 le 24 ``` Damit ist 10.101.0.0/16 und /24er aus dem Block nicht mehr erlaubt. --- ## Hugo CMS migration - URL: https://blog.kuepper.nrw/posts/2019-07-13-hugo-cms-migration/ - Datum: 2019-07-13 - Aktualisiert: 2019-07-13 - Autor: ruediger - Tags: Webpage, CMS, Hugo, Generator, Pipeline, Deployment, Automatisierung - Kategorien: Technik ## Migration nach Hugo CMS Die Seite ist jetzt zu einem Hugo CMS migriert worden. Hugo ist ein Static Page Generator. Im gegensatz zu Wordpress werden die Seiten nicht bei jedem Aufruf neu generiert, sondern nur nach Änderungen. Die fertig Seiten werden dann auf dem Server bereitgestellt. Das macht die Seite sehr schell und ich kann sie in Zukunft auch auf sehr vielen Servern, in Docker oder Kubernetes Clustern verteilen. Also auch sehr grosse Lastspitzen locker abfangen. ## Hugo - CMS > Als statischer Websitegenerator werden von Hugo die HTML-Dateien – im Gegensatz zu dynamischen Websitegeneratoren – nicht jedes Mal, wenn die Webseite aufgerufen wird, neu generiert, sondern nur, wenn sich der Inhalt der jeweiligen Seite ändert. Insbesondere ermöglicht es Hugo, dass nur diejenigen HTML-Dokumente der jeweiligen Webseite neu gebaut werden müssen, in denen Änderungen auftraten. Hierdurch sollen die Ressourcen des Servers geschont und eine hohe Effizienz von diesem erreicht werden.[3] Nach einer nicht-repräsentativen Benchmark generiert Hugo Webseiten 75-mal schneller als der ebenfalls statische Websitegenerator Middleman.[4] > Hugo unterstützt nativ neben HTML auch die Darstellung von Texten, die in Markdown verfasst wurden. Mit Hilfe externer Anwendungen kann diese Unterstützung auf AsciiDoc und reStructuredText erweitert werden. Auch YAML, JSON und TOML werden unterstützt. Mittels der sogenannten „LiveReload“-Funktion können Änderungen an den Dokumenten zeitgleich auf der Webseite übernommen werden. Die graphische Darstellung der Inhalte kann mittels verschiedener Themenvorlagen geregelt werden.[4][5] Dabei wird zwischen drei verschiedenen Grundtypen unterschieden: Single, List und Homepage. Die Nutzung der Themen erfolgt mittels der Template-Engine von Go. Hugo ermöglicht es zusätzlich, Inhalte der Webseiten mittels Schlüsselwörtern zu kategorisieren. > Eine Besonderheit von Hugo ist, dass es einen eigenen HTTP-Server mitliefert. Hierdurch sind Anwender nicht auf z. B. nginx oder den Apache HTTP Server angewiesen, wodurch Abhängigkeiten verhindert werden. Auch bestimmte Laufzeitumgebungen und Datenbanken wie Ruby, PHP oder MySQL werden zur Nutzung nicht benötigt. ## Format von manchen Blog Artikeln noch defekt In manchen Artikeln wurden Plugins für Tabellen, Soundcloud einbindung und andere benutzt. Daher können manche der Artikel aktuell noch nicht richtig angezeigt werden. Bei den Artikeln muss ich noch etwas nacharbeiten. ## Seite generieren auf GitLab Beschreibung wie die Seite mit Gitlab Pipeline generiert wird ... Um die Seite neu zu generieren werden Änderungen in den Master Branch gepushed. Dadurch wird eine Pipeline getriggert die sich ein Hugo Docker Image holt und die Seite wird damit generiert (build). Ist der Build fertig und ok wird ein ssh+rsync Docker Image geladen und die Seite wird auf den/die Webserver kopiert (deploy). Die Konfiguration `.gitlab-ci.yml` der Pipeline sieht so aus: ``` stages: - build - deploy build: stage: build image: registry.gitlab.com/ruedigerp/hugoci:latest script: - git submodule update --init --recursive - hugo -b "${BLOG_URL}" artifacts: paths: - public expire_in: 1 hour only: - master build: stage: build image: registry.gitlab.com/ruedigerp/hugoci:latest script: - git submodule update --init --recursive - hugo -b "${BLOG_URL}" artifacts: paths: - public expire_in: 1 hour only: - master deploy: stage: deploy image: registry.gitlab.com/ruedigerp/ci-deploy-rsync-ssh script: - echo "${SSH_PRIVATE_KEY}" > ${HOME}/id_rsa - chmod 400 ${HOME}/id_rsa - mkdir "${HOME}/.ssh" - echo "${SSH_KNOWN_HOSTS}" > "${HOME}/.ssh/known_hosts" - rsync -at --quiet --delete --delete-delay --delay-updates --exclude=_ --include=.well-known -e "ssh -i ~/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p ${DEPLOY_PORT}" public/ ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_DIR} variables: GIT_STRATEGY: none only: - master ``` Beim Build wird Die Seite nach `./Public` generiert und als Artifakt für 1 Stunde gespeichert. Das wird im Deploy Schritt dann wieder abgerufen und kann so dann in diesem Schritt benutzt werden. Damit die Pipeline funktioniert müssen in den Settings noch die Variabeln hinterlegt werden. * BLOG_URL * SSH_PRIVATE_KEY * SSH_KNOWN_HOSTS * DEPLOY_PORT * DEPLOY_USER * DEPLOY_HOST * DEPLOY_DIR Den SSH Host Key bekommt man mit: ssh-keyscan -p $PORT dein.ssh.host.de Wer noch keinen `ssh-key` hat oder einen neuen genieren will: ssh-keygen -t rsa -b 4096 [-f output_keyfile] [-C ci-cd-deploment] ~~Geplant sind noch weitere Stages um die Seite nicht nur lokal zu testen, sondern bevor sie Live jetzt auch im Internet in einer Testumgebung testen zu können.~~ Gerade wenn man Seiten mit mehreren Leuten betreut kann auf `staging` deployed werden und alle können überprüfen ob alles ok ist. Erst danach wird `staging` in `master` gemerged und automatisch in `PROD` deployed. Das ist jetzt auch umgesetzt mit folgender `.gitlab-ci.yml`. ``` stages: - build - deploy-dev - deploy build: stage: build image: registry.gitlab.com/ruedigerp/hugoci:latest script: - git submodule update --init --recursive - hugo -b "${BLOG_URL}" artifacts: paths: - public expire_in: 1 hour only: - master - dev dev: stage: deploy-dev image: registry.gitlab.com/ruedigerp/ci-deploy-rsync-ssh script: - echo "${SSH_PRIVATE_KEY}" > ${HOME}/id_rsa - chmod 400 ${HOME}/id_rsa - mkdir "${HOME}/.ssh" - echo "${SSH_KNOWN_HOSTS}" > "${HOME}/.ssh/known_hosts" - rsync -at --quiet --delete --delete-delay --delay-updates --exclude=_ --include=.well-known -e "ssh -i ~/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p ${DEPLOY_PORT}" public/ ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_DIR}dev variables: GIT_STRATEGY: none only: - dev deploy: stage: deploy image: registry.gitlab.com/ruedigerp/ci-deploy-rsync-ssh script: - echo "${SSH_PRIVATE_KEY}" > ${HOME}/id_rsa - chmod 400 ${HOME}/id_rsa - mkdir "${HOME}/.ssh" - echo "${SSH_KNOWN_HOSTS}" > "${HOME}/.ssh/known_hosts" - rsync -at --quiet --delete --delete-delay --delay-updates --exclude=_ --include=.well-known -e "ssh -i ~/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p ${DEPLOY_PORT}" public/ ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_DIR}dev variables: GIT_STRATEGY: none only: - master - dev when: manual ``` Push in `dev` triggert die Pipeline. Die macht den Build und das Deploy dann in die Testumgebung. Danach hält die Pipeline an und man kann alles testen. Wenn man dann nach `live` deployen will klickt man in der Pipeline und sie läuft weiter. ### Hugo Docker Image für GitLab Pipeline Um die Seiten zu generieren .... hugoci Dockerfile: ``` FROM alpine:3.7 RUN apk add --update \ git && \ rm -rf /var/cache/apk/* ENV HUGO_VERSION 0.42.2 ENV HUGO_RESOURCE hugo_${HUGO_VERSION}_Linux-64bit ADD https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/${HUGO_RESOURCE}.tar.gz /tmp/ RUN mkdir /tmp/hugo && \ tar -xvzf /tmp/${HUGO_RESOURCE}.tar.gz -C /tmp/hugo/ && \ mv /tmp/hugo/hugo /usr/bin/hugo && \ rm -rf /tmp/hugo* ``` .gitlab-ci.yml ``` image: docker:latest services: - docker:dind stages: - build variables: DOCKER_IMAGE_TAG: registry.gitlab.com/ruedigerp/ci-build-hugo before_script: # - echo $CI_BUILD_TOKEN | docker login --username gitlab-ci-token --password-stdin registry.gitlab.com - echo "AAA-BBBBBBBBBBBBB" | docker login --username gitlab-ci-token --password-stdin registry.gitlab.com build: stage: build script: - echo "AAA-BBBBBBBBBBBBB" | docker login --username gitlab-ci-token --password-stdin registry.gitlab.com - docker build --pull -t $DOCKER_IMAGE_TAG . - docker push $DOCKER_IMAGE_TAG ``` ### Sync Deploy Docker Image Um die Seiten zu deployen ... Docker image: registry.gitlab.com/ruedigerp/ci-deploy-rsync-ssh ``` # folgt noch ``` .gitlab-ci.yml ``` # folgt noch ``` --- ## Roundcube Webmail mit Zwei-Faktor Authentifizierung - URL: https://blog.kuepper.nrw/posts/2019-03-30-roundcube-webmail-mit-zwei-faktor-authentifizierung/ - Datum: 2019-03-30 - Aktualisiert: 2019-03-30 - Autor: ruediger - Tags: Alle Beiträge, Authentication, Authentifizierung, E-Mail, Internet, Internet, Security, Two-Factor, Two-Factor Authentication, Webmail, Zwei-Faktor - Kategorien: Internet Um die Sicherheit zu erhöhen habe ich vor ein paar Wochen das Two-Factor Gauthenticator Modul im Roundcube Webmail hinzugefügt. Damit können die User sich nicht mehr nur mit Benutzer und Passwort einloggen, sondern brauchen einen zweiten Faktor. Das ist dann ein 6-stelliger Code aus dem Google Authenticator auf dem Smartphone. Die Installation ist recht einfach. Das Module wird in das Webverzeichnis in dem Roundcube installiert ist im Ordner "**plugins**" gespeichert. z.B. "**/var/www/webmail/plugins**" > ``` > cd /var/www/webmail/plugins > git clone https://github.com/alexandregz/twofactor\_gauthenticator.git > ``` Um das PlugIn zu aktiveren öffnet man die Roundcube Konfigurationsdatei und fügt das PlugIn in der Liste hinzu: > ``` > $config\[‘plugins’\] = array(‘plugin1’, ‘plugin2’, ‘twofactor\_gauthenticator’); > ``` Anschliessend kann man sich im Roundcube einloggen und in die Einstellungen unter dem Punkt "**Zwei-Faktor Authentifizierung**" die Einstellungen vornehmen und den Goolge Authenticator einrichten. ![Roundcube Webmail mit Two-Factor Authentication](https://s3fs.kuepper.nrw/blog-images/posts/2factor-1024x312.webp) 1. Auf speichern klicken. 2. Es erscheint eine Meldung das nicht alle Felder ausgefüllt sind. 3. OK Bestätigen und es erscheint ein QR-Code. 4. Den Google Authenticator öffnen. 5. Hinzufügen auswählen und Barcode scannen. 6. Den 6 stelligen Code aus der App eingeben und prüfen anklicken. 7. Auf "Speichern" klicken. Ab jetzt wird nach dem Login im Webmail nach dem zweiten Faktor gefragt. Die Wiederherstellungscodes sollte man speichern und sicher aufbewahren. Diese werden benötigt wenn man das Smartphone verliert oder wenn der Google Authenticator zum Beispiel bei einem defekten Gerät nicht mehr zur Verfügung stehen sollte. --- ## Kopieren mit dd und Progressbar - URL: https://blog.kuepper.nrw/posts/2019-01-28-kopieren-mit-dd-und-progressbar/ - Datum: 2019-01-28 - Aktualisiert: 2019-01-28 - Autor: ruediger - Tags: Boot, CleanInstall, Installation, Linux, MacOS, Partition, Restore, Technik, USB-Stick - Kategorien: Technik Wer einen Rescue USB Stick erstellen will kann mit Hilfe von dd das .iso auf den Stick schreiben. ``` dd if=~/rescue.iso of=/dev/disk3 bs=1m ``` Dabei wird aber nicht angezeigt wie weit der Kopiervorgang ist. Bei den heutigen Betriebsystemen ist das Image aber mehrere Gigabytes groß und es dauert bei einem langsamen Stick sehr lange. Es könnte aber auch sein das ein Problem aufgetreten ist und man sieht nicht ob noch weiter kopiert wird. Daher kann man zwischen dem if (InputFile) und of (OutputFile) das Programm pv getrennt durch ein pipe setzen. ``` dd if=~/rescue.iso | pv | dd of=/dev/disk3 bs=1m mb-rp# dd if=snow\ leopard\ install.iso | pv | dd of=/dev/disk3 bs=1m 731MiB 0:28:30 [   0 B/s] [             <=>            ] ``` So sieht man wie viel MB/GB schon kopiert wurden und wird nach Stunden warten nicht nervös und bricht das kopieren nicht vor lauter Verzweiflung auch noch unwissend bei 98% ab. ;-) **DMG to ISO umwandeln** Da ich selbst auch immer suchen muss halte ich hier auch gleich das dmg zu iso Image umwandeln fest. ``` hdiutil convert /path/imagefile.dmg -format UDTO -o /path/convertedimage.iso ``` --- ## DSL Traffic 2018 - URL: https://blog.kuepper.nrw/posts/2019-01-04-dsl-traffic-2018/ - Datum: 2019-01-04 - Aktualisiert: 2019-01-04 - Autor: ruediger - Tags: Internet, Traffic, 2018 - Kategorien: Technik Unser DSL-Traffik 2018 Angabe in Terabytes Monat | Gesendet | Empfangen | Gesamt --------|----------|-----------|------- Jan | 0.07 | 1.20 | 1.30 Feb | 0.07 | 0.95 | 1.05 Mär | 0.73 | 0.92 | 1.02 Apr | 0.06 | 0.81 | 0.87 Mai | 0.08 | 1.28 | 1.36 Jun | 0.07 | 0.71 | 0.78 Jul | 0.10 | 0.82 | 0.92 Aug | 0.06 | 1.01 | 1.07 Sep | 0.08 | 1.09 | 1.18 Okt | 0.86 | 1.01 | 1.10 Nov | 0.04 | 1.17 | 1.21 Dez | 0.04 | 1.11 | 1.16 Gesamt: | 2.26 | 12.08 | 13.02 ![DSL Traffic IN/OUT/Summe](https://s3fs.kuepper.nrw/blog-images/posts/traffic_2018.webp) Wie auch schon im [letzten Jahr](https://blog.kuepper.nrw/2018/01/02/dsl-traffic-2017/) auch dieses Jahr wieder die DSL Traffik Statistik. Der Dennis hat seine [auch schon online](https://instant-thinking.de/2019/01/02/2018-in-vdsl-zahlen/). Und [Michel](https://pixelscheucher.de/vdsl-nutzung-2018/) so wie so. Die Grafiken dazu werden später noch nachgereicht. --- ## MPOW Bluetooth Headset Reset - URL: https://blog.kuepper.nrw/posts/2018-12-18-mpow-bluetooth-headset-reset/ - Datum: 2018-12-18 - Aktualisiert: 2018-12-18 - Autor: ruediger - Tags: Bluetooth, iPhone, Musik, Restore, Technik, Telefon, Zubehör - Kategorien: Technik Ich hatte 2016 ja [hier berichtet](https://blog.kuepper.nrw/2016/01/23/mpow-magneto-headset/): ich habe jetzt das MPOW Bluethoth Headset. Das benutze ich immer noch. ![MPOW Bluetooth Headset Reset](https://s3fs.kuepper.nrw/blog-images/posts/Mpow-Magneto-1.webp) Aus gegebenen Anlass mal ein Update zu dem Gerät. Funktioniert immer noch super und ich bin damit immer noch zufrieden. Vorgestern habe ich es einfach in den Rucksack geworfen und irgend etwas muss auf die Knöpfe gedrückt haben. Er blinkte nur noch wild. Gerade wollte ich einen Podcast hören und das Headset war nicht mehr gekoppelt. Mehrfach aus- und einschalten hat nicht geholfen. Wie Reset durchführen? Google wirft nur zig Anleitungen zum koppeln. Reset ist nirgendwo beschrieben. Ich hatte das Headset aber schon mal neu verbinden müssen. Aber erst einmal und das ist lange her und es war nur wegen neuem iDevice und nicht wegen einem Fehler. Es war etwas mit lange drücken. Also Power gedrückt, nichts. Die Taste für lauter ist es auch nicht. Es war die Leisetaste. Die einfach bei wild blinken 10 Sekunden gedrückt halten. Danach hört das blinken auf. Aber koppeln ging noch nicht. Dafür dann einfach ausschalten und wieder einschalten. Das Headset befindet sich dann im Pearingmodus. Am anderen Device taucht es dann auf, antippen und wird gekoppelt. --- ## DSGVO: temporäre Speicherung der IP Adressen bei Kommentaren - URL: https://blog.kuepper.nrw/posts/2018-07-23-dsgvo-temporaere-speicherung-der-ip-adressen-bei-kommentaren/ - Datum: 2018-07-23 - Aktualisiert: 2018-07-23 - Autor: ruediger - Tags: Blog, DSGvO, Internet, IP, IP-Adresse, Kommentare, Spam, Wordpress - Kategorien: Technik Mit der DSGVO ist die IP-Adresse zu schützen. Vorher wurde die IP auch nur temporär gespeichert. Nach der Freischaltung eines Kommentars wurden die IP Adressen bei den Kommentaren gelöscht. Mit der DSGVO habe ich einfach mal die IP-Adresse bei den Kommentaren komplett eliminiert und jetzt knapp 2 Monate versucht ohne auszukommen. Ergebnis: Scheisse. Früher war es so das Spammer versucht haben ihre Scheisse los zu werden. Dabei sind sie meistens wie folgt vorgegangen: 1. Kommentar gesendet. (z.B. von 123.123.123.123) 2. Minuten oder Stunden später kam dann der nächste Spam-Kommentar. wieder mit der gleichen IP Adresse. 3. Einige Zeit später ein weiterer Kommentar. 4. Spätestens jetzt habe ich dann die IP genommen, gegen ein Tool von mir geworfen und die IP landete in direkt in der Firewall in einer Blacklist. 5. Ruhe 6. Ohne geht das jetzt weiter bis dann 30 und mehr Spam-Kommentare das Blog zu müllen. Daher werden hier jetzt bei den Kommentaren die IP-Adressen wieder gespeichert und nach Freigabe oder Löschung der Kommentare die IP-Adressen aus der Datenbank entfernt. Das ganze ist natürlich in der Datenschutzerklärung abgedeckt. > ### Kommentare und Beiträge > > Wenn Nutzer Kommentare oder sonstige Beiträge hinterlassen, können ihre IP-Adressen auf Grundlage unserer berechtigten Interessen im Sinne des Art. 6 Abs. 1 lit. f. DSGVO für 7 Tage gespeichert werden. Zusätzlich werden Besucher beim Schreiben der Kommentare auf die Speicherung der IP-Adresse hingewiesen.  Icons made by [Freepik](http://www.freepik.com "Freepik") from [www.flaticon.com](https://www.flaticon.com/ "Flaticon") is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/ "Creative Commons BY 3.0") --- ## The iPhone X Index - URL: https://blog.kuepper.nrw/posts/2018-07-19-the-iphone-x-index/ - Datum: 2018-07-19 - Aktualisiert: 2018-07-19 - Autor: ruediger - Tags: Buy, Internet, iPhone, money, UBS - Kategorien: Internet Wer einfach mal wissen möchte wie lange er für ein iPhone arbeiten müsste. Oder sich einfach mal bewusst machen wie viel Glück die meisten von uns eigentlich haben ein Smartphone überhaupt zu haben. [howmuch.net](https://howmuch.net/articles/hours-work-buy-iphone) --- ## eBook React and React Native kostenlos - URL: https://blog.kuepper.nrw/posts/2018-07-19-ebook-react-and-react-native-kostenlos/ - Datum: 2018-07-18 - Aktualisiert: 2018-07-18 - Autor: ruediger - Tags: Application, Build-React, E-Book, epub, Facebooks-Relay, GraphQL, technologies, Internet, Platform, React, React-Native, Routing, Unified-Architecture, Web-Application - Kategorien: Musik Bei [packtpub.com](https://www.packtpub.com/packt/offers/free-learning) gibt es wieder das tägliche kostenlose E-Book. "React and React Native" heißt das heutige Buch und könnte für einige interessant sein. > React and React Native Use React and React Native to build applications for desktop browsers, mobile browsers, and even as native mobile apps Build React and React Native applications using familiar component concepts Dive deep into each platform, from routing in React to creating native mobile applications that can run offline Use Facebook's Relay, React and GraphQL technologies, to create a unified architecture that powers both web and native applications --- ## Twitter und Youtube/Vimeo Videos datenschutzgerecht eingebunden - URL: https://blog.kuepper.nrw/posts/2018-06-08-twitter-und-youtube-vimeo-videos-datenschutzgerecht-eingebunden/ - Datum: 2018-06-06 - Aktualisiert: 2018-06-06 - Autor: ruediger - Tags: datenschutz, dsgvo, Facebook, Internet, Social Network, Technik, Twitter, vimeo, wordpress, youtube - Kategorien: Technik Hier werden jetzt Videos von Youtube, Vimeo (Facebook würde auch gehen), sowie Tweets von Twitter datenschutzgerecht eingebunden.   [Heise.de](https://www.heise.de/newsticker/meldung/Embetty-Social-Media-Inhalte-datenschutzgerecht-einbinden-4060362.html) hat dafür [Embetty](https://github.com/heiseonline/embetty) und [Embetty Server](https://github.com/heiseonline/embetty-server) veröffentlicht. Beim Aufruf der Artikel werden die Videovorschau jetzt nicht mehr direkt bei Google und Co mit der IP der Besucher dieser Seite der abgerufen. Das wird jetzt alles über den Embetty Server gemacht. Embetty hängt also als Proxy zwischen euch und Youtube, Vimeo, Facebook und Twitter. Erst wenn ein Video angeklickt wird werden Daten an die Server der Anbieter gesendet. Im Wordpress Header wurde jetzt einfach der embetty Server hinzugefügt und im Webroot der Seite das embetty.js abgelegt: ``` ``` Tweet im Artikel einbinden: ``` ``` Ergebnis: ``` ``` Ergebnis: ``` git clone https://github.com/heiseonline/embetty-server.git cd embetty-server ``` docker-compose.yml ``` version: '3.1' services: server: image: heiseonline/embetty-server:latest ports: - 8089:8080 environment: - VALID_ORIGINS=http://localhost - TWITTER_ACCESS_TOKEN_KEY= - TWITTER_ACCESS_TOKEN_SECRET= - TWITTER_CONSUMER_KEY= - TWITTER_CONSUMER_SECRET= ``` Speichern .. ```docker-compose up -d``` und der Server rennt. --- ## Fender Squier Bullet Strat RW AW - URL: https://blog.kuepper.nrw/posts/2018-06-06-fender-squier-bullet-strat-rw-aw/ - Datum: 2018-06-06 - Aktualisiert: 2018-07-18 - Autor: ruediger - Tags: Guitar, Musik, Fender - Kategorien: Musik ### Neue Gitarre ![Fender Squier Bullet Strat RW AW](https://s3fs.kuepper.nrw/blog-images/posts/IMG_1116.webp) ![Fender Squier Bullet Strat RW AW](https://s3fs.kuepper.nrw/blog-images/posts/IMG_1123-e1455287450903-150x150.webp) ### Video --- ## Leckere BBQ-Soße einfach selbst gemacht. - URL: https://blog.kuepper.nrw/posts/2018-03-09-bbq-sose/ - Datum: 2018-03-09 - Aktualisiert: 2018-03-09 - Autor: ruediger - Tags: BBQ, Essen, Grillen, Kochen, Soße - Kategorien: Essen Leckere BBQ-Soße einfach selbst gemacht. * 1 Knoblauchzehe * 50 ml Apfelessig * 200 ml Apfelsaft * 200 g Tomatenketchup * 1 EL Sojasauce * 1 TL Worcestersauce * 4 EL Ahornsirup * 1-2 TL geräuchertes Paprikapulver * 1 EL Whiskey (Ich nehme immer gerne einen rauchigen/torfigen Whiskey) * Salz, Pfeffer Alle Zutaten ausser den Ahornsirup in einen Topf und 20 Minuten leicht köcheln lassen. Anschließend den Ahornsirup dazugeben und abschmecken. Das war es eigentlich schon. Jetzt muss sie Soße einfach nur noch weiter auf kleiner Stufe offen kochen. Keinen Deckel auf den Topf, da sonst das kondensierte Wasser wieder in die Soße tropft. Die Soße soll jetzt reduzieren und eine zähflüssige BBQ-Soße werden. --- ## Burger Buns - URL: https://blog.kuepper.nrw/posts/2018-03-09-burger-buns/ - Datum: 2018-03-09 - Aktualisiert: 2018-03-09 - Autor: ruediger - Tags: Backen, Burger Buns, Essen, Hamburgerbrötchen, Ofen - Kategorien: Essen Zutaten für Hamburgerbrötchen / Burger Buns * 200 ml warmes Wasser * 4 EL Milch * 1 Würfel Hefe * 35 g Zucker * 8 g Salz * 80 g Butter * 500 g Mehl Typ 550 * 1 Ei ![Burger Buns](https://s3fs.kuepper.nrw/blog-images/posts/img_0150.webp) Wasser und Zucker in eine Schüssel geben. Die Hefe zerbröselt dazugeben. Alles verrühren und für 5 Minuten ruhen lassen. Jetzt alle restlichen Zutaten dazu geben, gut vermischen und durchkneten. Kann auch in der Maschine gemacht werden. Nach ca. 5 Minuten sollte es einen geschmeidigen Teig. ![Burger Buns](https://s3fs.kuepper.nrw/blog-images/posts/img_0154.webp) Den Teig für eine Stunde an einem warmen Ort ruhen lassen. In dieser Zeit mache ich immer den "Klebstoff" für den Sesam. Dieser sollte Zimmertemperatur haben, da sonst die Buns beim bepinseln zusammen fallen könnten. * 1 Ei * 2 EL Milch * 2 EL Wasser * Sesam Ei, Wasser und Milch in einer kleinen Schale gut mit dem Schneebesen vermengen. Abdecken und beiseite stellen bis kurz bevor die Buns in den Ofen können. Nach einer Stunde ist der Teig gut aufgegangen und kann weiter verarbeitet werden. ![Burger Buns](https://s3fs.kuepper.nrw/blog-images/posts/img_0156.webp) Man nimmt jetzt ca 80-90 Gramm und formt den Teig in der Hand zu einer Kugel. Dabei ruhig ordentlich arbeiten. Das lässt die Buns nachher perfekt aussehen. Die Kugel auf das Blech legen und leicht herunter drücken. Aus dem Teig bekommt man zwischen 10 und 12 Buns. ![Burger Buns](https://s3fs.kuepper.nrw/blog-images/posts/img_0157-1.webp) Da diese Buns nicht für Burger gewesen sind, sondern für das [Pulled Pork](https://blog.pretzlaff.info/2018/03/09/pulled-pork/) sind sie nicht alle gleich gross und nicht perfekt auf 10 cm Durchmesser getrimmt. Alles darf jetzt wieder eine Stunde ruhen. Und werden jetzt wieder ein wenig wachsen. Daher nicht zu sparsam mit dem Platz sein. Später im Ofen werden sie dann auch noch grösser. Daher nicht zu eng auf das Blech legen. Ist die Stunde rum nimmt man das Ei-, Wasser- und Milchgemisch und verteilt mit einem Pinsel es auf die Buns. Jetzt noch Sesam auf die Buns und ab in den Ofen damit. Der Ofen wird auf 200 Grad Ober-/Unterhitze vorgeheizt. Die Buns bleiben für 15-20 Minuten im Ofen. Sie sollten oben Goldbraun sein. ![Burger Buns](https://s3fs.kuepper.nrw/blog-images/posts/img_0159.webp) Die fertigen Buns sind super lecker und schön fluffig. --- ## Pulled Pork - URL: https://blog.kuepper.nrw/posts/2018-03-09-pulled-pork/ - Datum: 2018-03-09 - Aktualisiert: 2018-03-09 - Autor: ruediger - Tags: Alle Beiträge, BBQ, Essen, Essen, Grillen, Kochen, Ofen, Pulled Pork - Kategorien: Essen #### Zutaten * 2,5 Kilo Schweinenacken ohne Knochen * ca. 250 ml Ananassaft * ca. 250 ml kräftige Gemüsebrühe * 3 EL brauner Zucker * 4 EL Senf * 3 EL Paprikapulver, edelsüß * 1,5 EL Salz * 1,5 EL Puderzucker * 1 EL Pfeffer * 1,5 EL Kreuzkümmel * 1,5 EL Knoblauchgranulat * 1,5 EL Cayennepfeffer #### Zubereitung 1. Alle Gewürze von Paprikapulver miteinander vermischen. 2. Fleisch mit dem Senf einreiben. 3. Anschliessend der trockenen Gewürzmischung sorgfälltig einreiben. Je sorgfältiger desto Besser wird es nachher schmecken. 4. Das Fleisch in einen Beutel packen Luft heraussaugen oder drücken, luftdicht verschliessen und für 24 Stunden in den Kühlschrank. ![Pulled Pork](https://s3fs.kuepper.nrw/blog-images/posts/img_5073-300x225.webp) Am nächsten Tag rechtzeitig das Fleisch aus dem Kühlschrank nehmen und bevor es in den Ofen kommt die Zimmertemperatur annehmen lassen. 1. Ofen auf 110 Grad vorheizen. Ober-/Unterhitze auf keinen Fall Umluft. 2. Ein Auflaufform oder Bräter der unter das Rost passt mit 250 ml Annanassaft, 250 ml Orangensaft ud 250 ml Brühe und den 3 EL braunen Zucker füllen. 3. Bräter/Auflauform in den Ofen und auf dem Gitter darüber das Fleisch legen. 4. Jetzt heisst es warten, warten, warten. 5. Wenn das Fleisch ein Kerntempertur von 65-70 Grad hat mit dem Sud aus dem Bräter mit einem Pinsel bestreichen. Wenn nötig ruhig Annanas und Co nachschütten. Wichtig. Immer Ruhe bewaren. Das Fleisch wird am anfang recht schnell hoch gehen. Wird dann aber Plateaus haben in denen nichts mehr passiert. Meistens bei ca. 65 Grad. Es kommt auch vor das später auch noch mal bei ca 75-78 Grad ein zweites Plateau kommt. Die Plateaus können auch schon mal bis zu 2 Stunden dauern bis es weiter geht. Also abwarten und nicht die Temperatur hoch drehen. Das wird schon. ![Pulled Pork](https://s3fs.kuepper.nrw/blog-images/posts/img_5078-1024x768.webp) Wenn 20-21 Stunden rum sind kann man auch schon einmal mit der [BBQ-Sosse](https://blog.pretzlaff.info/2018/03/09/bbq-sosse/) beginnen. Wer zum Pulled Pork auch [Buns](https://blog.pretzlaff.info/2018/03/09/burger-buns/) haben möchte auch diese. Beides dauert 2-3 Stunden. Aber reine Arbeitszeit ist fast gar keine. Dabei heisst es auch die meiste Zeit warten, warten, warten. Wird aber auch beides genau so belohnt wie das Pulled Pork. Unverschämt lecker. Wenn das Fleisch 95 Grad hat ist es fertig. Es kann jetzt aus dem Ofen genommen werden und auseinander gezupft werden. Es sollte jetzt aber schon von selbst auseinder fallen. Dafür nehme ich immer ein Backofenblech und lege das Fleisch drauf. Einfach mit 2 Gabeln  auseinander ziehen. Anschliessend gibt man noch etwas vom Sud und etwas von der [BBQ-Sosse](https://blog.pretzlaff.info/2018/03/09/bbq-sosse/) dazu. Fertig ist das Pulled Pork und kann gegessen werden. ![Pulled Pork](https://s3fs.kuepper.nrw/blog-images/posts/img_0161.webp) Beim schreiben dieses Artikels ist aufgefallen das vom fertigen Pulled Pork bis jetzt nur das Bild existiert wo die Schale schon fast leer war. Beim nächsten mal muss ich die hunrige Meute davon fern halten. Die fahren halt alle voll drauf ab. --- ## DSL Graph mit Python - URL: https://blog.kuepper.nrw/posts/2018-01-02-dsl-graph-mit-python/ - Datum: 2018-01-02 - Aktualisiert: 2018-01-02 - Autor: ruediger - Tags: Internet, Linux, MacOS, Programming, Python - Kategorien: Technik Da heute ja [hier](https://blog.pretzlaff.info/2018/01/02/dsl-traffic-2017/) Graphen zum Traffic im Jahr 2016 veröffentlicht wurde hier auch gleich eines der Scripts. Datenquelle: fb.csv ``` Monat;Gesendet;Empfangen;Gesamt Jan;4124;16815;20939 Feb;1078;403;1481 Mar;1199;446;1645 Apr;2464;36476;38940 Mai;92979;615268;708247 Jun;138402;664743;803145 Jul;116406;507155;623561 Aug;35654;471810;507464 Sep;31362;428400;459762 Okt;24072;549927;573999 Nov;44095;914362;958457 Dez;57889;1141699;1199588 Gesamt:;549724;5347504;5897228 ``` Benötigte python Pakete: * pygal * cairosvg `pip install pygal pip install cairosvg` bzw. `apt-get install -y python-cairosvg python-pygal` Script welches die Daten ausliest und die Grafik erstellt: ``` #!/bin/bash OUT=`for i in $(tail -n 13 fb.csv | head -n 12 | awk -F";" {'print $2'} ); do echo -n "$i "; done | sed -e 's/\ $//' | sed -e 's/\ /, /g'` IN=`for i in $(tail -n 13 fb.csv | head -n 12 | awk -F";" {'print $3'} ); do echo -n "$i "; done | sed -e 's/\ $//' | sed -e 's/\ /, /g'` SUM=`for i in $(tail -n 13 fb.csv | head -n 12 | awk -F";" {'print $4'} ); do echo -n "$i "; done | sed -e 's/\ $//' | sed -e 's/\ /, /g'` echo " import pygal bar_chart = pygal.Bar() bar_chart.x_labels = 'Jan', 'Feb', 'Mar', 'Apr', 'Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez' bar_chart.add('Eingehend', [$IN ]) bar_chart.add('Ausgehend', [$OUT ]) bar_chart.add('Gesamt', [$SUM ]) bar_chart.render_to_file('output.svg') " > generate.py ``` Ausführen: ``` /usr/bin/python generate.py ``` oder ausführbar machen und ausführen: ``` chmod +x generate.sh ./generate.sh ``` Ergebnis: [![PyGal output](https://s3fs.kuepper.nrw/blog-images/posts/output.svg)](-/imgages/posts/output.svg) --- ## DSL Traffic 2017 - URL: https://blog.kuepper.nrw/posts/2018-01-02-dsl-traffic-2017/ - Datum: 2018-01-02 - Aktualisiert: 2018-01-02 - Autor: ruediger - Tags: Internet, Traffic, 2017 - Kategorien: Technik Unser DSL-Traffik 2017 Angabe in Megabytes Monat | Gesendet | Empfangen | Gesamt --------|----------|-------------|------- Jan | 4124 | 16815 | 20939 Feb | 1078 | 403 | 1481 Mär | 1199 | 446 | 1645 Apr | 2464 | 36476 | 38940 Mai | 92979 | 615268 | 708247 Jun | 138402 | 664743 | 803145 Jul | 116406 | 507155 | 623561 Aug | 35654 | 471810 | 507464 Sep | 31362 | 428400 | 459762 Okt | 24072 | 549927 | 573999 Nov | 44095 | 914362 | 958457 Dez | 57889 | 1141699 | 1199588 Gesamt: | 549724 | 5347504 | 5897228 Anfang des Jahres war der Traffik hauptsächlich über den anderen Anschluss und taucht hier in der Statistik nicht auf. Im März und April war noch Renovierungszeit und es wurde weniger gestreamt. Wir haben uns nach der Arbeit eher selbst nur noch in Bett gestreamt. Im Oktober wurde das 1. OG mit aufgeklemmt und gepatched. Daher dann ab Ende Oktober der Anstieg des Traffiks. In den nächsten Tagen kommt noch das EG mit dazu, dann ist das Netzwerk endlich komplett. Im 2. und 3. OG ist schon alles fertig. 2. und 3. OG haben jeweils 2 x 16 Port Switche. Beide Etagen sind mit 4 Gigabit (LAG) verbunden. Im 2. OG ist dann noch der AccessPoint und ein Cisco 10 Port für Layer 3, der die unteren Etagen mit anbindet. Das 1. OG ist über den Cisco Switch mit 2 Leitungen (LAG) zum dort verbauten Cisco Switch verbunden. Das gleiche wird dann bald auch noch jeweils aus dem 1. und 2. OG in das EG gemacht. So das 4 Leitungen (LAG) 4 Gigabit theoretisch im ganzen Haus können. Wenn das gemacht ist werden alle Geräte über 2 Leitungen ins Internet gehen und die Jahresstatistik nächstes Jahr wird beide Anschlüsse beinhalten. Aber hier jetzt erst einmal die Graphen von 2017 mit nur einem Anschluss. Ein- und ausgehender Traffic: ![In/Out Stacked](https://s3fs.kuepper.nrw/blog-images/posts/inoutstacked.svg) Ein- und ausgehender Traffic (Summe) ![Traffic](https://s3fs.kuepper.nrw/blog-images/posts/sum.svg) Ein- und ausgehender Traffic: ![In/Outgoing Traffic](https://s3fs.kuepper.nrw/blog-images/posts/inout.svg) Ausgehender Traffic: ![Outgoing Traffic](https://s3fs.kuepper.nrw/blog-images/posts/outgoing.svg) Eingehender Traffic: ![Incoming Traffic](https://s3fs.kuepper.nrw/blog-images/posts/incomming.svg) Danke an Dennis für [den Hinweis](https://instant-thinking.de/2018/01/02/2017-in-vdsl-zahlen/) das ein Jahr wieder rum ist. --- ## PHP in AWS Lambda Function ausführen - URL: https://blog.kuepper.nrw/posts/2017-09-19-php-in-aws-lambda-function-ausfuehren/ - Datum: 2017-09-19 - Aktualisiert: 2017-09-19 - Autor: ruediger - Tags: Application, aws, Azure, Cloud, Google Cloud Computing, Internet, Internet, nodeJS, perl, php, programming - Kategorien: Internet Bei der Transformation in der Cloud möchte man manchmal schnell kleine Scripts in die Cloud bringen ohne erst Server oder Container in der Cloud zu installieren. In vielen Fällen reichen Funktionen wie AWS-Lambda-Functions, Azure Functions oder Google Cloud Functions. AWS Lambda bietet dafür in Lambda folgende (Script)-Sprachen an: * C# * Java * python * nodejs Gerade in älteren Umgebungen werden oft Perl und PHP für kleinere Webscripte, die Applikationen zum Austausch von Informationen benutzen eingesetzt. Klar, diese Scripte sollte man teilweise überdenken und erneuern. Das kostet zum einen Geld, jemand muss es machen und manche sollen auch nur noch bei dem Sprung in die Cloud helfen und dann eh das zeitliche segnen. Daher wäre es nicht schlecht diese Tools schnell mit in die Cloud zu nehmen um andere Software so in die Cloud zu bekommen und die Tools nicht als Spassbremse zu haben. nodejs und statisch kompliliertes php ------------------------------------- Da Lambda nodejs anbietet kann man es auch dazu nötigen PHP auszuführen. Ja, würde ich es jetzt auch lesen oder es von jemanden hören, ich würde mich jetzt genau so schütteln. Aber es soll ja nur den Sprung in die Cloud ermöglichen und zeigen wie man solche Probleme umgehen kann. PHP Binary erstellen -------------------- Um das PHP statisch als ein Binary vorliegen zu haben bietet sich Docker an. build\_php\_7.sh ``` #!/bin/sh PHP_VERSION_GIT_BRANCH=PHP-7.1.1 echo "Build PHP Binary from current branch '$PHP_VERSION_GIT_BRANCH' on https://github.com/php/php-src" docker build --build-arg PHP_VERSION=$PHP_VERSION_GIT_BRANCH -t php-build -f Dockerfile.BuildPHP . container=$(docker create php-build) docker -D cp $container:/root/php7/usr/bin/php ./php docker rm $container ``` Das stellt einen Container der PHP 7.7.1 zusammensetzt und anschliessend das fertige Binary aus den Docker Container kopiert. Wer eine andere Version benötigt kann die Version mit PHP\_VERSION\_GIT\_BRANCH setzen. PHP Script ---------- ``` ``` index.js spawn für php ---------------------- Damit der PHP Interpreter und ein Script aufgerufen werden benötigt man nur noch etwas JavaScript: ``` 'use strict'; var child_process = require('child_process'); exports.handler = function(event, context) { var strToReturn = ''; var proc = child_process.spawn('./php', [ "index.php", JSON.stringify(event), { stdio: 'inherit' } ]); proc.stdout.on('data', function (data) { var dataStr = data.toString() console.log('stdout: ' + dataStr); strToReturn += dataStr }); proc.on('close', function(code) { if(code !== 0) { return context.done(new Error("Process exited with non-zero status code")); } context.succeed(strToReturn); }); } ``` Alles einpacken und verschiffen ------------------------------- Jetzt nur noch ein Zip-File `aws-lambda-php-example.zip` erstellen mit den Dateien: * index.js * index.php * php zip aws-lambda-php-example.zip index.js index.php php In AWS eine neue Lambda Function erstellen mit folgenden Parametern: * Type: nodeJS * RAM: 128mb * Timeout: 3 seconds Den JavaScript Code nicht in den Online Editor kopieren, sondern Upload auswählen und das komplette ZIP-File hochladen und an der Lambda Function anhängen. Alternativ im S3 ablegen und aus dem Bucket heraus laden lassen Benötigt man die Schnittstelle per HTTP von extern und/oder anderen Instanzen kann man auch noch das API-Gateway von Amazon hinzuziehen. Spassbremse umgangen -------------------- Das Script stört nicht mehr die weitere Transformation und alle anderen Softwarebrocken können ihren Weg in die Cloud beschreiten. Die Teams, die solche Softwarestückchen einmal in schön abliefern müssen, können dies dann noch später nachholen. Haben aber auch erst einmal Zeit für die grösseren Cloud-Projekte. Wer aus "Gründen" nicht sofort eine komplette Software in die Cloud bringen kann sollte sich das API-Gateway einmal genauer angucken. Path und Method lassen sich damit sehr gut trennen. Einzelne Aufrufe lassen sich so auf die alte Software oder die neue Software leiten. Natürlich nur, so lange so etwas mit einer Software möglich ist und intern keine Abhängigkeiten bestehen. Ist es möglich steht einer Migration einzelner Funktionen nichts im Wege. Rollback inklusive. --- ## Atom Editor hinter Proxy - URL: https://blog.kuepper.nrw/posts/2017-09-18-atom-editor-hinter-proxy/ - Datum: 2017-09-18 - Aktualisiert: 2017-09-18 - Autor: ruediger - Tags: Free, Installation, Internet, Internet, Linux, MacOS, Programming - Kategorien: Internet Da will man sich im [Atom Editor](https://atom.io) mal eben das Package "script" installieren, um Code zum testen direkt im Atom auszuführen, da stellt sich der Proxy mal wieder in den Weg. ``` apm config set https-proxy http://proxy.example.com:3128 apm config set http-proxy http://proxy.example.com:3128 ``` Atom wieder öffnen und Package installieren. --- ## AWS Lambda serverless Framework und serverless-offline - URL: https://blog.kuepper.nrw/posts/2017-09-14-aws-lambda-serverless-tool-plus-erverless-offline/ - Datum: 2017-09-14 - Aktualisiert: 2017-09-14 - Autor: ruediger - Tags: AWS, Cloud, Internet, Javascript, NodeJS, npm, Programming, Serverless - Kategorien: Internet nodejs, npm, serverless und serverless-offline installieren =========================================================== * [serverless](https://github.com/serverless/serverless) * [serverless-offline](https://github.com/dherault/serverless-offline) * [NodeJS](https://nodejs.org/en/) Installation nodejs ------------------- ``` $ sudo apt-get install curl python-software-properties $ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - $ sudo apt-get install nodejs $ node -v v8.2.1 $ npm -v 5.3.0 ``` Installation serverless ----------------------- ``` npm install -g serverless ``` serverless service erstellen ============================ Project erstellen ----------------- ``` # Ein neues Serverless Service/Project erstellen serverless create --template aws-nodejs --path my-service # In das neue Verzeichnis wechseln cd my-service ``` AWS Access-Key und Secret ------------------------- ``` export AWS_ACCESS_KEY_ID= export AWS_SECRET_ACCESS_KEY= serverless deploy ``` oder ``` serverless config credentials --provider aws --key AKIAIOSFODNN7EXAMPLE --secret wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY ``` Deploy Service -------------- ``` serverless deploy -v ``` Die Logs der Function abrufen: ------------------------------ ``` serverless invoke -f hello -l ``` Service in AWS entfernen ------------------------ ``` serverless remove ``` mysql RSD DB mit NodeJS in Lambda benutzen ========================================== Als erstes im Projectverzeichnis (my-project) das mysql plugin installieren ``` npm install mysql ``` Im Code mysql hinzufügen und den restlichen mysql java-script Code: ``` var mysql = require('mysql'); module.exports.view = (event, context, callback) => { const ip = event.requestContext.identity.sourceIp; var connection = mysql.createConnection({ host: 'YourRdsDB.xxxxxxx.eu-central-1.rds.amazonaws.com', user: 'DBUser', password: 'DBPass', database: 'DBname' }); connection.connect(); // .... } ``` Serverless offline ohne AWS benutzen ==================================== Serverless-offline installieren ------------------------------- ``` npm install serverless-offline --save-dev ``` ### Hinter einem Cooperate-Proxy ggf. auch noch mit Auth? Einfach npm mit config set die Parameter für https-proxy und proxy setzen. ``` npm config set proxy http://"username:gehe\!m"@proxy.example.com:3128 npm config set https-proxy http://"username:gehe\!m"@proxy.example.com:3128 ``` Sonderzeichen wie das '!' hier im Beispiel müssen escaped werden. Und Benutzer und Password müssen komplett in " gesetzt werden. Das Verzeichnis "node\_modules" sollte jetzt ungefähr so aussehen und unter anderem serverless-offline auflisten: ``` ls node_modules/ accept babel-helpers balanced-match content esutils home-or-tmp json5 mimos os-homedir serverless-offline supports-color ammo babel-messages boom convert-source-map globals invariant jsonpath-plus minimatch os-tmpdir shot to-fast-properties ansi-regex babel-register brace-expansion core-js h2o2 iron js-string-escape minimist path-is-absolute slash topo ansi-styles babel-runtime call cryptiles hapi isemail js-tokens mkdirp peekaboo source-map trim-right b64 babel-template catbox crypto hapi-cors-headers is-finite kilt moment pez source-map-support velocityjs babel-code-frame babel-traverse catbox-memory debug has-ansi items lodash ms private statehood vise babel-core babel-types chalk detect-indent heavy joi loose-envify nigel regenerator-runtime strip-ansi wreck babel-generator babylon concat-map escape-string-regexp hoek jsesc mime-db number-is-nan repeating subtext ``` PlugIn in der serverless.yml hinzufügen --------------------------------------- ``` plugins: - serverless-offline ``` Überprüfen ob das PlugIn verfügbar ist -------------------------------------- ``` serverless ``` Die Ausgabe sollte unter commands jetzt zusätzlich "offline" und "offline-start" auflisten ``` ... logs .......................... Output the logs of a deployed function metrics ....................... Show metrics for a specific function offline ....................... Simulates API Gateway to call your lambda functions offline. offline start ................. Simulates API Gateway to call your lambda functions offline using backward compatible initialization. package ....................... Packages a Serverless service remove ........................ Remove Serverless service and all resources ... ``` In der letzten Zeile werden alle verfügbaren PlugIns aufgelistet und "Offline" sollte dort auch aufgelistet werden. ``` Plugins AwsCommon, AwsCompileAlexaSkillEvents, AwsCompileApigEvents, AwsCompileCloudWatchEventEvents, AwsCompileCloudWatchLogEvents, AwsCompileCognitoUserPoolEvents, AwsCompileFunctions, AwsCompileIoTEvents, AwsCompileS3Events, AwsCompileSNSEvents, AwsCompileScheduledEvents, AwsCompileStreamEvents, AwsConfigCredentials, AwsDeploy, AwsDeployFunction, AwsDeployList, AwsInfo, AwsInvoke, AwsInvokeLocal, AwsLogs, AwsMetrics, AwsPackage, AwsProvider, AwsRemove, AwsRollback, AwsRollbackFunction, Config, Create, Deploy, Emit, Info, Install, Invoke, Login, Logout, Logs, Metrics, Offline, Package, Platform, Remove, Rollback, Run, SlStats ``` Projekt offline starten ----------------------- `serverless offline start` or `sls offline start`. ``` serverless offline start Serverless: Starting Offline: dev/us-east-1. Serverless: Routes for hello: Serverless: (none) Serverless: Offline listening on http://localhost:3000 ``` Service testen -------------- Dabei an den Proxy denken und den Parameter `--noproxy` setzen: ``` curl --noproxy "127.0.0.1, localhost" http://localhost:3000 ``` Parameter von serverless-offline -------------------------------- ``` serverless offline --help --prefix -p Adds a prefix to every path, to send your requests to http://localhost:3000/[prefix]/[your_path] instead. E.g. -p dev --location -l The root location of the handlers' files. Defaults to the current directory --host -o Host name to listen on. Default: localhost --port -P Port to listen on. Default: 3000 --stage -s The stage used to populate your templates. Default: the first stage found in your project. --region -r The region used to populate your templates. Default: the first region for the first stage found. --noTimeout -t Disables the timeout feature. --noEnvironment Turns off loading of your environment variables from serverless.yml. Allows the usage of tools such as PM2 or docker-compose. --resourceRoutes Turns on loading of your HTTP proxy settings from serverless.yml. --dontPrintOutput Turns off logging of your lambda outputs in the terminal. --httpsProtocol -H To enable HTTPS, specify directory (relative to your cwd, typically your project dir) for both cert.pem and key.pem files. --skipCacheInvalidation -c Tells the plugin to skip require cache invalidation. A script reloading tool like Nodemon might then be needed. --corsAllowOrigin Used as default Access-Control-Allow-Origin header value for responses. Delimit multiple values with commas. Default: '*' --corsAllowHeaders Used as default Access-Control-Allow-Headers header value for responses. Delimit multiple values with commas. Default: 'accept,content-type,x-api-key' --corsDisallowCredentials When provided, the default Access-Control-Allow-Credentials header value will be passed as 'false'. Default: true --exec "