Skip to main content

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


1
cd /home/debian/baikal && sudo docker compose -p baikal up -d


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



1
2
ROUNDCUBEMAIL_COMPOSER_PLUGINS: "roundcube/carddav"
ROUNDCUBEMAIL_PLUGINS: "carddav"


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 :



1
2
3
PHP Fatal error: Uncaught Error: Interface
"MStilkerich\RCMCardDAV\Frontend\RcmInterface" not found
in /var/www/html/plugins/carddav/carddav.php:43


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 :



1
/.well-known/carddav -> http://baikal.juxjux.ovh/dav.php


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 :



1
2
3
4
5
6
location = /.well-known/carddav {
return 301 https://$host/dav.php/;
}
location = /.well-known/caldav {
return 301 https://$host/dav.php/;
}


À 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, sha256 9fa95edd2dcc2b864a10b503ab9220895ea28d4c5541ab02117de1511d5464d4)
  • Installé dans /home/debian/baikal/infcloud, propriétaire www-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 .inc auraient 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à :



1
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { proxy_pass http://127.0.0.1:8088; }


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 :



1
2
3
4
5
location ^~ /infcloud/ {
alias /home/debian/baikal/infcloud/;
index index.html;
location ~ /\. { deny all; }
}


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 :



1
'/dav.php/principals/',


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 :



1
83.195.45.93 - Julien "PROPFIND /dav.php/ HTTP/2.0" 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 base db.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



# État de la stack
sudo docker ps --filter name=baikal --filter name=roundcube

# Logs
sudo docker logs roundcube --tail 50
sudo docker logs baikal --tail 50

# Redéployer
cd /home/debian/baikal && sudo docker compose -p baikal up -d

# Inspecter la base Baïkal
sudo docker cp baikal:/var/www/baikal/Specific/db/db.sqlite /tmp/bk.sqlite
sudo sqlite3 /tmp/bk.sqlite "SELECT COUNT(*) FROM cards;"
sudo sqlite3 /tmp/bk.sqlite "SELECT COUNT(*) FROM calendarobjects;"
sudo sqlite3 /tmp/bk.sqlite "SELECT id, username FROM users;"

# Méthode d'authentification annoncée par Baïkal
curl -s -D - -o /dev/null -X PROPFIND -H 'Depth: 0' \
https://baikal.juxjux.ovh/dav.php/ | grep -i www-authenticate

# Suivre les requêtes DAV des clients (DAVx5, InfCloud, Roundcube)
sudo tail -f /var/log/nginx/access.log | grep dav.php

# Vérifier la découverte DAV
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
https://baikal.juxjux.ovh/.well-known/carddav


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) — uv est 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) :


1
claude mcp add -s user caldav -e CALDAV_URL=https://baikal.juxjux.ovh/dav.php/ -e CALDAV_USER=Julien -e CALDAV_DEFAULT_CALENDAR=NEXTE -e 'CALDAV_PASSWORD=<MDP>' -- "C:\Users\nexte\.local\bin\uv.exe" run --script "C:\Users\nexte\Nexte Claude\MCP CalDAV\260915-mcp_caldav_server.py"


Puis :


1
claude mcp list


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


1
curl -s -o /dev/null -w "%{http_code}\n" -X PROPFIND -H "Depth: 0" https://baikal.juxjux.ovh/dav.php/


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/.
  • Julien en majuscule dans les URLs.
  • Basic sur TLS : la bibliothèque caldav gè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) :


1
2
PROPFIND /dav.php/principals/Julien/ Depth: 0
<D:propfind xmlns:D="DAV:" xmlns:I="http://inf-it.com/ns/dav/"><D:prop><I:settings/></D:prop></D:propfind>


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


1
uv --version


Si absent :


1
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"


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 :


1
claude mcp add -s user caldav -e CALDAV_URL=https://baikal.juxjux.ovh/dav.php/ -e CALDAV_USER=Julien -e CALDAV_DEFAULT_CALENDAR=default -e 'CALDAV_PASSWORD=<MDP>' -- "<chemin complet de uv.exe>" run --script "<chemin>\260915-mcp_caldav_server.py"


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) ;
  • Julien avec un J majuscule (§5.6).

Vérifier :


1
claude mcp list


→ 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

  1. Ouvrir une nouvelle session Claude Code — pas une reprise (§5.5).
  2. Demander list_calendars : les deux calendriers doivent apparaître, Default calendar marqué par défaut.
  3. É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.json du 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_events sans calendar, ou free_slots).
  • Écrire dans le calendrier par défaut du PC ; n'écrire dans l'autre que sur demande explicite.
  • Ne jamais appeler delete_event sans 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_event avant 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

  1. Tasks.org installé depuis F-Droid.
  2. DAVx5 → compte Baïkal : calendriers et tâches activés pour NEXTE et Default 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).
  3. 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 calendar pour 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_task uniquement 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 avec include_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 due dans 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 :

  1. 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.
  2. 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.
  3. 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 :

  1. un outil d'ingestion des sources (vivantes et mortes) ;
  2. un espace de tri dans Baïkal, avant que les contacts n'entrent dans le carnet de référence ;
  3. 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, ORG le plus complet, NOTE avec provenance), Julien valide, la fiche entre dans default ou nexte et les fiches sources sont retirées du transit ;
  • classement par lots : « tous les contacts dont l'e-mail est en .gouv.fr ou mairie- → nexte, catégorie collectivité » ;
  • archivage : déplacement vers archive avec date et motif en NOTE.

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ée activeaddressbookcollections: ["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

  1. Modèle A (Baïkal seul foyer de création) ou modèle B (bidirectionnel) ? — §2.3
  2. Deux carnets de référence default/nexte, ou un seul avec catégories ? — §2.4
  3. 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)
  4. Roundcube : webmail utilisé, ou à arrêter ? — §2.6
  5. Synology Contacts : confirmer qu'il ne peut pas être client CardDAV ; sinon, souhaite-t-on qu'il reste alimenté ?
  6. L'autocomplétion Gmail manque-t-elle si Gmail cesse d'être alimenté ? (déclenche ou non un flux descendant Baïkal → Google)
  7. 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

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