This commit is contained in:
Gilles Soulier
2026-01-05 13:13:08 +01:00
parent 8e14adafc6
commit 1d177e96a6
149 changed files with 29541 additions and 1 deletions

185
docs/AGENT.md Normal file
View File

@@ -0,0 +1,185 @@
# 📄 agent.md — Mesh Agent (Rust pragmatique)
## 1. Description
**Mesh Agent** est un agent desktop multi-OS (Linux / Windows / macOS) conçu pour compléter Mesh (app web) avec des capacités locales avancées.
Objectifs :
- **Flux lourds P2P** (faible charge serveur)
- **Binaires multi-OS** simples à déployer
- Terminal/SSH partagé robuste
- Partage fichiers/dossiers performant
- Notifications directes Gotify
Lagent communique :
- avec le **Mesh Server** (REST + WebSocket) pour auth/permissions/signalisation,
- avec les **autres clients/agents** en **P2P** pour les flux data,
- avec **Gotify** pour les notifications.
---
## 2. Architecture (control plane vs data plane)
- **Control plane** : Mesh Server
- Auth
- Rooms & ACL
- Tokens de capacités (TTL court)
- Signalisation (WS)
- Événements + notifications Gotify
- **Data plane** : P2P
- Audio/Vidéo/Écran : WebRTC (côté client web)
- Fichiers/Dossiers/Terminal : P2P via **QUIC** (recommandé V1)
- fallback possible : WebRTC DataChannel (V2) ou HTTP temporaire (exception)
Le serveur ne transporte pas de média ni de transferts lourds.
---
## 3. Fonctions principales
### 3.1 Partage fichiers & dossiers (P2P)
- Envoi fichier : chunks + reprise + checksum
- Envoi dossier :
- mode simple : **zip à la volée**
- mode sync (optionnel) : watcher + manifest + diff
- Débit contrôlé + backpressure
### 3.2 Terminal / SSH share (preview + contrôle)
- Lagent lance une session locale via **PTY** (bash/pwsh) et peut lancer `ssh user@host`.
- Diffuse la sortie terminal en P2P.
- Modes :
- **preview (lecture seule)** par défaut
- **take control** (un seul contrôleur à la fois)
### 3.3 Notifications
- Envoi direct vers Gotify (agent → Gotify)
- Notifications OS locales (optionnel V1)
### 3.4 Intégration OS
- Tray icon (optionnel)
- Auto-start (optionnel)
- Identité machine : `device_id`
---
## 4. Stack technique (Rust)
### Runtime / réseau
- **Rust stable**
- **tokio** (async)
- **reqwest** (HTTP)
- **tokio-tungstenite** (WebSocket)
- **quinn** (QUIC P2P) — recommandé pour data plane (fichiers/terminal)
### Terminal
- Unix : **portable-pty** (ou equivalent) pour PTY
- Windows : **ConPTY** (crate dédiée / wrapper)
### FS / sync
- notify (watcher cross-platform)
- hashing (blake3 recommandé)
### Config / logs
- serde + toml/yaml
- tracing + tracing-subscriber
### Packaging
- binaire unique par OS
- install : MSI (Windows), deb/rpm (Linux), dmg/pkg (macOS) — V1/V2
---
## 5. Structure recommandée
```
agent/
Cargo.toml
src/
main.rs
config/
mod.rs
mesh/
rest.rs
ws.rs
types.rs
p2p/
mod.rs
quic/
endpoint.rs
protocol.rs
fallback_http/
mod.rs
share/
file_send.rs
folder_zip.rs
folder_sync/
manifest.rs
diff.rs
watcher.rs
terminal/
mod.rs
pty_unix.rs
conpty_windows.rs
stream.rs
notifications/
gotify.rs
router.rs
os/
autostart.rs
tray.rs
tests/
```
---
## 6. Protocole & permissions
- Toutes les actions P2P sont autorisées par **capability tokens** (TTL court), émis par le serveur :
- `share:file`, `share:folder`, `terminal:view`, `terminal:control`
- Le P2P (QUIC) utilise un **handshake applicatif** :
- échange dun token de session (issu du serveur)
- validation côté pair avant daccepter un flux
---
## 7. Sécurité (résumé)
- Les secrets SSH ne sortent jamais de la machine partageuse.
- Terminal share : preview-only par défaut.
- Chiffrement transport : QUIC (TLS 1.3), WebSocket/TLS.
- Logs sans contenu sensible.
---
## 8. TODO
### MVP (priorité)
- [ ] Config + identité `device_id`
- [ ] Connexion WS au serveur (auth)
- [ ] Réception events (room, terminal, share)
- [ ] Notifications Gotify
- [ ] Terminal preview (PTY) + stream P2P (QUIC)
### V1
- [ ] Envoi fichier P2P (QUIC)
- [ ] Envoi dossier (zip) P2P
- [ ] Take control terminal (arbitrage via serveur)
- [ ] Diagnostics (mode P2P, erreurs, stats)
### V2
- [ ] Sync dossier (manifest/diff)
- [ ] Tray + autostart multi-OS
- [ ] Fallback HTTP serveur (temporaire) si P2P impossible
- [ ] WebRTC DataChannel agent (si besoin compat)
---
## 9. Améliorations futures
- `.meshignore`
- Diff binaire + dédup
- Chiffrement applicatif E2E optionnel
- Tunnel SSH (TCP-like) au-dessus de QUIC (avancé)
---
## 10. Changelog
```
0.1.0 Rust agent skeleton (WS + Gotify)
0.2.0 Terminal share preview (QUIC)
0.3.0 File transfer (QUIC)
0.4.0 Folder zip + take control
0.5.0 Folder sync (beta)
```

View File

