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
```
-
+



@@ -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 "