SSD Nodes Learn Hosting plans →
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-27

নিজের সার্ভারে ntfy সেটআপ করার নিয়ম

Docker Compose ব্যবহার করে নিজের VPS-এ ntfy সার্ভার হোস্ট করুন। TLS কনফিগারেশন, ACL এবং ইউজার অথেন্টিকেশন সেটআপ করে cron ও systemd থেকে সরাসরি পুশ নোটিফিকেশন পাওয়ার উপায় জানুন।

একটি self-hosted ntfy সার্ভার যা করে

একটি self-hosted ntfy সার্ভার একটি HTTP POST অনুরোধকে আপনার ফোনে push notification হিসেবে রূপান্তর করে। আপনি curl ব্যবহার করে বার্তা পাঠান এবং সেটি Android অ্যাপ, iOS অ্যাপ, ব্রাউজার ট্যাব অথবা HTTP সংযোগ খোলা রাখতে সক্ষম এমন যেকোনো ডিভাইসে পৌঁছে যায়। এর জন্য কোনো client library ইনস্টল করতে হয় না এবং কোনো message broker চালানোর প্রয়োজন নেই।

ntfy বার্তাগুলোকে topic-এর মাধ্যমে চিহ্নিত করে। একটি topic হলো URL path-এর একটি নাম, যেমন https://ntfy.example.com/alerts, এবং কেউ এতে বার্তা পাঠানোর সাথে সাথেই এটি তৈরি হয়ে যায়। ডিফল্ট ইনস্টলেশনে, যে কেউ এই নামটি জানলে topic-টি পড়তে বা এতে লিখতে পারে, যে কারণে প্রকল্পের নিজস্ব নথিপত্রে topic-এর নামকে পাসওয়ার্ডের সাথে তুলনা করা হয়েছে। পাবলিক ntfy.sh সার্ভিসের জন্য এই মডেলটি ঠিক আছে। কিন্তু আপনার ব্যাকআপ ব্যর্থতার মতো সংবেদনশীল তথ্য বহনকারী সার্ভারের জন্য এটি নিরাপদ নয়, তাই এই নির্দেশিকায় প্রথম বার্তা পাঠানোর আগেই authentication চালু করা হয়েছে।

শুরু করার আগে আপনার যা প্রয়োজন

আপনার একটি VPS প্রয়োজন যেখানে Ubuntu 24.04 অথবা Debian 13-এর সাথে Docker Engine এবং Compose plugin ইনস্টল করা থাকবে। এছাড়া একটি ডোমেইন নাম এবং খুব সামান্য RAM প্রয়োজন। একটি DNS (domain name system) A record তৈরি করুন যা ntfy.example.com-কে সার্ভারের পাবলিক IP ঠিকানায় নির্দেশ করবে। অন্য কোনো কাজ শুরু করার আগে নিশ্চিত হয়ে নিন যে এটি সঠিকভাবে resolve হচ্ছে।

dig +short ntfy.example.com
sudo ufw allow 80,443/tcp
sudo ufw status

dig কমান্ডটি অবশ্যই আপনার সার্ভারের IP প্রদর্শন করবে। যদি এটি কিছু না দেখায়, তবে সার্টিফিকেট ইস্যু করা ব্যর্থ হবে, কারণ সার্টিফিকেট অথরিটি বাইরের দিক থেকে ডোমেইন নামটি যাচাই করে। Port 80 খোলা রাখতে হবে, কারণ Let's Encrypt-এর পেছনে থাকা ACME (automatic certificate management environment) প্রোটোকলটি HTTP challenge-এর জন্য এটি ব্যবহার করে। ntfy কন্টেইনারটির জন্য কোনো পাবলিক পোর্ট খোলার প্রয়োজন নেই।

ntfy কনফিগারেশন ফাইল তৈরি করা

