كيفية استضافة ntfy لإرسال تنبيهات الخادم عبر Docker
شغّل ntfy على VPS خاص بك خلف TLS باستخدام Docker Compose، واحمِ الـtopics بالمستخدمين وACLs، ثم أرسل تنبيهات cron ووحدات systemd OnFailure بأمان.
ما الذي يفعله خادم ntfy مستضاف ذاتياً
يحوّل خادم ntfy المستضاف ذاتياً طلب HTTP POST إلى إشعار فوري على هاتفك. تنشر باستخدام curl، وتصل الرسالة إلى تطبيق Android أو تطبيق iOS أو علامة تبويب في المتصفح أو أي جهاز آخر يمكنه إبقاء اتصال HTTP مفتوحاً. لا تحتاج إلى تثبيت مكتبة للعميل أو تشغيل وسيط رسائل.
يعنون ntfy الرسائل باستخدام topic. الـtopic هو اسم في مسار URL، مثل https://ntfy.example.com/alerts، ويُنشأ فور نشر أي شخص رسالة إليه. في التثبيت الافتراضي، يمكن لأي شخص يعرف هذا الاسم قراءة الـtopic والكتابة إليه، ولذلك تقارن وثائق المشروع اسم الـtopic بكلمة مرور. يناسب هذا النموذج خدمة ntfy.sh العامة. لكنه لا يناسب خادماً ينقل إشعارات فشل النسخ الاحتياطي لديك، لذلك يفعّل هذا الدليل المصادقة قبل إرسال أول رسالة.
ما تحتاج إليه قبل البدء
تحتاج إلى VPS يعمل بنظام Ubuntu 24.04 أو Debian 13، مع Docker Engine وCompose plugin، واسم نطاق، وذاكرة RAM قليلة جداً. أنشئ سجل A في DNS (نظام أسماء النطاقات) يوجّه ntfy.example.com إلى عنوان IP العام للخادم، ثم تأكد من أنه يُحلّ قبل تنفيذ أي خطوة أخرى.
dig +short ntfy.example.com
sudo ufw allow 80,443/tcp
sudo ufw statusيجب أن يطبع dig عنوان IP الخاص بخادمك. يفشل إصدار الشهادة إذا لم يطبع شيئاً، لأن الجهة المصدرة للشهادة تتحقق من الاسم من خارج الخادم. يبقى المنفذ 80 مفتوحاً لأن ACME (بيئة إدارة الشهادات تلقائياً)، وهو البروتوكول الذي تستخدمه Let's Encrypt، يستعمله في تحدي HTTP. لا تحصل حاوية ntfy نفسها على منفذ عام.
اكتب ملف إعداد ntfy
لا تحتوي صورة Docker على ملف إعداد، لذلك عليك إنشاء ملف. يقرأ كل أمر لاحق في هذا الدليل من هذا الملف. اعثر أولاً على معرّف المستخدم ومعرّف المجموعة اللذين ستعمل الحاوية بهما.
id -u
id -g
sudo install -d -o "$(id -u)" -g "$(id -g)" /etc/ntfy /var/cache/ntfy /var/lib/ntfy
sudo nano /etc/ntfy/server.ymlbase-url: "https://ntfy.example.com"
listen-http: ":2586"
behind-proxy: true
cache-file: "/var/cache/ntfy/cache.db"
cache-duration: "12h"
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"
enable-login: true
enable-signup: falseتحمل أربعة أسطر من هذه الأسطر المسؤولية الأساسية. يجب أن يكون base-url هو عنوان HTTPS العام الفعلي، لأن ntfy ينشئ روابط المرفقات وطلبات تطبيق الويب نفسه اعتماداً عليه. تؤدي القيمة الخاطئة إلى تحميل تطبيق الويب ثم فشل كل إجراء فيه. يربط listen-http: ":2586" الخدمة بجميع الواجهات داخل الحاوية. قد يبدو ذلك غير حذر، لكنه صحيح: للحاوية مساحة أسماء شبكية خاصة بها، لذلك سيجعل الربط بـ127.0.0.1 المنفذ غير قابل للوصول من المضيف، ولن يتصل منفذ Docker المنشور به. يمثّل auth-default-access: "deny-all" الوضع الأمني بأكمله، لأنه يرفض القراءة والكتابة من أي مستخدم لا يملك منحاً صريحاً. يخبر behind-proxy: true ntfy بأن يأخذ عنوان العميل من ترويسة X-Forwarded-For، لذلك تحسب حدود المعدل عدد الزوار الفعليين بدلاً من اعتبار الـreverse proxy عميلاً واحداً نشطاً جداً.
يتيح enable-login: true لتطبيق الويب وتطبيقات الهاتف تسجيل الدخول باستخدام كلمة مرور. ويبقى enable-signup مضبوطاً على false، لأن السماح بإنشاء الحسابات ذاتياً على خادم خاص يفتح مدخلاً غير ضروري.
sudo chown "$(id -u):$(id -g)" /etc/ntfy/server.yml
sudo chmod 600 /etc/ntfy/server.ymlتشغيل ntfy باستخدام Docker Compose
ضع هذا في /opt/ntfy/compose.yaml، واستبدل 1000:1000 بالرقمين id -u وid -g المطبوعين أعلاه.
services:
ntfy:
image: binwiederhier/ntfy:v2.27.0
container_name: ntfy
command: serve
user: "1000:1000"
environment:
- TZ=UTC
volumes:
- /etc/ntfy:/etc/ntfy
- /var/cache/ntfy:/var/cache/ntfy
- /var/lib/ntfy:/var/lib/ntfy
ports:
- "127.0.0.1:2586:2586"
restart: unless-stoppedcd /opt/ntfy
sudo docker compose up -d
sudo docker compose logs ntfy
curl -s http://127.0.0.1:2586/v1/healthيستجيب الخادم السليم بـ {"healthy":true}. هناك تفصيلان مقصودان في ملف Compose هذا. ثُبّتت الصورة على v2.27.0، وهو الإصدار الحالي حتى August 2026، بدلاً من latest، لأن استخدام latest يغيّر إصدار الخادم عند صدور docker compose pull التالي، ولن تعرف بذلك إلا بعد مراجعة سجل التغييرات. نُشر المنفذ على 127.0.0.1:2586:2586، لذلك لا يمكن الوصول إلى الحاوية إلا من عنوان loopback الخاص بالمضيف. إذا كتبت 2586:2586 بدلاً منه، فسيُدخل Docker قواعد جدار الحماية الخاصة به قبل قواعدك، ما يعني أن المنفذ سيستجيب من الإنترنت رغم أن ufw status يقول إن المنفذ مغلق.
إذا طبع curl القيمة Connection refused، فاقرأ سجل الحاوية. يعني خطأ الصلاحيات على /var/lib/ntfy/user.db أن السطر user: لا يطابق مالك تلك المجلدات، ولذلك لا تستطيع العملية إنشاء قاعدة بياناتها الخاصة وتتوقف. يشرح دليل أساسيات Docker Compose لخادم VPS ملكية volumes وسياسات إعادة التشغيل بمزيد من التفصيل.
ضع TLS أمام الخدمة باستخدام Caddy
يطلب Caddy الشهادة ويجددها تلقائياً، وهذا أقصر طريق لتشغيل TLS (أمن طبقة النقل).
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddyاستبدل محتويات /etc/caddy/Caddyfile بثلاثة أسطر.
ntfy.example.com {
reverse_proxy 127.0.0.1:2586
}sudo systemctl reload caddy
curl -s https://ntfy.example.com/v1/healthيعني نجاح طلب {"healthy":true} نفسه عبر HTTPS أن المسار بالكامل يعمل. يشير 502 الصادر عن Caddy إلى أن ntfy لا يستمع؛ تحقّق باستخدام sudo ss -lntp | grep 2586. يعني خطأ الشهادة عادةً أن سجل DNS غير صحيح أو أن المنفذ 80 محجوب، ويحدد sudo journalctl -u caddy -n 50 أيّهما السبب.
إذا كنت تشغّل nginx بالفعل، فانقل إعدادات الوكيل التي توثقها ntfy: proxy_http_version 1.1 وproxy_buffering off وproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for، واضبط مهلات القراءة والإرسال على ثلاث دقائق على الأقل. يُبقي المشترك اتصال HTTP واحداً مفتوحاً طوال فترة استماعه، بينما يغلق nginx اتصالاً خاملاً بالخادم الداخلي بعد 60 ثانية افتراضياً، لذلك يعيد المشتركون الاتصال في حلقة وقد تفوتهم الرسائل المرسلة أثناء الفجوة.
إنشاء المستخدمين وتقييد الموضوعات
تم تفعيل المصادقة، ولا يملك أي مستخدم صلاحية الوصول إلى أي شيء بعد. وهذا هو المطلوب. أنشئ حساب مسؤول واحداً لك، وحساباً آلياً واحداً للبرامج النصية. تقرأ هذه الأوامر /etc/ntfy/server.yml من داخل الحاوية، ولذلك فإن ملف الإعدادات مركّب كـvolume.
sudo docker compose exec ntfy ntfy user add --role=admin admin
sudo docker compose exec ntfy ntfy user add robot
sudo docker compose exec ntfy ntfy user listيطلب كل أمر كلمة مرور. يتجاهل المسؤول قائمة التحكم، ويمكنه قراءة كل موضوع والكتابة إليه. لذلك احتفظ بهذا الحساب لك ولتطبيق الهاتف. robot مستخدم عادي لا يملك أي صلاحية حتى تمنحه بعضها.
sudo docker compose exec ntfy ntfy access robot alerts write
sudo docker compose exec ntfy ntfy access robot "alerts_*" write
sudo docker compose exec ntfy ntfy accessإدخال ACL هو مستخدم وموضوع وصلاحية. يمكن أن يكون الموضوع اسماً حرفياً أو نمطاً، حيث يطابق * أي قيمة. لذلك يغطي alerts_* كلاً من alerts_backup وalerts_db دون الحاجة إلى أمر مستقل لكل مضيف. تعني الصلاحية write النشر فقط. لذلك لا يستطيع أي شخص يسرق رمزاً من مهمة cron الاشتراك وقراءة ما أرسلته المهمة. يحدد اسم المستخدم الخاص everyone ما يمكن لزائر غير موثّق القيام به. ولا تستخدمه إلا لفتح مورد عام عمداً، مثل ntfy access everyone status read.
يجب أن تحمل البرامج النصية رمزاً مميزاً، لا كلمة مرورك.
sudo docker compose exec ntfy ntfy token add robotيطبع الأمر رمزاً مميزاً يبدأ بـtk_. يرث الرمز المميز صلاحيات المستخدم المرتبط به بالكامل. لذلك يستطيع هذا الرمز النشر إلى الموضوعات alerts ولا يستطيع تنفيذ أي إجراء آخر. يعرض ntfy token list ما هو موجود، بينما يلغي ntfy token remove رمزاً واحداً دون تغيير كلمة مرور المستخدم.
إرسال رسالتك الأولى وإثبات عمل القفل
ابدأ بالتحقق من أن الباب مغلق.
curl -s -o /dev/null -w '%{http_code}\n' -d "hello" https://ntfy.example.com/alertsيطبع ذلك 403، وتكون 403 هي الإجابة الصحيحة: إذ يرفض auth-default-access: "deny-all" النشر المجهول. أرسل الآن رسالة فعلية.
curl -H "Authorization: Bearer tk_REPLACE_WITH_YOUR_TOKEN" \
-H "Title: Nightly backup finished" \
-H "Priority: default" \
-H "Tags: white_check_mark" \
-d "42 GB copied in 11 minutes" \
https://ntfy.example.com/alertsيرد الخادم بالرسالة المخزنة بتنسيق JSON، وهذا يثبت قبولها بدلاً من تجاهلها. تمثل Title السطر الأول العريض. تتراوح Priority من 1 إلى 5، أو يمكن تحديدها بالاسم من min إلى urgent، وهي التي تحدد ما إذا كان الهاتف سيصدر صوتاً. تتحول Tags إلى رموز تعبيرية في الإشعار عندما يطابق الاسم رمزاً مختصراً معروفاً لرمز تعبيري، وتبقى نصاً عادياً عندما لا يطابقه.
لمراقبة موضوع من الطرفية، اعرض تدفقه:
curl -s -u admin https://ntfy.example.com/alerts/rawيطلب curl كلمة المرور. تصل كل رسالة في سطر واحد، أما الأسطر الفارغة التي تظهر من وقت إلى آخر فهي رسائل إبقاء الاتصال حياً. يؤدي فتح https://ntfy.example.com في متصفح وتسجيل الدخول بالحساب نفسه إلى عرض إصدار تطبيق الويب من التدفق نفسه.
ضع حدوداً للطلبات حتى لا يتمكن برنامج نصي واحد من إغراق الخادم
بشكل افتراضي، يحصل كل زائر على حصة قدرها 60 طلباً، وتُعاد تعبئتها بمعدل طلب واحد كل 5 ثوانٍ. هذا الحد مرتفع لخادم خاص، وسيستنفده برنامج نصي عالق في حلقة إعادة المحاولة. أضف الحدود إلى server.yml.
visitor-request-limit-burst: 30
visitor-request-limit-replenish: "10s"
visitor-message-daily-limit: 500sudo docker compose restart ntfyيحصل الزائر الذي يتجاوز الحد على HTTP 429 بدلاً من تسليم الرسالة. يُحتسب الحد لكل عنوان زائر، ولذلك فإن behind-proxy: true مهم جداً: بدونه، لا يرى ntfy سوى عنوان Caddy، ويُحتسب كل عميل على أنه الزائر نفسه، ويستنفد برنامج نصي كثير الضجيج الحصة التي يشترك فيها هاتفك وخوادمك الأخرى.
تنبيه من مهمة cron تفشل
أبقِ الرمز السري خارج سطر الأوامر. يعرض ps aux سطر الأوامر الكامل لكل عملية قيد التشغيل لجميع المستخدمين على الخادم، لذلك يكون الرمز السري الممرَّر باستخدام -H قابلاً للقراءة من أي حساب محلي طوال مدة تشغيل curl. يتجنب ملف إعدادات curl ذلك.
sudo install -d -m 700 /etc/ntfy-alert
printf 'header = "Authorization: Bearer tk_REPLACE_WITH_YOUR_TOKEN"\n' | sudo tee /etc/ntfy-alert/curlrc
sudo chmod 600 /etc/ntfy-alert/curlrcغلّف المهمة الآن. احفظ هذا باسم /usr/local/bin/backup-with-alert.sh ثم نفّذ عليه chmod 750.
#!/bin/bash
out=$(/usr/local/bin/backup.sh 2>&1)
code=$?
if [ "$code" -ne 0 ]; then
printf '%s' "$out" | tail -c 1000 | curl -K /etc/ntfy-alert/curlrc \
-H "Title: backup.sh failed with exit $code" \
-H "Priority: high" \
-H "Tags: warning" \
--data-binary @- \
https://ntfy.example.com/alerts
fi
exit "$code"17 3 * * * /usr/local/bin/backup-with-alert.sh >> /var/log/backup-alert.log 2>&1يُلتقط $? في السطر الذي يلي الأمر مباشرة، لأن تشغيل الأمر التالي سيستبدله. يمر الإخراج عبر tail -c 1000 لأن ntfy يفرض حداً أقصى لحجم الرسالة، ولأن الإشعار ليس عارضاً للسجلات. يحافظ exit "$code" الختامي على حالة الخروج الأصلية، لذلك ستظل أي جهة أخرى تراقب هذه المهمة ترى أنها فشلت. اختبر العملية كاملة بتوجيه البرنامج النصي إلى /bin/false لتشغيل واحد.
يكون فرع الفشل الذي لا يُنفَّذ أسوأ من عدم وجود تنبيهات، لأنه يوحي بأن الصمت يعني النجاح. يوفّر cron لمهمتك بيئة شبه فارغة وPATH أقصر بكثير من بيئة shell الخاصة بتسجيل الدخول، لذلك قد يتوقف البرنامج النصي قبل أن يصل إلى سطر curl، رغم أنه يعمل عند تشغيله يدوياً. يشرح الدليل حول أسباب عدم تشغيل مهمة cron هذه المشكلات المتعلقة بالبيئة. استخدم المسارات المطلقة في كل المواضع، واقرأ ملف السجل بعد أول تشغيل مجدول بدلاً من الافتراض.
تنبيه عند فشل وحدة systemd
يتولى Cron الأعمال المجدولة. أما الخدمات طويلة التشغيل فتحتاج إلى OnFailure=، الذي يشغّله systemd عندما تدخل الوحدة في حالة failed. أنشئ وحدة قالب واحدة وأعد استخدامها لكل خدمة على الخادم. احفظها باسم /etc/systemd/system/ntfy-unit-failed@.service.
[Unit]
Description=Send an ntfy alert because %i failed
[Service]
Type=oneshot
ExecStart=/usr/local/bin/ntfy-unit-failed %iثم /usr/local/bin/ntfy-unit-failed، مع ضبط الوضع على 750:
#!/bin/bash
unit="$1"
journalctl -u "$unit" -n 15 --no-pager -o cat | tail -c 1000 | curl -K /etc/ntfy-alert/curlrc \
-H "Title: $unit failed on $(hostname -s)" \
-H "Priority: urgent" \
-H "Tags: rotating_light" \
--data-binary @- \
https://ntfy.example.com/alertsأرفقها بخدمة باستخدام ملف drop-in، حتى لا تتمكن ترقية الحزمة من استبدال تعديلاتك.
sudo systemctl edit myapp.service[Unit]
OnFailure=ntfy-unit-failed@%n.serviceيوسّع %n إلى اسم الوحدة الكامل، لذلك يصبح اسم النسخة ntfy-unit-failed@myapp.service، بينما يمرّر %i داخل القالب myapp.service إلى البرنامج النصي باعتباره الوسيط الأول. يتيح ذلك لقالب واحد خدمة كل وحدة. اختبر عمله باستخدام وحدة تتعمد الفشل، واحفظها باسم /etc/systemd/system/ntfy-selftest.service.
[Unit]
Description=Deliberately failing unit
OnFailure=ntfy-unit-failed@%n.service
[Service]
Type=oneshot
ExecStart=/bin/falsesudo systemctl daemon-reload
sudo systemctl start ntfy-selftest.serviceيخرج أمر البدء برمز غير صفري ويطبع Job for ntfy-selftest.service failed because the control process exited with error code، وينبغي أن يهتز الهاتف بعد نحو ثانية. احذف وحدة الاختبار بعد ذلك.
توجد نقطة مهمة يجب الانتباه إليها. يعمل OnFailure= فقط عندما تصل الوحدة إلى حالة failed، وقد لا تصل إليها خدمة تحتوي على Restart=always، لأن systemd يواصل إعادة تشغيلها بدلاً من ذلك. لا تفشل الوحدة إلا بعد أن تتجاوز StartLimitBurst عمليات إعادة التشغيل خلال StartLimitIntervalSec. اضبط هاتين القيمتين على أي خدمة تريد تلقي تنبيه بشأنها، وإلا فقد تستمر حلقة الانهيار بصمت لأيام. تُعد المؤقتات بديلاً أنظف لنمط cron السابق، لأن وحدة الخدمة الخاصة بالمؤقت تحصل على OnFailure= تلقائياً، كما يشرح الدليل الخاص بخدمات systemd والمؤقتات على VPS كيفية تحويل إحدى هذه الوحدات.
اربط مراقب وقت التشغيل بالموضوع نفسه
Uptime Kuma، مراقب الحالة المستضاف ذاتياً، يوفّر نوع إشعار ntfy. افتح Settings، ثم Notifications، ثم Setup Notification، واختر Ntfy، واضبط عنوان الخادم على https://ntfy.example.com والموضوع على alerts، واختر مستوى أولوية، والصق رمز الوصول robot. أرسل إشعار الاختبار قبل الحفظ، لأن اسم الموضوع الخاطئ يفشل بصمت عند استخدام منحة write التي لا تغطيه.
الحد الفعلي لهذا الترتيب هو أن مراقباً يعمل على VPS نفسه لا يستطيع إخبارك بأن VPS متوقف، كما أن ntfy لا يستطيع إيصال خبر تعطل ntfy. شغّل المراقب على جهاز مختلف، وامنحه قناة إشعار ثانية، مثل البريد الإلكتروني، لمراقبة ntfy نفسه. يغطي نوع Push في Uptime Kuma نقطة الضعف الأخرى: يستدعي cron job عنوان push بعد نجاح التشغيل، وينبّه Kuma عند توقف وصول هذه الاستدعاءات. لا يعمل فرع الفشل إلا عند تشغيل المهمة، لذلك لا يوضح شيئاً عن المهمة التي لم تبدأ أصلاً.
هل يعمل ntfy المستضاف ذاتياً على Android وiPhone؟
على Android، نعم دون قيود. ثبّت التطبيق من Google Play أو F-Droid، وافتح Settings، واضبط الخادم الافتراضي على https://ntfy.example.com، ثم أضف حسابك من شاشة إدارة المستخدمين، واشترك بعد ذلك في alerts. يحافظ التسليم الفوري على تشغيل خدمة في المقدمة، لذلك تصل الرسائل حتى عندما يكون الهاتف في وضع doze، والإشعار الدائم المصاحب لها هو متطلب في Android للخدمات التي تعمل في المقدمة، وليس خطأً. لا يتضمن إصدار F-Droid أي شيفرة Firebase على الإطلاق، لذلك يستخدم كل اشتراك التسليم الفوري. ويمكن لـntfy أيضاً العمل كموزّع UnifiedPush، وهو بديل مفتوح لخدمة الإشعارات الفورية من Google، ولذلك تستطيع التطبيقات الأخرى التي تدعم UnifiedPush التسليم عبر خادمك أيضاً.
على iOS، يعمل مع تبعية واحدة لا يمكنك إزالتها. لا يوقظ Apple تطبيقاً في الخلفية إلا عبر APNs (Apple push notification service)، ولا يستطيع إرسال الإشعار إليه إلا الطرف الذي يملك بيانات توقيع التطبيق، لذلك لا يملك خادمك وسيلة للوصول إلى التطبيق مباشرة. يحل ntfy هذه المشكلة باستخدام مرحّل: يرسل خادمك poll_request يتضمن معرّف الرسالة إلى ntfy.sh، الذي يمرره عبر Firebase وAPNs لإيقاظ التطبيق، ثم يجلب التطبيق نص الرسالة من خادمك.
upstream-base-url: "https://ntfy.sh"كن واضحاً بشأن تكلفة ذلك. يبقى محتوى الرسالة على خادمك، لكن حقيقة وصول رسالة ومعرّفها يمران عبر بنية تحتية لا تديرها. من دون هذا الإعداد، تصل إشعارات iPhone من خادم مستضاف ذاتياً متأخرة أو لا تصل إطلاقاً، لأن ما من شيء يوقظ التطبيق. الطريقة الوحيدة لإزالة المرحّل هي أن تبني تطبيق iOS بنفسك وتنشره باستخدام حساب Apple Developer الخاص بك ومفاتيح APNs الخاصة بك، وهذا يعني دفع رسوم سنوية وإعادة البناء مع كل تحديث. إذا كان استخدام المرحّل غير مقبول لك، فأبقِ التنبيهات على Android أو على تطبيق الويب المكتبي.
النسخ الاحتياطية والترقيات وتثبيت وسم الصورة
لا يمكن إعادة إنشاء المسارين التاليين: /etc/ntfy/server.yml و/var/lib/ntfy/user.db. يحتوي الثاني على جميع المستخدمين وتجزئات كلمات المرور وإدخالات ACL والرموز المميزة، لذا تعامل معه مثل مفتاح تشفيري خاص.
sudo tar czf ntfy-backup.tgz -C / etc/ntfy var/lib/ntfy
sudo chmod 600 ntfy-backup.tgzانسخ هذا الملف إلى خارج الخادم. يحتوي cache.db على الرسائل الحديثة فقط، أي رسائل 12 ساعة مع cache-duration المذكور أعلاه، لذا لا يؤدي فقدانه إلى خسارة بيانات تستحق الحماية. تعني الترقية تعديل الوسم في ملف compose ثم تنفيذ عملية السحب.
sudo docker compose pull
sudo docker compose up -d
curl -s https://ntfy.example.com/v1/healthاقرأ ملاحظات الإصدار أولاً. تُرحَّل قواعد بيانات SQLite عند بدء التشغيل، لذا لا يُعد الرجوع إلى وسم أقدم بعد تغيير المخطط آمناً. احتفظ بالنسخة الاحتياطية التي أنشأتها للتو حتى يعمل الإصدار الجديد لمدة يوم.
Gotify وApprise
يُعدّ Gotify الخيار الأصغر: ملفاً ثنائياً واحداً مع واجهة ويب وتطبيق Android، من دون دعم للبدائل العامة في أسماء الموضوعات ومن دون عميل iOS رسمي. لذلك يناسب خادماً خاصاً يكون فيه Android الوجهة الوحيدة.
Apprise مكتبة Python وأداة لسطر الأوامر، وليست خادماً. وتوزّع الرسالة نفسها على أكثر من مئة خدمة، بما فيها ntfy. لذلك تناسب برنامجاً نصياً يجب أن يرسل التنبيهات إلى عدة وجهات في الوقت نفسه.
أما ntfy فهو الخيار الذي يوفّر خادماً وواجهة HTTP API وتطبيقات على منصتي الأجهزة المحمولة. لذلك فهو الإجابة المعتادة لإرسال التنبيهات من خادم مستأجر.
FAQ
لماذا يعيد النشر إلى خادم ntfy لدي الرمز 403؟
عند استخدام auth-default-access: "deny-all" في server.yml، يُرفض النشر المجهول، وهذا هو السلوك المقصود. أرسل بيانات الاعتماد باستخدام -u user:pass أو -H "Authorization: Bearer tk_...". إذا كنت ترسل رمزاً مميزاً بالفعل وما زلت تتلقى 403، فلا يملك المستخدم المرتبط بذلك الرمز إدخال ACL مطابقاً للموضوع. شغّل ntfy access لطباعة القائمة الكاملة. تذكّر أن منح write لا يتيح الاشتراك، لذلك سيُرفض الحساب الذي ينشر بنجاح عندما يحاول قراءة الموضوع نفسه.
هل تعمل الإشعارات على iPhone مع خادم ntfy مستضاف ذاتياً؟
نعم، لكنها تعمل عبر مرحّل لا يمكنك تجنبه. لا يوقظ Apple التطبيقات إلا عبر APNs (خدمة Apple للإشعارات الفورية)، ولا يستطيع إرسال الإشعارات إليها إلا ناشر التطبيق. لذلك يرسل ntfy رسالة poll_request تحتوي على معرّف الرسالة إلى ntfy.sh، الذي يمررها إلى الجهاز. عيّن upstream-base-url: "https://ntfy.sh" في server.yml ثم أعد تشغيل الحاوية. أما نص الرسالة نفسه، فيُجلب من خادمك. من دون هذا الإعداد، تتأخر إشعارات iOS أو لا تظهر مطلقاً.
لماذا لم يصل تنبيه ntfy من مهمة cron؟
شغّل سطر curl منفرداً أولاً للتأكد من صحة الرمز المميز والموضوع. إذا نجح يدوياً ولم ينجح من cron، فالفشل يحدث قبل التنبيه: تشغّل cron المهام ببيئة محدودة وPATH قصير، لذلك قد يتوقف البرنامج النصي الذي يستدعي أمراً باسمه المجرد قبل الوصول إلى سطر curl. استخدم المسارات المطلقة، وأعد توجيه مخرجات المهمة إلى ملف سجل، ثم اقرأ ذلك الملف بعد التشغيل التالي. تعني استجابة 429 بدلاً من التسليم أن تحديد معدل الطلبات يعمل وأن البرنامج النصي يعيد المحاولة بسرعة كبيرة.
هل ينبغي أن أعرّض ntfy للإنترنت العام؟
تحتاج تطبيقات الهاتف إلى الوصول إليه عبر شبكات الهاتف المحمول، لذلك تُعد نقطة نهاية HTTPS عامة مع auth-default-access: "deny-all" وقوائم ACL لكل موضوع الإعداد المعتاد. ويكون هذا آمناً ما دام لا يمكن قراءة أي موضوع بواسطة everyone. يكون المثيل المتاح عبر VPN فقط مناسباً عندما يكون كل مشترك جهازاً تتحكم فيه. لكنه لا يناسب الهواتف جيداً، لأن التطبيق لا يستقبل الإشعارات إلا عندما يكون النفق متصلاً، ولذلك تتراكم التنبيهات إلى أن يعيد الهاتف الاتصال.