استضافة ntfy ذاتياً لإشعارات الخادم الفورية
شغّل ntfy على VPS خلف TLS باستخدام Docker Compose، واحمِ الموضوعات بالمستخدمين وACL، ثم أرسل تنبيهات cron ووحدات systemd OnFailure بأمان.
ما الذي يفعله خادم ntfy المستضاف ذاتياً
يحوّل خادم ntfy المستضاف ذاتياً طلب HTTP POST إلى إشعار فوري على هاتفك. تنشر الرسالة باستخدام curl، ثم تصل إلى تطبيق Android أو تطبيق iOS أو علامة تبويب في المتصفح أو أي جهة أخرى يمكنها إبقاء اتصال HTTP مفتوحاً. لا تحتاج إلى تثبيت مكتبة للعميل أو تشغيل وسيط رسائل.
يعنون ntfy الرسائل باستخدام الموضوع. الموضوع هو اسم في مسار URL، مثل https://ntfy.example.com/alerts، ويُنشأ فور نشر أي شخص رسالة عليه. في التثبيت الافتراضي، يمكن لأي شخص يعرف هذا الاسم قراءة الموضوع والكتابة إليه، ولذلك تقارن وثائق المشروع نفسها اسم الموضوع بكلمة مرور. يناسب هذا النموذج خدمة ntfy.sh العامة. لكنه لا يناسب خادماً ينقل إشعارات فشل النسخ الاحتياطي لديك، لذلك يفعّل هذا الدليل المصادقة قبل إرسال أول رسالة.
ما تحتاج إليه قبل البدء
تحتاج إلى VPS يعمل بنظام Ubuntu 24.04 أو Debian 13، مع Docker Engine وCompose plugin، واسم نطاق، وذاكرة RAM محدودة جداً. أنشئ سجل DNS من النوع A يوجّه 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 يشير إلى أن المنفذ مغلق. ينطبق هذان الأسلوبان أيضاً على الحاوية التالية التي تضيفها: يثبّت مرحل RustDesk المستضاف ذاتياً وسم الصورة بالطريقة نفسها، لكنه لا يستطيع الاختباء خلف loopback، لأن منفذي الإشارة والترحيل يجب أن يستجيبا من الإنترنت.
إذا طبع curl القيمة Connection refused، فاقرأ سجل الحاوية. يعني خطأ الصلاحيات على /var/lib/ntfy/user.db أن السطر user: لا يطابق مالك تلك الأدلة، ولذلك لا تستطيع العملية إنشاء قاعدة بياناتها الخاصة وتخرج. يشرح دليل أساسيات Docker Compose لخادم VPS ملكية وحدات التخزين وسياسات إعادة التشغيل بمزيد من التفصيل.
ضع 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 أيهما. عند إضافة خدمة أخرى لاحقاً، يكفي إضافة كتلة hostname أخرى في ملف Caddyfile نفسه. بهذه الطريقة يمكن لشيء مثل Halcyon، واجهة متجر فيديو من تسعينيات القرن الماضي لمكتبة Jellyfin لديك أن يعمل على نطاق فرعي ثانٍ على الخادم نفسه.
إذا كنت تشغّل nginx مسبقاً، فانسخ إعدادات proxy التي توثقها ntfy: proxy_http_version 1.1 وproxy_buffering off وproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for، واضبط مهلات القراءة والإرسال على ثلاث دقائق على الأقل. يحتفظ المشترك باتصال HTTP واحد مفتوحاً طوال مدة استماعه، ويغلق nginx اتصال upstream الخامل بعد 60 ثانية افتراضياً. لذلك يعيد المشتركون الاتصال في حلقة، وتفوت الرسائل المُرسلة أثناء الفاصل.
إنشاء المستخدمين وتقييد الوصول إلى الموضوعات
تم تفعيل المصادقة، ولا يملك أي مستخدم صلاحية الوصول إلى أي شيء حتى الآن، وهذا هو المطلوب. أنشئ حساب مسؤول واحداً لك، وحساباً آلياً واحداً للبرامج النصية. تقرأ هذه الأوامر /etc/ntfy/server.yml من داخل الحاوية، ولذلك فإن ملف الإعداد موصول بوصفه volume mount.
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 النشر فقط، لذلك لا يستطيع أحد استخدام token مسروق من cron job للاشتراك وقراءة ما أرسله. يحدد اسم المستخدم الخاص everyone ما يمكن لزائر غير موثّق فعله، ولا تستخدمه إلا لفتح مورد عام عن قصد، مثل ntfy access everyone status read.
يجب أن تحمل البرامج النصية token، لا كلمة مرورك.
sudo docker compose exec ntfy ntfy token add robotيطبع الأمر token يبدأ بـtk_. يرث token صلاحيات المستخدم الذي ينتمي إليه بالكامل، ولذلك يمكن لهذا token النشر إلى الموضوعات 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 إلى رموز emoji في الإشعار عندما يطابق الاسم رمزاً مختصراً معروفاً لـemoji، وتبقى نصاً عادياً عندما لا يطابقه.
لمراقبة موضوع من الطرفية، ابثه:
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، باستخدام mode 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. عيّن هاتين القيمتين في أي خدمة تريد تلقي تنبيه عنها، وإلا فستستمر حلقة الأعطال بصمت لأيام. تُعد Timers بديلاً أنظف لنمط 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 monitor في 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 للإشعارات الفورية)، ولا يستطيع إرسال الإشعار إليه إلا الطرف الذي يملك بيانات اعتماد توقيع التطبيق. لذلك لا يمكن لخادمك الوصول إلى التطبيق مباشرة. يحل ntfy هذه المشكلة باستخدام مرحّل: يرسل خادمك poll_request الذي يحتوي على معرّف الرسالة إلى ntfy.sh، فيمرّره 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 hours مع cache-duration المذكور أعلاه، لذا لا يسبب فقدانه خسارة تستحق الحماية. تعني الترقية تعديل الوسم في ملف compose ثم تنفيذ pull.
sudo docker compose pull
sudo docker compose up -d
curl -s https://ntfy.example.com/v1/healthاقرأ ملاحظات الإصدار أولاً. تُجري قواعد بيانات SQLite عملية الترحيل عند بدء التشغيل، لذلك لا تكون العودة إلى وسم أقدم بعد تغيير المخطط آمنة. احتفظ بالنسخة الاحتياطية التي أنشأتها للتو حتى يعمل الإصدار الجديد لمدة day. لكل خدمة على الخادم قائمة قصيرة خاصة بها من المسارات التي لا يمكن إعادة إنشائها، وتوضح مقارنة PhotoPrism وImmich هذه القائمة وأوامر النسخ الاحتياطي المرتبطة بها لمكتبة صور على VPS من النوع نفسه.
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 مستضاف ذاتياً؟
نعم، لكنها تمر عبر relay لا يمكنك تجنّبه. لا يوقظ Apple التطبيقات إلا عبر APNs (Apple push notification service)، ولا يستطيع الإرسال إليه إلا ناشر التطبيق، لذلك يرسل 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 مناسباً عندما يكون كل مشترك جهازاً تتحكم فيه. لكنه لا يناسب الهواتف جيداً، لأن التطبيق يستقبل الإشعارات فقط أثناء اتصال النفق، ولذلك تتراكم التنبيهات إلى أن يعاود الهاتف الاتصال.