SSD Nodes Learn 🎉 VPS $5.50/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor

নিজের VPS-এ ntfy চালিয়ে server alert পাঠান

Docker Compose দিয়ে TLS-এর পেছনে ntfy চালান, users ও ACL দিয়ে topic সুরক্ষিত করুন, তারপর cron এবং systemd OnFailure unit থেকে নিরাপদে alert পাঠান।

একটি self-hosted ntfy server কী করে

একটি self-hosted ntfy server HTTP POST-কে আপনার ফোনে push notification-এ রূপান্তর করে। আপনি curl ব্যবহার করে message publish করেন, এবং সেটি Android app, iOS app, browser tab বা HTTP connection চালু রাখতে পারে এমন অন্য যেকোনো client-এ পৌঁছে যায়। কোনো client library install করতে হয় না এবং কোনো message broker চালানোরও প্রয়োজন নেই।

ntfy topic ব্যবহার করে message নির্ধারণ করে। একটি topic হলো URL path-এর মধ্যে থাকা একটি নাম, যেমন https://ntfy.example.com/alerts, এবং কেউ সেখানে publish করলেই topic-টি তৈরি হয়। Default install-এ যে কেউ সেই নাম জানলে topic পড়তে এবং সেখানে লিখতে পারে। এই কারণেই প্রকল্পটির নিজস্ব documentation topic name-কে password-এর সঙ্গে তুলনা করে। Public ntfy.sh service-এর জন্য এই মডেলটি গ্রহণযোগ্য। কিন্তু আপনার backup failure বহনকারী server-এর জন্য এটি নিরাপদ নয়। তাই এই guide-এ প্রথম message পাঠানোর আগেই authentication চালু করা হবে।

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

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

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

dig-এর output-এ আপনার সার্ভারের IP দেখা যেতে হবে। এটি কিছু print না করলে certificate issuance ব্যর্থ হবে, কারণ certificate authority বাইরে থেকে name যাচাই করে। Port 80 open রাখুন, কারণ Let's Encrypt-এর ব্যবহৃত ACME (automatic certificate management environment) protocol HTTP challenge-এর জন্য এটি ব্যবহার করে। ntfy container নিজে কোনো public port পাবে না।

ntfy-এর config file লিখুন

Docker image-এ কোনো config file নেই, তাই একটি তৈরি করতে হবে। এই guide-এর পরের প্রতিটি command এই file থেকে configuration পড়বে। প্রথমে container যে 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

এই লাইনগুলোর মধ্যে 4টি বিশেষভাবে গুরুত্বপূর্ণ। base-url-এ অবশ্যই সঠিক public HTTPS address দিতে হবে, কারণ ntfy attachment link এবং web app-এর নিজস্ব request এই address থেকে তৈরি করে। ভুল value দিলে web app load হবে, কিন্তু পরে প্রতিটি action ব্যর্থ হবে। listen-http: ":2586" container-এর ভেতরে সব interface-এ bind করে। এটি অসতর্ক configuration মনে হলেও সঠিক। Container-এর নিজস্ব network namespace থাকে। তাই সেখানে 127.0.0.1-এ bind করলে host থেকে port-এ পৌঁছানো যেত না এবং Docker-এর published port কখনও সংযোগ করতে পারত না। auth-default-access: "deny-all"-ই পুরো security policy নির্ধারণ করে, কারণ explicit grant ছাড়া এটি কাউকে read বা write করতে দেয় না। behind-proxy: true ntfy-কে X-Forwarded-For header থেকে client address নিতে বলে। ফলে rate limit reverse proxy-কে একটি অত্যন্ত ব্যস্ত client হিসেবে গণনা না করে প্রকৃত visitor-দের গণনা করে।

enable-login: true web app এবং phone app-এ password দিয়ে sign in করার সুবিধা দেয়। enable-signup false থাকবে, কারণ private server-এ ব্যবহারকারীকে নিজে account তৈরি করতে দেওয়া অতিরিক্ত ধাপসহ একটি উন্মুক্ত প্রবেশপথ তৈরি করে।

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 -uid -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-এ স্থির করা হয়েছে, যা 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-টি বন্ধ দেখালেও port-টি Internet থেকে সাড়া দেয়।

