mirror of
https://github.com/skoelle/icloud-contacts-sync.git
synced 2026-09-17 15:30:24 +00:00
140 lines
4.6 KiB
Markdown
140 lines
4.6 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
ruff check src/
|
|
```
|
|
|
|
### Run sync locally (no Docker)
|
|
|
|
```bash
|
|
cd src && python3 sync.py
|
|
```
|
|
|
|
### Run mailer locally
|
|
|
|
```bash
|
|
cd src && python3 mailer.py
|
|
```
|
|
|
|
### Docker build
|
|
|
|
```bash
|
|
docker compose build
|
|
```
|
|
|
|
### Docker run
|
|
|
|
```bash
|
|
docker compose up -d
|
|
```
|
|
|
|
## 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.yml` 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: 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
|
|
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. 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
|
|
|
|
### Changing the sync logic
|
|
1. Edit `src/carddav_client.py` for CardDAV protocol changes
|
|
2. Edit `src/sync.py` for orchestration changes
|
|
3. Test with a single account first: set `LOG_LEVEL=DEBUG`
|