Ich betreibe Server und entwickle die Software, die darauf läuft.

System- und Cloud-EngineeringDE / FR / EN
Projekte ansehenKontakt
Porträt von Ngambo Kepawou Stephane Guerrin
B.Sc. Angewandte Informatik, HS RheinMain 2025

Über mich

Ich bin 1996 in Maroua, Kamerun, geboren und in Douala aufgewachsen. 2013 bin ich zum Studium nach Deutschland gekommen. Zuerst kamen drei Jahre Deutsch, eins in Kamerun und zwei hier, dann das Studium.

Neben dem Studium habe ich drei Jahre bei AOE in der IT gearbeitet: Konten und Rechte in Entra ID, PowerShell-Skripte, Jenkins. Dort ist auch meine Bachelorarbeit entstanden. Seba baue und betreibe ich allein, vom Server bis zur App.

Wer mich kennt, beschreibt mich als ruhig, strukturiert und hilfsbereit. Ich höre lieber zu, als viel zu reden, und ich halte Ordnung, in Systemen wie im Code.

Außerhalb der Arbeit

Reisen
Neue Orte und Kulturen kennenlernen und sehen, wie andere Menschen leben.
Sport
Amateurboxen und Krafttraining. Das hält mich fit und diszipliniert.
Alte Texte
Geschichte und Anthropologie ziehen mich an, also lese ich alte Texte, um frühere Zivilisationen zu verstehen.
Musik
Afrikanische Musik in der jeweiligen Landessprache, dazu Zouk, Funk, Soul und R&B.

Erfahrung

12/2025 bis 05/2026

IT Support Specialist

Clarius Services, Mainz
  • Support im 1st und 2nd Level für Windows-Clients und Microsoft 365
  • Fragen zu Konten, Zugriffen und Lizenzen in Microsoft 365
  • Lösungen und wiederkehrende Störungen in der Wissensdatenbank dokumentiert
05/2022 bis 09/2025

Werkstudent IT

AOE GmbH, Wiesbaden
  • Benutzerkonten, Gruppen und Rechte in Azure AD (Entra ID) verwaltet
  • Wiederkehrende Admin-Aufgaben mit PowerShell automatisiert
  • Jenkins-Pipelines für Build und Deployment eingerichtet und betreut
  • Technische Dokumentation zu Systemen und Abläufen geschrieben
2019 bis 2025

B.Sc. Angewandte Informatik

Hochschule RheinMain, Wiesbaden

Bachelorarbeit bei AOE: ein IT-Support-Assistent, der lokal aus Helpdesk-Tickets antwortet.

2016 bis 2019

Grundstudium Informatik

TU Kaiserslautern

Grundlagen der Informatik, danach der Wechsel an die Hochschule RheinMain.

2013 bis 2016

Deutsch als Fremdsprache

Kamerun und Deutschland

Ein Jahr in Kamerun, zwei in Deutschland, dann der Zugang zum Studium.

2006 bis 2013

Schule

Lycée de la cité des palmiers, Douala, Kamerun

Skills

Betrieb und Cloud

  • Linux
  • nginx
  • PM2
  • Docker
  • Hetzner
  • Azure AD (Entra ID)
  • Microsoft 365
  • Intune
  • Windows Server
  • VMware
  • Sophos
  • Kubernetes

Automatisierung und CI

  • PowerShell
  • Bash
  • Groovy
  • GitLab CI
  • Jenkins
  • n8n
  • Git

Daten

  • PostgreSQL
  • Redis
  • MongoDB
  • ChromaDB
  • Firebase

Sprachen und Frameworks

  • TypeScript
  • JavaScript
  • Python
  • Java
  • SQL
  • NestJS
  • React
  • React Native
  • Vue 3
  • Angular
  • Flask
  • LlamaIndex
  • Ollama
  • Unity
  • C#
  • .NET

Konzepte

  • REST
  • Clean Architecture
  • DDD
  • Multi-Tenancy
  • RBAC
  • LDAP
  • SAML
  • MDM
  • RAG
  • gRPC