curl Connection refused মুদ্রণ করলে container log পড়ুন। /var/lib/ntfy/user.db-এ permission error দেখা গেলে বুঝবেন, user: line-টি ওই directory-গুলোর owner-এর সঙ্গে মেলে না। তাই process নিজের database তৈরি করতে পারে না এবং বন্ধ হয়ে যায়। VPS-এর জন্য Docker Compose-এর প্রাথমিক নির্দেশিকা-তে volume ownership এবং restart policy সম্পর্কে আরও বিস্তারিত আছে।

Caddy দিয়ে সামনে TLS যুক্ত করুন

Caddy নিজেই certificate-এর জন্য অনুরোধ করে এবং তা নবায়ন করে। কার্যকর 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

একই {"healthy":true} HTTPS-এর মাধ্যমে চালালে পুরো পথটি কাজ করছে কি না বোঝা যায়। Caddy থেকে পাওয়া 502-এর অর্থ ntfy listening করছে না। `sudo ss -lntp | grep 2586 দিয়ে পরীক্ষা করুন। Certificate error সাধারণত DNS record ভুল হওয়া বা port 80 blocked থাকার কারণে হয়। sudo journalctl -u caddy -n 50` কোনটি ঘটেছে তা জানায়।

আপনি যদি আগে থেকেই nginx চালান, ntfy-এর documentation-এ দেওয়া proxy settings অনুলিপি করুন: `proxy_http_version 1.1, proxy_buffering off, proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for`। Read timeout এবং send timeout কমপক্ষে তিন মিনিট রাখুন। কোনো subscriber listening করার পুরো সময় একটি HTTP connection খোলা রাখে। nginx ডিফল্টভাবে 60 seconds পরে idle upstream connection বন্ধ করে। ফলে subscriber বারবার reconnect করে এবং এই বিরতির সময় পাঠানো message হারিয়ে যায়।

ব্যবহারকারী তৈরি করুন এবং topic-গুলোর প্রবেশাধিকার সীমিত করুন

Authentication চালু আছে, কিন্তু এখনো কেউ কোনো কিছুতে প্রবেশাধিকার পায়নি। এটিই প্রত্যাশিত অবস্থা। নিজের জন্য একটি admin account এবং script-এর জন্য একটি machine account তৈরি করুন। এই command-গুলো container-এর ভেতর থেকে /etc/ntfy/server.yml পড়ে। তাই config file-টি 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

প্রতিটি command password চাইবে। admin access list উপেক্ষা করে প্রতিটি topic পড়তে ও লিখতে পারে। তাই এই account শুধু নিজের জন্য এবং phone app-এর জন্য রাখুন। robot হলো সাধারণ user; কোনো প্রবেশাধিকার দেওয়া না পর্যন্ত এর কোনো access নেই।

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)-এর প্রতিটি entry-তে একটি user, একটি topic এবং একটি permission থাকে। topic হয় নির্দিষ্ট নাম, নয়তো এমন একটি pattern যেখানে * যেকোনো কিছুর সঙ্গে মেলে। তাই alerts_* দিয়ে alerts_backup এবং alerts_db কভার করা যায়; প্রতিটি host-এর জন্য আলাদা command চালাতে হয় না। write permission-এর অর্থ শুধু publish করা। তাই কোনো cron job থেকে token চুরি হলেও সেটি subscribe করে নিজের পাঠানো তথ্য পড়তে পারবে না। বিশেষ username everyone অননুমোদিত visitor কী করতে পারবে তা নির্ধারণ করে। এটি কেবল ইচ্ছাকৃতভাবে কোনো কিছু public করার সময় ব্যবহার করুন, যেমন ntfy access everyone status read

Script-এ আপনার password নয়, একটি token রাখুন।

sudo docker compose exec ntfy ntfy token add robot