Docker ইমেজে কোনো কনফিগারেশন ফাইল থাকে না, তাই আপনাকে একটি তৈরি করতে হবে। এই গাইডের পরবর্তী প্রতিটি কমান্ড এই ফাইলটি থেকে তথ্য পড়বে। প্রথমে কন্টেইনারটি যে user ID এবং group ID ব্যবহার করে চলবে তা খুঁজে বের করুন।

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.yml
base-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 হেডার থেকে নেওয়া হয়, যাতে রেট লিমিট করার সময় রিভার্স প্রক্সিকে একটি মাত্র ব্যস্ত ক্লায়েন্ট হিসেবে না ধরে প্রকৃত ভিজিটরদের গণনা করা যায়।

enable-login: true ওয়েব অ্যাপ এবং ফোন অ্যাপগুলোকে পাসওয়ার্ড দিয়ে সাইন-ইন করার সুযোগ দেয়। enable-signup false রাখা হয়েছে, কারণ ব্যক্তিগত সার্ভারে সেলফ-সার্ভিস অ্যাকাউন্ট তৈরির সুবিধা রাখা মানেই অনাকাঙ্ক্ষিতদের জন্য দরজা খুলে দেওয়া।

sudo chown "$(id -u):$(id -g)" /etc/ntfy/server.yml
sudo chmod 600 /etc/ntfy/server.yml

Docker Compose দিয়ে ntfy চালানো

এটি /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-stopped
cd /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 file-এ দুটি বিষয় ইচ্ছাকৃতভাবে নির্ধারণ করা হয়েছে। Image-টি v2.27.0-এ pin করা হয়েছে, যা August 2026 অনুযায়ী বর্তমান release; latest ব্যবহার করা হয়নি। কারণ latest ব্যবহার করলে পরবর্তী docker compose pull-এ server version পরিবর্তিত হয়, এবং পরে changelog দেখে বিষয়টি জানতে হয়। Port-টি 127.0.0.1:2586:2586 হিসেবে publish করা হয়েছে, তাই container-এ শুধু host-এর loopback address থেকে পৌঁছানো যায়। এর পরিবর্তে 2586:2586 লিখলে Docker আপনার firewall rule-এর আগে নিজস্ব firewall rule বসায়। ফলে ufw status port-টি closed দেখালেও internet থেকে ওই port-এ সাড়া পাওয়া যায়। আপনি পরে যে container যোগ করবেন, তার ক্ষেত্রেও এই দুটি অভ্যাস প্রযোজ্য হবে: self-hosted RustDesk relay একইভাবে image tag pin করে। তবে সেটিকে loopback-এর আড়ালে রাখা যায় না, কারণ এর signal এবং relay port-গুলোকে internet থেকে সাড়া দিতে হয়।

যদি curl কমান্ডটি Connection refused আউটপুট দেয়, তবে কন্টেইনারের লগ পড়ুন। /var/lib/ntfy/user.db-এ permission error হওয়ার অর্থ হলো user: লাইনটি ওই ডিরেক্টরিগুলোর মালিকের সাথে মিলছে না, তাই প্রসেসটি নিজের ডেটাবেস তৈরি করতে পারছে না এবং বন্ধ হয়ে যাচ্ছে। VPS-এর জন্য Docker Compose-এর মৌলিক নির্দেশিকা-তে ভলিউম ওনারশিপ এবং রিস্টার্ট পলিসি সম্পর্কে বিস্তারিত আলোচনা করা হয়েছে।

Caddy ব্যবহার করে TLS যুক্ত করা

Caddy নিজে থেকেই সার্টিফিকেট অনুরোধ এবং নবায়ন করে, যা TLS (transport layer security) সচল করার সবচেয়ে সংক্ষিপ্ত পথ।

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

HTTPS-এর মাধ্যমে একই {"healthy":true} ব্যবহার করার অর্থ হলো পুরো পাথটি সঠিকভাবে কাজ করছে। Caddy থেকে আসা একটি 502-এর অর্থ হলো ntfy লিসেন করছে না: sudo ss -lntp | grep 2586 দিয়ে এটি পরীক্ষা করুন। সার্টিফিকেট ত্রুটির অর্থ সাধারণত DNS রেকর্ড ভুল অথবা পোর্ট 80 ব্লক করা আছে, এবং sudo journalctl -u caddy -n 50 নির্দেশ করে কোনটি সমস্যা করছে। পরবর্তীতে অন্য কোনো সার্ভিস যোগ করতে চাইলে Caddyfile-এ আরও একটি hostname ব্লক যুক্ত করলেই হয়, ঠিক যেভাবে Halcyon, আপনার Jellyfin লাইব্রেরির জন্য 90-এর দশকের ভিডিও স্টোর ফ্রন্ট এন্ড একই সার্ভারের দ্বিতীয় সাবডোমেইনে যুক্ত করা যায়।

