Rewrite README around project values, split into focused docs
README/README.fr.md now lead with why gitfed exists (ownership, federation, control) instead of jumping straight to setup, with a language-switch link at the top of each. The deep technical content that used to be crammed into the README moved into two new docs: docs/HOW_IT_WORKS.md (the federation/identity model, walked through end to end) and docs/ARCHITECTURE.md (code layout, the admin RPC socket, the k8s topology).
4 files changed
+473 −117
A
README.fr.md
+118 −0
M
README.md
+69 −117
A
docs/ARCHITECTURE.md
+140 −0
A
docs/HOW_IT_WORKS.md
+146 −0
README.fr.md
@@ -0,0 +1,118 @@
+**Langues :** [English](README.md) · Français
+
+# gitfed
+
+*Un serveur git auto-hébergé et fédéré, pour celles et ceux qui veulent que
+leur code et leurs données leur appartiennent vraiment — sans renoncer à
+pouvoir collaborer avec qui que ce soit, où qu'iel héberge son instance.*
+
+---
+
+## Pourquoi gitfed existe
+
+La plupart des solutions d'hébergement git imposent un choix : garder son
+code sur une infrastructure qu'on ne contrôle pas, ou renoncer à collaborer
+avec quiconque en dehors de son propre serveur. gitfed existe pour supprimer
+ce compromis.
+
+- **Propriété** — vos dépôts, vos utilisateurs, votre base de données, sur
+ une machine que vous contrôlez. Aucune plateforme ne peut limiter votre
+ accès, exploiter votre code, ou vous en couper l'accès.
+- **Fédération** — votre instance peut faire confiance à d'autres instances,
+ comme les serveurs de mail se font confiance entre eux. Une personne sur
+ une instance gitfed complètement différente peut recevoir un accès à l'un
+ de vos dépôts sans jamais créer de compte chez vous.
+- **Contrôle** — chaque dépôt est explicitement public ou privé, chaque
+ collaborateur a un rôle explicite (lecture/écriture/admin), et chaque
+ relation de confiance entre instances est également explicite — rien
+ n'est accordé par défaut simplement parce qu'une requête est arrivée.
+
+gitfed n'essaie pas de rivaliser avec GitHub ou GitLab sur le nombre de
+fonctionnalités. L'interface web (navigateur de fichiers, gestion autonome
+des dépôts, panneau admin) existe pour rendre ce modèle d'identité
+utilisable au quotidien — le modèle d'identité est le vrai sujet.
+
+## Comment ça marche, en un paragraphe
+
+Chaque instance gitfed fait tourner sa propre autorité de certification.
+Quand vous poussez ou récupérez du code, votre instance d'origine signe un
+certificat SSH de courte durée qui prouve qui vous êtes. Toute instance qui
+a choisi de faire confiance à l'autorité de certification de votre instance
+d'origine accepte ce certificat — pas de second compte, pas de mot de passe
+partagé, pas d'inscription par instance. L'explication complète, avec
+schémas, est dans [`docs/HOW_IT_WORKS.md`](docs/HOW_IT_WORKS.md), et la même
+explication est aussi servie en direct sur `/security` sur n'importe quelle
+instance en fonctionnement.
+
+## Ce qui est inclus
+
+- **`gitfed-server`** — serveur SSH (opérations git + émission de
+ certificats) et le point de terminaison de fédération
+ `/.well-known/gitfed.json`.
+- **`gitfed-web`** — l'interface web : un navigateur de dépôts public, et,
+ derrière une connexion par mot de passe, la gestion autonome (vos propres
+ clés et dépôts) et une section admin, en français ou en anglais.
+- **`gitfed-tui`** — un outil d'administration en terminal ; le seul moyen
+ d'amorcer le tout premier compte.
+- **`gitfed-renew-cert`** — un petit outil client pour renouveler votre
+ propre certificat avant son expiration.
+
+## Pour aller plus loin
+
+| Doc | Contenu |
+|---|---|
+| [`docs/HOW_IT_WORKS.md`](docs/HOW_IT_WORKS.md) | Le modèle de fédération/identité de bout en bout : certificats, magasin de confiance, ACL, un vrai push détaillé étape par étape. |
+| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Comment le code est organisé : les quatre binaires, les paquets `internal/`, pourquoi il existe un socket RPC d'administration, la topologie Kubernetes. |
+| [`docs/security/AUDIT.md`](docs/security/AUDIT.md) | L'audit de sécurité pré-production et ce qui en a été corrigé. |
+| [`DESIGN.md`](DESIGN.md) | Le document de conception d'origine — le « pourquoi » derrière l'architecture, écrit avant le début de l'implémentation. |
+| [`CHANGELOG.md`](CHANGELOG.md) | Historique des versions (également servi sur `/changelog` dans l'interface web). |
+
+## Le faire tourner en local
+
+```sh
+go build -o bin/gitfed-server ./cmd/gitfed-server
+go build -o bin/gitfed-web ./cmd/gitfed-web
+go build -o bin/gitfed-tui ./cmd/gitfed-tui
+
+./bin/gitfed-server -init localhost -data-dir data -config gitfed.json
+# éditez gitfed.json si vous voulez d'autres ports (par défaut : ssh :2222, http :8443)
+./bin/gitfed-server -config gitfed.json &
+
+./bin/gitfed-tui -config gitfed.json
+# Users -> a (add user) : nom d'utilisateur, votre clé publique SSH, un mot de passe, admin : y
+
+./bin/gitfed-web -config gitfed.json &
+# ouvrez http://localhost:8088, connectez-vous avec ce que vous venez de créer
+```
+
+Le clonage/push passe toujours par SSH :
+
+```sh
+git clone ssh://git@localhost:2222/<user>/<repo>
+```
+
+## Déployer pour de vrai
+
+L'explication complète — manifestes, pourquoi le pod est formé ainsi, DNS,
+amorçage du premier compte — est dans
+[`deploy/k8s/README.md`](deploy/k8s/README.md). Chaque mise à jour après la
+première doit passer par [`deploy/update.sh`](deploy/update.sh), qui
+incrémente la version, construit et importe une image taguée, et effectue
+le déploiement :
+
+```sh
+# ajoutez d'abord une section "## X.Y.Z" à CHANGELOG.md décrivant le changement
+deploy/update.sh # incrément patch
+deploy/update.sh minor # ou : major, ou une version explicite X.Y.Z
+```
+
+## Sauvegardes
+
+Tout ce qui compte vit sur un seul volume persistant : la base de données
+(utilisateurs, dépôts, ACL, sessions, magasin de confiance, journal
+d'audit, révocations de certificats), la clé de l'autorité de certification
+de l'instance, la clé d'hôte SSH, et les dépôts bare eux-mêmes. Perdre la
+clé de la CA invalide tous les certificats jamais émis par cette instance
+et casse toutes les relations de confiance fédérées qui pointent vers elle
+— il n'y a pas de récupération possible en dehors de tout reconstruire
+depuis zéro. Sauvegardez le volume entier, pas seulement les données git.
README.md
@@ -1,100 +1,67 @@
+**Languages:** English · [Français](README.fr.md)
+
# gitfed
-A self-hosted git server where pushing to someone else's instance doesn't
-mean creating a second account there. Your home instance vouches for you —
-literally, via a short-lived SSH certificate it signs — and any instance
-that trusts your home instance's certificate authority accepts it.
-
-Current version: see [CHANGELOG.md](CHANGELOG.md) (also served at
-`/changelog` in the web UI, next to a `gitfed X.Y.Z` link in the footer).
-
-## What this is (and isn't)
-
-- **Git over SSH only.** No HTTP git protocol, no reimplementation of git
- itself — gitfed wraps `git-upload-pack`/`git-receive-pack` as
- subprocesses and lets git do what git does.
-- **Federated identity, not federated data.** A repo lives on exactly one
- instance; there's no replication or mirroring. Federation is purely
- about who's allowed to push/pull, carried by certificates instead of
- per-instance accounts.
-- **Not a GitHub/GitLab clone**, even though the web UI looks like one.
- The point of the project is the identity model above — everything else
- (file browser, self-service repos, admin panel) exists to make that
- model usable day to day, not to compete on feature count.
-
-Full design rationale and the protocol-level detail live in
-[DESIGN.md](DESIGN.md); this file is the practical "what do I actually run"
-version.
-
-## How it works
-
-- Each instance generates its own ed25519 **certificate authority** on
- first run. A local user with a registered SSH key can ask their own
- instance for a certificate (`ssh git@host gitfed-cert`, or the
- `gitfed-renew-cert` helper below); the certificate embeds the principal
- `<username>@<domain>` and expires in a couple of days by default.
-- To let someone from another instance collaborate on a repo, an admin (or
- the repo owner) grants their principal a role — `read`, `write`, or
- `admin`. The first time a given remote domain shows up, the instance
- fetches `https://<domain>/.well-known/gitfed.json` to learn that
- instance's CA public key, and either trusts it immediately or holds it
- **pending** for an admin to approve, depending on the configured trust
- policy (whitelist by default, auto-trust/TOFU opt-in).
-- Once a domain is trusted, verifying a certificate from it is entirely
- local — no network call on every SSH connection, no dependency on the
- remote instance being reachable at push/pull time.
-- Repos can be public (readable by any authenticated principal, from any
- trusted domain, without an explicit grant) or private (owner and
- explicitly granted collaborators only). Write access always requires an
- explicit grant regardless of visibility.
+*A self-hosted, federated git server for people who want their code and their
+data to stay theirs — without losing the ability to work with anyone,
+wherever they host.*
+
+---
+
+## Why gitfed exists
+
+Most git-hosting options ask for a trade: keep your code somewhere you don't
+control, or give up the ability to collaborate with anyone outside your own
+server. gitfed exists to remove that trade-off.
+
+- **Ownership** — your repositories, your users, your database, on hardware
+ you control. No platform can throttle your access, mine your code, or shut
+ you out.
+- **Federation** — your instance can trust other people's instances, the way
+ mail servers trust each other. Someone on a completely different gitfed
+ instance can be granted access to one of your repos without ever creating
+ an account with you.
+- **Control** — every repo is explicitly public or private, every
+ collaborator has an explicit role (read/write/admin), and every trust
+ relationship between instances is explicit too — nothing is granted by
+ default just because a request showed up.
+
+gitfed isn't trying to out-feature GitHub or GitLab. The web UI (file
+browser, self-service repos, admin panel) exists to make the identity model
+usable day to day — the identity model itself is the point.
+
+## How it works, in one paragraph
+
+Each gitfed instance runs its own certificate authority. When you push or
+pull, your home instance signs a short-lived SSH certificate that says who
+you are. Any instance that has chosen to trust your home instance's
+certificate authority accepts that certificate — no second account, no
+shared password, no per-instance registration. The full walkthrough is in
+[`docs/HOW_IT_WORKS.md`](docs/HOW_IT_WORKS.md) — written in French, like the
+rest of the project's internal docs — and the same explanation, with
+diagrams, is served live at `/security` on any running instance.
## What's included
-- **`gitfed-server`** — the daemon: SSH server (git + cert issuance) and
- the `/.well-known/gitfed.json` HTTP endpoint.
-- **`gitfed-web`** — the web UI: a public, unauthenticated repo browser
- (file tree, rendered README/LICENSE, tags, topics) plus, behind a
- username/password login, self-service (your own keys and repos) and an
- admin section (users, trust store, audit log) gated by an admin flag on
- the account. The web login only ever grants access to this UI — git
- push/pull always goes over SSH with a key or certificate, independently
- of it.
-- **`gitfed-tui`** — a terminal admin tool that talks to the same store
- gitfed-web does. It's the way to bootstrap the very first account (there
- is no self-registration), and works even if the web UI is down, since it
- can also open the database directly when the server isn't running.
-- **`gitfed-renew-cert`** — a small client-side tool end users run on
- *their own* machine (e.g. from a cron job or systemd timer) to renew
- their certificate shortly before it expires.
-
-## Project layout
-
-```
-cmd/
- gitfed-server/ SSH server + well-known endpoint
- gitfed-web/ web UI (public browsing + self-service + admin)
- gitfed-tui/ terminal admin tool
- gitfed-renew-cert/ client-side certificate renewal helper
-internal/
- ca/ CA key generation + certificate signing
- ssh/ SSH server, auth, ACL enforcement, git exec
- federation/ .well-known discovery, trust store
- acl/ repo access-control checks
- gitexec/ git-upload-pack/receive-pack + tree/blob reads
- store/ bbolt-backed persistence
- admin/ the Ops interface both TUI and web drive
- adminrpc/ lets gitfed-web talk to a *running* server
- over a Unix socket instead of racing it for
- the store's exclusive file lock
- opsconnect/ picks live-socket vs. direct-store mode
-deploy/
- docker/Dockerfile multi-stage build for server/web/tui
- k8s/ manifests + deployment README (k3s/Traefik/
- cert-manager)
- update.sh version bump + build + deploy in one command
-DESIGN.md architecture and protocol rationale
-CHANGELOG.md version history
-```
+- **`gitfed-server`** — SSH server (git operations + certificate issuance)
+ and the `/.well-known/gitfed.json` federation endpoint.
+- **`gitfed-web`** — the web UI: a public repo browser, and, behind a
+ password login, self-service (your own keys and repos) and an admin
+ section, in French or English.
+- **`gitfed-tui`** — a terminal admin tool; the only way to bootstrap the
+ first account.
+- **`gitfed-renew-cert`** — a small client tool to renew your own
+ certificate before it expires.
+
+## Learn more
+
+| Doc | What's in it |
+|---|---|
+| [`docs/HOW_IT_WORKS.md`](docs/HOW_IT_WORKS.md) *(French)* | The federation/identity model end to end: certificates, trust store, ACLs, a real push walked through step by step. |
+| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) *(French)* | How the code is organized: the four binaries, the `internal/` packages, why there's an admin RPC socket, the Kubernetes topology. |
+| [`docs/security/AUDIT.md`](docs/security/AUDIT.md) *(French)* | The pre-production security audit and what was fixed as a result. |
+| [`DESIGN.md`](DESIGN.md) *(French)* | The original design rationale — the "why" behind the architecture, written before implementation started. |
+| [`CHANGELOG.md`](CHANGELOG.md) | Version history (also served at `/changelog` in the web UI). |
## Running it locally
@@ -122,26 +89,11 @@ git clone ssh://git@localhost:2222/<user>/<repo>
## Deploying for real
-The setup this project actually runs on is a single-node k3s VPS with
-Traefik and cert-manager already handling another app (specifically,
-[ess-helm](https://github.com/element-hq/ess-helm)) — gitfed reuses both
-rather than standing up its own ingress/TLS. Full walkthrough, including
-why the deployment is shaped the way it is (bbolt's single-writer lock,
-why there are two containers in one pod, why the SSH port can't be 22),
-is in **[deploy/k8s/README.md](deploy/k8s/README.md)**.
-
-Short version:
-
-```sh
-docker build -f deploy/docker/Dockerfile -t gitfed:latest .
-docker save gitfed:latest -o gitfed.tar # then import into your cluster's runtime
-kubectl apply -f deploy/k8s/ # namespace, configmap, pvc, deployment, service, ingress
-```
-
-For every update after the first, use `deploy/update.sh` instead of
-repeating that by hand — it bumps `VERSION`, builds and imports an image
-tagged with the actual version (not just `:latest`), points the deployment
-at it, applies, and commits:
+The full walkthrough — manifests, why the pod is shaped the way it is, DNS,
+bootstrapping the first account — is in
+[`deploy/k8s/README.md`](deploy/k8s/README.md). Every update after the first
+should go through [`deploy/update.sh`](deploy/update.sh), which bumps the
+version, builds and imports a tagged image, and rolls it out:
```sh
# add a "## X.Y.Z" section to CHANGELOG.md describing the change first
@@ -151,10 +103,10 @@ deploy/update.sh minor # or: major, or an explicit X.Y.Z
## Backups
-Everything that matters lives on one persistent volume: the database
-(users, repos, ACLs, sessions, trust store, audit log), the instance's CA
-key, the SSH host key, and the bare repos themselves. Losing the CA key
-specifically invalidates every certificate this instance has ever issued
-and breaks every federated trust relationship pointing at it — there's no
+Everything that matters lives on one persistent volume: the database (users,
+repos, ACLs, sessions, trust store, audit log, certificate revocations), the
+instance's CA key, the SSH host key, and the bare repos themselves. Losing
+the CA key invalidates every certificate this instance has ever issued and
+breaks every federated trust relationship pointing at it — there's no
recovery short of everyone re-establishing trust from scratch. Back up the
whole volume, not just the git data.
docs/ARCHITECTURE.md
@@ -0,0 +1,140 @@
+# Architecture
+
+Comment le code de gitfed est organisé, et pourquoi. Pour le modèle
+d'identité/fédération lui-même, voir [`HOW_IT_WORKS.md`](HOW_IT_WORKS.md) ;
+pour la marche à suivre de déploiement, voir
+[`deploy/k8s/README.md`](../deploy/k8s/README.md).
+
+---
+
+## 1. Vue d'ensemble : quatre binaires, un cœur partagé
+
+```
+cmd/
+ gitfed-server/ démon : serveur SSH + endpoint /.well-known
+ gitfed-web/ interface web (navigation publique + self-service + admin)
+ gitfed-tui/ outil d'administration en terminal
+ gitfed-renew-cert/ outil client de renouvellement de certificat
+```
+
+`gitfed-server` est le seul processus qui touche directement la base de
+données et les dépôts git. `gitfed-web` et `gitfed-tui` ne réimplémentent
+rien : ils pilotent le même cœur métier via l'interface `admin.Ops`
+(§3) — soit en parlant à un `gitfed-server` déjà lancé via un socket,
+soit, s'il n'est pas lancé, en ouvrant directement la base.
+`gitfed-renew-cert` est complètement à part : un petit client autonome,
+pensé pour tourner sur la machine d'un·e utilisateur·ice (cron/systemd
+timer), qui ne parle qu'en SSH à une instance distante.
+
+## 2. Les paquets `internal/`
+
+| Paquet | Rôle |
+|---|---|
+| `ca` | Génère la clé d'autorité de certification de l'instance et signe les certificats utilisateurs. |
+| `ssh` | Le serveur SSH lui-même : authentification (clé nue ou certificat), vérification de révocation, exécution de `git-upload-pack`/`git-receive-pack`. |
+| `federation` | Découverte `.well-known`, garde-fous anti-SSRF (`wellknown.go`), magasin de confiance et sa politique (`resolver.go`). |
+| `acl` | Les règles d'autorisation par dépôt (public/privé, rôles read/write/admin). |
+| `gitexec` | Tout ce qui invoque `git` en sous-processus : init, lecture d'arbre/fichier à HEAD, résolution de branche par défaut. |
+| `store` | Persistance bbolt : utilisateurs, dépôts, ACL, sessions, magasin de confiance, journal d'audit, révocations. |
+| `admin` | L'interface `Ops` : chaque opération d'administration ou de self-service, dans un seul endroit, implémentée une fois. |
+| `adminrpc` | Protocole JSON-sur-socket-Unix qui expose `admin.Ops` à un client distant (voir §3). |
+| `opsconnect` | Choisit automatiquement le mode socket-vivant ou base-directe selon qu'un `gitfed-server` tourne déjà. |
+| `i18n` | Dictionnaires de traduction (français/anglais) de l'interface web et fonction de résolution de langue. |
+| `config` | Chargement du fichier de configuration de l'instance (`gitfed.json`). |
+| `version` | Numéro de version, injecté à la compilation. |
+
+## 3. Pourquoi un socket RPC d'administration
+
+bbolt (la base embarquée utilisée par `store`) impose un **verrou exclusif
+mono-écrivain** sur son fichier : un seul processus peut l'ouvrir en
+écriture à la fois. Si `gitfed-web` ouvrait la base directement pendant que
+`gitfed-server` tourne déjà, l'un des deux échouerait à démarrer, ou pire,
+les deux se disputeraient le verrou.
+
+La solution : `gitfed-server` expose son `admin.Ops` sur un socket Unix
+local (`data/admin.sock`), via un petit protocole JSON maison
+(`internal/adminrpc`). `gitfed-web` et `gitfed-tui` s'y connectent comme
+client au lieu de rouvrir la base — c'est `internal/opsconnect` qui décide,
+au démarrage, s'il faut passer par ce socket (mode « live ») ou ouvrir la
+base directement (mode « hors-ligne », utilisé par `gitfed-tui` quand aucun
+serveur ne tourne, par exemple pour créer le tout premier compte).
+
+Un piège corrigé au passage : les erreurs qui traversent ce socket perdent
+leur identité si on n'y prend pas garde — `errors.New("not found")` côté
+serveur redevient une simple chaîne de caractères côté client, qui ne peut
+plus être comparée avec `== store.ErrNotFound`. `adminrpc.Client` reconstruit
+donc explicitement les erreurs sentinelles connues par correspondance de
+message plutôt que de les laisser passer telles quelles.
+
+## 4. L'interface web (`cmd/gitfed-web`)
+
+Chaque page est un handler HTTP qui : vérifie les droits d'accès via
+`admin.Ops`, exécute un petit template Go pour produire le corps de la
+page, puis appelle `server.render()` qui enveloppe ce corps dans le
+"shell" commun (barre de navigation, pied de page, feuille de style,
+sprite d'icônes SVG) défini dans `render.go`.
+
+Points notables :
+
+- **Pas de framework JS.** Tout le rendu est fait côté serveur avec
+ `html/template` ; le peu de JavaScript (menu profil, bascule d'onglets,
+ copie dans le presse-papiers) est un unique bloc inline, dont le hash
+ SHA-256 est épinglé dans la politique CSP (voir §6).
+- **i18n par fonction de template.** Chaque page passe sa langue courante
+ (résolue par cookie, puis `Accept-Language`, voir `lang.go`) dans les
+ données du template ; les chaînes de caractères passent par
+ `{{t .Lang "clé"}}`, qui va chercher la traduction dans `internal/i18n`.
+- **Authentification web indépendante de git.** Le mot de passe web
+ (haché bcrypt, coût 12) n'ouvre qu'une session opaque côté web — il
+ n'intervient jamais dans l'authentification SSH/git, qui repose
+ uniquement sur les clés et certificats (voir `HOW_IT_WORKS.md`).
+
+## 5. Topologie de déploiement
+
+```
+┌─────────────────────────── Pod (1 réplique) ───────────────────────────┐
+│ │
+│ conteneur "server" conteneur "web" │
+│ gitfed-server gitfed-web │
+│ ├─ SSH :2222 (hostPort) ├─ HTTP :8088 │
+│ ├─ well-known :8443 └─ socket data/admin.sock (client) │
+│ └─ socket data/admin.sock (serveur) │
+│ │
+│ volume "data" (PVC) monté dans les deux conteneurs : │
+│ base bbolt, dépôts bare, clé de CA, clé d'hôte SSH, socket admin │
+└──────────────────────────────────────────────────────────────────────┘
+```
+
+- **Une seule réplique, toujours.** bbolt (verrou mono-écrivain), le socket
+ admin et le port SSH exposé en `hostPort` sont tous, par nature, des
+ ressources à instance unique — `deployment.yaml` fixe `replicas: 1` et
+ `strategy: Recreate` plutôt qu'un rolling update.
+- **Deux conteneurs, un seul pod.** Ils partagent le même volume de
+ données ; le conteneur `web` attend, via une boucle shell, que
+ `data/admin.sock` existe avant de démarrer.
+- **SSH sur `hostPort: 2222`**, pas 22 — le port 22 du nœud est déjà pris
+ par le sshd de la VPS elle-même.
+- **`gitfed-web` seul est exposé par une Ingress** (Traefik + cert-manager
+ déjà en place sur le cluster pour un autre projet) ; c'est sûr de
+ l'exposer publiquement car l'authentification web est réelle (mot de
+ passe + session), pas un simple accès admin non protégé.
+
+Le détail pas-à-pas (DNS, premier déploiement, amorçage du compte admin)
+est dans [`deploy/k8s/README.md`](../deploy/k8s/README.md).
+
+## 6. Ce qui protège l'instance en production
+
+Voir [`docs/security/AUDIT.md`](security/AUDIT.md) pour le détail complet,
+mais en résumé, ce qui est en place structurellement :
+
+- **Aucun protocole git en HTTP** — tout passe par SSH, une seule porte
+ d'entrée réseau pour le git lui-même.
+- **Anti-SSRF au moment de la connexion**, pas seulement à la validation du
+ nom de domaine (`internal/federation/wellknown.go`) — protège aussi
+ contre le DNS-rebinding.
+- **CSP stricte** avec script inline épinglé par hash, en-têtes de
+ durcissement standards, vérification d'origine sur les requêtes de
+ mutation (défense CSRF en profondeur, en complément de `SameSite=Lax`).
+- **Révocation de certificat immédiate**, consultée à chaque authentification
+ SSH (`internal/ssh/server.go`).
+- **Limitation de débit** sur le login web, par compte et par IP.
docs/HOW_IT_WORKS.md
@@ -0,0 +1,146 @@
+# Comment fonctionne gitfed
+
+Ce document explique le mécanisme central de gitfed : comment deux instances
+qui ne se connaissent pas peuvent laisser leurs utilisateurs collaborer sans
+qu'aucun compte ne soit jamais partagé entre elles. Pour le contexte plus
+large (fichiers, code, déploiement), voir le [README](../README.fr.md) ; pour
+le détail de chaque paquet Go, voir [`ARCHITECTURE.md`](ARCHITECTURE.md).
+
+---
+
+## 1. Le problème que ça résout
+
+Sur GitHub ou GitLab, collaborer avec quelqu'un suppose soit que vous ayez
+tous les deux un compte sur la même plateforme, soit que l'un des deux
+migre. gitfed part d'un principe différent, plus proche du mail ou
+d'ActivityPub : **chaque personne a un compte sur une seule instance — la
+sienne — et cette instance peut prouver son identité auprès de n'importe
+quelle autre instance qui accepte de lui faire confiance.**
+
+Concrètement : alice a un compte sur `chez-moi.fr`. Bob a un compte sur
+`ailleurs.net`. Bob peut pousser du code sur un dépôt d'alice sans jamais
+créer de compte chez elle — à condition que l'instance d'alice ait choisi de
+faire confiance à l'instance de Bob.
+
+## 2. L'identité : un certificat plutôt qu'un mot de passe partagé
+
+Chaque instance gitfed génère, à sa création, sa propre **autorité de
+certification** (une paire de clés ed25519, stockée sur le volume de
+données). C'est le socle de tout le modèle :
+
+1. Un utilisateur local enregistre une clé SSH publique auprès de son
+ instance (via `gitfed-tui` pour le tout premier compte, ou en
+ autonomie ensuite dans `Paramètres → Clés SSH`).
+2. Quand il en a besoin, il demande à son instance un certificat
+ (`ssh git@instance gitfed-cert`, ou via l'outil `gitfed-renew-cert` en
+ tâche planifiée). L'instance vérifie que la clé lui appartient bien, puis
+ signe un **certificat SSH** au format OpenSSH.
+3. Ce certificat embarque un **principal** — `bob@ailleurs.net`, jamais un
+ simple nom d'utilisateur seul — et une durée de validité courte,
+ **24 heures par défaut**.
+4. Quand Bob se connecte à l'instance d'alice, il présente ce certificat au
+ lieu de sa clé nue. L'instance d'alice vérifie deux choses : que la
+ signature du certificat correspond bien à une autorité en laquelle elle a
+ confiance, et que ce certificat n'a pas été révoqué (§6).
+
+Aucune de ces deux instances n'a besoin de contacter l'autre en direct pour
+cette vérification — la signature suffit, un peu comme un passeport n'a pas
+besoin d'un appel téléphonique au pays émetteur pour être vérifié à la
+frontière, tant qu'on connaît la clé publique de l'autorité qui l'a délivré.
+
+## 3. La confiance entre instances : le magasin de confiance
+
+Faire confiance à une autorité, ça se construit. La première fois que
+l'instance d'alice croise le domaine `ailleurs.net` (typiquement parce
+qu'alice a accordé un accès à `bob@ailleurs.net` sur un de ses dépôts), elle
+déclenche une **découverte fédérée** :
+
+1. Elle récupère `https://ailleurs.net/.well-known/gitfed.json`, un document
+ public qui contient la clé publique de l'autorité de certification de
+ cette instance.
+2. Elle vérifie que ce document est cohérent (le domaine déclaré correspond
+ bien, une clé est présente) et que la requête ne pointe pas vers une
+ adresse interne ou privée (protection anti-SSRF : IP littérales,
+ `127.0.0.0/8`, `10.0.0.0/8`, `169.254.0.0/16`/métadonnées cloud, etc. sont
+ toutes refusées, y compris après résolution DNS).
+3. Selon la politique configurée sur l'instance d'alice :
+ - **liste blanche (par défaut)** — le domaine est enregistré comme **en
+ attente** ; un administrateur doit l'approuver explicitement dans
+ `Administration → Confiance fédérée` avant que le moindre accès ne soit
+ accordé.
+ - **auto-confiance (TOFU — "trust on first use")** — le domaine est
+ approuvé immédiatement à la première découverte, à activer uniquement
+ si on accepte ce niveau de risque.
+4. La découverte est limitée à **10 nouveaux domaines par minute** pour
+ toute l'instance, pour empêcher qu'un compte compromis ne s'en serve
+ comme sonde réseau à grande échelle.
+
+Une fois un domaine approuvé, la vérification d'un certificat qui en
+provient est **entièrement locale** : aucune requête réseau n'est refaite à
+chaque `git push`.
+
+## 4. L'autorisation : qui peut faire quoi sur quel dépôt
+
+L'identité fédérée dit *qui* on est ; elle ne dit rien de ce qu'on a le
+droit de faire. Ça, c'est le rôle du modèle d'ACL, entièrement local à
+chaque dépôt :
+
+- Chaque dépôt a un **propriétaire** (toujours admin) et, optionnellement,
+ des **collaborateurs**, chacun avec un rôle explicite :
+ **lecture**, **écriture**, ou **admin**.
+- Un dépôt est en plus **public** ou **privé**. Un dépôt public accorde la
+ **lecture** à n'importe quel principal authentifié, de n'importe quelle
+ instance de confiance, sans qu'il soit besoin de l'ajouter comme
+ collaborateur. L'**écriture** n'est en revanche jamais implicite — même
+ sur un dépôt public, il faut un rôle explicite pour pousser du code.
+- Octroyer un accès à un principal sur un domaine encore inconnu de
+ l'instance est justement ce qui déclenche la découverte fédérée décrite
+ au §3.
+
+## 5. Le cycle complet, étape par étape
+
+Reprenons l'exemple : alice veut donner à Bob un accès en écriture sur
+`alice/mon-projet`.
+
+1. **Sur l'instance d'alice** — alice va dans les paramètres du dépôt et
+ accorde le rôle *écriture* à `bob@ailleurs.net`.
+2. Si `ailleurs.net` est un domaine encore inconnu, l'instance d'alice
+ déclenche la découverte (§3) et le domaine reste **en attente** tant
+ qu'un admin ne l'a pas approuvé.
+3. **Sur l'instance de Bob** — Bob demande un certificat à sa propre
+ instance (`gitfed-cert` ou renouvellement automatique). Son instance
+ signe un certificat portant le principal `bob@ailleurs.net`.
+4. Bob exécute `git push ssh://git@chez-moi.fr:2222/alice/mon-projet.git`,
+ en s'authentifiant avec son certificat.
+5. L'instance d'alice vérifie la signature du certificat contre la clé
+ d'autorité qu'elle a approuvée pour `ailleurs.net`, vérifie qu'il n'est
+ pas révoqué, puis vérifie dans son ACL locale que `bob@ailleurs.net` a
+ bien le rôle *écriture* sur `alice/mon-projet`.
+6. Si tout est en ordre, `git-receive-pack` s'exécute normalement, exactement
+ comme pour un utilisateur local.
+
+À aucun moment Bob n'a eu besoin d'un compte, d'un mot de passe ou d'une
+clé enregistrée chez alice.
+
+## 6. Expiration et révocation
+
+Un certificat de 24 h qui traîne après la suppression d'un compte reste, en
+théorie, un accès résiduel. gitfed traite ça de deux façons complémentaires :
+
+- **Le TTL est court** (24 h par défaut, configurable) — la fenêtre
+ d'exposition d'une clé compromise est bornée même sans action de
+ l'administrateur.
+- **La révocation est immédiate.** Supprimer un utilisateur ou retirer une
+ de ses clés SSH enregistre la révocation correspondante (par principal
+ et/ou par empreinte de clé) ; chaque authentification SSH consulte cette
+ liste avant d'accepter un certificat, même s'il est encore
+ cryptographiquement valide.
+
+## 7. Ce qui n'est *pas* fédéré
+
+Pour être clair sur les limites du modèle : **un dépôt vit sur une seule
+instance.** Il n'y a ni réplication, ni miroir automatique entre instances.
+La fédération ne concerne que l'identité — qui a le droit de pousser ou de
+lire — jamais les données elles-mêmes. Si l'instance d'alice tombe, son
+dépôt n'est disponible nulle part ailleurs tant qu'il n'a pas été cloné en
+dehors.