এই command tk_ দিয়ে শুরু হওয়া একটি token দেখায়। কোনো token যে user-এর অন্তর্গত, সেটির access ঠিক সেই user-এর access অনুযায়ী হয়। তাই এই token দিয়ে alerts topic-এ publish করা যাবে, অন্য কিছু করা যাবে না। ntfy token list দিয়ে বর্তমানে কী কী আছে তা দেখা যায়, আর ntfy token remove ব্যবহার করে user-এর password পরিবর্তন না করেই একটি token বাতিল করা যায়।

প্রথম বার্তা পাঠিয়ে lock কাজ করছে প্রমাণ করুন

প্রথমে পরীক্ষা করুন যে দরজাটি বন্ধ আছে।

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 হলো bold করা প্রথম লাইন। Priority-এর মান 1 থেকে 5 পর্যন্ত হতে পারে, অথবা নাম ব্যবহার করলে min থেকে urgent পর্যন্ত হতে পারে। এর মাধ্যমে নির্ধারিত হয় ফোনে শব্দ হবে কি না। নামটি পরিচিত emoji short code-এর সঙ্গে মিললে Tags notification-এ emoji হয়ে যায়; মিল না হলে plain text হিসেবেই থাকে।

কোনো terminal থেকে একটি topic পর্যবেক্ষণ করতে সেটি stream করুন:

curl -s -u admin https://ntfy.example.com/alerts/raw

curl password চায়। প্রতিটি বার্তা একটি লাইনে আসে, আর মাঝে মাঝে দেখা দেওয়া ফাঁকা লাইনগুলো keepalive। Browser-এ https://ntfy.example.com খুলে একই account দিয়ে sign in করলে একই stream-এর web app version পাওয়া যাবে।

Rate limit নির্ধারণ করুন, যাতে একটি script সার্ভারে অতিরিক্ত request পাঠাতে না পারে

ডিফল্টভাবে প্রতিটি visitor 60টি request-এর একটি bucket পায়। প্রতি 5 সেকেন্ডে 1টি request হারে bucket-টি পুনরায় পূরণ হয়। Private server-এর জন্য এটি যথেষ্ট উদার সীমা। Retry loop-এ আটকে থাকা কোনো script সহজেই পুরো bucket ব্যবহার করে ফেলতে পারে। server.yml-এ limit যোগ করুন।

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

সীমা অতিক্রমকারী visitor delivered message-এর পরিবর্তে HTTP 429 পায়। Limit-টি প্রতিটি visitor address অনুযায়ী গণনা করা হয়। তাই behind-proxy: true এত গুরুত্বপূর্ণ: এটি না থাকলে ntfy শুধু Caddy-এর address দেখতে পায়। তখন প্রতিটি client-কে একই visitor হিসেবে গণনা করা হয়। ফলে একটি অতিরিক্ত request পাঠানো script আপনার phone এবং অন্যান্য server-এর সঙ্গে ভাগ করা bucket দ্রুত শেষ করে ফেলে।

cron job ব্যর্থ হলে alert

টোকেনটি command line-এ রাখবেন না। ps aux মেশিনের সব user-কে চলমান প্রতিটি process-এর সম্পূর্ণ command line দেখায়। তাই -H দিয়ে পাঠানো টোকেনটি curl চলার পুরো সময় যেকোনো local account পড়তে পারে। একটি curl config file এই সমস্যা এড়ায়।

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

এখন job-টি wrapper script-এর মধ্যে চালান। এটি /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

$? command-এর ঠিক পরের লাইনে capture করা হয়, কারণ এর পরে চালানো পরবর্তী command এটি overwrite করে দেবে। ntfy সর্বোচ্চ message size নির্ধারণ করে, এবং notification log viewer নয়; তাই output tail -c 1000-এর মাধ্যমে পাঠানো হয়। শেষের exit "$code" মূল status বজায় রাখে। ফলে এই job পর্যবেক্ষণ করা অন্য কোনো ব্যবস্থা এখনও failure দেখতে পায়। পুরো প্রক্রিয়াটি পরীক্ষা করতে একবারের জন্য script-টিকে /bin/false-এ নির্দেশ করুন।

