214 lines
24 KiB
Markdown
214 lines
24 KiB
Markdown
# CodexPi
|
||
|
||
CodexPi est une interface web privée pour piloter le Codex CLI authentifié sur un Raspberry Pi. L'application est entièrement conteneurisée et n’utilise aucun environnement virtuel Python. De petites unités systemd côté hôte suivent le cycle de vie de Docker et réparent l’accès au socket sans exécuter l’application en root.
|
||
|
||
## Architecture
|
||
|
||
```text
|
||
Safari / navigateur du LAN ── HTTP :8765 ──┐
|
||
▼
|
||
Tailscale Serve ── HTTPS privé ────────► nginx (Docker)
|
||
├── Django + Uvicorn : interface, connexion, sessions
|
||
├── FastAPI + Uvicorn : API, WebSockets, terminal, Git, Docker, Codex
|
||
└── Chromium + Selenium/noVNC : navigateur partagé en direct
|
||
```
|
||
|
||
Seul nginx publie le port `8765` sur le réseau local. Les ports internes `8000` et `8001` ne sont pas publiés. Tailscale Serve reste disponible en parallèle et aucun port n'est ouvert automatiquement sur la box.
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
cd /home/config/CodexPi
|
||
chmod +x scripts/*.sh
|
||
./scripts/install.sh
|
||
./scripts/compose.sh ps
|
||
curl http://127.0.0.1:8765/proxy-health
|
||
```
|
||
|
||
La première ouverture lance l’assistant de configuration et crée le premier
|
||
compte administrateur. Les comptes suivants se gèrent ensuite uniquement dans
|
||
**Settings → Utilisateurs** ; chacun ne voit que ses projets et discussions.
|
||
|
||
Puis vérifiez et activez l'accès HTTPS privé :
|
||
|
||
```bash
|
||
./scripts/tailscale-serve.sh --check
|
||
./scripts/tailscale-serve.sh --apply
|
||
```
|
||
|
||
L'interface comprend dashboard système, sessions persistantes, streaming WebSocket, file d'attente de prompts, envoi privé de fichiers et photos, Markdown, diffs, Git, terminal xterm.js/tmux, commandes Docker limitées, responsive mobile et PWA. Codex, Claude Code et OpenCode sont installés dans l’image ; le moteur se choisit à chaque discussion et conserve son propre profil d’authentification monté depuis l’hôte. Un administrateur peut voir leur état et lancer leur procédure officielle de connexion depuis **Paramètres → Comptes IA** ; CodexPi ne collecte pas les identifiants du fournisseur.
|
||
|
||
La même page peut associer un **PC IA local** exécutant Ollama. Le petit
|
||
`codexpi_ai_bridge.py` expose uniquement les routes d’inférence nécessaires,
|
||
derrière un jeton aléatoire, tandis qu’Ollama reste attaché à localhost sur le
|
||
PC. CodexPi détecte les modèles, choisit le modèle par défaut et injecte une
|
||
configuration OpenCode isolée dans les nouvelles discussions. Consultez
|
||
[le guide du bridge PC](docs/local-ai-pc-bridge.md).
|
||
|
||
La page **Activité** regroupe les conversations, validations et signaux du canard jaune. **Atelier** conserve le chat, les commandes, le worktree et sa revue Git dans une même vue. Le **Chef d’orchestre** prépare une ébauche à valider, crée ensuite une branche et un worktree dédiés, vérifie la révision et attend la recette propriétaire avant fusion. Le chantier de remédiation des ressources se lance séparément depuis **Entreprise → Organisation** avec **Commencer le chantier** : l’Agent maître valide le plan ordonné, mandate l’agent du projet, contrôle son compte rendu et lui renvoie jusqu’à trois corrections dans la même discussion avant de poursuivre. Lors d’un plantage ou d’un blocage, l’incident est automatiquement confié à **CodexPi (Self)** dans un fil de réparation persistant. Après tests, son verdict structuré reprend la même étape et les mêmes fils, ou réclame explicitement le propriétaire si une autorité humaine, un secret ou une action irréversible manque ; trois incidents automatiques au maximum sont tentés par chantier. Lors d’une reprise, les objectifs peuvent être précisés depuis la version courante du plan sans changer l’ordre, la cible ni le niveau de risque ; les fils existants sont conservés et le budget correctif repart à zéro. Cette machine à états tourne côté serveur et continue donc lorsque la PWA est fermée. Les règles communes versionnées et les consignes du projet sont attachées à chaque message envoyé à Codex, Claude Code ou OpenCode ; une exécution déjà en file conserve son instantané.
|
||
|
||
**Paramètres → Apparence** sépare le type d’interface du thème. Le type **Tableau de bord** privilégie les projets et les tâches, **Atelier** conserve conversation, worktree et outils dans un même espace, et **Revue Git** organise la navigation autour des fichiers, branches, tests et actions Git. Les choix de type et de thème (clair, sombre ou système) sont enregistrés par utilisateur et s’appliquent à toute la navigation.
|
||
|
||
L’**Agent maître** est réservé à un administrateur et utilise un service hôte séparé. Il reste indisponible tant que le propriétaire ne l’active pas explicitement depuis l’hôte :
|
||
|
||
```bash
|
||
sudo ./scripts/host-agent-channel.sh install
|
||
./scripts/host-agent-channel.sh status
|
||
sudo ./scripts/host-agent-channel.sh revoke
|
||
```
|
||
|
||
Le service s’exécute sous le compte qui a lancé l’installation avec `sudo`. Ses capacités privilégiées proviennent uniquement des autorisations réelles de ce compte et de la politique `sudo` de l’hôte ; CodexPi ne crée aucune règle `sudoers`. La révocation arrête le service et supprime le socket et son jeton privé.
|
||
|
||
Le projet global privilégié **Pi Doctor / Agent Raspberry Pi** réutilise ce canal privé pour ses conversations normales. Les messages et la file d’attente restent visibles dans l’Atelier, mais le worker Codex s’exécute sur l’hôte réel plutôt que dans le conteneur API. Ce routage exige `FULL ACCESS`, le marqueur global réservé et le compte propriétaire ; un projet ordinaire portant un nom similaire ne peut pas l’activer.
|
||
|
||
Lors de l’ajout d’un dossier local, CodexPi demande s’il faut initialiser le
|
||
dépôt. Une nouvelle discussion propose ensuite une branche, un espace de travail
|
||
isolé et une description de commit. L’onglet **Modifications** constitue le centre
|
||
de revue : sélection des fichiers, diff, message généré par Codex, validation Git
|
||
et envoi, puis création de Pull Request GitHub ou Merge Request GitLab/Gitea.
|
||
L’onglet **Aperçu** détecte les serveurs web démarrés depuis l’espace de la discussion.
|
||
|
||
La connexion GitHub se configure dans **Settings → GitHub**. CodexPi génère une clé SSH Ed25519 privée sur le Pi, affiche la clé publique à ajouter au profil GitHub, vérifie le compte puis relie le projet sélectionné avec son URL SSH. Aucun token ne doit être placé dans l’URL du remote.
|
||
|
||
Une instance **GitLab self-hosted** se connecte dans **Settings → GitLab self-hosted** avec son URL et un jeton personnel doté du scope `api`. CodexPi vérifie le compte, chiffre le jeton avec le secret local, enregistre automatiquement sa clé SSH sur GitLab, puis permet de choisir l’espace de noms et de créer un dépôt directement depuis le projet. HTTP doit être autorisé explicitement pour un serveur local sans TLS ; la vérification du certificat HTTPS peut être désactivée pour un certificat autosigné.
|
||
|
||
Les fournisseurs sont regroupés dans **Settings → Dépôts** sous trois accordéons : Gitea, GitLab et GitHub. Gitea bénéficie du même parcours clé en main que GitLab : connexion par jeton chiffré, ajout automatique de la clé SSH, choix du compte ou de l’organisation, création du dépôt et configuration de `origin`.
|
||
|
||
Consultez [l'installation](docs/installation.md), [l'architecture](docs/architecture.md), [le modèle de sécurité](docs/security.md) et [l'audit](docs/SECURITY_AUDIT.md).
|
||
|
||
## Publication du code
|
||
|
||
Les mots de passe, jetons GitLab/Gitea, clés SSH, sessions Codex, clés Web Push,
|
||
bases SQLite et journaux restent dans `.env`, `data/`, le volume du navigateur
|
||
ou le dossier Codex monté depuis l'hôte. Aucun de ces emplacements ne doit être
|
||
publié. Les secrets principaux peuvent aussi provenir de fichiers externes avec
|
||
`CODEXPI_SECRET_KEY_FILE` et `CODEXPI_DJANGO_SECRET_KEY_FILE`.
|
||
|
||
Avant toute publication, exécutez :
|
||
|
||
```bash
|
||
./scripts/check-secrets.sh --worktree
|
||
./scripts/check-secrets.sh --history
|
||
```
|
||
|
||
Si le dépôt privé contient déjà une information sensible dans son historique,
|
||
créez une copie publique sans cet historique :
|
||
|
||
```bash
|
||
./scripts/export-public-source.sh ../CodexPi-public
|
||
```
|
||
|
||
La copie contient les sources courantes autorisées par `.gitignore`, initialise
|
||
une branche `main` vide et réactive le contrôle pré-commit. Vous pouvez ensuite
|
||
y créer le premier commit et la relier à un nouveau dépôt public.
|
||
|
||
Le hook `.githooks/pre-commit`, activé par `install.sh`, bloque également les
|
||
nouveaux commits contenant un nom de fichier sensible, une clé privée ou un
|
||
format de jeton connu. Une détection dans l'historique impose de publier un
|
||
historique nettoyé et de révoquer la clé concernée ; `.gitignore` ne nettoie
|
||
jamais rétroactivement un ancien commit.
|
||
|
||
## Développement et tests
|
||
|
||
Les dépendances sont installées dans l'image, jamais dans `.venv` :
|
||
|
||
```bash
|
||
./scripts/compose.sh build
|
||
./scripts/compose.sh run --rm django-web python3 manage.py check --deploy
|
||
docker build --target test -t codexpi-test .
|
||
docker run --rm codexpi-test
|
||
npm --prefix frontend ci
|
||
npm --prefix frontend run build
|
||
```
|
||
|
||
Pour appliquer une nouvelle version depuis une discussion CodexPi, utilisez `./scripts/redeploy.sh`. Le script construit immédiatement l’image, attend une période stable sans aucune tâche Codex, recrée les backends, attend leur état sain puis recharge nginx. Il évite ainsi de couper la discussion qui a déclenché le rebuild. Un rafraîchissement pendant la courte bascule affiche une page de mise à jour avec reconnexion automatique plutôt qu’une erreur 502 brute.
|
||
|
||
Utilisez `./scripts/compose.sh` pour les commandes Compose du projet. Ce wrapper relève à chaque exécution le groupe numérique du socket `/var/run/docker.sock` et l’ajoute au conteneur API ; l’accès Docker reste ainsi valide après un redémarrage ou une mise à jour qui recrée le socket avec un autre GID. La valeur `DOCKER_GID` de `.env` sert uniquement de valeur de secours pour une invocation directe de Compose.
|
||
|
||
Le minuteur hôte `codexpi-docker-access.timer`, installé automatiquement par `install.sh`, vérifie aussi ce groupe chaque minute. S’il a changé, il attend la fin des tâches Codex puis recrée uniquement l’API avec le nouveau GID. Une pile arrêtée volontairement n’est jamais démarrée par ce contrôle.
|
||
|
||
Le mode `FULL ACCESS` est désactivé par défaut et nécessite une confirmation. Il contourne volontairement le sandbox du moteur : le dossier courant reste le contexte de travail, mais il ne constitue plus une frontière d’écriture. CodexPi ne lance jamais Codex avec `sudo`.
|
||
|
||
Git est facultatif. En mode SAFE, Codex est lancé avec `--skip-git-repo-check` et `--sandbox workspace-write` : il peut lire et modifier le dossier du projet même sans `.git`, sans sortir de son espace de travail.
|
||
|
||
Le projet interne `CodexPi (Self)` est enregistré automatiquement depuis `CODEXPI_SELF_PROJECT_PATH`. Il pointe vers `/home/config/CodexPi`, reste en mode SAFE par défaut et permet à une session Codex d'améliorer les sources de l'interface elle-même. Une reconstruction Docker reste nécessaire pour mettre en service les changements de frontend ou de backend.
|
||
|
||
## Messages en attente et fichiers
|
||
|
||
Le champ de chat reste utilisable pendant qu'une tâche tourne. Les messages suivants sont stockés dans SQLite et exécutés séquentiellement dans la même session Codex, même si le navigateur est fermé. La liste d'attente permet de modifier ou retirer un message qui n'a pas encore démarré, ainsi que d'annuler toute la file en une fois. Un message déjà en cours reste immuable.
|
||
|
||
Pendant une exécution, le bouton **Arrêter** interrompt uniquement la tâche courante. Les modifications déjà écrites dans le projet sont conservées et les messages suivants de la file peuvent ensuite continuer normalement.
|
||
|
||
Le bouton `+` joint jusqu'à quatre fichiers de 100 Mo chacun. Une image copiée peut aussi être collée directement dans le champ du chat, y compris depuis Safari sur iPhone. Lorsqu'un texte dépasse la taille acceptée par un message, l'interface le transforme automatiquement en fichier `.txt` joint : jusqu'à 10 Mo, ce fichier peut ensuite être ouvert et modifié depuis la discussion. Les fichiers sont vérifiés, renommés avec un UUID, stockés sous `data/uploads/` avec des permissions privées et servis uniquement après authentification. Les images compatibles sont transmises au CLI avec `--image`, tandis que les autres fichiers sont signalés à l'agent par leur chemin de stockage ; aucun chemin fourni par le navigateur n'est exécuté.
|
||
|
||
À la création d'une session, le formulaire propose le modèle Codex et l'effort de raisonnement. Ces choix sont persistés par session, validés côté serveur et transmis au CLI comme arguments structurés. Les groupes « Actions réalisées » et « Fichiers modifiés » sont repliables afin de conserver un chat lisible sur mobile.
|
||
|
||
Chaque message propose un menu d'actions permettant de le citer dans la zone de saisie ou de « repartir ici ». La reprise crée une nouvelle session avec l'historique copié jusqu'au message choisi, le même modèle et le même effort ; la session d'origine n'est jamais tronquée ni supprimée.
|
||
|
||
Lorsqu’une tâche échoue, le badge rouge **Erreur** ouvre le diagnostic renvoyé par le moteur et permet de le copier pour le transmettre dans une discussion de correction, ou de renvoyer immédiatement le message. Si Codex ne retrouve plus les données locales du fil, CodexPi abandonne automatiquement cet identifiant périmé et relance le message, avec ses pièces jointes, dans un nouveau fil. La compression du contexte applique la même récupération : elle remet le compteur à zéro, signale que l’ancien contexte moteur est indisponible et laisse la prochaine demande démarrer un fil neuf. Jusqu’à trois projets distincts peuvent utiliser Codex en parallèle. Les tâches appartenant au même projet restent exécutées l’une après l’autre pour éviter les conflits de fichiers. Les espaces globaux Agent maître, Agent Raspberry Pi et Agent Bibliothécaire sont des projets distincts : ils peuvent donc travailler simultanément, dans la même limite globale, sans que leur marqueur d’interface commun ne les mette artificiellement en file d’attente.
|
||
|
||
Depuis **Entreprise → Organisation**, un groupe se crée en sélectionnant au moins deux projets locaux existants appartenant au même propriétaire. Un projet peut participer à plusieurs groupes. Les agents Codex SAFE d’un membre reçoivent les chemins des autres projets afin de consulter leur code en lecture seule, ainsi que les derniers comptes rendus produits par leurs agents. En `FULL ACCESS`, les moteurs reçoivent aussi ces chemins avec une consigne explicite de ne modifier un autre membre que sur demande du propriétaire ; cette consigne n’est pas une frontière technique, puisque le sandbox est désactivé. En SAFE, les moteurs qui ne garantissent pas la lecture croisée protégée continuent de ne recevoir que les noms et comptes rendus. Tous les projets qui partagent au moins un groupe utilisent des verrous d’exécution cumulés et ordonnés : plusieurs projets qui pilotent une même ressource deviennent séquentiels entre eux, y compris lorsqu’un projet relie plusieurs groupes. Ginoflex, NASflix et TvRework peuvent ainsi former le groupe `TV`, quel que soit leur mode d’autonomie. Au-delà de la capacité disponible, les messages attendent de façon visible et annulable. La limite se règle avec `CODEXPI_MAX_CONCURRENT_AGENTS` et vaut `3` par défaut. Les limitations temporaires HTTP 429 survenues avant toute action sont en plus réessayées automatiquement après 15, 45 puis 120 secondes. Une réponse terminée propose également **Réessayer** à côté des commandes de copie et de lecture vocale ; le texte et les pièces jointes d’origine sont réutilisés dans une nouvelle tâche sans modifier l’historique.
|
||
|
||
Le **Centre de coordination** de cette page ajoute quatre fonctions persistantes. **Décisions** reçoit les arbitrages demandés par un agent sous forme de cases à cocher ; si aucune proposition ne convient, le propriétaire peut cocher la réponse libre. La réponse repart dans la discussion d’origine et ne change aucune permission. **Incidents** centralise les tâches échouées et les confie automatiquement à `CodexPi (Self)` lorsque cet agent existe en `FULL ACCESS`; Self travaille dans un fil de diagnostic séparé, puis rend son résultat au fil source sans rejouer aveuglément les effets de bord. **Ressources** définit la concurrence globale, les seuils de charge et de mémoire, une fenêtre d’arrière-plan, la priorité de chaque projet et un budget de durée. Ces règles n’arrêtent ni ne préemptent jamais un agent actif : elles retardent seulement les nouvelles tâches et signalent un dépassement de durée. **Agents spécialisés** crée un atelier global SAFE à partir d’une description métier. Le nouvel agent construit son contrat et s’auto-évalue avant de devenir appelable par compétence ; il lit le projet source, produit ses artefacts dans son propre atelier, puis l’agent du projet conserve l’intégration, les tests et le déploiement. Une délégation peut appeler un second spécialiste, mais la profondeur est bornée à deux.
|
||
|
||
Les réponses finales proposent aussi une lecture à voix haute depuis leur pied de message. La synthèse vocale reste dans le navigateur, prend en charge la pause, la reprise et l'arrêt, et omet les blocs de code. Une réponse peut alterner librement texte Markdown et images : les captures du navigateur, les images générées et les images du projet sont copiées dans l’espace de travail puis placées entre deux paragraphes, ouvertes en plein écran, téléchargées ou citées dans la demande suivante. Les captures non placées explicitement restent visibles dans la galerie de pièces jointes du message. Les archives et autres artefacts ouvrent une fiche interne avec un bouton **Fermer**. Sur iPhone, **Enregistrer / partager** utilise la feuille de partage native afin que l’aperçu du fichier ne remplace jamais la PWA sans possibilité de retour.
|
||
|
||
Dans Settings → Paramètres du projet, l’effort peut être changé pour les prochaines demandes de toutes les sessions du projet, ainsi que pour les nouvelles sessions. Une demande déjà lancée conserve son effort. Le chronomètre du chat utilise l’heure de lancement enregistrée côté serveur et reprend le temps écoulé après un rechargement ou un retour depuis l’arrière-plan.
|
||
|
||
Notifications iOS : ajoutez CodexPi à l’écran d’accueil depuis Safari (iOS 16.4 minimum), ouvrez l’icône, puis **Réglages → Notifications PWA → Activer sur cet appareil** et acceptez la demande iOS. Trois catégories sont réglables séparément : fin de tâche, décision requise, incident/réparation. Les notifications ouvrent directement le fil ou l’onglet du Centre de coordination concerné, même quand l’app est fermée. Chaque abonnement reçoit uniquement les événements de son compte. Le bouton Désactiver retire cet appareil ; la déconnexion retire également son abonnement. Les clés VAPID et les envois en attente sont conservés dans la base persistante. Le serveur doit pouvoir joindre les services Push en HTTPS sortant. `CODEXPI_VAPID_SUBJECT` permet de préciser un contact VAPID (URL HTTPS ou mailto) ; par défaut l’origine HTTPS configurée est utilisée.
|
||
|
||
## Navigateur intégré
|
||
|
||
Chaque projet possède un onglet **Navigateur** qui affiche la même session Chromium partagée en temps réel. Toutes les IA et tous les projets retrouvent donc la fenêtre, les onglets et la connexion en cours. Codex peut ouvrir une URL, inspecter la page, cliquer, saisir du texte non sensible, défiler et prendre une capture. Les captures demandées par Codex sont automatiquement jointes à sa réponse et restent téléchargeables dans le journal d’activité du projet qui les a demandées.
|
||
|
||
Le bouton **Prendre la main** suspend globalement les commandes de toutes les IA et active l’interaction dans la vue. Sur écran tactile, la surface du navigateur fonctionne comme un pavé tactile : le geste déplace le pointeur relativement à sa position actuelle, sans le placer sous le doigt. Pour une connexion, un CAPTCHA, un paiement ou une information sensible, Codex doit utiliser la reprise en main humaine ; les secrets ne doivent jamais être envoyés dans le chat. Après l’action, **J’ai terminé — rendre à Codex** réactive l’automatisation. Le profil Chromium est conservé dans le volume Docker `browser-home`, tandis que les captures sont stockées sous `data/browser/screenshots/`.
|
||
|
||
Chromium démarre comme navigateur interactif normal, puis ChromeDriver s’attache à son interface de débogage locale pour laisser les IA l’automatiser. Ce mode évite les marqueurs de lancement WebDriver pendant les connexions humaines. Le port de débogage reste lié à l’intérieur du conteneur et le service Selenium n’expose aucun port sur l’hôte. La vue noVNC passe par nginx sous `/browser/` et est protégée par la même session Django que le reste de CodexPi. L’image ARM64 peut être remplacée ou épinglée avec `CODEXPI_BROWSER_IMAGE`.
|
||
|
||
## Agent léger distant
|
||
|
||
Le bridge téléchargé depuis **Machine distante** partage un seul dossier et se
|
||
connecte uniquement en sortie à CodexPi. En plus de lire et écrire des fichiers,
|
||
il peut exécuter dans ce dossier des commandes transmises comme tableaux
|
||
d’arguments (jamais par interpolation shell), avec délai et volume de sortie
|
||
limités. Le jeton reste dans la configuration privée de la machine distante et
|
||
la connexion est révocable depuis CodexPi.
|
||
|
||
## Réparation Docker
|
||
|
||
En cas de panne, lancez `./scripts/repair.sh` depuis le projet. Le script collecte l’état et les logs, sollicite une instance Codex uniquement lorsqu’une panne active est détectée, reconstruit l’image, vérifie les permissions persistantes et relance les services. `./scripts/repair.sh --check` réalise un diagnostic sans modification. Chaque intervention est enregistrée dans le tableau JSON structuré `data/repair.json` et visible dans **Settings → Historique de réparation**.
|
||
|
||
Pour un incident plus général du Raspberry Pi, lancez
|
||
`./repair-rpi-with-codex.sh`. Ce lanceur ouvre une session Codex interactive
|
||
dans un worktree Git isolé. Les diagnostics sont en lecture seule par défaut ;
|
||
les actions système, Docker, réseau, stockage et redémarrage exigent une
|
||
approbation explicite dans la session.
|
||
|
||
## Inventaire des stockages dupliqués
|
||
|
||
La comparaison de deux installations reste en lecture seule par défaut :
|
||
|
||
```bash
|
||
./scripts/storage-reconcile.py /home/config/CodexPi /home/config/CodexPi2 --pretty
|
||
```
|
||
|
||
Le rapport JSON croise les références SQLite, les sessions actives, les PID,
|
||
les worktrees Git, l'état sale, les verrous, l'âge, la taille et le propriétaire.
|
||
Une estimation récupérable n'est produite que pour un contenu identique dans
|
||
une instance secondaire et sans aucune exclusion. Les symlinks ne sont jamais
|
||
suivis. Une base absente ou illisible protège toute son instance, une référence
|
||
active protège aussi ses descendants, et l'état vivant est relu sous verrou
|
||
juste avant toute quarantaine. Un schéma SQLite incomplet est également refusé ;
|
||
la fenêtre de déplacement réserve les deux bases avec `BEGIN IMMEDIATE` pour
|
||
empêcher une admission concurrente. Si `data/` ou l'un de ses parents est un
|
||
symlink, l'instance n'est pas parcourue. Les profils Chromium et les volumes Docker nommés ne sont pas ouverts
|
||
automatiquement : seuls les caches explicitement versionnés sous
|
||
`data/browser-cache/<version>/{Cache,Code Cache,GPUCache}` sont comparés, et
|
||
uniquement entre versions majeures identiques.
|
||
|
||
Le backend possède une quarantaine atomique récupérable avec manifeste privé,
|
||
délai de grâce (14 jours par défaut), restauration et journal d'audit. Ces
|
||
mutations exigent un jeton de confirmation lié au dry-run (`QUARANTINE:<plan>`
|
||
ou `RESTORE:<reçu>`). La commande ci-dessus n'expose volontairement aucune
|
||
option de mutation ; aucune purge automatique n'est mise en œuvre.
|