telegram
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Deploy Admin Panel / Deploy Admin Panel (push) Waiting to run
Deploy Admin Panel / Admin Panel Verification (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
Deploy Telegram Bot / Deploy Telegram Bot (push) Waiting to run

This commit is contained in:
Dmitry 2025-12-11 21:23:55 +03:00
parent b058dfaaec
commit 7439c6aaa9
2 changed files with 84 additions and 4 deletions

View file

@ -56,6 +56,9 @@ ENV BACKEND_URL=https://api.mnemo-cards.online \
# No EXPOSE needed - bot uses outbound connections only (polling) # No EXPOSE needed - bot uses outbound connections only (polling)
# Signal for graceful shutdown
STOPSIGNAL SIGTERM
# Healthcheck - check if process is running # Healthcheck - check if process is running
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \ HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
CMD pgrep -f "/app/bot" > /dev/null || exit 1 CMD pgrep -f "/app/bot" > /dev/null || exit 1

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:mnemo_cards_telegram_bot/bot_config.dart'; import 'package:mnemo_cards_telegram_bot/bot_config.dart';
@ -10,10 +11,65 @@ import 'package:mnemo_cards_telegram_bot/backend_client.dart';
const String version = '0.0.1'; const String version = '0.0.1';
// Global variables for graceful shutdown
BackendClient? _globalBackendClient;
TeleDart? _globalTeledart;
bool _isShuttingDown = false;
final Completer<void> _shutdownCompleter = Completer<void>();
void main() async { void main() async {
// Setup signal handlers for graceful shutdown
ProcessSignal.sigterm.watch().listen((signal) {
print('[BOT] Received SIGTERM, shutting down gracefully...');
_shutdown();
});
ProcessSignal.sigint.watch().listen((signal) {
print('[BOT] Received SIGINT, shutting down gracefully...');
_shutdown();
});
return _run(); return _run();
} }
Future<void> _shutdown() async {
if (_isShuttingDown) {
print('[BOT] Shutdown already in progress');
return;
}
_isShuttingDown = true;
print('[BOT] Starting graceful shutdown...');
try {
if (_globalTeledart != null) {
print('[BOT] Stopping TeleDart...');
// TeleDart doesn't have explicit stop method, but we can dispose resources
// The process will exit naturally when main completes
}
} catch (e) {
print('[BOT] Error stopping TeleDart: $e');
}
try {
if (_globalBackendClient != null) {
print('[BOT] Disposing BackendClient...');
_globalBackendClient!.dispose();
}
} catch (e) {
print('[BOT] Error disposing BackendClient: $e');
}
print('[BOT] Shutdown complete');
// Complete the completer to allow main to exit
if (!_shutdownCompleter.isCompleted) {
_shutdownCompleter.complete();
}
exit(0);
}
String? _extractCodeFromText(String? rawText) { String? _extractCodeFromText(String? rawText) {
if (rawText == null) { if (rawText == null) {
return null; return null;
@ -92,6 +148,11 @@ Future<void> _handleCodeClaim({
} }
Future<void> _run() async { Future<void> _run() async {
if (_isShuttingDown) {
print('[BOT] Shutdown in progress, not starting bot');
return;
}
print('[BOT] Starting bot version $version'); print('[BOT] Starting bot version $version');
BackendClient? backendClient; BackendClient? backendClient;
TeleDart? teledart; TeleDart? teledart;
@ -110,6 +171,9 @@ Future<void> _run() async {
final username = (await Telegram(config.botToken).getMe()).username; final username = (await Telegram(config.botToken).getMe()).username;
teledart = TeleDart(config.botToken, Event(username!)); teledart = TeleDart(config.botToken, Event(username!));
// Store globally for shutdown handler
_globalTeledart = teledart;
Future<void> notifyAdmins(String message) async { Future<void> notifyAdmins(String message) async {
for (final admin in adminIds) { for (final admin in adminIds) {
try { try {
@ -127,6 +191,9 @@ Future<void> _run() async {
apiKey: config.apiKey, apiKey: config.apiKey,
); );
// Store globally for shutdown handler
_globalBackendClient = backendClient;
// At this point all variables are initialized and non-null // At this point all variables are initialized and non-null
final teledartInstance = teledart; final teledartInstance = teledart;
final backendClientInstance = backendClient; final backendClientInstance = backendClient;
@ -587,7 +654,16 @@ Future<void> _run() async {
teledartInstance.start(); teledartInstance.start();
print('[BOT] Bot started successfully'); print('[BOT] Bot started successfully');
// Wait for shutdown signal
// The bot will run until it receives SIGTERM/SIGINT
await _shutdownCompleter.future;
} on Exception catch (e, s) { } on Exception catch (e, s) {
if (_isShuttingDown) {
print('[BOT] Error during shutdown, exiting...');
return;
}
print('[BOT] ERROR: Error: $e\n$s'); print('[BOT] ERROR: Error: $e\n$s');
print('[BOT] Error while starting bot: $e'); print('[BOT] Error while starting bot: $e');
print('Stack trace: $s'); print('Stack trace: $s');
@ -598,16 +674,17 @@ Future<void> _run() async {
print(' - BACKEND_URL or MNEMO_BACKEND_URL (optional, default: https://api.mnemo-cards.online)'); print(' - BACKEND_URL or MNEMO_BACKEND_URL (optional, default: https://api.mnemo-cards.online)');
print(' - BOT_SHARE_DAILY_LIMIT (optional, default: 1)'); print(' - BOT_SHARE_DAILY_LIMIT (optional, default: 1)');
// Cleanup resources before restart // Cleanup resources
try { try {
backendClient?.dispose(); backendClient?.dispose();
} catch (_) { } catch (_) {
// Ignore cleanup errors // Ignore cleanup errors
} }
print('Restarting in 5 seconds...'); // Exit with error code instead of restarting
await Future.delayed(const Duration(seconds: 5)); // Let Docker/Coolify handle restarts with restart policy
return _run(); print('[BOT] Exiting with error code...');
exit(1);
} }
} }