আপনি যদি ইতিমধ্যে nginx ব্যবহার করেন, তবে ntfy-এর ডকুমেন্টেশনে থাকা প্রক্সি সেটিংসগুলো কপি করুন: proxy_http_version 1.1, proxy_buffering off, proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for, এবং রিড ও সেন্ড টাইমআউট অন্তত তিন মিনিটের জন্য সেট করুন। একজন সাবস্ক্রাইবার যতক্ষণ লিসেন করে ততক্ষণ একটি HTTP কানেকশন খোলা রাখে, আর nginx ডিফল্টভাবে 60 সেকেন্ড পর অলস আপস্ট্রিম কানেকশন বন্ধ করে দেয়। ফলে সাবস্ক্রাইবাররা বারবার রিকানেক্ট করতে থাকে এবং এই বিরতির সময় পাঠানো মেসেজগুলো হারিয়ে যায়।

ইউজার তৈরি করা এবং টপিক লক করা

Authentication চালু আছে এবং এখন পর্যন্ত কারো কোনো কিছুতে অ্যাক্সেস নেই, এটাই উদ্দেশ্য। নিজের জন্য একটি অ্যাডমিন অ্যাকাউন্ট এবং স্ক্রিপ্টের জন্য একটি মেশিন অ্যাকাউন্ট তৈরি করুন। এই কমান্ডগুলো কন্টেইনারের ভেতর থেকে /etc/ntfy/server.yml পড়ে, যে কারণে কনফিগারেশন ফাইলটি একটি ভলিউম মাউন্ট হিসেবে রাখা হয়েছে।

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 (access control list) এন্ট্রিতে একজন ইউজার, একটি টপিক এবং একটি পারমিশন থাকে। টপিকটি হয় একটি নির্দিষ্ট নাম অথবা এমন একটি প্যাটার্ন যেখানে * যেকোনো কিছুকে নির্দেশ করে, তাই alerts_* ব্যবহার করলে প্রতিটি হোস্টের জন্য আলাদা কমান্ড ছাড়াই alerts_backup এবং alerts_db কভার করা যায়। write পারমিশন মানে শুধুমাত্র পাবলিশ করা, তাই ক্রন জব থেকে চুরি হওয়া কোনো টোকেন সাবস্ক্রাইব করে পাঠানো তথ্য পড়তে পারবে না। বিশেষ ইউজারনেম 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" বেনামী প্রকাশ (anonymous publish) প্রত্যাখ্যান করে। এখন একটি আসল মেসেজ পাঠান।

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 পাসওয়ার্ডের জন্য প্রম্পট করবে। প্রতিটি মেসেজ একটি লাইন হিসেবে আসবে এবং মাঝে মাঝে যে ফাঁকা লাইনগুলো দেখা যায় সেগুলো হলো কিপ-অ্যালাইভ (keepalives)। ব্রাউজারে https://ntfy.example.com ওপেন করে একই অ্যাকাউন্ট দিয়ে সাইন-ইন করলে আপনি একই স্ট্রিমের ওয়েব অ্যাপ সংস্করণটি ব্যবহার করতে পারবেন।

একটি স্ক্রিপ্ট যেন সার্ভারে অতিরিক্ত চাপ সৃষ্টি করতে না পারে সেজন্য রেট লিমিট সেট করুন

ডিফল্টভাবে প্রতিটি ভিজিটর 60টি রিকোয়েস্টের একটি বাকেট পান, যা প্রতি 5 সেকেন্ডে একটি রিকোয়েস্ট হারে রিফিল হয়। ব্যক্তিগত সার্ভারের জন্য এটি বেশ উদার, কিন্তু কোনো স্ক্রিপ্ট যদি রিট্রাই লুপে আটকে যায় তবে সে পুরো বাকেটটি ব্যবহার করে ফেলবে। server.yml-এ লিমিট যোগ করুন।

