راه اندازی سرور CalDAV روی VPS با Radicale
با نصب Radicale روی VPS خود، تقویمها را بدون نیاز به Google بین گوشی و لپتاپ همگام کنید. این راهنما شامل تنظیمات TLS، قابلیت discovery و پیکربندی کلاینت برای عملکرد پایدار است.
آنچه میسازید
یک تقویم خود-میزبانیشده (self-hosted)، یک سرور CalDAV روی یک VPS تحت کنترل شماست که پشت TLS قرار دارد و برای هر نفر یک حساب کاربری مجزا تعریف میشود. گوشی در جیب شما و لپتاپ روی میزتان، رویدادهای یکسانی را نمایش میدهند و لپتاپ همسرتان نیز همینطور. هیچ حساب کاربری Google در این میان واسطه نیست.
این کار با یک صفحه رزرو خود-میزبانیشده متفاوت است. صفحه رزرو برای افراد غریبه است: زمانهای آزاد شما را منتشر میکند و به دیگران اجازه میدهد یکی را انتخاب کنند. سرور تقویم برای دستگاههای شخصی خودتان است: رویدادها را ذخیره میکند و همه کلاینتها را با هم همگام نگه میدارد. افراد اغلب هر دو را اجرا میکنند و ابزار رزرو، وضعیت در دسترس بودن را از سرور CalDAV که در اینجا میسازید، میخواند.
نصب آن کوچک است. Radicale یک بسته Python و حدود ده خط پیکربندی است. آنچه تعیین میکند آیا این تنظیمات در ماه اول دوام میآورد یا خیر، TLS، قابلیت کشف (discovery)، مجموعههای کاربری (collections) و پشتیبانگیری است. بخش عمدهای از مطالب زیر به این موارد اختصاص دارد.
CalDAV چیست و چرا اهمیت دارد؟
CalDAV پروتکلی برای همگامسازی تقویم بر بستر HTTP است. این پروتکل در RFC 4791 به عنوان افزونهای برای WebDAV (مخفف Web Distributed Authoring and Versioning، مجموعهای از متدهای اضافی HTTP که در RFC 4918 تعریف شده) معرفی شده است. یک تقویم در اینجا به عنوان یک collection شناخته میشود که رفتاری مشابه یک دایرکتوری دارد. هر رویداد، یک فایل درون آن است که با فرمت متنی iCalendar (طبق RFC 5545) نوشته شده است؛ همان فرمتی که در .ics پیوستهای ایمیل شما استفاده میشود.
کلاینتها از HTTP معمولی به همراه چند متد اضافه استفاده میکنند. PROPFIND میپرسد چه چیزی در اینجا وجود دارد و چه ویژگیهایی دارد. REPORT یک بخش فیلترشده را درخواست میکند، مثلاً تمام رویدادها در یک بازه زمانی خاص. PUT یک رویداد را مینویسد و DELETE آن را حذف میکند. هر رویداد دارای یک خط UID است و این شناسه همان چیزی است که باعث میشود دو دستگاه توافق کنند که در حال مشاهده یک رویداد واحد هستند، نه یک کپی از آن.
مزیت اصلی این کار، قابلیت حمل (Portability) است و همین دلیل کافی برای استفاده از آن محسوب میشود. سیستمعاملهای iOS، macOS، Thunderbird، Evolution و همچنین Android از طریق DAVx⁵ همگی از CalDAV پشتیبانی میکنند. دادههای شما به سروری که امروز انتخاب کردهاید محدود نمیشود. کافی است فایلها را به یک سرور CalDAV دیگر منتقل کنید و کلاینتها را به نام میزبان (hostname) جدید هدایت کنید؛ هیچ چیز دیگری تغییر نخواهد کرد.
CardDAV نیز در کنار آن قرار دارد. این پروتکل دقیقاً همان ایده را برای مخاطبین پیاده میکند، در RFC 6352 تعریف شده و به جای رویدادها، فایلهای vCard را ذخیره میکند. تمام سرورهای زیر، هر دو پروتکل را از یک حساب کاربری واحد ارائه میدهند، بنابراین به محض اینکه تقویم را راهاندازی کنید، فعالسازی دفترچه تلفن تنها به یک تیک زدن ساده نیاز دارد.
کدام سرور CalDAV را باید اجرا کنید؟
Radicale کوچکترین گزینهای است که کارایی لازم را دارد. این برنامه با Python نوشته شده، به دیتابیس نیاز ندارد و دادهها را در پوشهای از فایلهای متنی ساده ذخیره میکند. این راهنما از Radicale استفاده میکند، زیرا تقویم خانگی به چیزی بیش از این نیاز ندارد و احتمال بروز خطا در ساعت 3 صبح با آن بسیار کم است.
Baikal گزینهای است که پنل مدیریت تحت وب دارد. این برنامه با PHP و کتابخانه sabre/dav اجرا میشود، کاربران و تقویمها را در SQLite یا MySQL نگه میدارد و به شما اجازه میدهد به جای خط فرمان، از طریق مرورگر کاربر اضافه کنید. اگر حسابهای کاربری زیاد تغییر میکنند، این گزینه را انتخاب کنید.
Nextcloud زمانی مناسب است که تقویم تنها یکی از چندین قابلیت مورد نیاز شما باشد. شما تقویم، مخاطبین، فایلها و اپلیکیشن موبایل را دریافت میکنید، اما در عوض باید هزینه اجرای PHP-FPM، یک دیتابیس و یک اجراکننده وظایف پسزمینه (background job runner) را بپردازید. اگر این موارد برای نیاز واقعی شما سنگین به نظر میرسد، جایگزینهای سبکتر Nextcloud این نیاز را پوشش میدهند و همگامسازی فایل به صورت self-hosted بخش دیگر دلایل نصب Nextcloud را برطرف میکند.
DAViCal گزینه قدیمی و مبتنی بر PostgreSQL است. تنها در صورتی ارزش بررسی دارد که از قبل PostgreSQL را اجرا کرده باشید و بخواهید دادههای تقویم در آن ذخیره شود.
نصب Radicale روی Ubuntu 24.04
نسخه Radicale 3.5.10 آخرین نسخه منتشرشده تا اوت 2026 است. آن را در محیط مجازی (virtual environment) اختصاصی خود نصب کنید.
sudo apt update
sudo apt install -y python3-venv apache2-utils nginx
sudo useradd --system --user-group --home-dir / --shell /usr/sbin/nologin radicale
sudo install -d -o radicale -g radicale -m 750 /var/lib/radicale/collections
sudo install -d -m 750 -o root -g radicale /etc/radicale
sudo python3 -m venv /opt/radicale/venv
sudo /opt/radicale/venv/bin/pip install --upgrade radicaleاستفاده از محیط مجازی یک انتخاب سلیقهای نیست. اجرای sudo pip install radicale روی پایتون سیستم با خطای error: externally-managed-environment متوقف میشود، زیرا اوبونتو پایتون خود را تحت مدیریت apt قرار داده است تا pip نتواند فایلهای بستهبندیشده را بازنویسی کند.
دستور /etc/radicale/config را اجرا کنید:
[server]
hosts = 127.0.0.1:5232
[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = autodetect
[storage]
filesystem_folder = /var/lib/radicale/collectionsسرویس hosts عمداً روی loopback گوش میدهد. nginx عملیات TLS termination را انجام داده و درخواستها را به آن پورت هدایت میکند، بنابراین Radicale هرگز مستقیماً در معرض اینترنت قرار نمیگیرد. نمونه بالادستی 0.0.0.0:5232 یک سرویس رمزنگارینشده را منتشر میکند که رمز عبور میپذیرد؛ این تنها اشتباهی است که در اینجا اهمیت دارد.
حالا نوبت حسابهای کاربری است. -5 الگوریتم SHA-512 crypt را انتخاب میکند که Radicale آن را با htpasswd_encryption = autodetect و بدون نیاز به ماژول اضافی میخواند:
sudo htpasswd -5 -c /etc/radicale/users you
sudo htpasswd -5 /etc/radicale/users partner
sudo chown root:radicale /etc/radicale/users
sudo chmod 640 /etc/radicale/usersدستور -c فایل را ایجاد کرده و محتوای قبلی آن را پاک میکند. فقط برای کاربر اول از آن استفاده کنید. اجرای مجدد htpasswd -5 -c پس از چند ماه، تمام حسابهایی که بعد از نفر اول اضافه شدهاند را حذف میکند؛ نشانه این وضعیت این است که یک نفر بهخوبی همگامسازی (sync) میکند، در حالی که بقیه با یک پنجره درخواست رمز عبور مواجه میشوند که هرگز تمام نمیشود. Bcrypt نیز کار میکند و به نصب اضافی radicale[bcrypt] نیاز دارد.
فایل /etc/systemd/system/radicale.service را با اقتباس از unit موجود در مستندات Radicale ایجاد کنید:
[Unit]
Description=CalDAV and CardDAV server
After=network.target
Requires=network.target
[Service]
ExecStart=/opt/radicale/venv/bin/python -m radicale
Restart=on-failure
User=radicale
UMask=0027
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
NoNewPrivileges=true
ReadWritePaths=/var/lib/radicale/
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now radicale
curl -i http://127.0.0.1:5232/نتیجه مطلوب، دریافت 401 Unauthorized به همراه هدر WWW-Authenticate است: سرویس در حال گوش دادن است و احراز هویت فعال میباشد. وضعیت Connection refused به این معنی است که سرویس هرگز شروع نشده و journalctl -u radicale -n 50 نام گزینهای است که رد شده است. دستور ProtectSystem=strict سیستم فایل را برای این سرویس فقطخواندنی (read-only) میکند، بنابراین ReadWritePaths=/var/lib/radicale/ خطی است که به آن اجازه میدهد رویدادها را ذخیره کند. اگر آن خط را حذف کنید، عملیات خواندن همچنان کار میکند اما تمام عملیات نوشتن با شکست مواجه میشود.
استفاده از TLS اختیاری نیست، زیرا کلاینتها از اتصال متنساده (plaintext) خودداری میکنند
پروتکل CalDAV برای احراز هویت از HTTP Basic استفاده میکند که در هر درخواست، user:password را بهصورت base64 رمزگذاری کرده و ارسال میکند. Base64 یک روش کدگذاری است، نه رمزنگاری. در بستر HTTP ساده، شما رمز عبور خود را در تمام طول روز و طی هر همگامسازی، در اختیار تمام شبکههای بین گوشی و سرور قرار میدهید.
کلاینتها این موضوع را برای شما اجباری کردهاند. مستندات Radicale اشاره میکند که برنامه Calendar.app در macOS ممکن است بدون نمایش پیام، از ارسال اعتبارنامهها روی HTTP ناامن خودداری کند؛ رفتار iOS نیز به همین صورت است. در این حالت، حساب کاربری پیکربندیشده به نظر میرسد اما هرگز همگامسازی نمیشود و هیچ خطایی هم برای بررسی وجود ندارد.
ابتدا یک رکورد A برای cal.example.com به سمت VPS تنظیم کنید، زیرا مرجع صدور گواهی (CA) آن را بررسی میکند. سپس فایل /etc/nginx/sites-available/cal.example.com را ایجاد کنید:
server {
listen 80;
server_name cal.example.com;
location / {
proxy_pass http://localhost:5232/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_pass_header Authorization;
}
location = /.well-known/caldav { return 301 https://$host/; }
location = /.well-known/carddav { return 301 https://$host/; }
}چهار خط مربوط به هدرهای پروکسی از مستندات Radicale گرفته شدهاند. آنها را دقیقاً به همان شکل حفظ کنید.
sudo ln -s /etc/nginx/sites-available/cal.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d cal.example.com
curl -i -u you https://cal.example.com/دستور nginx -t خروجی syntax is ok و test is successful را چاپ میکند. تنها پس از اطمینان از صحت فایل، آن را Reload کنید؛ زیرا Reload کردن با یک فایل دارای خطا، پیکربندی قبلی را فعال نگه میدارد و اشتباه را تا زمان راهاندازی مجدد بعدی پنهان میکند. Certbot فایل سایت را در محل ویرایش میکند: گواهی را نصب کرده، بلاک مربوطه را به پورت 443 منتقل میکند و یک تغییر مسیر (redirect) از پورت 80 اضافه مینماید. دستور curl نهایی، رمز عبور را درخواست میکند و باید 200 را برگرداند که رابط وب خودِ Radicale است. دریافت 502 Bad Gateway به این معنی است که nginx در حال اجراست اما Radicale روی پورت 5232 گوش نمیدهد.
چرا افزودن حساب کاربری روی گوشی با خطا مواجه میشود؟
دلیل این امر به فرایند discovery مربوط است. استاندارد RFC 6764 توصیف میکند که چگونه یک کلاینت، نام میزبان (hostname) را به یک URL تقویم تبدیل میکند. کلاینت ابتدا به دنبال رکورد _caldavs._tcp SRV میگردد، سپس درخواست https://cal.example.com/.well-known/caldav را ارسال کرده و انتظار یک redirect به ریشه DAV را دارد. پس از آن، کلاینت درخواست current-user-principal و سپس calendar-home-set مربوط به آن principal را ارسال میکند و تنها در این مرحله است که تقویمهای شما را مشاهده میکند. گوشیها تنها یک فیلد برای آدرس سرور در اختیار شما میگذارند، بنابراین تمام این مراحل باید بدون دخالت کاربر و بهصورت خودکار انجام شوند.
curl -sI https://cal.example.com/.well-known/caldavپاسخ صحیح و بدون خطا، HTTP/2 301 به همراه هدر location: https://cal.example.com/ است. وجود یک 404 در این مرحله، دلیل این است که iOS پیام عدم تأیید اطلاعات حساب را نمایش میدهد، در حالی که Thunderbird در همان شبکه بهدرستی کار میکند؛ زیرا Thunderbird از URL کاملی که شما وارد کردهاید استفاده میکند و نیازی به انجام redirect ندارد.
مقصد redirect به سرور شما بستگی دارد. Radicale که در ریشه سایت سرویسدهی میکند، درخواستها را به / هدایت میکند. Baikal قوانین نمونهای ارائه میدهد که درخواستها را با وضعیت 308 به /dav.php هدایت میکنند. Nextcloud نیز درخواستها را به /remote.php/dav/ هدایت میکند.
ایجاد تقویمها و اشتراکگذاری یکی از آنها با شریک خود
بسیاری از کلاینتها قابلیت ایجاد تقویم را ندارند و تنها میتوانند در تقویمهای موجود عضو شوند (subscribe). برای این کار، https://cal.example.com/ را در مرورگر باز کنید، با حساب you وارد شوید و تقویم را در آنجا ایجاد کنید. این تقویم روی دیسک در مسیر /var/lib/radicale/collections/collection-root/you/ و با یک شناسه تولیدشده به عنوان نام پوشه ذخیره میشود.
بکاند پیشفرض برای مدیریت دسترسیها در Radicale، گزینه owner_only است: در این حالت، هر حساب کاربری احراز هویتشده میتواند مجموعههای (collections) خود را در مسیر /USERNAME/ بخواند و ویرایش کند و به هیچ داده دیگری دسترسی ندارد. برای اکثر خانوادهها، این تنظیم مناسبترین گزینه است و سادهترین راه برای اشتراکگذاری یک تقویم، ایجاد یک حساب کاربری سوم است. حساب household را با استفاده از htpasswd بسازید، تقویم اشتراکی را تحت آن حساب ایجاد کنید و آن را به عنوان یک حساب CalDAV دوم روی هر دستگاه اضافه کنید. این روش روی تمامی کلاینتها از جمله iOS کار میکند، زیرا تقویم در فضای اصلی (home) همان حساب قرار دارد.
اگر به کنترل دقیقتری نیاز دارید، از سیستم دسترسی مبتنی بر قانون (rule-based) استفاده کنید. کد زیر را به /etc/radicale/config اضافه کنید:
[rights]
type = from_file
file = /etc/radicale/rightsسپس بر اساس نمونه موجود در مستندات Radicale، فایل /etc/radicale/rights را تنظیم کنید:
[root]
user: .+
collection:
permissions: R
[principal]
user: .+
collection: {user}
permissions: RW
[own-calendars]
user: .+
collection: {user}/[^/]+
permissions: rw
[shared-household]
user: you|partner
collection: you/2f0a9c1e-1f4c-4c2b-9a1b-0d2f7a5c9e11
permissions: rwحروف بزرگ و کوچک معانی متفاوتی دارند. R و W امکان خواندن و نوشتن در مجموعههایی را فراهم میکنند که تقویم یا دفترچه تلفن نیستند (یعنی همان پوشه اصلی یا principal). r و w امکان خواندن و نوشتن در خود تقویمها را فراهم میکنند. در این بخش، شناسه مربوطه را با نام واقعی پوشه تقویم خود که از مسیر ذخیرهسازی در بالا به دست آوردید، جایگزین کنید.
یک محدودیت صادقانه: کلاینتی که فقط مسیر اصلی تقویم را میخواند، تقویمی را که در مسیر کاربر دیگری قرار دارد نمایش نمیدهد، زیرا فرآیند کشف (discovery) به آن مسیرها سرک نمیکشد. Thunderbird و DAVx⁵ میتوانند این تقویم را از طریق URL کامل اضافه کنند. سیستم iOS چنین قابلیتی ندارد و به همین دلیل است که الگوی استفاده از حساب کاربری اشتراکی، تنها روشی است که همیشه جواب میدهد.
راهاندازی کلاینتها؛ جایی که تقویمهای self-hosted معمولاً با شکست مواجه میشوند
iPhone و iPad. به Settings و سپس Calendar (در نسخههای جدید iOS در بخش Apps قرار دارد) بروید. مسیر Calendar Accounts، Add Account، Other و در نهایت Add CalDAV Account را دنبال کنید. آدرس سرور cal.example.com است و پس از آن نام کاربری و رمز عبور را وارد کنید. فیلد Description فقط یک برچسب است. اگر ذخیره نشد، دوباره حساب را باز کنید: در نمای پیشرفته (advanced view)، گزینههای Use SSL، پورت و URL کامل حساب نمایش داده میشوند؛ جایگذاری (paste) مستقیم URL باعث میشود مرحله discovery کاملاً نادیده گرفته شود.
Android. هیچ کلاینت CalDAV داخلی در اندروید وجود ندارد. برنامه DAVx⁵ را از F-Droid یا Google Play نصب کنید، یک حساب با استفاده از URL پایه https://cal.example.com/ و نام کاربری خود اضافه کنید و سپس تقویمهای مورد نظر را انتخاب نمایید. برنامه DAVx⁵ دادهها را در ارائهدهنده تقویم اندروید مینویسد، بنابراین رویدادها در هر برنامه تقویمی که از قبل استفاده میکنید، نمایش داده خواهند شد.
Thunderbird. گزینه New Calendar، سپس On the Network را انتخاب کنید و نام کاربری و آدرس https://cal.example.com/ را وارد نمایید. برنامه موارد یافتشده را لیست کرده و از شما میپرسد کدام تقویمها اضافه شوند.
macOS. به System Settings، بخش Internet Accounts، گزینه Add Other Account و سپس CalDAV بروید. نوع حساب (Account Type) را روی Manual تنظیم کنید و سپس نام کاربری، رمز عبور و آدرس سرور را وارد نمایید.
پروتکل CalDAV مبتنی بر polling است. در مشخصات این پروتکل قابلیت push وجود ندارد، بنابراین رویدادی که در لپتاپ اضافه میکنید، در لحظه روی گوشی ظاهر نمیشود و در همگامسازی (sync) بعدی دریافت خواهد شد. در هر کلاینت، بازه زمانی همگامسازی را روی مقداری تنظیم کنید که برایتان قابلقبول باشد؛ به یاد داشته باشید که بازههای زمانی کوتاهتر در گوشی، مصرف باتری را افزایش میدهد.
پشتیبانگیری از مخزن، که تنها شامل فایلها است
در Radicale، تقویم شما یک دایرکتوری از فایلهای .ics است که هر فایل مربوط به یک رویداد بوده و یک فایل کوچک properties نیز برای هر مجموعه وجود دارد. هر ابزاری که یک دایرکتوری را کپی کند، از آن پشتیبان میگیرد و شما میتوانید یک نسخه پشتیبان را با less باز کنید تا مطمئن شوید که حاوی رویدادهای واقعی است. این یک مزیت واقعی نسبت به dump دیتابیس است که نمیتوانید آن را بخوانید.
sudo systemctl stop radicale
sudo tar czf /root/radicale-$(date +%F).tar.gz -C /var/lib/radicale collections
sudo systemctl start radicaleسرویس را برای چند ثانیهای که آرشیو کردن طول میکشد متوقف کنید تا هنگام خواندن فایلها، هیچ کلاینتی در میانه عملیات نوشتن نباشد. پس از آن، آرشیو را از روی سرور کپی کنید، زیرا پشتیبانگیری روی همان VPS در صورت بروز خرابی که برای آن آماده میشوید، باقی نمیماند. بازیابی برعکس این فرآیند است: استخراج کنید، sudo chown -R radicale:radicale /var/lib/radicale/collections را انجام دهید و سرویس را استارت کنید. هر کلاینت نیز یک نسخه محلی از تقویمهای خود را نگه میدارد، بنابراین لپتاپی که از زمان خرابی همگامسازی نشده است، یک نسخه دوم از دادههای شما محسوب میشود.
چه زمانی Baikal یا Nextcloud انتخاب بهتری هستند
نسخه 0.12.1 از Baikal در تاریخ 5 اوت 2026 منتشر شد و به PHP 8.2 یا جدیدتر نیاز دارد. آن را خارج از web root استخراج کنید و فقط دایرکتوری html آن را در دسترس قرار دهید:
sudo apt install -y php-fpm php-sqlite3 php-xml php-mbstring php-curl unzip
cd /tmp
curl -LO https://github.com/sabre-io/Baikal/releases/download/0.12.1/baikal-0.12.1.zip
sudo unzip -q baikal-0.12.1.zip -d /srv
sudo chown -R www-data:www-data /srv/baikal/Specific /srv/baikal/configاین دو دایرکتوری تنها مواردی هستند که وبسرور در آنها عملیات نوشتن انجام میدهد، بنابراین هیچ بخش دیگری نیازی به دسترسی نوشتن ندارد. در داخل server block مربوط به nginx، بخشهای اختصاصی Baikal به شرح زیر هستند:
root /srv/baikal/html;
index index.php;
location ~ /(\.ht|Core|Specific|config) { deny all; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location = /.well-known/caldav { return 308 /dav.php; }
location = /.well-known/carddav { return 308 /dav.php; }سرویس nginx را reload کنید، سایت را در مرورگر باز کنید تا setup wizard حساب کاربری مدیر و دیتابیس SQLite را ایجاد کند. تنظیمات کلاینت مشابه Radicale است و از https://cal.example.com/ به عنوان آدرس سرور استفاده میشود، زیرا قانون well-known، فرآیند discovery را به /dav.php هدایت میکند.
Nextcloud تنها در صورتی ارزش استفاده دارد که بخواهید فایلها و اپلیکیشن موبایل را نیز با همان حساب کاربری داشته باشید. ریشه DAV آن /remote.php/dav/ است و همان قوانین discovery برای آن اعمال میشود. برای هر یک از این سرویسها، اجرای آنها در یک container باعث میشود نسخههای PHP از سیستمعامل میزبان شما جدا بمانند: Docker Compose روی VPS فایل compose و reverse proxy جلوی آن را پوشش میدهد، و چه سرویسهایی در سال 2026 ارزش self-hosting دارند مکان مناسبی برای تصمیمگیری در مورد میزان پیشروی در این مسیر است.
حالتهای شکست و پیامهایی که مشاهده خواهید کرد
هر همگامسازی با خطای 401 مواجه میشود. یا فایل رمز عبور حسابهای خود را به دلیل یک htpasswd -c دیگر از دست داده است، یا کاربر radicale امکان خواندن آن را ندارد. با استفاده از sudo -u radicale cat /etc/radicale/users بررسی کنید؛ اگر با خطای permission denied مواجه شدید، پاسخ همینجاست و راهحل آن قرار دادن فایل در گروه radicale با مجوز 640 است. Radicale بهصورت پیشفرض پس از هر ورود ناموفق، یک ثانیه تأخیر ایجاد میکند، بنابراین کلاینتی که رمز عبور قدیمی دارد، بهجای اینکه بلافاصله رد شود، کند به نظر میرسد.
پاسخ nginx به متد PROPFIND خطای 405 است. این URL بهعنوان یک فایل ایستا (static file) سرو میشود، بنابراین متد WebDAV هرگز به Radicale نمیرسد. نقطه پایانی (endpoint) را مستقیماً تست کنید:
curl -u you -X PROPFIND -H "Depth: 0" -i https://cal.example.com/you/یک مجموعه DAV فعال، پاسخ 207 Multi-Status را برمیگرداند. هر پاسخ دیگری به این معناست که درخواست در وبسرور متوقف شده است.
گوشی نمیتواند حساب را تأیید کند، اما مرورگر بهدرستی کار میکند. دو دلیل رایج وجود دارد. یا redirect معروف (well-known) وجود ندارد که با دستور curl بالا قابل تست است، یا زنجیره گواهی (certificate chain) ناقص است؛ مرورگرها با دریافت گواهی میانی (intermediate) گمشده این مشکل را نادیده میگیرند، اما iOS این کار را انجام نمیدهد. آن را از طریق shell بررسی کنید:
openssl s_client -connect cal.example.com:443 -servername cal.example.com </dev/nullبه دنبال Verify return code: 0 (ok) بگردید. اگر این بررسی شکست خورد، پیکربندی nginx به جای fullchain.pem به cert.pem اشاره میکند.
رویدادهای تکراری پس از import. هر رویداد دارای یک UID است و کلاینتها از آن بهعنوان شناسه استفاده میکنند. اگر فایلی را دو بار از طریق ابزاری که شناسهها را بازتولید میکند import کنید، دو رویداد خواهید داشت که هیچگاه با هم ادغام نمیشوند. نسخههای اضافی را روی یک دستگاه حذف کنید و اجازه دهید حذف از طریق همگامسازی اعمال شود.
همه چیز پس از reboot از کار میافتد. سرویس بهصورت دستی اجرا شده است. دستور sudo systemctl is-enabled radicale وضعیت disabled را نمایش میدهد و sudo systemctl enable --now radicale مشکل را بهصورت دائمی برطرف میکند.
FAQ
آیا واقعاً برای یک سرور CalDAV شخصی به TLS نیاز دارم؟
بله. CalDAV با استفاده از HTTP Basic احراز هویت میکند، بنابراین رمز عبور در هر درخواست بهصورت base64 کدگذاری و ارسال میشود و base64 بهسادگی قابل بازگشت است. کلاینتها نیز این موضوع را اجبار میکنند: برنامه macOS Calendar.app ممکن است بدون اطلاع قبلی از ارسال اعتبارنامهها روی HTTP ناامن خودداری کند و iOS نیز رفتار مشابهی دارد؛ در نتیجه حساب کاربری ذخیره میشود اما هرگز همگامسازی نمیشود. sudo certbot --nginx -d cal.example.com تمام کاری است که باید انجام دهید.
چرا گوشی من نمیتواند حساب را اضافه کند در حالی که Thunderbird کار میکند؟
نرمافزار Thunderbird از آدرس کامل URL که وارد کردهاید استفاده میکند. گوشی تنها یک فیلد برای سرور در اختیار شما میگذارد، بنابراین طبق استاندارد RFC 6764 عمل میکند: گوشی درخواست https://cal.example.com/.well-known/caldav را ارسال کرده و انتظار یک redirect به ریشه DAV را دارد. بدون آن redirect، گوشی با خطای 404 مواجه شده و گزارش میدهد که قادر به تأیید حساب نیست. تنظیم location = /.well-known/caldav { return 301 https://$host/; } را به nginx اضافه کنید و سپس با curl -sI https://cal.example.com/.well-known/caldav تأیید کنید که کد 301 و هدر location را دریافت میکنید.
آیا دو نفر میتوانند یک تقویم مشترک داشته باشند؟
بله، و روش مطمئن برای این کار استفاده از یک حساب کاربری مشترک است. یک حساب سوم با htpasswd ایجاد کنید، تقویم مشترک را زیرمجموعه آن قرار دهید و آن را به عنوان دومین حساب CalDAV روی هر دستگاه اضافه کنید. فایل حقوق دسترسی Radicale میتواند به یک کاربر مشخص اجازه خواندن و نوشتن روی یک مجموعه در مسیر کاربر دیگر را بدهد، اما کلاینتی که فقط تقویمهای اصلی (home set) خود را میخواند، آن را نمایش نخواهد داد؛ بنابراین این روش برای Thunderbird و DAVx⁵ مناسبتر از iOS است.
اگر VPS از دسترس خارج شود چه اتفاقی برای رویدادهای من میافتد؟
در Radicale، دادهها به صورت متن ساده ذخیره میشوند: هر رویداد یک فایل .ics در مسیر /var/lib/radicale/collections/collection-root/ است که میتوانید با tar از آن نسخه پشتیبان تهیه کرده و با less آن را بخوانید. بازیابی شامل استخراج فایلها، chown -R radicale:radicale و راهاندازی مجدد سرویس است. هر کلاینت همگامسازیشده نیز یک نسخه محلی نگه میدارد، بنابراین لپتاپی که پیش از خرابی سرور بهروز بوده، یک نسخه کامل دوم از تقویم شما را در اختیار دارد.
آیا سرور CalDAV مخاطبین من را هم همگامسازی میکند؟
مخاطبین از پروتکل CardDAV استفاده میکنند که یک پروتکل همخانواده تعریفشده در RFC 6352 است و بهجای رویدادها، فایلهای vCard را ذخیره میکند. Radicale، Baikal و Nextcloud همگی این سرویس را از همان حساب و همان نام میزبان ارائه میدهند. در اندروید، DAVx⁵ تقویمها و مخاطبین را از یک حساب همگامسازی میکند. در iOS، شما باید یک حساب دوم از نوع CardDAV با همان اعتبارنامهها اضافه کنید؛ به همین دلیل است که تنظیم /.well-known/carddav در پیکربندی nginx شما باید در کنار تنظیم CalDAV قرار گیرد.