Heller geschrieben ist, was ich täglich nutze. Gedämpft steht, womit ich gearbeitet habe, aber nicht jeden Tag.

Projekte

Seba

Schulverwaltung für Kamerun

Jede Schule hat ihre eigene Adresse und ihre eigenen Daten. Seba deckt Einschreibung, Anwesenheit, Noten, Zeugnisse, Gebühren und Nachrichten ab, auf Französisch und Englisch, weil in Kamerun beide Schulsysteme nebeneinander laufen.

Ich habe das Backend, die Web-App und die Eltern-App geschrieben und betreibe den Server bei Hetzner selbst. Der Code ist nicht öffentlich. Deshalb zeige ich hier, was im System passiert, wenn eine Schule ein Kind einschreibt, und danach, wie der Server abgesichert ist.

NestJSTypeScriptPostgreSQLRedisVue 3React NativenginxPM2HetznerGitLab CI
sebamun.com besuchen
Eine Einschreibung im Sekretariat: nginx, eine Kette von Guards und der Use Case, jede Stufe kann mit eigenem Statuscode ablehnen. Danach eine Transaktion und eine Matrikelnummer, mit der die Eltern ihr Konto verknüpfen.
Eine Einschreibung im Sekretariat: nginx, eine Kette von Guards und der Use Case, jede Stufe kann mit eigenem Statuscode ablehnen. Danach eine Transaktion und eine Matrikelnummer, mit der die Eltern ihr Konto verknüpfen.
1 / 5

Eine Einschreibung im Sekretariat

Eine Familie steht im Sekretariat. Die Sekretärin trägt Name, Geburtsdatum, Stufe, Klasse, mindestens einen Elternteil und einen Notfallkontakt ein und klickt auf Speichern. Ihr Browser schickt POST /api/students.

Schüler anlegen darf nur die Schulleitung oder eine Verwaltungskraft, der die Schulleitung dieses Recht gegeben hat. Lehrkräfte und Eltern können es nicht.

2 / 5

Welche Schule, welches Konto?

nginx nimmt die Anfrage an und begrenzt die Rate, helmet und CORS lassen nur die eigenen Origins durch. Danach liest Seba aus dem Host-Header, um welche Schule es geht, und legt sie für den Rest der Anfrage fest.

Das Token der Sekretärin muss zu genau dieser Schule gehören. Mit dem Token einer anderen Schule antwortet der Server mit 401.

3 / 5

Darf sie das, und passen die Angaben zusammen?

Die ValidationPipe entfernt jedes Feld, das nicht im DTO steht. Eine Matrikelnummer kann der Browser deshalb gar nicht mitschicken, die vergibt der Server.

Die Berechtigung prüft der Use Case selbst. GrantLoader lädt die Rechte der Sekretärin und verlangt CREATE:STUDENT. Danach prüft er, ob die Klasse zur Stufe gehört, die Stufe zum Zyklus passt und die Schule ein Schuljahr geöffnet hat.

4 / 5

Eine Transaktion für alles

Jedes Kind bekommt eine eigene Matrikelnummer, zum Beispiel MAT-2026-0042. Eine Sperre auf der Zählerzeile sorgt dafür, dass zwei gleichzeitige Einschreibungen nicht dieselbe Nummer bekommen.

In derselben Transaktion schreibt Seba den Schüler und seine Einschreibung für das Jahr. Scheitert ein Schritt, wird alles zurückgerollt, und es bleibt keine halbe Akte liegen.

5 / 5

Was die Familie mitnimmt

Nach dem Commit legt Seba das Gebührenblatt an und schreibt einen Eintrag ins Audit-Log. Hat die Schule die Gebühren dieser Stufe noch nicht eingetragen, scheitert nur das Gebührenblatt. Die Einschreibung bleibt gültig, denn eine fehlende Einstellung soll keine Familie wieder nach Hause schicken.

Die Sekretärin druckt den Eltern einen Zettel mit der Matrikelnummer. Bei der Registrierung in der App geben die Eltern die Nummern ihrer Kinder ein. Konto und Verknüpfung entstehen in einer Transaktion, und ist eine Nummer unbekannt oder schon vergeben, legt Seba gar kein Konto an.

