Hi, I'm Michael.
I build
Master's student (Dipl.-Ing.) in Networks & IT Security at JKU Linz, writing my thesis on permission metamodels at Dynatrace. I care about correctness, performance, and shipping things that actually work.
Featured work





Homelab Automation
A fully automated homelab configuration managed with Ansible and deployed over a Tailscale VPN. Two nodes cover the stack: a VPS hosting Caddy, CrowdSec, and public services like mljr.eu, and a home NUC running Grafana, Prometheus, and compute workloads. Everything from firewall rules to service updates is codified and repeatable.




Mljr Web
The portfolio you are looking at — built from scratch in Go using gomponents for type-safe HTML, Datastar for reactive updates without JavaScript frameworks, and Tailwind v4 for styling. A neo-brutalist four-theme component library powers over 170 UI components rendered server-side. The full monorepo includes a data pipeline, asset optimizer, and i…




Nightscout Tray
A cross-platform system tray application for people using CGM (continuous glucose monitoring). Built with Wails3, it runs natively on Windows, macOS, and Linux, showing the current glucose value directly in the system tray with color-coded urgency levels. Configurable polling, unit conversion, and delta trend arrows give a quick at-a-glance status …




GoDrive
A self-hosted file storage and sync platform with a Go REST backend and a Flutter mobile client. Files are chunked via the TUS resumable upload protocol so large transfers survive network interruptions. Thumbnails are generated and cached with inode-stable references, metadata lives in SQLite, and webhook events are signed with HMAC-SHA256. Multi-a…
Projects
Live Tools
Regex Lab
LIVEAn interactive regex workbench built in Go + Datastar. Uses regexp2 (PCRE-compatible engine) for backreferences, lookahead, and lookbehind. Matches highlight as you type via server-sent events, with a built-in cheat sheet and 20+ preset examples.
Playground
AI Snake
Snake plays itself by following a fixed loop through every cell on the board — a mathematical guarantee it can never trap itself.
Double Pendulum
A chaotic n-segment pendulum — tune the physics and watch tiny changes explode into wildly different motion.
Fourier Drawing
Draw any shape and watch a stack of rotating circles reconstruct it from pure sine waves.
Boids
A flock that organizes itself from three simple local rules — no leader, no plan, just steering.
Experience

Master Thesis · Software Engineer
Master thesis: abstracting complex IAM systems into a Prolog-based unified permission metamodel

Software Engineering Intern
Software engineering internship at Dynatrace Engineering Headquarters.

Student Assistant · Tutor
Tutor for Digital Circuits and Computer Architecture across six semesters

Software Development Intern
Angular and C# development of an existing cashless payment system

Software Development Intern
Development of a dashboard for team-relevant information with SvelteKit and TailwindCSS

Assembly & Testing Intern
Assembly of cable ducts and switch cabinets

Civil Servant
Support of office staff

Mandatory Internship
Data processing with Excel

Mandatory Internship
Assistance in production
Nov 2025 – Present · 8 monthsMaster thesis: abstracting complex IAM systems into a Prolog-based unified permission metamodel
Aug 2025 – Sep 2025 · 2 monthsSoftware engineering internship at Dynatrace Engineering Headquarters.
Oct 2023 – Jun 2026 · 2 yrs 9 moTutor for Digital Circuits and Computer Architecture across six semesters
Jul 2024 – Jul 2024 · 1 monthAngular and C# development of an existing cashless payment system
Aug 2023 – Sep 2023 · 2 monthsDevelopment of a dashboard for team-relevant information with SvelteKit and TailwindCSS
Jun 2021 – Jun 2021 · 1 monthAssembly of cable ducts and switch cabinets
Sep 2020 – May 2021 · 9 monthsSupport of office staff
Jul 2018 – Jul 2018 · 1 monthData processing with Excel
Jul 2017 – Jul 2017 · 1 monthAssistance in production
Education

MSc Networks and IT Security
Coursework and thesis work in network security, cryptography, and formal methods.

BSc Computer Science
Computer Science fundamentals with specialization in software engineering.

