Files
factory/schema.md
T
arcodangeandClaude Opus 5 b92da95701 docs(adr) — stockage objet MinIO : qui déclare quoi, et qui détient quoi
Trois questions indépendantes, tranchées lors du branchement de Kadans sur
MinIO (2026-07-26) : qui déclare les buckets d'une app, qui détient les
identifiants capables de les créer, et comment l'app lit les siens.

La décision de fond est du fondateur : CHACUN SON PÉRIMÈTRE. Une application
déclare ses buckets depuis son propre dépôt ; `tools` fournit le serveur, un
module de standardisation et un compte de provisionnement — pas la liste. Une
première version faisait tout porter par l'infra partagée : à ce rythme, chaque
bucket de chaque app devenait une PR sur le dépôt commun.

L'ADR consigne aussi les trois identités et leurs portées (root / provisionneur
/ compte de service), pourquoi la lecture des identifiants est une propriété
inconditionnelle de la plateforme plutôt qu'une déclaration par app, et pourquoi
les octets ne transitent pas par l'API — avec les conséquences que ça impose
(endpoint public, CORS aux origines exactes, pas de basic-auth sur l'ingress S3).

Les alternatives écartées sont listées avec leur motif, dont deux que j'avais
moi-même proposées et qui étaient plus faibles.

Deux limites assumées y figurent : le provisionneur est un secret PARTAGÉ entre
rôles CI (sa compromission permet de créer des buckets, pas de lire des objets),
et les noms d'actions d'administration MinIO n'ont pas été éprouvés contre le
serveur au moment d'écrire.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 10:19:48 +02:00

12 KiB

Home lab

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
graph LR
    %% Styles
    classDef internet fill:#f9f9f9,stroke:#999,color:#333,font-size:12px;
    classDef arcodange fill:#e6e6e6,stroke:#555,color:#333,font-size:12px;
    classDef cluster fill:#d4d4d4,stroke:#777,color:#333,font-size:12px;

    %% Internet
    subgraph "Internet / Le monde"
        direction TB
        DuckDNS["🌐 DuckDNS<br>(duckdns.org)"]:::internet
        GoogleCloud["☁️ Google Cloud<br>(cloud.google.com)"]:::internet
        Cloudflare["🛡️ Cloudflare<br>(cloudflare.com)"]:::internet
        OVHcloud["🖥️ OVHcloud<br>(ovhcloud.com)"]:::internet
        Zoho["📧 Zoho<br>(zoho.eu)"]:::internet
        CrowdSec["🔒 CrowdSec<br>(crowdsec.net)"]:::internet
    end

    %% Bureaux Arcodange
    subgraph "Bureaux Arcodange"
        direction TB
        Livebox["📡 Livebox<br>(gateway)"]:::arcodange
        MacBook["💻 MacBook Pro M4"]:::arcodange

        subgraph "Cluster Raspberry Pi"
            direction LR
            Pi1["🐧 pi1"]:::cluster
            Pi2["🐧 pi2"]:::cluster
            Pi3["🐧 pi3"]:::cluster
        end
    end

Network

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
graph LR
    %% Styles
    classDef internet fill:#f9f9f9,stroke:#999,color:#333,font-size:12px;
    classDef arcodange fill:#e6e6e6,stroke:#555,color:#333,font-size:12px;
    classDef k3sServer fill:#ff9999,stroke:#cc0000,color:#333,font-size:12px;
    classDef k3sAgent fill:#99ff99,stroke:#00cc00,color:#333,font-size:12px;
    classDef traefik fill:#9999ff,stroke:#0000cc,color:#333,font-size:12px;
    classDef dns fill:#ffff99,stroke:#cccc00,color:#333,font-size:12px;
    classDef cloudflared fill:#ccccff,stroke:#6666cc,color:#333,font-size:12px;

    %% Internet
    subgraph "Internet / Le monde"
        direction TB
        DuckDNS["🌐 DuckDNS<br>(duckdns.org)"]:::internet
        subgraph "Cloudflare"
            CloudflareService["🛡️ Cloudflare<br>(cloudflare.com)"]:::internet
            DNSNote@{ shape: text, label: "redirige *.arcodange.fr<br>→ Cloudflared Tunnel" }
        end
    end

    %% Bureaux Arcodange
    subgraph "Bureaux Arcodange"
        direction TB
        Livebox["📡 Livebox<br>(passerelle)"]:::arcodange
        MacBook["💻 MacBook Pro M4"]:::arcodange

        %% Cluster Raspberry Pi
        subgraph "Cluster Raspberry Pi"
            direction LR

            %% Noeud pi1
            subgraph "pi1"
                Pi1["🐧 pi1"]:::k3sServer
                subgraph "K3s (Server)"
                    Traefik["🚀 Traefik"]:::traefik
                    Cloudflared["☁️ Cloudflared Tunnel"]:::cloudflared
                end
                PiHole1["🧹 Pi-hole"]:::dns
                NS1@{ shape: text, label: "nameserver : pi3" }
                DNSArcodange@{ shape: text, label: ".arcodange.lab<br>→ Traefik", color:#cccc00 }
            end

            %% Noeud pi2
            subgraph "pi2"
                Pi2["🐧 pi2"]:::k3sAgent
            end

            %% Noeud pi3
            subgraph "pi3"
                Pi3["🐧 pi3"]:::k3sAgent
                PiHole3["🧹 Pi-hole"]:::dns
                NS3@{ shape: text, label: "nameserver : pi1" }
            end
        end
    end

    %% Connexions
    %% Pi-hole → Livebox (DNS)
    PiHole1 -.->|DNS| Livebox
    PiHole3 -.->|DNS| Livebox

    %% Cloudflare → Cloudflared Tunnel → Traefik
    CloudflareService -.-> Cloudflared
    Cloudflared --> Traefik

    %% Livebox → Traefik (HTTPS)
    Livebox -->|redirige HTTPS| Traefik

with Gitea

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
graph LR
    %% Styles
    classDef internet fill:#f9f9f9,stroke:#999,color:#333,font-size:12px;
    classDef arcodange fill:#e6e6e6,stroke:#555,color:#333,font-size:12px;
    classDef k3sServer fill:#ff9999,stroke:#cc0000,color:#333,font-size:12px;
    classDef k3sAgent fill:#99ff99,stroke:#00cc00,color:#333,font-size:12px;
    classDef traefik fill:#9999ff,stroke:#0000cc,color:#333,font-size:12px;
    classDef dns fill:#ffff99,stroke:#cccc00,color:#333,font-size:12px;
    classDef cloudflared fill:#ccccff,stroke:#6666cc,color:#333,font-size:12px;
    classDef gitea fill:#ffcc99,stroke:#cc9966,color:#333,font-size:12px;
    classDef cms fill:#99ffcc,stroke:#66cc99,color:#333,font-size:12px;
    classDef domainFR fill:#99ccff,stroke:#0066cc,color:#333,font-size:12px;
    classDef domainLAB fill:#ccff99,stroke:#66cc66,color:#333,font-size:12px;

    %% Internet
    subgraph "Internet / Le monde"
        direction TB
        DuckDNS["🌐 DuckDNS<br>(duckdns.org)"]:::internet
        subgraph "Cloudflare"
            CloudflareService["🛡️ Cloudflare<br>(cloudflare.com)"]:::internet
            DNSNote@{ shape: text, label: "redirige *.arcodange.fr<br>→ Cloudflared Tunnel" }
        end
    end

    %% Bureaux Arcodange
    subgraph "Bureaux Arcodange"
        direction TB
        Livebox["📡 Livebox<br>(passerelle)"]:::arcodange
        MacBook["💻 MacBook Pro M4"]:::arcodange

        %% Cluster Raspberry Pi
        subgraph "Cluster Raspberry Pi"
            direction LR

            %% Noeud pi1
            subgraph "pi1"
                Pi1["🐧 pi1"]:::k3sServer
                subgraph "K3s (Server)"
                    Traefik["🚀 Traefik"]:::traefik
                    Cloudflared["☁️ Cloudflared Tunnel"]:::cloudflared
                end
                PiHole1["🧹 Pi-hole"]:::dns
                GiteaRunner1["🏃 Gitea Act Runner"]:::gitea
                NS1@{ shape: text, label: "nameserver : pi3" }
                DNSArcodange@{ shape: text, label: ".arcodange.lab<br>→ Traefik", color:#cccc00 }
            end

            %% Noeud pi2
            subgraph "pi2"
                Pi2["🐧 pi2"]:::k3sAgent
                Gitea["🦊 Gitea"]:::gitea
                subgraph "K3s (Agent)"
                    CMS["📝 CMS"]:::cms
                end
            end

            %% Noeud pi3
            subgraph "pi3"
                Pi3["🐧 pi3"]:::k3sAgent
                PiHole3["🧹 Pi-hole"]:::dns
                GiteaRunner3["🏃 Gitea Act Runner"]:::gitea
                NS3@{ shape: text, label: "nameserver : pi1" }
            end
        end
    end

    %% Connexions Pi-hole → Livebox (DNS)
    PiHole1 -.->|DNS| Livebox
    PiHole3 -.->|DNS| Livebox

    %% Cloudflare → Cloudflared Tunnel → Traefik
    CloudflareService -.-> Cloudflared
    Cloudflared --> Traefik

    %% Livebox → Traefik (HTTPS)
    Livebox -->|redirige HTTPS| Traefik

    %% Gitea Act Runner → Gitea
    GiteaRunner1 -.->|communication| Gitea
    GiteaRunner3 -.->|communication| Gitea

    %% Domains → CMS via Traefik
    Traefik -->|cms-rec.arcodange.fr| CMS:::domainFR
    Traefik -->|www.arcodange.lab| CMS:::domainLAB

with security

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#05e2ff1c'}}}%%
graph LR
    %% Styles
    classDef internet fill:#f9f9f9,stroke:#999,color:#333,font-size:12px;
    classDef arcodange fill:#e6e6e6,stroke:#555,color:#333,font-size:12px;
    classDef k3sServer fill:#ff9999,stroke:#cc0000,color:#333,font-size:12px;
    classDef k3sAgent fill:#99ff99,stroke:#00cc00,color:#333,font-size:12px;
    classDef traefik fill:#9999ff,stroke:#0000cc,color:#333,font-size:12px;
    classDef dns fill:#ffff99,stroke:#cccc00,color:#333,font-size:12px;
    classDef cloudflared fill:#ccccff,stroke:#6666cc,color:#333,font-size:12px;
    classDef gitea fill:#ffcc99,stroke:#cc9966,color:#333,font-size:12px;
    classDef cms fill:#99ffcc,stroke:#66cc99,color:#333,font-size:12px;
    classDef domainFR fill:#99ccff,stroke:#0066cc,color:#333,font-size:12px;
    classDef domainLAB fill:#ccff99,stroke:#66cc66,color:#333,font-size:12px;
    classDef crowdsec fill:#ff99cc,stroke:#cc6699,color:#333,font-size:12px;
    classDef security fill:#ffcccc,stroke:#cc9999,color:#333,font-size:12px;

    %% Internet
    subgraph "Internet / Le monde"
        direction TB
        subgraph "Cloudflare"
            CloudflareService["🛡️ Cloudflare<br>(cloudflare.com)"]:::internet
            DNSNote@{ shape: text, label: "redirige *.arcodange.fr<br>→ Cloudflared Tunnel" }
        end
    end

    %% Bureaux Arcodange
    subgraph "Bureaux Arcodange"
        direction TB
        Livebox["📡 Livebox<br>(passerelle)"]:::arcodange
        DNSArcodange@{ shape: text, label: ".arcodange.lab<br>→ Traefik" }

        %% Cluster Raspberry Pi
        subgraph "Cluster Raspberry Pi"
            direction LR

            %% Noeud pi1
            subgraph "pi1"
                Pi1["🐧 pi1"]:::k3sServer
                subgraph "K3s (Server)"
                    Traefik["🚀 Traefik"]:::traefik
                    Cloudflared["☁️ Cloudflared Tunnel"]:::cloudflared
                end
                PiHole1["🧹 Pi-hole"]:::dns
                GiteaRunner1["🏃 Gitea Act Runner"]:::gitea
                NS1@{ shape: text, label: "nameserver : pi3" }
            end

            %% Noeud pi2
            subgraph "pi2"
                Pi2["🐧 pi2"]:::k3sAgent
                Gitea["🦊 Gitea"]:::gitea
                subgraph "K3s (Agent)"
                    CMS["📝 CMS"]:::cms
                end
            end

            %% Noeud pi3
            subgraph "pi3"
                Pi3["🐧 pi3"]:::k3sAgent
                PiHole3["🧹 Pi-hole"]:::dns
                GiteaRunner3["🏃 Gitea Act Runner"]:::gitea
                subgraph "K3s (Agent)"
                    CrowdSec["🔒 CrowdSec"]:::crowdsec
                end
                NS3@{ shape: text, label: "nameserver : pi1" }
            end
        end

        %% Sécurité combinée
        subgraph "Sécurité"
            Turnstile["🛡️ Cloudflared Turnstile<br>(CAPTCHA)"]:::security
            BannedIPs["🚫 CrowdSec<br>(IP bannies)"]:::security
        end
    end

    %% Connexions Pi-hole → Livebox (DNS)
    PiHole1 -.->|DNS| Livebox
    PiHole3 -.->|DNS| Livebox

    %% Internet → Livebox
    CloudflareService --> Livebox

    %% Gitea Act Runner → Gitea
    GiteaRunner1 -.->|communication| Gitea
    GiteaRunner3 -.->|communication| Gitea

    %% Cloudflare → Cloudflared Tunnel → Sécurité → Traefik
    CloudflareService -.-> Cloudflared
    Cloudflared --> Turnstile
    Turnstile --> BannedIPs
    BannedIPs --> Traefik

    %% Livebox → Sécurité → Traefik
    Livebox --> Turnstile

    %% Domaines → Gitea/CMS via Traefik
    Traefik -->|gitea.arcodange.fr| Gitea:::domainFR
    Traefik -->|gitea.arcodange.lab| Gitea:::domainLAB
    Traefik -->|cms-rec.arcodange.fr| CMS:::domainFR
    Traefik -->|www.arcodange.lab| CMS:::domainLAB

CI/CD Architecture

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
graph TD
    %% Styles
    classDef gitea fill:#ffcc99,stroke:#cc9966,color:#333;
    classDef argocd fill:#99ffcc,stroke:#66cc99,color:#333;
    classDef vault fill:#ccccff,stroke:#6666cc,color:#333;
    classDef k3s fill:#ff9999,stroke:#cc0000,color:#333;
    classDef runner fill:#ffff99,stroke:#cccc00,color:#333;

    %% Components
    Gitea["Gitea (pi2)"]:::gitea
    ArgoCD["ArgoCD (k3s)"]:::argocd
    Vault["Vault (k3s/tools)"]:::vault
    Runner1["Gitea Act Runner (pi1)"]:::runner
    Runner2["Gitea Act Runner (pi3)"]:::runner
    VaultOperator["Vault Secrets Operator (k3s/tools)"]:::vault
    k3s["k3s Cluster"]:::k3s
    WebApp["WebApp (k3s)"]:::k3s

    %% Workflow
    Gitea -->|OIDC Auth| Vault
    Gitea -->|Trigger CI| Runner1
    Gitea -->|Trigger CI| Runner2
    Runner1 -->|Deploy to| k3s
    Runner2 -->|Deploy to| k3s
    ArgoCD -->|GitOps Sync| Gitea
    ArgoCD -->|Deploy Apps| k3s
    VaultOperator -->|Inject Secrets| WebApp
    Vault -->|Secrets| VaultOperator
    WebApp -->|Uses| VaultOperator

    %% Annotations
    linkStyle 0,1,2,3,4,5,6,7 stroke:#999,stroke-width:1px;
    
    %% Notes
    note1["Custom runner image includes<br>.lab CA cert"]
    Runner1 --> note1
    Runner2 --> note1
    
    %% OIDC Workflow
    note2["OIDC Flow:<br>1. Runner executes vault_oauth__sh_b64<br>2. Obtains ID token from Gitea<br>3. Authenticates with Vault"]
    Runner1 --> note2
    Runner2 --> note2