যে failure branch কখনও execute হয় না, তা alerting না থাকার চেয়েও খারাপ। কারণ এতে মনে হয়, নীরবতার অর্থ success। Cron আপনার job-কে প্রায় খালি environment এবং login shell-এর তুলনায় অনেক ছোট PATH দেয়। তাই হাতে চালালে কাজ করা script curl line-এ পৌঁছানোর আগেই ব্যর্থ হতে পারে। cron job কেন চলে না সে বিষয়ে নির্দেশিকাটি এই environment-সংক্রান্ত সমস্যাগুলো ব্যাখ্যা করে। সব জায়গায় absolute path ব্যবহার করুন। প্রথম scheduled run-এর পরে log file পড়ুন। অনুমান করে নেবেন না যে সব ঠিক আছে।

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

Cron নির্ধারিত কাজের জন্য যথেষ্ট। দীর্ঘ সময় চলা service-এর জন্য OnFailure= প্রয়োজন, যা কোনো unit failed অবস্থায় প্রবেশ করলেই systemd চালায়। একটি template unit তৈরি করে সার্ভারের প্রতিটি service-এর জন্য পুনরায় ব্যবহার করুন। এটি /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-এর মাধ্যমে service-এর সঙ্গে যুক্ত করুন, যাতে package upgrade আপনার পরিবর্তন মুছে ফেলতে না পারে।

sudo systemctl edit myapp.service
[Unit]
OnFailure=ntfy-unit-failed@%n.service

%n সম্পূর্ণ unit name-এ প্রসারিত হয়। তাই instance-এর নাম হয় ntfy-unit-failed@myapp.service। Template-এর ভিতরের %i script-এ myapp.service-কে প্রথম argument হিসেবে পাঠায়। এভাবেই একটি template দিয়ে প্রতিটি 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

Start command non-zero status-এ শেষ হবে এবং Job for ntfy-selftest.service failed because the control process exited with error code দেখাবে। প্রায় এক সেকেন্ড পরে ফোনে alert আসা উচিত। এরপর test unit মুছে ফেলুন।

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

একই topic-এ uptime monitor সংযুক্ত করুন

Uptime Kuma, self-hosted status monitor, একটি ntfy notification type সরবরাহ করে। Settings খুলুন, তারপর Notifications, এরপর Setup Notification নির্বাচন করুন। Ntfy বেছে নিন, server URL হিসেবে https://ntfy.example.com এবং topic হিসেবে alerts সেট করুন, একটি priority নির্বাচন করুন, এবং robot access token পেস্ট করুন। সংরক্ষণ করার আগে test notification পাঠান, কারণ write grant-এ topic-টির অনুমতি না থাকলে ভুল topic name-এর কারণে কোনো error দেখা নাও যেতে পারে।

এই ব্যবস্থার সীমাবদ্ধতা স্পষ্ট: একই VPS-এ চলা monitor VPS-টি down কি না তা জানাতে পারে না, এবং ntfy নিজেই down থাকলে ntfy সেই খবর পাঠাতে পারে না। Monitor-টি অন্য একটি machine-এ চালান এবং এর জন্য email-এর মতো একটি দ্বিতীয় notification channel দিন, যাতে monitor নিজেই ntfy পর্যবেক্ষণ করতে পারে। Uptime Kuma-এর Push monitor type অন্য blind spot-টিও সামাল দেয়: সফলভাবে cron job চলার পরে আপনার cron job একটি push URL call করে, এবং সেই call আসা বন্ধ হলে Kuma alert পাঠায়। Failure branch কেবল job চলার সময় সক্রিয় হয়। তাই যে job কখনো শুরুই হয়নি, সে সম্পর্কে এটি কিছু জানায় না।

Android ও iPhone-এ self-hosted ntfy কি কাজ করে?