Seba: ein Kind einschreibenPOST /api/students
RANDSCHULE UND KONTORECHT UND PLAUSIBILITÄTTRANSAKTIONNACH DEM COMMITnginxTLS, HSTS, 30 Anfragen/s auf /api/429helmet + CORSCSP, nosniff, feste Origin-ListeblockedTenantMiddlewareHost-Header → Schule401ThrottlerGuardRedis, 300/min pro IP429JwtAuthGuardSchule im Token = Schule im Host401PermissionGuard@AuthorizedInUseCase403ValidationPipeWhitelist, keine Matrikelnummer im DTO400CreateStudentUseCaseCREATE:STUDENT, Klasse, Stufe, Schuljahr403 / 400PostgreSQLMatrikel, Schüler, EinschreibungrollbackGebührenblatt + Auditein Fehler kippt die Einschreibung nicht201 CreatedMatrikelnummer für den Elternzettel
Vom Klick bis zum Elternzettel

Jede Stufe darf ablehnen, und die erste, die es tut, beantwortet die Anfrage. Fahr über eine Stufe oder tippe sie an.

Der Server

Seba läuft auf einem VPS bei Hetzner: Produktion, eine Demo für Interessenten und die Marketing-Seite sebamun.com. Jede Maßnahme ist als Skript im Repository versioniert.

Von oben nach unten: was zwischen dem Internet und den Daten der Schulen steht.

1 / 6
Überblick: vier Schichten zwischen dem Internet und den Schuldaten (Netz, Eingang, minimale Rechte, Secrets und Backups). Die Überwachung beobachtet alle.
Überblick: vier Schichten zwischen dem Internet und den Schuldaten (Netz, Eingang, minimale Rechte, Secrets und Backups). Die Überwachung beobachtet alle.
1 / 6

Ein Server, drei Anwendungen

Auf der Maschine laufen Produktion, Demo und Marketing-Seite als eigene Node-Prozesse, dazu PostgreSQL und Redis. Der Schutz ist in Schichten aufgebaut: Netz, Eingang, Rechte, Daten, Überwachung. Jede Schicht wirkt unabhängig von den anderen.

2 / 6

Netz und SSH

ufw lehnt standardmäßig jede eingehende Verbindung ab und erlaubt nur SSH, HTTP und HTTPS. SSH akzeptiert nur Schlüssel, keinen Login als root und höchstens drei Versuche. fail2ban sperrt Adressen nach wiederholten Fehlversuchen.

Die SSH-Härtung liegt als eigene Datei in sshd_config.d und wird mit sshd -T gegen die geladene Konfiguration geprüft.

3 / 6

nginx als einziger Eingang

nginx übernimmt TLS, setzt HSTS, CSP und die übrigen Sicherheits-Header und begrenzt die Rate: 30 Anfragen pro Sekunde auf /api/, 2 auf /auth/. Dahinter zählt die App selbst noch einmal, in Redis.

Die drei Node-Prozesse hören nur auf 127.0.0.1. Die Demo ist zusätzlich durch Basic-Auth geschützt.

4 / 6

Minimale Rechte

Drei Linux-Konten mit getrennten Aufgaben. Das erste hat volle Rechte und administriert den Server, führt aber keine Anwendung aus. Das zweite führt Produktion und Demo aus, ohne sudo und ohne den eigenen Code ändern zu können. Das dritte führt nur die Marketing-Seite aus und kann die Secrets der anderen nicht lesen.

Dasselbe Prinzip in der Datenbank: Die Rolle der Marketing-Seite darf das Schulverzeichnis lesen und eine Anfrage eintragen, sonst nichts.

5 / 6

Secrets und Backups

Die .env-Dateien gehören root, die App darf sie nur lesen, und sie liegen nicht im Git. Secrets werden per Skript gewechselt, Redis verlangt ein Passwort.

Jede Nacht: pg_dump, Verschlüsselung mit age, Upload per rclone nach Backblaze B2. Der Server kennt nur den öffentlichen Schlüssel und kann seine Backups weder lesen noch löschen. Die Wiederherstellung ist dokumentiert und getestet.