@@ -0,0 +1,306 @@
# Livrables — Agent Rust
Ce document contient :
1. le fichier `agent/CLAUDE.md`
2. un prompt Codex initial pour générer le squelette compilable de `agent/`
---
## 1) `agent/CLAUDE.md`
```markdown
# CLAUDE.md — Mesh Agent (Rust)
## Portée
Ce fichier sapplique **uniquement** au code situé dans `agent/`.
Il complète le `CLAUDE.md` racine (vision/architecture). En cas de conflit, le `CLAUDE.md` racine prime.
---
## Objectifs de lagent
- Multi-OS (Linux, Windows, macOS)
- Faible charge serveur : le serveur ne transporte pas de flux lourds
- Data-plane performant :
- **QUIC (TLS 1.3)** pour fichiers/dossiers/terminal
- fallback HTTP temporaire (exceptionnel)
- Terminal share robuste via PTY (preview par défaut)
- Notifications directes Gotify
---
## Rappels darchitecture
- Control plane : Mesh Server (REST + WS)
- auth, rooms, ACL
- capability tokens TTL court
- orchestration des sessions P2P
- Data plane : agent ↔ agent
- QUIC via `quinn`
- handshake applicatif obligatoire `P2P_HELLO`
---
## Règles de sécurité (non négociables)
- Aucun contournement des capability tokens.
- Terminal : **preview-only** par défaut.
- Input distant uniquement si :
1) serveur a accordé `terminal:control`
2) lagent propriétaire confirme/maintient le contrôleur actif
- Ne jamais exfiltrer : clés SSH, mots de passe, variables secrètes.
- Logs sans contenu sensible (pas de contenu de terminal en clair dans les logs).
---
## Contraintes de code Rust
- Rust stable
- Async : `tokio`
- HTTP : `reqwest`
- WebSocket : `tokio-tungstenite`
- QUIC : `quinn`
- Config : `serde` + `toml` (ou yaml)
- Logs : `tracing`
Qualité :
- Interdiction de `unwrap()` / `expect()` en code final
- Erreurs explicites (`Result`) + `thiserror` recommandé
- Tests stubs acceptés, mais structure prête
---
## Structure obligatoire du dossier
Ne pas renommer arbitrairement.
```
agent/ Cargo.toml src/ main.rs config/ mesh/ p2p/ quic/ share/ terminal/ notifications/ os/ tests/
```
---
## Développement : ordre recommandé
1) **Squelette compilable** + config + logs
2) Client Mesh (REST + WS)
3) Notifications Gotify (agent → gotify)
4) Terminal preview via PTY (local) + stream (transport abstrait)
5) QUIC : endpoint + handshake `P2P_HELLO`
6) Terminal preview sur QUIC
7) File transfer sur QUIC
8) Folder zip sur QUIC
9) Terminal take-control (arbitrage via serveur)
---
## Consigne de suivi davancement
À chaque jalon significatif **ou** vers ~80 % dune session de travail, produire :
```
ÉTAT DAVANCEMENT Mesh Agent ✔ Terminé :
- ... ◻ En cours :
- ... ✖ À faire :
- ... Risques / points bloquants :
- ... Prochaine action recommandée :
- ...
```
---
## Gestion du contexte (pour Claude)
- Utiliser `/clear` entre tâches distinctes.
- Pour revue sécurité/perf : utiliser un sous-agent.
Le contexte durable doit rester dans :
- `CLAUDE.md` (racine)
- ce `agent/CLAUDE.md`
- `docs/`.
```
---
## 2) Prompt Codex initial — squelette compilable `agent/`
```markdown
# Codex Prompt — Mesh Agent (Rust) : Skeleton compilable (étape 1)
Contexte
- Projet Mesh : server Python (control plane), client web, agent Rust (data plane QUIC).
- Références : `CLAUDE.md` racine + `agent.md` + `agent/CLAUDE.md`.
Objectif
- Générer un squelette **compilable** du dossier `agent/` (Rust stable) avec la structure imposée.
- Aucun code “tout-en-un”. Travail en modules minimalistes.
Règles
- Rust stable, tokio, tracing.
- Pas de unwrap/expect dans le code final.
- Erreurs via Result + thiserror.
- Config via serde + toml.
Livrables attendus
1) `agent/Cargo.toml` avec dépendances minimales : tokio, tracing, tracing-subscriber, serde, toml, reqwest, tokio-tungstenite, thiserror.
2) Arborescence `src/` avec modules vides mais raccordés :
- config/
- mesh/ (rest.rs, ws.rs, types.rs)
- notifications/ (gotify.rs, router.rs)
- terminal/ (mod.rs, stream.rs)
- p2p/quic/ (endpoint.rs, protocol.rs)
- share/ (file_send.rs, folder_zip.rs)
- os/ (autostart.rs, tray.rs)
3) `src/main.rs` :
- charge config
- init tracing
- démarre runtime tokio
- affiche un log “agent started”
4) `agent/README.md` : commandes build/run
5) `agent/.env.example` (si utile) + `config.example.toml`
Ne pas implémenter la logique QUIC/WS complète maintenant : uniquement les stubs dAPI et la compilation.
Stop
- Quand tout compile (`cargo build`), terminer et fournir un court résumé des fichiers créés.
- Si tu estimes être à ~80 % de lobjectif, produire un état davancement au format imposé avant daller plus loin.
```
---
## Traçabilité des modifications de code (obligatoire)
Chaque **agent (Claude principal, sous-agent, Codex, ou autre)** qui **crée ou modifie du code** doit **explicitement lindiquer dans les fichiers concernés**.
### Règles de traçabilité
- **Tout fichier créé** doit contenir un commentaire en **début de fichier** indiquant :
- lagent auteur,
- la date,
- lobjectif de la création.
- **Toute modification dun fichier existant** doit être signalée :
- soit par un commentaire en **début de fichier** (si modification importante),
- soit par un commentaire **en fin de ligne** ou à proximité immédiate du changement (si modification ponctuelle).
### Format imposé des commentaires
#### Création de fichier
```text
// Created by: <agent_name>
// Date: YYYY-MM-DD
// Purpose: <short description>
```
#### Modification de fichier
```text
// Modified by: <agent_name> — YYYY-MM-DD — <reason>
```
Le format exact peut être adapté au langage (Rust, Python, TypeScript…), mais **les informations doivent toujours être présentes**.
### Agents concernés
Cette règle sapplique à :
- Claude (agent principal),
- tous les **sous-agents**,
- **Codex** ou tout autre générateur de code,
- toute automatisation produisant ou modifiant des fichiers.
### Objectif
- garantir une **traçabilité claire** des décisions et des changements,
- faciliter les revues (sécurité, performance, architecture),
- permettre de comprendre rapidement **qui a fait quoi et pourquoi**.
Cette consigne est **obligatoire et non optionnelle** pour le projet Mesh.
---
## Traçabilité des modifications de code (obligatoire)
Toute **création ou modification de code** réalisée pour lagent Mesh doit indiquer **explicitement lagent auteur**.
### Règles
- **Création de fichier** : commentaire en début de fichier.
- **Modification importante** : commentaire en début de fichier.
- **Modification ponctuelle** : commentaire en fin de ligne ou à proximité immédiate.
### Formats imposés (à adapter au langage)
**Création**
```text
// Created by: <agent_name>
// Date: YYYY-MM-DD
// Purpose: <short description>
```
**Modification**
```text
// Modified by: <agent_name> — YYYY-MM-DD — <reason>
```
### Agents concernés
- Claude (principal)
- Sous-agents
- Codex / générateurs de code
### Objectif
Garantir une **traçabilité claire** (qui / quand / pourquoi) pour les revues sécurité, performance et maintenance.
Cette règle est **non optionnelle** pour lagent Mesh.
---
## Traçabilité des modifications de code (obligatoire — Agent)
Cette règle sapplique **spécifiquement au dossier **`` et est prioritaire pour Codex.
### Exigences
- Chaque agent (Claude, sous-agent, Codex) doit **signer ses créations et modifications**.
- Aucune PR ou génération de code nest valide sans cette signature.
### Formats
**Création de fichier**
```text
// Created by: <agent_name>
// Date: YYYY-MM-DD
// Purpose: <short description>
```
**Modification**
```text
// Modified by: <agent_name> — YYYY-MM-DD — <reason>
```
### Placement
- Début de fichier : création ou refactor majeur.
- Fin de ligne / bloc : correction ponctuelle.
### Objectif
- Auditabilité
- Revue facilitée
- Historique lisible sans dépendre des commits
Cette consigne est **obligatoire** pour toute génération de code dans `agent/`.

590
docs/bundle_2_3_4.md Normal file
View File