visitor-request-limit-burst: 30
visitor-request-limit-replenish: "10s"
visitor-message-daily-limit: 500
sudo docker compose restart ntfy

লিমিট অতিক্রমকারী কোনো ভিজিটর মেসেজ পাওয়ার পরিবর্তে HTTP 429 রেসপন্স পাবেন। এই লিমিট প্রতিটি ভিজিটরের আইপি অ্যাড্রেস অনুযায়ী গণনা করা হয়, আর ঠিক এই কারণেই behind-proxy: true গুরুত্বপূর্ণ: এটি ছাড়া ntfy শুধুমাত্র Caddy-এর অ্যাড্রেস দেখতে পায়। ফলে প্রতিটি ক্লায়েন্টকে একই ভিজিটর হিসেবে গণ্য করা হয় এবং একটি মাত্র ত্রুটিপূর্ণ স্ক্রিপ্ট সেই বাকেটটি শেষ করে ফেলে যা আপনার ফোন এবং অন্যান্য সার্ভার শেয়ার করে।

ব্যর্থ হওয়া cron job থেকে সতর্কতা

কমান্ড লাইনে টোকেন রাখা থেকে বিরত থাকুন। 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 প্রদান করে, তাই হাতে চালানো কোনো স্ক্রিপ্ট curl লাইন পর্যন্ত পৌঁছানোর আগেই বন্ধ হয়ে যেতে পারে। Cron job কেন চলে না তার নির্দেশিকা এই এনভায়রনমেন্ট সংক্রান্ত সমস্যাগুলো নিয়ে আলোচনা করে। সব জায়গায় অ্যাবসোলিউট পাথ ব্যবহার করুন এবং প্রথমবার শিডিউল অনুযায়ী চলার পর অনুমান না করে সরাসরি লগ ফাইলটি পড়ুন।

systemd unit ব্যর্থ হলে সতর্কতা

Cron নির্ধারিত কাজগুলো পরিচালনা করে। দীর্ঘস্থায়ী সার্ভিসগুলোর জন্য OnFailure= প্রয়োজন, যা systemd-এর মাধ্যমে যেকোনো unit failed অবস্থায় পৌঁছালে কার্যকর হয়। একটি টেমপ্লেট unit তৈরি করুন এবং সার্ভারের প্রতিটি সার্ভিসের জন্য তা পুনরায় ব্যবহার করুন। এটি /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 পূর্ণ unit নামের সাথে প্রসারিত হয়, তাই instance-টি ntfy-unit-failed@myapp.service হয়ে যায় এবং টেমপ্লেটের ভেতরের %i, myapp.service-কে প্রথম আর্গুমেন্ট হিসেবে স্ক্রিপ্টে পাঠিয়ে দেয়। এটিই একটি টেমপ্লেটকে প্রতিটি unit-এর জন্য কাজ করার সুযোগ দেয়। এটি কাজ করছে কি না তা নিশ্চিত করতে ইচ্ছাকৃতভাবে ব্যর্থ হয় এমন একটি unit তৈরি করুন, যা /etc/systemd/system/ntfy-selftest.service হিসেবে সংরক্ষিত থাকবে।

[Unit]
Description=Deliberately failing unit
OnFailure=ntfy-unit-failed@%n.service

[Service]
Type=oneshot
ExecStart=/bin/false
sudo systemctl daemon-reload
sudo systemctl start ntfy-selftest.service

স্টার্ট কমান্ডটি নন-জিরো (non-zero) এক্সিট কোড দেয় এবং Job for ntfy-selftest.service failed because the control process exited with error code প্রিন্ট করে, এবং এক সেকেন্ডের মধ্যেই আপনার ফোনে নোটিফিকেশন আসার কথা। পরীক্ষা শেষে টেস্ট unit-টি মুছে ফেলুন।

