mirror of
https://github.com/skoelle/icloud-contacts-sync.git
synced 2026-09-17 15:30:24 +00:00
initial commit
This commit is contained in:
@@ -1 +1,214 @@
|
||||
# icloud-contacts-sync
|
||||
|
||||
Synct alle Kontakte mehrerer iCloud-Accounts per CardDAV Delta-Sync
|
||||
(RFC 6578) automatisiert alle 15 Minuten in eine MariaDB-Datenbank
|
||||
(`mariadb.internal`), plus täglichem Mailversand der heutigen
|
||||
Geburtstage. Für den vollständigen technischen Hintergrund siehe
|
||||
[SPEC.md](./SPEC.md).
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Eine oder mehrere Apple-IDs mit aktivierter Zwei-Faktor-Authentifizierung.
|
||||
- Für jede Apple-ID ein app-spezifisches Passwort.
|
||||
- Eine erreichbare MariaDB-Instanz mit vorbereiteter Datenbank.
|
||||
- Ein SMTP-Relay (z. B. dein Mailprovider oder ein lokaler Relay) für
|
||||
den Geburtstags-Mailer.
|
||||
- Docker bzw. Docker Compose auf dem Zielhost (z. B. der Docker-Host auf
|
||||
deinem Proxmox-Host).
|
||||
|
||||
## 1. App-spezifische Passwörter erzeugen
|
||||
|
||||
Für jede Apple-ID, die du syncen willst:
|
||||
|
||||
1. Auf `account.apple.com` mit dieser Apple-ID anmelden.
|
||||
2. Zu "Anmelden & Sicherheit" → "App-spezifische Passwörter" gehen.
|
||||
3. Ein neues Passwort mit sprechendem Namen erzeugen (z. B.
|
||||
`contacts-sync-debian`) und sofort sichern.
|
||||
|
||||
## 2. Multi-User-Konfiguration anlegen
|
||||
|
||||
```
|
||||
cp config/accounts.yml.example config/accounts.yml
|
||||
vim config/accounts.yml
|
||||
```
|
||||
|
||||
Trage für jede Apple-ID einen Eintrag mit eindeutigem `name`,
|
||||
`apple_email` und `apple_app_password` ein. Diese Datei bleibt lokal
|
||||
auf dem Host, sie ist in `.gitignore` ausgeschlossen und wird nur als
|
||||
Volume in den Container gemountet.
|
||||
|
||||
## 3. Datenbank vorbereiten
|
||||
|
||||
```
|
||||
mysql -h mariadb.internal -u root -p < sql/schema.sql
|
||||
```
|
||||
|
||||
Falls Datenbank und Benutzer noch nicht existieren, vorher z. B.:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE contacts CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER 'contacts_sync'@'%' IDENTIFIED BY 'ein-sicheres-passwort';
|
||||
GRANT ALL PRIVILEGES ON contacts.* TO 'contacts_sync'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
## 4. Umgebungsvariablen konfigurieren
|
||||
|
||||
```
|
||||
cp .env.example .env
|
||||
vim .env
|
||||
```
|
||||
|
||||
Trage mindestens `MARIADB_USER`, `MARIADB_PASSWORD` sowie (falls du den
|
||||
Mailer nutzen willst) `SMTP_HOST`, `MAIL_FROM` und `MAIL_TO` ein.
|
||||
|
||||
## 5. Image beziehen
|
||||
|
||||
```
|
||||
docker login ghcr.io -u DEIN_GITHUB_USER
|
||||
```
|
||||
|
||||
Passe in `docker-compose.yml` den Image-Namen
|
||||
(`ghcr.io/DEIN_GITHUB_USER/icloud-contacts-sync:latest`) auf deinen
|
||||
tatsächlichen GitHub-Namespace an.
|
||||
|
||||
## 6. Starten
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Beim ersten Start wird für jeden Account automatisch ein vollständiger
|
||||
initialer Sync ausgeführt (kein gespeicherter sync-token vorhanden).
|
||||
Danach laufen alle 15 Minuten nur noch Delta-Syncs, die ausschließlich
|
||||
Änderungen seit dem letzten Lauf übertragen.
|
||||
|
||||
## 7. Logs und Status prüfen
|
||||
|
||||
```
|
||||
docker logs -f icloud-contacts-sync
|
||||
```
|
||||
|
||||
Sync-Historie je Account:
|
||||
|
||||
```sql
|
||||
SELECT account, sync_type, started_at, finished_at, status,
|
||||
contacts_upserted, contacts_deleted
|
||||
FROM sync_runs
|
||||
ORDER BY started_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
Aktueller Delta-Sync-Token je Account:
|
||||
|
||||
```sql
|
||||
SELECT account, sync_token, updated_at FROM sync_state;
|
||||
```
|
||||
|
||||
Versandhistorie der Geburtstagsmails:
|
||||
|
||||
```sql
|
||||
SELECT sent_date, contacts_count, sent_at FROM birthday_mail_log
|
||||
ORDER BY sent_date DESC LIMIT 10;
|
||||
```
|
||||
|
||||
## 8. Geburtstags-Mailer
|
||||
|
||||
- Läuft automatisch täglich um die in `MAIL_SEND_HOUR` konfigurierte
|
||||
Stunde (Default 7 Uhr) innerhalb desselben Containers.
|
||||
- Über `MAILER_ENABLED=false` lässt sich der Mailer ganz abschalten,
|
||||
ohne den Kontakt-Sync zu beeinträchtigen.
|
||||
- Manueller Testlauf im laufenden Container:
|
||||
```
|
||||
docker exec -it icloud-contacts-sync python3 /app/mailer.py
|
||||
```
|
||||
- Ein zweiter manueller Lauf am selben Tag versendet keine zweite Mail,
|
||||
solange bereits ein Eintrag in `birthday_mail_log` für heute existiert.
|
||||
|
||||
## 9. Lokale Entwicklung (ohne Docker)
|
||||
|
||||
```
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
cd src
|
||||
python3 sync.py
|
||||
python3 mailer.py
|
||||
```
|
||||
|
||||
## 10. CI/CD
|
||||
|
||||
- Jeder Push auf `main` baut automatisch ein neues Image und pusht es
|
||||
nach `ghcr.io/<owner>/icloud-contacts-sync`.
|
||||
- Ein separater Cleanup-Job behält jeweils nur die letzten 4 erfolgreich
|
||||
gebauten, getaggten Images.
|
||||
- Details siehe SPEC.md, Abschnitt 9.
|
||||
|
||||
## Bekannte Grenzen und geplante Erweiterungen
|
||||
|
||||
- Delta-Sync reduziert die übertragene Datenmenge stark, ersetzt aber
|
||||
keine vollständige Historie: ein gelöschter iCloud-Kontakt wird auch
|
||||
aus MariaDB entfernt, ohne Archiv.
|
||||
- Nur iCloud als Quelle, Google/Microsoft sind nicht Teil dieses Repos.
|
||||
- Eine separate Web-Ansicht mit API ist als eigenständiges,
|
||||
nachgelagertes Container-Projekt geplant, das nur lesend auf dieselbe
|
||||
MariaDB zugreift (siehe SPEC.md, Abschnitt 11).
|
||||
|
||||
|
||||
## 11. Web-Ansicht und API (interner Zugriff über Authelia)
|
||||
|
||||
Läuft als zweiter Service aus demselben Image, aber mit anderem
|
||||
Startbefehl, siehe `docker-compose.yml` (`icloud-contacts-api`). Die API
|
||||
selbst hat kein eigenes Login, sie vertraut vollständig dem
|
||||
vorgeschalteten Reverse-Proxy mit Authelia.
|
||||
|
||||
### Voraussetzung: Reverse-Proxy mit Authelia
|
||||
|
||||
Dein bestehender Reverse-Proxy muss für den Pfad/Host der
|
||||
Web-Ansicht einen `auth_request` gegen Authelia ausführen und danach
|
||||
den authentifizierten Benutzernamen im Header `Remote-User` an
|
||||
`127.0.0.1:8000` weiterreichen. Ein typischer nginx-Ausschnitt:
|
||||
|
||||
```
|
||||
location / {
|
||||
auth_request /authelia/verify;
|
||||
auth_request_set $user $upstream_http_remote_user;
|
||||
proxy_set_header Remote-User $user;
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
}
|
||||
```
|
||||
|
||||
Falls dein Setup den Benutzernamen unter einem anderen Header liefert,
|
||||
passe `AUTH_REMOTE_USER_HEADER` in der `.env` entsprechend an.
|
||||
|
||||
### Accounts-Mapping ergänzen
|
||||
|
||||
In `config/accounts.json` bekommt jeder Account zusätzlich ein Feld
|
||||
`authelia_user`:
|
||||
|
||||
```json
|
||||
{
|
||||
"accounts": [
|
||||
{ "name": "markus", "apple_email": "...", "apple_app_password": "...", "authelia_user": "mmustermann" }
|
||||
],
|
||||
"admins": ["mmustermann"]
|
||||
}
|
||||
```
|
||||
|
||||
Ein Benutzer aus `admins` sieht alle Accounts, alle anderen gemappten
|
||||
Benutzer sehen ausschließlich ihren eigenen Account.
|
||||
|
||||
### Starten
|
||||
|
||||
```
|
||||
docker compose up -d icloud-contacts-api
|
||||
```
|
||||
|
||||
Der Service läuft nur an `127.0.0.1:8000`, ein direkter externer
|
||||
Zugriff ohne den Reverse-Proxy ist damit nicht möglich.
|
||||
|
||||
### API kurz testen (lokal auf der Docker-Host, mit Header simuliert)
|
||||
|
||||
```
|
||||
curl -H "Remote-User: mmustermann" http://127.0.0.1:8000/api/contacts
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user