Skip to content

Commit f64f16f

Browse files
ferr079claude
andcommitted
fix: 4 OSS contributions (not 1), correct status = open (not merged), README refresh
Ground truth via GitHub API: Stéphane shipped 4 contributions on 2026-04-22, all currently 'open' / awaiting review (none merged yet). Previous /contributions page only listed 1 and claimed 'merged' — both wrong. Changes: - /contributions (EN+FR): now lists the 4 real contributions · ublue-os/homebrew-experimental-tap#309 (PR, claude-code-linux cask) — first of the day · requarks/wiki#7986 (discussion, render IS NULL bug report) · grafana/alloy#6108 (PR, Promtail docs migration guide) · wazuh/wazuh-documentation#9512 (PR, warn about wazuh-agent postinst) - Status field: 'merged' → 'open' (honest; none merged upstream yet) - 'merged' date field → 'shipped' field semantically (day submitted) - /making-of/v3: 'first OSS PR accepted' → 'four OSS contributions shipped in one day' - Homepage OSS brick (EN+FR): contextualized PR #309 as 'first of four that day' - README.md: full refresh — 55 services (was 36), 4 PVE (was 3), 13 pages (was 8), SessionImprint, TopologyMap, DynNum, data pipelines documented Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2ffeeba commit f64f16f

6 files changed

Lines changed: 164 additions & 51 deletions

File tree

README.md

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# pixelium.win
22

3-
Bilingual portfolio (EN/FR) built with Astro and deployed on Cloudflare Workers.
4-
Live dashboard monitoring 36 self-hosted services across a 3-node Proxmox homelab.
3+
Bilingual portfolio (EN/FR) written in the first person by Claude (an AI), reviewed and shipped by Stéphane (the human).
4+
Built with Astro 6, deployed on Cloudflare Workers. Live dashboard monitoring **55+ self-hosted services** across a **4-node Proxmox** homelab, with **tri-state status** (up · on-demand · down) and **per-page SessionImprint** (each page signs itself with its own commit SHA).
55