Android-এ কোনো শর্ত ছাড়াই কাজ করে। Google Play বা F-Droid থেকে অ্যাপটি ইনস্টল করুন, Settings খুলে default server হিসেবে https://ntfy.example.com সেট করুন, user management screen-এ আপনার account যোগ করুন, তারপর alerts-এ subscribe করুন। Instant delivery চালু থাকলে একটি foreground service চলতে থাকে, তাই ফোন doze mode-এ থাকলেও message পৌঁছে যায়। এর সঙ্গে থাকা স্থায়ী notification Android-এর foreground service-এর জন্য নির্ধারিত requirement, এটি কোনো bug নয়। F-Droid build-এ কোনো Firebase code নেই, তাই প্রতিটি subscription instant delivery ব্যবহার করে। ntfy UnifiedPush distributor হিসেবেও কাজ করতে পারে। এটি Google-এর push service-এর একটি open replacement। তাই UnifiedPush সমর্থনকারী অন্যান্য app-ও আপনার server-এর মাধ্যমে delivery করতে পারে।

iOS-এ এটি কাজ করে, তবে একটি dependency সরানো যায় না। Apple backgrounded app-কে শুধু APNs (Apple push notification service)-এর মাধ্যমে জাগায়। শুধু app-এর signing credentials থাকা পক্ষই সেই app-এ message পাঠাতে পারে। তাই আপনার server সরাসরি app-এ পৌঁছাতে পারে না। ntfy এটি relay-এর মাধ্যমে সমাধান করে: আপনার server message ID ধারণকারী একটি poll_request ntfy.sh-এ পাঠায়। ntfy.sh সেটিকে Firebase এবং APNs-এর মাধ্যমে পাঠিয়ে app-কে জাগায়। এরপর app আপনার server থেকে message body fetch করে।

upstream-base-url: "https://ntfy.sh"

এই ব্যবস্থার খরচ সম্পর্কে পরিষ্কার থাকুন। Message content আপনার box-এই থাকে। তবে কোনো message এসেছে কি না এবং তার ID—এই তথ্য আপনার নিয়ন্ত্রণের বাইরে থাকা infrastructure-এর মধ্য দিয়ে যায়। এই setting ছাড়া self-hosted server থেকে iPhone-এ notification দেরিতে পৌঁছায় বা একেবারেই পৌঁছায় না, কারণ app-কে জাগানোর মতো কিছু থাকে না। Relay সরানোর একমাত্র উপায় হলো নিজের Apple developer account এবং নিজের APNs keys ব্যবহার করে iOS app নিজে build ও ship করা। এর জন্য annual fee দিতে হয় এবং প্রতিটি update-এর জন্য নতুন করে build করতে হয়। Relay আপনার ব্যবহারের জন্য গ্রহণযোগ্য না হলে alerting Android বা desktop web app-এ সীমাবদ্ধ রাখুন।

Backup, upgrade এবং image pinning

দুটি path পুনরায় তৈরি করা যায় না: /etc/ntfy/server.yml এবং /var/lib/ntfy/user.db। দ্বিতীয়টিতে প্রতিটি user, password hash, ACL entry এবং token থাকে। তাই এটিকে private key-এর মতো সুরক্ষিতভাবে পরিচালনা করুন।

sudo tar czf ntfy-backup.tgz -C / etc/ntfy var/lib/ntfy
sudo chmod 600 ntfy-backup.tgz

ওই file-টির একটি কপি server-এর বাইরে সংরক্ষণ করুন। cache.db-এ শুধু সাম্প্রতিক message থাকে—cache-duration-এ নির্ধারিত 12 ঘণ্টার message। তাই এটি হারালে সুরক্ষার যোগ্য কোনো গুরুত্বপূর্ণ data হারায় না। Upgrade করতে compose file-এ tag পরিবর্তন করে image pull করুন।

sudo docker compose pull
sudo docker compose up -d
curl -s https://ntfy.example.com/v1/health

প্রথমে release notes পড়ুন। Start হওয়ার সময় SQLite database migrate হয়। তাই schema পরিবর্তনের পরে পুরোনো tag-এ rollback করা নিরাপদ নয়। নতুন version 1 দিন চলা পর্যন্ত সদ্য নেওয়া backup সংরক্ষণ করুন।

Gotify এবং Apprise