একটি বিষয়ে সতর্ক থাকা প্রয়োজন। OnFailure= শুধুমাত্র তখনই কার্যকর হয় যখন একটি unit failed অবস্থায় পৌঁছায়। কিন্তু Restart=always যুক্ত কোনো সার্ভিস হয়তো কখনোই সেই অবস্থায় পৌঁছাবে না, কারণ systemd বারবার সেটিকে রিস্টার্ট করতে থাকে। unit-টি তখনই ব্যর্থ হিসেবে গণ্য হয় যখন এটি StartLimitIntervalSec সময়ের মধ্যে StartLimitBurst বারের বেশি রিস্টার্ট হয়। যে সার্ভিসগুলোর ব্যাপারে আপনি জানতে চান, সেগুলোতে এই দুটি মান সেট করুন, অন্যথায় একটি ক্র্যাশ লুপ দিনের পর দিন নীরবে চলতে থাকবে। টাইমারগুলো উপরের cron প্যাটার্নের একটি পরিচ্ছন্ন বিকল্প, কারণ একটি টাইমারের সার্ভিস unit স্বয়ংক্রিয়ভাবেই OnFailure= সুবিধা পায়, এবং VPS-এ systemd সার্ভিস এবং টাইমার সংক্রান্ত নির্দেশিকা-তে একটি সার্ভিসকে টাইমার-এ রূপান্তর করার প্রক্রিয়া বিস্তারিত আলোচনা করা হয়েছে।

একই টপিকে একটি আপটাইম মনিটর যুক্ত করা

Uptime Kuma, the self-hosted status monitor-এ ntfy নোটিফিকেশন টাইপ রয়েছে। Settings-এ যান, তারপর Notifications-এ ক্লিক করুন, এরপর Setup Notification নির্বাচন করুন। Ntfy বেছে নিন, সার্ভার URL হিসেবে https://ntfy.example.com এবং টপিক হিসেবে alerts সেট করুন। একটি প্রায়োরিটি নির্বাচন করুন এবং robot অ্যাক্সেস টোকেনটি পেস্ট করুন। সেভ করার আগে একটি টেস্ট নোটিফিকেশন পাঠান, কারণ ভুল টপিক নাম দিলে write গ্র্যান্টের অধীনে কোনো এরর মেসেজ ছাড়াই এটি ব্যর্থ হতে পারে।

এই ব্যবস্থার সীমাবদ্ধতা হলো: একই VPS-এ চলমান কোনো মনিটর আপনাকে জানাতে পারবে না যে VPS-টি ডাউন হয়েছে, এবং ntfy নিজে ডাউন হলে তা জানানোর ক্ষমতা ntfy-এর নেই। মনিটরটিকে অন্য কোনো মেশিনে চালান এবং সেটির জন্য ইমেইলের মতো দ্বিতীয় একটি নোটিফিকেশন চ্যানেল রাখুন, যা ntfy-এর ওপর নজর রাখবে। Uptime Kuma-এর Push মনিটর টাইপ অন্য একটি সীমাবদ্ধতা দূর করে: আপনার cron job সফলভাবে সম্পন্ন হওয়ার পর একটি push URL কল করবে, আর সেই কল আসা বন্ধ হলে Kuma সতর্কবার্তা পাঠাবে। ব্যর্থতার বিষয়টি কেবল তখনই ধরা পড়ে যখন জবটি রান করে, তাই যে জবটি কখনোই শুরু হয়নি সে সম্পর্কে এটি কোনো তথ্য দিতে পারে না।

সেলফ-হোস্টেড ntfy কি Android এবং iPhone-এ কাজ করে?

