> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# التقويم وجهات الاتصال على سيرفرك مع CalDAV وCardDAV
- URL: https://arabroot.io/articles/التقويم-وجهات-الاتصال-caldav-و-carddav/
- Published: 2026-10-05T09:45:00.000Z
- Updated: 2026-10-09T09:42:01.000Z
- Description: كيف تبقى مواعيدك وجهات اتصالك متطابقة بين الهاتف والحاسوب على سيرفرك، وما الذي يعمل مع Outlook وما الذي لا يعمل.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الاستضافة الذاتية والتطبيقات, تطبيقات لفريقك, البريد الإلكتروني, الاستضافة الذاتية

عندما تضيف موعداً في تقويم هاتفك ثم تفتح حاسوبك فتجده هناك، وعندما يحفظ زميلك رقم عميل جديد فيظهر في جهات اتصال الفريق كله، فهناك سيرفر في مكان ما يحمل النسخة الأصلية من هذه البيانات، وكل جهاز من أجهزتك يسأله باستمرار: ما الذي تغير منذ آخر مرة؟ وفي أغلب الشركات يكون هذا السيرفر عند Google أو Microsoft، وهذا مريح إلى أن تقرر أن تستضيف بريدك بنفسك، أو تريد أن تبقى بيانات فريقك عندك، فتكتشف أن البريد انتقل وبقي التقويم وجهات الاتصال في الخلف.