@@ -0,0 +1,590 @@
# Bundle Mesh — (2) infra, (3) prompts Claude, (4) server skeleton
Ce document contient **les fichiers** demandés, prêts à être copiés dans le dépôt. Chaque section indique le chemin du fichier.
---
## 2) `infra/docker-compose.yml` (final, avec reverse-proxy TLS)
```yaml
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- mesh-server
mesh-server:
build: ../server
restart: unless-stopped
environment:
- MESH_JWT_SECRET=${MESH_JWT_SECRET}
- MESH_PUBLIC_URL=${MESH_PUBLIC_URL}
- GOTIFY_URL=${GOTIFY_URL}
- GOTIFY_TOKEN=${GOTIFY_TOKEN}
- STUN_URL=${STUN_URL}
- TURN_URL=${TURN_URL}
- TURN_REALM=${TURN_REALM}
expose:
- "8000"
gotify:
image: gotify/server:latest
restart: unless-stopped
environment:
- GOTIFY_DEFAULTUSER_NAME=${GOTIFY_DEFAULTUSER_NAME:-admin}
- GOTIFY_DEFAULTUSER_PASS=${GOTIFY_DEFAULTUSER_PASS:-adminadmin}
ports:
- "8080:80" # optionnel: exposer en LAN uniquement
volumes:
- gotify_data:/app/data
coturn:
image: coturn/coturn:latest
restart: unless-stopped
network_mode: "host"
command: >
-n
--log-file=stdout
--external-ip=${TURN_EXTERNAL_IP}
--realm=${TURN_REALM}
--user=${TURN_USER}:${TURN_PASS}
--listening-port=3478
--min-port=49160 --max-port=49200
--fingerprint
--lt-cred-mech
--no-multicast-peers
--no-cli
volumes:
gotify_data:
caddy_data:
caddy_config:
```
### `infra/caddy/Caddyfile`
```caddyfile
{
email {$CADDY_EMAIL}
}
{$MESH_DOMAIN} {
encode gzip
@ws {
path /ws
}
reverse_proxy @ws mesh-server:8000 {
header_up Host {host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote}
}
reverse_proxy mesh-server:8000 {
header_up Host {host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote}
}
}
```
### `infra/.env.example`
```env
# Public
MESH_DOMAIN=mesh.example.com
MESH_PUBLIC_URL=https://mesh.example.com
CADDY_EMAIL=admin@example.com
# Server
MESH_JWT_SECRET=change_me_super_secret
GOTIFY_URL=http://gotify:80
GOTIFY_TOKEN=CHANGE_ME_GOTIFY_APP_TOKEN
# ICE
STUN_URL=stun:stun.l.google.com:19302
TURN_URL=turn:turn.example.com:3478
TURN_REALM=mesh
# TURN (coturn)
TURN_EXTERNAL_IP=203.0.113.10
TURN_USER=mesh
TURN_PASS=change_me_turn_password
```
### Notes réseau
- Ouvrir : **443 TCP** (Mesh via Caddy), **3478 UDP/TCP** (TURN), **49160-49200 UDP** (media relay TURN).
- Gotify peut rester LAN-only.
---
## 3) Prompts Claude Code découpés
### `docs/claude-prompt-server.md`
```markdown
# Claude Code Prompt — Mesh Server (FastAPI control plane)
Génère le squelette du dossier `server/` pour Mesh.
Contraintes
- Python 3.12+
- FastAPI
- WebSocket /ws
- JWT auth
- SQLite (MVP)
- Server = control plane (aucun média ni fichier transitant)
Fonctions
- POST /auth/login (dev) -> JWT
- POST /rooms -> create
- POST /rooms/{room_id}/join
- POST /caps -> capability token TTL 120s
- GET /health
WebSocket /ws
- Auth JWT au handshake
- presence
- relay des messages: rtc.offer / rtc.answer / rtc.ice
- relay contrôle: call.request, screen.share.request, share.file.request, share.folder.request,
terminal.share.request, terminal.control.take, terminal.control.release
- Validation: refuser relay si capability manquante/expirée pour laction
Notifications
- Adapter Gotify (GOTIFY_URL + GOTIFY_TOKEN)
- Router: notify sur chat.message.created, call.missed, share.completed, terminal.share.started
Qualité
- Typage, structure modules, tests stubs
- .env.example + instructions README serveur
Deliverable
- Tous les fichiers nécessaires pour `uvicorn app.main:app --reload`.
```
### `docs/claude-prompt-agent.md`
```markdown
# Claude Code Prompt — Mesh Agent (Python, multi-OS)
Génère le squelette du dossier `agent/` pour Mesh.
Contraintes
- Python 3.12+
- asyncio
- httpx + websockets
- config fichier (yaml/toml) + env
Fonctions MVP
- Connexion au Mesh Server (REST + WS)
- Enregistrement device_id
- Réception events
- Notifications Gotify directes (gotify_url + token)
- Terminal share preview:
- spawn PTY local (bash)
- capture output
- abstraction transport (DataChannel prévu), MVP autorisé sur WS mais interface doit permettre swap
Bonus V1
- File send via WebRTC DataChannel (interfaces + stubs acceptés si lib WebRTC non choisie)
- Tray + autostart (stubs par OS)
Qualité
- Modules: core/config, mesh client, notifications, terminal, transfer
- Tests stubs
- README agent
```
### `docs/claude-prompt-client.md`
```markdown
# Claude Code Prompt — Mesh Client (Web)
Génère le squelette du dossier `client/`.
Stack
- Vite + React + TypeScript
- WebSocket client
- WebRTC placeholders
- xterm.js terminal viewer
Écrans MVP
- Login
- Rooms: create/join
- Room view: chat minimal + boutons (call/screen/file/terminal)
Réseau
- REST login
- WS connect
- Signaling handlers rtc.offer/answer/ice
Terminal
- composant xterm.js capable dafficher TERM_OUT
Qualité
- code propre, modules lib/api.ts, lib/ws.ts, lib/rtc.ts
- README client
```
---
## 4) Server skeleton (FastAPI) — fichiers prêts à coller
### `server/pyproject.toml`
```toml
[project]
name = "mesh-server"
version = "0.1.0"
dependencies = [
"fastapi>=0.110",
"uvicorn[standard]>=0.27",
"python-dotenv>=1.0",
"pyjwt>=2.8",
"pydantic>=2.6",
]
requires-python = ">=3.12"
[tool.uvicorn]
factory = false
```
### `server/app/main.py`
```python
from __future__ import annotations
import os
import time
import json
import uuid
from typing import Any, Dict, Optional
import jwt
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
JWT_SECRET = os.getenv("MESH_JWT_SECRET", "dev_secret_change_me")
JWT_ALG = "HS256"
CAP_TTL_SECONDS = int(os.getenv("MESH_CAP_TTL", "120"))
app = FastAPI(title="Mesh Server", version="0.1.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"] ,
allow_headers=["*"],
)
# In-memory MVP stores (replace by DB later)
ROOMS: Dict[str, Dict[str, Any]] = {} # room_id -> {name, members:set(peer_id/user_id)}
PEERS: Dict[str, Dict[str, Any]] = {} # peer_id -> {user_id, ws}
# -------------------- Models --------------------
class LoginReq(BaseModel):
username: str
password: str
class LoginResp(BaseModel):
token: str
class CreateRoomReq(BaseModel):
name: str
class CreateRoomResp(BaseModel):
room_id: str
class JoinRoomReq(BaseModel):
room_id: str
class CapsReq(BaseModel):
room_id: str
peer_id: str
caps: list[str]
target_peer_id: Optional[str] = None
max_size: Optional[int] = None
class CapsResp(BaseModel):
cap_token: str
exp: int
# -------------------- Helpers --------------------
def now_ts() -> int:
return int(time.time())
def issue_jwt(payload: dict, ttl_seconds: int) -> str:
data = dict(payload)
data["exp"] = now_ts() + ttl_seconds
return jwt.encode(data, JWT_SECRET, algorithm=JWT_ALG)
def verify_jwt(token: str) -> dict:
try:
return jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
def ws_send(ws: WebSocket, msg: dict) -> None:
# FastAPI WS requires await; this helper exists for symmetry; see async use.
raise NotImplementedError
def make_event(event_type: str, _from: str, to: str, payload: dict) -> dict:
return {
"type": event_type,
"id": str(uuid.uuid4()),
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"from": _from,
"to": to,
"payload": payload,
}
# -------------------- REST --------------------
@app.get("/health")
def health() -> dict:
return {"ok": True, "ts": now_ts()}
@app.post("/auth/login", response_model=LoginResp)
def login(req: LoginReq) -> LoginResp:
# Dev-only auth
if not req.username or not req.password:
raise HTTPException(status_code=400, detail="Missing credentials")
token = issue_jwt({"sub": req.username, "user_id": req.username}, ttl_seconds=3600)
return LoginResp(token=token)
@app.post("/rooms", response_model=CreateRoomResp)
def create_room(req: CreateRoomReq) -> CreateRoomResp:
room_id = str(uuid.uuid4())
ROOMS[room_id] = {"name": req.name, "members": set()}
return CreateRoomResp(room_id=room_id)
@app.post("/rooms/{room_id}/join")
def join_room(room_id: str) -> dict:
if room_id not in ROOMS:
raise HTTPException(status_code=404, detail="Room not found")
return {"ok": True}
@app.post("/caps", response_model=CapsResp)
def caps(req: CapsReq) -> CapsResp:
if req.room_id not in ROOMS:
raise HTTPException(status_code=404, detail="Room not found")
exp = now_ts() + CAP_TTL_SECONDS
cap_token = jwt.encode(
{
"sub": req.peer_id,
"room_id": req.room_id,
"caps": req.caps,
"target_peer_id": req.target_peer_id,
"max_size": req.max_size,
"exp": exp,
},
JWT_SECRET,
algorithm=JWT_ALG,
)
return CapsResp(cap_token=cap_token, exp=exp)
# -------------------- WebSocket --------------------
async def ws_auth(ws: WebSocket) -> dict:
# token can be provided as query param: /ws?token=...
token = ws.query_params.get("token")
if not token:
await ws.close(code=4401)
raise HTTPException(status_code=401, detail="Missing token")
data = verify_jwt(token)
return data
def cap_allows(cap_token: str | None, required_cap: str, room_id: str, from_peer: str, target_peer: str | None) -> bool:
if not cap_token:
return False
try:
data = jwt.decode(cap_token, JWT_SECRET, algorithms=[JWT_ALG])
except Exception:
return False
if data.get("room_id") != room_id:
return False
if data.get("sub") != from_peer:
return False
caps = set(data.get("caps") or [])
if required_cap not in caps:
return False
tp = data.get("target_peer_id")
if target_peer and tp and tp != target_peer:
return False
return True
CAP_MAP = {
# signaling / media control
"call.request": "call",
"screen.share.request": "screen",
# shares
"share.file.request": "share:file",
"share.folder.request": "share:folder",
# terminal
"terminal.share.request": "terminal:view",
"terminal.control.take": "terminal:control",
"terminal.control.release": "terminal:control",
# rtc relay is gated by the feature that initiated it; for MVP we accept rtc.* if a cap_token is present
"rtc.offer": "rtc",
"rtc.answer": "rtc",
"rtc.ice": "rtc",
}
@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
auth = await ws_auth(ws)
peer_id = str(uuid.uuid4())
user_id = auth.get("user_id")
PEERS[peer_id] = {"user_id": user_id, "ws": ws}
# welcome
await ws.send_text(json.dumps(make_event("system.welcome", "server", peer_id, {"peer_id": peer_id, "user_id": user_id})))
try:
while True:
raw = await ws.receive_text()
msg = json.loads(raw)
mtype = msg.get("type")
payload = msg.get("payload") or {}
room_id = payload.get("room_id")
target = payload.get("target") or payload.get("target_peer_id")
cap_token = payload.get("cap_token")
# room join bookkeeping (MVP)
if mtype == "room.join":
if not room_id or room_id not in ROOMS:
await ws.send_text(json.dumps(make_event("error", "server", peer_id, {"code": "ROOM_NOT_FOUND"})))
continue
ROOMS[room_id]["members"].add(peer_id)
# broadcast joined
for member in list(ROOMS[room_id]["members"]):
mws = PEERS.get(member, {}).get("ws")
if mws:
await mws.send_text(json.dumps(make_event("room.joined", "server", room_id, {"peer_id": peer_id, "room_id": room_id})))
continue
required = CAP_MAP.get(mtype)
if required:
if not room_id:
await ws.send_text(json.dumps(make_event("error", "server", peer_id, {"code": "MISSING_ROOM_ID"})))
continue
# Special-case rtc.*: require any valid cap_token with "rtc" cap OR reuse the feature cap.
if mtype.startswith("rtc."):
ok = cap_allows(cap_token, "rtc", room_id, peer_id, target)
if not ok:
await ws.send_text(json.dumps(make_event("error", "server", peer_id, {"code": "CAP_REQUIRED", "detail": "rtc"})))
continue
else:
ok = cap_allows(cap_token, required, room_id, peer_id, target)
if not ok:
await ws.send_text(json.dumps(make_event("error", "server", peer_id, {"code": "CAP_REQUIRED", "detail": required})))
continue
# Relay: to specific target peer (typical for rtc.*)
if target and target in PEERS:
t_ws = PEERS[target]["ws"]
await t_ws.send_text(json.dumps(make_event(mtype, peer_id, target, payload)))
continue
# Relay: to room
if room_id and room_id in ROOMS:
for member in list(ROOMS[room_id]["members"]):
mws = PEERS.get(member, {}).get("ws")
if mws:
await mws.send_text(json.dumps(make_event(mtype, peer_id, room_id, payload)))
continue
# fallback
await ws.send_text(json.dumps(make_event("error", "server", peer_id, {"code": "UNROUTABLE"})))
except WebSocketDisconnect:
pass
finally:
# cleanup
PEERS.pop(peer_id, None)
for r in ROOMS.values():
r["members"].discard(peer_id)
```
### `server/.env.example`
```env
MESH_JWT_SECRET=change_me
MESH_PUBLIC_URL=http://localhost:8000
GOTIFY_URL=http://localhost:8080
GOTIFY_TOKEN=
STUN_URL=stun:stun.l.google.com:19302
TURN_URL=turn:turn.example.com:3478
TURN_REALM=mesh
MESH_CAP_TTL=120
```
### `server/README.md`
```markdown
# Mesh Server (MVP)
## Run locally
```bash
cd server
python -m venv .venv && . .venv/bin/activate
pip install -U pip
pip install -e .
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```
## Endpoints
- GET /health
- POST /auth/login
- POST /rooms
- POST /caps
- WS /ws?token=...
## Notes
- Ce MVP garde les rooms/peers en mémoire.
- Remplacer par DB + persistance en V1.
```
```
---
## À faire ensuite (aligné P2P)
- Émettre un vrai `rtc` cap_token par feature (call/screen/share/terminal)
- Ajouter stockage DB
- Ajouter router Gotify + préférences
- Client/Agent: utiliser DataChannel réel

183
docs/claude(1).md Normal file
View File

@@ -0,0 +1,183 @@
# 📄 claude.md — Mesh (spécification générale, Rust pragmatique)
## 1. Description
**Mesh** est une application de communication pour petites équipes (2 à 4 personnes), **self-hosted**, optimisée pour :
- **faible charge serveur** (control plane uniquement),
- **flux directs P2P** (média + data),
- sécurité via autorisations centralisées,
- notifications via **Gotify**.
Fonctionnalités :
- chat,
- audio / vidéo,
- partage décran,
- partage fichiers & dossiers,
- partage de terminal (session SSH “preview” + contrôle),
- notifications (serveur + client/agent → Gotify).
---
## 2. Architecture globale
Séparation stricte :
- **Control plane** : Mesh Server (Python)
- **Media/Data plane** : P2P
```
┌──────────────┐
│ Gotify │
└──────▲───────┘
events/notify (server+agent)
Client Web ── P2P (WebRTC RTP) ── Client Web
│ \ / │
│ \ / │
│ \— P2P (QUIC data) ——--/ │
│ (agent↔agent) │
└────────── WS (signaling) ─────────┘
Mesh Server
```
- Audio/Vidéo/Écran : **WebRTC** (navigateurs)
- Fichiers/Dossiers/Terminal : **P2P data**
- recommandé V1 : **QUIC (TLS 1.3)** via Agent Rust (agent↔agent, agent↔client si support)
- option V2 : WebRTC DataChannel (si besoin dunifier)
- fallback exceptionnel : HTTP temporaire via serveur
---
## 3. Stack technique (pragmatique)
### Serveur (Python)
- Python 3.12+
- FastAPI
- WebSocket `/ws` (signalisation + events)
- JWT (auth)
- Capabilities tokens TTL court
- SQLite (MVP), PostgreSQL (V1)
- Adapter notifications : Gotify
- Déploiement : Docker
### Client (Web)
- Vite + React + TypeScript (ou équivalent)
- WebRTC (call/screen)
- UI terminal : xterm.js
### Agent (Rust)
- tokio + reqwest + tokio-tungstenite
- QUIC P2P : quinn
- Terminal : portable-pty / ConPTY
- FS watcher : notify
- Logs : tracing
---
## 4. Rôles & responsabilités
### Mesh Server (control plane)
- Auth / sessions
- Rooms (24) + ACL
- Émission de capability tokens (TTL court)
- Signalisation WebRTC (offer/answer/ice)
- Arbitrage contrôle terminal (“take control”)
- Event bus + notifications Gotify
### Clients (web)
- UI + WebRTC call/screen
- Chat
- Signaling WS
- Terminal viewer
### Agents (Rust)
- Terminal share (PTY + stream)
- Partage fichiers/dossiers P2P
- Sync dossiers (optionnel)
- Notifications Gotify
---
## 5. Structure du dépôt
```
mesh/
server/
client/
agent/
infra/
docs/
```
Docs attendus :
- `protocol-events.md`
- `signaling.md`
- `security.md`
- `deployment.md`
---
## 6. Règles de permissions (capabilities)
Le serveur émet des tokens de capacités (TTL 60180 s), scoppés :
- `room_id`
- `peer_id` / `device_id`
- `caps[]` ex: `call`, `screen`, `share:file`, `share:folder`, `terminal:view`, `terminal:control`
- option : `target_peer_id`, `max_size`, `max_rate`
Règles :
- aucune session P2P ne démarre sans token valide.
- terminal : `terminal:view` pour recevoir, `terminal:control` pour envoyer input.
---
## 7. Étapes du projet (roadmap)
### Phase 1 — Fondation (MVP control plane)
- [ ] Server skeleton FastAPI (auth/rooms/ws)
- [ ] Protocole events & signalling (WS)
- [ ] Capabilities tokens + validation
- [ ] Adapter Gotify (server)
### Phase 2 — Communication (Web)
- [ ] Chat (MVP)
- [ ] WebRTC audio/vidéo P2P
- [ ] Screen share P2P
### Phase 3 — Agent Rust (data plane)
- [ ] Agent Rust skeleton (WS + config + Gotify)
- [ ] Terminal share preview (PTY → P2P QUIC)
- [ ] Take control (arbitrage via serveur)
### Phase 4 — Partage data
- [ ] File transfer P2P (QUIC)
- [ ] Folder zip P2P
- [ ] Fallback HTTP (exception)
### Phase 5 — Sync & polish
- [ ] Folder sync (manifest/diff + conflits)
- [ ] Packaging multi-OS (MSI/deb/dmg)
- [ ] Monitoring minimal + diagnostics
---
## 8. TODO global
- [ ] RBAC simple (owner/member/guest)
- [ ] Quotas (taille, débit)
- [ ] Préférences notifications (par room / par event)
- [ ] Journaux & rotation
- [ ] Tests dintégration (WS + tokens)
---
## 9. Améliorations futures
- Mobile native
- Federation Mesh↔Mesh
- Plugin system
- E2E applicatif optionnel
- TCP-like tunneling au-dessus de QUIC (SSH tunnel)
---
## 10. Changelog
```
0.1.0 Architecture validée (Python server + Rust agent)
0.2.0 MVP WebRTC (call/screen)
0.3.0 Agent terminal preview (QUIC)
0.4.0 File/folder transfer (QUIC)
0.5.0 Folder sync (beta)
```

93
docs/deployment.md Normal file
View File

@@ -0,0 +1,93 @@
# 📄 deployment.md — Mesh Deployment (self-hosted)
## 1. Composants
- mesh-server (FastAPI + WS)
- coturn (TURN) — fallback NAT strict
- gotify (notifications)
- (optionnel) reverse proxy (Caddy/Nginx) + TLS
## 2. Variables denvironnement (exemple)
- MESH_PUBLIC_URL=https://mesh.example.com
- MESH_JWT_SECRET=...
- GOTIFY_URL=https://gotify.example.com
- GOTIFY_TOKEN=...
- TURN_HOST=turn.example.com
- TURN_PORT=3478
- TURN_USER=mesh
- TURN_PASS=...
## 3. docker-compose (exemple)
Placez ceci dans `infra/docker-compose.yml`.
services:
mesh-server:
build: ../server
environment:
- MESH_JWT_SECRET=${MESH_JWT_SECRET}
- GOTIFY_URL=${GOTIFY_URL}
- GOTIFY_TOKEN=${GOTIFY_TOKEN}
- TURN_URL=${TURN_URL}
- STUN_URL=${STUN_URL}
ports:
- "8000:8000"
restart: unless-stopped
coturn:
image: coturn/coturn:latest
command: >
-n
--log-file=stdout
--external-ip=${TURN_EXTERNAL_IP}
--realm=${TURN_REALM}
--user=${TURN_USER}:${TURN_PASS}
--listening-port=3478
--min-port=49160 --max-port=49200
--fingerprint
--lt-cred-mech
--no-multicast-peers
--no-cli
network_mode: "host"
restart: unless-stopped
gotify:
image: gotify/server:latest
environment:
- GOTIFY_DEFAULTUSER_NAME=admin
- GOTIFY_DEFAULTUSER_PASS=adminadmin
ports:
- "8080:80"
volumes:
- gotify_data:/app/data
restart: unless-stopped
volumes:
gotify_data:
## 4. Notes TURN
- TURN peut devenir “lourd” si beaucoup de pairs passent en relay.
- Prévoir monitoring trafic + quotas.
- Credentials temporaires (V1+) recommandé.
## 5. Reverse proxy + TLS (recommandé)
- Terminer TLS au proxy (Caddy/Nginx).
- Forward:
- /api → mesh-server
- /ws → mesh-server (upgrade websocket)
- TURN: idéalement domaine dédié (turn.example.com) + ports ouverts.
## 6. Ports réseau
- Mesh Server: 443 (TLS) / 80 (redirect)
- TURN: 3478 UDP/TCP + range UDP (ex 49160-49200)
- Gotify: 443/80 (si exposé), sinon LAN only
## 7. Checks de santé
- /health sur mesh-server
- gotify UI accessible
- test ICE: vérifier host/srflx/relay
## 8. Exploitation
- Sauvegarder:
- DB mesh (si sqlite/postgres)
- gotify_data
- Rotation logs

View File

@@ -0,0 +1,172 @@
# Templates de headers — Traçabilité Mesh
Ce document définit les **templates de commentaires obligatoires** à utiliser lors de toute **création ou modification de code** dans le projet Mesh.
Objectifs :
- traçabilité claire (qui / quand / pourquoi),
- auditabilité hors Git,
- revues facilitées (sécurité, performance, architecture).
---
## Règles générales
- **Création de fichier** : ajouter un bloc de header en **début de fichier**.
- **Modification majeure** : ajouter une ligne `Modified by` en début de fichier (sous le header existant).
- **Modification ponctuelle** : ajouter un commentaire **en fin de ligne** ou juste au-dessus du changement.
### Champs obligatoires
- `Created by` / `Modified by` : nom de lagent (ex. `Claude`, `Codex`, `SubAgent:SecurityReview`).
- `Date` : format `YYYY-MM-DD`.
- `Purpose` / `Reason` : une ligne concise.
### Champs optionnels
- `Refs` : issue, ticket, PR, ou référence interne.
- `Scope` : module ou sous-système concerné.
---
## Noms dagents recommandés
- `Claude`
- `Codex`
- `SubAgent:SecurityReview`
- `SubAgent:PerfReview`
- `SubAgent:CodeReview`
- `Automation:<nom>`
---
## Templates par langage
### Rust (`.rs`)
**Création (début de fichier)**
```rust
// Created by: <agent_name>
// Date: YYYY-MM-DD
// Purpose: <short description>
// Refs: <optional>
```
**Modification majeure (début de fichier)**
```rust
// Modified by: <agent_name> — YYYY-MM-DD — <reason>
```
**Modification ponctuelle (fin de ligne)**
```rust
let timeout_ms = 1200; // Modified by: Codex — 2025-12-29 — tune retry timeout
```
---
### Python (`.py`)
**Création**
```python
# Created by: <agent_name>
# Date: YYYY-MM-DD
# Purpose: <short description>
# Refs: <optional>
```
**Modification ponctuelle**
```python
MAX_CHUNK = 262144 # Modified by: Claude — 2025-12-29 — align with 256KB chunking
```
---
### TypeScript / JavaScript (`.ts`, `.tsx`, `.js`)
**Création**
```ts
// Created by: <agent_name>
// Date: YYYY-MM-DD
// Purpose: <short description>
// Refs: <optional>
```
**Modification ponctuelle**
```ts
const WS_PATH = "/ws"; // Modified by: Codex — 2025-12-29 — keep routing consistent
```
---
### HTML
```html
<!--
Created by: <agent_name>
Date: YYYY-MM-DD
Purpose: <short description>
Refs: <optional>
-->
```
---
### CSS
```css
/*
Created by: <agent_name>
Date: YYYY-MM-DD
Purpose: <short description>
Refs: <optional>
*/
```
---
### YAML (`.yml`, `.yaml`)
```yaml
# Created by: <agent_name>
# Date: YYYY-MM-DD
# Purpose: <short description>
# Refs: <optional>
```
---
### TOML (`.toml`)
```toml
# Created by: <agent_name>
# Date: YYYY-MM-DD
# Purpose: <short description>
# Refs: <optional>
```
---
### Markdown (`.md`)
```markdown
<!--
Created by: <agent_name>
Date: YYYY-MM-DD
Purpose: <short description>
Refs: <optional>
-->
```
---
## Rappel obligatoire
Ces templates sont **normatifs** pour le projet Mesh. Toute génération de code (Claude, sous-agent, Codex, automatisation) doit les appliquer sans exception.

View File

@@ -0,0 +1,93 @@
# 📄 signaling.md — Mesh WebRTC Signaling & ICE Strategy (v2)
## 1. Objectif
Décrire la **signalisation WebRTC** (média) et la stratégie de connexions pour Mesh.
Mesh utilise :
- **WebRTC** pour audio/vidéo/partage écran entre navigateurs
- **QUIC P2P** pour transferts data (fichiers/dossiers/terminal) via Agent Rust
---
## 2. Rappels darchitecture
- **Control plane** : Mesh Server (WS)
- **Media plane** : WebRTC (clients web)
- **Data plane** : QUIC (agents Rust)
Le serveur ne transporte pas de média.
---
## 3. WebRTC (média) — ICE
### STUN (obligatoire)
Découverte IP publique.
### TURN (fallback)
Relais en cas déchec P2P direct.
Politique candidats :
1. host
2. srflx
3. relay
Recommandations :
- Ne jamais forcer TURN
- Logguer si relay utilisé
---
## 4. WebRTC — séquence standard
1) Client A demande action (call/screen) au serveur
2) serveur valide ACL + émet `cap_token`
3) échange SDP/ICE via WS (`rtc.offer/answer/ice`)
4) flux média direct A↔B
---
## 5. QUIC P2P (data) — stratégie
- QUIC = TLS 1.3 natif, multiplexing streams, perf élevée
- Démarrage : création de session via `p2p.session.request`
- Le serveur fournit `session_token` + endpoints
- Les pairs se connectent directement (UDP)
### NAT / connectivité
- QUIC peut nécessiter :
- UDP sortant autorisé
- parfois des contraintes NAT/CGNAT
Recommandations :
- privilégier LAN direct
- sur Internet : documenter ouverture/autorisation UDP
- fallback HTTP temporaire (exception)
---
## 6. Optimisations data
- chunking 64256 KB
- backpressure
- reprise par offset
- hash par chunk + hash final (blake3 recommandé)
---
## 7. Sécurité
- actions gated by capability token TTL court
- QUIC TLS 1.3
- terminal : preview-only par défaut, contrôle arbitré via serveur
---
## 8. Diagnostics
Exposer :
- WebRTC : host/srflx/relay
- QUIC : latence, débit, retries
- erreurs : sessions refusées, tokens expirés
---
## 9. Changelog
```
0.1.0 WebRTC signaling + ICE
0.2.0 QUIC data-plane sessions (agent Rust)
```

View File

@@ -0,0 +1,223 @@
# 📄 protocol-events.md — Mesh Event & Signaling Protocol (v2, Rust pragmatique)
## 1. Objectif
Ce document définit le protocole dévénements Mesh pour :
- signalisation WebRTC (média),
- orchestration des connexions P2P (data),
- autorisations (capabilities),
- notifications Gotify.
Séparation :
- **Control plane** : Mesh Server (REST + WebSocket)
- **Media plane** : WebRTC (clients web)
- **Data plane** : QUIC P2P (agents Rust)
---
## 2. Transports
- **WSS** : Client/Agent ↔ Mesh Server
- **WebRTC** : audio/vidéo/écran (client ↔ client)
- **QUIC (TLS 1.3)** : fichiers/dossiers/terminal (agent ↔ agent)
---
## 3. Format WS
```json
{
"type": "event.type",
"id": "uuid",
"timestamp": "ISO-8601",
"from": "peer_id|device_id|server",
"to": "peer_id|device_id|room_id|server",
"payload": {}
}
```
Payload recommandé : `room_id`, `target_peer_id`, `target_device_id`, `cap_token`, `session_id`.
---
## 4. Identités
- `user_id` : utilisateur
- `peer_id` : client web
- `device_id` : agent
- `room_id` : room (24)
---
## 5. Capabilities (JWT TTL court)
Contenu : `sub`, `room_id`, `caps[]`, `exp` (+ option `target_*`, `max_size`, `max_rate`).
Caps typiques :
- `call`, `screen`
- `share:file`, `share:folder`
- `terminal:view`, `terminal:control`
Règles :
- aucune session P2P ne démarre sans cap_token valide.
- terminal : input interdit sans `terminal:control`.
---
## 6. Système
### `system.hello`
Client/Agent → Server
```json
{ "peer_type": "client|agent", "version": "x.y.z" }
```
### `system.welcome`
Server → Client/Agent
```json
{ "peer_id": "...", "user_id": "..." }
```
---
## 7. Rooms / présence
### `room.join`
```json
{ "room_id": "..." }
```
### `room.joined`
```json
{ "peer_id": "...", "role": "member", "room_id": "..." }
```
### `presence.update`
```json
{ "peer_id": "...", "status": "online|busy|sharing" }
```
---
## 8. Chat
### `chat.message.send`
```json
{ "room_id": "...", "content": "hello" }
```
### `chat.message.created`
```json
{ "message_id": "...", "from": "...", "content": "hello" }
```
---
## 9. WebRTC signaling (média)
### `rtc.offer`
```json
{ "room_id": "...", "target_peer_id": "...", "sdp": "...", "cap_token": "..." }
```
### `rtc.answer`
```json
{ "room_id": "...", "target_peer_id": "...", "sdp": "...", "cap_token": "..." }
```
### `rtc.ice`
```json
{ "room_id": "...", "target_peer_id": "...", "candidate": {}, "cap_token": "..." }
```
---
## 10. QUIC P2P sessions (data plane)
Les flux data (file/folder/terminal) utilisent une session QUIC orchestrée par le serveur.
### 10.1 Création
#### `p2p.session.request`
Agent A → Server
```json
{
"room_id": "...",
"target_device_id": "...",
"kind": "file|folder|terminal",
"cap_token": "...",
"meta": { "name": "...", "size": 123 }
}
```
#### `p2p.session.created`
Server → A et Server → B
```json
{
"session_id": "uuid",
"kind": "file|folder|terminal",
"expires_in": 180,
"auth": {
"session_token": "...",
"fingerprint": "..."
},
"endpoints": {
"a": { "ip": "x.x.x.x", "port": 45432 },
"b": { "ip": "y.y.y.y", "port": 45433 }
}
}
```
### 10.2 Handshake applicatif
Premier message sur un stream QUIC :
`P2P_HELLO`
```json
{ "t": "P2P_HELLO", "session_id": "...", "session_token": "...", "from_device_id": "..." }
```
Réponse : `P2P_OK` ou `P2P_DENY`.
---
## 11. Data messages (sur QUIC)
### 11.1 Fichier
- `FILE_META` (nom, taille, hash)
- `FILE_CHUNK` (offset, bytes)
- `FILE_ACK` (last_offset)
- `FILE_DONE` (hash final)
### 11.2 Dossier
- `FOLDER_MODE` (zip|sync)
- (zip) `ZIP_META` / `ZIP_CHUNK` / `ZIP_DONE`
- (sync, V2) `MANIFEST` / `DIFF_CHUNK` / `CONFLICT`
### 11.3 Terminal
- `TERM_OUT` (UTF-8)
- `TERM_RESIZE` (cols/rows)
- `TERM_IN` (input) — seulement si contrôle accordé
---
## 12. Contrôle terminal
Arbitrage via WS :
- `terminal.control.take`
- `terminal.control.granted`
- `terminal.control.release`
---
## 13. Notifications (Gotify)
Événements notifiables :
- `chat.message.created`
- `call.missed`
- `share.completed`
- `terminal.share.started`
- `agent.offline`
---
## 14. Erreurs
`error`
```json
{ "code": "CAP_EXPIRED", "message": "Capability token expired" }
```
Codes suggérés : `CAP_REQUIRED`, `CAP_EXPIRED`, `ROOM_NOT_FOUND`, `P2P_SESSION_DENIED`, `UNROUTABLE`.
---
## 15. Changelog
```
0.1.0 Base events + WebRTC signaling
0.2.0 QUIC sessions for data plane (file/folder/terminal)
```

223
docs/protocol_events_v_2.md Normal file
View File

@@ -0,0 +1,223 @@
# 📄 protocol-events.md — Mesh Event & Signaling Protocol (v2, Rust pragmatique)
## 1. Objectif
Ce document définit le protocole dévénements Mesh pour :
- signalisation WebRTC (média),
- orchestration des connexions P2P (data),
- autorisations (capabilities),
- notifications Gotify.
Séparation :
- **Control plane** : Mesh Server (REST + WebSocket)
- **Media plane** : WebRTC (clients web)
- **Data plane** : QUIC P2P (agents Rust)
---
## 2. Transports
- **WSS** : Client/Agent ↔ Mesh Server
- **WebRTC** : audio/vidéo/écran (client ↔ client)
- **QUIC (TLS 1.3)** : fichiers/dossiers/terminal (agent ↔ agent)
---
## 3. Format WS
```json
{
"type": "event.type",
"id": "uuid",
"timestamp": "ISO-8601",
"from": "peer_id|device_id|server",
"to": "peer_id|device_id|room_id|server",
"payload": {}
}
```
Payload recommandé : `room_id`, `target_peer_id`, `target_device_id`, `cap_token`, `session_id`.
---
## 4. Identités
- `user_id` : utilisateur
- `peer_id` : client web
- `device_id` : agent
- `room_id` : room (24)
---
## 5. Capabilities (JWT TTL court)
Contenu : `sub`, `room_id`, `caps[]`, `exp` (+ option `target_*`, `max_size`, `max_rate`).
Caps typiques :
- `call`, `screen`
- `share:file`, `share:folder`
- `terminal:view`, `terminal:control`
Règles :
- aucune session P2P ne démarre sans cap_token valide.
- terminal : input interdit sans `terminal:control`.
---
## 6. Système
### `system.hello`
Client/Agent → Server
```json
{ "peer_type": "client|agent", "version": "x.y.z" }
```
### `system.welcome`
Server → Client/Agent
```json
{ "peer_id": "...", "user_id": "..." }
```
---
## 7. Rooms / présence
### `room.join`
```json
{ "room_id": "..." }
```
### `room.joined`
```json
{ "peer_id": "...", "role": "member", "room_id": "..." }
```
### `presence.update`
```json
{ "peer_id": "...", "status": "online|busy|sharing" }
```
---
## 8. Chat
### `chat.message.send`
```json
{ "room_id": "...", "content": "hello" }
```
### `chat.message.created`
```json
{ "message_id": "...", "from": "...", "content": "hello" }
```
---
## 9. WebRTC signaling (média)
### `rtc.offer`
```json
{ "room_id": "...", "target_peer_id": "...", "sdp": "...", "cap_token": "..." }
```
### `rtc.answer`
```json
{ "room_id": "...", "target_peer_id": "...", "sdp": "...", "cap_token": "..." }
```
### `rtc.ice`
```json
{ "room_id": "...", "target_peer_id": "...", "candidate": {}, "cap_token": "..." }
```
---
## 10. QUIC P2P sessions (data plane)
Les flux data (file/folder/terminal) utilisent une session QUIC orchestrée par le serveur.
### 10.1 Création
#### `p2p.session.request`
Agent A → Server
```json
{
"room_id": "...",
"target_device_id": "...",
"kind": "file|folder|terminal",
"cap_token": "...",
"meta": { "name": "...", "size": 123 }
}
```
#### `p2p.session.created`
Server → A et Server → B
```json
{
"session_id": "uuid",
"kind": "file|folder|terminal",
"expires_in": 180,
"auth": {
"session_token": "...",
"fingerprint": "..."
},
"endpoints": {
"a": { "ip": "x.x.x.x", "port": 45432 },
"b": { "ip": "y.y.y.y", "port": 45433 }
}
}
```
### 10.2 Handshake applicatif
Premier message sur un stream QUIC :
`P2P_HELLO`
```json
{ "t": "P2P_HELLO", "session_id": "...", "session_token": "...", "from_device_id": "..." }
```
Réponse : `P2P_OK` ou `P2P_DENY`.
---
## 11. Data messages (sur QUIC)
### 11.1 Fichier
- `FILE_META` (nom, taille, hash)
- `FILE_CHUNK` (offset, bytes)
- `FILE_ACK` (last_offset)
- `FILE_DONE` (hash final)
### 11.2 Dossier
- `FOLDER_MODE` (zip|sync)
- (zip) `ZIP_META` / `ZIP_CHUNK` / `ZIP_DONE`
- (sync, V2) `MANIFEST` / `DIFF_CHUNK` / `CONFLICT`
### 11.3 Terminal
- `TERM_OUT` (UTF-8)
- `TERM_RESIZE` (cols/rows)
- `TERM_IN` (input) — seulement si contrôle accordé
---
## 12. Contrôle terminal
Arbitrage via WS :
- `terminal.control.take`
- `terminal.control.granted`
- `terminal.control.release`
---
## 13. Notifications (Gotify)
Événements notifiables :
- `chat.message.created`
- `call.missed`
- `share.completed`
- `terminal.share.started`
- `agent.offline`
---
## 14. Erreurs
`error`
```json
{ "code": "CAP_EXPIRED", "message": "Capability token expired" }
```
Codes suggérés : `CAP_REQUIRED`, `CAP_EXPIRED`, `ROOM_NOT_FOUND`, `P2P_SESSION_DENIED`, `UNROUTABLE`.
---
## 15. Changelog
```
0.1.0 Base events + WebRTC signaling
0.2.0 QUIC sessions for data plane (file/folder/terminal)
```

64
docs/security.md Normal file
View File

@@ -0,0 +1,64 @@
# 📄 security.md — Mesh Security Model
## 1. Principes
- **Server = control plane** : auth, ACL, signaling WebRTC, événements, notifications.
- **Clients/Agents = data plane** : média + fichiers + terminal en P2P (WebRTC).
- Le serveur ne transporte pas de flux média ni de transferts lourds (sauf fallback explicite).
## 2. Identités
- user_id : utilisateur
- peer_id : instance client web
- device_id : agent desktop
- room_id : salon (24 personnes)
## 3. Authentification
- JWT (access token court recommandé, refresh optionnel).
- WS authentifié (JWT dans query param sécurisé ou header lors du handshake).
- Rotation et révocation (à prévoir V1/V2).
## 4. Autorisations : Capability Tokens
Le serveur émet des tokens à TTL court (ex 120s) contenant:
- room_id
- peer_id (ou device_id)
- caps : ["call", "screen", "share:file", "share:folder", "terminal:view", "terminal:control"]
- contraintes optionnelles : max_size, max_rate, target_peer_id
Règles:
- aucun offer WebRTC nest accepté/relayé sans capability valide associée à laction.
- un viewer en terminal na pas le droit denvoyer `TERM_IN` sans `terminal:control`.
## 5. WebRTC sécurité
- Chiffrement natif DTLS/SRTP (média) + DTLS (DataChannel).
- Vérification fingerprint SDP côté client (V1+).
- ICE: STUN + TURN (TURN = bande passante sensible; limiter via quotas).
## 6. Terminal share (SSH preview)
- Le SSH tourne **sur la machine du partageur** (agent), via PTY.
- Aucun secret SSH (clé, mot de passe) nest transmis aux viewers.
- Mode par défaut : **preview (read-only)**.
- “Take control” : un seul contrôleur à la fois, arbitrage via serveur (capability).
- Option V2: masquage best-effort de secrets (non garanti).
## 7. Notifications (Gotify)
- Le serveur et lagent peuvent notifier Gotify.
- Ne jamais inclure de secrets dans le texte des notifications.
- Niveau de détail configurable (ex: “Nouveau message” vs contenu).
## 8. Journalisation
- Server logs: auth failures, room joins, capability issuance, signaling events (sans contenu média).
- Agent logs: démarrage/arrêt de partage, erreurs sync, diagnostics ICE.
- Politique de rétention courte (ex 714 jours) + rotation.
## 9. Menaces & mitigations (résumé)
- Usurpation peer_id → JWT + WS auth + device_id registration.
- Replay doffers → capability TTL court + nonce/session_id.
- Accès non autorisé à une room → ACL server-side + tokens scoped room_id.
- Exfiltration via terminal → preview-only par défaut + contrôle explicite.
- TURN abuse → quotas, credentials temporaires, rate limiting.
## 10. Roadmap sécurité
- Refresh tokens + révocation
- RBAC (owner/member/guest)
- E2E applicatif optionnel (au-dessus de WebRTC)
- Attestation device (optionnel)

144
docs/signaling.md Normal file
View File

@@ -0,0 +1,144 @@
# 📄 signaling.md — Mesh WebRTC Signaling & ICE Strategy
## 1. Objectif
Ce document décrit la **signalisation WebRTC**, la stratégie **ICE / STUN / TURN**, et les règles doptimisation des flux pour Mesh.
Objectifs :
- Connexions **client ↔ client** directes par défaut
- Charge serveur minimale
- Fallback robuste (NAT strict)
- Sécurité et contrôle centralisé
---
## 2. Rappels darchitecture
- **Control plane** : Mesh Server (WebSocket)
- **Media/Data plane** : WebRTC P2P
- Le serveur **ne transporte aucun flux média ou fichier**
```
Client A ── P2P (RTP/DataChannel) ── Client B
│ ▲
└────── WS ─────┘
Mesh Server
```
---
## 3. WebRTC : rôles
- **Offerer** : initiateur du flux (call/share/file/terminal)
- **Answerer** : pair cible
- **Signaling server** : Mesh Server (WS JSON)
---
## 4. ICE configuration
### STUN (obligatoire)
Utilisé pour la découverte dIP publiques.
Exemples :
- `stun:stun.l.google.com:19302`
- `stun:stun.mesh.local:3478`
### TURN (fallback)
Utilisé uniquement si le P2P direct échoue.
- Recommandé : **coturn**
- Transport : UDP en priorité, TCP en fallback
```
turn:turn.mesh.local:3478?transport=udp
turn:turn.mesh.local:3478?transport=tcp
```
Authentification :
- Credentials temporaires (REST API TURN)
- TTL court
---
## 5. Politique ICE Mesh
Priorité des candidats :
1. host
2. srflx (STUN)
3. relay (TURN)
Règles :
- Ne jamais forcer TURN
- Logguer si relay utilisé (diagnostic)
---
## 6. Séquence de signalisation (exemple : partage écran)
1) Client A → Server : `screen.share.request`
2) Server : vérifie droits + émet capability token
3) Server → Client B : `screen.share.granted`
4) A ↔ Server ↔ B : échange SDP (offer/answer)
5) A ↔ Server ↔ B : ICE candidates
6) A ↔ B : flux P2P direct
---
## 7. DataChannel (fichiers, terminal)
### Paramètres recommandés
- ordered: true
- maxPacketLifeTime: null
- maxRetransmits: null
### Sous-canaux logiques
- `file-transfer`
- `folder-sync`
- `terminal-stream`
- `terminal-input`
---
## 8. Optimisations performance
- Chunking (64256 KB)
- Backpressure (bufferedAmount)
- Pause/reprise
- Hash par chunk
---
## 9. Fallback HTTP (exceptionnel)
Si WebRTC impossible :
- Upload temporaire serveur
- URL signée + expiration
- Nettoyage automatique
---
## 10. Sécurité
- DTLS / SRTP natif WebRTC
- Capability token requis avant offer
- Vérification fingerprint SDP
- TTL court TURN
---
## 11. Diagnostics
Expose localement :
- mode ICE utilisé (host/srflx/relay)
- latence RTT
- débit moyen
- pertes
---
## 12. TODO
- [ ] ICE restarts
- [ ] QUIC (WebTransport)
- [ ] TCP-over-DataChannel
- [ ] Priorisation flux (media > data)
---
## 13. Changelog
```
0.1.0 Base signaling + ICE
0.2.0 DataChannel optimisation
```

161
docs/signaling_v_2.md Normal file
View File

@@ -0,0 +1,161 @@
# 📄 signaling.md — Mesh Signaling & Connectivity (v2)
## 1. Objectif
Documenter :
- la **signalisation WebRTC** (média) via Mesh Server,
- la stratégie **ICE/STUN/TURN**,
- la stratégie **data-plane QUIC** (agents Rust) et ses contraintes réseau,
- les optimisations et diagnostics.
---
## 2. Architecture
- **Control plane** : Mesh Server (WS)
- **Media plane** : WebRTC (clients web)
- **Data plane** : QUIC P2P (agents Rust)
Le serveur ne transporte aucun flux média ou transfert lourd.
---
## 3. WebRTC (média) — ICE
### STUN (obligatoire)
Découverte IP publique.
### TURN (fallback)
Relais si P2P direct échoue.
Politique candidats :
1. host
2. srflx
3. relay
Recommandations :
- Ne pas forcer TURN
- Logguer quand relay est utilisé
- Credentials temporaires TURN (V1+)
---
## 4. WebRTC — séquence
1. Action (call/screen) demandée au serveur
2. Serveur valide ACL + émet `cap_token`
3. Échange SDP/ICE via WS (`rtc.offer/answer/ice`)
4. Flux média direct A↔B
---
## 5. QUIC P2P (data plane) — stratégie
- QUIC = TLS 1.3 natif + multiplexing streams
- Sessions orchestrées via `p2p.session.request/created`
- Connexion directe agent↔agent sur UDP
### Contraintes réseau
- UDP sortant requis.
- Certains NAT/CGNAT peuvent compliquer la traversée.
Stratégie :
- privilégier LAN
- documenter louverture/autorisation UDP côté WAN
- fallback exceptionnel : HTTP temporaire via serveur
---
## 6. Optimisations transferts
- chunks 64256 KB
- backpressure
- reprise par offset
- hashing blake3 (chunk + final)
---
## 7. Sécurité
- Actions gated by capability token TTL court.
- QUIC : TLS 1.3.
- Terminal : preview-only par défaut, contrôle arbitré par serveur.
---
## 8. Diagnostics
Exposer :
- WebRTC : host/srflx/relay
- QUIC : latence, débit, pertes, retries
- erreurs : sessions refusées, tokens expirés
---
## 9. Changelog
```
0.1.0 WebRTC signaling + ICE
0.2.0 QUIC data-plane sessions (agent Rust)
```
---
## Consignes de gestion du contexte et des fichiers CLAUDE.md (obligatoire)
### Utilisation de plusieurs fichiers `CLAUDE.md`
- Utilisez **plus dun fichier **`` dans le projet.
- Conservez un ``** général à la racine** du dépôt Mesh (vision globale, règles communes).
- Ajoutez des ``** spécifiques dans les sous-dossiers** (`server/`, `agent/`, `client/`, `infra/`, etc.) afin de fournir à Claude un **contexte ciblé et local**.
- Chaque fichier `CLAUDE.md` de sous-dossier doit compléter le fichier racine, sans le contredire.
### Gestion de la fenêtre de contexte
Au fil dune session longue, la fenêtre de contexte de Claude peut se saturer, entraînant une perte de précision ou loubli dinstructions importantes.
Pour éviter cela, appliquer systématiquement les pratiques suivantes :
#### 1. Réinitialisation stratégique du contexte
- Utiliser régulièrement la commande :
```
/clear
```
- En particulier :
- entre deux tâches distinctes,
- entre conception et implémentation,
- entre implémentation et revue.
Cette commande efface lhistorique courant et permet de repartir sur une **ardoise vierge**, en sappuyant uniquement sur les fichiers `CLAUDE.md` pertinents.
#### 2. Utilisation de sous-agents
Pour les flux de travail complexes ou multi-étapes, déléguer explicitement à des **sous-agents**.
Exemple :
> « Tu viens décrire le code du partage de fichiers. Maintenant, utilise un sous-agent pour effectuer une revue de sécurité de ce code. »
Avantages :
- séparation claire des responsabilités,
- contexte dédié pour chaque analyse,
- réduction du bruit dans la conversation principale.
### Principe fondamental
Le **contexte de référence du projet Mesh doit toujours être porté par les fichiers **``, et non par lhistorique de la conversation. Lhistorique nest quun support temporaire.
Ces consignes sont **obligatoires** pour toute utilisation de Claude dans le cadre du projet Mesh.

View File

@@ -0,0 +1,265 @@
# Tooling Mesh — Pre-commit (headers) + Snippets VS Code
Ce document fournit :
1) un **contrôle automatique** (pre-commit) qui vérifie la présence des headers de traçabilité Mesh
2) des **snippets VS Code** pour insérer rapidement les headers
Référence normative : `docs/headers-template.md`.
---
## 1) Pre-commit : vérification des headers
### 1.1 Fichiers à ajouter
#### `.pre-commit-config.yaml` (à la racine)
```yaml
repos:
- repo: local
hooks:
- id: mesh-traceability-headers
name: Mesh traceability headers check
entry: python3 scripts/check_trace_headers.py
language: system
types_or: [python, javascript, typescript, rust, yaml, toml, markdown, css, html]
pass_filenames: true
```
#### `scripts/check_trace_headers.py`
```python
#!/usr/bin/env python3
# Created by: Claude — 2025-12-29
# Purpose: Validate Mesh traceability headers in repo files
from __future__ import annotations
import re
import sys
from pathlib import Path
# File extensions we validate
VALID_EXTS = {
".rs",
".py",
".ts",
".tsx",
".js",
".jsx",
".yml",
".yaml",
".toml",
".md",
".css",
".html",
".htm",
}
# For markdown we allow HTML comment headers
HEADER_PATTERNS = [
re.compile(r"^\s*//\s*Created by:\s*.+$", re.IGNORECASE),
re.compile(r"^\s*#\s*Created by:\s*.+$", re.IGNORECASE),
re.compile(r"^\s*/\*\s*$", re.IGNORECASE),
re.compile(r"^\s*<!--\s*$", re.IGNORECASE),
]
REQUIRED_FIELDS = [
re.compile(r"Created by:\s*.+", re.IGNORECASE),
re.compile(r"Date:\s*\d{4}-\d{2}-\d{2}", re.IGNORECASE),
re.compile(r"Purpose:\s*.+", re.IGNORECASE),
]
def is_text_file(path: Path) -> bool:
# basic heuristic: try decode as utf-8
try:
path.read_text(encoding="utf-8")
return True
except Exception:
return False
def has_required_header(text: str) -> bool:
# Check within first 40 lines
head = "\n".join(text.splitlines()[:40])
if not any(p.search(head) for p in HEADER_PATTERNS):
return False
return all(p.search(head) for p in REQUIRED_FIELDS)
def main(argv: list[str]) -> int:
files = [Path(a) for a in argv[1:] if a and not a.startswith("-")]
if not files:
return 0
bad: list[str] = []
for f in files:
if not f.exists() or f.is_dir():
continue
if f.suffix.lower() not in VALID_EXTS:
continue
if not is_text_file(f):
continue
txt = f.read_text(encoding="utf-8", errors="replace")
if not has_required_header(txt):
bad.append(str(f))
if bad:
sys.stderr.write("\nMesh header check failed. Missing/invalid traceability header in:\n")
for b in bad:
sys.stderr.write(f"- {b}\n")
sys.stderr.write("\nExpected header fields near top of file: Created by, Date (YYYY-MM-DD), Purpose.\n")
sys.stderr.write("See: docs/headers-template.md\n\n")
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))
```
#### `scripts/check_trace_headers.py` (permissions)
```bash
chmod +x scripts/check_trace_headers.py
```
### 1.2 Installation pre-commit
```bash
pipx install pre-commit # ou pip install pre-commit
pre-commit install
pre-commit run --all-files
```
### 1.3 Notes
- Ce hook vérifie uniquement les **headers de création** (Created by / Date / Purpose) dans les premières lignes.
- La traçabilité des **modifications ponctuelles** (fin de ligne) reste une règle humaine + revue.
- Si tu veux imposer aussi `Modified by` dans certaines conditions, on peut ajouter une règle (par ex. pour fichiers existants dans Git).
---
## 2) Snippets VS Code
### 2.1 Fichier snippets à ajouter
Créer : `.vscode/mesh.code-snippets`
```json
{
"Mesh Header — Rust": {
"prefix": "mesh-header-rs",
"body": [
"// Created by: ${1:AgentName}",
"// Date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"// Purpose: ${2:Short description}",
"// Refs: ${3:optional}",
""
],
"description": "Insert Mesh traceability header for Rust files"
},
"Mesh Header — Python": {
"prefix": "mesh-header-py",
"body": [
"# Created by: ${1:AgentName}",
"# Date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"# Purpose: ${2:Short description}",
"# Refs: ${3:optional}",
""
],
"description": "Insert Mesh traceability header for Python files"
},
"Mesh Header — TS/JS": {
"prefix": "mesh-header-ts",
"body": [
"// Created by: ${1:AgentName}",
"// Date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"// Purpose: ${2:Short description}",
"// Refs: ${3:optional}",
""
],
"description": "Insert Mesh traceability header for TypeScript/JavaScript files"
},
"Mesh Header — YAML": {
"prefix": "mesh-header-yaml",
"body": [
"# Created by: ${1:AgentName}",
"# Date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"# Purpose: ${2:Short description}",
"# Refs: ${3:optional}",
""
],
"description": "Insert Mesh traceability header for YAML files"
},
"Mesh Header — TOML": {
"prefix": "mesh-header-toml",
"body": [
"# Created by: ${1:AgentName}",
"# Date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"# Purpose: ${2:Short description}",
"# Refs: ${3:optional}",
""
],
"description": "Insert Mesh traceability header for TOML files"
},
"Mesh Header — Markdown": {
"prefix": "mesh-header-md",
"body": [
"<!--",
"Created by: ${1:AgentName}",
"Date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"Purpose: ${2:Short description}",
"Refs: ${3:optional}",
"-->",
""
],
"description": "Insert Mesh traceability header for Markdown files"
},
"Mesh Header — CSS": {
"prefix": "mesh-header-css",
"body": [
"/*",
"Created by: ${1:AgentName}",
"Date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"Purpose: ${2:Short description}",
"Refs: ${3:optional}",
"*/",
""
],
"description": "Insert Mesh traceability header for CSS files"
},
"Mesh Header — HTML": {
"prefix": "mesh-header-html",
"body": [
"<!--",
"Created by: ${1:AgentName}",
"Date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"Purpose: ${2:Short description}",
"Refs: ${3:optional}",
"-->",
""
],
"description": "Insert Mesh traceability header for HTML files"
},
"Mesh Modified Tag": {
"prefix": "mesh-mod",
"body": [
"Modified by: ${1:AgentName} — ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} — ${2:reason}"
],
"description": "Insert a Mesh modified-by tag"
}
}
```
### 2.2 Activation
Les snippets dans `.vscode/*.code-snippets` sont automatiquement pris en compte par VS Code.
Utilisation :
- taper `mesh-header-` puis sélectionner le snippet.
---
## 3) Recommandation Mesh
- Appliquer le hook pre-commit sur tous les développeurs.
- Dans Codex, fixer `AgentName` à une valeur stable (ex. `Codex`).
- Pour les sous-agents : `SubAgent:SecurityReview`, etc.