1
0
Fork
You've already forked heron
0
CRM adapté à mes besoins (IMAP, CalDAV, Todoist, Logseq)
  • Python 95.2%
  • JavaScript 4.4%
  • HTML 0.4%
Claude Code 5d9feb66ef docs(readme): CardDAV, projection Obsidian, retrait Logseq/PERSONAL_DOMAINS
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026年06月22日 11:59:20 +02:00
contacts fix(carddav): strip() défensif sur url/username/password du client 2026年06月20日 14:42:34 +02:00
core feat(obsidian): retrait de l'UI web métier (admin-only) 2026年06月12日 10:30:00 +02:00
docs docs(carddav): renumérote les tâches du Plan A_2 (Tâche 1..7) 2026年06月20日 01:01:22 +02:00
emails refactor(tests,obsidian): nettoyage des findings mineurs (assertions, commentaire, prefetch) 2026年06月20日 00:35:29 +02:00
events refactor(tests,obsidian): nettoyage des findings mineurs (assertions, commentaire, prefetch) 2026年06月20日 00:35:29 +02:00
extension feat(extension): ignorer les conversations LinkedIn de plus de 12 mois 2026年05月22日 23:10:54 +02:00
heron refactor(settings): retire HERON_PERSONAL_DOMAINS (mort depuis le retrait de resolve_organisme) 2026年06月21日 14:42:23 +02:00
linkedin chore(notes): retrait de logseq_sync (Logseq n'est plus une source) 2026年06月15日 09:50:39 +02:00
notes feat(obsidian): suppression du rendu markdown et des champs *_html 2026年06月15日 09:55:59 +02:00
obsidian feat(obsidian): détecte et compte les *.sync-conflict-* (SyncRun) 2026年06月20日 01:16:59 +02:00
projects Modification du modèle de projet 2026年06月18日 12:31:50 +02:00
scripts feat(carddav): script debug_carddav (rejoue la requête sync-collection réelle, sortie obfusquée) 2026年06月20日 14:42:57 +02:00
tasks feat(obsidian): retrait de l'UI web métier (admin-only) 2026年06月12日 10:30:00 +02:00
vault_template feat(obsidian): scaffolding du vault (Bases + README) 2026年06月12日 08:15:48 +02:00
.env.example refactor(settings): retire HERON_PERSONAL_DOMAINS (mort depuis le retrait de resolve_organisme) 2026年06月21日 14:42:23 +02:00
.env_remote chore(notes): retrait de logseq_sync (Logseq n'est plus une source) 2026年06月15日 09:50:39 +02:00
.gitignore Skip si le fichier obsidian existe déjà 2026年06月18日 11:01:51 +02:00
.pre-commit-config.yaml chore: pre-commit (ruff format+lint, djlint, hygiene) 2026年05月18日 04:42:04 +02:00
CLAUDE.md docs(obsidian): admin-only — mise à jour CLAUDE.md + vérif finale phase 3 2026年06月15日 10:07:40 +02:00
conftest.py refactor(settings): retire HERON_PERSONAL_DOMAINS (mort depuis le retrait de resolve_organisme) 2026年06月21日 14:42:23 +02:00
manage.py core: squelette projet Django (settings env-driven, urls, wsgi/asgi) 2026年05月18日 04:50:06 +02:00
nginx.conf.example infra: navbar Projets active + volumes media/logseq + nginx /media/ 2026年05月20日 09:24:48 +02:00
obsidian.md chore(notes): retrait de logseq_sync (Logseq n'est plus une source) 2026年06月15日 09:50:39 +02:00
plan.md Chnagements dans la dynamique de création du projet T4 2026年05月20日 09:55:53 +02:00
pyproject.toml feat(carddav): parser vCard (vobject) 2026年06月19日 15:15:27 +02:00
rapport.md chore(notes): retrait de logseq_sync (Logseq n'est plus une source) 2026年06月15日 09:50:39 +02:00
README.md docs(readme): CardDAV, projection Obsidian, retrait Logseq/PERSONAL_DOMAINS 2026年06月22日 11:59:20 +02:00
spec.md docs: retire envoi SMTP du périmètre, renumérote T2→T4 2026年05月18日 11:42:12 +02:00
uv.lock feat(carddav): parser vCard (vobject) 2026年06月19日 15:15:27 +02:00

Héron CRM

CRM mono-utilisateur intégrant IMAP, CalDAV, CardDAV et Todoist. Le front-end est un vault Obsidian alimenté par projection depuis SQLite ; l'admin Django ne sert plus que d'observabilité. Stack : Django 5.2 + SQLite + gunicorn, Python 3.13, dépendances gérées avec uv.

Développement local

Prérequis : Python 3.13 + uv

cp .env.example .env
# éditer .env : HERON_USERNAME / HERON_PASSWORD / DJANGO_SECRET_KEY au minimum
uv sync
uv run pre-commit install
uv run python manage.py migrate
uv run python manage.py runserver

Ouvrir http://localhost:8000 → redirige vers l'admin Django (seule UI web ; le front-end métier est un vault Obsidian, voir plus bas) → se connecter avec les credentials de .env.

Tests

uv run pytest # tous les tests
uv run pytest contacts/tests/ # une seule app
uv run pytest -k carddav # un sous-ensemble par nom

Linting

uv run ruff check .
uv run ruff format .

Variables d'environnement

Voir .env.example. Les plus importantes :

  • HERON_USERNAME, HERON_PASSWORD — identifiants de l'utilisateur unique
  • HERON_FIRSTNAME, HERON_LASTNAME, HERON_EMAIL — profil
  • DJANGO_SECRET_KEY — clé Django (longue, aléatoire)
  • BOOTSWATCH_THEME — thème Bootswatch de l'admin (ex flatly, superhero)
  • DATABASE_PATH — chemin SQLite (défaut : heron.sqlite3 à la racine)
  • HERON_MEDIA_ROOT — dossier des pièces jointes (défaut : media/)
  • HERON_OBSIDIAN_VAULT_DIR — dossier du vault Obsidian, cible de la projection
  • HERON_SSO_ENABLED — activer le SSO YunoHost (défaut : false)

Modèle de synchronisation

  • Ingestion → SQLite (sens unique, incrémental) : imap_sync, caldav_sync, carddav_sync, todoist_sync.
  • Projection SQLite → vault Obsidian : obsidian_sync (le vault est le front-end ; le corps des notes appartient à l'utilisateur, jamais écrasé).
  • Source de vérité des contacts/organismes : le carnet CardDAV. Les contacts ne sont plus auto-créés depuis les mails/évènements ; ces derniers les référencent par email (lien « mort » tant que le contact n'est pas dans le carnet, résolu automatiquement dès qu'il y arrive).

Ordre conseillé d'un cycle complet : carddav_syncimap_synccaldav_synctodoist_syncobsidian_sync.

Déploiement YunoHost

Voir le repo heron_ynh.

Déploiement manuel (uv + systemd)

Ces instructions ciblent Debian 12 / Ubuntu 24.04 avec nginx.

1. Prérequis

sudo apt install -y nginx python3-pip
# Installer uv
curl -LsSf https://astral.sh/uv/install.sh | sh

2. Utilisateur système et répertoires

sudo useradd --system --home /opt/heron --shell /usr/sbin/nologin heron
sudo mkdir -p /opt/heron /var/lib/heron/media /var/lib/heron/vault
sudo chown heron:heron /opt/heron /var/lib/heron

3. Code et dépendances

sudo -u heron git clone https://codeberg.org/ColinMaudry/heron /opt/heron
sudo -u heron /bin/bash -c "cd /opt/heron && uv python install 3.13 && uv sync --frozen --no-dev"

4. Configuration

sudo cp /opt/heron/.env.example /opt/heron/.env
sudo nano /opt/heron/.env # remplir SECRET_KEY, HERON_USERNAME, HERON_PASSWORD, etc.
# Chemins recommandés :
# DATABASE_PATH=/var/lib/heron/heron.sqlite3
# HERON_MEDIA_ROOT=/var/lib/heron/media
# HERON_OBSIDIAN_VAULT_DIR=/var/lib/heron/vault
# HERON_SSO_ENABLED=false # true uniquement si nginx injecte Remote-User

5. Migrations et statiques

sudo -u heron /bin/bash -c "cd /opt/heron && uv run python manage.py migrate --noinput"
sudo -u heron /bin/bash -c "cd /opt/heron && uv run python manage.py collectstatic --noinput"

6. Service gunicorn

Créer /etc/systemd/system/heron.service :

[Unit]
Description=Héron CRM — gunicorn
After=network.target
[Service]
User=heron
WorkingDirectory=/opt/heron
EnvironmentFile=/opt/heron/.env
ExecStart=/opt/heron/.venv/bin/python -m gunicorn heron.wsgi:application \
 --bind 127.0.0.1:8000 \
 --workers 2
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now heron

7. Timers de synchronisation

Créer /etc/systemd/system/heron-sync@.service :

[Unit]
Description=Héron sync — %i
After=heron.service
[Service]
User=heron
WorkingDirectory=/opt/heron
EnvironmentFile=/opt/heron/.env
ExecStart=/opt/heron/.venv/bin/python manage.py %i
Type=oneshot

Créer un timer par commande de sync (imap_sync, caldav_sync, carddav_sync, todoist_sync, obsidian_sync) sur le modèle suivant — adapter Description, Unit=heron-sync@<commande>.service et OnCalendar :

[Unit]
Description=Héron CardDAV sync (toutes les 15 min)
[Timer]
Unit=heron-sync@carddav_sync.service
OnCalendar=*:0/15
RandomizedDelaySec=60
Persistent=true
[Install]
WantedBy=timers.target

Cadences conseillées : imap_sync *:0/5, caldav_sync/carddav_sync/ todoist_sync/obsidian_sync *:0/15.

sudo systemctl daemon-reload
sudo systemctl enable --now \
 heron-imap_sync.timer heron-caldav_sync.timer heron-carddav_sync.timer \
 heron-todoist_sync.timer heron-obsidian_sync.timer

8. Nginx

Adapter nginx.conf.example (remplacer domaine et chemins) et l'activer :

sudo cp nginx.conf.example /etc/nginx/sites-available/heron
sudo nano /etc/nginx/sites-available/heron
# Remplacer heron.example.org, /chemin/vers/staticfiles, /chemin/vers/media
sudo ln -s /etc/nginx/sites-available/heron /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Chemins des statiques et media (avec la config recommandée ci-dessus) :

  • Statiques : /opt/heron/staticfiles/
  • Media : /var/lib/heron/media/

Synchronisation IMAP

Variables .env : HERON_IMAP_HOST, HERON_IMAP_PORT, HERON_IMAP_USERNAME, HERON_IMAP_PASSWORD, HERON_IMAP_USE_SSL, HERON_IMAP_FOLDERS.

uv run python manage.py imap_sync

Idempotente : checkpoint par dossier (UIDVALIDITY + last UID). Ne crée plus de contacts — elle persiste les emails des participants et lie le mail aux contacts CardDAV déjà connus ; un mail n'est matérialisé que s'il a au moins un contact connu (non ignoré).

Synchronisation CalDAV

Variables .env : HERON_CALDAV_URL, HERON_CALDAV_USERNAME, HERON_CALDAV_PASSWORD, HERON_CALDAV_PAST_DAYS, HERON_CALDAV_FUTURE_DAYS.

uv run python manage.py caldav_sync

Incrémentale via sync-token RFC 6578. Comme l'IMAP, elle ne crée pas de contacts : les évènements persistent les emails des participants et se lient aux contacts CardDAV connus.

Synchronisation CardDAV

Le carnet CardDAV est la source de vérité des contacts et organismes. Les organismes sont dérivés du champ ORG des vCards ; un contact taggé de la catégorie heron_ignore est ignoré par Héron (pas de note, ne déclenche pas la matérialisation d'un mail).

Variables .env : HERON_CARDDAV_URL (URL de la collection carnet), HERON_CARDDAV_USERNAME, HERON_CARDDAV_PASSWORD.

uv run python manage.py carddav_sync

Incrémentale via sync-collection (RFC 6578). Quand un contact arrive dans le carnet, les liens « morts » par email des mails/évènements existants se résolvent automatiquement.

Outil de diagnostic (lecture seule, sortie obfusquée) pour valider la connexion : uv run python scripts/probe_carddav.py.

Bascule one-shot vers CardDAV (purge des contacts legacy auto-créés + reset des checkpoints IMAP/CalDAV pour une re-ingestion complète) : uv run python manage.py carddav_cutover --yes.

Projection Obsidian

Variable .env : HERON_OBSIDIAN_VAULT_DIR.

uv run python manage.py obsidian_sync

Projection à sens unique SQLite → vault (contacts, organismes, projets, évènements, mails). Le frontmatter possédé est réécrit à chaque run ; le corps des notes et les clés ajoutées à la main sont préservés. Écritures idempotentes (pas de réécriture si inchangé) et détection des fichiers de conflit Syncthing.

Synchronisation Todoist

Variables .env : HERON_TODOIST_API_TOKEN, HERON_TODOIST_PROJECT_WHITELIST.

Convention de taggage : tagger les tâches Todoist avec @<slug-organisme> (slug visible dans l'admin Django).

uv run python manage.py todoist_sync

LinkedIn

L'ingestion LinkedIn (extension Firefox → POST /api/linkedin/ingest) est désactivée pour l'instant : les contacts proviennent du carnet CardDAV. La ré-activation/adaptation est prévue ultérieurement.

Architecture

Voir docs/superpowers/specs/ pour les specs détaillées (dont le pivot Obsidian et l'ingestion CardDAV).

Changelog

1.2.0

  • Contacts et organismes sourcés depuis un carnet CardDAV (carddav_sync) ; fin de l'auto-création depuis mails/évènements et LinkedIn (désactivée).
  • Mails/évènements : liens contacts par email, résolus quand le contact arrive dans le carnet ; matérialisation des mails conditionnée à un contact connu.
  • Commande de bascule carddav_cutover ; détection des conflits Syncthing.
  • Retrait de Logseq (logseq_sync), du statut/poubelle des contacts, de la projection photo, et de HERON_PERSONAL_DOMAINS.

1.1.0

  • plus de Web UI, le contenu est projeté sur Obsidian

1.0.0

  • version initiale, avec Web UI et sources des notes sur logseq