mirror of
https://github.com/skoelle/icloud-contacts-sync.git
synced 2026-09-17 15:30:24 +00:00
4.6 KiB
4.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, supercronic for 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.yml + 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 — supercronic crontab generation
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.yml — Per-account credentials (NOT in git, volume-mounted)
docker/
entrypoint.sh — Generates crontab, starts supercronic
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.yml + all env vars |
src/db.py |
All MariaDB queries |
sql/schema.sql |
Canonical schema definition |
.env.example |
All supported environment variables |
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
Code Conventions
- All source in
src/, single package, nosetup.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.ymland.envare gitignored. - The DB schema uses
accountcolumn 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 syncsync_runs— Sync run history with status and statsbirthday_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 connectionSMTP_*/MAIL_*— Birthday mailerAUTH_REMOTE_USER_HEADER— Authelia header name (default:Remote-User)MAILER_ENABLED— Feature flag for birthday mailerMAIL_SEND_HOUR— Hour (0-23) for daily birthday email
Architecture Notes
- Single Docker image, two roles: cron (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).
Common Tasks
Adding a new API endpoint
- Add route in
src/api/main.py - Add Pydantic model in
src/api/schemas.pyif needed - Add DB query in
src/db.pyif needed - Test with:
curl -H "Remote-User: <user>" http://127.0.0.1:8000/<path>
Adding a new contact field
- Add column to
contactstable insql/schema.sql - Update
src/vcard_parser.pyto extract the field - Update
src/db.pyupsert query - Update
src/api/schemas.pyif exposing via API
Changing the sync logic
- Edit
src/carddav_client.pyfor CardDAV protocol changes - Edit
src/sync.pyfor orchestration changes - Test with a single account first: set
LOG_LEVEL=DEBUG