692 lines
24 KiB
HTML
692 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="description" content="Portfolio of Quentin Pismont, senior/lead platform engineer: distributed systems, observability, security and self-hosted CI/CD, tested in production.">
|
|
<title>Quentin Pismont — Senior/Lead Platform Engineer</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap" rel="stylesheet">
|
|
<!-- Open Graph -->
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:title" content="Quentin Pismont — Senior/Lead Platform Engineer">
|
|
<meta property="og:description" content="Self-hosted infrastructure running in production, a security-first AI agent in Rust, and a distributed media pipeline. Source code available.">
|
|
<style>
|
|
:root {
|
|
--bg: #0b0b14;
|
|
--bg-soft: #14142a;
|
|
--bg-card: #101024;
|
|
--border: #3a3a6a;
|
|
--text: #e8e8f0;
|
|
--text-dim: #bcbcd8;
|
|
--text-faint: #7a7aa4;
|
|
--accent: #3cbcfc;
|
|
--accent-soft: rgba(60, 188, 252, 0.12);
|
|
--accent-2: #f8b800;
|
|
--green: #00e436;
|
|
--pink: #ff2d78;
|
|
--mono: 'VT323', 'Courier New', monospace;
|
|
--pixel: 'Press Start 2P', 'Courier New', monospace;
|
|
}
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
html { scroll-behavior: smooth; }
|
|
body {
|
|
background-color: var(--bg);
|
|
/* Scanlines + grid live in the background layer, never over the glyphs */
|
|
background-image:
|
|
repeating-linear-gradient(
|
|
to bottom,
|
|
rgba(0, 0, 0, 0.22) 0px,
|
|
rgba(0, 0, 0, 0.22) 1px,
|
|
transparent 1px,
|
|
transparent 4px
|
|
),
|
|
linear-gradient(rgba(60, 188, 252, 0.035) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(60, 188, 252, 0.035) 1px, transparent 1px);
|
|
background-size: 100% 4px, 32px 32px, 32px 32px;
|
|
background-attachment: fixed, scroll, scroll;
|
|
color: var(--text);
|
|
font-family: var(--mono);
|
|
font-size: 21px;
|
|
line-height: 1.6;
|
|
}
|
|
/* CRT vignette — smooth gradient, does not cut through text */
|
|
body::after {
|
|
content: "";
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 9999;
|
|
pointer-events: none;
|
|
background: radial-gradient(ellipse at center, transparent 72%, rgba(0, 0, 0, 0.28) 100%);
|
|
}
|
|
.wrap {
|
|
max-width: 880px;
|
|
margin: 0 auto;
|
|
padding: 0 24px;
|
|
}
|
|
/* ---------- NAV ---------- */
|
|
nav {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
background: var(--bg);
|
|
border-bottom: 4px solid var(--border);
|
|
}
|
|
nav .wrap {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
height: 64px;
|
|
}
|
|
nav .logo {
|
|
font-family: var(--pixel);
|
|
font-weight: 400;
|
|
font-size: 10px;
|
|
color: var(--text);
|
|
text-transform: uppercase;
|
|
}
|
|
nav .logo span { color: var(--accent-2); }
|
|
nav .logo::after {
|
|
content: "_";
|
|
color: var(--accent);
|
|
animation: blink 1s steps(2, start) infinite;
|
|
}
|
|
nav .links { display: flex; gap: 24px; }
|
|
nav a {
|
|
color: var(--text-dim);
|
|
text-decoration: none;
|
|
font-family: var(--pixel);
|
|
font-size: 8px;
|
|
text-transform: uppercase;
|
|
}
|
|
nav a:hover {
|
|
color: var(--accent-2);
|
|
text-decoration: underline;
|
|
text-decoration-thickness: 2px;
|
|
text-underline-offset: 4px;
|
|
}
|
|
@media (max-width: 620px) { nav .links { display: none; } }
|
|
/* ---------- HERO ---------- */
|
|
header {
|
|
padding: 72px 0 64px;
|
|
border-bottom: 4px solid var(--border);
|
|
}
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
font-family: var(--pixel);
|
|
font-size: 8px;
|
|
text-transform: uppercase;
|
|
color: var(--green);
|
|
background: transparent;
|
|
border: 3px solid var(--green);
|
|
padding: 8px 12px;
|
|
margin-bottom: 32px;
|
|
box-shadow: 4px 4px 0 rgba(0, 228, 54, 0.25);
|
|
}
|
|
.badge .dot {
|
|
width: 8px; height: 8px;
|
|
background: var(--green);
|
|
animation: blink 1s steps(2, start) infinite;
|
|
}
|
|
header h1 {
|
|
font-family: var(--pixel);
|
|
font-size: clamp(0.85rem, 2.6vw, 1.45rem);
|
|
line-height: 1.9;
|
|
letter-spacing: 0;
|
|
margin-bottom: 28px;
|
|
font-weight: 400;
|
|
text-transform: uppercase;
|
|
text-shadow: 3px 3px 0 #000, 6px 6px 0 rgba(60, 188, 252, 0.25);
|
|
}
|
|
header h1 .grad {
|
|
color: var(--accent-2);
|
|
background: none;
|
|
-webkit-text-fill-color: currentColor;
|
|
}
|
|
header p.lead {
|
|
font-size: 23px;
|
|
line-height: 1.65;
|
|
color: var(--text-dim);
|
|
max-width: 660px;
|
|
margin-bottom: 32px;
|
|
}
|
|
.cta {
|
|
display: flex;
|
|
gap: 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 12px 18px;
|
|
text-decoration: none;
|
|
font-family: var(--pixel);
|
|
font-size: 8px;
|
|
text-transform: uppercase;
|
|
border: 3px solid var(--text);
|
|
background: var(--bg-soft);
|
|
color: var(--text);
|
|
box-shadow: 5px 5px 0 #000;
|
|
transition: none;
|
|
}
|
|
.btn-primary {
|
|
background: var(--accent);
|
|
color: #0b0b14;
|
|
border-color: var(--accent);
|
|
}
|
|
.btn-primary:hover {
|
|
background: var(--accent-2);
|
|
border-color: var(--accent-2);
|
|
transform: translate(2px, 2px);
|
|
box-shadow: 3px 3px 0 #000;
|
|
}
|
|
.btn-ghost {
|
|
background: var(--bg-soft);
|
|
color: var(--text);
|
|
border-color: var(--border);
|
|
}
|
|
.btn-ghost:hover {
|
|
color: var(--accent-2);
|
|
border-color: var(--accent-2);
|
|
transform: translate(2px, 2px);
|
|
box-shadow: 3px 3px 0 #000;
|
|
}
|
|
/* ---------- SECTIONS ---------- */
|
|
section { padding: 64px 0; border-bottom: 4px solid var(--border); }
|
|
.section-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
margin-bottom: 36px;
|
|
}
|
|
.section-head .num {
|
|
font-family: var(--pixel);
|
|
font-size: 10px;
|
|
color: #0b0b14;
|
|
background: var(--accent-2);
|
|
padding: 6px 8px;
|
|
box-shadow: 3px 3px 0 #000;
|
|
}
|
|
.section-head h2 {
|
|
font-family: var(--pixel);
|
|
font-size: clamp(0.85rem, 2.2vw, 1.1rem);
|
|
letter-spacing: 0;
|
|
font-weight: 400;
|
|
text-transform: uppercase;
|
|
color: var(--text);
|
|
}
|
|
.section-intro {
|
|
color: var(--text-dim);
|
|
max-width: 660px;
|
|
margin: -20px 0 36px;
|
|
font-size: 22px;
|
|
line-height: 1.65;
|
|
}
|
|
/* ---------- ABOUT ---------- */
|
|
.about-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 36px;
|
|
}
|
|
@media (max-width: 620px) { .about-grid { grid-template-columns: 1fr; gap: 28px; } }
|
|
.about-grid p { color: var(--text-dim); margin-bottom: 18px; font-size: 22px; line-height: 1.65; }
|
|
.about-grid p strong { color: var(--accent-2); font-weight: 400; }
|
|
.principles { list-style: none; }
|
|
.principles li {
|
|
padding: 10px 0 10px 26px;
|
|
position: relative;
|
|
color: var(--text-dim);
|
|
border-bottom: 2px dashed var(--border);
|
|
font-size: 21px;
|
|
line-height: 1.6;
|
|
}
|
|
.principles li:last-child { border-bottom: none; }
|
|
.principles li::before {
|
|
content: ">";
|
|
position: absolute;
|
|
left: 0;
|
|
color: var(--green);
|
|
font-family: var(--pixel);
|
|
font-size: 9px;
|
|
top: 14px;
|
|
}
|
|
/* ---------- PROJECTS ---------- */
|
|
.project {
|
|
background: var(--bg-card);
|
|
border: 3px solid var(--border);
|
|
border-radius: 0;
|
|
padding: 28px;
|
|
margin-bottom: 28px;
|
|
box-shadow: 6px 6px 0 #000;
|
|
transition: none;
|
|
}
|
|
.project:hover {
|
|
border-color: var(--accent);
|
|
transform: none;
|
|
box-shadow: 6px 6px 0 rgba(60, 188, 252, 0.35);
|
|
}
|
|
.project-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: 16px;
|
|
margin-bottom: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.project h3 {
|
|
font-family: var(--pixel);
|
|
font-size: 11px;
|
|
line-height: 1.8;
|
|
font-weight: 400;
|
|
text-transform: uppercase;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
color: var(--accent);
|
|
}
|
|
.project .tag {
|
|
font-family: var(--pixel);
|
|
font-size: 7px;
|
|
padding: 6px 8px;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
color: var(--accent);
|
|
border: 2px solid var(--accent);
|
|
white-space: nowrap;
|
|
text-transform: uppercase;
|
|
}
|
|
.tag.wip { background: transparent; color: var(--accent-2); border-color: var(--accent-2); }
|
|
.tag.done { background: transparent; color: var(--green); border-color: var(--green); }
|
|
.project .subtitle {
|
|
color: var(--text-dim);
|
|
font-size: 22px;
|
|
line-height: 1.65;
|
|
margin-bottom: 24px;
|
|
}
|
|
.project .subtitle strong { color: var(--text); font-weight: 400; }
|
|
.block { margin-bottom: 20px; }
|
|
.block-label {
|
|
font-family: var(--pixel);
|
|
font-size: 8px;
|
|
color: var(--accent-2);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0;
|
|
margin-bottom: 10px;
|
|
}
|
|
.block-label::before { content: "■ "; color: var(--pink); }
|
|
.block p, .block ul { color: var(--text-dim); font-size: 21px; line-height: 1.65; }
|
|
.block ul { list-style: none; }
|
|
.block ul li {
|
|
padding: 6px 0 6px 22px;
|
|
position: relative;
|
|
}
|
|
.block ul li::before {
|
|
content: ">";
|
|
position: absolute;
|
|
left: 0;
|
|
color: var(--green);
|
|
font-family: var(--pixel);
|
|
font-size: 8px;
|
|
top: 9px;
|
|
}
|
|
.block strong { color: var(--text); font-weight: 400; }
|
|
.block code {
|
|
font-family: var(--mono);
|
|
font-size: 0.95em;
|
|
background: #000;
|
|
padding: 1px 5px;
|
|
border-radius: 0;
|
|
color: var(--accent);
|
|
border: 2px solid var(--border);
|
|
}
|
|
.stack {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-top: 24px;
|
|
padding-top: 20px;
|
|
border-top: 2px dashed var(--border);
|
|
}
|
|
.stack span {
|
|
font-family: var(--pixel);
|
|
font-size: 7px;
|
|
color: var(--text-dim);
|
|
background: #000;
|
|
padding: 6px 8px;
|
|
border-radius: 0;
|
|
border: 2px solid var(--border);
|
|
text-transform: uppercase;
|
|
}
|
|
/* ---------- SKILLS ---------- */
|
|
.skills-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
.skill-card {
|
|
background: var(--bg-card);
|
|
border: 3px solid var(--border);
|
|
border-radius: 0;
|
|
padding: 20px;
|
|
box-shadow: 5px 5px 0 #000;
|
|
}
|
|
.skill-card:hover { border-color: var(--accent-2); }
|
|
.skill-card h4 {
|
|
font-family: var(--pixel);
|
|
font-size: 8px;
|
|
line-height: 1.8;
|
|
margin-bottom: 12px;
|
|
color: var(--text);
|
|
text-transform: uppercase;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.skill-card h4 .ic { color: var(--pink); }
|
|
.skill-card p { color: var(--text-dim); font-size: 21px; line-height: 1.6; }
|
|
/* ---------- CONTACT ---------- */
|
|
#contact { text-align: center; border-bottom: none; }
|
|
#contact h2 { justify-content: center; }
|
|
#contact .section-head { justify-content: center; }
|
|
#contact p { color: var(--text-dim); max-width: 560px; margin: 0 auto 32px; font-size: 22px; line-height: 1.65; }
|
|
.contact-links { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }
|
|
/* ---------- FOOTER ---------- */
|
|
footer {
|
|
padding: 32px 0;
|
|
text-align: center;
|
|
color: var(--text-faint);
|
|
font-size: 18px;
|
|
font-family: var(--mono);
|
|
border-top: 4px solid var(--border);
|
|
}
|
|
footer .wrap::before {
|
|
content: "PRESS START TO CONTINUE";
|
|
display: block;
|
|
font-family: var(--pixel);
|
|
font-size: 7px;
|
|
color: var(--text-faint);
|
|
margin-bottom: 14px;
|
|
animation: blink 1.4s steps(2, start) infinite;
|
|
}
|
|
footer .wrap::after {
|
|
content: "1UP HI-SCORE 000000 © 2026";
|
|
display: block;
|
|
font-family: var(--pixel);
|
|
font-size: 7px;
|
|
color: var(--text-faint);
|
|
margin-top: 14px;
|
|
}
|
|
@keyframes blink {
|
|
0%, 49% { opacity: 1; }
|
|
50%, 100% { opacity: 0; }
|
|
}
|
|
/* Reveal animation: hard step, no easing */
|
|
.reveal {
|
|
opacity: 0;
|
|
transform: translateY(12px);
|
|
transition: opacity 0.15s steps(2, end), transform 0.15s steps(2, end);
|
|
}
|
|
.reveal.visible { opacity: 1; transform: translateY(0); }
|
|
/* Accessibility: no animation if the user prefers reduced motion */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
html { scroll-behavior: auto; }
|
|
.reveal { opacity: 1; transform: none; transition: none; }
|
|
.project:hover, .btn-primary:hover, .btn-ghost:hover { transform: none; }
|
|
.badge .dot, nav .logo::after, footer .wrap::before { animation: none; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<nav>
|
|
<div class="wrap">
|
|
<div class="logo">1UP <span>QUENTIN_PISMONT</span></div>
|
|
<div class="links">
|
|
<a href="#about">About</a>
|
|
<a href="#projects">Projects</a>
|
|
<a href="#skills">Skills</a>
|
|
<a href="#contact">Contact</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<header>
|
|
<div class="wrap">
|
|
<div class="badge"><span class="dot"></span> Player 1 ready — open to work</div>
|
|
<h1>I design systems<br><span class="grad">end to end</span>,<br>built to hold up<br>in production.</h1>
|
|
<p class="lead">
|
|
Platform / infrastructure engineer. From code to production, through CI/CD,
|
|
observability and security — an integrated ecosystem designed to be operated
|
|
over the long run.
|
|
</p>
|
|
<div class="cta">
|
|
<a href="#projects" class="btn btn-primary">Start game ></a>
|
|
<a href="https://gitea.qpismont.fr/explore/repos" target="_blank" class="btn btn-ghost">Gitea / Source code</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<!-- ABOUT -->
|
|
<section id="about">
|
|
<div class="wrap">
|
|
<div class="section-head reveal">
|
|
<span class="num">01</span>
|
|
<h2>About</h2>
|
|
</div>
|
|
<div class="about-grid reveal">
|
|
<div>
|
|
<p>
|
|
Working as an engineer for <strong>10 years</strong>. My specialty: designing coherent
|
|
distributed systems and keeping them operable over time.
|
|
</p>
|
|
<p>
|
|
What I show here is infrastructure
|
|
<strong>actually running in production</strong> for my daily use, <strong>tested under failure</strong>,
|
|
with full observability and an end-to-end CI/CD chain.
|
|
</p>
|
|
<p>
|
|
On the people side, I <strong>mentored two interns from the ground up</strong> on our stack —
|
|
both were hired at the end of their internship. Helping others level up
|
|
is part of how I approach the craft.
|
|
</p>
|
|
<p>
|
|
My areas of focus: <strong>Rust</strong>, container orchestration,
|
|
security / isolation, networking, and self-hosted observability.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<div class="block-label">Principles</div>
|
|
<ul class="principles">
|
|
<li>If it hasn't been tested under failure, it isn't resilient.</li>
|
|
<li>A technical choice is justified by its tradeoffs, not by trends.</li>
|
|
<li>Migrate for value, never for dogma.</li>
|
|
<li>Security is designed starting from the threat model.</li>
|
|
<li>A system must be observable before it's scaled.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<!-- PROJECTS -->
|
|
<section id="projects">
|
|
<div class="wrap">
|
|
<div class="section-head reveal">
|
|
<span class="num">02</span>
|
|
<h2>Selected projects</h2>
|
|
</div>
|
|
<p class="section-intro reveal">
|
|
Three pieces that, together, cover the essentials of a platform profile:
|
|
operating a system, designing a secure architecture, and thinking about scale.
|
|
</p>
|
|
<!-- PROJECT 1 : INFRA -->
|
|
<div class="project reveal">
|
|
<div class="project-top">
|
|
<h3>Self-hosted infrastructure <span class="tag done">Complete & tested</span></h3>
|
|
</div>
|
|
<p class="subtitle">A complete ecosystem — code -> CI -> production -> observability — on bare metal, validated by real failure tests.</p>
|
|
<div class="block">
|
|
<div class="block-label">The problem</div>
|
|
<p>Having a reliable personal platform to host and operate my own services, with the standards of real production: resilience, alerting, zero data loss.</p>
|
|
</div>
|
|
<div class="block">
|
|
<div class="block-label">The architecture</div>
|
|
<ul>
|
|
<li><strong>Networking</strong> — WireGuard (the only entry point), Docker overlay, and <code>CoreDNS</code> for <code>*.internal</code> resolution with fallback to Docker's DNS.</li>
|
|
<li><strong>CI/CD</strong> — Gitea + Woodpecker: lint -> tests -> build -> automatic push registry on tag/release -> deploy on prod.</li>
|
|
<li><strong>Observability</strong> — OtelCollector (central hub), Prometheus (storage + alerting), Loki (logs) + Tempo (traces), Perses dashboards, Alertmanager -> ntfy.</li>
|
|
<li><strong>Safety net</strong> — Blackbox scraped directly by Prometheus: monitoring survives a Collector failure.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="block">
|
|
<div class="block-label">What sets it apart — validated resilience</div>
|
|
<ul>
|
|
<li>Gitea outage -> ntfy alert received [OK]</li>
|
|
<li>OtelCollector outage -> alert received, monitoring maintained [OK]</li>
|
|
<li>Multiple redeployments -> zero data loss (persistent volumes) [OK]</li>
|
|
</ul>
|
|
</div>
|
|
<div class="stack">
|
|
<span>Docker Swarm</span><span>WireGuard</span><span>CoreDNS</span><span>Gitea</span><span>Woodpecker CI</span><span>OpenTelemetry</span><span>Prometheus</span><span>Loki</span><span>Tempo</span><span>Perses</span><span>Alertmanager</span>
|
|
</div>
|
|
</div>
|
|
<!-- PROJECT 2 : HERALD -->
|
|
<div class="project reveal">
|
|
<div class="project-top">
|
|
<h3>Herald — AI agent for Gitea <span class="tag wip">In active development</span></h3>
|
|
</div>
|
|
<p class="subtitle">A code-review and AI-assistance bot for Gitea, written in Rust, with a <strong>security-first</strong> agentic architecture.</p>
|
|
<div class="block">
|
|
<div class="block-label">The problem</div>
|
|
<p>No AI assistance solution exists for Gitea. A real need: solo code review, implementing tests/features, usable day to day — including from a phone.</p>
|
|
</div>
|
|
<div class="block">
|
|
<div class="block-label">The security model</div>
|
|
<p>The challenge of an agent that executes code isn't making it run, but making it run <strong>without risk of exfiltration or compromise</strong>. The decisions:</p>
|
|
<ul>
|
|
<li>Agent loop on the host, <strong>side effects isolated in a container</strong></li>
|
|
<li><strong>Git handled exclusively by the host</strong> (Gitea token): no secret ever touches the sandbox.</li>
|
|
<li><code>.git</code> <strong>"disarmed"</strong> for reading (remotes removed, credentials cleared, hooks emptied) — on a copy, with the armed original staying on the host.</li>
|
|
<li><strong>Network cut during the AI phase</strong> (disconnect via bollard): exfiltration is impossible. Network active only for <code>postCreate</code>.</li>
|
|
<li>Persistence from the start (resume after stop).</li>
|
|
</ul>
|
|
</div>
|
|
<div class="block">
|
|
<div class="block-label">Roadmap (intentional semver)</div>
|
|
<ul>
|
|
<li><strong>1.0</strong> — Review + comments posted to the right line on the MR</li>
|
|
<li><strong>1.1</strong> — The 3 observability signals</li>
|
|
<li><strong>1.2</strong> — Sandboxing begins (read-only first)</li>
|
|
<li><strong>2.0</strong> — Feature creation (write / execute)</li>
|
|
<li><strong>2.x</strong> — Advanced persistence + web config dashboard</li>
|
|
</ul>
|
|
</div>
|
|
<div class="stack">
|
|
<span>Rust</span><span>Tokio</span><span>bollard</span><span>DevContainers</span><span>OpenRouter</span><span>OpenTelemetry</span>
|
|
</div>
|
|
</div>
|
|
<!-- PROJECT 3 : PIPELINE -->
|
|
<div class="project reveal">
|
|
<div class="project-top">
|
|
<h3>Distributed media pipeline <span class="tag wip">Design / upcoming</span></h3>
|
|
</div>
|
|
<p class="subtitle">An extended imgproxy-style media processing pipeline, designed for horizontal scaling and streaming.</p>
|
|
<div class="block">
|
|
<div class="block-label">The problem</div>
|
|
<p>Process media (images, streaming) compositionally, with an architecture able to scale out horizontally — and serve as a real load-testing playground.</p>
|
|
</div>
|
|
<div class="block">
|
|
<div class="block-label">The architecture</div>
|
|
<ul>
|
|
<li>Compositional API: a <strong>core</strong> + <strong>runners</strong> communicating over <code>NATS</code>.</li>
|
|
<li>Multi-level cache, horizontal scaling of runners.</li>
|
|
<li><strong>Claim-check pattern</strong>: files transit through an object store (S3/Minio); NATS carries only commands and metadata.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="block">
|
|
<div class="block-label">The technical goal</div>
|
|
<p>An ideal candidate for <strong>load tests (k6) instrumented by my observability stack</strong> — validating system behavior at scale and documenting the breaking points.</p>
|
|
</div>
|
|
<div class="stack">
|
|
<span>Rust</span><span>Tokio</span><span>NATS</span><span>S3 / Minio</span><span>k6</span><span>OpenTelemetry</span><span>Docker Swarm</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<!-- SKILLS -->
|
|
<section id="skills">
|
|
<div class="wrap">
|
|
<div class="section-head reveal">
|
|
<span class="num">03</span>
|
|
<h2>Skills</h2>
|
|
</div>
|
|
<div class="skills-grid">
|
|
<div class="skill-card reveal">
|
|
<h4><span class="ic">■</span> Languages</h4>
|
|
<p>Rust (async / Tokio), TypeScript / Node. Looking for the right tool for the right problem.</p>
|
|
</div>
|
|
<div class="skill-card reveal">
|
|
<h4><span class="ic">■</span> Infrastructure & Ops</h4>
|
|
<p>Docker Swarm, bare metal, Fedora Bootc (immutable OS), CI/CD (Gitea, Woodpecker).</p>
|
|
</div>
|
|
<div class="skill-card reveal">
|
|
<h4><span class="ic">■</span> Observability</h4>
|
|
<p>OpenTelemetry, Prometheus, Loki, Tempo, Perses, Alertmanager. Full stack, tested under failure.</p>
|
|
</div>
|
|
<div class="skill-card reveal">
|
|
<h4><span class="ic">■</span> Networking</h4>
|
|
<p>WireGuard, CoreDNS, overlay networks, Caddy. Designing self-hosted private networks.</p>
|
|
</div>
|
|
<div class="skill-card reveal">
|
|
<h4><span class="ic">■</span> Security & Isolation</h4>
|
|
<p>Container sandboxing, privilege separation (DBus / Polkit), threat modeling.</p>
|
|
</div>
|
|
<div class="skill-card reveal">
|
|
<h4><span class="ic">■</span> Distributed & Messaging</h4>
|
|
<p>NATS, distributed architectures, claim-check patterns, horizontal scaling.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<!-- CONTACT -->
|
|
<section id="contact">
|
|
<div class="wrap">
|
|
<div class="section-head reveal">
|
|
<span class="num">04</span>
|
|
<h2>Contact</h2>
|
|
</div>
|
|
<p class="reveal">
|
|
Interested in platform / infrastructure / staff roles where technical quality
|
|
and autonomy matter. The code for everything shown here is available to review.
|
|
</p>
|
|
<div class="contact-links reveal">
|
|
<a href="mailto:pismont.quentin@protonmail.com" class="btn btn-primary">Get in touch</a>
|
|
<a href="https://gitea.qpismont.fr/explore/repos" class="btn btn-ghost">Gitea</a>
|
|
<a href="#" class="btn btn-ghost">LinkedIn</a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<footer>
|
|
<div class="wrap">
|
|
INSERT COIN
|
|
</div>
|
|
</footer>
|
|
<script>
|
|
// Reveal on scroll
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('visible');
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, { threshold: 0.12 });
|
|
document.querySelectorAll('.reveal').forEach((el, i) => {
|
|
// slight cascade offset
|
|
el.style.transitionDelay = (i % 4 * 60) + 'ms';
|
|
observer.observe(el);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |