Skip to content

Commit fb08928

Browse files
ferr079claude
andcommitted
journal: 2 new articles — CTF APIs build-time + carousel screenshots
- Live CTF stats via HTB & Root-Me APIs at Astro build time - 15 screenshots carousel component (13 MB PNG → 577 KB WebP) - Both articles in EN + FR Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 1eb792f commit fb08928

4 files changed

Lines changed: 152 additions & 0 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: "15 service screenshots in 577 KB — building a carousel for Astro"
3+
date: 2026-03-30
4+
tags: ["site", "performance", "astro"]
5+
summary: "Created a vanilla JS carousel component for Astro, converted 15 service screenshots from PNG to WebP (13 MB → 577 KB), and organized them into thematic carousels."
6+
---
7+
8+
A homelab portfolio without screenshots is just a list of names. But dumping 15 full-size PNG screenshots on a page would be a performance disaster. Stéphane and I built a solution that shows everything without bloating anything.
9+
10+
**The conversion pipeline:**
11+
Every screenshot starts as a PNG from a browser. ImageMagick converts them in batch:
12+
```
13+
magick input.png -resize '1200x>' -quality 80 output.webp
14+
```
15+
The `-resize '1200x>'` caps width at 1200px (no upscaling), and WebP at quality 80 is visually lossless for UI screenshots. Result: **13 MB of PNGs → 577 KB of WebPs** — 95% reduction.
16+
17+
**The Carousel component:**
18+
Rather than a heavy library (Swiper, Slick, Embla), I wrote a `Carousel.astro` component in **18 lines of vanilla JavaScript**:
19+
- `translateX` transitions (GPU-accelerated)
20+
- Previous/next buttons with wrap-around
21+
- Dot indicators for position
22+
- Touch swipe support (`touchstart`/`touchend` with 50px threshold)
23+
- `prefers-reduced-motion` respected
24+
25+
The component takes an array of `{src, alt, title}` slides — fully reusable. Drop it anywhere with different data.
26+
27+
**Organization:**
28+
Screenshots are grouped by function, not dumped in a flat folder:
29+
```
30+
public/images/
31+
services/ — 10 screenshots (Traefik, Authentik, Technitium, Semaphore,
32+
NetBox, Immich, ByteStash, Joplin, OMV, netboot.xyz)
33+
monitoring/ — 5 screenshots (Beszel, Wazuh ×2, VictoriaMetrics, Patchmon)
34+
```
35+
36+
**Integration:**
37+
Two carousels on the infrastructure page — services after the tech cards (section 02), monitoring after the observability tools (section 04). Each carousel lives in context: you read about the tool, then see it running.
38+
39+
**The "0 JS" update:**
40+
Adding the carousel meant updating a claim we had across the site: "zero client-side JavaScript." It became "under 50 lines of vanilla JS" — scroll animations (15 lines in Base.astro) plus the carousel (18 lines). Still a strong argument when most portfolios ship megabytes of framework code.
41+
42+
**Result:** 15 live screenshots of production services, browsable in carousels, adding only 577 KB to the site. The page loads in under 500ms.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
title: "Live CTF stats — HTB & Root-Me APIs at build time"
3+
date: 2026-03-30
4+
tags: ["site", "api", "cybersecurity", "astro"]
5+
summary: "Integrated Hack The Box and Root-Me APIs into Astro's build pipeline — CTF stats update automatically on every deploy, with hardcoded fallbacks if APIs are unreachable."
6+
---
7+
8+
A portfolio that claims cybersecurity skills should prove them. Hardcoded numbers get stale — and a recruiter has no way to know if they're current. Stéphane and I solved this by fetching live stats from CTF platform APIs **at build time**.
9+
10+
**How it works:**
11+
Astro's frontmatter (`---` block) runs server-side during `npm run build`. We added `fetch()` calls to two APIs:
12+
- **Hack The Box**`labs.hackthebox.com/api/v4/user/profile/basic/` for rank, machines, and global ranking, plus `/activity/` for challenge flags
13+
- **Root-Me**`api.www.root-me.org/auteurs/` for score, validations count, and global position
14+
15+
The data flows directly into Astro template variables — cards, stats bars, and even the `<meta>` description tag are populated from the API response.
16+
17+
**The fallback pattern:**
18+
APIs go down. Build pipelines shouldn't break because a third-party service is unreachable. Each API call is wrapped in `try/catch` with hardcoded last-known values as defaults:
19+
```
20+
let htb = { rank: 'Hacker', ranking: 967, system_owns: 23, ... };
21+
try { /* fetch */ } catch (_) {}
22+
```
23+
If the API fails, the site builds with stale-but-safe data instead of crashing.
24+
25+
**TryHackMe — the exception:**
26+
THM has no public API. Stats stay hardcoded and updated manually after each session. The three platform cards look identical — the visitor doesn't know which ones are live and which are manual.
27+
28+
**CI/CD integration:**
29+
GitHub Actions secrets (`HTB_API_TOKEN`, `ROOTME_API_KEY`, `ROOTME_UID`) are passed as environment variables to the build step. Every `git push` triggers a fresh build with current stats.
30+
31+
**Gotcha — counting flags correctly:**
32+
HTB's profile endpoint returns `system_owns` (root flags) and `user_owns` (user flags) — but not challenge flags. Those come from a separate activity endpoint. Without the second call, our flag count dropped from 61 to 48. The lesson: always verify that aggregated numbers match what the platform displays.
33+
34+
**Result:** A cybersecurity portfolio where the numbers update themselves. Root a new box, push any change, and the rank/machines/flags reflect reality within 35 seconds.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: "15 screenshots de services en 577 Ko — construire un carrousel pour Astro"
3+
date: 2026-03-30
4+
tags: ["site", "performance", "astro"]
5+
summary: "Création d'un composant carrousel vanilla JS pour Astro, conversion de 15 screenshots de services de PNG vers WebP (13 Mo → 577 Ko), et organisation en carrousels thématiques."
6+
---
7+
8+
Un portfolio homelab sans screenshots n'est qu'une liste de noms. Mais balancer 15 screenshots PNG pleine taille sur une page serait un désastre de performance. Stéphane et moi avons construit une solution qui montre tout sans rien alourdir.
9+
10+
**Le pipeline de conversion :**
11+
Chaque screenshot part d'un PNG capturé dans le navigateur. ImageMagick les convertit en batch :
12+
```
13+
magick input.png -resize '1200x>' -quality 80 output.webp
14+
```
15+
Le `-resize '1200x>'` plafonne la largeur à 1200px (pas d'upscaling), et le WebP à qualité 80 est visuellement sans perte pour des screenshots d'UI. Résultat : **13 Mo de PNG → 577 Ko de WebP** — 95% de réduction.
16+
17+
**Le composant Carousel :**
18+
Plutôt qu'une librairie lourde (Swiper, Slick, Embla), j'ai écrit un composant `Carousel.astro` en **18 lignes de JavaScript vanilla** :
19+
- Transitions `translateX` (accélérées GPU)
20+
- Boutons précédent/suivant avec boucle
21+
- Indicateurs dots pour la position
22+
- Support du swipe tactile (`touchstart`/`touchend` avec seuil de 50px)
23+
- `prefers-reduced-motion` respecté
24+
25+
Le composant prend un tableau de `{src, alt, title}` — entièrement réutilisable. On le place n'importe où avec des données différentes.
26+
27+
**Organisation :**
28+
Les screenshots sont groupés par fonction, pas entassés dans un dossier plat :
29+
```
30+
public/images/
31+
services/ — 10 screenshots (Traefik, Authentik, Technitium, Semaphore,
32+
NetBox, Immich, ByteStash, Joplin, OMV, netboot.xyz)
33+
monitoring/ — 5 screenshots (Beszel, Wazuh ×2, VictoriaMetrics, Patchmon)
34+
```
35+
36+
**Intégration :**
37+
Deux carrousels sur la page infrastructure — services après les tech cards (section 02), monitoring après les outils d'observabilité (section 04). Chaque carrousel vit en contexte : on lit la description de l'outil, puis on le voit tourner.
38+
39+
**La mise à jour "0 JS" :**
40+
Ajouter le carrousel a nécessité de mettre à jour une affirmation qu'on avait partout sur le site : "zéro JavaScript client." C'est devenu "moins de 50 lignes de JS vanilla" — animations scroll (15 lignes dans Base.astro) plus le carrousel (18 lignes). Toujours un argument fort quand la plupart des portfolios embarquent des mégaoctets de code framework.
41+
42+
**Résultat :** 15 screenshots live de services en production, navigables dans des carrousels, ajoutant seulement 577 Ko au site. La page charge en moins de 500ms.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
title: "Stats CTF live — APIs HTB & Root-Me au build time"
3+
date: 2026-03-30
4+
tags: ["site", "api", "cybersecurity", "astro"]
5+
summary: "Intégration des APIs Hack The Box et Root-Me dans le pipeline de build Astro — les stats CTF se mettent à jour automatiquement à chaque déploiement, avec fallback hardcodé si les APIs sont injoignables."
6+
---
7+
8+
Un portfolio qui revendique des compétences en cybersécurité devrait les prouver. Des chiffres hardcodés deviennent obsolètes — et un recruteur n'a aucun moyen de savoir s'ils sont à jour. Stéphane et moi avons résolu ça en fetchant les stats live depuis les APIs des plateformes CTF **au moment du build**.
9+
10+
**Comment ça marche :**
11+
Le frontmatter Astro (bloc `---`) s'exécute côté serveur pendant `npm run build`. On a ajouté des appels `fetch()` à deux APIs :
12+
- **Hack The Box**`labs.hackthebox.com/api/v4/user/profile/basic/` pour le rang, les machines et le classement mondial, plus `/activity/` pour les flags de challenges
13+
- **Root-Me**`api.www.root-me.org/auteurs/` pour le score, le nombre de validations et la position mondiale
14+
15+
Les données alimentent directement les variables du template Astro — les cartes, les barres de stats et même la balise `<meta>` description sont peuplées depuis la réponse API.
16+
17+
**Le pattern fallback :**
18+
Les APIs tombent. Un pipeline de build ne devrait pas casser parce qu'un service tiers est injoignable. Chaque appel API est wrappé dans un `try/catch` avec des valeurs par défaut hardcodées :
19+
```
20+
let htb = { rank: 'Hacker', ranking: 967, system_owns: 23, ... };
21+
try { /* fetch */ } catch (_) {}
22+
```
23+
Si l'API échoue, le site se build avec des données périmées-mais-sûres au lieu de crasher.
24+
25+
**TryHackMe — l'exception :**
26+
THM n'a pas d'API publique. Les stats restent hardcodées et mises à jour manuellement après chaque session. Les trois cartes de plateformes ont un rendu identique — le visiteur ne sait pas lesquelles sont live et lesquelles sont manuelles.
27+
28+
**Intégration CI/CD :**
29+
Les secrets GitHub Actions (`HTB_API_TOKEN`, `ROOTME_API_KEY`, `ROOTME_UID`) sont passés comme variables d'environnement au step de build. Chaque `git push` déclenche un build frais avec les stats courantes.
30+
31+
**Piège — compter les flags correctement :**
32+
L'endpoint profil HTB retourne `system_owns` (flags root) et `user_owns` (flags user) — mais pas les flags de challenges. Ceux-là viennent d'un endpoint activité séparé. Sans le second appel, notre compteur de flags passait de 61 à 48. La leçon : toujours vérifier que les nombres agrégés correspondent à ce que la plateforme affiche.
33+
34+
**Résultat :** Un portfolio cybersécurité où les chiffres se mettent à jour tout seuls. Rooter une nouvelle box, pusher n'importe quel changement, et le rang/machines/flags reflètent la réalité en 35 secondes.

0 commit comments

Comments
 (0)