6 / 6

Überwachung und Updates

healthping prüft alle fünf Minuten die drei Anwendungen, den Plattenplatz und die Zertifikate und meldet sich nur, wenn alles passt. Bleibt die Meldung aus, kommt der Alarm von außen. Logs und Metriken laufen über Grafana, Loki und Prometheus, alles auf 127.0.0.1.

Sicherheitsupdates spielt unattended-upgrades ein, in der CI blockiert yarn npm audit Abhängigkeiten ab Schweregrad high. Die Ops-Skripte sind wiederholbar, laufen ohne --apply nur zur Probe, sichern vor dem Schreiben und haben einen Rollback.

Seba: der ServerHetzner VPS, Ubuntu
NETZEINGANGMINIMALE RECHTESECRETS UND DATENÜBERWACHUNGufwalles zu, nur 22, 80 und 443 offendenysshdnur Schlüssel, kein root, 3 Versuchepublickeyfail2banSSH-Jail, Sperren bis zu einer WochebannginxTLS (Let's Encrypt), HSTS, CSPHSTSRatenbegrenzung30 r/s auf /api/, 2 r/s auf /auth/429Node-Prozessedrei Prozesse, nur auf 127.0.0.1127.0.0.1App-Kontokein sudo, keine Shell, Code nur lesenno sudoMarketing-Kontoeigenes Konto, eigene Postgres-RolleSELECT + INSERTSecretsgehören root, App darf nur lesen, nie im Git640Backuppg_dump, age, rclone nach Backblaze B2agehealthpingalle 5 Min., Stille löst den Alarm aus5 minGrafanaLoki, Prometheus, alles auf 127.0.0.1OAuth
Vom Internet bis zu den Daten

Jede Schicht arbeitet für sich, fällt eine aus, hält die nächste. Fahr über eine Schicht oder tippe sie an.

KI-gestützter IT-Support-Assistent

Beantwortet Fragen von Mitarbeitenden aus fünf Jahren Helpdesk-Tickets, komplett lokal

Der First-Level-Support bei AOE beantwortet dieselben Fragen immer wieder. Für meine Bachelorarbeit habe ich einen Assistenten gebaut, der aus rund 13.000 Deskpro-Tickets und 54 internen Anleitungen antwortet. Er läuft vollständig lokal, kein Ticket verlässt das Unternehmen.

RAG mit lokalen Open-Source-Modellen war eine bewusste Wahl: Die Tickets ändern sich jeden Tag, und jede Antwort soll sich auf ihre Quellen zurückführen lassen. Die ganze Pipeline habe ich selbst gebaut, von der Datenabfrage bis zum Chat-Widget.

PythonLlamaIndexOllamaChromaDBBM25MongoDBReactPhoenix
Bachelorarbeit, Hochschule RheinMain, bei AOE, 2025
1 / 5
Architektur: Tickets aus MongoDB und Anleitungen von der Platte landen als Vektoren in ChromaDB. Eine Frage läuft durch Pre-Retrieval, Retrieval und Post-Retrieval mit einem lokalen LLM, Feedback landet im Cache.
Architektur: Tickets aus MongoDB und Anleitungen von der Platte landen als Vektoren in ChromaDB. Eine Frage läuft durch Pre-Retrieval, Retrieval und Post-Retrieval mit einem lokalen LLM, Feedback landet im Cache.
1 / 4

Die Daten

Ich habe die Tickets aus fünf Jahren mit allen Nachrichten über die Deskpro-API in MongoDB geladen, ein Dokument pro Ticket. Vor dem Einbetten entferne ich HTML, Telefonnummern, URLs und E-Mail-Adressen. Die 54 Anleitungen teile ich in Stücke von 256 Tokens.

Die Tickets mischen Deutsch und Englisch, deshalb bettet das mehrsprachige Modell BAAI/bge-m3 alles ein. Tickets und Anleitungen liegen in zwei eigenen ChromaDB-Collections, daneben steht ein BM25-Index für exakte Begriffe.

2 / 4

Die Suche

Llama 3.2 mit 3B Parametern läuft lokal über Ollama und formuliert die Frage dreimal um. Für die Beispielfrage:

  1. Wie gebe ich einem neuen Mitarbeiter Zugang zum Zoom-Konto der Firma?
  2. Wie füge ich einen Mitarbeiter als Teilnehmer zu unseren Zoom-Meetings hinzu?
  3. Wie richte ich die Rechte ein, damit ein neuer Kollege unser Zoom nutzen kann?

Jede Variante läuft durch BM25 und die Vektorsuche, auf Tickets und Anleitungen, mit je 5 Treffern. Ich normalisiere die Scores per Min-Max, verrechne sie nach Rang mit 1/(Rang+k) und behalte pro Quelle die besten 3.

3 / 4

Die Antwort

Der Prompt erlaubt nur, was in den Treffern steht, Wissen von außen ist verboten. Das Modell schreibt eine Antwort aus den Tickets und eine aus den Anleitungen. Der Router nimmt die Antwort, deren Treffer im Schnitt höher bewertet wurden.

Bei dieser Frage gewannen die Anleitungen, 1,13 gegen 0,73 für die Tickets. Die Antwort folgt der Anleitung zur Zoom-Lizenz: im Admin-Bereich unter Benutzer prüfen, ob eine Lizenz frei ist, und dann die E-Mail-Adresse des Mitarbeiters eintragen. Ist keine mehr frei, erst prüfen, ob alte Konten entfernt werden können, bevor neue Lizenzen bestellt werden.

Im React-Chat kann man jede Antwort mit Daumen hoch oder runter bewerten, das Feedback landet in MongoDB.

4 / 4

Was ich heute anders machen würde

Gemessen habe ich Latenz und Antwortverhalten von vier lokalen Modellen: Llama 3.2 3B, Mistral 7B, Gemma2 9B und DeepSeek-R1 7B. Arize Phoenix hat über OpenTelemetry jeden Schritt aufgezeichnet, so sah ich die Zeit pro Schritt. Llama 3.2 war in jedem Schritt am schnellsten.

Beim erneuten Lesen des Codes habe ich einen Fehler gefunden, der einen Teil der Suchergebnisse verlor, bevor sie fusioniert wurden. Ohne festen Testsatz ist er mir damals nicht aufgefallen. Heute würde ich deshalb zuerst einen festen Testsatz aus Tickets von 2024 bauen und daran Recall@k der Suche und die Quellentreue der Antworten messen. Dazu kämen ein Reranker, Quellenangaben in jeder Antwort und eine gründlichere Entfernung personenbezogener Daten.

Beispiel"Wie gebe ich einem Mitarbeiter Zugang zu unserem Zoom?"
Assistent: eine Frage beantwortenPOST /query
DATEN (OFFLINE)FRAGESUCHEANTWORTAUSGABEBEOBACHTUNGDeskpro → MongoDBein Dokument pro Ticket, rund 13.000 Tickets5 JahreBereinigungHTML, Telefonnummern, URLs, E-Mails entferntPIIbge-m3 + Chroma, BM25zwei Collections: Tickets und 54 Anleitungen256 tokensMulti-QueryLlama 3.2 3B formuliert die Frage dreimal um3 VariantenHybride SucheBM25 + Vektoren, Tickets und Anleitungentop 5FusionMin-Max, dann 1/(Rang+k)top 3Generierungnur aus den Treffern, eine Antwort pro Quelle2 AntwortenRouterhöherer Durchschnitts-Score gewinntAnleitungenChat + FeedbackReact-Widget, Daumen hoch/runter in MongoDBfeedbackPhoenixOpenTelemetry-Trace für jeden SchrittOpenTelemetry
Von der Frage zur Antwort

Oben die Daten, die ich vorab aufbereitet habe, darunter der Weg einer Frage. Fahr über eine Stufe oder tippe sie an.

Kontakt

Eine Frage oder eine Stelle? Schreib mir eine E-Mail oder ruf direkt an. Ich melde mich zeitnah.

E-Mail schreiben