وقد ذكرنا في [دليل استضافة البريد الإلكتروني للمؤسسات](https://arabroot.io/articles/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D9%84%D9%84%D9%85%D8%A4%D8%B3%D8%B3%D8%A7%D8%AA/) أن مزامنة البريد تنقل الرسائل فقط، وأن التقويم وجهات الاتصال تحتاج إلى خطوة مستقلة، وهذا المقال هو تلك الخطوة، حيث نشرح البروتوكولين اللذين تقوم عليهما مزامنة التقويم وجهات الاتصال خارج أنظمة Google وMicrosoft وهما CalDAV وCardDAV، ثم نشغل سيرفراً صغيراً ونرسل له الطلبات بأيدينا حتى ترى ما يحدث داخل كل مزامنة، ثم ننتقل إلى الجانب العملي: أي سيرفر تستضيف، وكيف تربط به الهاتف والحاسوب وOutlook، وكيف تنقل بياناتك القديمة. وسوف نناقش في هذا المقال ما يلي:

- المشكلة: تقويم وجهات اتصال محبوسة عند مزود واحد، وفريق يحتاج إلى تقاويم مشتركة وحجز قاعات، وأجهزة يجب أن تبقى متطابقة، ولماذا لا يحل تصدير ملف مرة واحدة هذه المشكلة.
- اللبنات الأساسية: ملفات iCalendar وvCard، ثم WebDAV وCalDAV وCardDAV، والاكتشاف التلقائي، وETag وsync-token، والدعوات عبر iTIP وiMIP.
- جولة مزامنة حقيقية بأوامر `curl` على سيرفر Radicale مع المخرج الفعلي لكل طلب.
- السيرفرات التي تستطيع أن تستضيفها: Radicale وBaïkal وNextcloud وSOGo داخل mailcow وStalwart وDAViCal.
- ربط الأجهزة: iPhone وMac وAndroid وThunderbird وسطح مكتب Linux، ثم Outlook بصراحة: ما الذي يعمل وما الذي لا يعمل.
- ميزات الفريق، والمناطق الزمنية والمواعيد المتكررة، والترحيل من Google وMicrosoft 365، والحماية، ثم جدول يساعدك على القرار.

## أين المشكلة في تقويم الفريق وجهات اتصاله؟

لنفرض أن لديك فريقاً من عشرين شخصاً، فهناك ثلاث حاجات منفصلة تجتمع في مكان واحد. الأولى أن بيانات كل شخص يجب أن تكون على كل أجهزته في الوقت نفسه، أي الهاتف والحاسوب في العمل والحاسوب في البيت وربما جهاز لوحي Tablet، وأي تعديل على أحدها يظهر على البقية خلال دقائق. والثانية أن الفريق يحتاج إلى أشياء مشتركة: تقويم للإجازات يراه الجميع، وتقويم لقاعة الاجتماعات حتى لا يحجزها شخصان في الوقت نفسه، ودفتر عناوين Address Book للعملاء والموردين يعدل فيه أكثر من شخص. والثالثة أن الموظفين يستخدمون برامج مختلفة، فهذا على iPhone، وذاك على Android، وثالث لا يتنازل عن Outlook.

وعند Google وMicrosoft 365 تحصل على كل ذلك دون أن تفكر فيه، والسبب أن المزود يملك السيرفر والتطبيقات معاً، ولكن هذا هو بالضبط ما يجعل البيانات محبوسة Vendor Lock-in، فتقويمك يعيش في نظام مصمم ليعمل مع تطبيقات هذا المزود أولاً، والخروج منه يكون بملف تصدير. والصورة التالية تبين الحالتين: أجهزة تتزامن مع سحابة مزود واحد ولا يخرج منها إلا ملف، ثم الأجهزة نفسها وهي تتزامن في الاتجاهين مع سيرفرك عبر معايير مفتوحة Open Standards يفهمها أي برنامج:

![مخطط يقارن هاتفاً وحاسوباً وOutlook متصلة بسحابة مزود عليها قفل ولا يخرج منها إلا ملف export.ics يمثل نسخة من يوم واحد، بالأجهزة نفسها وهي تتزامن في الاتجاهين مع سيرفر CalDAV وCardDAV خاص عليه تقويم وجهات اتصال](https://arabroot.io/content/images/2026/10/calendar-contacts-01-problem.webp)

عند المزود تخرج بياناتك ملفاً يتوقف عن التحديث، وعلى سيرفرك تبقى نسخة واحدة تتزامن معها كل الأجهزة

### لماذا لا يحل المشكلة أن تصدر ملفاً مرة واحدة؟

أول حل يخطر على البال هو أن تصدر Export التقويم من Google في ملف `.ics` وجهات الاتصال في ملف `.vcf` ثم تستوردها Import في كل جهاز، وهذا الحل ينقل البيانات فعلاً، ولكنه ينقل نسخة من يوم التصدير فقط، فالموعد الذي تضيفه غداً على الهاتف لن يظهر على الحاسوب، والرقم الذي يعدله زميلك لن يصل إليك، وبعد أسبوع يصبح لديك ثلاث نسخ مختلفة ولا تعرف أيها الصحيحة.

والحل الثاني أن تضع الملف في مجلد مشترك على Seafile أو Dropbox ويفتحه الجميع منه، وهو أفضل قليلاً لأن النسخة واحدة، ولكن التقويم ليس مستنداً يحرره شخص واحد، فإذا عدل شخصان الملف في الوقت نفسه فإن آخر من يحفظ يمسح تعديل الأول، والبرامج على الهاتف لا تقرأ ملفاً من مجلد مشترك أصلاً.

وما نحتاجه في الحقيقة هو المزامنة Synchronization، أي سيرفر يحفظ كل موعد وكل جهة اتصال كعنصر مستقل، ويعطي كل عنصر رقم نسخة يتغير عند كل تعديل، ويجيب كل جهاز عن سؤال واحد: ما الذي تغير منذ آخر مرة سألت؟ ويرفض التعديل إذا كان مبنياً على نسخة قديمة. وهذا بالضبط ما يقدمه CalDAV للتقويم وCardDAV لجهات الاتصال، وكلاهما معيار مفتوح من IETF تدعمه أجهزة Apple وThunderbird وتطبيقات Android وعشرات السيرفرات.

## اللبنات الأساسية: الملفات ثم البروتوكولات

قبل أن نصل إلى المزامنة نحتاج إلى أن نفهم طبقتين: صيغة الملف الذي يحمل الموعد أو جهة الاتصال، والبروتوكول Protocol الذي ينقل هذه الملفات بين الجهاز والسيرفر. والجميل في هذا الموضوع أن كل طبقة مبنية على التي تحتها، فإذا فهمت HTTP فقد فهمت نصف الطريق.

### صيغة الموعد iCalendar وصيغة جهة الاتصال vCard

الموعد في كل أنظمة التقويم تقريباً يحفظ ويتبادل بصيغة [iCalendar](https://datatracker.ietf.org/doc/html/rfc5545?ref=arabroot.io) المعرفة في RFC 5545 الصادر في سبتمبر 2009، وامتداد ملفاتها `.ics`، وهي ملف نصي من أسطر بالشكل `اسم:قيمة` داخل كتل تبدأ بـ `BEGIN` وتنتهي بـ `END`. والمثال التالي هو اجتماع أسبوعي سوف نستخدمه في التجربة العملية بعد قليل:

```ini
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//ArabRoot//Lab//EN
BEGIN:VEVENT
UID:weekly-standup@example.com
DTSTAMP:20261005T080000Z
DTSTART;TZID=Asia/Riyadh:20261006T090000
DTEND;TZID=Asia/Riyadh:20261006T093000
RRULE:FREQ=WEEKLY;BYDAY=TU
SUMMARY:Weekly stand-up
LOCATION:Room 2
END:VEVENT
END:VCALENDAR
```

في الملف أعلاه لاحظ التالي:

- الحقل `UID` هو هوية الموعد الثابتة، ولا يتغير مهما عدلت الموعد، وعليه يعتمد كل شيء لاحقاً: المزامنة، والرد على الدعوات، والترحيل بين السيرفرات، لذلك لا تقم بتغييره أبداً عند نقل البيانات.
- الوقت `DTSTART` مكتوب مع منطقته الزمنية Time Zone عبر `TZID=Asia/Riyadh`، أي التاسعة صباحاً بتوقيت الرياض، وليس التاسعة بتوقيت الجهاز الذي يقرأ الملف.
- السطر `RRULE` هو قاعدة التكرار Recurrence Rule، فالموعد يتكرر كل أسبوع يوم الثلاثاء، وهو موعد واحد في الملف وليس مئات المواعيد.
- المعيار يشترط أن ينتهي كل سطر بالرمزين CRLF، أي `\r\n`، لذلك سوف نكتب الملف في التجربة بالأمر `printf` وليس بمحرر نصوص قد يحفظ `\n` فقط.

أما جهة الاتصال فتحفظ بصيغة [vCard](https://datatracker.ietf.org/doc/html/rfc6350?ref=arabroot.io)، وإصدارها الحالي 4.0 معرف في RFC 6350 الصادر في أغسطس 2011، وامتداد ملفاتها `.vcf`، وهي على الطريقة نفسها: `BEGIN:VCARD`، ثم الاسم `FN` والبريد `EMAIL` والهاتف `TEL` والجهة `ORG`، ولكل بطاقة `UID` ثابت أيضاً. وهذه الملفات هي ما تنتجه أزرار التصدير في Google وOutlook، أي أن مشكلتنا ليست في الصيغة، فالصيغة موحدة منذ سنوات، وإنما في الطريقة التي تبقى بها هذه الملفات متطابقة بين الأجهزة.

### WebDAV: إدارة الملفات والمجلدات فوق HTTP

[بروتوكول HTTP](https://arabroot.io/articles/http-%D9%88-https-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/) الذي يعمل به الويب يعرف أن يجلب ملفاً بالطلب `GET` ويرفعه بالطلب `PUT` ويحذفه بالطلب `DELETE`، ولكنه لا يعرف أن يسرد محتويات مجلد، ولا أن ينشئ مجلداً، ولا أن يسأل عن خصائص ملف مثل اسمه المعروض أو تاريخ تعديله. ولهذا جاء [WebDAV](https://datatracker.ietf.org/doc/html/rfc4918?ref=arabroot.io)، واختصاره Web Distributed Authoring and Versioning، وإصداره الحالي في RFC 4918 الصادر في يونيو 2007، فأضاف إلى HTTP طلبات جديدة أهمها `PROPFIND` لقراءة الخصائص Properties وسرد محتويات المجلد، و`PROPPATCH` لتعديل الخصائص، و`MKCOL` لإنشاء مجلد، ويسمى المجلد في WebDAV مجموعة Collection. والرد على هذه الطلبات يكون بالرمز `207 Multi-Status` ومعه ملف XML فيه حالة كل عنصر.

وهذا هو البروتوكول نفسه الذي تستخدمه حين تفتح مكتبات Seafile من برنامج ملفات عبر WebDAV كما شرحنا في [دليل Seafile](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-seafile-%D9%84%D9%85%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA/)، أي أنك تتعامل معه غالباً دون أن تنتبه.

### CalDAV وCardDAV: تقويم وجهات اتصال فوق WebDAV

الفكرة التي بني عليها [CalDAV](https://datatracker.ietf.org/doc/html/rfc4791?ref=arabroot.io) المعرف في RFC 4791 الصادر في مارس 2007 بسيطة جداً: التقويم مجلد WebDAV، وكل موعد ملف `.ics` داخله. ثم أضاف ما يحتاجه التقويم تحديداً: الطلب `MKCALENDAR` لإنشاء تقويم، وتقارير REPORT تبحث في المواعيد، مثل `calendar-query` الذي يطلب مواعيد فترة محددة، و`calendar-multiget` الذي يجلب عدة مواعيد في طلب واحد. وعلى الفكرة نفسها جاء [CardDAV](https://datatracker.ietf.org/doc/html/rfc6352?ref=arabroot.io) في RFC 6352 الصادر في أغسطس 2011، فدفتر العناوين مجلد، وكل جهة اتصال ملف `.vcf`، ومعه التقرير `addressbook-query` للبحث. والصورة التالية تبين الطبقات من الأسفل إلى الأعلى، ثم السيرفر نفسه كما يراه الكلاينت Client، أي شجرة من العناوين URLs:

![مخطط طبقات: HTTPS في الأسفل ثم HTTP بطلبات GET وPUT وDELETE ثم WebDAV بطلبات PROPFIND وMKCOL وREPORT ثم CalDAV وفيه ملف standup.ics بصيغة iCalendar وCardDAV وفيه ملف sara.vcf بصيغة vCard، وبجانبها شجرة عناوين تبدأ من /.well-known/caldav إلى المستخدم /alice/ ثم التقويم /alice/work/ ودفتر العناوين /alice/contacts/](https://arabroot.io/content/images/2026/10/calendar-contacts-02-stack.webp)

كل تقويم مجلد، وكل موعد ملف داخله، وكل ذلك طلبات HTTP عادية

وقد يتساءل البعض: لماذا بني التقويم على WebDAV ولم يصمم له بروتوكول جديد؟ والإجابة أن HTTP يمر في كل شبكة وكل جدار ناري Firewall، ويعمل خلف أي Reverse Proxy، ويحمل معه TLS والمصادقة Authentication جاهزة، وبالتالي فإن سيرفر CalDAV هو في النهاية تطبيق ويب تنشره بالطريقة التي تنشر بها أي تطبيق آخر، على المنفذ 443 وخلف الشهادة نفسها.

### كيف يجد الجهاز تقويمك؟ الاكتشاف التلقائي Discovery

عندما تضيف حساباً في iPhone أو في DAVx⁵ فإنك تكتب غالباً اسم السيرفر والبريد وكلمة المرور فقط، ثم يجد الجهاز تقاويمك بنفسه، وهذا يمر بأربع خطوات معرفة في [RFC 6764](https://datatracker.ietf.org/doc/html/rfc6764?ref=arabroot.io) الصادر في فبراير 2013:

1. **سجل SRV في DNS:** يسأل الجهاز عن السجل `_caldavs._tcp.example.com` للتقويم و`_carddavs._tcp.example.com` لجهات الاتصال، فيعرف منه اسم السيرفر والمنفذ، وقد شرحنا هذا النوع من السجلات في [سجل SRV: أين تجد البرامج خدمة معينة](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#srv). وهذه الخطوة اختيارية، فإذا كتب المستخدم اسم السيرفر مباشرة تجاوزها الجهاز.
2. **العنوان المعروف Well-Known URL:** يطلب الجهاز `/.well-known/caldav` أو `/.well-known/carddav`، فيحوله السيرفر إلى المسار الحقيقي للخدمة، وهذه الخطوة هي سبب أغلب مشكلات الإعداد خلف Reverse Proxy كما سيأتي.
3. **الحساب Principal:** يسأل الجهاز السيرفر بالطلب `PROPFIND` عن الخاصية `current-user-principal`، أي ما عنوان المستخدم الذي دخلت به؟ والإجابة مسار مثل `/alice/`.
4. **مكان المجموعات Home Set:** يسأل عن `calendar-home-set` و`addressbook-home-set`، أي المجلد الذي فيه تقاويم هذا المستخدم ودفاتر عناوينه، ثم يسرد محتوياته فيجد كل تقويم وكل دفتر.

### رقم النسخة ETag، ورمز المزامنة sync-token

هنا قلب المزامنة كله. فكل ملف على السيرفر له ETag، وهو قيمة يحسبها السيرفر من محتوى الملف وتتغير عند أي تعديل عليه، فإذا احتفظ الهاتف بال ETag الذي رآه آخر مرة لكل موعد، عرف أن الموعد تغير بمجرد أن يختلف الرقم. ولكن السؤال عن ETag كل موعد في تقويم فيه ألفا موعد كل خمس دقائق مكلف، لذلك يحتاج الجهاز إلى طريقة يعرف بها أن المجلد كله لم يتغير.

والطريقة القديمة هي ctag، وهي خاصية على مستوى المجلد تتغير مع أي تعديل داخله، وهي ليست في أي RFC وإنما [امتداد من Apple](https://github.com/apple/ccs-calendarserver/blob/master/doc/Extensions/caldav-ctag.txt?ref=arabroot.io) انتشر حتى صار شبه معيار. والطريقة الحديثة هي [مزامنة المجموعات Collection Synchronization](https://datatracker.ietf.org/doc/html/rfc6578?ref=arabroot.io) في RFC 6578 الصادر في مارس 2012، حيث يعطي السيرفر الجهاز رمزاً اسمه sync-token مع كل مزامنة، وفي المرة التالية يرسله الجهاز في تقرير `sync-collection` فيرد السيرفر بالعناصر التي تغيرت أو حذفت منذ ذلك الرمز فقط، ومعها رمز جديد. وبالتالي فإن الجولة التي لا تغير فيها شيء تنتهي بطلب واحد ورد فارغ تقريباً، والجولة التي تغير فيها موعدان تجلب موعدين فقط.

ويبقى سؤال التعارض Conflict: ماذا لو عدل الهاتف والحاسوب الموعد نفسه في الوقت نفسه؟ والإجابة في الترويسة `If-Match` من HTTP، فالجهاز يرسل التعديل ومعه ال ETag الذي بنى عليه تعديله، فإذا كان الموعد قد تغير على السيرفر في هذه الأثناء رفض السيرفر الطلب بالرمز `412 Precondition Failed`، فيجلب الجهاز النسخة الجديدة ويقرر ماذا يفعل، بدلاً من أن يمسح تعديل غيره دون أن يعلم. وسوف ترى هذا كله بالأرقام الحقيقية في التجربة العملية.

### الدعوات: iTIP وiMIP والجدولة في CalDAV

الموعد الذي يحضره أكثر من شخص يحتاج إلى أكثر من التخزين، فالمنظم Organizer يرسل دعوة Invitation، والمدعو Attendee يقبل أو يعتذر، وعند التعديل أو الإلغاء يجب أن تصل الرسالة إلى الجميع. وهذا تنظمه ثلاثة معايير يكمل بعضها بعضاً، ويكفيك أن تعرف وظيفة كل واحد:

- [iTIP](https://datatracker.ietf.org/doc/html/rfc5546?ref=arabroot.io) في RFC 5546 الصادر في ديسمبر 2009: يحدد شكل الرسالة نفسها، فهي ملف iCalendar فيه السطر `METHOD:REQUEST` للدعوة، و`METHOD:REPLY` للرد، و`METHOD:CANCEL` للإلغاء، ولا يهتم بالطريق الذي تسلكه الرسالة.
- [iMIP](https://datatracker.ietf.org/doc/html/rfc6047?ref=arabroot.io) في RFC 6047 الصادر في ديسمبر 2010: يحمل رسالة iTIP داخل رسالة بريد عادية مرفق بها ملف `.ics`، وهذه هي الدعوة التي تصلك من شخص في شركة أخرى فتظهر في Gmail أو Outlook بأزرار قبول واعتذار.
- [الجدولة في CalDAV Scheduling Extensions](https://datatracker.ietf.org/doc/html/rfc6638?ref=arabroot.io) في RFC 6638 الصادر في يونيو 2012: يجعل السيرفر هو الذي يوزع الدعوات، فيحفظ المنظم الموعد مع قائمة المدعوين، ويضع السيرفر الدعوة في صندوق الجدولة schedule-inbox لكل مدعو على السيرفر نفسه، ويرسلها بالبريد عبر iMIP لمن هو خارجه.

والصورة التالية تبين الطريقين معاً، فالرسالة واحدة وهي iTIP، والذي يتغير هو الطريق:

![مخطط يبين Alice تحفظ اجتماعاً على سيرفر CalDAV مع مدعوين، فتصل الدعوة إلى Bob على السيرفر نفسه عبر صندوق الجدولة schedule-inbox ويعود رده بالقبول، وتخرج إلى Sam في مؤسسة أخرى رسالة بريد عبر SMTP فيها invite.ics مع METHOD:REQUEST ويعود رده برسالة METHOD:REPLY](https://arabroot.io/content/images/2026/10/calendar-contacts-04-invitations.webp)

داخل السيرفر تمر الدعوة عبر صندوق الجدولة، وإلى الخارج تخرج رسالة بريد تحمل ملف ics

ولاحظ أن الجزء الخارجي يعتمد على بريدك، فإذا كانت رسائل سيرفرك تصل إلى الرسائل المزعجة Spam فإن دعواتك تصل إليها أيضاً، لذلك فإن سجلات SPF وDKIM وDMARC التي شرحناها في دليل البريد شرط لعمل الدعوات مع العالم الخارجي كما هي شرط لعمل البريد. ولاحظ أيضاً أن بعض السيرفرات لا تطبق RFC 6638 أصلاً، فيبقى إرسال الدعوات على عاتق البرنامج في جهاز المستخدم، وهذا من أهم الفروق بين السيرفرات كما سيأتي.

### الاشتراك في رابط ics ليس مزامنة

هناك طريقة ثالثة سوف تقابلها كثيراً وهي الاشتراك Subscription في رابط تقويم، مثل تقويم الإجازات الرسمية أو جدول مباريات، فتعطي البرنامج رابطاً ينتهي بـ `.ics` أو يبدأ بـ `webcal://`، فيجلب البرنامج الملف كاملاً كل عدة ساعات ويعرضه. وهذا اشتراك للقراءة فقط Read-only، فلا تستطيع أن تضيف فيه موعداً ولا أن تعدل موعداً، والتحديث بطيء، فمثلاً تذكر Microsoft في [صفحة الاشتراك في التقويم](https://support.microsoft.com/en-us/outlook/import-or-subscribe-to-a-calendar-in-outlook-com-or-outlook-on-the-web?ref=arabroot.io) أن Outlook.com يحدث التقويم المشترك فيه كل ثلاث ساعات تقريباً، وOutlook على الويب كل ست ساعات تقريباً، وأن التحديث قد يتأخر أكثر من 24 ساعة. لذلك فالاشتراك مناسب لعرض تقويم يديره غيرك، وغير مناسب إطلاقاً لتقويم تعمل عليه، وسوف نرى أنه للأسف الطريق الوحيد مع بعض إصدارات Outlook.

## مزامنة حقيقية خطوة بخطوة بالأمر curl

الكلام عن ETag وsync-token يبقى نظرياً حتى ترى الطلبات بعينك، لذلك سوف نشغل سيرفر [Radicale](https://radicale.org/v3.html?ref=arabroot.io) صغيراً في Container على جهازك، ثم نقوم بما يقوم به الهاتف تماماً، ولكن بالأمر `curl`، طلباً بعد طلب. وكل ما تحتاجه هو Docker وcurl، والسيرفر يستمع على `127.0.0.1` فقط فلا يراه أحد غيرك.

### تشغيل السيرفر

أنشئ مجلداً فارغاً، وضع فيه ملف الإعداد التالي باسم `config`، وهو يحدد المنفذ، والمصادقة من ملف `htpasswd`، ومكان التخزين، وقاعدة الصلاحيات `owner_only` التي تعني أن كل مستخدم يرى مجموعاته فقط:

```ini
[server]
hosts = 0.0.0.0:5232

[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = autodetect

[storage]
filesystem_folder = /var/lib/radicale/collections

[rights]
type = owner_only
```

الآن نقوم بتشغيل Container من Image بايثون الرسمية، يثبت Radicale 3.8.1 بإصداره المحدد، وينشئ المستخدم `alice` بكلمة مرور مشفرة بخوارزمية bcrypt، ثم يشغل السيرفر:

```bash
docker run -d --name lab-caldav-radicale -p 127.0.0.1:18532:5232 \
  -v "$PWD/config:/etc/radicale/config:ro" python:3.13-alpine \
  sh -c "pip install --quiet radicale==3.8.1 bcrypt && apk add --quiet apache2-utils && htpasswd -B -b -c /etc/radicale/users alice 'Lab-Pass-2026' && exec python -m radicale --config /etc/radicale/config"
docker logs lab-caldav-radicale 2>&1 | tail -3
```

وبعد أقل من دقيقة سوف يكون المخرج كما يلي:

```bash
[2026-10-05 20:20:16 +0000] [1] [INFO] Listening on '0.0.0.0:5232'
[2026-10-05 20:20:16 +0000] [1] [INFO] Maximum parallel connections: 8
[2026-10-05 20:20:16 +0000] [1] [INFO] Radicale server ready
```

ولتختصر الأوامر التالية عرف متغيرين في الطرفية Terminal، الأول لاسم المستخدم وكلمة المرور، والثاني لعنوان السيرفر:

```bash
U='alice:Lab-Pass-2026'
B='http://127.0.0.1:18532'
```

### الاكتشاف: من أنا، وأين تقاويمي؟

نبدأ كما يبدأ الهاتف، بالعنوان المعروف، ثم بسؤال السيرفر عن حساب المستخدم، ثم عن مكان مجموعاته:

```bash
curl -s -i $B/.well-known/caldav | head -5

curl -s -u $U -X PROPFIND -H "Depth: 0" -H "Content-Type: application/xml" $B/ --data '<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d="DAV:"><d:prop><d:current-user-principal/></d:prop></d:propfind>'

curl -s -u $U -X PROPFIND -H "Depth: 0" -H "Content-Type: application/xml" $B/alice/ --data '<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:card="urn:ietf:params:xml:ns:carddav"><d:prop><c:calendar-home-set/><card:addressbook-home-set/></d:prop></d:propfind>'
```

والمخرج سوف يكون كما يلي، رد لكل طلب بالترتيب:

```bash
HTTP/1.0 301 Moved Permanently
Date: Mon, 05 Oct 2026 20:20:45 GMT
Server: WSGIServer/0.2 CPython/3.13.16
Location: /
Content-Type: text/plain; charset=utf-8

<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:"><response><href>/</href><propstat><prop><current-user-principal><href>/alice/</href></current-user-principal></prop><status>HTTP/1.1 200 OK</status></propstat></response></multistatus>

<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav"><response><href>/alice/</href><propstat><prop><C:calendar-home-set><href>/alice/</href></C:calendar-home-set><CR:addressbook-home-set><href>/alice/</href></CR:addressbook-home-set></prop><status>HTTP/1.1 200 OK</status></propstat></response></multistatus>
```

في المخرج أعلاه لاحظ التالي:

- العنوان `/.well-known/caldav` يحول إلى `/` بالرمز 301، فالخدمة في Radicale على المسار الرئيسي للموقع، وفي سيرفرات أخرى يحول إلى مسار مثل `/dav/`، والجهاز لا يحتاج إلى أن يعرف ذلك مسبقاً.
- الرد على `PROPFIND` هو ملف XML بالصيغة `multistatus`، وفيه لكل عنصر `href` و`propstat` بحالة HTTP خاصة به، وهذا شكل كل ردود WebDAV.
- الحساب هو `/alice/`، ومكان التقاويم ودفاتر العناوين هو المسار نفسه في Radicale، أي أن مجموعات المستخدم تكون مباشرة تحت حسابه.

### إنشاء تقويم ودفتر عناوين

الهاتف عادة لا ينشئ التقويم، وإنما ينشئه المستخدم من واجهة السيرفر، ولكن من المفيد أن ترى أن إنشاء التقويم طلب HTTP أيضاً، فالتقويم ينشأ بالطلب `MKCALENDAR`، ودفتر العناوين بالطلب `MKCOL` مع نوع المجموعة `addressbook`:

```bash
curl -s -i -u $U -X MKCALENDAR -H "Content-Type: application/xml" $B/alice/work/ --data '<?xml version="1.0" encoding="utf-8"?>
<c:mkcalendar xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><d:set><d:prop><d:displayname>Work</d:displayname><c:supported-calendar-component-set><c:comp name="VEVENT"/></c:supported-calendar-component-set></d:prop></d:set></c:mkcalendar>' | head -1

curl -s -i -u $U -X MKCOL -H "Content-Type: application/xml" $B/alice/contacts/ --data '<?xml version="1.0" encoding="utf-8"?>
<d:mkcol xmlns:d="DAV:" xmlns:card="urn:ietf:params:xml:ns:carddav"><d:set><d:prop><d:resourcetype><d:collection/><card:addressbook/></d:resourcetype><d:displayname>Contacts</d:displayname></d:prop></d:set></d:mkcol>' | head -1
```

والمخرج سوف يكون كما يلي:

```bash
HTTP/1.0 201 Created
HTTP/1.0 201 Created
```

### رفع أول موعد بالطلب PUT

الآن نكتب الاجتماع الأسبوعي الذي رأيناه في قسم iCalendar في ملف بنهايات أسطر CRLF، ثم نرفعه إلى التقويم بالطلب `PUT`، ونضيف الترويسة `If-None-Match: *` التي تعني: أنشئ الملف فقط إذا لم يكن موجوداً، حتى لا نمسح موعداً بالاسم نفسه دون قصد:

```bash
printf 'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//ArabRoot//Lab//EN\r\nBEGIN:VEVENT\r\nUID:weekly-standup@example.com\r\nDTSTAMP:20261005T080000Z\r\nDTSTART;TZID=Asia/Riyadh:20261006T090000\r\nDTEND;TZID=Asia/Riyadh:20261006T093000\r\nRRULE:FREQ=WEEKLY;BYDAY=TU\r\nSUMMARY:Weekly stand-up\r\nLOCATION:Room 2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n' > standup.ics

curl -s -i -u $U -X PUT -H "Content-Type: text/calendar; charset=utf-8" -H "If-None-Match: *" --data-binary @standup.ics $B/alice/work/standup.ics
```

والمخرج سوف يكون كما يلي:

```bash
HTTP/1.0 201 Created
Date: Mon, 05 Oct 2026 20:21:02 GMT
Server: WSGIServer/0.2 CPython/3.13.16
ETag: "33260a33a3749e04e83eda3fd53358f54888c059bf921a8b4f0229d5e9379652"
Content-Length: 0
```

ولاحظ أن السيرفر أعاد مع الرد ال ETag الخاص بالموعد، فالجهاز الذي رفع الموعد يحفظ هذه القيمة ليبني عليها أي تعديل قادم. والآن نسأل التقويم نفسه عن رقمي نسخته، أي ctag وsync-token:

```bash
curl -s -u $U -X PROPFIND -H "Depth: 0" -H "Content-Type: application/xml" $B/alice/work/ --data '<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/"><d:prop><d:displayname/><d:resourcetype/><cs:getctag/><d:sync-token/></d:prop></d:propfind>'
```

والمخرج سوف يكون كما يلي:

```bash
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/"><response><href>/alice/work/</href><propstat><prop><displayname>Work</displayname><resourcetype><C:calendar /><collection /></resourcetype><CS:getctag>"eb4451d8de850ac7944eabb58c9a61c76571e1e8b1434e9c0c055046ce965a7c"</CS:getctag><sync-token>http://radicale.org/ns/sync/f0a783fbd6b0dd21cd722837382e4be5dce5e26f12a17fc725684bbc6e94d913</sync-token></prop><status>HTTP/1.1 200 OK</status></propstat></response></multistatus>
```

والنوع `resourcetype` يقول إن هذا المجلد تقويم `calendar` وليس مجلداً عادياً، وهذا ما يجعل الهاتف يعرضه في قائمة التقاويم.

### جولة المزامنة: ما الذي تغير منذ آخر مرة؟

لنفرض أن الهاتف يتصل بالتقويم أول مرة، فهو يرسل تقرير `sync-collection` برمز فارغ، أي أعطني كل شيء:

```bash
curl -s -u $U -X REPORT -H "Depth: 1" -H "Content-Type: application/xml" $B/alice/work/ --data '<?xml version="1.0" encoding="utf-8"?>
<d:sync-collection xmlns:d="DAV:"><d:sync-token></d:sync-token><d:sync-level>1</d:sync-level><d:prop><d:getetag/></d:prop></d:sync-collection>'
```

والمخرج سوف يكون كما يلي:

```bash
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:"><sync-token>http://radicale.org/ns/sync/f0a783fbd6b0dd21cd722837382e4be5dce5e26f12a17fc725684bbc6e94d913</sync-token><response><href>/alice/work/standup.ics</href><propstat><prop><getetag>"33260a33a3749e04e83eda3fd53358f54888c059bf921a8b4f0229d5e9379652"</getetag></prop><status>HTTP/1.1 200 OK</status></propstat></response></multistatus>
```

فالهاتف عرف الآن أن في التقويم موعداً واحداً بال ETag الذي رأيناه عند الرفع، واحتفظ بالرمز الذي ينتهي بـ `f0a783fb…`، وسوف نسميه T1\. والآن لنفرض أن الحاسوب عدل مكان الاجتماع من Room 2 إلى Room 5، ثم أضاف موعداً جديداً، فالتعديل يرسل مع `If-Match` وال ETag القديم، والإضافة مع `If-None-Match`:

```bash
sed 's/LOCATION:Room 2/LOCATION:Room 5/' standup.ics > standup2.ics
curl -s -i -u $U -X PUT -H "Content-Type: text/calendar; charset=utf-8" -H 'If-Match: "33260a33a3749e04e83eda3fd53358f54888c059bf921a8b4f0229d5e9379652"' --data-binary @standup2.ics $B/alice/work/standup.ics | grep -E "HTTP|ETag"

printf 'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//ArabRoot//Lab//EN\r\nBEGIN:VEVENT\r\nUID:launch-review@example.com\r\nDTSTAMP:20261005T081000Z\r\nDTSTART:20261008T110000Z\r\nDTEND:20261008T120000Z\r\nSUMMARY:Launch review\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n' > review.ics
curl -s -o /dev/null -w "%{http_code}\n" -u $U -X PUT -H "Content-Type: text/calendar; charset=utf-8" -H "If-None-Match: *" --data-binary @review.ics $B/alice/work/review.ics
```

والمخرج سوف يكون كما يلي:

```bash
HTTP/1.0 204 No Content
ETag: "17521cb92ba2d39b6ad86693440e88f50c5e15cd711d8975251ebb8cac9b62f1"
201
```

الآن يعود الهاتف في موعد مزامنته التالي ويرسل الرمز T1 الذي احتفظ به:

```bash
T1='http://radicale.org/ns/sync/f0a783fbd6b0dd21cd722837382e4be5dce5e26f12a17fc725684bbc6e94d913'
curl -s -u $U -X REPORT -H "Depth: 1" -H "Content-Type: application/xml" $B/alice/work/ --data "<?xml version=\"1.0\" encoding=\"utf-8\"?>
<d:sync-collection xmlns:d=\"DAV:\"><d:sync-token>$T1</d:sync-token><d:sync-level>1</d:sync-level><d:prop><d:getetag/></d:prop></d:sync-collection>"
```

والمخرج سوف يكون كما يلي:

```bash
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:"><sync-token>http://radicale.org/ns/sync/f7eec23c6fb140b4cbe9e9664d15fd59d276ca2b22e07e1e97cd22eda6ba3ecd</sync-token><response><href>/alice/work/review.ics</href><propstat><prop><getetag>"43739ab836b3f6a159407c4bddf9d7a6661afa106102b1ef57a92f8405d4815d"</getetag></prop><status>HTTP/1.1 200 OK</status></propstat></response><response><href>/alice/work/standup.ics</href><propstat><prop><getetag>"17521cb92ba2d39b6ad86693440e88f50c5e15cd711d8975251ebb8cac9b62f1"</getetag></prop><status>HTTP/1.1 200 OK</status></propstat></response></multistatus>
```

في المخرج أعلاه لاحظ التالي:

- السيرفر لم يرسل التقويم كله، وإنما العنصرين اللذين تغيرا منذ T1 فقط: الموعد الجديد `review.ics`، والاجتماع الذي تغير ETag الخاص به من `33260a33…` إلى `17521cb9…`.
- الرد يحمل رمزاً جديداً ينتهي بـ `f7eec23c…`، وسوف نسميه T2، فيحفظه الهاتف مكان T1 ليسأل به في المرة القادمة.
- الرد فيه أرقام النسخ فقط وليس محتوى المواعيد، والسبب أن الهاتف قد يكون عنده بعضها بالفعل، فهو يقارن ثم يجلب ما ينقصه.

والصورة التالية تجمع الجولة كلها بالقيم نفسها، من السؤال عن التغييرات إلى جلب المتغير ثم الكتابة مع `If-Match`:

![مخطط تسلسلي بين هاتف وسيرفر من خمس خطوات: الهاتف يرسل REPORT sync-collection بالرمز T1، والسيرفر يرد 207 بالرمز الجديد T2 وقائمة بالملفات التي تغيرت أو أضيفت أو حذفت مع ETag لكل منها، ثم يطلب الهاتف calendar-multiget لملفين، فيرد السيرفر بمحتواهما، ثم يرسل الهاتف PUT مع If-Match فإما 204 مع ETag جديد وإما 412 إذا سبقه أحد بالتعديل](https://arabroot.io/content/images/2026/10/calendar-contacts-03-sync.webp)

رمز المزامنة يحول السؤال من «أعطني كل شيء» إلى «ما الذي تغير منذ آخر مرة؟»

### جلب المتغير فقط بالتقرير calendar-multiget

الآن يطلب الهاتف محتوى الموعد الذي تغير، وفي الواقع يطلب كل ما تغير في طلب واحد مهما كان عدده:

```bash
curl -s -u $U -X REPORT -H "Depth: 1" -H "Content-Type: application/xml" $B/alice/work/ --data '<?xml version="1.0" encoding="utf-8"?>
<c:calendar-multiget xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><d:prop><d:getetag/><c:calendar-data/></d:prop><d:href>/alice/work/standup.ics</d:href></c:calendar-multiget>'
```

والمخرج سوف يكون كما يلي:

```bash
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"><response><href>/alice/work/standup.ics</href><propstat><prop><getetag>"17521cb92ba2d39b6ad86693440e88f50c5e15cd711d8975251ebb8cac9b62f1"</getetag><C:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//ArabRoot//Lab//EN
BEGIN:VTIMEZONE
TZID:Asia/Riyadh
BEGIN:STANDARD
DTSTART:20000101T000000
RRULE:FREQ=YEARLY;BYMONTH=1
TZNAME:+03
TZOFFSETFROM:+0300
TZOFFSETTO:+0300
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:weekly-standup@example.com
DTSTART;TZID=Asia/Riyadh:20261006T090000
DTEND;TZID=Asia/Riyadh:20261006T093000
DTSTAMP:20261005T080000Z
LOCATION:Room 5
RRULE:FREQ=WEEKLY;BYDAY=TU
SUMMARY:Weekly stand-up
END:VEVENT
END:VCALENDAR
</C:calendar-data></prop><status>HTTP/1.1 200 OK</status></propstat></response></multistatus>
```

والملاحظة المهمة هنا أن الملف الذي عاد ليس هو الملف الذي أرسلناه حرفياً، فقد أضاف إليه السيرفر الكتلة `VTIMEZONE` التي تعرف المنطقة `Asia/Riyadh` وفرقها عن UTC وهو `+0300`، والسبب أن المعيار يشترط أن يحمل الملف تعريف كل منطقة زمنية يذكرها، ونحن لم نكتب هذا التعريف. وRadicale أصلح ذلك، ولكن لا تعتمد على أن كل سيرفر وكل برنامج يصلحه، فالموعد الذي يصل دون تعريف لمنطقته قد يظهر عند بعض البرامج في ساعة خاطئة، وهذا أصل كثير من مشكلات «الموعد انتقل ساعة» التي سوف نعود إليها. ولاحظ أيضاً أن المكان أصبح `Room 5`، أي أن تعديل الحاسوب وصل إلى الهاتف.

بعد ذلك نحذف الموعد الثاني، ونرى كيف يعرف الهاتف بالحذف، فاختفاء الملف من السيرفر لا يراه الهاتف وحده، والذي يحدث أن السيرفر يخبر الأجهزة صراحة أنه حذف:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -u $U -X DELETE $B/alice/work/review.ics

T2='http://radicale.org/ns/sync/f7eec23c6fb140b4cbe9e9664d15fd59d276ca2b22e07e1e97cd22eda6ba3ecd'
curl -s -u $U -X REPORT -H "Depth: 1" -H "Content-Type: application/xml" $B/alice/work/ --data "<?xml version=\"1.0\" encoding=\"utf-8\"?>
<d:sync-collection xmlns:d=\"DAV:\"><d:sync-token>$T2</d:sync-token><d:sync-level>1</d:sync-level><d:prop><d:getetag/></d:prop></d:sync-collection>"
```

والمخرج سوف يكون كما يلي:

```bash
200
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:"><sync-token>http://radicale.org/ns/sync/3d747676e24e875fb5cacc26cceab6923ada7476a8cc6c3c9f09ac10f1181b97</sync-token><response><href>/alice/work/review.ics</href><status>HTTP/1.1 404 Not Found</status></response></multistatus>
```

فالعنصر المحذوف يظهر في الرد بالحالة `404 Not Found`، فيحذفه الهاتف من نسخته، وهذا ما لا يستطيعه ملف تصدير أبداً، فالملف يخبرك بما هو موجود فقط ولا يخبرك بما حذف.

### البحث بالتاريخ والمواعيد المتكررة

عندما تفتح عرض يوم واحد في التقويم على الويب، فالبرنامج لا يحتاج إلى كل المواعيد، وإنما إلى مواعيد هذا اليوم فقط، وهذا هو التقرير `calendar-query` مع فترة زمنية `time-range`. ولنسأل عن يوم 20 أكتوبر 2026 وهو يوم ثلاثاء، ثم عن اليوم الذي بعده:

```bash
curl -s -u $U -X REPORT -H "Depth: 1" -H "Content-Type: application/xml" $B/alice/work/ --data '<?xml version="1.0" encoding="utf-8"?>
<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><d:prop><d:getetag/></d:prop><c:filter><c:comp-filter name="VCALENDAR"><c:comp-filter name="VEVENT"><c:time-range start="20261020T000000Z" end="20261021T000000Z"/></c:comp-filter></c:comp-filter></c:filter></c:calendar-query>'

curl -s -u $U -X REPORT -H "Depth: 1" -H "Content-Type: application/xml" $B/alice/work/ --data '<?xml version="1.0" encoding="utf-8"?>
<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><d:prop><d:getetag/></d:prop><c:filter><c:comp-filter name="VCALENDAR"><c:comp-filter name="VEVENT"><c:time-range start="20261021T000000Z" end="20261022T000000Z"/></c:comp-filter></c:comp-filter></c:filter></c:calendar-query>'
```

والمخرج سوف يكون كما يلي:

```bash
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:"><response><href>/alice/work/standup.ics</href><propstat><prop><getetag>"17521cb92ba2d39b6ad86693440e88f50c5e15cd711d8975251ebb8cac9b62f1"</getetag></prop><status>HTTP/1.1 200 OK</status></propstat></response></multistatus>
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:" />
```

والاجتماع لم نحفظه إلا مرة واحدة بتاريخ 6 أكتوبر، ومع ذلك ظهر في يوم 20 أكتوبر، لأن السيرفر طبق قاعدة `RRULE` وعرف أن الثلاثاء 20 أكتوبر هو التكرار الثالث، أما الأربعاء 21 أكتوبر فلا يوجد فيه أي موعد. وهذا يبين لماذا يجب أن يفهم السيرفر التكرار والمناطق الزمنية فهماً صحيحاً، فهو الذي يجيب عن سؤال «ما مواعيد هذا اليوم؟» وليس الجهاز وحده.

### اكسرها لتفهمها: التعديل على نسخة قديمة، والطلب دون كلمة مرور

لنفرض أن جهازاً ثالثاً كان مغلقاً طوال الوقت، وما زال يظن أن ETag الاجتماع هو القيمة الأولى `33260a33…`، ثم حاول أن يحفظ تعديلاً عليه:

```bash
curl -s -i -u $U -X PUT -H "Content-Type: text/calendar; charset=utf-8" -H 'If-Match: "33260a33a3749e04e83eda3fd53358f54888c059bf921a8b4f0229d5e9379652"' --data-binary @standup.ics $B/alice/work/standup.ics | head -1
curl -s -i -X PROPFIND $B/alice/ | head -1
curl -s -i -X PROPFIND $B/alice/ | grep -i www-auth
```

والمخرج سوف يكون كما يلي:

```bash
HTTP/1.0 412 Precondition Failed
HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="Radicale - Password Required"
```

الرد الأول `412` هو حماية بيانات الفريق، فالسيرفر رفض أن يمسح تعديل الحاسوب بنسخة قديمة، والبرنامج الجيد عندما يرى هذا الرد يجلب النسخة الحالية ويعرض عليك التعارض أو يدمج التغيير. والرد الثاني والثالث يبينان أن الطلب دون كلمة مرور يرفض، وأن طريقة المصادقة هي Basic، أي أن اسم المستخدم وكلمة المرور يرسلان مع كل طلب بترميز Base64 وليس بتشفير، وهذا وحده سبب كاف لأن لا تنشر سيرفر CalDAV إلا عبر HTTPS كما سيأتي في قسم الحماية.

### جهات الاتصال بالطريقة نفسها

جهات الاتصال تعمل بالطريقة نفسها تماماً، والفرق في صيغة الملف وفي اسم التقرير، فنرفع بطاقة vCard ثم نبحث في دفتر العناوين عن كل من بريده في النطاق `example.com`:

```bash
printf 'BEGIN:VCARD\r\nVERSION:4.0\r\nUID:urn:uuid:4f9b2c1e-7a35-4d8e-9b61-2c0d3e5f7a10\r\nFN:Sara Ahmed\r\nN:Ahmed;Sara;;;\r\nEMAIL;TYPE=work:sara@example.com\r\nTEL;TYPE=cell:+966500000000\r\nORG:Example Co\r\nEND:VCARD\r\n' > sara.vcf
curl -s -i -u $U -X PUT -H "Content-Type: text/vcard; charset=utf-8" -H "If-None-Match: *" --data-binary @sara.vcf $B/alice/contacts/sara.vcf | grep -E "HTTP|ETag"

curl -s -u $U -X REPORT -H "Depth: 1" -H "Content-Type: application/xml" $B/alice/contacts/ --data '<?xml version="1.0" encoding="utf-8"?>
<card:addressbook-query xmlns:d="DAV:" xmlns:card="urn:ietf:params:xml:ns:carddav"><d:prop><d:getetag/><card:address-data/></d:prop><card:filter><card:prop-filter name="EMAIL"><card:text-match match-type="contains">example.com</card:text-match></card:prop-filter></card:filter></card:addressbook-query>'
```

والمخرج سوف يكون كما يلي:

```bash
HTTP/1.0 201 Created
ETag: "b500633ceba97e7ffd6deca931d8ef93bb70c60bbc1d4dcc791b4a50f62677a0"
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:" xmlns:CR="urn:ietf:params:xml:ns:carddav"><response><href>/alice/contacts/sara.vcf</href><propstat><prop><getetag>"b500633ceba97e7ffd6deca931d8ef93bb70c60bbc1d4dcc791b4a50f62677a0"</getetag><CR:address-data>BEGIN:VCARD
VERSION:4.0
UID:urn:uuid:4f9b2c1e-7a35-4d8e-9b61-2c0d3e5f7a10
EMAIL;TYPE=work:sara@example.com
FN:Sara Ahmed
N:Ahmed;Sara;;;
ORG:Example Co
TEL;TYPE=cell:+966500000000
END:VCARD
</CR:address-data></prop><status>HTTP/1.1 200 OK</status></propstat></response></multistatus>
```

وهذا التقرير هو ما يستخدمه برنامج البريد عندما تكتب أول حروف من اسم زميلك في خانة المرسل إليه، فيبحث في دفتر العناوين على السيرفر بدلاً من أن ينزله كاملاً.

### التقويم كملف واحد للاشتراك

وأخيراً، إذا طلبت مجلد التقويم نفسه بالطلب `GET` فإن Radicale يرد بالتقويم كله في ملف `.ics` واحد، وهذا هو الرابط الذي تعطيه لبرنامج لا يعرف CalDAV حتى يشترك فيه للقراءة:

```bash
curl -s -i -u $U $B/alice/work/ | head -12
```

والمخرج سوف يكون كما يلي:

```bash
HTTP/1.0 200 OK
Date: Mon, 05 Oct 2026 20:22:06 GMT
Server: WSGIServer/0.2 CPython/3.13.16
Content-Type: text/calendar; charset=utf-8
Last-Modified: Mon, 05 Oct 2026 20:21:53 GMT
ETag: "496f79691ab2b242c5295b79f71ae6fd8539070aab75d59e8ea0d6d53c32c7d5"
Content-Disposition: attachment; filename*=utf-8''Work.ics
Content-Length: 543

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//PYVOBJECT//NONSGML Version 0.9.9//EN
```

وهذا الملف نفسه هو أبسط نسخة احتياطية Backup لتقويم مستخدم واحد، ولكنه نسخة من لحظة الطلب كما ذكرنا في بداية المقال.

وعند الانتهاء من التجربة احذف ال Container وال Image التي نزلها حتى لا يبقى أي أثر على جهازك:

```bash
docker rm -f lab-caldav-radicale
docker rmi python:3.13-alpine
```

## أي سيرفر تستضيف؟

كل السيرفرات التالية تتكلم CalDAV وCardDAV، أي أن الهاتف لا يفرق بينها، والفرق في ما حول البروتوكول: هل فيها واجهة ويب يرى منها المستخدم تقويمه؟ هل توزع الدعوات بنفسها؟ هل تحجز القاعات؟ وهل هي جزء من خادم بريد عندك أصلاً؟ ولهذا فالسؤال الأول قبل أن تختار: هل تستضيف بريدك بنفسك؟ فإذا كان الجواب نعم فالأغلب أن خادم بريدك يقدم التقويم وجهات الاتصال، ولا داعي لسيرفر آخر.

### Radicale: الأصغر والأبسط

[Radicale](https://github.com/Kozea/Radicale?ref=arabroot.io) هو السيرفر الذي استخدمناه في التجربة، وهو برنامج بايثون مرخص بالرخصة GPLv3، وآخر إصدار له [3.8.1](https://github.com/Kozea/Radicale/releases/tag/v3.8.1?ref=arabroot.io) في سبتمبر 2026، ويحفظ كل شيء ملفات في مجلد دون قاعدة بيانات، ويقبل المصادقة من ملف htpasswd أو LDAP أو IMAP أو OAuth2 بحسب [وثائقه](https://radicale.org/v3.html?ref=arabroot.io).

- **نقاط القوة:** يعمل في دقائق ويستهلك موارد قليلة جداً، ونسخه الاحتياطي نسخ مجلد واحد، وفيه منذ الإصدار 3.7 مشاركة المجموعات بين المستخدمين بعد تفعيلها في الإعداد، وواجهة ويب بسيطة لإنشاء التقاويم ودفاتر العناوين.
- **نقاط الضعف:** لا توجد فيه واجهة ويب يرى منها المستخدم مواعيده، ولا يطبق الجدولة في RFC 6638، فالدعوات يرسلها برنامج المستخدم بنفسه، ولا يوجد فيه حجز قاعات أو وقت متاح Free/Busy للفريق.
- **يناسب:** شخصاً أو عائلة أو فريقاً صغيراً جداً يريد مزامنة الهواتف والحواسيب فقط.

### Baïkal: واجهة إدارة فوق sabre/dav

[Baïkal](https://sabre.io/baikal/?ref=arabroot.io) سيرفر PHP مبني على مكتبة sabre/dav المعروفة، ومرخص بالرخصة GPLv3، وآخر إصدار له [0.12.1](https://github.com/sabre-io/Baikal/releases?ref=arabroot.io) في أغسطس 2026، ويحفظ بياناته في SQLite أو MySQL أو PostgreSQL.

- **نقاط القوة:** لوحة إدارة في المتصفح تضيف منها المستخدمين والتقاويم ودفاتر العناوين دون ملفات إعداد، ويعمل على أي استضافة فيها PHP.
- **نقاط الضعف:** اللوحة للمدير فقط، فلا يوجد تقويم يفتحه المستخدم في المتصفح، ولا توجد فيه أدوات لحجز القاعات أو عرض الوقت المتاح للفريق.
- **يناسب:** فريقاً صغيراً يريد سيرفر مزامنة خفيفاً يديره شخص واحد من المتصفح.

### Nextcloud: تقويم كامل داخل منصة كبيرة

تطبيقا [Calendar](https://apps.nextcloud.com/apps/calendar?ref=arabroot.io) وContacts في Nextcloud يعطيان المستخدم تقويماً كاملاً في المتصفح، ومشاركة التقاويم مع المستخدمين والمجموعات، وروابط عامة للقراءة، والاشتراك في روابط ics، ودعوات بالبريد، وعرض الوقت المتاح للمدعوين، وحجز القاعات والموارد دون تعارض، بحسب [وثائق التقويم](https://docs.nextcloud.com/server/latest/user%5Fmanual/en/groupware/calendar.html?ref=arabroot.io).

- **نقاط القوة:** أكمل الخيارات المستقلة عن البريد في ميزات الفريق، ومعه الملفات والتطبيقات الأخرى في منصة واحدة.
- **نقاط الضعف:** منصة كبيرة تحتاج إلى قاعدة بيانات وذاكرة وصيانة مستمرة، وهذا ثقيل إذا كانت حاجتك التقويم وحده، وقد ذكرنا في [دليل Seafile](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-seafile-%D9%84%D9%85%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA/) لماذا نفضل أداة متخصصة للملفات.
- **يناسب:** فريقاً قرر أن يعتمد Nextcloud لأسباب أخرى، فيحصل على التقويم معه.

### SOGo داخل mailcow

إذا ثبت [خادم البريد mailcow](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A8%D8%B1%D9%8A%D8%AF-mailcow/) فعندك [SOGo](https://www.sogo.nu/?ref=arabroot.io) بالفعل، وهو بريد عبر الويب Webmail مع تقويم وجهات اتصال، ويتكلم CalDAV وCardDAV، ويدعم موارد مثل القاعات تقبل الدعوات تلقائياً بحسب [دليل تثبيته](https://www.sogo.nu/files/docs/SOGoInstallationGuide.html?ref=arabroot.io)، ويقدم ActiveSync للهواتف. ورخصته GPL، أما ActiveSync فيه فيذكر الدليل نفسه أن استخدامه في بيئة الإنتاج Production يحتاج إلى ترخيص استخدام من Microsoft، والسبب أن Microsoft تملك حقوق هذا البروتوكول، فاقرأ هذه الفقرة قبل أن تبني خطتك عليه.

- **نقاط القوة:** لا يوجد سيرفر إضافي تثبته، فالمستخدم يجد بريده وتقويمه وجهات اتصاله في صفحة واحدة بالحساب نفسه، ويعطيك mailcow [ملف إعداد Configuration Profile](https://docs.mailcow.email/client/client-apple/?ref=arabroot.io) يضبط iPhone وMac بخطوة واحدة.
- **نقاط الضعف:** مرتبط بخادم البريد، فإذا تعطل mailcow تعطل التقويم معه، واستهلاكه للذاكرة يزيد مع عدد الهواتف المتصلة.
- **يناسب:** كل فريق يستضيف بريده على mailcow.

### Stalwart: التقويم داخل خادم البريد نفسه

شرحنا في [دليل Stalwart](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-stalwart/) أنه خادم بريد في برنامج واحد، وفيه أيضاً CalDAV وCardDAV وWebDAV للملفات، وJMAP للتقويم وجهات الاتصال، ويطبق [الجدولة](https://stalw.art/docs/collaboration/scheduling?ref=arabroot.io) في RFC 6638، فيضع الدعوة في صندوق المدعو إذا كان على السيرفر نفسه، ويرسلها بريداً مع ملف ics إذا كان خارجه، ويقبل الردود.

- **نقاط القوة:** كل شيء في برنامج واحد بقاعدة بيانات واحدة، والجدولة ودعوات البريد مدمجة دون إعداد إضافي.
- **نقاط الضعف:** لا يتضمن بريداً عبر الويب ولا تقويماً في المتصفح، فالمستخدم يعمل من برامج الهاتف والحاسوب فقط، وبعض الميزات مثل تخصيص قوالب رسائل الدعوات للإصدار التجاري Enterprise.
- **يناسب:** فريقاً يستضيف بريده على Stalwart ويعمل من البرامج والهواتف.

### DAViCal: الأقدم

[DAViCal](https://www.davical.org/?ref=arabroot.io) سيرفر CalDAV وCardDAV قديم مكتوب بلغة PHP على قاعدة بيانات PostgreSQL، وما زال يصدر تحديثات، فآخر إصدار له 1.1.13 في مايو 2026\. ولن نتوسع فيه، والسبب أن الخيارات السابقة تغطي حالاته بإعداد أبسط، ولكنه موجود إذا ورثت سيرفراً يعمل به.

## ربط الأجهزة: الهاتف والحاسوب وOutlook

هذا هو الجزء الذي يقرر نجاح المشروع أو فشله، فالموظف لا يرى السيرفر، وإنما يرى هاتفه وبرنامجه. والخبر الجيد أن أغلب الأجهزة تتكلم CalDAV وCardDAV دون أي إضافة، والخبر السيئ هو Outlook. والصورة التالية تبين كيف يتصل كل جهاز:

![مخطط يضع سيرفر التقويم في الوسط، وعلى يمينه iPhone وiPad وMac بدعم أصلي في الاتجاهين، وAndroid عبر تطبيق DAVx5 في الاتجاهين، وThunderbird وGNOME وKDE بدعم أصلي، وعلى يساره Outlook الكلاسيكي على Windows عبر إضافة CalDav Synchronizer في الاتجاهين، وOutlook الجديد وOutlook على الويب باشتراك ics للقراءة فقط، والهواتف عبر ActiveSync إذا كان السيرفر SOGo مع ملاحظة أنه لا يعمل مع Outlook 2019 وما بعده](https://arabroot.io/content/images/2026/10/calendar-contacts-05-clients.webp)

الأخضر يعمل كما تتوقع، والبرتقالي يحتاج إلى إضافة، والرمادي اشتراك للقراءة فقط

### iPhone وiPad وMac: دعم أصلي

أجهزة Apple تدعم CalDAV وCardDAV في نظامها مباشرة، ففي iPhone تضيف الحساب من إعدادات التطبيقات باختيار Add Other Account ثم حساب CalDAV أو CardDAV بحسب [دليل Apple](https://support.apple.com/guide/iphone/set-up-mail-contacts-and-calendar-accounts-ipha0d932e96/ios?ref=arabroot.io)، وفي Mac من تطبيق Calendar باختيار [Other CalDAV Account](https://support.apple.com/guide/calendar/add-or-delete-calendar-accounts-icl4308d6701/mac?ref=arabroot.io). وإذا كان الاكتشاف التلقائي مضبوطاً فيكفي اسم السيرفر والبريد وكلمة المرور، وإلا فاكتب العنوان الكامل للخدمة. ومع mailcow الأسهل أن يدخل المستخدم إلى الواجهة وينزل ملف الإعداد الذي يضبط البريد والتقويم وجهات الاتصال معاً.

### Android: تطبيق DAVx⁵

نظام Android لا يدعم CalDAV وCardDAV في أصله، والحل المعتمد هو تطبيق [DAVx⁵](https://www.davx5.com/download?ref=arabroot.io)، وهو مفتوح المصدر بالرخصة GPLv3، ويباع بسعر رمزي على Google Play دعماً لتطويره، ومجاني على [F-Droid](https://f-droid.org/packages/at.bitfire.davdroid/?ref=arabroot.io) الذي يبنيه من الكود المصدري. والتطبيق لا يعرض المواعيد بنفسه، وإنما يزامن الحساب مع تقويم الهاتف وجهات اتصاله، فتظهر المواعيد في أي تطبيق تقويم تستخدمه. ومن الفريق نفسه تطبيق [ICSx⁵](https://icsx5.bitfire.at/?ref=arabroot.io) للاشتراك في روابط ics للقراءة. ولاحظ أن mailcow يعرض في [صفحة Android](https://docs.mailcow.email/client/client-android/?ref=arabroot.io) طريقة ActiveSync، وهي تعمل أيضاً، ولكن DAVx⁵ يعمل مع أي سيرفر في هذا المقال.

### Thunderbird وسطح مكتب Linux

برنامج Thunderbird يدعم التقويم عبر CalDAV منذ سنوات، وأضاف دفاتر العناوين عبر CardDAV في [الإصدار 91](https://www.thunderbird.net/en-US/thunderbird/91.0/releasenotes/?ref=arabroot.io) مع اكتشافها تلقائياً من بيانات الحساب، وبالتالي فهو أقرب برنامج على Windows إلى تجربة Outlook مع سيرفرك. وفي GNOME أضاف [الإصدار 46](https://release.gnome.org/46/?ref=arabroot.io) نوع حساب WebDAV عاماً للتقويم وجهات الاتصال والملفات، وفي KDE يتصل Kontact بالسيرفر عبر مصدر DAV groupware.

### Outlook: ما الذي يعمل وما الذي لا يعمل؟

وهنا نحتاج إلى الصراحة، لأن أغلب الشركات التي تنتقل من Microsoft 365 فيها موظفون لا يعرفون غير Outlook. وOutlook لا يدعم CalDAV ولا CardDAV في أي إصدار من إصداراته، والتفصيل كما يلي:

- **Outlook الكلاسيكي على Windows:** الحل هو إضافة [Outlook CalDav Synchronizer](https://github.com/aluxnimm/outlookcaldavsynchronizer?ref=arabroot.io)، وهي مفتوحة المصدر بالرخصة AGPLv3، وتعمل على Windows فقط، وتزامن المواعيد والمهام وجهات الاتصال في الاتجاهين، وتدعم إصدارات Outlook من 2007 إلى 2021 وMicrosoft 365 على سطح المكتب، وما زالت تصدر تحديثات، فآخر إصدار لها [4.7.1](https://github.com/aluxnimm/outlookcaldavsynchronizer/releases?ref=arabroot.io) في أغسطس 2026\. وهي تعمل جيداً، ولكنها إضافة تثبتها وتضبطها على كل جهاز، ويحتاج كل مستخدم إلى إعداد تقويماته فيها.
- **Outlook الجديد على Windows:** لا تعمل معه الإضافة السابقة، فقد ذكرت [وثائق mailcow](https://docs.mailcow.email/client/client-outlook/?ref=arabroot.io) أن Outlook الجديد لا يدعم CalDAV وCardDAV لا بنفسه ولا عبر CalDav Synchronizer، والحسابات التي يقبلها بحسب [صفحة Microsoft](https://learn.microsoft.com/en-us/microsoft-365-apps/outlook/get-started/supported-account-types?ref=arabroot.io) هي Microsoft 365 وOutlook.com وGmail وYahoo وiCloud وأي حساب IMAP أو POP، وحساب IMAP يزامن البريد وحده. فالذي يبقى لك معه هو الاشتراك في رابط ics للقراءة فقط.
- **Outlook على Mac:** تذكر وثائق mailcow أنه لا يزامن التقويم وجهات الاتصال مع mailcow ولا تنصح به، والبديل تطبيقا Calendar وContacts في macOS.
- **ActiveSync مع Outlook:** قد تظن أن SOGo يحل المشكلة لأنه يتكلم ActiveSync، ولكن وثائق mailcow تقول صراحة إن ActiveSync لا يعمل مع mailcow في Outlook 2019 وما بعده ومنه Microsoft 365 وOutlook الجديد، والسبب أن Microsoft أوقفت المصادقة الأساسية Basic Authentication لهذا البروتوكول في هذه الإصدارات وأصبحت تشترط OAuth2، وأنه لا يعمل بشكل موثوق في الإصدارات الأقدم ولا ينصحون به.
- **Outlook على الهاتف:** تقول Microsoft في [صفحة الدعم](https://support.microsoft.com/en-us/outlook/can-t-sync-calendar-and-contacts-with-my-phone-or-tablet?ref=arabroot.io) إن IMAP وPOP يزامنان البريد فقط، وإن مزامنة التقويم وجهات الاتصال في تطبيق Outlook تحتاج إلى مزود يقدم Exchange ActiveSync، لذلك اجعل الموظف يقرأ بريده في Outlook على الهاتف إذا أراد، ولكن تقويمه وجهات اتصاله في تطبيقات الهاتف نفسه عبر الحساب الأصلي في iPhone أو DAVx⁵ في Android.

⚠️

إذا كان Outlook شرطاً لا نقاش فيه في مؤسستك، وخاصة Outlook الجديد الذي تنقل Microsoft المستخدمين إليه تدريجياً، فاعلم أن التقويم المشترك والدعوات لن تعمل فيه مع سيرفرك كما تعمل في Microsoft 365\. والخيار الواقعي إما أن يبقى الموظفون على Outlook الكلاسيكي مع CalDav Synchronizer وأنت تعرف أن هذا الطريق له نهاية، وإما أن ينتقلوا إلى Thunderbird أو تطبيقات النظام، وإما أن تبقي التقويم والبريد في Microsoft 365\. فقرر هذا قبل الترحيل وليس بعده.

## ميزات الفريق: المشاركة والتفويض والقاعات

مزامنة تقويم شخص واحد مع أجهزته هي الجزء السهل، أما الفريق فيحتاج إلى ما يلي، وهو ما يفرق بين السيرفرات فعلاً:

- **التقاويم المشتركة Shared Calendars:** تقويم يراه الجميع ويعدل فيه بعضهم، مثل تقويم الإجازات، وتمنح فيه صلاحيات Permissions للقراءة أو الكتابة لكل مستخدم أو مجموعة. وتجده في Nextcloud وSOGo وStalwart، وفي Radicale منذ الإصدار 3.7.
- **التفويض Delegation:** أن يدير مساعد تقويم مديره، فيقبل الدعوات نيابة عنه، وهذا في الأساس صلاحية كتابة على تقويم شخص آخر، والبرامج تختلف في عرضه.
- **الوقت المتاح Free/Busy:** أن ترى عند إنشاء اجتماع متى يكون كل مدعو مشغولاً دون أن ترى تفاصيل مواعيده، وهذا يحتاج إلى سيرفر يطبق الجدولة، مثل Nextcloud.
- **حجز القاعات والموارد Room and Resource Booking:** القاعة أو جهاز العرض حساب له تقويم، تدعوه إلى الاجتماع فيقبل تلقائياً إذا كان متاحاً ويرفض إذا كان محجوزاً، وهذا موجود في Nextcloud وفي SOGo.
- **الدعوات بين المؤسسات:** تعمل بين أي سيرفرين وأي مزود عبر iMIP كما شرحنا، فدعوتك تصل إلى موظف يستخدم Gmail أو Microsoft 365 ويرد عليها، بشرط أن يصل بريدك أصلاً، وأن يحافظ الموعد على `UID` نفسه في كل الرسائل.

### المناطق الزمنية والمواعيد المتكررة: أين تقع الأخطاء؟

أغلب الشكاوى بعد الانتقال تكون من نوعين: موعد ظهر في ساعة خاطئة، وموعد متكرر اختفى منه يوم أو تكرر فيه يوم. وسببهما في الغالب واحد من الأسباب التالية:

- **موعد دون منطقة زمنية صحيحة:** الموعد المكتوب بوقت محلي دون `TZID` يسمى وقتاً عائماً Floating Time، ويعرض في كل جهاز بتوقيت ذلك الجهاز، فإذا سافر الموظف تحرك الموعد معه، والموعد الذي يذكر `TZID` دون تعريف `VTIMEZONE` قد يفهمه برنامج ولا يفهمه آخر كما رأينا في التجربة. لذلك اختبر بعد الاستيراد موعدين أو ثلاثة على جهازين مختلفين قبل أن تعلن نجاح الترحيل.
- **التوقيت الصيفي Daylight Saving Time:** إذا كان في فريقك من يعمل من بلد يغير ساعته مرتين في السنة، فالاجتماع المتكرر يجب أن يحفظ بمنطقته الزمنية الحقيقية مثل `Europe/Berlin` وليس بفارق ثابت مثل `+0100`، والسبب أن الفارق الثابت يجعل الاجتماع ينتقل ساعة عند تغيير التوقيت.
- **الاستثناءات في التكرار:** عندما تلغي اجتماعاً واحداً من سلسلة أسبوعية أو تنقله، يحفظ هذا الاستثناء في الحقل `EXDATE` أو في نسخة منفصلة من الموعد تحمل `RECURRENCE-ID` و`UID` السلسلة نفسه، وبعض أدوات التصدير والاستيراد تفقد هذه الاستثناءات، فتعود الاجتماعات الملغاة إلى الظهور.

## الترحيل من Google وMicrosoft 365

الترحيل Migration هنا له طريقان، والفرق بينهما هو الفرق بين التصدير والمزامنة الذي بدأنا به المقال:

1. **التصدير والاستيراد بالملفات:** من Google Calendar تصدر كل تقاويمك في ملف مضغوط فيه ملف ics لكل تقويم من الإعدادات على الحاسوب فقط وليس من التطبيق، ويشترط أن تكون لك صلاحية إدارة التقويم بحسب [صفحة التصدير](https://support.google.com/calendar/answer/37111?ref=arabroot.io)، ومن Google Contacts تصدر جهات الاتصال بصيغة vCard بحسب [صفحة التصدير](https://support.google.com/contacts/answer/7199294?ref=arabroot.io). ومن Outlook الكلاسيكي تحفظ التقويم ملف iCalendar من File ثم Save Calendar مع اختيار الفترة بحسب [صفحة Microsoft](https://support.microsoft.com/en-us/office/export-an-outlook-calendar-to-google-calendar-662fa3bb-0794-4b18-add8-9968b665f4e6?ref=arabroot.io)، أما جهات الاتصال فيصدرها Outlook بصيغة CSV وليس vCard بحسب [صفحة التصدير](https://support.microsoft.com/en-us/office/export-contacts-from-outlook-10f09abd-643c-4495-bb80-543714eca73f?ref=arabroot.io)، فتحتاج إلى خطوة تحويل أو استيراد من واجهة تقبل CSV. ثم تستورد الملفات إلى السيرفر من واجهته أو من برنامج المستخدم.
2. **أدوات الترحيل المباشر:** تسحب البيانات من الحساب القديم مباشرة، وأوضح مثال أداة [Vandelay](https://stalw.art/docs/migration/import-export/?ref=arabroot.io) في Stalwart التي تنقل الحساب من IMAP أو JMAP أو Exchange أو Google Takeout مع التقويم وجهات الاتصال، وهي أفضل من الملفات حين يكون عدد المستخدمين كبيراً.

وفي الطريقين احرص على ثلاثة أمور، والسبب أنها ما يجعل الدعوات تستمر بعد الانتقال. الأول أن يبقى `UID` كل موعد كما هو، فالردود التي تصل لاحقاً على اجتماع قديم تبحث عنه بهذا الرقم. والثاني أن يبقى عنوان بريد المنظم في المواعيد هو نفسه عنوان الموظف على السيرفر الجديد، وإلا فلن يعرف السيرفر أن هذا الموظف هو صاحب الاجتماع ولن يقبل منه التعديل. والثالث أن تحدد يوماً للتحويل وتبلغ الفريق أن الاجتماعات الجديدة تنشأ بعده على السيرفر الجديد فقط، والسبب أن الاجتماعات التي تنشأ في الفترة الانتقالية على النظامين معاً هي التي تتكرر أو تضيع. وقد شرحنا ترتيب الانتقال كله مع البريد في [خطة النقل على مراحل](https://arabroot.io/articles/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D9%84%D9%84%D9%85%D8%A4%D8%B3%D8%B3%D8%A7%D8%AA/#rollout)، فاجعل التقويم وجهات الاتصال جزءاً من المرحلة التجريبية Pilot فيها.

💡

الاشتراكات في روابط ics لا تنتقل مع التصدير، ففي ملف التصدير تجد تقاويمك أنت فقط، أما تقويم الإجازات الرسمية الذي اشتركت فيه أو تقويم زميل مشترك معك فيجب أن تضيفه من جديد على النظام الجديد، لذلك اطلب من كل موظف أن يكتب قائمة باشتراكاته قبل الترحيل.

## الحماية والنسخ الاحتياطي

التقويم وجهات الاتصال من أكثر بيانات المؤسسة حساسية، ففيها مواعيد الاجتماعات مع العملاء وأسماؤهم وأرقامهم، لذلك عليك بالقواعد التالية:

- **HTTPS فقط:** كما رأينا في التجربة فإن المصادقة في أغلب سيرفرات DAV من نوع Basic، أي أن كلمة المرور تمر مع كل طلب بترميز وليس بتشفير، وبالتالي لا تقم بنشر السيرفر على HTTP إطلاقاً، وضعه خلف Reverse Proxy مع شهادة TLS، وتأكد أن ال Reverse Proxy يحول `/.well-known/caldav` و`/.well-known/carddav` إلى المسار الصحيح، والسبب أن هذين العنوانين أكثر ما يكسر الاكتشاف التلقائي خلف Reverse Proxy.
- **كلمات مرور التطبيقات App Passwords مع الدخول الموحد:** إذا كان فريقك يدخل عبر الدخول الموحد SSO كما شرحنا في [شرح OAuth وOpenID Connect](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)، فاعلم أن أغلب برامج CalDAV وCardDAV لا تفتح صفحة دخول في المتصفح وإنما تطلب اسم مستخدم وكلمة مرور، لذلك أنشئ لكل جهاز كلمة مرور تطبيق خاصة به، وهذا موجود في mailcow وفي صفحة `/account` في Stalwart. والسبب أن ضياع الهاتف حينها يعني إلغاء كلمة مرور ذلك الهاتف وحده، وليس تغيير كلمة مرور الموظف في كل مكان.
- **لا تنشر لوحة الإدارة:** واجهة إدارة Baïkal أو Radicale أو لوحة mailcow لا يحتاجها إلا المدير، فاقصرها على عناوين محددة أو على VPN، أما مسارات DAV نفسها فهي التي تحتاج إلى أن تكون منشورة للهواتف.
- **النسخ الاحتياطي:** في Radicale انسخ مجلد المجموعات كله، وفي Baïkal وNextcloud انسخ قاعدة البيانات، وفي mailcow وStalwart يدخل التقويم في النسخة الاحتياطية للخادم نفسه كما شرحنا في دليل كل منهما. ثم جرب الاستعادة مرة على سيرفر مؤقت، فالنسخة التي لم تجرب استعادتها لا تعرف هل تعمل أم لا.

## أي حل يناسبك؟

والجدول التالي يجمع ما سبق في قرار واحد بحسب حالتك:

| حالتك                   | الحل الأنسب                                                                                                                           | والسبب                                                                                                                      |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| شخص واحد أو عائلة       | Radicale                                                                                                                              | يعمل في دقائق، ونسخه الاحتياطي مجلد واحد، والهواتف والحواسيب لا تحتاج إلى أكثر من المزامنة.                                 |
| فريق صغير دون بريد ذاتي | Baïkal، أو Nextcloud إذا كنت تحتاج إلى تقويم في المتصفح وحجز قاعات                                                                    | Baïkal خفيف بلوحة إدارة، وNextcloud أكمل في ميزات الفريق ولكنه منصة أثقل.                                                   |
| فريق على mailcow        | SOGo المدمج                                                                                                                           | موجود بالفعل، ويعطي الموظف بريده وتقويمه في صفحة واحدة، مع موارد للقاعات.                                                   |
| فريق على Stalwart       | CalDAV وCardDAV المدمجان                                                                                                              | الجدولة والدعوات بالبريد مدمجة، دون سيرفر إضافي، والعمل من البرامج والهواتف.                                                |
| مؤسسة تعتمد Outlook     | Outlook الكلاسيكي مع CalDav Synchronizer مؤقتاً، أو نقل المستخدمين إلى Thunderbird وتطبيقات النظام، أو إبقاء التقويم في Microsoft 365 | Outlook لا يدعم CalDAV، وOutlook الجديد يقبل الاشتراك للقراءة فقط، وActiveSync مع mailcow لا يعمل في Outlook 2019 وما بعده. |

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- الموعد ملف iCalendar وجهة الاتصال ملف vCard، والصيغتان موحدتان منذ سنوات، والمشكلة الحقيقية هي إبقاء النسخ متطابقة، وتصدير الملف مرة واحدة ينقل نسخة من يوم واحد ولا يعرف ما حذف بعده.
- CalDAV وCardDAV طبقتان فوق WebDAV الذي هو بدوره HTTP، فالتقويم مجلد وكل موعد ملف، والجهاز يجد مكانه عبر `/.well-known` وسجلات SRV ثم الحساب ومكان المجموعات.
- المزامنة تقوم على ETag لكل ملف وsync-token لكل مجلد، فيسأل الجهاز عما تغير منذ آخر رمز ويجلب المتغير فقط، ويرفض السيرفر التعديل على نسخة قديمة بالرمز 412 فلا يمسح أحد تعديل غيره.
- الدعوات رسائل iTIP، تمر داخل السيرفر عبر الجدولة في RFC 6638، وتخرج إلى المؤسسات الأخرى بريداً عبر iMIP، لذلك يحتاج التقويم إلى بريد يصل إلى صناديق الآخرين.
- إذا كنت تستضيف بريدك فالتقويم غالباً عندك بالفعل: SOGo في mailcow أو المدمج في Stalwart، وإلا فـ Radicale للأفراد وBaïkal أو Nextcloud للفرق.
- Apple وThunderbird وGNOME تدعم البروتوكولين مباشرة، وAndroid عبر DAVx⁵، أما Outlook فلا يدعمهما، والكلاسيكي يحتاج إلى CalDav Synchronizer، والجديد يقبل الاشتراك للقراءة فقط، فقرر موقفك من Outlook قبل الترحيل.
- انشر السيرفر عبر HTTPS فقط، واستخدم كلمات مرور التطبيقات مع الدخول الموحد، وحافظ على `UID` وبريد المنظم عند الترحيل حتى تستمر الدعوات.

[![](https://arabroot.io/content/images/2026/10/mailcow-cover-cover.webp)دليلتثبيت خادم البريد mailcow باستخدام Dockerكيف تشغل خادم بريد مؤسستك بنفسك مع mailcow، وتضبط سجلات DNS حتى تصل رسائلك إلى صندوق الوارد لا إلى Spam.](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A8%D8%B1%D9%8A%D8%AF-mailcow/)[![](https://arabroot.io/content/images/2026/10/stalwart-cover-cover.webp)دليلتثبيت خادم البريد Stalwart باستخدام Docker Composeكيف تشغل خادم بريد كاملاً في برنامج واحد مع Stalwart على سيرفرك، مع سجلات DNS التي يولدها لك والنسخ الاحتياطي.](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-stalwart/)[![](https://arabroot.io/content/images/2026/10/email-hosting-guide-cover-cover.webp)دليلكيف تستضيف بريد مؤسستك بنفسك، من القرار إلى التشغيلمتى تستضيف بريد شركتك بنفسك ومتى يكون البريد المستضاف أنسب، وكيف تنقل المستخدمين على مراحل دون أن تضيع رسالة.](https://arabroot.io/articles/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D9%84%D9%84%D9%85%D8%A4%D8%B3%D8%B3%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/seafile-cover-cover-1.webp)دليلSeafile: مزامنة ملفات فريقك ومشاركتها على سيرفرككيف تثبت Seafile على سيرفرك لتزامن ملفات فريقك وتشاركها بدل Dropbox وOneDrive، مع نسخة احتياطية تستطيع استعادتها.](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-seafile-%D9%84%D9%85%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/oauth-oidc-cover-cover.webp)دليلشرح OAuth وOpenID Connect للمبتدئينلماذا لا يجب أن يعرف أي تطبيق كلمة مرورك، وكيف يحل OAuth ذلك بتوكنات محدودة، وكيف يضيف OpenID Connect الدخول الموحد.](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-oauth-%D9%88-openid-connect-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتنفيذ جولة المزامنة كاملة بالأمر curl على Radicale 3.8.1.