Gotify ছোট বিকল্প: একটি binary, একটি web UI এবং একটি Android app রয়েছে। এতে topic wildcard নেই এবং official iOS client-ও নেই। তাই Android-ই একমাত্র target হলে private box-এর জন্য এটি উপযোগী। Apprise server নয়; এটি একটি Python library এবং command line tool। এটি একটি message একসঙ্গে একশরও বেশি service-এ পাঠাতে পারে, যার মধ্যে ntfy-ও রয়েছে। তাই একই সময়ে একাধিক জায়গায় পৌঁছাতে হবে এমন script-এর জন্য এটি উপযুক্ত। ntfy এমন একটি বিকল্প, যা server, HTTP API এবং উভয় mobile platform-এর জন্য app দেয়। এই কারণে rented server থেকে alert পাঠানোর ক্ষেত্রে এটিই সাধারণত ব্যবহৃত হয়।

FAQ

ntfy server-এ publish করলে 403 কেন ফেরত আসে?

server.yml-এ auth-default-access: "deny-all" চালু থাকলে anonymous publish প্রত্যাখ্যাত হয়, এবং এটিই প্রত্যাশিত আচরণ। -u user:pass অথবা -H "Authorization: Bearer tk_..." ব্যবহার করে credentials পাঠান। আপনি token পাঠানোর পরও যদি 403 পান, তাহলে ওই token-এর পেছনের user-এর topic-এর জন্য কোনো মিলে যাওয়া ACL entry নেই। সম্পূর্ণ তালিকা দেখাতে ntfy access চালান। মনে রাখবেন, write grant থাকলে subscribe করার অনুমতি পাওয়া যায় না। তাই কোনো account publish করতে পারলেও একই topic পড়ার চেষ্টা করলে প্রত্যাখ্যাত হবে।

self-hosted ntfy server-এর সঙ্গে iPhone-এ notification কাজ করে কি?

কাজ করে, তবে এমন একটি relay-এর মাধ্যমে যেটি এড়ানো যায় না। Apple কেবল APNs (Apple push notification service)-এর মাধ্যমে app জাগায়, এবং কেবল app-এর publisher-ই সেখানে পাঠাতে পারে। তাই ntfy একটি poll_request, যাতে message ID থাকে, ntfy.sh-এ পাঠায়; ntfy.sh সেটি device-এ relay করে। server.yml-এ upstream-base-url: "https://ntfy.sh" সেট করে container restart করুন। Message body এখনও আপনার server থেকেই fetch করা হয়। এই setting না থাকলে iOS notification দেরিতে আসে অথবা একেবারেই দেখা যায় না।

আমার cron job-এর ntfy alert কেন কখনও পৌঁছায়নি?

প্রথমে curl line-টি আলাদাভাবে চালিয়ে token এবং topic সঠিক কি না নিশ্চিত করুন। হাতে চালালে কাজ করে কিন্তু cron থেকে না করলে, সমস্যা alert পাঠানোর আগের ধাপে। cron job-গুলো minimal environment এবং সংক্ষিপ্ত PATH নিয়ে চলে। তাই bare name দিয়ে command চালানো কোনো script curl line-এ পৌঁছানোর আগেই ব্যর্থ হতে পারে। Absolute path ব্যবহার করুন, job-এর output একটি log file-এ redirect করুন, এবং পরবর্তী run-এর পরে সেই file পড়ুন। Delivery-এর পরিবর্তে 429 response পাওয়া মানে rate limit কাজ করছে এবং আপনার script খুব দ্রুত retry করছে।

ntfy কি public internet-এ expose করা উচিত?

Phone app-গুলোকে mobile network থেকে server-এ পৌঁছাতে হয়। তাই auth-default-access: "deny-all" এবং per-topic ACL-সহ একটি public HTTPS endpoint সাধারণ setup। কোনো topic everyone দ্বারা readable না হলে এটি নিরাপদ। প্রত্যেক subscriber যদি আপনার নিয়ন্ত্রণাধীন machine হয়, তাহলে VPN-only instance যুক্তিযুক্ত। Phone-এর ক্ষেত্রে এটি উপযুক্ত নয়, কারণ tunnel চালু থাকা অবস্থাতেই app notification পায়। তাই phone পুনরায় connect না করা পর্যন্ত alert queue হয়ে থাকে।