|
|
|
@@ -0,0 +1,586 @@
|
|
|
|
|
<!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 a platform / infrastructure engineer: distributed systems, observability, security and self-hosted CI/CD, tested in production.">
|
|
|
|
|
<title>Portfolio — Platform Engineer</title>
|
|
|
|
|
<!-- Open Graph -->
|
|
|
|
|
<meta property="og:type" content="website">
|
|
|
|
|
<meta property="og:title" content="Portfolio — 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: #0d1117;
|
|
|
|
|
--bg-soft: #161b22;
|
|
|
|
|
--bg-card: #161b22;
|
|
|
|
|
--border: #21262d;
|
|
|
|
|
--border-hover: #30363d;
|
|
|
|
|
--text: #e6edf3;
|
|
|
|
|
--text-dim: #8b949e;
|
|
|
|
|
--text-faint: #6e7681;
|
|
|
|
|
--accent: #58a6ff;
|
|
|
|
|
--accent-soft: rgba(88, 166, 255, 0.1);
|
|
|
|
|
--green: #3fb950;
|
|
|
|
|
--mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
|
|
|
|
--sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
|
|
}
|
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
|
html { scroll-behavior: smooth; }
|
|
|
|
|
body {
|
|
|
|
|
background: var(--bg);
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-family: var(--sans);
|
|
|
|
|
line-height: 1.65;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
|
|
}
|
|
|
|
|
.wrap {
|
|
|
|
|
max-width: 860px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
padding: 0 24px;
|
|
|
|
|
}
|
|
|
|
|
/* ---------- NAV ---------- */
|
|
|
|
|
nav {
|
|
|
|
|
position: sticky;
|
|
|
|
|
top: 0;
|
|
|
|
|
z-index: 100;
|
|
|
|
|
background: rgba(13, 17, 23, 0.8);
|
|
|
|
|
backdrop-filter: blur(12px);
|
|
|
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
nav .wrap {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
height: 60px;
|
|
|
|
|
}
|
|
|
|
|
nav .logo {
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
}
|
|
|
|
|
nav .logo span { color: var(--accent); }
|
|
|
|
|
nav .links { display: flex; gap: 28px; }
|
|
|
|
|
nav a {
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
transition: color 0.2s;
|
|
|
|
|
}
|
|
|
|
|
nav a:hover { color: var(--text); }
|
|
|
|
|
@media (max-width: 620px) { nav .links { display: none; } }
|
|
|
|
|
/* ---------- HERO ---------- */
|
|
|
|
|
header {
|
|
|
|
|
padding: 100px 0 80px;
|
|
|
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
.badge {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: var(--green);
|
|
|
|
|
background: rgba(63, 185, 80, 0.1);
|
|
|
|
|
border: 1px solid rgba(63, 185, 80, 0.2);
|
|
|
|
|
padding: 5px 12px;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
margin-bottom: 28px;
|
|
|
|
|
}
|
|
|
|
|
.badge .dot {
|
|
|
|
|
width: 7px; height: 7px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: var(--green);
|
|
|
|
|
box-shadow: 0 0 8px var(--green);
|
|
|
|
|
}
|
|
|
|
|
header h1 {
|
|
|
|
|
font-size: clamp(2rem, 5vw, 3rem);
|
|
|
|
|
line-height: 1.15;
|
|
|
|
|
letter-spacing: -0.02em;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
header h1 .grad {
|
|
|
|
|
background: linear-gradient(120deg, #58a6ff, #a371f7);
|
|
|
|
|
-webkit-background-clip: text;
|
|
|
|
|
background-clip: text;
|
|
|
|
|
-webkit-text-fill-color: transparent;
|
|
|
|
|
}
|
|
|
|
|
header p.lead {
|
|
|
|
|
font-size: 1.15rem;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
max-width: 640px;
|
|
|
|
|
margin-bottom: 32px;
|
|
|
|
|
}
|
|
|
|
|
.cta {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
.btn {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 10px 20px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
}
|
|
|
|
|
.btn-primary {
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
color: #0d1117;
|
|
|
|
|
}
|
|
|
|
|
.btn-primary:hover { background: #79b8ff; transform: translateY(-1px); }
|
|
|
|
|
.btn-ghost {
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
border-color: var(--border-hover);
|
|
|
|
|
}
|
|
|
|
|
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
|
|
|
|
|
/* ---------- SECTIONS ---------- */
|
|
|
|
|
section { padding: 80px 0; border-bottom: 1px solid var(--border); }
|
|
|
|
|
.section-head {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-bottom: 40px;
|
|
|
|
|
}
|
|
|
|
|
.section-head .num {
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
.section-head h2 {
|
|
|
|
|
font-size: 1.6rem;
|
|
|
|
|
letter-spacing: -0.01em;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
.section-intro {
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
max-width: 640px;
|
|
|
|
|
margin: -24px 0 40px;
|
|
|
|
|
}
|
|
|
|
|
/* ---------- ABOUT ---------- */
|
|
|
|
|
.about-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
gap: 40px;
|
|
|
|
|
}
|
|
|
|
|
@media (max-width: 620px) { .about-grid { grid-template-columns: 1fr; gap: 28px; } }
|
|
|
|
|
.about-grid p { color: var(--text-dim); margin-bottom: 16px; }
|
|
|
|
|
.about-grid p strong { color: var(--text); font-weight: 500; }
|
|
|
|
|
.principles { list-style: none; }
|
|
|
|
|
.principles li {
|
|
|
|
|
padding: 10px 0 10px 24px;
|
|
|
|
|
position: relative;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
.principles li:last-child { border-bottom: none; }
|
|
|
|
|
.principles li::before {
|
|
|
|
|
content: "→";
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 0;
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
/* ---------- PROJECTS ---------- */
|
|
|
|
|
.project {
|
|
|
|
|
background: var(--bg-card);
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
padding: 32px;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
transition: border-color 0.25s, transform 0.25s;
|
|
|
|
|
}
|
|
|
|
|
.project:hover {
|
|
|
|
|
border-color: var(--border-hover);
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
}
|
|
|
|
|
.project-top {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
.project h3 {
|
|
|
|
|
font-size: 1.3rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
.project .tag {
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
padding: 3px 10px;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
background: var(--accent-soft);
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
border: 1px solid rgba(88,166,255,0.2);
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
.tag.wip { background: rgba(210,153,34,0.1); color: #d29922; border-color: rgba(210,153,34,0.2); }
|
|
|
|
|
.tag.done { background: rgba(63,185,80,0.1); color: var(--green); border-color: rgba(63,185,80,0.2); }
|
|
|
|
|
.project .subtitle {
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
font-size: 0.98rem;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
.block { margin-bottom: 20px; }
|
|
|
|
|
.block-label {
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--text-faint);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.05em;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
.block p, .block ul { color: var(--text-dim); font-size: 0.95rem; }
|
|
|
|
|
.block ul { list-style: none; }
|
|
|
|
|
.block ul li {
|
|
|
|
|
padding: 4px 0 4px 20px;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
.block ul li::before {
|
|
|
|
|
content: "▹";
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 0;
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
font-size: 0.85em;
|
|
|
|
|
}
|
|
|
|
|
.block code {
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
font-size: 0.85em;
|
|
|
|
|
background: var(--bg-soft);
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
.stack {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
padding-top: 20px;
|
|
|
|
|
border-top: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
.stack span {
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
background: var(--bg-soft);
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
/* ---------- SKILLS ---------- */
|
|
|
|
|
.skills-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
.skill-card {
|
|
|
|
|
background: var(--bg-card);
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 22px;
|
|
|
|
|
}
|
|
|
|
|
.skill-card h4 {
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
.skill-card h4 .ic { color: var(--accent); }
|
|
|
|
|
.skill-card p { color: var(--text-dim); font-size: 0.9rem; }
|
|
|
|
|
/* ---------- 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: 500px; margin: 0 auto 32px; }
|
|
|
|
|
.contact-links { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
|
|
|
|
|
/* ---------- FOOTER ---------- */
|
|
|
|
|
footer {
|
|
|
|
|
padding: 40px 0;
|
|
|
|
|
text-align: center;
|
|
|
|
|
color: var(--text-faint);
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
border-top: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
/* Reveal animation */
|
|
|
|
|
.reveal {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transform: translateY(20px);
|
|
|
|
|
transition: opacity 0.6s ease, transform 0.6s ease;
|
|
|
|
|
}
|
|
|
|
|
.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 { transform: none; }
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<nav>
|
|
|
|
|
<div class="wrap">
|
|
|
|
|
<div class="logo">~/<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> Open to opportunities</div>
|
|
|
|
|
<h1>I design systems<br><span class="grad">end to end</span>, built to hold up 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">View projects →</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>
|
|
|
|
|
Developer for <strong>over 20 years</strong> (first lines of C at age 10),
|
|
|
|
|
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 ✓</li>
|
|
|
|
|
<li>OtelCollector outage → alert received, monitoring maintained ✓</li>
|
|
|
|
|
<li>Multiple redeployments → zero data loss (persistent volumes) ✓</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">
|
|
|
|
|
© 2026
|
|
|
|
|
</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>
|