mirror of
https://github.com/skoelle/icloud-contacts-sync.git
synced 2026-09-17 15:30:24 +00:00
5.2 KiB
5.2 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)
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 |
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.jsonand.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: 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
- Add route in
src/api/main.py - Add Pydantic model in
src/api/schemas.pyif needed - Add DB query in
src/db.pyif needed - Update endpoint table in
SPEC.mdandREADME.md - 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 - Update
SPEC.md(data model section)
Changing the sync logic
- Edit
src/carddav_client.pyfor CardDAV protocol changes - Edit
src/sync.pyfor orchestration changes - Update
SPEC.mdif sync behavior changes - 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