From 8bd9dc1c036a8a366785f858dc93fe589178572a Mon Sep 17 00:00:00 2001 From: Stefan Koelle Date: Wed, 5 Aug 2026 02:23:10 +0200 Subject: [PATCH] AGENTS.md --- AGENTS.md | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3f624cb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,139 @@ +# 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: " http://127.0.0.1:8000/` + +### 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`