158 lines
7.1 KiB
TypeScript
158 lines
7.1 KiB
TypeScript
|
|
import type { ReactNode } from 'react';
|
||
|
|
import { Cpu, GitBranch, Network, Radio, Server, ShieldCheck } from 'lucide-react';
|
||
|
|
import { MarketingHeader } from './MarketingHeader';
|
||
|
|
import { MarketingFooter } from './MarketingFooter';
|
||
|
|
|
||
|
|
const FLOW = [
|
||
|
|
{
|
||
|
|
title: 'Caddy (edge partagé)',
|
||
|
|
detail:
|
||
|
|
"TLS Let's Encrypt automatique, routage par nom d'hôte. Ce Caddy sert aussi six autres domaines sur ce même serveur — tripoli-edge n'a jamais eu les ports 80/443 pour lui seul.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: 'tripoli-edge — Rust (hyper/tokio)',
|
||
|
|
detail:
|
||
|
|
'Reverse-proxy interne (172.17.0.1:9000, passerelle docker0, jamais exposé directement). Expose /healthz et /metrics en clair, sans framework de métriques : cinq compteurs atomiques suffisaient.',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: 'tripoli-api — Rust (axum/sqlx)',
|
||
|
|
detail:
|
||
|
|
"Authentification par session opaque (Argon2, pas de JWT — révoquer une session, c'est supprimer une ligne). Upload en streaming vers disque, recherche plein texte Postgres.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: 'PostgreSQL + blobs',
|
||
|
|
detail:
|
||
|
|
"Conteneur Docker dédié à Tripoli Cloud, séparé de celui d'un autre projet sur ce même serveur. Les fichiers sont nommés par UUID sur disque — le nom d'origine ne vit qu'en base.",
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
const LABS: { icon: ReactNode; title: string; detail: string }[] = [
|
||
|
|
{
|
||
|
|
icon: <Network size={18} />,
|
||
|
|
title: 'Tunnel WireGuard',
|
||
|
|
detail:
|
||
|
|
"10.100.0.0/24 entre le Mac d'administration et le serveur — canal SSH/Ansible, jamais dans le chemin de trafic applicatif.",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
icon: <Server size={18} />,
|
||
|
|
title: 'tripoli-netctl',
|
||
|
|
detail:
|
||
|
|
'Netns + veth créés à la demande via rtnetlink (Rust). Testé (création, ping traversant, destruction propre) — volontairement jamais branché sur le trafic de prod.',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
icon: <Radio size={18} />,
|
||
|
|
title: 'bird-lab — BGP + BFD',
|
||
|
|
detail:
|
||
|
|
'Deux namespaces en session BGP via une racine en transit (AS 65000/65010/65011). Sessions Established, BFD Up et détection de panne vérifiées, puis démonté proprement.',
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
const STATE: [string, string][] = [
|
||
|
|
['Frontend (app/)', 'Fait — branché sur les vraies données de tripoli-api, aucun mock.'],
|
||
|
|
['WireGuard', 'Fait — 10.100.0.0/24.'],
|
||
|
|
['tripoli-edge', 'Fait, en prod — /healthz, /metrics vérifiés.'],
|
||
|
|
['tripoli-api', 'Fait, en prod — auth, fichiers, notifications, recherche.'],
|
||
|
|
['tripoli-netctl', 'Fait — labo, jamais branché sur le trafic de prod.'],
|
||
|
|
['Observabilité', 'Fait — scrape Prometheus/Grafana partagé, alertes versionnées.'],
|
||
|
|
['bird-lab (BGP/BFD)', 'Fait, bonus — démonté après vérification.'],
|
||
|
|
['CI/CD', 'Fait — Forgejo Actions, compte deploy sans privilège.'],
|
||
|
|
['OpenTofu', 'Code écrit, non appliqué — identifiants OVH/Hetzner non fournis.'],
|
||
|
|
];
|
||
|
|
|
||
|
|
export function ArchitecturePage() {
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen bg-bg text-ink">
|
||
|
|
<MarketingHeader />
|
||
|
|
|
||
|
|
<main className="mx-auto max-w-[880px] px-6 py-16 sm:px-10 sm:py-24">
|
||
|
|
<span className="inline-flex items-center gap-2 rounded-full border border-line bg-field px-3.5 py-1.5 text-xs font-semibold tracking-wide text-ink-soft">
|
||
|
|
<Cpu size={13} />
|
||
|
|
Un seul serveur, pas un cluster
|
||
|
|
</span>
|
||
|
|
|
||
|
|
<h1 className="m-0 my-5 font-display text-[36px] font-semibold leading-[1.08] tracking-tight sm:text-[48px]">
|
||
|
|
Ce qui tourne <span className="italic">réellement</span>.
|
||
|
|
</h1>
|
||
|
|
<p className="m-0 max-w-[62ch] text-[15.5px] leading-relaxed text-ink-soft">
|
||
|
|
Un VPS Hetzner CPX22 (2 vCPU, 3,7 Go de RAM), partagé avec six autres projets personnels.
|
||
|
|
Ce qui suit est honnête sur ce qui est en place — pas de cluster simulé, pas de haute
|
||
|
|
disponibilité qui n'existe pas.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<section className="mt-14">
|
||
|
|
<h2 className="m-0 mb-6 font-display text-[22px] font-semibold text-ink">
|
||
|
|
Le chemin d'une requête
|
||
|
|
</h2>
|
||
|
|
<div className="flex flex-col gap-3">
|
||
|
|
{FLOW.map((step, i) => (
|
||
|
|
<div key={step.title} className="flex gap-4 rounded-2xl border border-line bg-panel p-5 shadow-tc">
|
||
|
|
<span className="flex h-8 w-8 flex-none items-center justify-center rounded-full bg-field text-[13px] font-bold text-ink">
|
||
|
|
{i + 1}
|
||
|
|
</span>
|
||
|
|
<div>
|
||
|
|
<p className="m-0 mb-1 text-[14.5px] font-bold text-ink">{step.title}</p>
|
||
|
|
<p className="m-0 text-[13.5px] leading-relaxed text-ink-soft">{step.detail}</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section className="mt-14">
|
||
|
|
<h2 className="m-0 mb-2 font-display text-[22px] font-semibold text-ink">
|
||
|
|
Hors du chemin de trafic
|
||
|
|
</h2>
|
||
|
|
<p className="m-0 mb-6 text-[13.5px] text-ink-soft">
|
||
|
|
Labos pédagogiques — jamais branchés sur le trafic de production.
|
||
|
|
</p>
|
||
|
|
<div className="grid gap-3 sm:grid-cols-3">
|
||
|
|
{LABS.map((lab) => (
|
||
|
|
<div key={lab.title} className="rounded-2xl border border-line bg-panel p-5 shadow-tc">
|
||
|
|
<div className="mb-2 text-ink-soft">{lab.icon}</div>
|
||
|
|
<p className="m-0 mb-1 text-[13.5px] font-bold text-ink">{lab.title}</p>
|
||
|
|
<p className="m-0 text-[12.5px] leading-relaxed text-ink-soft">{lab.detail}</p>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section className="mt-14">
|
||
|
|
<h2 className="m-0 mb-6 font-display text-[22px] font-semibold text-ink">État réel</h2>
|
||
|
|
<div className="overflow-hidden rounded-2xl border border-line bg-panel shadow-tc">
|
||
|
|
{STATE.map(([name, status], i) => (
|
||
|
|
<div
|
||
|
|
key={name}
|
||
|
|
className={`flex items-center justify-between gap-4 px-5 py-3.5 text-[13px] ${
|
||
|
|
i !== STATE.length - 1 ? 'border-b border-line/70' : ''
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
<span className="font-semibold text-ink">{name}</span>
|
||
|
|
<span className="text-right text-ink-soft">{status}</span>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section className="mt-14 rounded-2xl border border-line bg-field p-6">
|
||
|
|
<div className="mb-2 flex items-center gap-2 text-ink">
|
||
|
|
<ShieldCheck size={16} />
|
||
|
|
<p className="m-0 text-[13.5px] font-bold">Limites assumées</p>
|
||
|
|
</div>
|
||
|
|
<p className="m-0 text-[13px] leading-relaxed text-ink-soft">
|
||
|
|
SSH public encore ouvert (choix explicite, pas encore restreint au VPN). Pas de
|
||
|
|
virtualisation imbriquée sur ce VPS. Alertes Prometheus qui se déclenchent mais ne sont
|
||
|
|
pas encore routées par notification. Détail complet dans le README du dépôt.
|
||
|
|
</p>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<div className="mt-10 flex items-center gap-2 text-[13px] text-ink-soft">
|
||
|
|
<GitBranch size={15} />
|
||
|
|
Code et historique de commits versionnés sur Forgejo.
|
||
|
|
</div>
|
||
|
|
</main>
|
||
|
|
|
||
|
|
<MarketingFooter />
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|