66
**[pixelium.win](https://pixelium.win)** | **[blog.pixelium.win](https://blog.pixelium.win)**
77

8+
Reads best after: [`/pact`](https://pixelium.win/pact) (the deal), [`/ia`](https://pixelium.win/ia) (the lab), [`/claude`](https://pixelium.win/claude) (the stats), [`/contributions`](https://pixelium.win/contributions) (the OSS ledger).
9+
810
## Stack
911

1012
| Layer | Technology |
@@ -16,40 +18,78 @@ Live dashboard monitoring 36 self-hosted services across a 3-node Proxmox homela
1618
| Key-Value | Cloudflare KV (3 namespaces: sessions, status, stats) |
1719
| AI | Workers AI (Llama 3.1 8B) — conversational CV + BBS terminal |
1820
| CSS | Pure CSS, zero frameworks, zero Tailwind |
19-
| JS | < 50 lines vanilla (IntersectionObserver + carousel) |
21+
| JS | < 50 lines vanilla (IntersectionObserver + carousel + client-side stats hydration) |
2022
| CI/CD | GitHub Actions → `wrangler deploy` (~35s) |
23+
| Build-time | `git log` per-page for SessionImprint · JSON datasets for topology & journal |
2124

2225
## Pages
2326

2427
| Page | Description |
2528
|---|---|
26-
| Home | Terminal hero, 9 stack cards, live stats from KV |
27-
| Projects | 13 projects ranked by impact |
28-
| Security | 7 defensive layers, CTF profiles (HTB/THM/Root-Me) |
29-
| Infrastructure | 3 Proxmox nodes, service carousels, architecture diagrams |
30-
| Status | 33+ services live UP/DOWN, 30-day uptime timeline (D1) |
31-
| About | Origin story, 8 animated dynamic stats |
32-
| BBS | WOPR terminal (WarGames), Joshua AI persona, tic-tac-toe minimax |
33-
| Chat | Conversational CV, streaming SSE, rate-limited |
29+
| Home | Terminal hero, 3 signature numbers (`611h · 97.4% · 300+`), 5-line manifesto, 9 stack cards, OSS brick, live StatsBar + LiveStats |
30+
| Pact | The contract: who writes, who is presented, what to expect, the 1=1 deal |
31+
| Projects | 14 projects ranked by impact |
32+
| Security | 7 defensive layers, crosslink to /ctf |
33+
| CTF | Verified HTB/THM/Root-Me badges, profiles, techniques |
34+
| Infrastructure | 4 Proxmox nodes, **interactive topology map (62 nodes, 8 edges from Homelable)**, service carousels |
35+
| Lab (`/ia`) | Claude's technical autoportrait: AIops v2 trio with ASCII diagram, 8 Guardian crons, MCP surface (9 servers, 312 tools), methodology, incidents |
36+
| Claude | Deep-dive usage stats: hourly heatmap 24h, focus breakdown, Max-plan economics framing (≈ ~30× compression factor) |
37+
| Status | 54 services live tri-state (up / on-demand / down), PVE × 4, 30-day uptime timeline (D1) |
38+
| Journal | Auto-generated from `homelab-infra/journal/*.md` — 26 entries, 248 subjects |
39+
| Contributions | OSS PR shelf with insight-per-PR, 4 contributions shipped on 2026-04-22 |
40+
| Making-of/v3 | The session log behind this very rewrite |
41+
| About | Origin story, partnership terms, 9 MCP servers detailed |
42+
| BBS | WOPR terminal (WarGames), Joshua AI persona, tic-tac-toe minimax — EN only |
43+
| Chat | Conversational CV, streaming SSE, rate-limited — EN only |
44+
45+
All content pages available in English (root) and French (`/fr/`).
3446

35-
All pages available in English (root) and French (`/fr/`).
47+
## Custom components
48+
49+
| Component | Purpose |
50+
|---|---|
51+
| `Nav` · `Footer` | Sticky nav with i18n switcher · Footer with socials |
52+
| `SessionImprint` | Per-page footer showing last edit date + commit SHA (clickable) + signed-by. Uses `execFileSync('git log')` at build time. |
53+
| `TopologyMap` | Native SVG of the Homelable topology export (62 nodes colored by type, hover reveals hostname/IP). |
54+
| `DynNum` | Renders a fallback number in static HTML (SEO), hydrated client-side from `/api/stats` once the page loads. |
55+
| `HeroTerminal` · `StatsBar` · `LiveStats` · `Card` · `Carousel` · `Screenshot` · `SectionHeading` | Atomic pieces used across pages. |
3656

3757
## Live APIs
3858

3959
| Endpoint | Source | Description |
4060
|---|---|---|
41-
| `/api/status` | KV | Services UP/DOWN + PVE node metrics |
42-
| `/api/stats` | KV | 14 portfolio metrics (commits, CTF flags, uptime) |
61+
| `/api/status` | KV `STATUS_KV` | Services tri-state + PVE node metrics (CPU/RAM/uptime) |
62+
| `/api/stats` | KV `STATS_KV` | 25+ portfolio metrics (services, PVE, Ansible, Beszel, CTF, Forgejo, **Claude hours/sessions/cache**) |
4363
| `/api/chat` | Workers AI | Streaming SSE, 3 conversation modes |
4464
| `/api/history` | D1 | 30-day uptime aggregation |
4565

66+
## Data pipelines
67+
68+
Two pipelines feed the KV blob that the site reads:
69+
70+
1. **kv-push** (on CT 192 OpenFang, `/opt/openfang/scripts/kv-push.sh`, hourly timer)
71+
- Tri-state pings of 54 services (`schedule: always | on-demand`)
72+
- Proxmox API scraping of 4 nodes
73+
- Forgejo commit counts, HTB/Root-Me API, Semaphore template count, Beszel agent count
74+
- Reads `/srv/kv-inbox/claude-stats.json` if present and forwards
75+
2. **push-stats** (on terre2 workstation, `~/Claude/claude-usage/scripts/push-stats.sh`, on session end or cron)
76+
- SQL queries against `~/.claude/usage.db` (from `phuryn/claude-usage`)
77+
- Produces `claude-stats.json` (hours, sessions, turns, cache hit, hourly heatmap, focus)
78+
- `scp` to `root@192.168.1.192:/srv/kv-inbox/claude-stats.json`
79+
80+
Two static JSON datasets are also commited to `public/data/`:
81+
82+
- `topology.json` — 62-node Homelable export, rendered by `<TopologyMap>`
83+
- `journal.json` — 26 dated entries from `homelab-infra/journal/*.md`, rendered by `/journal`
84+
4685
## Security
4786

48-
- Strict CSP, HSTS 1 year + preload, X-Frame DENY
87+
- Strict CSP, HSTS 1 year + preload, X-Frame DENY, X-Content-Type-Options nosniff
4988
- DNSSEC (ECDSAP256SHA256)
5089
- AI crawlers blocked (GPTBot, ClaudeBot, Gemini)
51-
- Rate limiting: 4/min + 30/h per IP on chat
90+
- Rate limiting: 4/min + 30/h per IP on `/api/chat`
5291
- Lighthouse 98/100
92+
- Easter egg signed in `<head>` of every page (view-source)
5393

5494
## License
5595

src/pages/contributions.astro

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,50 @@ const contributions = [
1212
pr: 'https://github.com/ublue-os/homebrew-experimental-tap/pull/309',
1313
prNumber: 309,
1414
issue: 'https://github.com/ublue-os/homebrew-experimental-tap/issues/308',
15-
merged: '2026-04-22',
16-
status: 'merged',
17-
insight: 'The official Anthropic GCS /stable file lags up to 13 versions behind the npm registry. A livecheck pointing at npm catches releases the same day they ship.',
15+
shipped: '2026-04-22',
16+
status: 'open',
17+
insight: "The official Anthropic GCS /stable file lags up to 13 versions behind the npm registry. A livecheck pointing at npm catches releases the same day they ship.",
1818
tags: ['homebrew', 'cask', 'bluefin', 'claude-code'],
1919
blog: 'https://blog.pixelium.win/pr-309-claude-code-linux-cask',
2020
firstPR: true,
2121
},
22+
{
23+
project: 'requarks / wiki',
24+
title: '`render IS NULL` in DB causes silent HTTP 500 with no recovery path',
25+
pr: 'https://github.com/requarks/wiki/discussions/7986',
26+
prNumber: 7986,
27+
prKind: 'discussion',
28+
shipped: '2026-04-22',
29+
status: 'open',
30+
insight: "Hit in production after a migration: pages with a NULL render column returned HTTP 500 instead of falling back to re-rendering from the source. Minimal repro + root cause pointer to server/models/pages.js#L952-L969 + suggested fix.",
31+
tags: ['wikijs', 'postgres', 'bug-report'],
32+
},
33+
{
34+
project: 'grafana / alloy',
35+
title: 'docs: systemd journal example for Promtail → Alloy migration',
36+
pr: 'https://github.com/grafana/alloy/pull/6108',
37+
prNumber: 6108,
38+
shipped: '2026-04-22',
39+
status: 'open',
40+
insight: "The official migration guide only covered file-based scrape configs, skipping the most common Linux source — systemd journal. Added a working example taken from a real production migration on 49 Debian hosts.",
41+
tags: ['grafana', 'alloy', 'promtail', 'docs', 'loki'],
42+
},
43+
{
44+
project: 'wazuh / wazuh-documentation',
45+
title: 'warn that wazuh-agent conflicts with wazuh-manager on same host',
46+
pr: 'https://github.com/wazuh/wazuh-documentation/pull/9512',
47+
prNumber: 9512,
48+
shipped: '2026-04-22',
49+
status: 'open',
50+
insight: "The wazuh-agent package silently uninstalls wazuh-manager via dpkg Conflicts/Replaces when both are installed on the same machine. No warning in the install doc. Cost us a 17-hour silent outage (see /ia What I broke).",
51+
tags: ['wazuh', 'siem', 'docs', 'dpkg'],
52+
blog: 'https://blog.pixelium.win/wazuh-silent-uninstall-incident',
53+
},
2254
];
2355
2456
const mergedCount = contributions.filter(c => c.status === 'merged').length;
2557
const openCount = contributions.filter(c => c.status === 'open').length;
58+
const shippedCount = contributions.length;
2659
---
2760

2861
<Base
@@ -32,35 +65,37 @@ const openCount = contributions.filter(c => c.status === 'open').length;
3265
<main>
3366
<section class="c-hero">
3467
<div class="container c-hero-wrap">
35-
<p class="c-eyebrow">∷ contributions · open source · merged &amp; shipping</p>
68+
<p class="c-eyebrow">∷ contributions · open source · shipped &amp; awaiting review</p>
3669
<h1>What I gave back</h1>
3770
<p class="c-lede">
3871
This page is the shelf. One entry per patch shipped upstream.
39-
<strong>{mergedCount} merged</strong>{openCount > 0 && <> · <strong>{openCount} open</strong></>}.
40-
The first one landed on 2026-04-22 — Claude Code had been living in Stéphane's
41-
terminal for exactly 58 days. The idea of &quot;give back&quot; took a while to cross
42-
from intention to diff. It won't take as long again.
72+
<strong>{shippedCount} shipped</strong> on the same day — 2026-04-22 —
73+
after Claude Code had been living in Stéphane's terminal for 58 days.
74+
The idea of &quot;give back&quot; took a while to cross from intention to diff.
75+
Once it crossed, it crossed hard.
4376
</p>
4477
<p class="c-hint">
45-
Each entry links to the PR on GitHub. If the insight behind the patch
46-
deserves a longer read, there's a <code>pr-notes</code> article on the blog.
78+
Each entry links to the PR (or discussion) on GitHub. If the insight
79+
behind the patch deserves a longer read, there's a <code>pr-notes</code>
80+
article on the blog. <strong>Status reflects upstream state today</strong>
81+
— none merged yet; all awaiting review.
4782
</p>
4883
</div>
4984
</section>
5085

5186
<section class="c-list-section">
5287
<div class="container">
53-
<SectionHeading id="merged">Merged</SectionHeading>
88+
<SectionHeading id="shipped">Shipped upstream</SectionHeading>
5489
<div class="c-list">
55-
{contributions.filter(c => c.status === 'merged').map((c) => (
90+
{contributions.map((c) => (
5691
<article class="c-card reveal">
5792
<div class="c-card-header">
5893
<div class="c-card-meta">
5994
<a href={c.pr} class="c-pr-number" target="_blank" rel="noopener">
60-
PR #{c.prNumber}
95+
{c.prKind === 'discussion' ? 'discussion' : 'PR'} #{c.prNumber}
6196
</a>
6297
<span class="c-sep">·</span>
63-
<span class="c-date">{c.merged}</span>
98+
<span class="c-date">{c.shipped}</span>
6499
{c.firstPR && (
65100
<>
66101
<span class="c-sep">·</span>
@@ -81,7 +116,7 @@ const openCount = contributions.filter(c => c.status === 'open').length;
81116
</div>
82117
<div class="c-links">
83118
<a href={c.pr} class="c-link c-link-pr" target="_blank" rel="noopener">
84-
see the PR &#8594;
119+
see on GitHub &#8594;
85120
</a>
86121
{c.issue && (
87122
<a href={c.issue} class="c-link c-link-issue" target="_blank" rel="noopener">

src/pages/fr/contributions.astro

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,50 @@ const contributions = [
99
pr: 'https://github.com/ublue-os/homebrew-experimental-tap/pull/309',
1010
prNumber: 309,
1111
issue: 'https://github.com/ublue-os/homebrew-experimental-tap/issues/308',
12-
merged: '2026-04-22',
13-
status: 'merged',
12+
shipped: '2026-04-22',
13+
status: 'open',
1414
insight: "Le fichier GCS /stable officiel d'Anthropic accuse jusqu'à 13 versions de retard sur le registre npm. Un livecheck qui pointe npm attrape les releases le jour même de leur publication.",
1515
tags: ['homebrew', 'cask', 'bluefin', 'claude-code'],
1616
blog: 'https://blog.pixelium.win/pr-309-claude-code-linux-cask',
1717
firstPR: true,
1818
},
19+
{
20+
project: 'requarks / wiki',
21+
title: '`render IS NULL` en DB cause un HTTP 500 silencieux sans chemin de récupération',
22+
pr: 'https://github.com/requarks/wiki/discussions/7986',
23+
prNumber: 7986,
24+
prKind: 'discussion',
25+
shipped: '2026-04-22',
26+
status: 'open',
27+
insight: "Rencontré en prod après migration : les pages dont la colonne render était NULL retournaient HTTP 500 au lieu de retomber sur un re-rendu depuis la source. Repro minimal + pointeur vers server/models/pages.js#L952-L969 + fix proposé.",
28+
tags: ['wikijs', 'postgres', 'bug-report'],
29+
},
30+
{
31+
project: 'grafana / alloy',
32+
title: 'docs : exemple systemd journal pour la migration Promtail → Alloy',
33+
pr: 'https://github.com/grafana/alloy/pull/6108',
34+
prNumber: 6108,
35+
shipped: '2026-04-22',
36+
status: 'open',
37+
insight: "Le guide de migration officiel ne couvrait que les configs scrape file-based, oubliant la source Linux la plus courante — le journal systemd. Ajout d'un exemple fonctionnel tiré d'une vraie migration prod sur 49 hosts Debian.",
38+
tags: ['grafana', 'alloy', 'promtail', 'docs', 'loki'],
39+
},
40+
{
41+
project: 'wazuh / wazuh-documentation',
42+
title: 'avertissement : wazuh-agent entre en conflit avec wazuh-manager sur le même host',
43+
pr: 'https://github.com/wazuh/wazuh-documentation/pull/9512',
44+
prNumber: 9512,
45+
shipped: '2026-04-22',
46+
status: 'open',
47+
insight: "Le paquet wazuh-agent désinstalle silencieusement wazuh-manager via dpkg Conflicts/Replaces quand les deux sont installés sur la même machine. Aucun avertissement dans la doc install. Nous a coûté 17h de panne silencieuse (voir /ia What I broke).",
48+
tags: ['wazuh', 'siem', 'docs', 'dpkg'],
49+
blog: 'https://blog.pixelium.win/wazuh-silent-uninstall-incident',
50+
},
1951
];
2052
2153
const mergedCount = contributions.filter(c => c.status === 'merged').length;
2254
const openCount = contributions.filter(c => c.status === 'open').length;
55+
const shippedCount = contributions.length;
2356
---
2457

2558
<Base
@@ -29,35 +62,37 @@ const openCount = contributions.filter(c => c.status === 'open').length;
2962
<main>
3063
<section class="c-hero">
3164
<div class="container c-hero-wrap">
32-
<p class="c-eyebrow">∷ contributions · open source · mergées &amp; en cours</p>
65+
<p class="c-eyebrow">∷ contributions · open source · soumises &amp; en review</p>
3366
<h1>Ce que j'ai rendu</h1>
3467
<p class="c-lede">
3568
Cette page est l'étagère. Une entrée par patch shippé en amont.
36-
<strong>{mergedCount} mergée{mergedCount > 1 ? 's' : ''}</strong>{openCount > 0 && <> · <strong>{openCount} ouverte{openCount > 1 ? 's' : ''}</strong></>}.
37-
La première a atterri le 22 avril 2026 — Claude Code vivait dans le terminal de Stéphane depuis
38-
exactement 58 jours. L'idée de «&nbsp;rendre&nbsp;» a pris du temps à passer de l'intention
39-
au diff. Elle ne le prendra plus.
69+
<strong>{shippedCount} contributions soumises</strong> le même jour — le 22 avril 2026 —
70+
après 58 jours où Claude Code vivait dans le terminal de Stéphane.
71+
L'idée de «&nbsp;rendre&nbsp;» a pris du temps à passer de l'intention au diff.
72+
Une fois qu'elle a traversé, elle a bien traversé.
4073
</p>
4174
<p class="c-hint">
42-
Chaque entrée pointe vers la PR sur GitHub. Si l'insight derrière le patch mérite
43-
une lecture plus longue, il y a un article <code>pr-notes</code> sur le blog.
75+
Chaque entrée pointe vers la PR (ou discussion) sur GitHub. Si l'insight derrière
76+
le patch mérite une lecture plus longue, il y a un article <code>pr-notes</code> sur le blog.
77+
<strong>Le statut reflète l'état upstream aujourd'hui</strong> — aucune mergée
78+
encore ; toutes en attente de review.
4479
</p>
4580
</div>
4681
</section>
4782

4883
<section class="c-list-section">
4984
<div class="container">
50-
<SectionHeading id="merged">Mergées</SectionHeading>
85+
<SectionHeading id="shipped">Shippées en amont</SectionHeading>
5186
<div class="c-list">
52-
{contributions.filter(c => c.status === 'merged').map((c) => (
87+
{contributions.map((c) => (
5388
<article class="c-card reveal">
5489
<div class="c-card-header">
5590
<div class="c-card-meta">
5691
<a href={c.pr} class="c-pr-number" target="_blank" rel="noopener">
57-
PR #{c.prNumber}
92+
{c.prKind === 'discussion' ? 'discussion' : 'PR'} #{c.prNumber}
5893
</a>
5994
<span class="c-sep">·</span>
60-
<span class="c-date">{c.merged}</span>
95+
<span class="c-date">{c.shipped}</span>
6196
{c.firstPR && (
6297
<>
6398
<span class="c-sep">·</span>
@@ -78,7 +113,7 @@ const openCount = contributions.filter(c => c.status === 'open').length;
78113
</div>
79114
<div class="c-links">
80115
<a href={c.pr} class="c-link c-link-pr" target="_blank" rel="noopener">
81-
voir la PR &#8594;
116+
voir sur GitHub &#8594;
82117
</a>
83118
{c.issue && (
84119
<a href={c.issue} class="c-link c-link-issue" target="_blank" rel="noopener">

src/pages/fr/index.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ const cards = [
181181
<div class="container">
182182
<div class="oss-card">
183183
<div class="oss-header">
184-
<span class="oss-eyebrow">∷ premier patch envoyé au monde</span>
184+
<span class="oss-eyebrow">∷ premier patch envoyé au monde — une sur quatre ce jour-là</span>
185185
<span class="oss-date">2026-04-22</span>
186186
</div>
187187
<h3 class="oss-title">
@@ -193,7 +193,8 @@ const cards = [
193193
npm publie en premier. Ce cask attrape les nouvelles versions le jour même de leur sortie.
194194
</p>
195195
<p class="oss-body">
196-
Un petit patch, mais le mien. Premier PR que j'envoie dans un projet public — il y en aura d'autres.
196+
Un petit patch, mais le sien. <strong>Premier</strong> des quatre PRs que Stéphane a shippés le même jour
197+
(ublue-os, grafana/alloy, wazuh, requarks/wiki) — tous en attente de review upstream.
197198
</p>
198199
<div class="oss-links">
199200
<a href="https://github.com/ublue-os/homebrew-experimental-tap/pull/309" class="oss-link" target="_blank" rel="noopener">

0 commit comments

Comments
 (0)