# wp-back — Design d'architecture

**Date** : 2026-07-23
**Statut** : validé en brainstorming (choix structurants approuvés par l'utilisateur), en attente de revue de spec

## 1. Produit

Plugin WordPress **commercial** concurrent de WP Time Capsule, avec 4 features cibles :

1. Backups incrémentaux (fichiers + base de données)
2. Restores incrémentaux (vue calendrier, restore granulaire)
3. Staging (clone 1-clic sur l'hébergement du client, push staging → prod)
4. Updates sécurisés (backup avant update, test en staging, rollback sélectif)

**Différence structurelle avec WP Time Capsule** : WPTC demande au client ses propres credentials cloud (BYO-cloud). Ici, le stockage est sur **notre compte Wasabi**, invisible pour le client. Conséquences :

- Il faut un **backend SaaS** qui médiatise tout accès au stockage (le plugin ne détient jamais de clés S3).
- Le **metering** par client est possible et nécessaire (base de la facturation).
- Argument commercial fort : backups **immuables pour un attaquant** (un site compromis ne peut rien supprimer) — « ransomware-proof ».

## 2. Décisions validées

| Décision | Choix | Alternatives écartées |
|---|---|---|
| Backend SaaS | **Cloudflare Workers** (TypeScript, Hono, wrangler) | Next.js/Vercel, PHP/Laravel |
| Isolation Wasabi | **Buckets poolés + préfixe par site** (`s/{site_id}/`) | 1 bucket/client (plafond ~100-1000 buckets/compte), sub-comptes WACM (réservé partenaires channel) |
| Transfert des données | **Presigned URLs directes** site ↔ Wasabi (15 min, scopées objet par objet) | Proxy via l'API (impossible sur des backups multi-Go, coûts) |
| Périmètre V1 | **Backup + Restore incrémentaux** ; Staging en V1.1, Updates en V1.2 | Les 4 features d'un coup |

## 3. Composants

### 3.1 Plugin WordPress (`plugin/`)

PHP 8.1+. Responsabilités :

- **Moteur incrémental fichiers** : manifest local (table `wpb_files` : chemin, taille, mtime, sha256). Scan par batches relançables (Action Scheduler) pour survivre aux timeouts PHP et aux gros sites. Fichier modifié → upload en **content-addressed storage** : `s/{site_id}/blobs/{sha256}`. La déduplication inter-versions est gratuite par construction.
- **Moteur incrémental DB** : V1 = checksum par table, dump chunké de la table entière si changement. Granularité par lignes (niveau WPTC) en itération ultérieure sur les grosses tables (`posts`, `postmeta`).
- **Snapshots** : chaque backup = un manifest (liste des blobs + méta : version WP, plugins, thème) uploadé sous `s/{site_id}/snapshots/{ts}.json`. Le restore incrémental = diff entre manifest cible et état courant du disque → seuls les blobs manquants/différents sont téléchargés.
- **UI admin** : vue calendrier des points de restauration, restore granulaire (fichiers individuels), état des quotas.
- Jamais de credentials S3 côté plugin — uniquement un token de site (JWT) + presigned URLs éphémères.

### 3.2 API SaaS (`api/`)

Cloudflare Workers + Hono, TypeScript, pnpm. Responsabilités :

- **Licences & activation** : `POST /v1/sites/activate` (clé de licence + URL du site) → crée `site_id`, assigne un bucket du pool, retourne un token de site (JWT rotatif).
- **Broker de stockage** : le plugin déclare l'opération (« PUT blob sha256:… de N octets »), le backend vérifie quotas/droits, **construit lui-même la clé S3** (le client ne fournit jamais de chemin) et signe une presigned URL courte durée. Idem pour les GET au restore. Pas de LIST ni DELETE côté client.
- **Metering** : journalisation de chaque URL émise (site, op, clé, taille déclarée) + **job de réconciliation** (Workers Cron) qui vérifie les tailles réelles via HEAD/LIST par préfixe. Wasabi n'émet pas d'événements S3 → la réconciliation périodique est la source de vérité.
- **Quotas** : compteurs par site (stockage total, upload mensuel, nb snapshots), enforcement au moment de la signature.
- **Rétention & GC** : rétention par plan (ex. 30/90/365 j) ; GC qui supprime les blobs non référencés par aucune snapshot vivante ; purge par préfixe à la résiliation.
- **Facturation** : Stripe, plans à paliers de stockage.

### 3.3 Wasabi (notre compte)

- Pool de N buckets partagés (répartition des sites pour lisser les limites par bucket) ; chaque site sous `s/{site_id}/`.
- Layout objet :
  - `s/{site_id}/blobs/{sha256}` — contenu des fichiers (dédupliqué)
  - `s/{site_id}/db/{table}/{ts}.sql.gz` — dumps DB chunkés
  - `s/{site_id}/snapshots/{ts}.json` — manifests de snapshots
- Chiffrement V1 : TLS + SSE Wasabi. Suppressions uniquement via le GC backend → immutabilité effective vis-à-vis d'un site compromis.

## 4. Flux principaux

### Backup incrémental
1. Cron/déclenchement → scan par batches, comparaison au manifest local.
2. Pour chaque blob nouveau : `POST /v1/storage/put` → presigned PUT → upload direct vers Wasabi.
3. Dump DB des tables modifiées, même circuit.
4. Upload du manifest de snapshot → le backend enregistre la snapshot et met à jour les compteurs.

### Restore incrémental
1. L'utilisateur choisit une date dans le calendrier.
2. Le plugin télécharge le manifest cible, diffe avec l'état du disque.
3. Presigned GET des seuls blobs nécessaires, remplacement atomique (fichier temporaire + rename), puis import DB.

### Staging (V1.1)
Clone sur l'hébergement du client (sous-dossier + préfixe de tables séparé), créé par restore d'une snapshot vers un autre emplacement. Push staging → prod = diff de manifests. Réutilise entièrement le moteur.

### Updates sécurisés (V1.2)
Snapshot auto avant update → update → healthcheck → rollback sélectif = restore incrémental des seuls fichiers touchés. Comparaison visuelle en itération ultérieure.

## 5. Sécurité

- Le backend construit toutes les clés S3 ; presigned URLs scopées à un objet et une opération, TTL 15 min.
- Un site ne peut jamais : sortir de son préfixe, lister, supprimer, écraser un blob existant (les blobs sont content-addressed → un PUT sur un sha256 déjà présent est refusé ou sans effet).
- Rate limiting par site, taille max d'objet, rotation du token de site.
- Modèle de menace de référence : le site WordPress est compromis. L'attaquant ne doit pouvoir ni lire un autre site, ni détruire l'historique du sien.
- **Risque accepté (V1)** : quiconque connaît le couple (clé de licence, URL du site) peut ré-activer le site et faire tourner la rotation de token, invalidant celui du site légitime — un DoS de session, sans aucun accès aux données ni à l'historique. Mitigations : la clé de licence est le credential racine (format haute entropie exigé à la génération), rate limit 10 req/min/IP sur l'activation, et le plugin ré-active automatiquement sur 401. Durcissement candidat V2 : confirmation par le titulaire (email) lors d'une activation depuis une nouvelle origine.

## 6. Erreurs & résilience

- Uploads relançables : le manifest de snapshot n'est committé qu'en dernier — un backup interrompu ne produit jamais de snapshot corrompue, juste des blobs orphelins que le GC ramasse.
- Batches idempotents (reprise sur sha256 déjà présents).
- Réconciliation metering tolérante aux échecs (recalcul complet par préfixe possible).

## 7. Tests

- **Plugin** : PHPUnit + wp-env ; fixtures de sites (petits/gros, tables volumineuses) ; tests du diff manifest et du restore atomique.
- **API** : Vitest + Miniflare ; tests des règles de signature (jamais de clé hors préfixe), quotas, GC.
- **E2E** : site WP jetable → backup → mutation → backup → restore à J-1 → vérification byte-à-byte.

## 8. Questions ouvertes (à trancher avant ou pendant l'implémentation)

- **Chiffrement côté client** (clé par site, libsodium) : argument commercial vs complexité de restore — V2 ?
- **DB backend** : D1 suffit-il pour le metering à l'échelle, ou Postgres (Hyperdrive) d'emblée ?
- **Nom du produit** et pricing exact des paliers.
- **Multisite WordPress** : supporté en V1 ou explicitement exclu ?
- Politique exacte du pool de buckets (nombre initial, stratégie d'assignation).