Matura · Mechatronics and Robotics
Robotics, automation engineering, embedded systems, and practical technical project work.
Places
Homelab
Self-hosted fleet
Avg response time
127 msCrowdSec perimeter
How it hangs together
All public traffic enters through Caddy on the VPS, with CrowdSec banning attackers at the edge and Authelia guarding private apps. Behind that, three machines talk over an encrypted Tailscale mesh — no open ports at home. Every host, container and config file is declared in one Ansible repo: a single make deploy converges the whole fleet.
Activity
Recent public activities
aggregatedTraining load
Skills
Languages
8Web
8Infra / Homelab
7Security
5Embedded / BLE
5Ops / Data
5Under the hood
Every component on this page is a Go function. No React, no npm, no build pipeline for the markup — the server renders HTML, Datastar adds reactivity, and SVG charts are computed at request time.
The arc math behind the gauges in the open-source section.
// Gauge renders a circular gauge as inline SVG.
// Zero JS, zero dependencies — pure server-side Go.
func Gauge(p GaugeProps) g.Node {
cx, cy := float64(p.Size)/2, float64(p.Size)/2
radius := float64(p.Size) * 0.38
strokeW := float64(p.Size) * 0.1
// Arc spans 270° (from 135° to 45°)
startAngle := 135.0
totalAngle := 270.0
pct := (p.Value - p.Min) / (p.Max - p.Min)
toRad := func(deg float64) float64 {
return deg * math.Pi / 180
}
arcPt := func(deg float64) (float64, float64) {
a := toRad(deg)
return cx + radius*math.Cos(a),
cy + radius*math.Sin(a)
}
arcPath := func(start, end float64, color string) string {
x1, y1 := arcPt(start)
x2, y2 := arcPt(end)
large := 0
if math.Abs(end-start) > 180 {
large = 1
}
return fmt.Sprintf(
'<path d="M %.1f %.1f A %.1f %.1f 0 %d 1 %.1f %.1f"
fill="none" stroke="%s" stroke-width="%.1f"
stroke-linecap="round"/>',
x1, y1, radius, radius, large, x2, y2,
color, strokeW)
}
// Track (full arc), then value arc on top
sb.WriteString(arcPath(startAngle, startAngle+totalAngle, p.TrackColor))
sb.WriteString(arcPath(startAngle, startAngle+totalAngle*pct, p.Color))
// …
}The homelab panel polls VictoriaMetrics — range queries over 40 days come back empty, so the attack heatmap is fetched in 30-day chunks.
// Attacks blocked per day, last ~12 months.
// VictoriaMetrics range queries spanning >40 days can return
// empty results (per-day index cutoff), so fetch the year in
// 30-day chunks and dedupe by day.
seenDay := map[string]bool{}
for chunk := 11; chunk >= 0; chunk-- {
start := now.AddDate(0, 0, -30*(chunk+1))
end := now.AddDate(0, 0, -30*chunk)
pts, err := p.promQueryRange(ctx,
"sum(increase(cs_bucket_overflowed_total[1d]))",
start, end, 24*time.Hour,
)
if err != nil {
continue // empty until a year of data exists
}
for _, pt := range pts {
day := time.Unix(int64(pt[0]), 0)
key := day.Format("2006-01-02")
if pt[1] > 0 && !seenDay[key] {
seenDay[key] = true
s.AttackDays = append(s.AttackDays,
DayValue{Date: day, Count: int(pt[1])})
}
}
}This very carousel. Pages flip on a Datastar signal; the observer only reacts to display:none→visible, so the animation can never re-trigger itself.
// PaginatedPages: each page shown while the shared
// Datastar signal matches its index.
for i, page := range pages {
wrapped[i] = h.Div(
g.Attr("data-slot", "page"),
ui.Show(fmt.Sprintf("$%s === %d", sig, i)),
page,
)
}
// Entrance animation replays exactly once per page switch:
// the observer only reacts to style mutations whose OLD value
// was display:none — the CSS animation itself can never
// re-trigger it.
var obs=new MutationObserver(function(muts){
muts.forEach(function(m){
var was=(m.oldValue||'');
var wasHidden=was.indexOf('display:none')>-1;
if(wasHidden&&m.target.style.display!=='none'){
m.target.removeAttribute('data-anim');
void m.target.offsetWidth; // restart keyframes
m.target.setAttribute('data-anim','');
}
});
});Live updates without a framework: one attribute on the section, one SSE handler, Datastar morphs the panel in place.
// One attribute on the section…
h.Section(
h.ID("homelab"),
g.Attr("data-on-interval__duration.60s",
"@get('/api/homelab')"),
// …
)
// …one SSE handler on the server.
e.GET("/api/homelab", func(c echo.Context) error {
sse := datastar.NewSSE(c.Response().Writer, c.Request())
return sse.PatchElements(
web.RenderToString(pages.HomelabPanel(snapshot())),
)
})
// Datastar morphs #homelab-panel in place every 60 s:
// no reload, no virtual DOM, no client-side state to sync.The skills radar — plain trigonometry rendered to an SVG polygon at request time.
// Radar chart: axis i sits at angle 2πi/n, starting at
// 12 o'clock. Values scale along the spoke; the series
// becomes a single <polygon>.
angle := func(i int) float64 {
return float64(i)*2*math.Pi/float64(n) - math.Pi/2
}
pt := func(radius float64, i int) (float64, float64) {
a := angle(i)
return cx + radius*math.Cos(a), cy + radius*math.Sin(a)
}
pts := make([]string, len(p.Axes))
for i := range p.Axes {
scaled := (s.Values[i] / p.Max) * r
x, y := pt(scaled, i)
pts[i] = fmt.Sprintf("%.1f,%.1f", x, y)
}
sb.WriteString(fmt.Sprintf(
'<polygon points="%s" fill="%s" fill-opacity="0.18"
stroke="%s" stroke-width="2"/>',
strings.Join(pts, " "), color, color))





































