Files
codexpi/README.md
CodexPi 995b17a6e2
Some checks are pending
Qualité / tests (push) Waiting to run
feat: publish CodexPi contest snapshot
2026-11-14 20:23:59 +01:00

24 KiB
Raw Blame History

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

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

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é :

./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.

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 :

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, l'architecture, le modèle de sécurité et l'audit.

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 :

./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 :

./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 :

./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 :

./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.