Baikal MCP pour la gestion des agendas perso et NEXTE et de l'observatoire des tâches
1-l'infrastructure Baikal et Infcloud sur le vps personnel - juxjux.ovh
Cas à part de la gestion des contacts et du calendrier -
Non pas sur alteris.ovh mais sur juxjux.ovh car primauté de l'infrastructure personnelle de JUlien pour la gestion des agendas et des contacts.
la solution contacts et calendrier
Solution d'unification des contacts et du calendrier de Julien.
État au 3 août 2026 — déployé et opérationnel de bout en bout. Seul vdirsyncer (agrégation Google/Infomaniak) reste à faire.
1. Objectif
Unifier la base de contacts et de calendriers, dispersée entre plusieurs sources (Google, Infomaniak, téléphone), autour d'un point central auto-hébergé sur le VPS juxjux.ovh, synchronisé vers tous les clients existants.
Baïkal est la source de vérité unique. Roundcube, InfCloud et DAVx5 en sont des clients ; vdirsyncer sera le seul composant qui écrit depuis l'extérieur vers Baïkal.
2. Architecture réellement déployée
| Composant | Rôle | Emplacement | État |
|---|---|---|---|
| Baïkal | Serveur CardDAV + CalDAV, stockage central | Docker, VPS juxjux | En service |
| InfCloud | Agenda et contacts web | Statique, servi par nginx | En service |
| Roundcube | Interface web des contacts | Docker, VPS juxjux | En service |
| Nginx | Reverse proxy HTTPS | Hôte VPS | En service |
| DAVx5 | Pont de synchronisation vers Android | Téléphone | Opérationnel |
| Fossify Agenda | Client calendrier | Téléphone (via DAVx5) | Opérationnel |
| vdirsyncer | Agrège Google et Infomaniak vers Baïkal | VPS, cron | Non déployé |
| Thunderbird | Client mail, indépendant de ce circuit | Téléphone | Inchangé |
3. Accès
| Service | URL | Identifiant |
|---|---|---|
| Agenda + contacts web | https://baikal.juxjux.ovh/infcloud/ | Julien (compte Baïkal) |
| Contacts web (Roundcube) | https://secretariat.juxjux.ovh | julien.bertrand@ik.me (compte mail Infomaniak) |
| Administration Baïkal | https://baikal.juxjux.ovh/admin/ | compte admin Baïkal |
| Découverte DAV (DAVx5) | https://baikal.juxjux.ovh/dav.php/ |
Julien |
Utilisateur Baïkal : Julien — avec un J majuscule. La casse compte dans les URLs DAV.
- Principal :
https://baikal.juxjux.ovh/dav.php/principals/Julien/ - Contacts :
https://baikal.juxjux.ovh/dav.php/addressbooks/Julien/default/ - Calendrier :
https://baikal.juxjux.ovh/dav.php/calendars/Julien/default/
Certificats Let's Encrypt valides jusqu'au 1er novembre 2026 pour les deux domaines. Celui de baikal.juxjux.ovh existait déjà mais avait expiré le 8 février 2026 (vestige d'une tentative antérieure) ; il a été renouvelé le 3 août.
4. Déploiement
Stack sur le VPS : /home/debian/baikal/docker-compose.yml, projet compose baikal. Sources versionnées : Syncthing/Jux_univers/Jux-scripts/Baikal-Contacts/.
| Fichier | Rôle |
|---|---|
docker-compose.yml |
Stack Baïkal + Roundcube |
baikal.juxjux.ovh.conf |
Vhost Baïkal (.well-known forcés en HTTPS + service d'InfCloud) |
secretariat.juxjux.ovh.conf |
Vhost Roundcube |
infcloud-config.js |
Configuration InfCloud |
finaliser_tls.sh |
Vérifie le DNS puis lance certbot |
vps_backup.sh |
Script de sauvegarde complet, Baïkal inclus |
push_bookstack.py |
Publication de cette page via l'API BookStack |
| Service | Image | Port local | Base | RAM |
|---|---|---|---|---|
| baikal | ckulka/baikal:nginx |
127.0.0.1:8088 | SQLite | ~38 Mo |
| roundcube | roundcube/roundcubemail:latest (1.7.2) |
127.0.0.1:8083 | SQLite | ~32 Mo |
| InfCloud | fichiers statiques (13 Mo sur disque) | — | aucune | 0 |
Les deux containers pèsent environ 70 Mo au total, contre environ 600 Mo avec l'architecture MariaDB initialement prévue. Ce choix était important : le VPS était en pression mémoire au moment du déploiement (swap à 3,8/4,0 Go).
5. Écarts avec le plan initial
Le plan préparé prévoyait une configuration qui ne pouvait pas fonctionner en l'état. Écarts constatés et corrigés :
| Point | Prévu | Réel | Raison |
|---|---|---|---|
| Port Baïkal | 8081 | 8088 | 8081 est occupé par FreshRSS. 8088 correspond au vhost baikal.juxjux.ovh préexistant |
| Port Roundcube | 8082 | 8083 | 8082 est ciblé par un vhost nextcloud mort |
| Domaine Roundcube | mail.juxjux.ovh |
secretariat.juxjux.ovh |
Aucun DNS n'existait ; mail. prêtait à confusion avec les MX OVH du domaine |
| Base Roundcube | MariaDB | SQLite | ~400 Mo de RAM économisés, suffisant en mono-utilisateur |
| Vhost Baïkal | À créer | Déjà présent | Le fichier baikal.conf préparé n'a pas servi |
| Agenda web | Plugin calendar Roundcube | InfCloud | Plugin inutilisable, voir §6.2 |
6. Pièges rencontrés
6.1 Les plugins carddav et calendar ne sont pas dans l'image Roundcube
L'image officielle ne fournit ni carddav ni calendar. Les déclarer dans ROUNDCUBEMAIL_PLUGINS ne fait que les activer — s'ils sont absents, Roundcube part en erreur fatale.
Il faut les faire installer par composer au démarrage, via ROUNDCUBEMAIL_COMPOSER_PLUGINS (et non ROUNDCUBEMAIL_INSTALL_PLUGINS, qui n'existe pas dans cette image) :
Le premier démarrage est alors plus long (téléchargement composer).
6.2 Le plugin calendar de Roundcube est une impasse
Trois obstacles successifs, testés le 3 août :
a) Il casse le tout premier démarrage. Le script d'initialisation de base de kolab/calendar s'exécute avant que l'entrypoint n'écrive la configuration Roundcube : il tombe sur le DSN MySQL par défaut et échoue en SQLSTATE[HY000] [2002] No such file or directory. Cette erreur fatale empêche la régénération de l'autoloader composer, ce qui casse aussi le plugin carddav :
Symptôme : HTTP 500 permanent, alors que les fichiers du plugin sont bien présents sur le disque — c'est vendor/composer/autoload_psr4.php qui ne contient aucune entrée carddav.
b) Ce premier obstacle est contournable. Réinstallé après que Roundcube ait été initialisé une première fois, la configuration existe déjà dans le volume et l'initialisation aboutit (Creating database schema... [OK]). Vérifié.
c) Mais le plugin ne sait pas parler à Baïkal. La seule version installable, kolab/calendar 3.2.9.1, ne livre que les pilotes database, kolab et ldap — aucun pilote caldav. Il ne créerait qu'un agenda local dans Roundcube, sans lien avec Baïkal ni le téléphone : pire qu'inutile, on pourrait y saisir des rendez-vous en croyant qu'ils se synchronisent.
Les versions qui embarquent le pilote CalDAV (3.5.7, 3.6.1) dépendent de kolab/libkolab, qui réclame pear/http_request2 — paquet bloqué par composer pour faille de sécurité (PKSA-jrt3-xndd-g4sz). Ne pas contourner ce garde-fou sur un service exposé sur internet.
Conclusion : agenda web assuré par InfCloud (§7), plugin calendar définitivement écarté.
6.3 Baïkal ignore X-Forwarded-Proto — fuite HTTP sur la découverte
Baïkal ne tient pas compte de X-Forwarded-Proto et générait ses redirections de découverte en HTTP nu :
C'est exactement le risque identifié dès la conception : CardDAV et CalDAV envoient les identifiants à chaque synchronisation. Un client suivant cette redirection part sur du HTTP.
Corrigé en court-circuitant Baïkal directement dans le vhost :
À vérifier après tout reload nginx : le rechargement n'est pas instantané, un test lancé dans la foulée peut encore montrer l'ancien comportement.
6.4 Roundcube exige un serveur IMAP pour authentifier
Roundcube n'a pas de base d'utilisateurs propre : son écran de login valide les identifiants contre le serveur IMAP configuré. Sans IMAP joignable, aucune connexion n'est possible — donc aucun accès aux contacts non plus.
L'idée d'un Roundcube « contacts et agenda seulement, sans IMAP » n'est pas réalisable. La configuration retenue pointe vers ssl://mail.infomaniak.com:993, ce qui couvre le compte julien.bertrand@ik.me (le domaine ik.me est servi par l'infrastructure Infomaniak).
Si un jour le compte passe en double authentification, il faudra générer un mot de passe d'application Infomaniak.
6.5 Baïkal doit être installé en SQLite
L'installeur Baïkal propose MySQL ou SQLite. Choisir SQLite : la stack ne contient aucun serveur MySQL, cocher « Enable MySQL » mène à une impasse. Base à /var/www/baikal/Specific/db/db.sqlite, dans le volume baikal-data.
7. InfCloud — l'agenda web
Client CalDAV/CardDAV entièrement côté navigateur : aucun backend, aucune base, du HTML/JS servi en statique par nginx. Affiche agenda et contacts.
- Source officielle :
https://www.inf-it.com/InfCloud_0.13.1.zip(3,9 Mo, sha2569fa95edd2dcc2b864a10b503ab9220895ea28d4c5541ab02117de1511d5464d4) - Installé dans
/home/debian/baikal/infcloud, propriétairewww-data - Servi sur le même domaine que Baïkal (
/infcloud/) et non sur un sous-domaine dédié : InfCloud interroge/dav.php/en direct depuis le navigateur ; depuis une autre origine il aurait fallu ouvrir du CORS sur les méthodes DAV de Baïkal, ce qui est fragile et l'affaiblirait. Même origine = aucun CORS. - Dossier
auth/supprimé : module PHP d'authentification par proxy inutilisé ici. nginx ne traite pas le PHP à cet emplacement — servis en statique, ces fichiers.incauraient exposé leur contenu en clair.
Réserve assumée : projet figé depuis 2015, interface datée, embarque jQuery 2.1.4. Acceptable parce qu'il n'a aucun composant serveur : rien à maintenir, rien à patcher. La seule alternative maintenue serait SOGo, au prix d'environ 500 Mo de RAM et d'une base PostgreSQL.
7.1 Les trois pièges d'InfCloud
a) Le bloc regex du vhost avale tous les JS. Le vhost Baïkal contient déjà :
En nginx, un bloc regex ~* a priorité sur un bloc préfixe ordinaire. Tous les fichiers JavaScript d'InfCloud auraient donc été proxifiés vers Baïkal. Comme InfCloud est presque intégralement du JavaScript, la page serait restée blanche — symptôme difficile à diagnostiquer. Il faut ^~, qui passe devant les regex :
b) L'endpoint par défaut vise DAViCal. Le config.js livré pointe /caldav.php/ ; Baïkal expose /dav.php/.
c) Le href doit viser les principals, pas la racine DAV. InfCloud accole le nom d'utilisateur au href. Avec /dav.php/ il demandait /dav.php/Julien/ → 404. La documentation du fichier le précise : « globalNetworkCheckSettings : principal URL WITHOUT the USER/ part ». Chez Baïkal, la bonne valeur est donc :
Après toute modification de config.js, rechargement forcé du navigateur (Ctrl+Maj+R) : le fichier est mis en cache.
7.2 Digest → Basic : le blocage d'authentification
Baïkal était configuré en dav_auth_type: Digest et annonçait WWW-Authenticate: Digest realm="BaikalDAV".
InfCloud ne sait faire que du Basic. Ses requêtes partaient donc en 401 en boucle, alors que DAVx5 — qui gère Digest — fonctionnait parfaitement avec le même compte. Le symptôme prêtait à confusion : les identifiants étaient bons.
Diagnostic par les logs nginx, où le champ utilisateur est renseigné mais la réponse reste 401 :
Correctif : dav_auth_type: Basic dans /var/www/baikal/config/baikal.yaml, puis docker restart baikal.
Ce n'est pas un pis-aller. Digest repose sur MD5, est considéré comme obsolète, et impose au serveur de stocker une empreinte exploitable ; Basic sur TLS est aujourd'hui la recommandation, et tout est en HTTPS ici, y compris les redirections de découverte (§6.3).
Les mots de passe restent valides : Baïkal calcule la même empreinte md5(username:auth_realm:password) dans les deux modes (Baikal\Core\PDOBasicAuth::validateUserPass), avec auth_realm: BaikalDAV conservé en interne — même si le serveur annonce désormais realm="sabre/dav" côté client. Rien à redéfinir, DAVx5 bascule tout seul.
Sauvegarde : /var/www/baikal/config/baikal.yaml.bak-260803-digest (dans le container).
8. Sauvegarde
Baïkal a été ajouté à /opt/backups/vps_backup.sh le 3 août 2026 (cron quotidien à 2h UTC).
- Dossier kDrive de destination : 1467771 (
SYNC-pour_VPS/backups/baikal) - Le bilan de fin de log passe de 5 à 6 services (le total est calculé dynamiquement, pas codé en dur)
- Sauvegarde de l'ancien script :
/opt/backups/vps_backup.sh.bak-260803
La fonction archive deux volumes, via un tar intermédiaire puis 7z :
Specific/— la basedb.sqlite(contacts et calendriers)config/—baikal.yaml(paramètres et empreinte du mot de passe admin)
Sans le second, la restauration est incomplète. Testée le 3 août : upload HTTP 200, aucun résidu temporaire.
InfCloud n'a pas besoin d'être sauvegardé (fichiers statiques réinstallables), mais son config.js est versionné dans Jux-scripts/Baikal-Contacts/.
9. Vérifications effectuées le 3 août 2026
| Test | Résultat |
|---|---|
GET /dav.php/ |
HTTP 401 — authentification bien exigée |
PROPFIND /dav.php/ |
HTTP 401 et non 405 — nginx laisse passer les méthodes DAV étendues |
.well-known/carddav et caldav |
301 vers HTTPS |
| Installation Baïkal | Utilisateur Julien, carnet et calendrier default créés |
| Login Roundcube | IMAP Infomaniak accepté |
| Découverte CardDAV Roundcube | Carnet résolu à .../dav.php/addressbooks/Julien/default/ |
| Écriture d'un contact | vCard 3.0 écrite par RCMCardDAV v5.1.3, relue dans Baïkal, accents UTF-8 préservés |
| Synchronisation DAVx5 | 5 rendez-vous d'août écrits dans Baïkal par DAVx5/4.5.18-ose, synctoken du calendrier à 6 |
| InfCloud | 32 ressources servies en 200 ; PROPFIND sur principals, addressbooks et calendars tous en 207 |
| Dotfiles InfCloud | .htaccess bloqué en 403 |
| Sauvegarde Baïkal | Archive uploadée sur kDrive, HTTP 200 |
Note : dans la table carddav_addressbooks de Roundcube, une seconde ligne nommée %N avec une URL vide n'est pas une erreur — c'est le template de rcmcarddav v5, qui porte les réglages appliqués aux carnets découverts.
10. Reste à faire
10.1 vdirsyncer — agrégation Google et Infomaniak
Seule brique du plan initial encore absente. Objectif : faire remonter dans Baïkal les contacts existants sur Google et Infomaniak, pour que Baïkal devienne le point central réel et pas un silo de plus.
- Google Contacts : CardDAV via
https://www.google.com/carddav/v1/principals/EMAIL/— nécessite un mot de passe d'application (compte en 2FA) - Infomaniak : CardDAV natif, identifiants standards du compte mail
- vdirsyncer : outil CLI léger, une paire de synchronisation par source, exécuté par cron sur le VPS
Décision à prendre avant déploiement :
- one-way (Google/Infomaniak → Baïkal, lecture seule) : sans risque, mais les modifications faites dans Baïkal ne remontent pas vers Google
- two-way : tout reste aligné, mais un conflit ou une erreur de configuration peut propager des suppressions dans les deux sens
Recommandation : démarrer en one-way le temps de vérifier que l'import est fidèle, puis basculer si besoin.
10.2 Nettoyage
Quatre vhosts morts traînent dans /etc/nginx/sites-enabled/, vestiges des tentatives précédentes : radicale, agendav, infcloud, nextcloud. Le dernier cible le port 8082, ce qui a contraint le choix du port de Roundcube. Attention : le vhost nommé radicale.juxjux.ovh sert en réalité Readeck — ne pas le supprimer sans vérifier.
11. Checklist sécurité
- Aucun mot de passe
CHANGE_ME_...en production — la bascule vers SQLite a supprimé les identifiants MariaDB du compose - HTTPS forcé sur les deux vhosts (redirection 80 vers 443)
- Certificats Let's Encrypt valides, renouvellement automatique certbot en place
- Découverte DAV forcée en HTTPS (§6.3)
- Authentification Basic uniquement sur TLS (§7.2)
- Les deux containers n'écoutent que sur
127.0.0.1— seul nginx les expose - Module PHP
auth/d'InfCloud supprimé, dotfiles bloqués - Sauvegarde quotidienne des volumes Baïkal vers kDrive
- Mot de passe d'application dédié pour l'accès CardDAV Google — à faire au moment de vdirsyncer
12. Commandes utiles
13. Choix d'architecture — pourquoi ces outils
Radicale écarté : stockage fichier brut, pas de vraie gestion. La stack Radicale/InfCloud initialement envisagée n'a pas été mise en place — seul InfCloud a finalement été retenu, en client de Baïkal.
Nextcloud écarté : trop lourd pour l'usage visé (PHP-FPM + base + Redis + écosystème complet), alors que le VPS fait déjà tourner PostGIS, Metabase, BookStack, FreshRSS.
Baïkal retenu : léger, CardDAV et CalDAV natifs, interface d'administration suffisante. Développé par des bénévoles sous l'organisation sabre-io (mainteneurs de SabreDAV, la librairie sous-jacente) — pas de société commerciale derrière, mais communauté active et outil robuste pour un usage personnel sur la durée.
Roundcube retenu comme interface de saisie des contacts, Baïkal n'ayant pas de vraie UI de consultation : léger, traduit en français, projet actif (version 1.7.2 en août 2026). A rejoint la famille Nextcloud en 2023 tout en restant indépendant, sous licence GPL. Réserves constatées à l'usage : il impose une authentification IMAP (§6.4) et ne couvre pas l'agenda (§6.2).
InfCloud retenu pour l'agenda web : sans backend, donc sans coût mémoire ni surface d'attaque serveur, et couvre agenda et contacts. Ancien mais fonctionnel.
SOGo envisagé et écarté : suite complète et maintenue, mais environ 500 Mo de RAM et une base PostgreSQL sur un VPS déjà en pression mémoire, et il aurait fait doublon avec Roundcube.
Thunderbird containerisé (VNC/noVNC) envisagé puis écarté : fonctionnel mais plus lourd et moins adapté au multi-accès qu'un vrai webmail.
2-MCP CalDAV pour Claude Code — un agenda Baïkal partagé par les deux PC
MCP CalDAV pour Claude Code
État au 16 septembre 2026 — opérationnel sur les deux PC. Cycle complet validé le 15/09 sur NEXTE : création de calendrier, écriture d'un événement, relecture, visibilité sur le téléphone (DAVx5 → Fossify) et dans InfCloud, suppression. Installé sur le PC perso le 16/09 (§7.8), avec un écart de méthode : venv Python sur D: au lieu de uv.
Prérequis : l'infrastructure Baïkal décrite en page 1 de ce chapitre (Baïkal en Docker sur juxjux.ovh, Basic auth sur TLS, utilisateur DAV Julien).
1. Décision d'architecture
Un seul Baïkal, sur juxjux.ovh, partagé par les deux Claude Code (PC perso et PC NEXTE). C'est la seule exception assumée à l'étanchéité entre l'univers Jux et l'univers Alteris/NEXTE.
Pourquoi ne pas dupliquer Baïkal sur le VPS Alteris :
- Baïkal est posé comme source de vérité unique (page 1). Deux Baïkal = deux vérités, et le problème d'origine (agendas dispersés) se recrée.
- Deux flux dans DAVx5 fonctionnent, mais les deux calendriers ne se voient pas : les conflits d'horaire entre perso et pro deviennent invisibles.
- Doublement de la maintenance (certificat, sauvegarde, compose, InfCloud) sur un VPS déjà chargé.
Pourquoi l'exception est acceptable : l'étanchéité protège les fichiers et données de mission. Un agenda n'est pas une donnée de mission, c'est un service de la personne, comme le téléphone. Le PC NEXTE devient un client CalDAV de plus, au même titre que DAVx5 ; aucune donnée Jux n'entre sur NEXTE, aucune donnée Alteris ne sort vers juxjux. Seul l'identifiant Baïkal traverse — il est dans le Vault 2603.
2. Modèle : un utilisateur, deux calendriers
| Calendrier | URL | Usage | Couleur | PC qui y écrit par défaut |
|---|---|---|---|---|
Default calendar |
https://baikal.juxjux.ovh/dav.php/calendars/Julien/default/ |
Personnel (Julien) | (installeur) | PC perso |
NEXTE |
https://baikal.juxjux.ovh/dav.php/calendars/Julien/nexte/ |
Professionnel NEXTE (pas Alteris) | #1E88E5 bleu |
PC NEXTE |
Même compte Julien pour les deux, donc les deux calendriers apparaissent ensemble dans DAVx5, Fossify et InfCloud, chacun avec sa couleur.
Règle de lecture/écriture du MCP : chaque PC écrit dans son calendrier par défaut (CALDAV_DEFAULT_CALENDAR) mais lit les deux — sinon Claude placerait un rendez-vous pro sur un créneau perso. L'écriture dans l'autre calendrier reste possible en le nommant explicitement (calendar="Default calendar").
Le calendrier NEXTE a été créé le 15/09/2026 par le MCP lui-même (MKCALENDAR, outil create_calendar). Dans DAVx5, il faut ensuite rafraîchir la liste des calendriers du compte et le cocher pour qu'il se synchronise. Dans InfCloud, il faut l'inscrire dans les réglages stockés sur le serveur — voir §5.7, c'est désormais fait automatiquement par create_calendar.
3. Le serveur MCP
Aucun serveur MCP CalDAV communautaire mûr : serveur maison, ~400 lignes de Python.
- Fichier :
C:\Users\nexte\Nexte Claude\MCP CalDAV\260915-mcp_caldav_server.py - Script autonome PEP 723 : dépendances déclarées en tête de fichier, résolues par
uv run --script. Aucune installation Python système nécessaire (il n'y en a pas sur NEXTE, seulement celui de QGIS) —uvest déjà là pour le MCP QGIS. - Dépendances :
mcp>=1.2,<2(FastMCP),caldav(3.3 au 15/09),icalendar,python-dateutil. - Configuration par variables d'environnement, passées par
claude mcp add -e:
| Variable | Valeur NEXTE | Rôle |
|---|---|---|
CALDAV_URL |
https://baikal.juxjux.ovh/dav.php/ |
racine DAV de Baïkal |
CALDAV_USER |
Julien |
utilisateur DAV — J majuscule, la casse compte |
CALDAV_PASSWORD |
Vault 2603 | mot de passe de l'utilisateur DAV (pas celui de l'admin Baïkal) |
CALDAV_DEFAULT_CALENDAR |
NEXTE |
calendrier d'écriture par défaut (default sur le PC perso) |
CALDAV_TZ |
Europe/Paris (défaut) |
fuseau des heures saisies sans fuseau, et fuseau posé sur les calendriers créés |
Le script ne journalise jamais le mot de passe ; s'il manque, il le signale sur stderr sans en dire plus.
Outils exposés
| Outil | Rôle |
|---|---|
list_calendars |
calendriers du compte, avec marquage du calendrier par défaut |
create_calendar(name, cal_id, color) |
MKCALENDAR, puis pose calendar-color / calendar-order / calendar-timezone (palette par défaut) et inscrit le calendrier dans les réglages InfCloud (§5.7) ; refuse si le nom existe déjà |
infcloud_sync_collections |
inscrit dans les réglages InfCloud tous les calendriers du compte qui n'y figurent pas — pour un calendrier créé ailleurs (DAVx5, admin Baïkal) |
list_events(start, end, calendar, all_calendars) |
événements sur une période (défaut : aujourd'hui → +14 j), tous calendriers sauf si calendar est nommé ; occurrences récurrentes développées |
search_events(text, calendar) |
recherche plein texte (résumé, lieu, description), fenêtre −6/+12 mois |
get_event(uid, calendar) |
détail + iCalendar brut |
create_event(summary, start, end, calendar, all_day, location, description, rrule) |
end absent → +1 h (ou +1 jour en journée entière) ; heures sans fuseau = Europe/Paris ; dates ISO ou jj/mm/aaaa, heures 16:00 ou 16h00 |
update_event(uid, …) |
ne modifie que les champs fournis ; incrémente SEQUENCE et LAST-MODIFIED |
delete_event(uid, calendar) |
suppression définitive — uniquement après confirmation explicite de Julien |
free_slots(start, end, duration_minutes, day_start, day_end, weekdays_only) |
créneaux libres tous calendriers confondus, dans la plage horaire quotidienne indiquée |
4. Enregistrement dans Claude Code (PC NEXTE)
Portée utilisateur (-s user), comme le MCP QGIS. À lancer dans un terminal, en remplaçant <MDP> par le mot de passe de l'utilisateur DAV Julien (Vault 2603) :
Puis :
qui doit afficher caldav: … - ✔ Connected.
Pour refaire l'enregistrement (mot de passe changé, mauvais calendrier par défaut) : claude mcp remove -s user caldav puis la commande add.
Où va le secret : claude mcp add l'écrit en clair dans C:\Users\nexte\.claude.json — même niveau de protection que le MCP postgres. Le mot de passe reste archivé dans le Vault 2603 ; il n'a pas à figurer dans une conversation ni dans BookStack.
5. Pièges rencontrés le 15/09/2026
5.1 Les chevrons du gabarit ne font pas partie du mot de passe
Une commande donnée avec CALDAV_PASSWORD=<MDP> a été lancée telle quelle, puis avec <vrai-mdp> chevrons compris. Dans les deux cas Baïkal répond 401. Vérification sans afficher le secret : longueur de la valeur stockée, premier caractère, présence des caractères spéciaux attendus.
5.2 Mot de passe admin ≠ mot de passe utilisateur DAV
Baïkal a deux comptes distincts : l'admin (/admin/) et l'utilisateur DAV (Julien). Le premier essai a été fait avec le mot de passe admin → 401 systématique, avec Julien comme avec julien, alors que le serveur annonçait bien Basic realm="sabre/dav". Le bon mot de passe est celui saisi dans DAVx5 et InfCloud. Test de référence qui distingue « serveur joignable » de « identifiants acceptés » :
401 sans identifiants = normal ; 401 avec identifiants = mauvais couple utilisateur/mot de passe.
5.3 Caractères spéciaux dans PowerShell
Le mot de passe contient % et @ : passer la variable entre apostrophes simples ('CALDAV_PASSWORD=…'), pas entre guillemets. Vérifié : la valeur stockée est intacte.
5.4 SDK mcp 2.x
La 2.x du SDK Python a renommé FastMCP en MCPServer et cassé l'import mcp.server.fastmcp. Le script épingle mcp<2. À revoir si on migre un jour vers la 2.x.
5.5 Reprise de session ≠ nouvelle session
Un serveur MCP est chargé au démarrage de la session. Après claude mcp add, une session reprise dans l'app ne voit pas le nouveau serveur alors que claude mcp list le donne connecté. Il faut une nouvelle session. Entre-temps, le script peut être piloté directement (import du module + lecture de l'env dans .claude.json) — c'est ainsi que le calendrier NEXTE et l'événement test ont été créés.
5.6 Rappels de la page 1 qui s'appliquent au MCP
- Racine DAV
/dav.php/, pas/caldav.php/. Julienen majuscule dans les URLs.- Basic sur TLS : la bibliothèque
caldavgère la négociation 401 → Basic toute seule.
5.7 Un calendrier créé hors InfCloud n'apparaît jamais dans InfCloud
Symptôme : NEXTE visible dans DAVx5 et Fossify, mais absent d'InfCloud, même après déconnexion, Ctrl+Maj+R et reconnexion.
Fausse piste écartée en premier : le calendrier créé par MKCALENDAR nu n'avait ni calendar-color, ni calendar-order, ni calendar-timezone (404 sur ces trois propriétés, alors que le calendrier de l'installeur les a). Elles ont été posées par PROPPATCH — nécessaire pour l'homogénéité, mais insuffisant.
Cause réelle : config.js d'InfCloud a settingsAccount: true. InfCloud stocke alors ses réglages sur le serveur, dans la propriété {http://inf-it.com/ns/dav/}settings du principal (/dav.php/principals/Julien/), sous forme de JSON. Ce JSON contient loadedcalendarcollections et activecalendarcollections (idem pour les tâches), figés à la première session — donc réduits à default/. InfCloud ne charge que les collections de cette liste ; un nouveau calendrier n'y entre jamais tout seul.
Diagnostic (avec identifiants) :
puis lire loadedcalendarcollections dans le JSON retourné.
Correctif : relire le JSON, ajouter l'URL du calendrier aux quatre listes (loadedcalendarcollections, activecalendarcollections, loadedtodocollections, activetodocollections), réécrire la propriété par PROPPATCH, puis déconnexion/reconnexion InfCloud (les réglages sont lus au login). Sauvegarde du JSON d'origine : MCP CalDAV\260915-infcloud_settings_backup.json.
Depuis le 15/09, create_calendar fait tout cela d'emblée, et infcloud_sync_collections rattrape un calendrier créé ailleurs. Le config.js lui-même n'est pas en cause (additionalResources: [], pas de liste figée).
5.8 Le parseur de dates lisait 2026-10-02 comme le 10 février
dateutil.parser.parse(..., dayfirst=True) — voulu pour accepter 02/10/2026 — inverse aussi jour et mois d'une date ISO quand les deux valeurs sont ≤ 12 : 2026-10-02 devient le 10 février, 2026-10-09 le 10 septembre, 2026-10-12 le 10 décembre. 2026-10-13 passe, ce qui rend le bug intermittent et sournois.
Constaté lors du premier blocage de créneaux (6 dates, 3 fausses). Correctif dans _parse_dt : datetime.fromisoformat d'abord, dateutil en jour-premier seulement en repli ; 16h00 / 14h sont normalisés en 16:00 / 14:00. Les trois événements erronés ont été supprimés et recréés.
Règle : toujours relire les dates renvoyées par create_event (le résultat affiche start/end en ISO avec fuseau) avant d'annoncer qu'un créneau est bloqué.
6. Vérifications effectuées le 15/09/2026
| Test | Résultat |
|---|---|
PROPFIND /dav.php/ sans identifiants |
401, Basic realm="sabre/dav" |
list_calendars |
Default calendar visible |
create_calendar("NEXTE", cal_id="nexte") |
créé, URL …/calendars/Julien/nexte/ |
list_events 14 jours, tous calendriers |
1 événement perso lu |
create_event dans NEXTE (16/09 10:00–10:30, Europe/Paris) |
écrit, relu par get_event avec fuseau +02:00 |
| Synchronisation DAVx5 → Fossify | événement visible sur le téléphone après avoir coché NEXTE |
delete_event par UID |
supprimé, calendrier NEXTE vide |
| PROPPATCH couleur/ordre/fuseau sur NEXTE | 200, relu |
| Inscription de NEXTE dans les réglages InfCloud | 200 ; NEXTE visible dans InfCloud après reconnexion |
infcloud_sync_collections après correction |
added: [] — idempotent |
create_calendar("NEXTE") une seconde fois |
refusé, « existe déjà » |
claude mcp list |
caldav … ✔ Connected |
| 6 créneaux « Tourrettes PLU (validation en attente) » dans NEXTE (02→16/10) | créés, relus aux bonnes dates après correctif §5.8 ; aucun conflit perso |
7. Installation sur le PC perso
Objectif : que le Claude Code du PC perso lise les deux calendriers et écrive dans l'un ou l'autre, avec le même serveur Baïkal et le même compte Julien. Aucune configuration côté Baïkal : le compte voit déjà les deux calendriers. Tout se passe sur le PC perso.
7.1 Copier le script
Source (PC NEXTE) : C:\Users\nexte\Nexte Claude\MCP CalDAV\260915-mcp_caldav_server.py — prendre la version courante, elle contient les correctifs du 15/09 (§5.7 InfCloud, §5.8 dates).
Destination recommandée : Syncthing/Jux_univers/Jux-scripts/Baikal-Contacts/260915-mcp_caldav_server.py, à côté d'infcloud-config.js — c'est le dossier où sont déjà versionnés les fichiers Baïkal (page 1, §4). Le script est autonome : un seul fichier, pas de dépendance à copier.
Point d'attention : le fichier ne doit pas être dans un coffre Cryptomator — un MCP se lance au démarrage de Claude Code, coffre fermé ou non (règle « aucun script ne dépend d'un coffre déverrouillé »).
7.2 Vérifier uv
Si absent :
puis rouvrir le terminal. uv télécharge lui-même un Python et les dépendances au premier lancement (~50 paquets, une dizaine de secondes) ; aucune installation Python système n'est nécessaire.
Repérer le chemin complet de uv.exe ((Get-Command uv).Source en PowerShell) — l'utiliser en absolu dans la commande d'enregistrement, comme sur NEXTE, évite les surprises de PATH au lancement par Claude Code.
Fait autrement sur le PC perso (16/09) : pas d'uv, un venv sur D: — voir §7.8 pour la raison.
7.3 Choisir le calendrier par défaut
| Choix | CALDAV_DEFAULT_CALENDAR |
Comportement |
|---|---|---|
| Recommandé | default |
le PC perso écrit dans le calendrier perso sauf mention contraire ; symétrique de NEXTE sur le PC pro |
| Variante | NEXTE |
le PC perso se comporte exactement comme le PC NEXTE |
Dans les deux cas, la lecture porte sur les deux calendriers, et l'écriture dans l'autre calendrier reste possible en le nommant (« ajoute ça dans NEXTE »).
7.4 Enregistrer le MCP
Dans un terminal (PowerShell), en remplaçant <MDP> par le mot de passe de l'utilisateur DAV Julien (Vault 2603) et <chemin> par le dossier de destination :
Rappels des pièges §5 :
- mot de passe de l'utilisateur DAV
Julien, pas celui de l'admin Baïkal (§5.2) ; - sans chevrons autour de la valeur (§5.1) ;
- apostrophes simples autour de
CALDAV_PASSWORD=…à cause des%et@(§5.3) ; Julienavec un J majuscule (§5.6).
Vérifier :
→ caldav: … - ✔ Connected. Si Failed to connect : lancer à la main uv run --script "<chemin>\260915-mcp_caldav_server.py" dans le terminal pour voir l'erreur (dépendance, chemin, Python).
Pour corriger une valeur : claude mcp remove -s user caldav puis relancer la commande add.
7.5 Nouvelle session et test
- Ouvrir une nouvelle session Claude Code — pas une reprise (§5.5).
- Demander
list_calendars: les deux calendriers doivent apparaître,Default calendarmarqué par défaut. - Écrire un événement test dans NEXTE (« crée un test demain 10 h dans NEXTE »), le vérifier sur le téléphone ou dans InfCloud, puis le faire supprimer.
7.6 Ce qui n'est PAS à faire
- Rien dans l'admin Baïkal : pas de second utilisateur, pas de partage de calendrier.
- Rien dans DAVx5 ni InfCloud : ils voient déjà les deux calendriers.
- Ne pas dupliquer Baïkal sur un autre VPS (§1).
- Ne pas copier le mot de passe dans BookStack, dans un script ou dans une conversation : il vit dans le Vault 2603 et dans
.claude.jsondu PC.
7.7 Entretien à deux PC
Le script existe en deux exemplaires (NEXTE et perso). Toute correction faite sur l'un doit être recopiée sur l'autre — c'est le prix d'un serveur maison hors dépôt. Convention : le fichier daté reste 260915-… tant que l'API des outils ne change pas ; une refonte produit un nouveau fichier daté et une mise à jour des deux enregistrements MCP.
7.8 Installation effective sur le PC perso — 16/09/2026
Faite depuis une session Claude Code du poste julie, après dépôt du script par Julien. Vérifiée en session : list_calendars rend Default calendar (par défaut) et NEXTE, list_tasks lit les deux tâches ouvertes de NEXTE.
| Élément | Valeur sur le PC perso |
|---|---|
| Script | D:\Syncthing\Jux_univers\Jux-scripts\Baikal-Contacts\260915-mcp_caldav_server.py — dans l'arbre Syncthing, hors coffre |
| Interpréteur | D:\Python\venv-caldav-mcp\Scripts\python.exe (venv créé par py -3.12 -m venv, puis pip install "mcp>=1.2,<2" "caldav>=1.4" "icalendar>=6" python-dateutil — mcp 1.30, caldav 3.3, icalendar 7.3) |
| Enregistrement | à la main dans C:\Users\julie\.claude.json, clé mcpServers.caldav (portée utilisateur, type: stdio, command = python du venv en absolu, args = chemin du script, env = les 4 variables). Sauvegarde : .claude.json.bak-260916-caldav |
CALDAV_DEFAULT_CALENDAR |
default — le PC perso écrit dans le perso, lit les deux |
Pourquoi pas uv ni pip dans le Python système. Sur ce poste, Claude Code tourne dans un conteneur MSIX : tout ce qu'une session écrit sous %LOCALAPPDATA% ou %APPDATA% est redirigé vers …\Packages\Claude_pzs8sxrjxfjjc\LocalCache\, invisible du reste du système (page 282). Or le Python 3.12 du poste est installé sous %LOCALAPPDATA%\Programs\Python, et uv met son cache dans %LOCALAPPDATA%\uv : un pip install ou un premier uv run lancés depuis la session auraient produit une installation fantôme. Un venv sur D: est hors redirection, et cohérent avec les six autres MCP Python du poste (enregistrés avec un python.exe en chemin absolu, page 282). uv reste valable sur NEXTE, où Claude Code n'est pas dans un conteneur.
Pas de claude mcp add : le CLI claude n'est pas dans le PATH de ce poste (application de bureau). L'entrée est écrite directement dans .claude.json, comme pour Mealie le 13/09. Le mot de passe y est en clair, au même titre que sur NEXTE.
Correctif apporté au script (à recopier sur NEXTE, §7.7). list_calendars marquait le calendrier par défaut en comparant CALDAV_DEFAULT_CALENDAR au seul nom affiché. Avec default, le nom affiché est Default calendar : aucun calendrier n'était marqué (default: false partout), alors que l'écriture fonctionnait — _find_calendar, lui, se rabat sur le dernier segment d'URL. Invisible sur NEXTE parce que nom et segment coïncident (NEXTE/nexte). list_calendars applique désormais la même règle : nom affiché ou segment d'URL, insensible à la casse. Une ligne changée, même fichier daté 260915-… (l'API des outils ne bouge pas).
Chargement en session : après l'écriture dans .claude.json, l'application de bureau a dû être fermée et rouverte — une session reprise ne voit pas le nouveau serveur (§5.5). Au retour, les 17 outils mcp__caldav__* étaient présents et list_calendars a répondu par le MCP.
8. Règles d'usage pour Claude
- Lire les deux calendriers avant de proposer un créneau (
list_eventssanscalendar, oufree_slots). - Écrire dans le calendrier par défaut du PC ; n'écrire dans l'autre que sur demande explicite.
- Ne jamais appeler
delete_eventsans confirmation explicite de Julien dans la conversation. - Ne jamais afficher, journaliser ni répéter
CALDAV_PASSWORD; diagnostiquer sur les longueurs et les codes HTTP. - Si un calendrier manque dans InfCloud :
infcloud_sync_collections, puis déconnexion/reconnexion — avant toute autre hypothèse. - Relire les dates renvoyées par
create_eventavant d'annoncer un créneau bloqué (§5.8).
3 - gestionnaires de tâches - Tasks.org sur Baikal - Infcloud et DAVx5
Gestionnaire de tâches — Tasks.org sur Baïkal, InfCloud et DAVx5
État au 15 septembre 2026 — opérationnel de bout en bout. Cycle validé dans les deux sens : une tâche créée par le MCP est apparue dans Tasks.org, y a été cochée, et le MCP l'a relue COMPLETED ; une tâche saisie dans Tasks.org a été lue par le MCP ; les deux sont visibles dans InfCloud (§6).
Décision : on valorise au maximum l'existant — Baïkal, InfCloud, DAVx5. Les tâches vivent dans les mêmes collections que les événements, aucun composant serveur n'est ajouté. Seul ajout : Tasks.org sur le téléphone, comme écran des tâches que DAVx5 lui dépose.
1. Ce que l'existant sait déjà faire
| Composant | Tâches (VTODO) |
Constat |
|---|---|---|
| Baïkal | oui, nativement | les deux collections default/ et nexte/ annoncent VEVENT et VTODO (vérifié par PROPFIND supported-calendar-component-set) |
| InfCloud | oui, onglet Tâches | les réglages serveur contiennent déjà loadedtodocollections / activetodocollections avec les deux calendriers (page 2, §5.7) |
| DAVx5 | oui, synchronise les VTODO |
mais Android n'a pas de fournisseur de tâches natif : DAVx5 ne peut les déposer que dans une appli tierce — Tasks.org, jtx Board ou OpenTasks. Sans elle, l'option « Tâches » n'apparaît même pas dans le compte |
| MCP CalDAV | oui depuis le 15/09 | 7 outils *_task ajoutés au script (§3) |
2. Choix du client Android
Fossify Calendar — écarté
La fonction « Tâche » de Fossify est locale : c'est un événement d'un type particulier dans sa propre base, jamais écrit en VTODO, jamais synchronisé (héritage de Simple Calendar, dont la synchro CalDAV ne couvre que les événements). Une tâche saisie là n'atteint ni Baïkal, ni InfCloud, ni le MCP — et on croirait qu'elle est synchronisée. Même famille de piège que le plugin calendar de Roundcube (page 1, §6.2).
Fossify reste le client événements ; il n'affiche pas les tâches — chaque appli dans son rôle.
Tasks.org (F-Droid, 15.x) — retenu
| Avantages | Inconvénients | |
|---|---|---|
| Synchro | VTODO complet : échéance, priorité, récurrence, rappels, sous-tâches, étiquettes ; CalDAV direct ou via DAVx5 |
deux chemins possibles → en choisir un seul (§4) |
| Fonctionnel | vraie appli de tâches : listes = collections CalDAV, filtres, widgets, tri par échéance | plus riche que nécessaire pour un usage simple — d'où le §5 |
| Projet | actif, GPL, version F-Droid sans services Google | pas de synchro Google Tasks sur F-Droid — sans importance ici |
| Cohérence | les tâches apparaissent dans InfCloud et sont lisibles/écrites par le MCP | — |
Alternative connue : jtx Board (bitfire, même éditeur que DAVx5) — tâches + journaux (VJOURNAL), intégration DAVx5 très propre, mais moins de fonctions de gestion de tâches. À garder en tête si les notes datées deviennent un besoin ; Baïkal gère VJOURNAL.
Branchement : via DAVx5, pas en CalDAV direct
Tasks.org sait parler CalDAV tout seul, mais on le branche comme fournisseur de tâches de DAVx5 : un seul compte Baïkal sur le téléphone, une seule planification de synchro, un seul mot de passe stocké. DAVx5 détecte Tasks.org à l'installation et fait apparaître « Tâches » dans le compte. Rien à saisir dans Tasks.org : ni compte, ni URL, ni mot de passe.
Emplacement des tâches : dans les calendriers existants
Pas de collection dédiée aux tâches. La séparation perso/pro est portée par la collection (Default calendar / NEXTE), InfCloud est déjà configuré ainsi, et Tasks.org montre deux listes portant ces noms. L'échéance d'une tâche reste visible à côté des rendez-vous dans InfCloud.
3. Outils tâches du MCP CalDAV
Ajoutés le 15/09/2026 au script 260915-mcp_caldav_server.py (page 2, §3). Même mécanique que les événements : écriture dans le calendrier par défaut du PC, lecture des deux.
| Outil | Rôle |
|---|---|
list_tasks(calendar, include_completed, due_before) |
tâches ouvertes de tous les calendriers (ou d'un seul) ; include_completed ajoute terminées/annulées ; due_before filtre sur l'échéance ; tri échéance puis priorité |
get_task(uid, calendar) |
détail + iCalendar brut |
create_task(summary, due, calendar, priority, description, start, categories, rrule) |
due = date seule (journée) ou date-heure (Europe/Paris) ; priority 0 aucune / 1 haute / 5 moyenne / 9 basse (convention Tasks.org) ; rrule ex. FREQ=WEEKLY;BYDAY=MO |
update_task(uid, …) |
ne modifie que les champs fournis ; due="" retire l'échéance ; status ∈ NEEDS-ACTION / IN-PROCESS / COMPLETED / CANCELLED ; percent_complete |
complete_task(uid, calendar) |
STATUS COMPLETED, COMPLETED = maintenant, 100 % ; une tâche récurrente est close pour l'occurrence courante et la suivante est générée |
reopen_task(uid, calendar) |
retour à NEEDS-ACTION, COMPLETED et pourcentage retirés |
delete_task(uid, calendar) |
suppression définitive — uniquement après confirmation explicite de Julien |
Champs renvoyés : calendar, uid, summary, status, due, start, priority, percent_complete, completed, description, categories, rrule, last_modified.
4. Configuration du téléphone — faite le 15/09
- Tasks.org installé depuis F-Droid.
- DAVx5 → compte Baïkal : calendriers et tâches activés pour
NEXTEetDefault calendar(DAVx5 récent les montre dans le même onglet, avec une icône différente — il faut cocher les deux entrées de chaque collection). - Tasks.org → Paramètres → Synchronisation : le compte « DAVx5 » est actif. Aucun compte CalDAV ajouté dans Tasks.org lui-même — ce serait le second chemin de synchro qu'on a écarté.
5. Utiliser Tasks.org au quotidien
Tasks.org intimide parce qu'il s'ouvre sur un filtre (« Ma journée » ou « Boîte de réception »), pas sur les listes. Une tâche due demain n'y apparaît pas ; il faut aller la chercher dans sa liste ou dans « Toutes les tâches ». Le vocabulaire à connaître :
5.1 Listes et filtres
| Notion | Ce que c'est | Où |
|---|---|---|
| Liste | une collection CalDAV — ici NEXTE et Default calendar |
menu ≡ (icône en bas à gauche, ou balayage depuis le bord gauche), bloc au nom du compte DAVx5 |
| Filtre | une vue transversale sur toutes les listes | menu ≡, en haut |
| « Toutes les tâches » | l'ensemble des tâches ouvertes, perso + pro, dans une seule vue — la vue du quotidien | filtre |
| « Ma journée » | échues aujourd'hui ou en retard, tous calendriers — l'équivalent de list_tasks(due_before=aujourd'hui) |
filtre |
| « Récemment modifiées » | ce qui a bougé, utile après une synchro | filtre |
| Filtre personnalisé | composition de critères : listes, étiquettes, priorité, échéance (« cette semaine »), état | menu ≡ → Filtres → + ; devient une entrée du menu |
| Widget | un widget d'écran d'accueil par liste ou par filtre — « Toutes les tâches » en widget montre donc les deux calendriers | appui long sur l'écran d'accueil |
Pour distinguer perso et pro dans la vue commune : Paramètres → Apparence → regrouper par liste (ou afficher le nom de la liste sous chaque tâche), et une couleur par liste (appui long sur la liste → couleur), affichée en pastille. La couleur est locale à Tasks.org ; celle d'InfCloud est la calendar-color de la collection (page 2, §5.7).
5.2 Le déclenchement — quand une tâche se rappelle à toi
C'est la partie à comprendre avant de s'en remettre à l'appli :
| Situation | Notification ? |
|---|---|
| Tâche sans échéance (ex. « reprendre des fruits ») | jamais. Elle n'existe que dans les listes et « Toutes les tâches ». C'est un pense-bête, pas un rappel |
Échéance date seule (DUE;VALUE=DATE) |
à l'heure d'échéance par défaut de Tasks.org (Paramètres → Échéances / Notifications → « heure par défaut », à régler — souvent 18 h d'origine) |
| Échéance date + heure | à cette heure, si le rappel « à l'échéance » est actif dans les réglages de la tâche (il l'est par défaut pour les tâches créées dans Tasks.org ; pour une tâche créée par le MCP, voir §7.3) |
| Rappels supplémentaires (dans la tâche → Rappels) | « au début », « à l'échéance », « aléatoire », « personnalisé » (x heures/jours avant ou après), et « quand dépassée » qui relance périodiquement tant que la tâche n'est pas cochée |
| Tâche cochée | plus aucune notification, elle passe dans les terminées (masquées ou non selon Paramètres → Apparence → « afficher les terminées ») |
Une notification peut être reportée (snooze) depuis la notification elle-même. Les rappels sont écrits en VALARM dans le VTODO et donc synchronisés vers Baïkal ; InfCloud les affiche, le MCP les lit dans get_task (iCalendar brut) mais ne les crée pas encore.
5.3 Ce que porte une tâche, et sa correspondance CalDAV
| Dans Tasks.org | Dans le VTODO |
Vu par le MCP |
|---|---|---|
| Titre | SUMMARY |
summary |
| Échéance (date, ou date + heure) | DUE |
due |
| Date de début (« masquer jusqu'à ») | DTSTART |
start |
| Priorité (aucune / basse / moyenne / haute) | PRIORITY 0 / 9 / 5 / 1 |
priority |
| Notes | DESCRIPTION |
description |
| Étiquettes (tags) | CATEGORIES |
categories |
| Répétition | RRULE |
rrule |
| Sous-tâches | RELATED-TO (tâche parente) |
non exposé — à ajouter si l'usage vient |
| Rappels | VALARM |
iCalendar brut seulement |
| Coché | STATUS:COMPLETED + COMPLETED + PERCENT-COMPLETE:100 |
status, completed, percent_complete |
| Pièces jointes, lieux, minuteur | locaux, jamais synchronisés | — |
Piège constaté : une tâche saisie dans Tasks.org sans toucher à la priorité arrive avec PRIORITY:9 (basse), pas 0. Ne pas lire « basse » comme un choix de Julien.
5.4 Trois gestes utiles
- Changer une tâche de liste (perso ↔ pro) : ouvrir la tâche → champ Liste. Tasks.org la déplace côté serveur (suppression dans une collection, création dans l'autre : l'UID change).
- Saisie rapide : le bouton + saisit dans la liste ou le filtre courant — depuis « Toutes les tâches », la tâche va dans la liste par défaut (Paramètres → Liste par défaut : à fixer sur
Default calendarpour que le perso soit le réflexe et que NEXTE soit un choix). - Forcer la synchro : balayage vers le bas dans une liste, ou DAVx5 → Synchroniser maintenant. Sinon, périodicité DAVx5 (120 s dans InfCloud, celle de DAVx5 pour le téléphone).
6. Vérifications effectuées le 15/09/2026
6.1 MCP → Baïkal (script seul)
| Test | Résultat |
|---|---|
PROPFIND supported-calendar-component-set sur default/ et nexte/ |
VEVENT, VTODO sur les deux |
create_task échéance 18/09/2026, priorité 1, catégorie |
créée dans NEXTE, DUE;VALUE=DATE |
create_task échéance 2026-09-20 17h00, priorité 5 |
créée, DUE horodaté +02:00 |
list_tasks(calendar="NEXTE") |
les deux, triées par échéance |
list_tasks(due_before="19/09/2026") |
une seule (la première) |
update_task IN-PROCESS, 50 %, échéance décalée au 19/09 |
relu conforme |
complete_task |
COMPLETED, 100 %, horodatage COMPLETED posé |
list_tasks sans / avec include_completed |
0 / 1 |
reopen_task |
NEEDS-ACTION, 0 %, COMPLETED retiré |
update_event (régression §7.1) |
description posée sur un créneau Tourrettes, date inchangée |
delete_task ×2 |
NEXTE vide |
claude mcp list après modification |
caldav … ✔ Connected |
6.2 Bout en bout, avec le téléphone
| Étape | Résultat |
|---|---|
create_task « TEST MCP → Tasks.org », échéance 16/09, priorité 5, dans NEXTE |
créée |
| Synchro DAVx5 → Tasks.org, liste NEXTE | visible, avec échéance et priorité |
| Cochée dans Tasks.org, synchro | list_tasks(include_completed=True) : COMPLETED 100 %, completed 15/09 17:02 |
| Tâche « reprendre des fruits » saisie dans Tasks.org (liste NEXTE) | lue par le MCP : NEEDS-ACTION, sans échéance, PRIORITY:9 |
| InfCloud → onglet Tâches | les deux visibles |
Les deux tâches sont laissées en place à la demande de Julien, pour observer le comportement des rappels (§5.2).
7. Pièges rencontrés le 15/09/2026
7.1 icalendar.walk() renvoie une liste
Avec icalendar 6, Calendar.walk("VTODO") renvoie une liste, pas un itérateur : next(ic.walk(...)) échoue en TypeError. Le même code dormait dans update_event depuis la création du script — jamais exercé jusque-là. Corrigé aux deux endroits (ic.walk(...)[0]).
7.2 Todo.complete() ne pose pas le pourcentage
La bibliothèque caldav pose STATUS:COMPLETED et COMPLETED:<horodatage> mais laisse PERCENT-COMPLETE à sa valeur précédente (50 % dans le test). Certains clients affichent alors une tâche « terminée à 50 % ». complete_task force 100 %.
7.3 Une tâche créée par le MCP n'a pas de rappel
Le MCP écrit DUE mais aucun VALARM. Dans Tasks.org, la tâche a une échéance mais son rappel « à l'échéance » dépend du réglage par défaut de l'appli pour les tâches importées (Paramètres → Notifications). Si Julien veut être notifié des tâches créées depuis Claude, deux options : régler Tasks.org pour appliquer les rappels par défaut aux nouvelles tâches synchronisées, ou ajouter un VALARM côté MCP (paramètre reminder sur create_task). À trancher à l'usage.
7.4 Échéance : date seule vs date-heure
Une échéance 18/09/2026 doit produire DUE;VALUE=DATE:20260918 (journée entière), et 2026-09-20 17h00 un DUE horodaté en Europe/Paris. Les deux formes sont gérées ; due_before compare une date seule à la fin de journée, pour ne pas exclure une tâche échue le jour même.
7.5 Priorité 9 par défaut côté Tasks.org
Voir §5.3 : « basse » n'est pas un choix, c'est le défaut de l'appli.
8. Règles d'usage pour Claude
- Une tâche se crée dans le calendrier par défaut du PC (NEXTE ici) ; les tâches perso ne sont lues que pour la vue d'ensemble, jamais modifiées sans demande explicite.
delete_taskuniquement après confirmation explicite de Julien. Terminer ≠ supprimer : une tâche faite se termine (complete_task), on ne l'efface pas — l'historique reste consultable avecinclude_completed.- Priorités : n'en poser une que si Julien la donne ; par défaut 0. Ne pas interpréter un 9 venant de Tasks.org.
- Une tâche sans échéance ne notifiera jamais : si Julien veut « être rappelé », demander une date.
- Ne jamais annoncer qu'une tâche est créée sans relire
duedans le résultat (même règle que pour les dates d'événements, page 2 §5.8).
4 - intégration des contacts personnels et professionnels de Julien
Intégration des contacts personnels et professionnels de Julien
Statut au 15 septembre 2026 : en discussion. Rien n'est construit. Cette page contient le plan en trois actes posé par Julien, puis les retours de Claude. Les décisions prises seront consignées en §5 au fur et à mesure.
1. Le plan de Julien (15/09/2026)
Sources actuelles, dispersées :
- contacts hérités dans Synology Contacts (NAS SasNexte) ;
- contacts pro chez Infomaniak (compte
julien.bertrand@ik.me) ; - contacts téléphone qui se baladent dans le Xiaomi (compte local / Mi Cloud) ;
- contacts Gmail perso.
Trois actes :
- Une base unificatrice simple (SQLite) pour tout rapatrier, trier, sérier, ancrer, archiver — lisible depuis Thunderbird sur le téléphone, depuis Synology Contacts, etc.
- Baïkal comme catalyseur de cette base : il la distribue, mais reçoit aussi les mises à jour depuis les foyers de création que sont Gmail, Infomaniak et le Xiaomi.
- InfCloud comme répertoire web (Roundcube : à quoi sert-il encore ?), pour rapprocher adresses et rendez-vous.
Procédure hébergée sur le VPS perso juxjux.ovh, comme le reste de la stack (page 1).
2. Retours de Claude
2.1 L'acte 1 existe déjà : Baïkal est la base SQLite
Baïkal stocke ses contacts dans une base SQLite (/var/www/baikal/Specific/db/db.sqlite, table cards, une vCard par ligne — page 1 §6.5 et §12). Construire une seconde SQLite « unificatrice » à côté, c'est créer deux sources de vérité pour le même objet, avec un synchroniseur maison entre les deux à écrire et à maintenir. C'est précisément le problème que Baïkal a été installé pour résoudre (page 1 §1 : « Baïkal est la source de vérité unique »).
Ce que le plan attend de la base « versatile dans sa lecture » — être vue par Thunderbird, par le téléphone, par un répertoire web — c'est exactement ce qu'un serveur CardDAV fournit. Une SQLite nue ne le fournit pas : il faudrait un connecteur par client. Baïkal est la SQLite plus la couche de lecture universelle.
Proposition : fusionner les actes 1 et 2. Baïkal est la base ET le catalyseur. Ce qui manque n'est pas une base, ce sont trois choses :
- un outil d'ingestion des sources (vivantes et mortes) ;
- un espace de tri dans Baïkal, avant que les contacts n'entrent dans le carnet de référence ;
- un outil de triage : détecter les doublons, fusionner, classer, archiver.
Le tri, le sériage, l'ancrage et l'archivage se font dans le vocabulaire vCard, pas dans un schéma SQL à inventer : CATEGORIES pour les étiquettes (mairie, bureau d'études, famille, …), NOTE ou un champ X-PROVENANCE pour l'origine, ORG/TITLE pour l'ancrage professionnel, et plusieurs carnets pour les grands compartiments. Tous les clients relisent ces champs.
2.2 Les sources ne sont pas de même nature
Le plan les traite comme quatre « foyers de création » équivalents. Elles ne le sont pas :
| Source | Nature | Traitement proposé |
|---|---|---|
| Synology Contacts (hérités) | source morte : un stock à vider, pas un flux | export .vcf une fois, import dans un carnet de transit, puis triage. Synology Contacts n'est ensuite plus alimenté |
| Infomaniak (pro) | source vivante, CardDAV natif | vdirsyncer, prévu depuis août (page 1 §10.1), en sens unique vers Baïkal pour commencer |
| Gmail (perso) | source vivante, CardDAV Google (mot de passe d'application, compte en 2FA) | idem, vdirsyncer sens unique vers Baïkal |
| Xiaomi | pas une source distante : ce sont des contacts dans le compte « Téléphone » ou « Mi Cloud » de l'appareil, invisibles de tout serveur | export .vcf une fois depuis l'appli Contacts, import en transit, triage ; puis réglage du compte par défaut pour les nouveaux contacts = compte Baïkal de DAVx5, et désactivation de la synchro contacts Mi Cloud / Google pour tarir la fuite |
Point important : une fois le compte DAVx5 défini comme compte par défaut sur le téléphone, le téléphone n'est plus un foyer de création à synchroniser — il écrit directement dans Baïkal. Même chose pour InfCloud, Roundcube et Claude (via le futur MCP CardDAV). Restent deux vrais flux entrants : Infomaniak et Gmail.
2.3 Sur le « reçoit les mises à jour » de l'acte 2 : le sens du flux est la décision structurante
Deux modèles possibles, à trancher avant de configurer vdirsyncer :
Modèle A — Baïkal devient l'unique foyer de création. Gmail et Infomaniak sont vidés dans Baïkal une fois (sens unique), puis on cesse d'y créer des contacts : nouveaux contacts saisis sur le téléphone (compte DAVx5), dans InfCloud, ou par Claude. Gmail et Infomaniak deviennent des vestiges, ou reçoivent une copie descendante (Baïkal → Google) si l'autocomplétion Gmail compte.
- Avantages : aucune synchro bidirectionnelle, donc aucun conflit et aucune propagation de suppression accidentelle ; vdirsyncer peut même être arrêté après la migration.
- Coût : une discipline — ne plus créer de contact dans Gmail ni dans le webmail Infomaniak.
Modèle B — synchronisation bidirectionnelle permanente avec Gmail et Infomaniak.
- Avantage : on continue de créer des contacts partout.
- Risques : un contact supprimé d'un côté est supprimé partout ; un doublon fusionné dans Baïkal peut être recréé par la source ; les champs que Google ne connaît pas sont perdus au retour. vdirsyncer sait faire, mais c'est le mode où l'on découvre les problèmes après coup.
Recommandation : modèle A, avec vdirsyncer en sens unique le temps de la migration, puis à l'arrêt. C'est aussi la recommandation déjà posée en page 1 §10.1. Si l'autocomplétion Gmail manque, ajouter plus tard un flux descendant Baïkal → Google (sens unique aussi) — pas de remontée.
2.4 Espace de tri : des carnets de transit dans Baïkal
Plutôt que de trier dans une base à part, créer dans Baïkal, sous le même utilisateur Julien :
| Carnet | Rôle | Synchronisé vers le téléphone ? |
|---|---|---|
default |
contacts personnels de référence | oui |
nexte |
contacts professionnels de référence (mairies, DDTM, bureaux d'études, …) | oui |
archive |
contacts conservés mais sortis de l'usage (anciens clients, personnes décédées, …) | non — visible dans InfCloud seulement |
transit-synology, transit-gmail, transit-infomaniak, transit-xiaomi |
chaque source importée telle quelle, sans modification | non |
Les carnets de transit sont éphémères : une fois vidés par le triage, on les supprime. Tout ce qui est décidé s'exprime par un déplacement d'un carnet à un autre — ce qui est tracé par Baïkal (horodatage, synctoken) et sauvegardé chaque nuit (page 1 §8).
Deux carnets de référence (default / nexte) plutôt qu'un seul : même logique que pour les calendriers (page 2 §1) — le PC NEXTE n'écrit que dans nexte, les contacts personnels ne transitent par une conversation Alteris que si Julien le demande, et DAVx5 permet de n'exposer que l'un des deux à une appli donnée. Une mairie qui est « les deux » va dans nexte avec une CATEGORIES explicite ; on ne duplique pas.
2.5 L'outil de triage : c'est là que Claude a une valeur
Le triage manuel de quatre sources est ce qui a fait échouer toutes les tentatives de rangement de contacts depuis vingt ans. Ce que le MCP CardDAV (à écrire, comme pour les tâches — page 3) permettrait :
- inventaire : combien de fiches par carnet de transit, combien avec e-mail, téléphone, organisation, combien vides ;
- rapport de doublons : même e-mail, même téléphone normalisé (
+33 6 …), même nom à casse/accent près, entre carnets ; - fusion assistée : Claude propose la fiche fusionnée (union des e-mails et téléphones,
ORGle plus complet,NOTEavec provenance), Julien valide, la fiche entre dansdefaultounexteet les fiches sources sont retirées du transit ; - classement par lots : « tous les contacts dont l'e-mail est en
.gouv.froumairie-→nexte, catégorie collectivité » ; - archivage : déplacement vers
archiveavec date et motif enNOTE.
Rien de tout cela ne demande une base à part : ce sont des opérations CardDAV (REPORT addressbook-query, PUT, DELETE, MOVE) sur des vCards 3.0 — le format que Roundcube écrit déjà et que tout le monde relit (page 1 §9).
2.6 Acte 3 — InfCloud, Roundcube, Thunderbird, Synology
- InfCloud : oui comme répertoire web. Sa moitié CardDavMATE lit et édite les carnets ; il autocomplète les participants d'un rendez-vous depuis les carnets chargés — c'est le « matcher les adresses avec les rendez-vous ». Rappel : un nouveau carnet doit être inscrit dans ses réglages serveur (
loadedaddressbookcollections), même piège qu'en page 2 §5.7 ; on a d'ailleurs vu dans ces réglages une entréeactiveaddressbookcollections: ["https://undefined"]à corriger au passage. - Roundcube : il n'a plus de rôle propre pour les contacts dès qu'InfCloud les couvre. Il ne se justifie que comme webmail du compte
ik.me. Si Julien ne l'utilise pas comme webmail, il peut être arrêté : un container, un vhost, ~32 Mo et une surface d'attaque de moins (page 1 §6.4 pour ses contraintes IMAP). À décider — pas dans l'urgence. - Thunderbird sur le téléphone : il ne gère pas de carnet ; il lit les contacts Android, qui viennent de DAVx5. Rien à faire de plus. Thunderbird sur PC (s'il y en a un) parle CardDAV nativement : URL du carnet + identifiants, et il voit
default/nexte. - Synology Contacts : à ma connaissance, l'application Synology expose du CardDAV (elle est serveur) et importe des comptes Google/Microsoft, mais ne consomme pas un CardDAV tiers. À vérifier sur le DSM. Si c'est confirmé, le NAS n'est pas un client à alimenter mais une source à vider (§2.2), et il n'a plus de rôle après la migration — sauf comme archive figée de l'existant, ce qui est très bien.
2.7 Ordre de chantier proposé
Chaque étape est réversible et laisse la stack en état de marche. Rien n'est lancé sans validation de Julien.
| # | Étape | Où | Outil |
|---|---|---|---|
| 0 | Inventaire : nombre de contacts par source, présence d'e-mail/téléphone, date du dernier ajout | chaque source | exports .vcf, comptage |
| 1 | Gel des sources : arrêt de la création de contacts dans Gmail et le webmail Infomaniak ; compte DAVx5 par défaut sur le Xiaomi ; synchro contacts Mi Cloud/Google coupée | téléphone, discipline | réglages Android |
| 2 | Carnets : création de nexte, archive et des carnets de transit ; inscription dans InfCloud |
Baïkal | admin Baïkal ou MCP |
| 3 | Imports morts : Synology et Xiaomi → carnets de transit | VPS | .vcf + PUT CardDAV |
| 4 | Imports vivants : vdirsyncer sens unique Gmail → transit, Infomaniak → transit ; cron le temps de la migration | VPS | vdirsyncer (page 1 §10.1) |
| 5 | Triage : inventaire, doublons, fusions validées, classement default/nexte/archive |
MCP CardDAV | Claude + Julien |
| 6 | Vérification clients : téléphone (DAVx5), InfCloud, Thunderbird PC | clients | — |
| 7 | Fin de migration : suppression des carnets de transit, arrêt de vdirsyncer (modèle A), sort de Roundcube | VPS | — |
Le MCP CardDAV (outils *_contact) est un préalable à l'étape 5 et sert dès l'étape 0 pour compter. Il se construit comme celui des tâches, dans le même script.
3. Ce que Claude ne recommande pas
- Une SQLite maison à côté de Baïkal (§2.1).
- Un conteneur « tout-en-un » (Nextcloud, SOGo) : déjà écarté en page 1 §13 pour de bonnes raisons de RAM et de surface ; la stack actuelle est plus riche en pièces mais chaque pièce est petite et remplaçable.
- La bidirectionnalité d'emblée avec Gmail et Infomaniak (§2.3).
- Le triage à la main dans une interface web, fiche par fiche.
4. Questions à trancher
- Modèle A (Baïkal seul foyer de création) ou modèle B (bidirectionnel) ? — §2.3
- Deux carnets de référence
default/nexte, ou un seul avec catégories ? — §2.4 - Le pro, c'est NEXTE ou Alteris, ou les deux avec une catégorie ? (même question que pour le calendrier, tranchée « NEXTE » le 15/09)
- Roundcube : webmail utilisé, ou à arrêter ? — §2.6
- Synology Contacts : confirmer qu'il ne peut pas être client CardDAV ; sinon, souhaite-t-on qu'il reste alimenté ?
- L'autocomplétion Gmail manque-t-elle si Gmail cesse d'être alimenté ? (déclenche ou non un flux descendant Baïkal → Google)
- Ordre de grandeur : combien de contacts au total, toutes sources confondues ? (change la méthode de triage : 300 se font en une séance, 3 000 par lots)
# Note de déploiement — miroir contacts Baïkal → Infomaniak (VPS juxjux
Rédigée le 22/09/2026 par l'instance Claude du **PC NEXTE**, à l'attention de l'instance Claude du **PC perso** qui a l'accès SSH au VPS juxjux. Documentation du chantier : BookStack Alteris page 499 (chapitre 486, livre 280).
---
## 1. Ce qui est déjà fait (sur le PC NEXTE, en production)
Le miroir tourne et a été validé de bout en bout. Il est **lancé à la main** depuis le PC NEXTE ; il faut maintenant le **planifier sur le VPS** pour qu'il vive sans ce PC.
État au 22/09 au soir :
| Paire | Identifiant court | Carnet Infomaniak | Baïkal | Infomaniak `Baikal-NEXTE` |
|---|---|---|---|---|
| Carnet**NEXTE** NEXTE→ `julien.bertrand@nexte.fr` | `JB08607` | `Baikal-NEXTE` | 1 585584 fiches | 1 585 fiches,584, identiques |
| Carnet**Jux** Jux→ `julien.bertrand@ik.me` | `JB06766` | `Baikal-Jux` | 305 fiches | *(paire305, non encore créée — voir §5)*identiques |
Les deux paires sont déclarées dans `PAIRES` en tête du script et tournent en production ; un passage à blanc ne trouve plus rien à faire des deux côtés.
Chaîne vérifiée : correction d'une fiche dans InfCloud → descend dans Infomaniak et sur le téléphone (DAVx5/Fossify). Création **et modification** dans le webmail Infomaniak → remonteremontent dans Baïkal.Baïkal (§2).
## 2. Le script
`260922-miroir_infomaniak.py` (ce dossier Syncthing, `Jux-scripts/Baikal-Contacts/`). Autonome PEP 723, dépendance `vobject`, lancé par `uv run --script`.
**Principe** — Baïkal est le maître :
- **descente** : toute fiche Baïkal absente ou sémantiquement différente côté Infomaniak est écrite (PUT, même UID) ; toute fiche que le miroir avait déposée et qui a disparu de Baïkal est supprimée côté Infomaniak ;
- **remontée*remontée des créations** : une fiche présente côté Infomaniak dont l'UID n'a **jamais** été déposé par le miroir (donc créée dans le webmail) est recopiée dans Baïkal, avec une note de provenance ;
- **remontée des modifications** *(ajoutée le 22/09, avant déploiement)* : une fiche existante modifiée dans le webmail remonte dans Baïkal. L'arbitrage repose sur les **ETag mémorisés des deux côtés** : si seul Infomaniak a bougé, la fiche remonte ; si seul Baïkal a bougé, elle redescend ; si les deux ont bougé, le **`REV` le plus récent gagne** et le conflit est compté dans la sortie (`conflits_arbitres`) ;
- **aucune suppression ne remonte jamais** — une suppression dans le webmail est annulée au passage suivant. C'est voulu : protection contre les suppressions intempestives et contre une compromission de la boîte.
Deux propriétés vérifiées avant de coder l'arbitrage, à re-vérifier si Infomaniak change de moteur : leurs **ETag sont stables** entre deux lectures sans modification (1 585/1 585 testées) et le **`REV` de Baïkal est conservé à l'identique** au stockage.
Le fichier d'état `miroir_<paire>.json` (liste**version des2** UID: déposés)`{uid: {"ik": etag, "bk": etag}}`) est **ce qui distingue** « supprimée dans Baïkal » de « créée dans le webmail »., et quel côté a bougé. **Ne pas le perdre** : sans lui, le premier passage suivant remonteraitretombe sur le comportement « Baïkal gagne » et les modifications faites dans Baïkalle touteswebmail lesseraient fichesécrasées. Il migre automatiquement depuis le format v1 (simple liste d'Infomaniak comme si elles étaient des créations.UID).
Comparaison **sémantique** (nom, N, e-mails, téléphones, organisation, fonction, catégories, note, adresses) et non octet à octet : Infomaniak réécrit les vCards au stockage, une comparaison stricte réécrirait tout à chaque passage.
## 3. Ce qu'il faut faire sur le VPS
### 3.1 Fichiers à déposer
| Fichier | Où | Contenu |
|---|---|---|
| `260922-miroir_infomaniak.py` | `/home/debian/baikal-miroir/` | le script (copie depuis Syncthing) |
| `260915-mcp_caldav_server.py` | `/home/debian/baikal-miroir/` | **requis** : le miroir l'importe comme bibliothèque (accès Baïkal) |
| `infomaniak.env` | `/home/debian/baikal-miroir/` | `NEXTE_FR_PASSWORD=…` et `IK_ME_PASSWORD=…` — mots de passe d'application Infomaniak.Infomaniak (16 caractères chacun). `chmod 600`. **À consigner dans le Vault 2603** : le fichier a disparu une fois du PC NEXTE sans explication, le 22/09 |
| `~/.claude.json` **ou** variables d'environnement | — | le script serveur lit `CALDAV_URL`, `CALDAV_USER`, `CALDAV_PASSWORD` dans `~/.claude.json` → **à adapter** (voir §3.2) |
### 3.2 Adaptation à faire dans `load_server()`
Sur le PC NEXTE, les identifiants Baïkal viennent de `~/.claude.json` (config du MCP Claude Code). **Sur le VPS il n'y a pas de Claude Code** : remplacer, dans `260922-miroir_infomaniak.py` (et dans les autres scripts si tu les déploies), la fonction :
```python
def load_server():
cfg = json.load(open(os.path.expanduser("~/.claude.json"), encoding="utf-8"))
os.environ.update(cfg["mcpServers"]["caldav"]["env"])
...
```
par une lecture directe d'un fichier `baikal.env` (même dossier, `chmod 600`) :
```
CALDAV_URL=https://baikal.juxjux.ovh/dav.php/
CALDAV_USER=Julien
CALDAV_PASSWORD=<mot de passe DAV de l'utilisateur Julien — Vault 2603>
CALDAV_DEFAULT_CALENDAR=NEXTE
```
Remarque : le VPS juxjux héberge Baïkal lui-même ; le miroir peut donc taper sur `http://127.0.0.1:8088/dav.php/` plutôt que de ressortir par nginx. À toi de voir — l'URL publique fonctionne et évite de dépendre du port interne (page 487 §4).
### 3.3 Cron
```
*/15 * * * * cd /home/debian/baikal-miroir && /usr/local/bin/uv run --script 260922-miroir_infomaniak.py --env infomaniak.env --etat /home/debian/baikal-miroir/etat --real >> /var/log/baikal-miroir.log 2>&1
```
- `uv` à installer si absent (`curl -LsSf https://astral.sh/uv/install.sh | sh`) ; sinon un venv avec `vobject`, `caldav`, `icalendar`, `python-dateutil`, `mcp<2`.
- **Verrou** : ajouter `flock` pour qu'un passage lent ne chevauche pas le suivant :
`flock -n /tmp/baikal-miroir.lock -c '…'`
- **Alerte** : en cas de sortie contenant `"echecs": [1-9]` ou d'un code de retour non nul, envoyer un e-mail comme le fait `vps_backup.sh` (même mécanisme, page 487 §8).
Durée observée d'un passage sans changement : quelques secondes. Un passage qui réécrit 1 500 fiches : ~5 minutes.
### 3.4 Export `.vcf` quotidien
Décision de Julien : **un seul fichier, toujours à jour, réécrit à chaque passage** — pas de rotation, le versionnage est assuré par la sauvegarde nocturne de Baïkal vers kDrive (page 487 §8). Destination : le dossier Syncthing `Jux_univers/jux_contacts/` (si le VPS est dans le maillage ; sinon `/home/debian/baikal-miroir/export/`).
Poids mesuré le 22/09 : **1,92 Mo** pour les deux carnets réunis (1 890 fiches), dont 62 % de photos (116 photos). À écrire une fois par nuit, pas à chaque passage du miroir.
Le code d'export existe déjà, dupliqué dans plusieurs scripts (`260921-restaurer_carnets.py`, `260921-fusion_lots.py`) :
```python
with open(dest, "w", encoding="utf-8", newline="") as f:
for c in m._fetch_contacts(m._find_addressbook(book)):
f.write(c["_raw"].rstrip("\r\n") + "\r\n")
```
## 4. Pièges rencontrés — à ne pas redécouvrir
1. **Identifiant CardDAV Infomaniak** : ce n'est **pas** l'adresse e-mail mais un **identifiant court** de synchronisation (`JB08607` pour `julien.bertrand@nexte.fr`), obtenu sur `https://config.infomaniak.com` → *Contacts & calendriers* → appareil **GNU/Linux**. Avec l'adresse e-mail, l'authentification passe (207 à la racine) mais toute requête répond `Principal with name … not found` — cherche-erreur garanti.
2. **Mots de passe d'application Infomaniak** : ils n'ont **pas** de périmètre par service (contrairement à ce que j'avais supposé) ; un seul mot de passe vaut pour IMAP, CalDAV, CardDAV. Ne pas confondre avec les mots de passe **de boîte mail** générés par `config.infomaniak.com` pour un appareil : ceux-là ne donnent pas accès au CardDAV.
3. **MKCOL interdit** : Infomaniak refuse la création d'un carnet par CardDAV (403 Forbidden). Le carnet cible doit être créé **à la main dans le webmail**, avec le nom exact attendu par `PAIRES` (`Baikal-NEXTE`, `Baikal-Jux`).
4. **Lignes pliées** : Infomaniak lit mal les vCards pliées à 75 caractères (RFC 6350 §3.2) — la catégorie `Important` devenait `Imp`. Le script **déplie** les vCards avant le PUT (`data.replace("\r\n ", "")`). Ne pas retirer cette ligne.
5. **Suivre `carnet_ik_cree`** dans la sortie JSON : `true` signifie que le carnet n'a pas été trouvé par son nom — souvent un carnet renommé ou une faute de frappe, pas une vraie création (elle est interdite, cf. 3).
## 5. Reste à faire
1. **~~Paire `Jux` → `ik.me`~~ — **faite le 22/09** : décommenter l'entrée dans `PAIRES` avec l'identifiant court du compte `julien.bertrand@ik.me` (à obtenir par Julien sur `config.infomaniak.com` connecté à ce compte)JB06766`, et **créer le carnet `Baikal-Jux`, 305 fiches déposées, 0 échec.
*Au passage, un piège à connaître* : le compte `ik.me` ne contenait qu'un seul carnet, « Julien BERTRAND », avec **1 664 fiches jamais triées** (ce compte ne faisait pas partie des cinq exports du 17/09). Julien les a jugées « doublon de doublon de doublon » et fait supprimer — sauvegarde préalable dans `jux_contacts/260922-1643-sauvegarde-ikme-JulienBERTRAND.vcf` (736 Ko). **Il ne fallait surtout pas renommer ce carnet en `Baikal-Jux` avant de le vider** : le miroir aurait pris ses 1 664 fiches pour des créations faites dans le webmail et les aurait remontées dans le carnet Jux de ceBaïkal, compte**puis (MKCOLsur interdit).le téléphone. Vider d'abord, renommer ensuite.
2. **~~Remontée des modifications*modifications~~ — **faite le 22/09**, avant déploiement (voir §2). Testée deux fois : aujourd'hui,mise à jour substantielle d'une fiche existante modifiée dans le webmail (nom, organisation, fonction, note de 483 caractères) et **ajout d'une photo de 70 Ko** — remontées intactes dans Baïkal, base64 et attribut de recadrage compris.
3. **Surveiller le poids du carnet** (question posée par Julien le 22/09, laissée ouverte) : le miroir relit **l'intégralité** des deux carnets à chaque passage. À 2 Mo aujourd'hui c'est écraséeindolore ; mais les photos ajoutées par le webmail pèsent ~70 Ko chacune (contre 7 Ko pour les vignettes héritées), et le base64 ajoute 33 %. À 500 photos, le carnet ferait ~46 Mo, soit ~4 Go de trafic par jour à raison d'un passage tous les quarts d'heure. Deux parades, à préparer avant d'en arriver là : **lecture incrémentale** (`sync-collection` + `sync-token`, supportés par Baïkal auet passageInfomaniak suivant.— Évolutionvérifié) prévueet/ou :**redimensionnement arbitragedes parphotos `REV`à l'entrée** (le400 pluspx, récemment~25 modifiéKo). gagne),Julien sansa jamais remonterchoisi de suppression.laisser ~20tel lignes dans `run_pair()`. À faire côté NEXTE avant déploiement, ou sur le VPS ensuite — se coordonnerquel pour nel'instant paset diverger.d'en discuter avec toi.3.4. **Ne pas oublier** : le troisième compte `nexte@ik.me` (trash) **ne doit jamais être branché** — c'est une consigne explicite de Julien.
## 6. Contrat entre nos deux instances
Le script maître reste celui du **pivot Syncthing** `Jux-scripts/Baikal-Contacts/`. Si tu le modifies pour le VPS (notamment `load_server()`), garde la version VPS distincte et datée (`2609xx-miroir_infomaniak_vps.py`) plutôt que d'écraser le fichier commun — le PC NEXTE continue de s'en servir pour les opérations manuelles sur les carnets.
Renvoie-moi une note dans ce même dossier quand le cron tourne, avec : chemin d'installation, contenu exact de la crontab, emplacement du journal, et le premier passage observé (JSON de sortie). Je la consignerai en page 499.