Android-এর ক্ষেত্রে, হ্যাঁ, কোনো শর্ত ছাড়াই। Google Play বা F-Droid থেকে অ্যাপটি ইনস্টল করুন, Settings খুলুন, ডিফল্ট সার্ভার হিসেবে https://ntfy.example.com সেট করুন, ইউজার ম্যানেজমেন্ট স্ক্রিনে আপনার অ্যাকাউন্ট যোগ করুন এবং তারপর alerts-এ সাবস্ক্রাইব করুন। ইনস্ট্যান্ট ডেলিভারির জন্য একটি foreground service চালু থাকে যাতে ফোন doze মোডে থাকলেও মেসেজ পৌঁছায়। এর সাথে থাকা স্থায়ী নোটিফিকেশনটি Android-এর foreground service-এর একটি প্রয়োজনীয়তা, কোনো বাগ নয়। 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-এ নোটিফিকেশন দেরিতে আসে বা একেবারেই আসে না, কারণ অ্যাপটিকে সচল করার মতো কোনো মাধ্যম থাকে না। রিলে সরানোর একমাত্র উপায় হলো আপনার নিজস্ব Apple ডেভেলপার অ্যাকাউন্ট এবং নিজস্ব APNs কি (keys) ব্যবহার করে iOS অ্যাপটি নিজে বিল্ড এবং শিপ করা, যার অর্থ হলো বার্ষিক ফি প্রদান এবং প্রতিটি আপডেটের জন্য অ্যাপটি পুনরায় বিল্ড করা। যদি এই রিলে আপনার ব্যবহারের জন্য গ্রহণযোগ্য না হয়, তবে 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 ডাটাবেসগুলো স্টার্ট হওয়ার সময় মাইগ্রেট হয়, তাই স্কিমা পরিবর্তনের পর পুরনো ট্যাগে ফিরে যাওয়া নিরাপদ নয়। নতুন ভার্সনটি একদিন সফলভাবে চলার আগ পর্যন্ত আপনার নেওয়া ব্যাকআপটি রেখে দিন। সার্ভারের প্রতিটি সার্ভিসেরই এমন কিছু নির্দিষ্ট পাথ থাকে যা পুনরায় তৈরি করা যায় না, এবং PhotoPrism এবং Immich-এর তুলনা অংশে এই তালিকার বিস্তারিত এবং একই ধরনের VPS-এ ফটো লাইব্রেরির জন্য প্রয়োজনীয় ব্যাকআপ কমান্ডগুলো আলোচনা করা হয়েছে।

Gotify এবং Apprise

Gotify হলো তুলনামূলক ছোট একটি বিকল্প: এটি একটি ওয়েব UI এবং Android অ্যাপসহ একটি মাত্র binary, এতে কোনো topic wildcard নেই এবং কোনো অফিসিয়াল iOS client নেই। এটি এমন একটি ব্যক্তিগত সার্ভারের জন্য উপযুক্ত যেখানে শুধুমাত্র Android ডিভাইস ব্যবহার করা হয়। Apprise কোনো সার্ভার নয়, বরং এটি একটি Python library এবং command line tool। এটি একটি মাত্র মেসেজকে ntfy সহ শতাধিক সার্ভিসে পৌঁছে দিতে পারে, যা এমন সব script-এর জন্য উপযোগী যেগুলোকে একই সাথে একাধিক জায়গায় নোটিফিকেশন পাঠাতে হয়। ntfy এমন একটি সমাধান যা আপনাকে একটি সার্ভার, একটি HTTP API এবং উভয় মোবাইল প্ল্যাটফর্মের জন্য অ্যাপ প্রদান করে। এই কারণেই ভাড়া করা সার্ভার থেকে অ্যালার্ট পাঠানোর ক্ষেত্রে এটিই সাধারণত সবচেয়ে জনপ্রিয় সমাধান।

FAQ

আমার ntfy সার্ভারে পাবলিশ করার সময় কেন 403 এরর আসে?

auth-default-access: "deny-all"-কে server.yml-এ সেট করা থাকলে, বেনামী (anonymous) পাবলিশ প্রত্যাখ্যান করা হয় এবং এটিই প্রত্যাশিত আচরণ। -u user:pass বা -H "Authorization: Bearer tk_..." ব্যবহার করে ক্রেডেনশিয়াল পাঠান। আপনি যদি ইতিমধ্যে একটি টোকেন পাঠিয়ে থাকেন এবং তবুও 403 পান, তবে সেই টোকেনের ব্যবহারকারীর কাছে ঐ টপিকের জন্য কোনো ম্যাচিং ACL এন্ট্রি নেই। সম্পূর্ণ তালিকা দেখতে ntfy access চালান। মনে রাখবেন যে, একটি write গ্র্যান্ট সাবস্ক্রাইব করার অনুমতি দেয় না, তাই একটি অ্যাকাউন্ট যা সফলভাবে পাবলিশ করতে পারে, সেটি একই টপিক পড়ার চেষ্টা করলে প্রত্যাখ্যান করা হবে।

