diff --git a/README.md b/README.md index ac2579a..b672b18 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ M A N A G E R ``` -![Version](https://img.shields.io/badge/version-1.1-blue) +![Version](https://img.shields.io/badge/version-1.2--Beta-blue) ![License](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Linux-orange) ![Docker](https://img.shields.io/badge/Docker-Required-2496ED?logo=docker&logoColor=white) @@ -18,31 +18,55 @@ A powerful management tool for deploying and managing Psiphon Conduit nodes on Linux servers. Help users access the open internet during network restrictions. -## Quick Install +## Quick Install (Beta) ```bash -curl -sL https://raw.githubusercontent.com/SamNet-dev/conduit-manager/main/conduit.sh | sudo bash +curl -sL https://raw.githubusercontent.com/SamNet-dev/conduit-manager/beta-releases/conduit.sh | sudo bash ``` Or download and run manually: ```bash -wget https://raw.githubusercontent.com/SamNet-dev/conduit-manager/main/conduit.sh +wget https://raw.githubusercontent.com/SamNet-dev/conduit-manager/beta-releases/conduit.sh sudo bash conduit.sh ``` -## What's New in v1.1 +> For stable release, use `main` instead of `beta-releases` in the URL above. -- **Multi-Container Support** — Run up to 5 Conduit containers on a single server for higher throughput -- **Background Traffic Tracker** — Continuous tcpdump-based tracker service with per-country GeoIP stats -- **Advanced Stats Page** — Live dashboard with top countries by peers, download, upload, and unique IPs (bar charts, auto-refresh) -- **Live Dashboard Overhaul** — Side-by-side active clients and top upload by country with real-time bars -- **Per-Container Settings** — Configure max-clients and bandwidth individually for each container -- **Container Manager** — Add or remove containers on the fly with auto-refreshing status view -- **Smart Install** — Detects CPU cores and RAM, recommends container count for your hardware -- **Info & Help Hub** — Multi-page guide covering the tracker, stats, containers, privacy, and about -- **Service Auto-Recovery** — Automatically restarts failed conduit.service on script launch -- **Seamless Upgrade** — Existing v1.0.x users can run the new script without reinstalling; old containers are recognized automatically +## v1.2-Beta Changelog + +> This list will grow as more features are added before the full v1.2 release. + +**New Features** +- Telegram bot notifications with guided setup wizard (periodic status reports via Telegram) +- Systemd-based notification service (survives reboots and TUI exits) +- Compact number display — large counts show as 16.5K, 1.2M +- Active clients count in dashboard and Telegram reports +- Total bandwidth served in reports +- Timestamps on all Telegram reports + +**Bug Fixes** +- Auto-restart for stuck containers with improved detection +- False WAITING status in health check for connected containers without stats +- Container start/stop/restart logic +- Duplicate country entries in GeoIP data with broader name normalization +- TUI stability (multiple fixes) +- Health check edge cases +- CPU normalization in reports (divide by core count) +- Peers count consistency across views +- Telegram markdown escaping (backslash handling) +- Wizard failure paths now preserve existing config +- Uninstall cleanup for Telegram service +- Menu no longer restarts notification loop on every open +- PID management for background processes + +**Security** +- Silent bot token input (not echoed) +- Numeric-only chat ID validation +- Restricted PID file permissions (600) +- BotFather privacy guidance in setup wizard +- OPSEC warning for operators in censored regions +- Curl calls with `--max-filesize` and `--max-time` limits ## Features @@ -54,12 +78,11 @@ sudo bash conduit.sh - **Advanced Stats** — Top countries by connected peers, download, upload, and unique IPs with bar charts - **Live Peer Traffic** — Real-time traffic table by country with speed, total bytes, and IP/client counts - **Background Tracker** — Continuous traffic monitoring via systemd service with GeoIP resolution +- **Telegram Notifications** — Optional periodic status reports and alerts via Telegram bot - **Per-Container Settings** — Configure max-clients and bandwidth per container -- **Easy Management** — Powerful CLI commands or interactive menu - **Backup & Restore** — Backup and restore your node identity keys - **Health Checks** — Comprehensive diagnostics for troubleshooting -- **Info & Help** — Built-in multi-page guide explaining how everything works -- **Complete Uninstall** — Clean removal of all components +- **Complete Uninstall** — Clean removal of all components including Telegram service ## Supported Distributions @@ -75,102 +98,28 @@ sudo bash conduit.sh After installation, use the `conduit` command: -### Status & Monitoring ```bash -conduit status # Show current status and resource usage -conduit stats # View live statistics (real-time dashboard) -conduit logs # View raw Docker logs -conduit health # Run health check diagnostics -conduit peers # Live peer traffic by country (GeoIP) -``` - -### Rewards -```bash -conduit qr # Show QR code to claim rewards via Ryve app -``` - -### Container Management -```bash -conduit start # Start all Conduit containers -conduit stop # Stop all Conduit containers -conduit restart # Restart all Conduit containers -conduit update # Update to the latest Conduit image -``` - -### Configuration -```bash -conduit settings # Change max-clients and bandwidth per container conduit menu # Open interactive management menu -``` - -### Backup & Restore -```bash -conduit backup # Backup your node identity keys -conduit restore # Restore node identity from backup -``` - -### Maintenance -```bash +conduit status # Show current status +conduit stats # Live statistics dashboard +conduit peers # Live peer traffic by country +conduit start # Start all containers +conduit stop # Stop all containers +conduit restart # Restart all containers +conduit update # Update Conduit image +conduit backup # Backup node identity keys +conduit restore # Restore from backup +conduit qr # Show QR code for rewards +conduit health # Run health diagnostics conduit uninstall # Remove all components -conduit version # Show version information -conduit help # Show help message ``` -## Interactive Menu - -The interactive menu (`conduit menu`) provides access to all features: - -| Option | Description | -|--------|-------------| -| **1** | View status dashboard — real-time stats with active clients and top upload by country | -| **2** | Live connection stats — streaming stats from Docker logs | -| **3** | View logs — raw Docker log output | -| **4** | Live peers by country — per-country traffic table with speed and client counts | -| **5** | Start Conduit | -| **6** | Stop Conduit | -| **7** | Restart Conduit | -| **8** | Update Conduit image | -| **9** | Settings & Tools — max-clients, bandwidth, QR code, backup, restore, health check, uninstall | -| **c** | Manage containers — add or remove containers (up to 5) | -| **a** | Advanced stats — top 5 charts for peers, download, upload, unique IPs | -| **i** | Info & Help — multi-page guide with tracker, stats, containers, privacy, about | -| **0** | Exit | - -## Configuration Options +## Configuration | Option | Default | Range | Description | |--------|---------|-------|-------------| -| `max-clients` | 200 | 1–1000 | Maximum concurrent proxy clients per container | -| `bandwidth` | 5 | 1–40, -1 | Bandwidth limit per peer (Mbps). Use -1 for unlimited. | - -**Recommended values based on server hardware:** - -| CPU Cores | RAM | Recommended Containers | Max Clients (per container) | -|-----------|-----|------------------------|-----------------------------| -| 1 Core | < 1 GB | 1 | 100 | -| 2 Cores | 2 GB | 1–2 | 200 | -| 4 Cores | 4 GB+ | 2–3 | 400 | -| 8+ Cores | 8 GB+ | 3–5 | 800 | - -## Installation Options - -```bash -# Standard install -sudo bash conduit.sh - -# Force reinstall -sudo bash conduit.sh --reinstall - -# Uninstall everything -sudo bash conduit.sh --uninstall - -# Show help -sudo bash conduit.sh --help -``` - -## Upgrading from v1.0.x - -Just run the new script. When prompted, select **"Open management menu"** — your existing container is recognized automatically. No reinstall needed. The background tracker service starts when you next start/restart from the menu. +| `max-clients` | 200 | 1–1000 | Max concurrent clients per container | +| `bandwidth` | 5 | 1–40, -1 | Bandwidth limit per peer (Mbps). -1 for unlimited | ## Requirements @@ -179,210 +128,23 @@ Just run the new script. When prompted, select **"Open management menu"** — yo - Internet connection - Minimum 512MB RAM (1GB+ recommended for multi-container) -## How It Works +## Upgrading -1. **Detection** — Identifies your Linux distribution and init system -2. **Docker Setup** — Installs Docker if not present -3. **Hardware Check** — Detects CPU/RAM and recommends container count -4. **Container Deployment** — Pulls and runs the official Psiphon Conduit image -5. **Auto-Start Configuration** — Sets up systemd/OpenRC/SysVinit service -6. **Tracker Service** — Starts background traffic tracker with GeoIP resolution -7. **CLI Installation** — Creates the `conduit` management command +Just run the install command above. When prompted, select **"Open management menu"** — existing containers are recognized automatically. Telegram settings are preserved across upgrades. ## Claim Rewards (OAT Tokens) -Conduit node operators can earn OAT tokens for contributing to the Psiphon network. To claim rewards: - -1. **Install the Ryve app** on your phone -2. **Create a crypto wallet** within the app -3. **Link your Conduit containers** by scanning the QR code: - - From the menu: Select Settings & Tools **Option 6 → Show QR Code & Conduit ID** - - From Manage Containers: press **[q]** to display QR code - - CLI: `conduit qr` -4. **Scan the QR code** with the Ryve app to link your node -5. **Monitor & earn** — the app shows your last 48 hours of connection activity and OAT token rewards - -> Each container has its own unique Conduit ID and QR code. If running multiple containers, you'll need to link each one separately. +1. Install the **Ryve app** on your phone +2. Create a **crypto wallet** within the app +3. Run `conduit qr` or use the menu to show your QR code +4. Scan with Ryve to link your node and start earning ## Security -- **Secure Backups**: Node identity keys are stored with restricted permissions (600) +- **Secure Backups**: Node identity keys stored with restricted permissions (600) - **No Telemetry**: The manager collects no data and sends nothing externally - **Local Tracking Only**: Traffic stats are stored locally and never transmitted - ---- - -
- -# راهنمای فارسی - مدیریت کاندوییت - -ابزار قدرتمند برای راه‌اندازی و مدیریت نود سایفون کاندوییت روی سرورهای لینوکس. به کاربران کمک کنید تا در زمان محدودیت‌های اینترنتی به اینترنت آزاد دسترسی داشته باشند. - -## نصب سریع - -دستور زیر را در ترمینال سرور اجرا کنید: - -```bash -curl -sL https://raw.githubusercontent.com/SamNet-dev/conduit-manager/main/conduit.sh | sudo bash -``` - -یا دانلود و اجرای دستی: - -```bash -wget https://raw.githubusercontent.com/SamNet-dev/conduit-manager/main/conduit.sh -sudo bash conduit.sh -``` - -## تازه‌های نسخه 1.1 - -- **پشتیبانی از چند کانتینر** — اجرای تا ۵ کانتینر روی یک سرور برای ظرفیت بیشتر -- **ردیاب ترافیک پس‌زمینه** — سرویس ردیابی مداوم با آمار جغرافیایی به تفکیک کشور -- **صفحه آمار پیشرفته** — داشبورد زنده با نمودار میله‌ای برای برترین کشورها -- **داشبورد بازطراحی شده** — نمایش کلاینت‌های فعال و آپلود برتر به تفکیک کشور -- **تنظیمات هر کانتینر** — پیکربندی جداگانه حداکثر کاربران و پهنای باند -- **مدیریت کانتینرها** — اضافه یا حذف کانتینر به صورت آنی -- **نصب هوشمند** — تشخیص CPU و RAM و پیشنهاد تعداد کانتینر مناسب -- **بخش راهنما** — راهنمای چندصفحه‌ای شامل ردیاب، آمار، کانتینرها، حریم خصوصی و درباره ما -- **بازیابی خودکار سرویس** — ریستارت خودکار سرویس در صورت خرابی -- **ارتقا بدون نصب مجدد** — کاربران نسخه قبلی بدون نیاز به نصب مجدد می‌توانند آپدیت کنند - -## ویژگی‌ها - -- **نصب با یک کلیک** — داکر و تمام موارد مورد نیاز به صورت خودکار نصب می‌شود -- **مقیاس‌پذیری چند کانتینره** — اجرای ۱ تا ۵ کانتینر برای حداکثر استفاده از سرور -- **پشتیبانی از توزیع‌های مختلف** — اوبونتو، دبیان، سنت‌اواس، فدورا، آرچ، آلپاین -- **راه‌اندازی خودکار** — پس از ریستارت سرور، سرویس به صورت خودکار اجرا می‌شود -- **داشبورد زنده** — نمایش لحظه‌ای وضعیت، تعداد کاربران، مصرف CPU و RAM -- **آمار پیشرفته** — نمودار میله‌ای برترین کشورها بر اساس اتصال، دانلود، آپلود و IP -- **مانیتورینگ ترافیک** — جدول لحظه‌ای ترافیک بر اساس کشور با سرعت و تعداد کلاینت -- **ردیاب پس‌زمینه** — سرویس ردیابی مداوم ترافیک با تشخیص جغرافیایی -- **تنظیمات هر کانتینر** — پیکربندی حداکثر کاربران و پهنای باند برای هر کانتینر -- **مدیریت آسان** — دستورات قدرتمند CLI یا منوی تعاملی -- **پشتیبان‌گیری و بازیابی** — پشتیبان‌گیری و بازیابی کلیدهای هویت نود -- **بررسی سلامت** — تشخیص جامع برای عیب‌یابی -- **راهنما و اطلاعات** — راهنمای چندصفحه‌ای داخلی -- **حذف کامل** — پاکسازی تمام فایل‌ها و تنظیمات - -## دستورات CLI - -### وضعیت و مانیتورینگ -```bash -conduit status # نمایش وضعیت و مصرف منابع -conduit stats # داشبورد زنده (لحظه‌ای) -conduit logs # لاگ‌های داکر -conduit health # بررسی سلامت سیستم -conduit peers # ترافیک بر اساس کشور (GeoIP) -``` - -### پاداش -```bash -conduit qr # نمایش QR کد برای دریافت پاداش از اپلیکیشن Ryve -``` - -### مدیریت کانتینر -```bash -conduit start # شروع تمام کانتینرها -conduit stop # توقف تمام کانتینرها -conduit restart # ریستارت تمام کانتینرها -conduit update # به‌روزرسانی به آخرین نسخه -``` - -### پیکربندی -```bash -conduit settings # تغییر تنظیمات هر کانتینر -conduit menu # منوی تعاملی -``` - -### پشتیبان‌گیری و بازیابی -```bash -conduit backup # پشتیبان‌گیری از کلیدهای نود -conduit restore # بازیابی کلیدهای نود از پشتیبان -``` - -### نگهداری -```bash -conduit uninstall # حذف کامل -conduit version # نمایش نسخه -conduit help # راهنما -``` - -## منوی تعاملی - -| گزینه | توضیحات | -|-------|---------| -| **1** | داشبورد وضعیت — آمار لحظه‌ای با کلاینت‌های فعال و آپلود برتر | -| **2** | آمار زنده اتصال — استریم آمار از لاگ داکر | -| **3** | مشاهده لاگ — خروجی لاگ داکر | -| **4** | ترافیک زنده به تفکیک کشور — جدول ترافیک با سرعت و تعداد کلاینت | -| **5** | شروع کاندوییت | -| **6** | توقف کاندوییت | -| **7** | ریستارت کاندوییت | -| **8** | به‌روزرسانی ایمیج | -| **9** | تنظیمات و ابزارها — پهنای باند، QR کد، پشتیبان‌گیری، بازیابی، بررسی سلامت، حذف نصب | -| **c** | مدیریت کانتینرها — اضافه یا حذف (تا ۵) | -| **a** | آمار پیشرفته — نمودار برترین کشورها | -| **i** | راهنما — توضیحات ردیاب، آمار، کانتینرها، حریم خصوصی | -| **0** | خروج | - -## تنظیمات - -| گزینه | پیش‌فرض | محدوده | توضیحات | -|-------|---------|--------|---------| -| `max-clients` | 200 | ۱–۱۰۰۰ | حداکثر کاربران همزمان برای هر کانتینر | -| `bandwidth` | 5 | ۱–۴۰ یا ۱- | محدودیت پهنای باند (Mbps). برای نامحدود ۱- وارد کنید. | - -**مقادیر پیشنهادی بر اساس سخت‌افزار سرور:** - -| پردازنده | رم | کانتینر پیشنهادی | حداکثر کاربران (هر کانتینر) | -|----------|-----|-------------------|----------------------------| -| ۱ هسته | کمتر از ۱ گیگ | ۱ | ۱۰۰ | -| ۲ هسته | ۲ گیگ | ۱–۲ | ۲۰۰ | -| ۴ هسته | ۴ گیگ+ | ۲–۳ | ۴۰۰ | -| ۸+ هسته | ۸ گیگ+ | ۳–۵ | ۸۰۰ | - -## ارتقا از نسخه 1.0.x - -فقط اسکریپت جدید را اجرا کنید. وقتی سوال پرسیده شد، گزینه **«Open management menu»** را انتخاب کنید. کانتینر موجود شما به صورت خودکار شناسایی می‌شود. نیازی به نصب مجدد نیست. - -## پیش‌نیازها - -- سرور لینوکس -- دسترسی root یا sudo -- اتصال اینترنت -- حداقل ۵۱۲ مگابایت رم (۱ گیگ+ برای چند کانتینر پیشنهاد می‌شود) - -## نحوه عملکرد - -1. **تشخیص** — شناسایی توزیع لینوکس و سیستم init -2. **نصب داکر** — در صورت نبود، داکر نصب می‌شود -3. **بررسی سخت‌افزار** — تشخیص CPU و RAM و پیشنهاد تعداد کانتینر -4. **راه‌اندازی کانتینر** — دانلود و اجرای ایمیج رسمی سایفون -5. **پیکربندی سرویس** — تنظیم سرویس خودکار (systemd/OpenRC/SysVinit) -6. **سرویس ردیاب** — شروع ردیاب ترافیک پس‌زمینه -7. **نصب CLI** — ایجاد دستور مدیریت `conduit` - -## دریافت پاداش (توکن OAT) - -اپراتورهای نود کاندوییت می‌توانند با مشارکت در شبکه سایفون توکن OAT کسب کنند. مراحل دریافت پاداش: - -1. **اپلیکیشن Ryve** را روی گوشی نصب کنید -2. **یک کیف پول کریپتو** در اپلیکیشن بسازید -3. **کانتینرهای خود را لینک کنید** با اسکن QR کد: - - از منو تنظیمات: **گزینه ۶ ← نمایش QR کد و شناسه کاندوییت** - - از مدیریت کانتینرها: کلید **[q]** را بزنید - - CLI: `conduit qr` -4. **QR کد را اسکن کنید** با اپلیکیشن Ryve تا نود شما لینک شود -5. **مانیتور و کسب درآمد** — اپلیکیشن فعالیت ۴۸ ساعت اخیر و توکن‌های OAT را نمایش می‌دهد - -> هر کانتینر شناسه و QR کد منحصر به فرد خود را دارد. اگر چند کانتینر اجرا می‌کنید، باید هر کدام را جداگانه لینک کنید. - -## امنیت - -- **پشتیبان‌گیری امن**: کلیدهای هویت نود با دسترسی محدود (600) ذخیره می‌شوند -- **بدون تلمتری**: هیچ داده‌ای جمع‌آوری یا ارسال نمی‌شود -- **ردیابی محلی**: آمار ترافیک فقط به صورت محلی ذخیره شده و هرگز ارسال نمی‌شود - -
+- **Telegram Optional**: Bot notifications are opt-in only, zero resources used if disabled --- @@ -394,6 +156,8 @@ MIT License Pull requests welcome. For major changes, open an issue first. +This is a **beta release** — please report any issues. + ## Links - [Psiphon](https://psiphon.ca/) diff --git a/conduit.sh b/conduit.sh index 97a10f7..e07985f 100644 --- a/conduit.sh +++ b/conduit.sh @@ -31,7 +31,7 @@ if [ -z "$BASH_VERSION" ]; then exit 1 fi -VERSION="1.1" +VERSION="1.2-Beta" CONDUIT_IMAGE="ghcr.io/ssmirr/conduit/conduit:latest" INSTALL_DIR="${INSTALL_DIR:-/opt/conduit}" BACKUP_DIR="$INSTALL_DIR/backups" @@ -679,6 +679,15 @@ run_conduit() { save_settings_install() { mkdir -p "$INSTALL_DIR" + # Preserve existing Telegram settings on reinstall + local _tg_token="" _tg_chat="" _tg_interval="6" _tg_enabled="false" + if [ -f "$INSTALL_DIR/settings.conf" ]; then + source "$INSTALL_DIR/settings.conf" 2>/dev/null + _tg_token="${TELEGRAM_BOT_TOKEN:-}" + _tg_chat="${TELEGRAM_CHAT_ID:-}" + _tg_interval="${TELEGRAM_INTERVAL:-6}" + _tg_enabled="${TELEGRAM_ENABLED:-false}" + fi cat > "$INSTALL_DIR/settings.conf" << EOF MAX_CLIENTS=$MAX_CLIENTS BANDWIDTH=$BANDWIDTH @@ -688,6 +697,10 @@ DATA_CAP_IFACE= DATA_CAP_BASELINE_RX=0 DATA_CAP_BASELINE_TX=0 DATA_CAP_PRIOR_USAGE=0 +TELEGRAM_BOT_TOKEN="$_tg_token" +TELEGRAM_CHAT_ID="$_tg_chat" +TELEGRAM_INTERVAL=$_tg_interval +TELEGRAM_ENABLED=$_tg_enabled EOF chmod 600 "$INSTALL_DIR/settings.conf" 2>/dev/null || true @@ -810,7 +823,7 @@ create_management_script() { # Reference: https://github.com/ssmirr/conduit/releases/latest # -VERSION="1.1" +VERSION="1.2-Beta" INSTALL_DIR="REPLACE_ME_INSTALL_DIR" BACKUP_DIR="$INSTALL_DIR/backups" CONDUIT_IMAGE="ghcr.io/ssmirr/conduit/conduit:latest" @@ -835,6 +848,10 @@ DATA_CAP_IFACE=${DATA_CAP_IFACE:-} DATA_CAP_BASELINE_RX=${DATA_CAP_BASELINE_RX:-0} DATA_CAP_BASELINE_TX=${DATA_CAP_BASELINE_TX:-0} DATA_CAP_PRIOR_USAGE=${DATA_CAP_PRIOR_USAGE:-0} +TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} +TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-} +TELEGRAM_INTERVAL=${TELEGRAM_INTERVAL:-6} +TELEGRAM_ENABLED=${TELEGRAM_ENABLED:-false} # Ensure we're running as root if [ "$EUID" -ne 0 ]; then @@ -1155,7 +1172,7 @@ show_dashboard() { [ "$pct" -gt 100 ] && pct=100 local bl=$((pct / 20)); [ "$bl" -lt 1 ] && bl=1; [ "$bl" -gt 5 ] && bl=5 local bf=""; local bp=""; for ((bi=0; bi/dev/null; then + echo "0" + elif [ "$n" -ge 1000000 ]; then + awk "BEGIN {printf \"%.1fM\", $n/1000000}" + elif [ "$n" -ge 1000 ]; then + awk "BEGIN {printf \"%.1fK\", $n/1000}" + else + echo "$n" + fi +} + # Background tracker helper is_tracker_active() { if command -v systemctl &>/dev/null; then @@ -1689,6 +1719,12 @@ check_stuck_containers() { if docker restart "$cname" >/dev/null 2>&1; then CONTAINER_LAST_RESTART[$cname]=$now CONTAINER_LAST_ACTIVE[$cname]=$now + # Send Telegram alert if enabled + if [ "$TELEGRAM_ENABLED" = "true" ] && [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then + local safe_cname=$(escape_telegram_markdown "$cname") + telegram_send_message "⚠️ *Conduit Alert* +Container ${safe_cname} was stuck (no peers for $((idle_time/3600))h) and has been auto-restarted." + fi fi fi done @@ -2004,7 +2040,7 @@ show_advanced_stats() { fi local tstat="${RED}Off${NC}"; is_tracker_active && tstat="${GREEN}On${NC}" - printf "${CYAN}║${NC} Tracker: %b Clients: ${GREEN}%d${NC} Unique IPs: ${YELLOW}%d${NC} In: ${GREEN}%s${NC} Out: ${YELLOW}%s${NC}\033[K\n" "$tstat" "$total_conn" "$total_active" "$(format_bytes $total_in)" "$(format_bytes $total_out)" + printf "${CYAN}║${NC} Tracker: %b Clients: ${GREEN}%s${NC} Unique IPs: ${YELLOW}%s${NC} In: ${GREEN}%s${NC} Out: ${YELLOW}%s${NC}\033[K\n" "$tstat" "$(format_number $total_conn)" "$(format_number $total_active)" "$(format_bytes $total_in)" "$(format_bytes $total_out)" # TOP 5 by Unique IPs (from tracker) echo -e "${CYAN}╠─── ${CYAN}TOP 5 BY UNIQUE IPs${NC} ${DIM}(tracked)${NC}\033[K" @@ -2016,7 +2052,7 @@ show_advanced_stats() { local pct=$((peers * 100 / total_conn)) local blen=$((pct / 8)); [ "$blen" -lt 1 ] && blen=1; [ "$blen" -gt 14 ] && blen=14 local bfill=""; for ((i=0; i/dev/null + systemctl daemon-reload 2>/dev/null || true echo "" echo -e "${RED}╔═══════════════════════════════════════════════════════════════════╗${NC}" echo -e "${RED}║ ⚠️ UNINSTALL CONDUIT ║${NC}" @@ -3429,6 +3468,10 @@ DATA_CAP_IFACE=$DATA_CAP_IFACE DATA_CAP_BASELINE_RX=$DATA_CAP_BASELINE_RX DATA_CAP_BASELINE_TX=$DATA_CAP_BASELINE_TX DATA_CAP_PRIOR_USAGE=${DATA_CAP_PRIOR_USAGE:-0} +TELEGRAM_BOT_TOKEN="$TELEGRAM_BOT_TOKEN" +TELEGRAM_CHAT_ID="$TELEGRAM_CHAT_ID" +TELEGRAM_INTERVAL=${TELEGRAM_INTERVAL:-6} +TELEGRAM_ENABLED=${TELEGRAM_ENABLED:-false} EOF # Save per-container overrides for i in $(seq 1 5); do @@ -3440,6 +3483,452 @@ EOF chmod 600 "$INSTALL_DIR/settings.conf" 2>/dev/null || true } +# ─── Telegram Bot Functions ─────────────────────────────────────────────────── + +escape_telegram_markdown() { + local text="$1" + text="${text//\\/\\\\}" + text="${text//\*/\\*}" + text="${text//_/\\_}" + text="${text//\`/\\\`}" + text="${text//\[/\\[}" + text="${text//\]/\\]}" + echo "$text" +} + +telegram_send_message() { + local message="$1" + { [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; } && return 1 + local response + response=$(curl -s --max-time 10 --max-filesize 1048576 -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=$TELEGRAM_CHAT_ID" \ + --data-urlencode "text=$message" \ + --data-urlencode "parse_mode=Markdown" 2>/dev/null) + [ $? -ne 0 ] && return 1 + echo "$response" | grep -q '"ok":true' && return 0 + return 1 +} + +telegram_test_message() { + local interval_label="${TELEGRAM_INTERVAL:-6}" + local report=$(telegram_build_report) + local message="✅ *Conduit Manager Connected!* + +🔗 *What is Psiphon Conduit?* +You are running a Psiphon relay node that helps people in censored regions access the open internet. + +📬 *What this bot sends you every ${interval_label}h:* +• Container status & uptime +• Connected peers count +• Upload & download totals +• CPU & RAM usage +• Data cap usage (if set) +• Top countries being served + +⚠️ *Alerts:* +If a container gets stuck and is auto-restarted, you will receive an immediate alert. + +━━━━━━━━━━━━━━━━━━━━ +📊 *Your first report:* +━━━━━━━━━━━━━━━━━━━━ + +${report}" + telegram_send_message "$message" +} + +telegram_get_chat_id() { + local response + response=$(curl -s --max-time 10 --max-filesize 1048576 "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates" 2>/dev/null) + [ -z "$response" ] && return 1 + # Verify API returned success + echo "$response" | grep -q '"ok":true' || return 1 + # Extract chat id: find "message"..."chat":{"id":NUMBER pattern + # Use python if available for reliable JSON parsing, fall back to grep + local chat_id="" + if command -v python3 &>/dev/null; then + chat_id=$(python3 -c " +import json,sys +try: + d=json.loads(sys.stdin.read()) + msgs=d.get('result',[]) + if msgs: + print(msgs[-1]['message']['chat']['id']) +except: pass +" <<< "$response" 2>/dev/null) + fi + # Fallback: POSIX-compatible grep extraction + if [ -z "$chat_id" ]; then + chat_id=$(echo "$response" | grep -o '"chat"[[:space:]]*:[[:space:]]*{[[:space:]]*"id"[[:space:]]*:[[:space:]]*-*[0-9]*' | grep -o -- '-*[0-9]*$' | tail -1 2>/dev/null) + fi + if [ -n "$chat_id" ]; then + # Validate chat_id is numeric (with optional leading minus for groups) + if ! echo "$chat_id" | grep -qE '^-?[0-9]+$'; then + return 1 + fi + TELEGRAM_CHAT_ID="$chat_id" + return 0 + fi + return 1 +} + +telegram_build_report() { + local report="📊 *Conduit Status Report*" + report+=$'\n' + report+="🕐 $(date '+%Y-%m-%d %H:%M %Z')" + report+=$'\n' + report+=$'\n' + + # Container status & uptime (check all containers, use earliest start) + local running_count=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -c "^conduit" || echo 0) + local total=$CONTAINER_COUNT + if [ "$running_count" -gt 0 ]; then + local earliest_start="" + for i in $(seq 1 $CONTAINER_COUNT); do + local cname=$(get_container_name $i) + local started=$(docker inspect --format='{{.State.StartedAt}}' "$cname" 2>/dev/null | cut -d'.' -f1) + if [ -n "$started" ]; then + local se=$(date -d "$started" +%s 2>/dev/null || echo 0) + if [ -z "$earliest_start" ] || [ "$se" -lt "$earliest_start" ] 2>/dev/null; then + earliest_start=$se + fi + fi + done + if [ -n "$earliest_start" ] && [ "$earliest_start" -gt 0 ] 2>/dev/null; then + local now=$(date +%s) + local up=$((now - earliest_start)) + local days=$((up / 86400)) + local hours=$(( (up % 86400) / 3600 )) + local mins=$(( (up % 3600) / 60 )) + if [ "$days" -gt 0 ]; then + report+="⏱ Uptime: ${days}d ${hours}h ${mins}m" + else + report+="⏱ Uptime: ${hours}h ${mins}m" + fi + report+=$'\n' + fi + fi + report+="📦 Containers: ${running_count}/${total} running" + report+=$'\n' + + # Connected peers (use awk like show_status does) + local total_peers=0 + for i in $(seq 1 $CONTAINER_COUNT); do + local cname=$(get_container_name $i) + local last_stat=$(docker logs --tail 50 "$cname" 2>&1 | grep "\[STATS\]" | tail -1) + local peers=$(echo "$last_stat" | awk '{for(j=1;j<=NF;j++){if($j=="Connected:") print $(j+1)+0}}' | head -1) + total_peers=$((total_peers + ${peers:-0})) + done + report+="👥 Peers: ${total_peers} connected" + report+=$'\n' + + # CPU / RAM (normalize CPU by core count like dashboard) + local stats=$(get_container_stats) + local raw_cpu=$(echo "$stats" | awk '{print $1}') + local cores=$(get_cpu_cores) + local cpu=$(awk "BEGIN {printf \"%.1f%%\", ${raw_cpu%\%} / $cores}" 2>/dev/null || echo "$raw_cpu") + local ram=$(echo "$stats" | awk '{print $2, $3, $4}') + cpu=$(escape_telegram_markdown "$cpu") + ram=$(escape_telegram_markdown "$ram") + report+="🖥 CPU: ${cpu} | RAM: ${ram}" + report+=$'\n' + + # Data usage + if [ "$DATA_CAP_GB" -gt 0 ] 2>/dev/null; then + local usage=$(get_data_usage 2>/dev/null) + local used_rx=$(echo "$usage" | awk '{print $1}') + local used_tx=$(echo "$usage" | awk '{print $2}') + local total_used=$(( ${used_rx:-0} + ${used_tx:-0} + ${DATA_CAP_PRIOR_USAGE:-0} )) + local used_gb=$(awk "BEGIN {printf \"%.2f\", $total_used/1073741824}" 2>/dev/null || echo "0") + report+="📈 Data: ${used_gb} GB / ${DATA_CAP_GB} GB" + report+=$'\n' + fi + + # Top countries from cumulative_data (field 3 = upload bytes, matching dashboard) + local data_file="$INSTALL_DIR/traffic_stats/cumulative_data" + if [ -s "$data_file" ]; then + local top_countries + top_countries=$(awk -F'|' '{if($1!="" && $3+0>0) bytes[$1]+=$3+0} END{for(c in bytes) print bytes[c]"|"c}' "$data_file" 2>/dev/null | sort -t'|' -k1 -nr | head -3) + if [ -n "$top_countries" ]; then + report+="🌍 Top countries:" + report+=$'\n' + while IFS='|' read -r bytes country; do + [ -z "$country" ] && continue + local safe_country=$(escape_telegram_markdown "$country") + local fmt=$(format_bytes "$bytes" 2>/dev/null || echo "${bytes} B") + report+=" • ${safe_country} (${fmt})" + report+=$'\n' + done <<< "$top_countries" + fi + fi + + # Active clients from tracker_snapshot + local snapshot_file="$INSTALL_DIR/traffic_stats/tracker_snapshot" + if [ -s "$snapshot_file" ]; then + local active_clients=$(wc -l < "$snapshot_file" 2>/dev/null || echo 0) + report+="📡 Active clients: ${active_clients}" + report+=$'\n' + fi + + # Total bandwidth served from cumulative_data + if [ -s "$data_file" ]; then + local total_bw + total_bw=$(awk -F'|' '{s+=$2+0; s+=$3+0} END{printf "%.0f", s}' "$data_file" 2>/dev/null || echo 0) + if [ "${total_bw:-0}" -gt 0 ] 2>/dev/null; then + local total_bw_fmt=$(format_bytes "$total_bw" 2>/dev/null || echo "${total_bw} B") + report+="📊 Total bandwidth served: ${total_bw_fmt}" + report+=$'\n' + fi + fi + + echo "$report" +} + +telegram_generate_notify_script() { + cat > "$INSTALL_DIR/conduit-telegram.sh" << 'TGEOF' +#!/bin/bash +# Conduit Telegram Notification Service +# Runs as a systemd service, sends periodic status reports + +INSTALL_DIR="/opt/conduit" + +[ -f "$INSTALL_DIR/settings.conf" ] && source "$INSTALL_DIR/settings.conf" + +# Exit if not configured +[ "$TELEGRAM_ENABLED" != "true" ] && exit 0 +[ -z "$TELEGRAM_BOT_TOKEN" ] && exit 0 +[ -z "$TELEGRAM_CHAT_ID" ] && exit 0 + +telegram_send() { + local message="$1" + curl -s --max-time 10 --max-filesize 1048576 -X POST \ + "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=$TELEGRAM_CHAT_ID" \ + --data-urlencode "text=$message" \ + --data-urlencode "parse_mode=Markdown" >/dev/null 2>&1 +} + +escape_md() { + local text="$1" + text="${text//\\/\\\\}" + text="${text//\*/\\*}" + text="${text//_/\\_}" + text="${text//\`/\\\`}" + text="${text//\[/\\[}" + text="${text//\]/\\]}" + echo "$text" +} + +get_container_name() { + local i=$1 + if [ "$i" -le 1 ]; then + echo "conduit" + else + echo "conduit${i}" + fi +} + +get_cpu_cores() { + local cores=1 + if command -v nproc &>/dev/null; then + cores=$(nproc) + elif [ -f /proc/cpuinfo ]; then + cores=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null || echo 1) + fi + [ "$cores" -lt 1 ] 2>/dev/null && cores=1 + echo "$cores" +} + +build_report() { + local report="📊 *Conduit Status Report*" + report+=$'\n' + report+="🕐 $(date '+%Y-%m-%d %H:%M %Z')" + report+=$'\n' + report+=$'\n' + + # Container status + uptime + local running=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -c "^conduit" || echo 0) + local total=${CONTAINER_COUNT:-1} + report+="📦 Containers: ${running}/${total} running" + report+=$'\n' + + # Uptime from earliest container + local earliest_start="" + for i in $(seq 1 ${CONTAINER_COUNT:-1}); do + local cname=$(get_container_name $i) + local started=$(docker inspect --format='{{.State.StartedAt}}' "$cname" 2>/dev/null) + [ -z "$started" ] && continue + local se=$(date -d "$started" +%s 2>/dev/null || echo 0) + if [ -z "$earliest_start" ] || [ "$se" -lt "$earliest_start" ] 2>/dev/null; then + earliest_start=$se + fi + done + if [ -n "$earliest_start" ] && [ "$earliest_start" -gt 0 ] 2>/dev/null; then + local now=$(date +%s) + local diff=$((now - earliest_start)) + local days=$((diff / 86400)) + local hours=$(( (diff % 86400) / 3600 )) + local mins=$(( (diff % 3600) / 60 )) + report+="⏱ Uptime: ${days}d ${hours}h ${mins}m" + report+=$'\n' + fi + + # Peers + local total_peers=0 + for i in $(seq 1 ${CONTAINER_COUNT:-1}); do + local cname=$(get_container_name $i) + local last_stat=$(docker logs --tail 50 "$cname" 2>&1 | grep "\[STATS\]" | tail -1) + local peers=$(echo "$last_stat" | awk '{for(j=1;j<=NF;j++){if($j=="Connected:") print $(j+1)+0}}' | head -1) + total_peers=$((total_peers + ${peers:-0})) + done + report+="👥 Peers: ${total_peers} connected" + report+=$'\n' + + # Active unique clients + local snapshot_file="$INSTALL_DIR/traffic_stats/tracker_snapshot" + if [ -s "$snapshot_file" ]; then + local active_clients=$(wc -l < "$snapshot_file" 2>/dev/null || echo 0) + report+="👤 Active clients: ${active_clients} unique IPs" + report+=$'\n' + fi + + # Total bandwidth served (all-time from cumulative_data) + local data_file_bw="$INSTALL_DIR/traffic_stats/cumulative_data" + if [ -s "$data_file_bw" ]; then + local total_bytes=$(awk -F'|' '{s+=$2+$3} END{print s+0}' "$data_file_bw" 2>/dev/null) + local total_served="" + if [ "${total_bytes:-0}" -gt 0 ] 2>/dev/null; then + total_served=$(awk "BEGIN {b=$total_bytes; if(b>1099511627776) printf \"%.2f TB\",b/1099511627776; else if(b>1073741824) printf \"%.2f GB\",b/1073741824; else printf \"%.1f MB\",b/1048576}" 2>/dev/null) + report+="📡 Total served: ${total_served}" + report+=$'\n' + fi + fi + + # CPU / RAM + local stats=$(docker stats --no-stream --format "{{.CPUPerc}} {{.MemUsage}}" $(docker ps --format '{{.Names}}' 2>/dev/null | grep "^conduit") 2>/dev/null | head -1) + local raw_cpu=$(echo "$stats" | awk '{print $1}') + local cores=$(get_cpu_cores) + local cpu=$(awk "BEGIN {printf \"%.1f%%\", ${raw_cpu%\%} / $cores}" 2>/dev/null || echo "$raw_cpu") + local ram=$(echo "$stats" | awk '{print $2, $3, $4}') + cpu=$(escape_md "$cpu") + ram=$(escape_md "$ram") + report+="🖥 CPU: ${cpu} | RAM: ${ram}" + report+=$'\n' + + # Data usage + if [ "${DATA_CAP_GB:-0}" -gt 0 ] 2>/dev/null; then + local iface="${DATA_CAP_IFACE:-eth0}" + local rx=$(cat /sys/class/net/$iface/statistics/rx_bytes 2>/dev/null || echo 0) + local tx=$(cat /sys/class/net/$iface/statistics/tx_bytes 2>/dev/null || echo 0) + local total_used=$(( rx + tx + ${DATA_CAP_PRIOR_USAGE:-0} )) + local used_gb=$(awk "BEGIN {printf \"%.2f\", $total_used/1073741824}" 2>/dev/null || echo "0") + report+="📈 Data: ${used_gb} GB / ${DATA_CAP_GB} GB" + report+=$'\n' + fi + + # Top countries + local data_file="$INSTALL_DIR/traffic_stats/cumulative_data" + if [ -s "$data_file" ]; then + local top_countries + top_countries=$(awk -F'|' '{if($1!="" && $3+0>0) bytes[$1]+=$3+0} END{for(c in bytes) print bytes[c]"|"c}' "$data_file" 2>/dev/null | sort -t'|' -k1 -nr | head -3) + if [ -n "$top_countries" ]; then + report+="🌍 Top countries:" + report+=$'\n' + local total_upload=$(awk -F'|' '{s+=$3+0} END{print s+0}' "$data_file" 2>/dev/null) + while IFS='|' read -r bytes country; do + [ -z "$country" ] && continue + local pct=0 + [ "$total_upload" -gt 0 ] 2>/dev/null && pct=$(awk "BEGIN {printf \"%.0f\", ($bytes/$total_upload)*100}" 2>/dev/null || echo 0) + local safe_country=$(escape_md "$country") + local fmt=$(awk "BEGIN {b=$bytes; if(b>1073741824) printf \"%.1f GB\",b/1073741824; else if(b>1048576) printf \"%.1f MB\",b/1048576; else printf \"%.1f KB\",b/1024}" 2>/dev/null) + report+=" • ${safe_country}: ${pct}% (${fmt})" + report+=$'\n' + done <<< "$top_countries" + fi + fi + + echo "$report" +} + +# Main loop +elapsed=0 +interval_secs=$(( ${TELEGRAM_INTERVAL:-6} * 3600 )) + +while true; do + sleep 60 + elapsed=$((elapsed + 60)) + + # Re-read settings + [ -f "$INSTALL_DIR/settings.conf" ] && source "$INSTALL_DIR/settings.conf" + + # Exit if disabled + [ "$TELEGRAM_ENABLED" != "true" ] && exit 0 + [ -z "$TELEGRAM_BOT_TOKEN" ] && exit 0 + + # Update interval + interval_secs=$(( ${TELEGRAM_INTERVAL:-6} * 3600 )) + + if [ "$elapsed" -ge "$interval_secs" ]; then + report=$(build_report) + telegram_send "$report" + elapsed=0 + fi +done +TGEOF + chmod 700 "$INSTALL_DIR/conduit-telegram.sh" +} + +setup_telegram_service() { + telegram_generate_notify_script + if command -v systemctl &>/dev/null; then + cat > /etc/systemd/system/conduit-telegram.service << EOF +[Unit] +Description=Conduit Telegram Notifications +After=network.target docker.service +Requires=docker.service + +[Service] +Type=simple +ExecStart=/bin/bash $INSTALL_DIR/conduit-telegram.sh +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +EOF + systemctl daemon-reload 2>/dev/null || true + systemctl enable conduit-telegram.service 2>/dev/null || true + systemctl restart conduit-telegram.service 2>/dev/null || true + fi +} + +telegram_stop_notify() { + if command -v systemctl &>/dev/null && [ -f /etc/systemd/system/conduit-telegram.service ]; then + systemctl stop conduit-telegram.service 2>/dev/null || true + fi + # Also clean up legacy PID-based loop if present + if [ -f "$INSTALL_DIR/telegram_notify.pid" ]; then + local pid=$(cat "$INSTALL_DIR/telegram_notify.pid" 2>/dev/null) + if echo "$pid" | grep -qE '^[0-9]+$' && kill -0 "$pid" 2>/dev/null; then + kill -- -"$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true + fi + rm -f "$INSTALL_DIR/telegram_notify.pid" + fi +} + +telegram_start_notify() { + telegram_stop_notify + if [ "$TELEGRAM_ENABLED" = "true" ] && [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then + setup_telegram_service + fi +} + +telegram_disable_service() { + if command -v systemctl &>/dev/null && [ -f /etc/systemd/system/conduit-telegram.service ]; then + systemctl stop conduit-telegram.service 2>/dev/null || true + systemctl disable conduit-telegram.service 2>/dev/null || true + fi +} + show_about() { clear echo -e "${CYAN}══════════════════════════════════════════════════════════════════${NC}" @@ -3498,6 +3987,8 @@ show_settings_menu() { echo -e " 8. 📖 About Conduit" echo "" echo -e " 9. 🔄 Reset tracker data" + echo -e " t. 📲 Telegram Notifications" + echo -e "" echo -e " u. 🗑️ Uninstall" echo -e " 0. ← Back to main menu" echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" @@ -3570,6 +4061,10 @@ show_settings_menu() { read -n 1 -s -r -p "Press any key to return..." < /dev/tty || true redraw=true ;; + t) + show_telegram_menu + redraw=true + ;; u) uninstall_all exit 0 @@ -3586,6 +4081,227 @@ show_settings_menu() { done } +show_telegram_menu() { + while true; do + # Reload settings from disk to reflect any changes + [ -f "$INSTALL_DIR/settings.conf" ] && source "$INSTALL_DIR/settings.conf" + clear + print_header + if [ "$TELEGRAM_ENABLED" = "true" ] && [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then + # Already configured — show management menu + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo -e "${CYAN} TELEGRAM NOTIFICATIONS${NC}" + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo "" + echo -e " Status: ${GREEN}✓ Enabled${NC} (every ${TELEGRAM_INTERVAL}h)" + echo "" + echo -e " 1. 📩 Send test message" + echo -e " 2. ⏱ Change interval" + echo -e " 3. ❌ Disable notifications" + echo -e " 4. 🔄 Reconfigure (new bot/chat)" + echo -e " 0. ← Back" + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo "" + read -p " Enter choice: " tchoice < /dev/tty || return + case "$tchoice" in + 1) + echo "" + echo -ne " Sending test message... " + if telegram_test_message; then + echo -e "${GREEN}✓ Sent!${NC}" + else + echo -e "${RED}✗ Failed. Check your token/chat ID.${NC}" + fi + read -n 1 -s -r -p " Press any key..." < /dev/tty || true + ;; + 2) + echo "" + echo -e " Select notification interval:" + echo -e " 1. Every 1 hour" + echo -e " 2. Every 3 hours" + echo -e " 3. Every 6 hours (recommended)" + echo -e " 4. Every 12 hours" + echo -e " 5. Every 24 hours" + echo "" + read -p " Choice [1-5]: " ichoice < /dev/tty || true + case "$ichoice" in + 1) TELEGRAM_INTERVAL=1 ;; + 2) TELEGRAM_INTERVAL=3 ;; + 3) TELEGRAM_INTERVAL=6 ;; + 4) TELEGRAM_INTERVAL=12 ;; + 5) TELEGRAM_INTERVAL=24 ;; + *) echo -e " ${RED}Invalid choice${NC}"; read -n 1 -s -r -p " Press any key..." < /dev/tty || true; continue ;; + esac + save_settings + telegram_start_notify + echo -e " ${GREEN}✓ Interval set to every ${TELEGRAM_INTERVAL} hours${NC}" + read -n 1 -s -r -p " Press any key..." < /dev/tty || true + ;; + 3) + TELEGRAM_ENABLED=false + save_settings + telegram_disable_service + echo -e " ${GREEN}✓ Telegram notifications disabled${NC}" + read -n 1 -s -r -p " Press any key..." < /dev/tty || true + ;; + 4) + telegram_setup_wizard + ;; + 0) return ;; + esac + elif [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then + # Disabled but credentials exist — offer re-enable + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo -e "${CYAN} TELEGRAM NOTIFICATIONS${NC}" + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo "" + echo -e " Status: ${RED}✗ Disabled${NC} (credentials saved)" + echo "" + echo -e " 1. ✅ Re-enable notifications (every ${TELEGRAM_INTERVAL:-6}h)" + echo -e " 2. 🔄 Reconfigure (new bot/chat)" + echo -e " 0. ← Back" + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo "" + read -p " Enter choice: " tchoice < /dev/tty || return + case "$tchoice" in + 1) + TELEGRAM_ENABLED=true + save_settings + telegram_start_notify + echo -e " ${GREEN}✓ Telegram notifications re-enabled${NC}" + read -n 1 -s -r -p " Press any key..." < /dev/tty || true + ;; + 2) + telegram_setup_wizard + ;; + 0) return ;; + esac + else + # Not configured — run wizard + telegram_setup_wizard + return + fi + done +} + +telegram_setup_wizard() { + # Save and restore variables on Ctrl+C + local _saved_token="$TELEGRAM_BOT_TOKEN" + local _saved_chatid="$TELEGRAM_CHAT_ID" + local _saved_interval="$TELEGRAM_INTERVAL" + local _saved_enabled="$TELEGRAM_ENABLED" + trap 'TELEGRAM_BOT_TOKEN="$_saved_token"; TELEGRAM_CHAT_ID="$_saved_chatid"; TELEGRAM_INTERVAL="$_saved_interval"; TELEGRAM_ENABLED="$_saved_enabled"; trap - SIGINT; echo; return' SIGINT + clear + echo -e "${CYAN}══════════════════════════════════════════════════════════════════${NC}" + echo -e " ${BOLD}TELEGRAM NOTIFICATIONS SETUP${NC}" + echo -e "${CYAN}══════════════════════════════════════════════════════════════════${NC}" + echo "" + echo -e " ${BOLD}Step 1: Create a Telegram Bot${NC}" + echo -e " ${CYAN}─────────────────────────────${NC}" + echo -e " 1. Open Telegram and search for ${BOLD}@BotFather${NC}" + echo -e " 2. Send ${YELLOW}/newbot${NC}" + echo -e " 3. Choose a name (e.g. \"My Conduit Monitor\")" + echo -e " 4. Choose a username (e.g. \"my_conduit_bot\")" + echo -e " 5. BotFather will give you a token like:" + echo -e " ${YELLOW}123456789:ABCdefGHIjklMNOpqrsTUVwxyz${NC}" + echo "" + echo -e " ${BOLD}Recommended:${NC} Send these commands to @BotFather:" + echo -e " ${YELLOW}/setjoingroups${NC} → Disable (prevents adding to groups)" + echo -e " ${YELLOW}/setprivacy${NC} → Enable (limits message access)" + echo "" + echo -e " ${YELLOW}⚠ OPSEC Note:${NC} Enabling Telegram notifications creates" + echo -e " outbound connections to api.telegram.org from this server." + echo -e " This traffic may be visible to your network provider." + echo "" + read -s -p " Enter your bot token: " TELEGRAM_BOT_TOKEN < /dev/tty || { trap - SIGINT; TELEGRAM_BOT_TOKEN="$_saved_token"; return; } + echo "" + # Trim whitespace + TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN## }" + TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN%% }" + if [ -z "$TELEGRAM_BOT_TOKEN" ]; then + echo -e " ${RED}No token entered. Setup cancelled.${NC}" + read -n 1 -s -r -p " Press any key..." < /dev/tty || true + trap - SIGINT; return + fi + + # Validate token format + if ! echo "$TELEGRAM_BOT_TOKEN" | grep -qE '^[0-9]+:[A-Za-z0-9_-]+$'; then + echo -e " ${RED}Invalid token format. Should be like: 123456789:ABCdefGHI...${NC}" + TELEGRAM_BOT_TOKEN="$_saved_token"; TELEGRAM_CHAT_ID="$_saved_chatid"; TELEGRAM_INTERVAL="$_saved_interval"; TELEGRAM_ENABLED="$_saved_enabled" + read -n 1 -s -r -p " Press any key..." < /dev/tty || true + trap - SIGINT; return + fi + + echo "" + echo -e " ${BOLD}Step 2: Get Your Chat ID${NC}" + echo -e " ${CYAN}────────────────────────${NC}" + echo -e " 1. Open your new bot in Telegram" + echo -e " 2. Send it the message: ${YELLOW}/start${NC}" + echo -e " 3. Press Enter here when done..." + echo "" + read -p " Press Enter after sending /start to your bot... " < /dev/tty || { trap - SIGINT; TELEGRAM_BOT_TOKEN="$_saved_token"; TELEGRAM_CHAT_ID="$_saved_chatid"; TELEGRAM_INTERVAL="$_saved_interval"; TELEGRAM_ENABLED="$_saved_enabled"; return; } + + echo -ne " Detecting chat ID... " + local attempts=0 + TELEGRAM_CHAT_ID="" + while [ $attempts -lt 3 ] && [ -z "$TELEGRAM_CHAT_ID" ]; do + if telegram_get_chat_id; then + break + fi + attempts=$((attempts + 1)) + sleep 2 + done + + if [ -z "$TELEGRAM_CHAT_ID" ]; then + echo -e "${RED}✗ Could not detect chat ID${NC}" + echo -e " Make sure you sent /start to the bot and try again." + TELEGRAM_BOT_TOKEN="$_saved_token"; TELEGRAM_CHAT_ID="$_saved_chatid"; TELEGRAM_INTERVAL="$_saved_interval"; TELEGRAM_ENABLED="$_saved_enabled" + read -n 1 -s -r -p " Press any key..." < /dev/tty || true + trap - SIGINT; return + fi + echo -e "${GREEN}✓ Chat ID: ${TELEGRAM_CHAT_ID}${NC}" + + echo "" + echo -e " ${BOLD}Step 3: Notification Interval${NC}" + echo -e " ${CYAN}─────────────────────────────${NC}" + echo -e " 1. Every 1 hour" + echo -e " 2. Every 3 hours" + echo -e " 3. Every 6 hours (recommended)" + echo -e " 4. Every 12 hours" + echo -e " 5. Every 24 hours" + echo "" + read -p " Choice [1-5] (default 3): " ichoice < /dev/tty || true + case "$ichoice" in + 1) TELEGRAM_INTERVAL=1 ;; + 2) TELEGRAM_INTERVAL=3 ;; + 4) TELEGRAM_INTERVAL=12 ;; + 5) TELEGRAM_INTERVAL=24 ;; + *) TELEGRAM_INTERVAL=6 ;; + esac + + echo "" + echo -ne " Sending test message... " + if telegram_test_message; then + echo -e "${GREEN}✓ Success!${NC}" + else + echo -e "${RED}✗ Failed to send. Check your token.${NC}" + TELEGRAM_BOT_TOKEN="$_saved_token"; TELEGRAM_CHAT_ID="$_saved_chatid"; TELEGRAM_INTERVAL="$_saved_interval"; TELEGRAM_ENABLED="$_saved_enabled" + read -n 1 -s -r -p " Press any key..." < /dev/tty || true + trap - SIGINT; return + fi + + TELEGRAM_ENABLED=true + save_settings + telegram_start_notify + + trap - SIGINT + echo "" + echo -e " ${GREEN}${BOLD}✓ Telegram notifications enabled!${NC}" + echo -e " You'll receive reports every ${TELEGRAM_INTERVAL} hours." + echo "" + read -n 1 -s -r -p " Press any key to return..." < /dev/tty || true +} + show_menu() { # Auto-fix conduit.service if it's in failed state if command -v systemctl &>/dev/null; then @@ -3604,6 +4320,16 @@ show_menu() { fi fi + # Load settings (Telegram service is only started explicitly by the user via the Telegram menu) + [ -f "$INSTALL_DIR/settings.conf" ] && source "$INSTALL_DIR/settings.conf" + + # If the Telegram service is already running, regenerate the script and restart + # so it picks up any code changes from a script upgrade + if command -v systemctl &>/dev/null && systemctl is-active conduit-telegram.service &>/dev/null; then + telegram_generate_notify_script + systemctl restart conduit-telegram.service 2>/dev/null || true + fi + local redraw=true while true; do if [ "$redraw" = true ]; then @@ -4369,6 +5095,9 @@ print_summary() { #═══════════════════════════════════════════════════════════════════════ uninstall() { + telegram_disable_service + rm -f /etc/systemd/system/conduit-telegram.service 2>/dev/null + systemctl daemon-reload 2>/dev/null || true echo "" echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════════╗${NC}" echo "║ ⚠️ UNINSTALL CONDUIT "