17 β Production va deploy¶
β¬ οΈ Oldingi: 16 β Testlash (FakeNutgram) Β· π README Β· Keyingi: 18 β Yakuniy kapston: to'liq bot β‘οΈ
Bu bobda: botni mashinangizdan haqiqiy serverga chiqaramiz va uni shu yerda ishonchli, doimiy ishlatishni o'rganamiz. Mavzular: ikki arxitektura va ularning prod xulqi (polling vs webhook); polling botini doimiy ushlash uchun supervisor va systemd (avtomatik qayta ishga tushirish, log); webhook uchun nginx + php-fpm (TLS, marshrut,
secret_token);.envva sirlarni koddan ajratish (token hech qachon kodga yozilmaydi); Docker (Dockerfilephp-cli va php-fpm uchun,docker-compose.ymlbilan bot + Redis + DB); xato boshqaruvi (onException/onApiError, Monolog bilan strukturali logging); graceful (texnik ish rejimi / kill-switch, signal bilan to'xtash); monitoring (healthcheck, uptime, Sentry kabi xato kuzatuvi); va prod'da holatni keshda saqlash zarurati. CI/CD'ni faqat eslatib o'tamiz β to'liqrog'i ../git-github/ da.Halol eslatma: bu bobning serverga oid qismi (supervisor/systemd/nginx/php-fpm/Docker) bu muhitda jonli ishga tushirilmaydi β konfiguratsiya fayllari va buyruqlar illustrativ (lekin to'g'ri yozilgan, real serverga ko'chirib ishlatsa bo'ladi). Botning PHP mantig'i esa β
onExceptionxatoni tutishi (umumiy va turga bog'liq), texnik-ish kill-switch'i handlerni bloklashi,.envparseri βNutgram::fake()(FakeNutgram) bilan offline, tokensiz HAQIQATAN tekshirilgan (7/7 PASS). "Bot serverda ishladi / xabar yetkazildi" kabi soxta tasdiq YOZMAYMIZ β jonli qismni siz o'z serveringizda ko'rasiz.
Avval: lokal run() nega prod uchun yetarli emas?¶
Oldingi boblarda biz botni shunchaki php bot.php bilan ishga tushirdik:
<?php
require __DIR__ . '/vendor/autoload.php';
use SergiX44\Nutgram\Nutgram;
$bot = new Nutgram($_ENV['TELEGRAM_TOKEN']);
$bot->onCommand('start', fn (Nutgram $bot) => $bot->sendMessage('Salom!'));
$bot->run(); // <-- long-polling sikli: getUpdates, ishlov, takror...
Bu lokalda ajoyib. Ammo prod'da uch muammo bor:
- Terminalni yopsangiz β bot o'ladi. SSH uzilsa yoki server qayta yuklansa, jarayon to'xtaydi va bot "jim" qoladi.
- Crash bo'lsa β hech kim ko'tarmaydi. Bot xato bilan to'xtasa, uni qayta ishga tushiruvchi yo'q.
- Hech kim kuzatmaydi. Bot tirikmi, xatolar bormi β bilmaysiz.
Prod'ga chiqish demak β shu uch muammoni hal qilish: doimiy ishlatish, avtomatik qayta tiklash va kuzatuv. Boshlaymiz arxitekturani tanlashdan.
Ikki arxitektura: polling vs webhook (prod nuqtai nazaridan)¶
Telegram botingizga update'larni ikki yo'l bilan yetkazadi (bu 01-bob va 13-bobda ko'rilgan; bu yerda prod jihatiga qaraymiz):
- Polling (long-polling): bot Telegram'dan so'raydi (
getUpdates). Bot doimiy ishlab turishi kerak. Public domen, TLS, ochiq port β kerak emas. Sozlash eng oson. - Webhook: Telegram sizga HTTP POST yuboradi. Public HTTPS domen va amaldagi TLS sertifikat shart. Har update β alohida qisqa so'rov; php-fpm bilan yaxshi miqyoslanadi.
Qaysi birini tanlash?
| Mezon | Polling | Webhook |
|---|---|---|
| Sozlash | Oson (domen/TLS shart emas) | Murakkabroq (nginx + TLS) |
| Talab | Doimiy jarayon (supervisor/systemd) | Public HTTPS + php-fpm |
| Yuqori yuk | Bir nechta vorker kerak | Tabiiy miqyoslanadi |
| Bir vaqtda nusxa | Faqat bitta (aks holda 409 Conflict) | Cheklov yo'q |
| Lokal/dev | Ideal | Noqulay (tunnel kerak) |
Amaliy maslahat: kichik va o'rta hajmdagi bot uchun polling + supervisor β eng sodda va ishonchli yo'l. Sekundiga yuzlab update keladigan katta bot uchun webhook + nginx + php-fpm miqyoslanadi. Ko'p loyihalar polling'dan boshlab, kerak bo'lganda webhook'ga o'tadi.
DIQQAT (409 Conflict): polling rejimida bot bir vaqtning o'zida faqat bitta nusxada ishlashi mumkin. Ikkita
run()jarayonini birga ishga tushirsangiz, Telegram409 Conflict: terminated by other getUpdates requestqaytaradi. Deploy paytida eski nusxani to'xtatib, keyin yangisini ishga tushiring. Polling va webhook ikkalasini birga yoqib qo'ymang β webhook yoqiq bo'lsagetUpdatesishlamaydi (avvaldeleteWebhookqiling).
Sirlar: .env va token (koddan TASHQARIDA)¶
Birinchi va eng muhim prod qoidasi: token va boshqa sirlar hech qachon kodga yoki git'ga tushmaydi. Token kim qo'liga tushsa, botingizni to'liq egallaydi.
Sirlarni .env faylida saqlaymiz va uni .gitignore ga qo'shamiz:
# .env (git'ga TUSHMAYDI)
TELEGRAM_TOKEN=123456:AAH-RealTokenFromBotFather
APP_ENV=production
DB_DSN=mysql:host=db;dbname=botdb
DB_USER=bot
DB_PASS=super-secret
REDIS_URL=redis://redis:6379
SENTRY_DSN=
Loyihaga namuna sifatida .env.example (sirlarsiz, faqat kalitlar) qo'shing β uni git'ga tushiring, yangi dasturchi nusxa olib to'ldiradi:
# .env.example (git'da bor, qiymatlar bo'sh)
TELEGRAM_TOKEN=
APP_ENV=local
DB_DSN=
DB_USER=
DB_PASS=
REDIS_URL=
SENTRY_DSN=
Kodda .env ni o'qish uchun amalda vlucas/phpdotenv ishlatiladi (loyiha tuzilishi 11-bobda):
<?php
require __DIR__ . '/vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load(); // .env -> $_ENV / getenv()
$token = $_ENV['TELEGRAM_TOKEN'] ?? getenv('TELEGRAM_TOKEN');
if (!$token) {
fwrite(STDERR, "TELEGRAM_TOKEN topilmadi! .env ni tekshiring.\n");
exit(1);
}
$bot = new SergiX44\Nutgram\Nutgram($token);
Tushunish uchun "qanday ishlashini" sodda parser bilan ko'rsatamiz (bu mantiqni offline tekshirdik β .env matnidan token o'qildi, # izoh tashlandi):
<?php
function parseEnv(string $text): array
{
$out = [];
foreach (explode("\n", $text) as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, '#')) {
continue; // bo'sh qator yoki izohni tashlaymiz
}
[$key, $val] = array_pad(explode('=', $line, 2), 2, '');
$out[trim($key)] = trim($val);
}
return $out;
}
Konteyner muhitida: Docker/Kubernetes ishlatsangiz, ko'pincha sirlarni
.envo'rniga to'g'ridan-to'g'ri environment variable sifatida beriladi (docker-composeenvironment:yoki secret-manager).getenv()ikkala holatda ham ishlaydi. Asosiy qoida bir xil: sir koddan tashqarida.
Variant A β Polling'ni prod'da doimiy ishlatish¶
Polling botini "doimiy" qilish demak β uni nazoratchi jarayon ostiga qo'yish, u qulasa qayta tiklasin. Ikki keng tarqalgan vosita: supervisor va systemd.
supervisor bilan¶
supervisor β uzoq ishlovchi jarayonlarni boshqaruvchi vosita. Konfiguratsiya (illustrativ β real Linux serverda /etc/supervisor/conf.d/mybot.conf):
[program:mybot]
command=/usr/bin/php /var/www/mybot/bot.php
directory=/var/www/mybot
autostart=true
autorestart=true
startretries=5
stopwaitsecs=10
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/mybot/bot.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
environment=APP_ENV="production"
Muhim sozlamalar: autorestart=true β bot qulasa avtomatik qayta ishga tushadi; startretries β necha marta urinish; stopwaitsecs β to'xtatishda jarayonga "tugatish" uchun beriladigan vaqt (graceful, pastda).
Boshqaruv buyruqlari (illustrativ):
sudo supervisorctl reread # konfigni qayta o'qish
sudo supervisorctl update # o'zgarishlarni qo'llash
sudo supervisorctl start mybot
sudo supervisorctl status mybot
sudo supervisorctl restart mybot # deploy'dan keyin
sudo supervisorctl tail -f mybot # loglarni kuzatish
systemd bilan (alternativa)¶
Ko'p zamonaviy serverlarda systemd mavjud β alohida supervisor o'rnatish shart emas. Servis fayli (illustrativ β /etc/systemd/system/mybot.service):
[Unit]
Description=My Telegram Bot (Nutgram polling)
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/mybot
ExecStart=/usr/bin/php /var/www/mybot/bot.php
Restart=always
RestartSec=3
StandardOutput=journal
StandardError=journal
EnvironmentFile=/var/www/mybot/.env
[Install]
WantedBy=multi-user.target
Boshqaruv (illustrativ):
sudo systemctl daemon-reload
sudo systemctl enable --now mybot # yoqish + ishga tushirish
sudo systemctl status mybot
sudo systemctl restart mybot # deploy'dan keyin
journalctl -u mybot -f # loglarni jonli kuzatish
Restart=always + RestartSec=3 β bot qulasa 3 soniyada qayta tiklanadi. EnvironmentFile orqali .env to'g'ridan-to'g'ri jarayon muhitiga yuklanadi.
Polling deploy oqimi:
git pullβcomposer install --no-dev --optimize-autoloaderβ migratsiyalar (DB) βsystemctl restart mybot(yokisupervisorctl restart). Restart paytida eski jarayon to'xtaydi, yangisi ko'tariladi β shuning uchun 409 Conflict bo'lmaydi.
Variant B β Webhook'ni nginx + php-fpm bilan¶
Webhook'da Telegram sizga so'rov yuboradi, demak siz public HTTPS endpoint taqdim etasiz. Nutgram'da running-mode'ni Webhook ga o'tkazamiz (bu metodlar tekshirildi β setRunningMode, RunningMode\Webhook mavjud).
Webhook kirish nuqtasi (public/webhook.php):
<?php
require __DIR__ . '/../vendor/autoload.php';
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\RunningMode\Webhook;
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
$bot = new Nutgram($_ENV['TELEGRAM_TOKEN']);
$bot->setRunningMode(Webhook::class);
// ... handlerlarni ro'yxatdan o'tkazing (alohida faylda) ...
$bot->onCommand('start', fn (Nutgram $bot) => $bot->sendMessage('Salom!'));
$bot->run(); // webhook rejimida: kelgan POST'ni bir marta ishlab, javob qaytaradi
Webhook'ni Telegram'da bir marta ro'yxatdan o'tkazasiz (alohida skript yoki Artisan-stil buyruq orqali). Bu jonli Telegram'ni talab qiladi β illustrativ:
<?php
// register-webhook.php (bir marta ishga tushiriladi)
$bot->setWebhook(
'https://bot.example.uz/webhook.php',
secret_token: $_ENV['WEBHOOK_SECRET'], // qo'shimcha himoya (pastda)
);
// Tekshirish: $bot->getWebhookInfo();
// Bekor qilish (polling'ga qaytish): $bot->deleteWebhook();
nginx konfiguratsiyasi (illustrativ β /etc/nginx/sites-available/mybot):
server {
listen 443 ssl http2;
server_name bot.example.uz;
ssl_certificate /etc/letsencrypt/live/bot.example.uz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bot.example.uz/privkey.pem;
root /var/www/mybot/public;
index webhook.php;
location /webhook.php {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# boshqa hamma narsani rad etamiz
location / { return 404; }
}
TLS sertifikat: bepul sertifikatni Let's Encrypt (
certbot) bilan olasiz. Telegram o'z-o'zidan imzolangan (self-signed) sertifikatni ham qabul qiladi, lekin amaldagi (CA imzolagan) sertifikat soddaroq.
Webhook xavfsizligi: secret_token¶
Webhook URL'ingizni kimdir bilib qolsa, soxta update yuborishi mumkin. setWebhook ning secret_token parametri buni bartaraf etadi: Telegram har so'rovda X-Telegram-Bot-Api-Secret-Token sarlavhasini yuboradi, siz uni tekshirasiz. Nutgram'ning Webhook running-mode'i buni qo'llab-quvvatlaydi β sirni .env da saqlang.
Webhook holat-tuzog'i: webhook'da har update yangi php-fpm jarayoni bo'lib ishlaydi β xotiradagi massiv (default storage) so'rovlar orasida yashamaydi. Shuning uchun webhook prod'da
setUserData/conversation holatini albatta Redis yoki DB ga saqlash kerak (cache sozlash 11-bobda). Polling'da bitta uzoq jarayon bo'lgani uchun bu unchalik kritik emas, lekin restart'da xotira nollanadi β baribir tashqi storage tavsiya etiladi.
Docker bilan paketlash¶
Docker bot va uning bog'liqliklarini (Redis, DB) bitta takrorlanadigan to'plamga joylaydi β "menda ishladi, serverda ishlamadi" muammosini yo'qotadi.
Dockerfile β polling (php-cli)¶
FROM php:8.4-cli-alpine
# kerakli kengaytmalar
RUN docker-php-ext-install pdo pdo_mysql pcntl \
&& apk add --no-cache git
# composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
# avval faqat bog'liqlik fayllari β kesh qatlamini saqlash uchun
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --optimize-autoloader --no-interaction
# so'ng kod
COPY . .
CMD ["php", "bot.php"]
pcntlkengaytmasini qo'shdik β graceful to'xtash uchun signallarni tutishga kerak bo'ladi (pastda). Polling botidarestart: unless-stoppedDocker'ning o'zi nazoratchi rolini bajaradi β supervisor shart emas.
Dockerfile β webhook (php-fpm)¶
Webhook variantida cli o'rniga fpm bazasi va nginx kerak:
FROM php:8.4-fpm-alpine
RUN docker-php-ext-install pdo pdo_mysql opcache
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-interaction
COPY . .
# php-fpm 9000-portda kutadi; nginx alohida konteynerda reverse-proxy qiladi
EXPOSE 9000
docker-compose.yml β bot + Redis + DB (polling misoli)¶
services:
bot:
build: .
restart: unless-stopped
env_file: .env
depends_on:
- redis
- db
# CMD: php bot.php (Dockerfile'dan)
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis-data:/data
db:
image: mysql:8
restart: unless-stopped
environment:
MYSQL_DATABASE: botdb
MYSQL_USER: bot
MYSQL_PASSWORD: ${DB_PASS}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
volumes:
- db-data:/var/lib/mysql
volumes:
redis-data:
db-data:
Buyruqlar (illustrativ):
docker compose up -d --build # qurish + fonda ishga tushirish
docker compose logs -f bot # bot loglarini kuzatish
docker compose restart bot # deploy'dan keyin
docker compose down # to'xtatish
Nega
restart: unless-stopped? Bot qulasa yoki server qayta yuklansa, Docker konteynerni avtomatik qayta ko'taradi β bu polling uchun supervisor/systemd'ning Docker'dagi ekvivalenti.env_file: .envsirlarni koddan tashqarida tutadi (.envni image ichigaCOPYqilmang!).
Xato boshqaruvi: onException va onApiError¶
Prod'da bitta tutilmagan xato butun botni qulatishi mumkin. Nutgram ikki maxsus handler beradi (ikkalasi ham tekshirildi β SpecialListeners da mavjud):
onExceptionβ handleringiz ichida har qanday PHP exception otilsa, shu yerga tushadi.onApiErrorβ Telegram API xato qaytarsa (masalan, "bot was blocked by the user"), shu yerga tushadi.
onException β umumiy tutgich¶
<?php
use SergiX44\Nutgram\Nutgram;
$bot->onException(function (Nutgram $bot, \Throwable $e) {
// 1) xatoni log qiling (foydalanuvchiga texnik tafsilot KO'RSATMANG)
error_log('[BOT XATO] ' . $e->getMessage());
// 2) foydalanuvchiga muloyim javob
$bot->sendMessage('Kechirasiz, kutilmagan xatolik yuz berdi. Birozdan keyin urinib koring.');
});
$bot->onCommand('hisobot', function (Nutgram $bot) {
throw new \RuntimeException('DB ulanmadi'); // misol uchun
});
Buni FakeNutgram bilan offline tekshirdik: handler ichida RuntimeException otilganda onException uni tutdi ($e->getMessage() === 'DB ulanmadi') va foydalanuvchiga uzr xabari yuborildi.
Turga bog'liq onException (aniqroq ishlov)¶
onException ning birinchi argumentiga exception sinfini berib, faqat shu turga ishlov bera olasiz. Mos tur uchun maxsus handler, qolganlari uchun umumiy:
<?php
// Faqat DB xatolari uchun
$bot->onException(\PDOException::class, function (Nutgram $bot, \Throwable $e) {
error_log('[DB XATO] ' . $e->getMessage());
$bot->sendMessage('Bazaga ulanishda muammo. Tez orada tuzatamiz.');
});
// Boshqa hamma narsa uchun "tutib qoluvchi"
$bot->onException(function (Nutgram $bot, \Throwable $e) {
error_log('[BOSHQA XATO] ' . $e::class . ': ' . $e->getMessage());
$bot->sendMessage('Kutilmagan xatolik. Birozdan keyin urinib koring.');
});
Tekshirildi: InvalidArgumentException otilganda turga mos handler tanlandi (umumiy emas) β Nutgram exception sinfiga qarab to'g'ri tutgichni topdi.
onApiError β Telegram javob bergan xatolar¶
Eng tez-tez uchraydigan API xato: foydalanuvchi botni bloklagan yoki chatni o'chirgan. Broadcast'da (qarang 15-bob) bu muqarrar. onApiError matn-naqsh (pattern) bo'yicha ishlaydi:
<?php
// "blocked" so'zi bor xatolarni tutamiz
$bot->onApiError('.*blocked.*', function (Nutgram $bot, \Throwable $e) {
// foydalanuvchini DB'da "nofaol" deb belgilaymiz, qayta urinmaymiz
error_log('Foydalanuvchi botni bloklagan: ' . $bot->userId());
});
// Umumiy API xato tutgichi
$bot->onApiError(function (Nutgram $bot, \Throwable $e) {
error_log('[API XATO] ' . $e->getMessage());
});
Tartib:
onApiError(pattern, ...)da naqsh Telegram qaytargan xato matniga moslanadi. Aniq naqshlarni (masalan,.*blocked.*,.*chat not found.*) umumiy tutgichdan oldin qo'ying. Aniq mos kelmasa, umumiy (naqshsiz) handler ishlaydi.
Strukturali logging: Monolog¶
error_log lokalda yetadi, lekin prod'da loglar strukturali (JSON) va markazlashgan bo'lgani ma'qul β keyin ularni qidirish, filtrlash, alert qo'yish oson. PHP olamida standart vosita β Monolog (composer require monolog/monolog).
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
use SergiX44\Nutgram\Nutgram;
$log = new Logger('bot');
// stdout'ga JSON yozamiz β Docker/systemd loglarni shu yerdan yig'adi
$stream = new StreamHandler('php://stdout', Logger::INFO);
$stream->setFormatter(new JsonFormatter());
$log->pushHandler($stream);
// Nutgram'ning xato handlerlariga ulaymiz:
$bot->onException(function (Nutgram $bot, \Throwable $e) use ($log) {
$log->error('Tutilmagan exception', [
'exception' => $e::class,
'message' => $e->getMessage(),
'user_id' => $bot->userId(),
'update_id' => $bot->update()?->update_id,
]);
$bot->sendMessage('Kechirasiz, xatolik yuz berdi.');
});
$bot->onApiError(function (Nutgram $bot, \Throwable $e) use ($log) {
$log->warning('Telegram API xato', ['message' => $e->getMessage()]);
});
Nega
php://stdout? Konteyner va systemd dunyosida "12-factor" yondashuvi: ilova loglarni fayl boshqarmaydi, balki stdout'ga oqim qilib chiqaradi.docker logsyokijournalctlularni yig'adi, keyin tashqi yig'gichga (Loki, ELK, CloudWatch) uzatadi. Log fayllarini o'zingiz aylantirishingiz (rotate) shart bo'lmaydi.DIQQAT: logga token, parol, foydalanuvchining shaxsiy ma'lumotlarini yozmang.
JsonFormatterbutun kontekstni yozadi β unga sirlarni qo'shmang.
Graceful: texnik-ish rejimi va toza to'xtash¶
Kill-switch / texnik-ish rejimi (maintenance mode)¶
Ba'zan botni vaqtincha "o'chirib", foydalanuvchilarga xabar berishingiz kerak (migratsiya, yangilanish). Buni eng toza yo'l β global middleware (qarang 09-bob) bilan short-circuit:
<?php
use SergiX44\Nutgram\Nutgram;
$bot->middleware(function (Nutgram $bot, $next) {
$maintenance = ($_ENV['MAINTENANCE'] ?? '0') === '1';
if ($maintenance) {
$bot->sendMessage('Bot vaqtincha texnik ishlar tufayli oflayn. Tez orada qaytamiz!');
return; // $next chaqirilmaydi -> hamma handler bloklanadi
}
$next($bot);
});
Bu mantiqni FakeNutgram bilan tekshirdik: MAINTENANCE=1 bo'lganda /start handleri umuman ishlamadi, foydalanuvchi faqat texnik-ish xabarini oldi. Rejimni yoqish/o'chirish β .env da MAINTENANCE ni o'zgartirib, botni restart qilish (yoki Redis bayrog'i orqali restart'siz).
Signal bilan toza to'xtash (polling, php-cli)¶
Polling botini supervisor/systemd to'xtatganda u SIGTERM signali yuboradi. pcntl bilan buni tutib, joriy update'ni tugatib, keyin chiqishingiz mumkin β yarmida uzilib qolmaslik uchun:
<?php
declare(ticks=1);
$shuttingDown = false;
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGTERM, function () use (&$shuttingDown) {
$shuttingDown = true; // joriy ishni tugatib, keyin chiqamiz
error_log('SIGTERM oldim, toza to\'xtayapman...');
});
}
Eslatma: Nutgram'ning
run()siklini siz to'g'ridan-to'g'ri boshqarmaysiz, shuning uchun amalda eng ishonchli graceful βstopwaitsecs(supervisor) yokiTimeoutStopSec(systemd) ni yetarli qo'yib, qisqa update'lar yozish. Webhook'da har so'rov allaqachon qisqa va mustaqil β graceful muammosi deyarli yo'q.
Monitoring: bot tirikmi va xatolar oqimi¶
Deploy qilib bo'lib, eng so'nggi qadam β kuzatuv. Uch qatlam:
- Healthcheck / uptime. Bot jarayoni tirikmi? Eng sodda yo'l β vaqti-vaqti bilan
getMechaqirib, javob kelishini tekshirish (cron yoki tashqi uptime-xizmat). systemd/Dockerrestartsiyosati jarayon qulashidan himoya qiladi, lekin "jarayon tirik, ammo Telegram bilan aloqa yo'q" holatini healthcheck ushlaydi.
<?php
// healthcheck.php β cron har 5 daqiqada ishga tushiradi (illustrativ)
try {
$me = $bot->getMe();
echo "OK: @{$me->username}\n"; // exit 0
} catch (\Throwable $e) {
fwrite(STDERR, 'BOT NOSOG\'LOM: ' . $e->getMessage() . "\n");
exit(1); // uptime-monitor alert yuboradi
}
-
Xato kuzatuvi (Sentry kabi).
onException/onApiErrorichida xatolarni Sentry'ga yuboring β har bir xato bo'yicha guruhlangan, stack-trace bilan ogohlantirish olasiz. Monolog'ningSentryHandleri buni avtomatlashtiradi. -
Metrikalar. Update'lar soni, ishlov vaqti, xatolar ulushi β buni global middleware'da o'lchab (qarang 09-bob β before/after timing), Prometheus/Grafana ga uzatish mumkin.
Eng kam to'plam: kichik bot uchun
restart=always(systemd/Docker) +onExceptionlog + tashqi uptime-monitor (UptimeRobot kabi) β yetarli va deyarli bepul boshlanish.
CI/CD haqida qisqacha¶
Har deploy'da qo'lda SSH qilib git pull && composer install && restart qilish β xato manbai. Buni avtomatlashtiring: git'ga push qilganda testlar (16-bob) ishga tushsin, o'tsa β serverga avtomatik deploy bo'lsin.
To'liq CI/CD ssenariysi (GitHub Actions, pipeline, sirlar sozlash) β ../git-github/ bo'limida. Telegram bot uchun tipik pipeline:
Bot uchun nozik nuqta: deploy bosqichida albatta eski nusxani to'xtatib, keyin yangisini ishga tushiring (polling 409 Conflict'dan qochish uchun). Webhook'da bu muammo yo'q β fpm jarayonlari deploy'dan keyin avtomatik yangi kodni oladi.
Bobni qanday tekshirdik (halol eslatma)¶
Botning PHP mantig'i Nutgram::fake() (FakeNutgram) bilan offline, tokensiz va tarmoqsiz haqiqatan ishga tushirilib tasdiqlandi (7/7 PASS):
onExceptionβ handler ichidagi exception'ni tutishi va foydalanuvchiga uzr yuborishi;- turga bog'liq
onExceptionβ mos exception sinfini umumiy tutgichdan ajratib tanlashi; - texnik-ish kill-switch (global middleware short-circuit) β
MAINTENANCEyoqilganda handlerni bloklashi; .envparseri β token va kalitlarni o'qishi,#izohni tashlashi.
onApiError, setRunningMode(Webhook::class), setWebhook/deleteWebhook/getWebhookInfo, setMyCommands, getMe β Nutgram 4.46 manbasida mavjudligi tekshirildi (xayoliy metod yo'q).
Quyidagilar jonli muhitni talab qiladi va shuning uchun illustrativ (kod/konfiguratsiya to'g'ri, lekin bu yerda RUN qilinmaydi): supervisor/systemd jarayoni, nginx + php-fpm, real TLS sertifikat, Docker build/up, jonli setWebhook va Telegram'ning POST yuborishi, jonli getMe/healthcheck, Sentry/uptime alertlari. "Bot serverda ishladi / xabar yetkazildi" deb soxta tasdiq bermaymiz β bularni o'z serveringizda ko'rasiz.
Mashqlar¶
Oson¶
.envva.env.examplefayllarini yarating (token,APP_ENV,DB_DSN)..envni.gitignorega qo'shing,.env.exampleni esa qo'shmang. Nega farqli ekanini bir jumlada izohlang.- Yuqoridagi
parseEnv()funksiyasini yozing va"TOKEN=abc\n# izoh\nENV=prod\n"matnida sinab ko'ring:TOKEN/ENVo'qilsin, izoh tashlansin. onExceptionumumiy handler yozing: xatonierror_logga chiqarsin va foydalanuvchiga "Xatolik yuz berdi" yuborsin. Bilib turib/testhandlerda exception oting.- Polling vs webhook farqini 3 ta nuqtada (kim so'raydi, HTTPS kerakmi, bir vaqtda nechta nusxa) jadval qilib yozing.
- systemd servis faylida
Restart=alwaysvaRestartSec=3nima qilishini izohlang. Bularsiz bot qulaganda nima bo'ladi? docker-compose.ymldarestart: unless-stoppednirestart: noga o'zgartirsangiz, server qayta yuklanganda bot bilan nima bo'ladi?
O'rta¶
- Texnik-ish rejimini middleware bilan yozing:
MAINTENANCE=1bo'lsa har bir handlerni bloklab, foydalanuvchiga xabar bersin. FakeNutgram bilan ikkala holatni (1va0) tekshiring:1da handler ishlamasligi,0da ishlashi. - Turga bog'liq
onExceptionyozing:\PDOExceptionuchun "Baza muammosi", boshqa har qanday exception uchun "Kutilmagan xato". FakeNutgram bilan ikkala tarmoqni tasdiqlang (assertReplyText). onApiError('.*blocked.*', ...)handler yozing: bloklagan foydalanuvchini logga "nofaol" deb yozsin, qayta xabar yubormasin. Bu broadcast'da nega muhim ekanini izohlang.- Monolog'ni
php://stdoutga JSON formatda sozlang vaonExceptionichida xatoniuser_id,update_idkontekst bilan log qiling. Nega faylga emas, stdout'ga yozish konteynerda afzal? - Polling botini supervisor bilan ishlatish uchun to'liq
.confyozing (autorestart,stdout_logfile,user). Keyin deploy oqimini (pull β install β restart) bandlar bilan yozing. - Webhook kirish nuqtasi (
webhook.php) yozing:setRunningMode(Webhook::class), bitta/starthandler. Alohidaregister-webhook.phpdasetWebhooknisecret_tokenbilan chaqiring. (Jonli setWebhook illustrativ.)
Qiyin¶
- To'liq Docker to'plami:
Dockerfile(php-cli, pdo + pcntl),docker-compose.yml(bot + redis + db,restart: unless-stopped,env_file) va.dockerignore(.env,vendor/,.git) yozing. Nega.envni image ichigaCOPYqilmaslik kerakligini, va nega webhook prod'da holatni Redis'ga saqlash zarurligini izohlang. - Xato boshqaruvi qatlamlari:
onException(turga bog'liq\PDOException+ umumiy) vaonApiError(.*blocked.*+ umumiy) ni birga sozlang, hammasini Monolog'ga ulang. FakeNutgram bilan: (a)\PDOExceptionotilganda DB-handler ishlashini, (b) boshqa exception umumiy handlerga tushishini tasdiqlang. (onApiErrorjonli API talab qiladi β uni illustrativ qoldiring.) - Healthcheck + alert mantig'i:
getMemuvaffaqiyatli bo'lsaexit(0), xato bo'lsaexit(1)qaytaradiganhealthcheck.phpyozing. Buni cron/uptime-monitor bilan qanday bog'lashni va systemdrestartdan farqini (jarayon tirik, ammo Telegram aloqasi yo'q holati) tushuntiring. (JonligetMeillustrativ.) - Polling β webhook migratsiyasi: mavjud polling botini webhook'ga o'tkazish rejasini yozing: (1)
deleteWebhook/setWebhooktartibi va nega ikkalasi birga ishlamasligi, (2) holatni xotiradan Redis'ga ko'chirish, (3) nginx + php-fpm marshruti, (4) 409 Conflict'dan qochish uchun deploy ketma-ketligi. Har qadamni qisqa izohlang.
Yechimlar
Oson 1. .env da haqiqiy sirlar (token, parol) bor β u hech qachon git'ga tushmaydi (.gitignore). .env.example esa faqat kalitlar ro'yxati (qiymatsiz) β uni git'ga qo'shamiz, yangi dasturchi nusxa olib (cp .env.example .env) o'z qiymatlari bilan to'ldiradi.
Oson 2.
<?php
function parseEnv(string $text): array {
$out = [];
foreach (explode("\n", $text) as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, '#')) continue;
[$k, $v] = array_pad(explode('=', $line, 2), 2, '');
$out[trim($k)] = trim($v);
}
return $out;
}
$env = parseEnv("TOKEN=abc\n# izoh\nENV=prod\n");
assert($env['TOKEN'] === 'abc');
assert($env['ENV'] === 'prod');
assert(!isset($env['# izoh']));
Oson 3.
<?php
use SergiX44\Nutgram\Nutgram;
$bot->onException(function (Nutgram $bot, \Throwable $e) {
error_log('[XATO] ' . $e->getMessage());
$bot->sendMessage('Xatolik yuz berdi');
});
$bot->onCommand('test', fn (Nutgram $bot) => throw new \RuntimeException('sinov'));
Oson 4.
| Polling | Webhook | |
|---|---|---|
| Kim so'raydi | Bot (getUpdates) |
Telegram (POST) |
| HTTPS domen | Kerak emas | Shart |
| Bir vaqtda nusxa | Faqat bitta (409 Conflict) | Cheklov yo'q |
Oson 5. Restart=always β jarayon qanday tugashidan qat'i nazar systemd uni qayta ishga tushiradi; RestartSec=3 β 3 soniya kutib, keyin. Bularsiz bot qulaganda o'chiq qoladi, kimdir qo'lda ishga tushirmaguncha javob bermaydi.
Oson 6. restart: no bo'lsa, server qayta yuklanganda (yoki bot qulasa) konteyner avtomatik ko'tarilmaydi β bot o'chiq qoladi. unless-stopped esa siz ataylab to'xtatmagan bo'lsangiz qayta ishga tushiradi.
O'rta 1.
<?php
use SergiX44\Nutgram\Nutgram;
function makeBot(bool $maintenance): Nutgram {
$bot = Nutgram::fake();
$bot->middleware(function (Nutgram $bot, $next) use ($maintenance) {
if ($maintenance) { $bot->sendMessage('Texnik ishlar, tez orada qaytamiz!'); return; }
$next($bot);
});
$bot->onCommand('start', fn (Nutgram $bot) => $bot->sendMessage('Salom'));
return $bot;
}
// maintenance ON
$on = makeBot(true);
$on->hearText('/start')->reply();
$on->assertReplyText('Texnik ishlar, tez orada qaytamiz!');
// maintenance OFF
$off = makeBot(false);
$off->hearText('/start')->reply();
$off->assertReplyText('Salom');
O'rta 2.
<?php
use SergiX44\Nutgram\Nutgram;
$bot = Nutgram::fake();
$bot->onException(\PDOException::class, fn (Nutgram $bot, \Throwable $e) => $bot->sendMessage('Baza muammosi'));
$bot->onException(fn (Nutgram $bot, \Throwable $e) => $bot->sendMessage('Kutilmagan xato'));
$bot->onCommand('db', fn (Nutgram $bot) => throw new \PDOException('x'));
$bot->onCommand('other', fn (Nutgram $bot) => throw new \RuntimeException('y'));
$bot->hearText('/db')->reply();
$bot->assertReplyText('Baza muammosi');
$bot->hearText('/other')->reply();
$bot->assertReplyText('Kutilmagan xato');
O'rta 3.
<?php
use SergiX44\Nutgram\Nutgram;
$bot->onApiError('.*blocked.*', function (Nutgram $bot, \Throwable $e) {
error_log('Nofaol (bloklagan): ' . $bot->userId());
// qayta sendMessage QILMAYMIZ β baribir yetmaydi
});
O'rta 4.
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
use SergiX44\Nutgram\Nutgram;
$log = new Logger('bot');
$h = new StreamHandler('php://stdout', Logger::INFO);
$h->setFormatter(new JsonFormatter());
$log->pushHandler($h);
$bot->onException(function (Nutgram $bot, \Throwable $e) use ($log) {
$log->error('exception', [
'class' => $e::class, 'msg' => $e->getMessage(),
'user_id' => $bot->userId(), 'update_id' => $bot->update()?->update_id,
]);
$bot->sendMessage('Xatolik yuz berdi');
});
docker logs, journalctl), fayl aylantirish (rotate) va disk to'lishi haqida o'ylash shart emas β "12-factor" yondashuvi.
O'rta 5.
[program:mybot]
command=/usr/bin/php /var/www/mybot/bot.php
directory=/var/www/mybot
autostart=true
autorestart=true
startretries=5
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/mybot/bot.log
git pull
2. composer install --no-dev --optimize-autoloader
3. migratsiyalar (DB sxema o'zgarsa)
4. sudo supervisorctl restart mybot (eski nusxa to'xtaydi, yangisi ko'tariladi β 409 yo'q)
O'rta 6.
<?php
// webhook.php
require __DIR__ . '/../vendor/autoload.php';
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\RunningMode\Webhook;
$bot = new Nutgram(getenv('TELEGRAM_TOKEN'));
$bot->setRunningMode(Webhook::class);
$bot->onCommand('start', fn (Nutgram $bot) => $bot->sendMessage('Salom!'));
$bot->run();
<?php
// register-webhook.php (bir marta β jonli, illustrativ)
$bot->setWebhook('https://bot.example.uz/webhook.php', secret_token: getenv('WEBHOOK_SECRET'));
Qiyin 1.
# Dockerfile
FROM php:8.4-cli-alpine
RUN docker-php-ext-install pdo pdo_mysql pcntl && apk add --no-cache git
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --optimize-autoloader --no-interaction
COPY . .
CMD ["php", "bot.php"]
# docker-compose.yml
services:
bot:
build: .
restart: unless-stopped
env_file: .env
depends_on: [redis, db]
redis:
image: redis:7-alpine
restart: unless-stopped
volumes: ["redis-data:/data"]
db:
image: mysql:8
restart: unless-stopped
environment:
MYSQL_DATABASE: botdb
MYSQL_USER: bot
MYSQL_PASSWORD: ${DB_PASS}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
volumes: ["db-data:/var/lib/mysql"]
volumes:
redis-data:
db-data:
.env ni image'ga COPY qilmaymiz, chunki image registry'ga yuklansa sir tarqaydi; uni runtime'da env_file orqali beramiz. Webhook'da har so'rov yangi fpm jarayoni β xotiradagi holat yashamaydi, shuning uchun setUserData/conversation Redis'da saqlanishi shart.
Qiyin 2.
<?php
use SergiX44\Nutgram\Nutgram;
// (Monolog sozlash O'rta 4 dagidek β $log)
$bot = Nutgram::fake();
$bot->onException(\PDOException::class, function (Nutgram $bot, \Throwable $e) use ($log) {
$log->error('db', ['msg' => $e->getMessage()]);
$bot->sendMessage('Baza muammosi');
});
$bot->onException(function (Nutgram $bot, \Throwable $e) use ($log) {
$log->error('other', ['class' => $e::class]);
$bot->sendMessage('Kutilmagan xato');
});
// onApiError jonli API talab qiladi β illustrativ:
$bot->onApiError('.*blocked.*', fn (Nutgram $bot, \Throwable $e) => $log->warning('blocked'));
$bot->onApiError(fn (Nutgram $bot, \Throwable $e) => $log->warning('api', ['msg' => $e->getMessage()]));
$bot->onCommand('db', fn (Nutgram $bot) => throw new \PDOException('x'));
$bot->onCommand('boom', fn (Nutgram $bot) => throw new \LogicException('y'));
$bot->hearText('/db')->reply(); $bot->assertReplyText('Baza muammosi');
$bot->hearText('/boom')->reply(); $bot->assertReplyText('Kutilmagan xato');
Qiyin 3.
<?php
// healthcheck.php (jonli getMe β illustrativ)
try {
$me = $bot->getMe();
fwrite(STDOUT, "OK: @{$me->username}\n");
exit(0);
} catch (\Throwable $e) {
fwrite(STDERR, 'NOSOG\'LOM: ' . $e->getMessage() . "\n");
exit(1);
}
exit(1) bo'lsa uptime-monitor (UptimeRobot/cron-mail) alert yuboradi. systemd Restart=always faqat jarayon qulaganda ishlaydi; healthcheck esa "jarayon tirik, lekin Telegram bilan aloqa uzilgan / token bekor qilingan" holatini ham ushlaydi β bu systemd ko'rmaydigan turdagi nosozlik.
Qiyin 4. Polling β webhook migratsiyasi:
1. deleteWebhook shart emas, setWebhook o'zi yetadi (webhook o'rnatilishi bilan getUpdates ishlamay qoladi) β lekin avval polling jarayonini to'xtating (systemctl stop mybot), aks holda u getUpdates ni davom ettirib 409 beradi. Polling va webhook bir vaqtda yoqilib bo'lmaydi: webhook yoqiq bo'lsa Telegram getUpdates'ga ruxsat bermaydi.
2. Holat: xotiradagi setUserData/conversation webhook'da yashamaydi (har so'rov yangi fpm) β cache'ni Redis'ga ko'chiring (11-bob).
3. nginx: location /webhook.php β fastcgi_pass php-fpm soketiga; root public/; qolgani 404.
4. Deploy ketma-ketligi: kod deploy β setWebhook(url, secret_token) β tekshirish getWebhookInfo. Webhook'da deploy'da to'xtatish/ishga tushirish bosqichi yo'q (fpm yangi kodni avtomatik oladi), shuning uchun 409 muammosi umuman yo'q.
β¬ οΈ Oldingi: 16 β Testlash (FakeNutgram) Β· π README Β· Keyingi: 18 β Yakuniy kapston: to'liq bot β‘οΈ