সেলফ-হোস্টেড ntfy সার্ভারের সাথে কি আইফোনে নোটিফিকেশন কাজ করে?

এগুলো কাজ করে, তবে একটি রিলে-এর মাধ্যমে যা এড়িয়ে যাওয়ার উপায় নেই। অ্যাপল শুধুমাত্র APNs (Apple push notification service)-এর মাধ্যমে অ্যাপগুলোকে সচল করে এবং শুধুমাত্র অ্যাপের প্রকাশকই সেখানে পাঠাতে পারে, তাই ntfy মেসেজ আইডি সম্বলিত একটি poll_request ntfy.sh-এ ফরোয়ার্ড করে, যা পরবর্তীতে ডিভাইসে রিলে করে। server.yml-এ upstream-base-url: "https://ntfy.sh" সেট করুন এবং কন্টেইনারটি রিস্টার্ট করুন। মেসেজের মূল অংশটি আপনার সার্ভার থেকেই ফেচ করা হয়। এই সেটিংস ছাড়া, iOS নোটিফিকেশন দেরি করে আসে অথবা কখনোই আসে না।

আমার ক্রন জব-এর ntfy অ্যালার্ট কেন কখনোই পৌঁছায় না?

টোকেন এবং টপিক সঠিক কি না তা নিশ্চিত করতে প্রথমে curl লাইনটি আলাদাভাবে চালিয়ে দেখুন। যদি এটি ম্যানুয়ালি কাজ করে কিন্তু ক্রন থেকে না করে, তবে ব্যর্থতা অ্যালার্টের আগের কোনো ধাপে ঘটছে: ক্রন খুব সীমিত এনভায়রনমেন্ট এবং সংক্ষিপ্ত PATH নিয়ে জব চালায়, তাই যে স্ক্রিপ্ট সরাসরি কমান্ডের নাম ধরে কল করে সেটি curl লাইনে পৌঁছানোর আগেই বন্ধ হয়ে যেতে পারে। অ্যাবসোলিউট পাথ ব্যবহার করুন, জবের আউটপুট একটি লগ ফাইলে রিডাইরেক্ট করুন এবং পরবর্তী রান শেষে সেই ফাইলটি পড়ুন। ডেলিভারির পরিবর্তে 429 রেসপন্স পাওয়ার অর্থ হলো রেট লিমিট কাজ করছে এবং আপনার স্ক্রিপ্ট খুব দ্রুত পুনরায় চেষ্টা (retry) করছে।

আমার কি ntfy-কে পাবলিক ইন্টারনেটে এক্সপোজ করা উচিত?

ফোন অ্যাপগুলোর মোবাইল নেটওয়ার্ক থেকে সার্ভারে পৌঁছানোর প্রয়োজন হয়, তাই auth-default-access: "deny-all" এবং প্রতি-টপিক ACL সহ একটি পাবলিক HTTPS এন্ডপয়েন্ট হলো স্বাভাবিক সেটআপ। যতক্ষণ কোনো টপিক everyone দ্বারা পড়ার যোগ্য না থাকে, ততক্ষণ এটি নিরাপদ। যদি প্রতিটি সাবস্ক্রাইবার আপনার নিয়ন্ত্রিত মেশিন হয়, তবে শুধুমাত্র VPN-ভিত্তিক ইনস্ট্যান্স ব্যবহার করা যুক্তিসঙ্গত। ফোনের জন্য এটি খুব একটা কার্যকর নয়, কারণ টানেল চালু থাকলেই কেবল অ্যাপটি নোটিফিকেশন গ্রহণ করতে পারে, ফলে ফোন পুনরায় কানেক্ট না হওয়া পর্যন্ত অ্যালার্টগুলো কিউতে (queue) জমা থাকে।