Files
2026-08-10 17:42:13 +02:00

5.6 KiB

AGENTS.md — iCloud Contacts Sync

Project Overview

Automated CardDAV delta-sync (RFC 6578) of multiple iCloud accounts into a shared MariaDB database, with a daily birthday email mailer and a read-only web UI/API behind Authelia. Runs as Docker containers on a Proxmox host.

Tech Stack

  • Language: Python 3.12
  • Framework: FastAPI (API), PyMySQL (DB), vobject (vCard), lxml (XML), requests (HTTP)
  • Database: MariaDB (InnoDB, utf8mb4)
  • Container: python:3.12-slim, Python-based scheduler (no external cron)
  • CI/CD: GitHub Actions — lint (ruff), build+push (ghcr.io)
  • Auth: Authelia reverse-proxy header (Remote-User), no built-in login

Repository Structure

src/
  sync.py            — Main sync orchestrator (cron entry point)
  mailer.py          — Birthday email sender (cron entry point)
  config.py          — Reads accounts.json + env vars
  db.py              — MariaDB connection and queries
  carddav_client.py  — CardDAV HTTP client (sync-collection, addressbook-query)
  vcard_parser.py    — vCard parsing via vobject
  scheduler.py       — Python-based scheduler (PID 1 in container)
  api/
    main.py          — FastAPI app (uvicorn entry point)
    auth.py          — Remote-User header dependency
    schemas.py       — Pydantic response models
    static/          — CSS/JS assets
    templates/       — Jinja2 HTML templates
sql/
  schema.sql         — Full schema (contacts, sync_state, sync_runs, birthday_mail_log)
  db-and-user.sql    — One-time DB + user setup
config/
  accounts.json      — Per-account credentials (NOT in git, volume-mounted)
docker/
  entrypoint.sh      — Starts scheduler.py (or exec's custom command)
demo.py              — SQLite demo app with fake contacts (screenshot/showcase)
demo.sh              — Launches demo: creates venv, installs deps, starts server

Key Files

File Role
src/sync.py CLI entry point for sync (python3 sync.py)
src/mailer.py CLI entry point for birthday mailer (python3 mailer.py)
src/api/main.py FastAPI app entry point (uvicorn api.main:app)
src/config.py Loads accounts.json + all env vars
src/db.py All MariaDB queries
sql/schema.sql Canonical schema definition
.env.example All supported environment variables
demo.py SQLite demo app with fake contacts (screenshot/showcase)
demo.sh Launches demo: creates venv, installs deps, starts server

Commands

Lint

ruff check src/

Run sync locally (no Docker)

cd src && python3 sync.py

Run mailer locally

cd src && python3 mailer.py

Docker build

docker compose build

Docker run

docker compose up -d

Run demo (no MariaDB needed)

./demo.sh

Starts SQLite-based demo on 0.0.0.0:8000 with 6 fake contacts.

Code Conventions

  • All source in src/, single package, no setup.py/pyproject.toml.
  • No comments in code unless explicitly requested.
  • Follow existing code style; no new dependencies unless absolutely necessary.
  • Secrets must never be committed. config/accounts.json and .env are gitignored.
  • The DB schema uses account column as tenant key — all queries are scoped per account.
  • JSON columns (emails, phones, etc.) store multi-value vCard fields.

Database Schema

Four tables:

  • contacts — All synced contacts (unique on (account, uid))
  • sync_state — Per-account CardDAV sync token for delta sync
  • sync_runs — Sync run history with status and stats
  • birthday_mail_log — Prevents duplicate birthday emails on same day

Schema is defined in sql/schema.sql. Always update schema.sql when changing the data model.

Environment Variables

See .env.example for full list. Key variables:

  • MARIADB_* — Database connection
  • SMTP_* / MAIL_* — Birthday mailer
  • AUTH_REMOTE_USER_HEADER — Authelia header name (default: Remote-User)
  • MAILER_ENABLED — Feature flag for birthday mailer
  • MAIL_SEND_HOUR — Hour (0-23) for daily birthday email

Architecture Notes

  • Single Docker image, two roles: scheduler (sync+mailer) and API (uvicorn).
  • API is read-only; only the sync container writes to MariaDB.
  • Sync uses CardDAV sync-collection (RFC 6578) for efficient delta sync.
  • Token expiry (~29 days) triggers automatic full re-sync.
  • Deleted contacts are removed from DB (no archival).

Documentation

IMPORTANT: README.md and SPEC.md are the authoritative German-language documentation for this project. They must be kept up to date whenever code, configuration, schema, or architecture changes. Always update these files as part of any feature or fix implementation.

Common Tasks

Adding a new API endpoint

  1. Add route in src/api/main.py
  2. Add Pydantic model in src/api/schemas.py if needed
  3. Add DB query in src/db.py if needed
  4. Update endpoint table in SPEC.md and README.md
  5. Test with: curl -H "Remote-User: <user>" http://127.0.0.1:8000/<path>

Adding a new contact field

  1. Add column to contacts table in sql/schema.sql
  2. Update src/vcard_parser.py to extract the field
  3. Update src/db.py upsert query
  4. Update src/api/schemas.py if exposing via API
  5. Update SPEC.md (data model section)

Changing the sync logic

  1. Edit src/carddav_client.py for CardDAV protocol changes
  2. Edit src/sync.py for orchestration changes
  3. Update SPEC.md if sync behavior changes
  4. Test with a single account first: set LOG_LEVEL=DEBUG

License

MIT License - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)

  • Full text in LICENSE
  • License headers in all source code files