From bd45862be386e76bcbc4c98e1c4791a57cba5610 Mon Sep 17 00:00:00 2001 From: SamNet-dev Date: Sun, 25 Jan 2026 20:48:13 -0600 Subject: [PATCH] feat: add live peer connections dashboard & #FreeIran --- LICENSE | 21 + README.md | 214 ++++++ conduit.sh | 1670 +++++++++++++++++++++++++++++++++++++++++++++++ conduitmenu.png | Bin 0 -> 39114 bytes 4 files changed, 1905 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 conduit.sh create mode 100644 conduitmenu.png diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9c60a53 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 SamNet-dev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc130bb --- /dev/null +++ b/README.md @@ -0,0 +1,214 @@ +# Conduit Manager + +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 + +```bash +curl -sL https://raw.githubusercontent.com/SamNet-dev/conduit-manager/main/conduit.sh | sudo bash +``` + +Or download and run manually: + +```bash +wget https://raw.githubusercontent.com/SamNet-dev/conduit-manager/main/conduit.sh +sudo bash conduit.sh +``` + +## Features + +- **One-Click Deployment** - Automatically installs Docker and configures everything +- **Multi-Distro Support** - Works on Ubuntu, Debian, CentOS, Fedora, Arch, Alpine, openSUSE +- **Auto-Start on Boot** - Supports systemd, OpenRC, and SysVinit +- **Live Monitoring** - Real-time connection stats with CPU/RAM monitoring +- **Easy Management** - Powerful CLI commands or interactive menu +- **Complete Uninstall** - Clean removal of all components + +![Conduit Manager Menu](conduitmenu.png) + +## Supported Distributions + +| Family | Distributions | +|--------|---------------| +| Debian | Ubuntu, Debian, Linux Mint, Pop!_OS, Kali, Raspbian | +| RHEL | CentOS, Fedora, Rocky Linux, AlmaLinux, Amazon Linux | +| Arch | Arch Linux, Manjaro, EndeavourOS | +| SUSE | openSUSE Leap, openSUSE Tumbleweed | +| Alpine | Alpine Linux | + +## CLI Reference + +After installation, use the `conduit` command: + +### Status & Monitoring +```bash +conduit status # Show current status and resource usage +conduit stats # View live statistics (real-time) +conduit logs # View raw Docker logs +``` + +### Container Management +```bash +conduit start # Start the Conduit container +conduit stop # Stop the Conduit container +conduit restart # Restart the Conduit container +``` + +### Configuration +```bash +conduit settings # Change max-clients and bandwidth +conduit menu # Open interactive management menu +``` + +### Maintenance +```bash +conduit uninstall # Remove all components +conduit help # Show help message +``` + +## Configuration Options + +| Option | Default | Range | Description | +|--------|---------|-------|-------------| +| `max-clients` | 200 | 1-1000 | Maximum concurrent proxy clients | +| `bandwidth` | 5 | 1-40, -1 | Bandwidth limit per peer (Mbps). Use -1 for unlimited. | + +**Recommended values based on server CPU:** + +| CPU Cores | Max Clients | +|-----------|-------------| +| 8+ Cores | 800 | +| 4 Cores | 400 | +| 2 Cores | 200 | +| 1 Core | 100 | + +## 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 +``` + +## Requirements + +- Linux server (any supported distribution) +- Root/sudo access +- Internet connection +- Minimum 512MB RAM (1GB+ recommended) + +## How It Works + +1. **Detection** - Identifies your Linux distribution and init system +2. **Docker Setup** - Installs Docker if not present +3. **Container Deployment** - Pulls and runs the official Psiphon Conduit image +4. **Auto-Start Configuration** - Sets up systemd/OpenRC/SysVinit service +5. **CLI Installation** - Creates the `conduit` management command + +--- + +
+ +# راهنمای فارسی - مدیریت کاندوییت + +ابزار قدرتمند برای راه‌اندازی و مدیریت نود سایفون کاندوییت روی سرورهای لینوکس. + +## نصب سریع + +دستور زیر را در ترمینال سرور اجرا کنید: + +```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 +``` + +## ویژگی‌ها + +- **نصب با یک کلیک** - داکر و تمام موارد مورد نیاز به صورت خودکار نصب می‌شود +- **پشتیبانی از توزیع‌های مختلف** - اوبونتو، دبیان، سنت‌اواس، فدورا، آرچ، آلپاین +- **راه‌اندازی خودکار** - پس از ریستارت سرور، سرویس به صورت خودکار اجرا می‌شود +- **مانیتورینگ زنده** - نمایش تعداد کاربران متصل و مصرف منابع +- **مدیریت آسان** - دستورات قدرتمند CLI یا منوی تعاملی +- **حذف کامل** - پاکسازی تمام فایل‌ها و تنظیمات + +## دستورات CLI + +### وضعیت و مانیتورینگ +```bash +conduit status # نمایش وضعیت و مصرف منابع +conduit stats # آمار زنده (لحظه‌ای) +conduit logs # لاگ‌های داکر +``` + +### مدیریت کانتینر +```bash +conduit start # شروع کانتینر +conduit stop # توقف کانتینر +conduit restart # ریستارت کانتینر +``` + +### پیکربندی +```bash +conduit settings # تغییر تنظیمات +conduit menu # منوی تعاملی +``` + +### نگهداری +```bash +conduit uninstall # حذف کامل +conduit help # راهنما +``` + +## تنظیمات + +| گزینه | پیش‌فرض | محدوده | توضیحات | +|-------|---------|--------|---------| +| `max-clients` | 200 | 1-1000 | حداکثر کاربران همزمان | +| `bandwidth` | 5 | 1-40, -1 | محدودیت پهنای باند (Mbps). برای نامحدود -1 وارد کنید. | + +**مقادیر پیشنهادی بر اساس پردازنده (CPU):** + +| تعداد هسته | حداکثر کاربران | +|------------|----------------| +| +8 هسته | 800 | +| 4 هسته | 400 | +| 2 هسته | 200 | +| 1 هسته | 100 | + +## پیش‌نیازها + +- سرور لینوکس +- دسترسی root یا sudo +- اتصال اینترنت +- حداقل 512 مگابایت رم + +
+ +--- + +## License + +MIT License + +## Contributing + +Pull requests welcome. For major changes, open an issue first. + +## Links + +- [Psiphon](https://psiphon.ca/) +- [Psiphon Conduit](https://github.com/Psiphon-Inc/conduit) diff --git a/conduit.sh b/conduit.sh new file mode 100644 index 0000000..026c184 --- /dev/null +++ b/conduit.sh @@ -0,0 +1,1670 @@ +#!/bin/bash +# +# ╔═══════════════════════════════════════════════════════════════════╗ +# ║ 🚀 PSIPHON CONDUIT MANAGER v1.0.1 ║ +# ║ ║ +# ║ One-click setup for Psiphon Conduit ║ +# ║ ║ +# ║ • Installs Docker (if needed) ║ +# ║ • Runs Conduit in Docker with live stats ║ +# ║ • Auto-start on boot via systemd/OpenRC/SysVinit ║ +# ║ • Easy management via CLI or interactive menu ║ +# ║ ║ +# ║ GitHub: https://github.com/Psiphon-Inc/conduit ║ +# ╚═══════════════════════════════════════════════════════════════════╝ +# core engine: https://github.com/Psiphon-Labs/psiphon-tunnel-core +# Usage: +# curl -sL https://raw.githubusercontent.com/SamNet-dev/conduit-manager/main/conduit.sh | sudo bash +# +# Reference: https://github.com/ssmirr/conduit/releases/tag/d8522a8 +# Conduit CLI options: +# -m, --max-clients int maximum number of proxy clients (1-1000) (default 200) +# -b, --bandwidth float bandwidth limit per peer in Mbps (1-40, or -1 for unlimited) (default 5) +# -v, --verbose increase verbosity (-v for verbose, -vv for debug) +# + +set -e + +# Ensure we're running in bash (not sh/dash) +if [ -z "$BASH_VERSION" ]; then + echo "Error: This script requires bash. Please run with: bash $0" + exit 1 +fi + +VERSION="1.0.1" +CONDUIT_IMAGE="ghcr.io/ssmirr/conduit/conduit:d8522a8" +INSTALL_DIR="/opt/conduit" +FORCE_REINSTALL=false + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +#═══════════════════════════════════════════════════════════════════════ +# Utility Functions +#═══════════════════════════════════════════════════════════════════════ + +print_header() { + echo -e "${CYAN}" + echo "╔═══════════════════════════════════════════════════════════════════╗" + echo "║ 🚀 PSIPHON CONDUIT MANAGER v${VERSION} ║" + echo "╠═══════════════════════════════════════════════════════════════════╣" + echo "║ Help users access the open internet during shutdowns ║" + echo "╚═══════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" +} + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[✓]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[!]${NC} $1" +} + +log_error() { + echo -e "${RED}[✗]${NC} $1" +} + +check_root() { + if [ "$EUID" -ne 0 ]; then + log_error "This script must be run as root (use sudo)" + exit 1 + fi +} + +detect_os() { + OS="unknown" + OS_VERSION="unknown" + OS_FAMILY="unknown" + HAS_SYSTEMD=false + PKG_MANAGER="unknown" + + # Detect OS from /etc/os-release + if [ -f /etc/os-release ]; then + . /etc/os-release + OS="$ID" + OS_VERSION="${VERSION_ID:-unknown}" + elif [ -f /etc/redhat-release ]; then + OS="rhel" + elif [ -f /etc/debian_version ]; then + OS="debian" + elif [ -f /etc/alpine-release ]; then + OS="alpine" + elif [ -f /etc/arch-release ]; then + OS="arch" + elif [ -f /etc/SuSE-release ] || [ -f /etc/SUSE-brand ]; then + OS="opensuse" + else + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + fi + + # Determine OS family and package manager + case "$OS" in + ubuntu|debian|linuxmint|pop|elementary|zorin|kali|raspbian) + OS_FAMILY="debian" + PKG_MANAGER="apt" + ;; + rhel|centos|fedora|rocky|almalinux|oracle|amazon|amzn) + OS_FAMILY="rhel" + if command -v dnf &>/dev/null; then + PKG_MANAGER="dnf" + else + PKG_MANAGER="yum" + fi + ;; + arch|manjaro|endeavouros|garuda) + OS_FAMILY="arch" + PKG_MANAGER="pacman" + ;; + opensuse|opensuse-leap|opensuse-tumbleweed|sles) + OS_FAMILY="suse" + PKG_MANAGER="zypper" + ;; + alpine) + OS_FAMILY="alpine" + PKG_MANAGER="apk" + ;; + *) + OS_FAMILY="unknown" + PKG_MANAGER="unknown" + ;; + esac + + # Check for systemd + if command -v systemctl &>/dev/null && [ -d /run/systemd/system ]; then + HAS_SYSTEMD=true + fi + + log_info "Detected: $OS ($OS_FAMILY family), Package manager: $PKG_MANAGER" +} + +install_package() { + local package="$1" + log_info "Installing $package..." + + case "$PKG_MANAGER" in + apt) + apt-get update -qq 2>/dev/null + apt-get install -y -qq "$package" 2>/dev/null + ;; + dnf) + dnf install -y -q "$package" 2>/dev/null + ;; + yum) + yum install -y -q "$package" 2>/dev/null + ;; + pacman) + pacman -Sy --noconfirm "$package" 2>/dev/null + ;; + zypper) + zypper install -y -n "$package" 2>/dev/null + ;; + apk) + apk add --no-cache "$package" 2>/dev/null + ;; + *) + log_warn "Unknown package manager. Please install $package manually." + return 1 + ;; + esac +} + +check_dependencies() { + # Check for bash + if [ "$OS_FAMILY" = "alpine" ]; then + if ! command -v bash &>/dev/null; then + log_info "Installing bash (required for this script)..." + apk add --no-cache bash 2>/dev/null + fi + fi + + # Check for curl + if ! command -v curl &>/dev/null; then + install_package curl || log_warn "Could not install curl automatically" + fi + + # Check for basic tools + if ! command -v awk &>/dev/null; then + case "$PKG_MANAGER" in + apt) install_package gawk ;; + apk) install_package gawk ;; + *) install_package awk ;; + esac + fi + + # Check for free command + if ! command -v free &>/dev/null; then + case "$PKG_MANAGER" in + apt|dnf|yum) install_package procps ;; + pacman) install_package procps-ng ;; + zypper) install_package procps ;; + apk) install_package procps ;; + esac + fi + + # Check for tput (ncurses) + if ! command -v tput &>/dev/null; then + case "$PKG_MANAGER" in + apt) install_package ncurses-bin ;; + apk) install_package ncurses ;; + *) install_package ncurses ;; + esac + fi + + # Check for tcpdump + if ! command -v tcpdump &>/dev/null; then + install_package tcpdump || log_warn "Could not install tcpdump automatically" + fi + + # Check for GeoIP tools + if ! command -v geoiplookup &>/dev/null; then + case "$PKG_MANAGER" in + apt) install_package geoip-bin ;; + dnf|yum) + # On RHEL/CentOS + if ! rpm -q epel-release &>/dev/null; then + log_info "Enabling EPEL repository for GeoIP..." + $PKG_MANAGER install -y epel-release &>/dev/null || true + fi + install_package GeoIP + ;; + pacman) install_package geoip ;; + zypper) install_package GeoIP ;; + apk) install_package geoip ;; + *) log_warn "Could not install geoiplookup automatically" ;; + esac + fi +} + +get_ram_mb() { + # Get RAM in MB + local ram="" + + # Try free command first + if command -v free &>/dev/null; then + ram=$(free -m 2>/dev/null | awk '/^Mem:/{print $2}') + fi + + # Fallback: parse /proc/meminfo + if [ -z "$ram" ] || [ "$ram" = "0" ]; then + if [ -f /proc/meminfo ]; then + local kb=$(awk '/^MemTotal:/{print $2}' /proc/meminfo 2>/dev/null) + if [ -n "$kb" ]; then + ram=$((kb / 1024)) + fi + fi + fi + + # Ensure minimum of 1 + if [ -z "$ram" ] || [ "$ram" -lt 1 ] 2>/dev/null; then + echo 1 + else + echo "$ram" + 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) + fi + + # Safety check + if [ -z "$cores" ] || [ "$cores" -lt 1 ] 2>/dev/null; then + echo 1 + else + echo "$cores" + fi +} + +calculate_recommended_clients() { + local cores=$(get_cpu_cores) + # Logic: 100 clients per CPU core, max 1000 + local recommended=$((cores * 100)) + if [ "$recommended" -gt 1000 ]; then + echo 1000 + else + echo "$recommended" + fi +} + +#═══════════════════════════════════════════════════════════════════════ +# Interactive Setup +#═══════════════════════════════════════════════════════════════════════ + +prompt_settings() { + local ram_mb=$(get_ram_mb) + local cpu_cores=$(get_cpu_cores) + local recommended=$(calculate_recommended_clients) + + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} CONDUIT CONFIGURATION ${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}" + echo "" + echo -e " ${BOLD}Server Info:${NC}" + echo -e " CPU Cores: ${GREEN}${cpu_cores}${NC}" + if [ "$ram_mb" -ge 1000 ]; then + local ram_gb=$(awk "BEGIN {printf \"%.1f\", $ram_mb/1024}") + echo -e " RAM: ${GREEN}${ram_gb} GB${NC}" + else + echo -e " RAM: ${GREEN}${ram_mb} MB${NC}" + fi + echo -e " Recommended max-clients: ${GREEN}${recommended}${NC}" + echo "" + echo -e " ${BOLD}Conduit Options:${NC}" + echo -e " ${YELLOW}--max-clients${NC} Maximum proxy clients (1-1000)" + echo -e " ${YELLOW}--bandwidth${NC} Bandwidth per peer in Mbps (1-40, or -1 for unlimited)" + echo "" + + # Max clients prompt + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + echo -e " Enter max-clients (1-1000)" + echo -e " Press Enter for recommended: ${GREEN}${recommended}${NC}" + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + read -p " max-clients: " input_clients < /dev/tty || true + + if [ -z "$input_clients" ]; then + MAX_CLIENTS=$recommended + elif [[ "$input_clients" =~ ^[0-9]+$ ]] && [ "$input_clients" -ge 1 ] && [ "$input_clients" -le 1000 ]; then + MAX_CLIENTS=$input_clients + else + log_warn "Invalid input. Using recommended: $recommended" + MAX_CLIENTS=$recommended + fi + + echo "" + + # Bandwidth prompt + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + echo -e " Do you want to set ${BOLD}UNLIMITED${NC} bandwidth? (Recommended for servers)" + echo -e " ${YELLOW}Note: High bandwidth usage may attract attention.${NC}" + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + read -p " Set unlimited bandwidth? [y/N] " unlimited_bw < /dev/tty || true + + if [[ "$unlimited_bw" =~ ^[Yy] ]]; then + BANDWIDTH="-1" + echo -e " Selected: ${GREEN}Unlimited (-1)${NC}" + else + echo "" + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + echo -e " Enter bandwidth per peer in Mbps (1-40)" + echo -e " Press Enter for default: ${GREEN}5${NC} Mbps" + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + read -p " bandwidth: " input_bandwidth < /dev/tty || true + + if [ -z "$input_bandwidth" ]; then + BANDWIDTH=5 + elif [[ "$input_bandwidth" =~ ^[0-9]+$ ]] && [ "$input_bandwidth" -ge 1 ] && [ "$input_bandwidth" -le 40 ]; then + BANDWIDTH=$input_bandwidth + elif [[ "$input_bandwidth" =~ ^[0-9]*\.[0-9]+$ ]]; then + local float_ok=$(awk -v val="$input_bandwidth" 'BEGIN { print (val >= 1 && val <= 40) ? "yes" : "no" }') + if [ "$float_ok" = "yes" ]; then + BANDWIDTH=$input_bandwidth + else + log_warn "Invalid input. Using default: 5 Mbps" + BANDWIDTH=5 + fi + else + log_warn "Invalid input. Using default: 5 Mbps" + BANDWIDTH=5 + fi + fi + + echo "" + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + echo -e " ${BOLD}Your Settings:${NC}" + echo -e " Max Clients: ${GREEN}${MAX_CLIENTS}${NC}" + if [ "$BANDWIDTH" == "-1" ]; then + echo -e " Bandwidth: ${GREEN}Unlimited${NC}" + else + echo -e " Bandwidth: ${GREEN}${BANDWIDTH}${NC} Mbps" + fi + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + echo "" + + read -p " Proceed with these settings? [Y/n] " confirm < /dev/tty || true + if [[ "$confirm" =~ ^[Nn] ]]; then + prompt_settings + fi +} + +#═══════════════════════════════════════════════════════════════════════ +# Installation Functions +#═══════════════════════════════════════════════════════════════════════ + +install_docker() { + if command -v docker &>/dev/null; then + log_success "Docker is already installed" + return 0 + fi + + log_info "Installing Docker..." + + # Alpine + if [ "$OS_FAMILY" = "alpine" ]; then + apk add --no-cache docker docker-cli-compose 2>/dev/null + rc-update add docker boot 2>/dev/null || true + service docker start 2>/dev/null || rc-service docker start 2>/dev/null || true + else + # Use official Docker install + curl -fsSL https://get.docker.com | sh + + # Enable and start Docker + if [ "$HAS_SYSTEMD" = "true" ]; then + systemctl enable docker 2>/dev/null || true + systemctl start docker 2>/dev/null || true + else + # Fallback for non-systemd (SysVinit, OpenRC, etc.) + if command -v update-rc.d &>/dev/null; then + update-rc.d docker defaults 2>/dev/null || true + elif command -v chkconfig &>/dev/null; then + chkconfig docker on 2>/dev/null || true + elif command -v rc-update &>/dev/null; then + rc-update add docker default 2>/dev/null || true + fi + service docker start 2>/dev/null || /etc/init.d/docker start 2>/dev/null || true + fi + fi + + # Wait for Docker to be ready + sleep 3 + local retries=27 + while ! docker info &>/dev/null && [ $retries -gt 0 ]; do + sleep 1 + retries=$((retries - 1)) + done + + if docker info &>/dev/null; then + log_success "Docker installed successfully" + else + log_error "Docker installation may have failed. Please check manually." + return 1 + fi +} + +run_conduit() { + log_info "Starting Conduit container..." + + # Stop existing container + docker rm -f conduit 2>/dev/null || true + + # Pull image + log_info "Pulling Conduit image ($CONDUIT_IMAGE)..." + if ! docker pull $CONDUIT_IMAGE; then + log_error "Failed to pull Conduit image. Check your internet connection." + exit 1 + fi + + # Run container with host networking + docker run -d \ + --name conduit \ + --restart unless-stopped \ + -v conduit-data:/home/conduit/data \ + --network host \ + $CONDUIT_IMAGE \ + start --max-clients "$MAX_CLIENTS" --bandwidth "$BANDWIDTH" -v + + sleep 3 + + if docker ps | grep -q conduit; then + log_success "Conduit container is running" + if [ "$BANDWIDTH" == "-1" ]; then + log_success "Settings: max-clients=$MAX_CLIENTS, bandwidth=Unlimited" + else + log_success "Settings: max-clients=$MAX_CLIENTS, bandwidth=${BANDWIDTH}Mbps" + fi + else + log_error "Conduit failed to start" + docker logs conduit 2>&1 | tail -10 + exit 1 + fi +} + +save_settings() { + mkdir -p $INSTALL_DIR + + # Save settings + cat > "$INSTALL_DIR/settings.conf" << EOF +MAX_CLIENTS=$MAX_CLIENTS +BANDWIDTH=$BANDWIDTH +EOF + + if [ ! -f "$INSTALL_DIR/settings.conf" ]; then + log_error "Failed to save settings. Check disk space and permissions." + return 1 + fi + + log_success "Settings saved" +} + +setup_autostart() { + log_info "Setting up auto-start on boot..." + + if [ "$HAS_SYSTEMD" = "true" ]; then + # Systemd-based systems + cat > /etc/systemd/system/conduit.service << 'EOF' +[Unit] +Description=Psiphon Conduit Service +After=network.target docker.service +Requires=docker.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/docker start conduit +ExecStop=/usr/bin/docker stop conduit + +[Install] +WantedBy=multi-user.target +EOF + + systemctl daemon-reload + systemctl enable conduit.service 2>/dev/null || true + systemctl start conduit.service 2>/dev/null || true + log_success "Systemd service created, enabled, and started" + + elif command -v rc-update &>/dev/null; then + # OpenRC (Alpine, Gentoo, etc.) + cat > /etc/init.d/conduit << 'EOF' +#!/sbin/openrc-run + +name="conduit" +description="Psiphon Conduit Service" +depend() { + need docker + after network +} +start() { + ebegin "Starting Conduit" + docker start conduit + eend $? +} +stop() { + ebegin "Stopping Conduit" + docker stop conduit + eend $? +} +EOF + chmod +x /etc/init.d/conduit + rc-update add conduit default 2>/dev/null || true + log_success "OpenRC service created and enabled" + + elif [ -d /etc/init.d ]; then + # SysVinit fallback + cat > /etc/init.d/conduit << 'EOF' +#!/bin/sh +### BEGIN INIT INFO +# Provides: conduit +# Required-Start: $docker +# Required-Stop: $docker +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Psiphon Conduit Service +### END INIT INFO + +case "$1" in + start) + docker start conduit + ;; + stop) + docker stop conduit + ;; + restart) + docker restart conduit + ;; + status) + docker ps | grep -q conduit && echo "Running" || echo "Stopped" + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac +EOF + chmod +x /etc/init.d/conduit + if command -v update-rc.d &>/dev/null; then + update-rc.d conduit defaults 2>/dev/null || true + elif command -v chkconfig &>/dev/null; then + chkconfig conduit on 2>/dev/null || true + fi + log_success "SysVinit service created and enabled" + + else + log_warn "Could not set up auto-start. Docker's restart policy will handle restarts." + log_info "Container is set to restart unless-stopped, which works on reboot if Docker starts." + fi +} + +#═══════════════════════════════════════════════════════════════════════ +# Management Script +#═══════════════════════════════════════════════════════════════════════ + +create_management_script() { + cat > $INSTALL_DIR/conduit << 'MANAGEMENT' +#!/bin/bash +# +# Psiphon Conduit Manager +# Reference: https://github.com/ssmirr/conduit/releases/tag/d8522a8 +# + +VERSION="1.0.1" +INSTALL_DIR="/opt/conduit" +CONDUIT_IMAGE="ghcr.io/ssmirr/conduit/conduit:d8522a8" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +# Load settings +[ -f "$INSTALL_DIR/settings.conf" ] && source "$INSTALL_DIR/settings.conf" +MAX_CLIENTS=${MAX_CLIENTS:-200} +BANDWIDTH=${BANDWIDTH:-5} + +# Ensure we're running as root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Error: This command must be run as root (use sudo conduit)${NC}" + exit 1 +fi + +# Check if Docker is available +check_docker() { + if ! command -v docker &>/dev/null; then + echo -e "${RED}Error: Docker is not installed!${NC}" + echo "" + echo "Docker is required to run Conduit. Please reinstall:" + echo " curl -fsSL https://get.docker.com | sudo sh" + echo "" + echo "Or re-run the Conduit installer:" + echo " sudo bash conduit.sh" + exit 1 + fi + + if ! docker info &>/dev/null; then + echo -e "${RED}Error: Docker daemon is not running!${NC}" + echo "" + echo "Start Docker with:" + echo " sudo systemctl start docker # For systemd" + echo " sudo /etc/init.d/docker start # For SysVinit" + echo " sudo rc-service docker start # For OpenRC" + exit 1 + fi +} + +# Run Docker check +check_docker + +# Check for awk (needed for stats parsing) +if ! command -v awk &>/dev/null; then + echo -e "${YELLOW}Warning: awk not found. Some stats may not display correctly.${NC}" +fi + +print_header() { + echo -e "${CYAN}" + echo "╔═══════════════════════════════════════════════════════════════════╗" + printf "║ 🚀 PSIPHON CONDUIT MANAGER v%-5s ║\n" "${VERSION}" + echo "╚═══════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" +} + +print_live_stats_header() { + local EL="\033[K" + echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════════╗${EL}" + echo -e "║ CONDUIT LIVE STATISTICS ║${EL}" + echo -e "╠═══════════════════════════════════════════════════════════════════╣${EL}" + printf "║ Max Clients: ${GREEN}%-52s${CYAN}║${EL}\n" "${MAX_CLIENTS}" + if [ "$BANDWIDTH" == "-1" ]; then + printf "║ Bandwidth: ${GREEN}%-52s${CYAN}║${EL}\n" "Unlimited" + else + printf "║ Bandwidth: ${GREEN}%-52s${CYAN}║${EL}\n" "${BANDWIDTH} Mbps" + fi + echo -e "║ ║${EL}" + echo -e "╚═══════════════════════════════════════════════════════════════════╝${EL}" + echo -e "${NC}\033[K" +} + + + +get_node_id() { + if docker volume inspect conduit-data >/dev/null 2>&1; then + local mountpoint=$(docker volume inspect conduit-data --format '{{ .Mountpoint }}') + if [ -f "$mountpoint/conduit_key.json" ]; then + # Extract privateKeyBase64, decode, take last 32 bytes, encode base64 + # Logic provided by user + cat "$mountpoint/conduit_key.json" | grep "privateKeyBase64" | awk -F'"' '{print $4}' | base64 -d 2>/dev/null | tail -c 32 | base64 | tr -d '=\n' + fi + fi +} + +show_dashboard() { + local stop_dashboard=0 + # Setup trap to catch signals gracefully + trap 'stop_dashboard=1' SIGINT SIGTERM + + # Use alternate screen buffer if available for smoother experience + tput smcup 2>/dev/null || true + echo -ne "\033[?25l" # Hide cursor + # Initial clear + clear + + while [ $stop_dashboard -eq 0 ]; do + # Move cursor to top-left (0,0) + # We NO LONGER clear the screen here to avoid the "full black" flash + if ! tput cup 0 0 2>/dev/null; then + printf "\033[H" + fi + + print_live_stats_header + + show_status "live" + + # Show Node ID in its own section + local node_id=$(get_node_id) + if [ -n "$node_id" ]; then + echo -e "${CYAN}═══ CONDUIT ID ═══${NC}\033[K" + echo -e " ${CYAN}${node_id}${NC}\033[K" + echo -e "\033[K" + fi + + echo -e "${BOLD}Refreshes every 5 seconds. Press any key to return to menu...${NC}\033[K" + + # Clear any leftover lines below the dashboard content (Erase to End of Display) + # This only cleans up if the dashboard gets shorter + if ! tput ed 2>/dev/null; then + printf "\033[J" + fi + + # Wait 4 seconds for keypress (compensating for processing time) + # Redirect from /dev/tty ensures it works when the script is piped + if read -t 4 -n 1 -s <> /dev/tty 2>/dev/null; then + stop_dashboard=1 + fi + done + + echo -ne "\033[?25h" # Show cursor + # Restore main screen buffer + tput rmcup 2>/dev/null || true + trap - SIGINT SIGTERM # Reset traps +} + +get_container_stats() { + # Get CPU and RAM usage for conduit container + # Returns: "CPU_PERCENT RAM_USAGE" + local stats=$(docker stats --no-stream --format "{{.CPUPerc}} {{.MemUsage}}" conduit 2>/dev/null) + if [ -z "$stats" ]; then + echo "0% 0MiB" + else + # Extract just the raw numbers/units, simpler format + echo "$stats" + 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) + fi + if [ -z "$cores" ] || [ "$cores" -lt 1 ] 2>/dev/null; then echo 1; else echo "$cores"; fi +} + +get_system_stats() { + # Get System CPU (Live Delta) and RAM + # Returns: "CPU_PERCENT RAM_USED RAM_TOTAL RAM_PCT" + + # 1. System CPU (Live Delta) + local sys_cpu="0%" + if [ -f /proc/stat ]; then + # Read 1 + read -r cpu user nice system idle iowait irq softirq steal guest < /proc/stat + local total1=$((user + nice + system + idle + iowait + irq + softirq + steal)) + local work1=$((user + nice + system + irq + softirq + steal)) + + sleep 0.1 + + # Read 2 + read -r cpu user nice system idle iowait irq softirq steal guest < /proc/stat + local total2=$((user + nice + system + idle + iowait + irq + softirq + steal)) + local work2=$((user + nice + system + irq + softirq + steal)) + + local total_delta=$((total2 - total1)) + local work_delta=$((work2 - work1)) + + if [ "$total_delta" -gt 0 ]; then + local cpu_usage=$((work_delta * 100 / total_delta)) + sys_cpu="${cpu_usage}%" + fi + else + sys_cpu="N/A" + fi + + # 2. System RAM (Used, Total, Percentage) + local sys_ram_used="N/A" + local sys_ram_total="N/A" + local sys_ram_pct="N/A" + + if command -v free &>/dev/null; then + # Output: used total percentage + local ram_data=$(free -m 2>/dev/null | awk '/^Mem:/{printf "%s %s %.2f%%", $3, $2, ($3/$2)*100}') + local ram_human=$(free -h 2>/dev/null | awk '/^Mem:/{print $3 " " $2}') + + sys_ram_used=$(echo "$ram_human" | awk '{print $1}') + sys_ram_total=$(echo "$ram_human" | awk '{print $2}') + sys_ram_pct=$(echo "$ram_data" | awk '{print $3}') + fi + + echo "$sys_cpu $sys_ram_used $sys_ram_total $sys_ram_pct" +} + +show_live_stats() { + print_header + echo -e "${YELLOW}Reading traffic history...${NC}" + echo -e "${CYAN}Press ANY KEY to return to menu${NC}" + echo "" + + # Run logs in background + # Stream logs, filter for [STATS], and strip everything before [STATS] + # Tail 2500 to reliably capture stats (performance cost is negligible) + docker logs -f --tail 2500 conduit 2>&1 | grep --line-buffered "\[STATS\]" | sed -u -e 's/.*\[STATS\]/[STATS]/' & + local cmd_pid=$! + + # Wait for any key press + # Redirect from /dev/tty ensures it works when the script is piped + read -n 1 -s -r <> /dev/tty 2>/dev/null || true + + # Kill the background process + kill $cmd_pid 2>/dev/null + wait $cmd_pid 2>/dev/null +} + +show_peers() { + local stop_peers=0 + trap 'stop_peers=1' SIGINT SIGTERM + + # Check dependencies again in case they were removed + if ! command -v tcpdump &>/dev/null || ! command -v geoiplookup &>/dev/null; then + echo -e "${RED}Error: tcpdump or geoiplookup not found!${NC}" + echo "Please re-run the main installer to fix dependencies." + read -n 1 -s -r -p "Press any key to return..." < /dev/tty || true + return 1 + fi + + # Detect primary interface and local IP to filter it out + local iface="any" + local local_ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7}') + [ -z "$local_ip" ] && local_ip=$(hostname -I | awk '{print $1}') + + tput smcup 2>/dev/null || true + echo -ne "\033[?25l" # Hide cursor + clear + + while [ $stop_peers -eq 0 ]; do + if ! tput cup 0 0 2>/dev/null; then printf "\033[H"; fi + # Clear screen from cursor down to prevent ghosting from previous updates + tput ed 2>/dev/null || printf "\033[J" + + # Header Section + echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════════╗${NC}" + echo -e "║ LIVE PEER CONNECTIONS BY COUNTRY ║" + echo -e "${CYAN}╠═══════════════════════════════════════════════════════════════════╣${NC}" + if [ -f /tmp/conduit_peers_current ]; then + local update_time=$(date '+%H:%M:%S') + # 1(║)+2(sp)+13(Last Update: )+8(time)+36(sp)+6([LIVE])+2(sp)+1(║) = 69 total + echo -e "║ Last Update: ${update_time} ${GREEN}[LIVE]${NC} ║" + else + echo -e "║ Status: ${YELLOW}Initial setup...${NC} ║" + fi + echo -e "${CYAN}╚═══════════════════════════════════════════════════════════════════╝${NC}" + echo -e "" + + # Data Table Section + if [ -s /tmp/conduit_peers_current ]; then + echo -e "${BOLD} Peers | Country${NC}" + echo -e " ──────|──────────────────────────────────────" + while read -r line; do + local p_count=$(echo "$line" | awk '{print $1}') + local country=$(echo "$line" | cut -d' ' -f2-) + # Pad country to prevent wrapping/junk + printf " ${GREEN}%5s${NC} | ${CYAN}%-40s${NC}\n" "$p_count" "$country" + done < /tmp/conduit_peers_current + else + echo -e " ${YELLOW}Waiting for first snapshot... (High traffic helps speed this up)${NC}" + for i in {1..8}; do echo ""; done + fi + + echo -e "" + echo -e "${CYAN}─────────────────────────────────────────────────────────────────────${NC}" + + # Background capture starts here + # Removed -c limit to ensure we respect the 14s timeout even on high traffic + timeout 14 tcpdump -ni $iface '(tcp or udp)' 2>/dev/null | \ + grep ' IP ' | \ + sed -nE 's/.* IP ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})(\.[0-9]+)?[ >].*/\1/p' | \ + grep -vE "^($local_ip|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|0\.|169\.254\.)" | \ + sort -u | \ + xargs -n1 geoiplookup 2>/dev/null | \ + awk -F: '/Country Edition/{print $2}' | \ + sed 's/^ // ' | \ + sed 's/Iran, Islamic Republic of/Iran - #FreeIran/' | \ + sort | \ + uniq -c | \ + sort -nr | \ + head -20 > /tmp/conduit_peers_next 2>/dev/null & + + local tcpdump_pid=$! + + # Indicator Loop + local count=0 + while kill -0 $tcpdump_pid 2>/dev/null; do + if read -t 1 -n 1 -s <> /dev/tty 2>/dev/null; then + stop_peers=1 + kill $tcpdump_pid 2>/dev/null + break + fi + count=$((count + 1)) + [ $count -gt 14 ] && count=1 + echo -ne "\r [${YELLOW}" + for ((i=0; i/dev/null + + echo -ne "\r ${GREEN}✓ Update complete! Refreshing...${NC} \033[K" + sleep 1 + done + + echo -ne "\033[?25h" # Show cursor + tput rmcup 2>/dev/null || true + rm -f /tmp/conduit_peers_current + trap - SIGINT SIGTERM +} + +show_status() { + local mode="${1:-normal}" # 'live' mode adds line clearing + local EL="" + if [ "$mode" == "live" ]; then + EL="\033[K" # Erase Line escape code + fi + + echo "" + + + if docker ps 2>/dev/null | grep -q "[[:space:]]conduit$"; then + # Fetch stats once + local logs=$(docker logs --tail 1000 conduit 2>&1 | grep "STATS" | tail -1) + + # Get Resource Stats + local stats=$(get_container_stats) + + # Normalize App CPU (Docker % / Cores) + local raw_app_cpu=$(echo "$stats" | awk '{print $1}' | tr -d '%') + local num_cores=$(get_cpu_cores) + local app_cpu="0%" + local app_cpu_display="" + + if [[ "$raw_app_cpu" =~ ^[0-9.]+$ ]]; then + # Use awk for floating point math + app_cpu=$(awk -v cpu="$raw_app_cpu" -v cores="$num_cores" 'BEGIN {printf "%.2f%%", cpu / cores}') + if [ "$num_cores" -gt 1 ]; then + app_cpu_display="${app_cpu} (${raw_app_cpu}% vCPU)" + else + app_cpu_display="${app_cpu}" + fi + else + app_cpu="${raw_app_cpu}%" + app_cpu_display="${app_cpu}" + fi + + # Keep full "Used / Limit" string for App RAM + local app_ram=$(echo "$stats" | awk '{print $2, $3, $4}') + + local sys_stats=$(get_system_stats) + local sys_cpu=$(echo "$sys_stats" | awk '{print $1}') + local sys_ram_used=$(echo "$sys_stats" | awk '{print $2}') + local sys_ram_total=$(echo "$sys_stats" | awk '{print $3}') + local sys_ram_pct=$(echo "$sys_stats" | awk '{print $4}') + + if [ -n "$logs" ]; then + local connecting=$(echo "$logs" | sed -n 's/.*Connecting:[[:space:]]*\([0-9]*\).*/\1/p') + local connected=$(echo "$logs" | sed -n 's/.*Connected:[[:space:]]*\([0-9]*\).*/\1/p') + local upload=$(echo "$logs" | sed -n 's/.*Up:[[:space:]]*\([^|]*\).*/\1/p' | xargs) + local download=$(echo "$logs" | sed -n 's/.*Down:[[:space:]]*\([^|]*\).*/\1/p' | xargs) + local uptime=$(echo "$logs" | sed -n 's/.*Uptime:[[:space:]]*\(.*\)/\1/p' | xargs) + + # Default to 0 if missing/empty + connecting=${connecting:-0} + connected=${connected:-0} + + echo -e "🚀 PSIPHON CONDUIT MANAGER v${VERSION}${EL}" + echo -e "${NC}${EL}" + + if [ -n "$uptime" ]; then + echo -e "${BOLD}Status:${NC} ${GREEN}Running${NC} (${uptime}) | ${BOLD}Clients:${NC} ${GREEN}${connected}${NC} connected, ${YELLOW}${connecting}${NC} connecting${EL}" + else + echo -e "${BOLD}Status:${NC} ${GREEN}Running${NC} | ${BOLD}Clients:${NC} ${GREEN}${connected}${NC} connected, ${YELLOW}${connecting}${NC} connecting${EL}" + fi + + echo -e "${EL}" + echo -e "${CYAN}═══ Traffic ═══${NC}${EL}" + [ -n "$upload" ] && echo -e " Upload: ${CYAN}${upload}${NC}${EL}" + [ -n "$download" ] && echo -e " Download: ${CYAN}${download}${NC}${EL}" + + echo -e "${EL}" + echo -e "${CYAN}═══ Resource Usage ═══${NC}${EL}" + printf " %-8s CPU: ${YELLOW}%-20s${NC} | RAM: ${YELLOW}%-20s${NC}${EL}\n" "App:" "$app_cpu_display" "$app_ram" + printf " %-8s CPU: ${YELLOW}%-20s${NC} | RAM: ${YELLOW}%-20s${NC}${EL}\n" "System:" "$sys_cpu" "$sys_ram_used / $sys_ram_total" + printf " %-8s CPU: ${YELLOW}%-20s${NC} | RAM: ${YELLOW}%-20s${NC}${EL}\n" "Total:" "$sys_cpu" "$sys_ram_pct" + + else + echo -e "🚀 PSIPHON CONDUIT MANAGER v${VERSION}${EL}" + echo -e "${NC}${EL}" + echo -e "${BOLD}Status:${NC} ${GREEN}Running${NC}${EL}" + echo -e "${EL}" + echo -e "${CYAN}═══ Resource Usage ═══${NC}${EL}" + printf " %-8s CPU: ${YELLOW}%-20s${NC} | RAM: ${YELLOW}%-20s${NC}${EL}\n" "App:" "$app_cpu_display" "$app_ram" + printf " %-8s CPU: ${YELLOW}%-20s${NC} | RAM: ${YELLOW}%-20s${NC}${EL}\n" "System:" "$sys_cpu" "$sys_ram_used / $sys_ram_total" + printf " %-8s CPU: ${YELLOW}%-20s${NC} | RAM: ${YELLOW}%-20s${NC}${EL}\n" "Total:" "$sys_cpu" "$sys_ram_pct" + echo -e "${EL}" + echo -e " Stats: ${YELLOW}Waiting for first stats...${NC}${EL}" + fi + + else + echo -e "🚀 PSIPHON CONDUIT MANAGER v${VERSION}${EL}" + echo -e "${NC}${EL}" + echo -e "${BOLD}Status:${NC} ${RED}Stopped${NC}${EL}" + fi + + + + echo "" + echo -e "${CYAN}═══ SETTINGS ═══${NC}${EL}" + echo -e " Max Clients: ${MAX_CLIENTS}${EL}" + if [ "$BANDWIDTH" == "-1" ]; then + echo -e " Bandwidth: Unlimited${EL}" + else + echo -e " Bandwidth: ${BANDWIDTH} Mbps${EL}" + fi + + + echo "" + echo -e "${CYAN}═══ AUTO-START SERVICE ═══${NC}" + # Check for systemd + if command -v systemctl &>/dev/null && systemctl is-enabled conduit.service 2>/dev/null | grep -q "enabled"; then + echo -e " Auto-start: ${GREEN}Enabled (systemd)${NC}" + local svc_status=$(systemctl is-active conduit.service 2>/dev/null) + echo -e " Service: ${svc_status:-unknown}" + # Check for OpenRC + elif command -v rc-status &>/dev/null && rc-status -a 2>/dev/null | grep -q "conduit"; then + echo -e " Auto-start: ${GREEN}Enabled (OpenRC)${NC}" + # Check for SysVinit + elif [ -f /etc/init.d/conduit ]; then + echo -e " Auto-start: ${GREEN}Enabled (SysVinit)${NC}" + else + echo -e " Auto-start: ${YELLOW}Not configured${NC}" + echo -e " Note: Docker restart policy handles restarts" + fi + echo "" +} + +start_conduit() { + echo "Starting Conduit..." + if docker ps -a 2>/dev/null | grep -q "[[:space:]]conduit$"; then + if docker start conduit 2>/dev/null; then + echo -e "${GREEN}✓ Conduit started${NC}" + else + echo -e "${RED}✗ Failed to start Conduit${NC}" + return 1 + fi + else + echo "Container not found. Creating new container..." + docker run -d \ + --name conduit \ + --restart unless-stopped \ + -v conduit-data:/home/conduit/data \ + --network host \ + $CONDUIT_IMAGE \ + start --max-clients "$MAX_CLIENTS" --bandwidth "$BANDWIDTH" -v + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Conduit started${NC}" + else + echo -e "${RED}✗ Failed to start Conduit${NC}" + return 1 + fi + fi +} + +stop_conduit() { + echo "Stopping Conduit..." + if docker ps 2>/dev/null | grep -q "[[:space:]]conduit$"; then + docker stop conduit 2>/dev/null + echo -e "${YELLOW}✓ Conduit stopped${NC}" + else + echo -e "${YELLOW}Conduit is not running${NC}" + fi +} + +restart_conduit() { + echo "Restarting Conduit..." + if docker ps -a 2>/dev/null | grep -q "[[:space:]]conduit$"; then + docker restart conduit 2>/dev/null + echo -e "${GREEN}✓ Conduit restarted${NC}" + else + echo -e "${RED}Conduit container not found. Use 'conduit start' to create it.${NC}" + return 1 + fi +} + +change_settings() { + echo "" + echo -e "${CYAN}Current Settings:${NC}" + echo -e " Max Clients: ${MAX_CLIENTS}" + if [ "$BANDWIDTH" == "-1" ]; then + echo -e " Bandwidth: Unlimited" + else + echo -e " Bandwidth: ${BANDWIDTH} Mbps" + fi + echo "" + + read -p "New max-clients (1-1000) [${MAX_CLIENTS}]: " new_clients < /dev/tty || true + + + # Bandwidth prompt logic for settings menu + echo "" + if [ "$BANDWIDTH" == "-1" ]; then + echo "Current bandwidth: Unlimited" + else + echo "Current bandwidth: ${BANDWIDTH} Mbps" + fi + read -p "Set unlimited bandwidth (-1)? [y/N]: " set_unlimited < /dev/tty || true + + if [[ "$set_unlimited" =~ ^[Yy] ]]; then + new_bandwidth="-1" + else + read -p "New bandwidth in Mbps (1-40) [${BANDWIDTH}]: " input_bw < /dev/tty || true + if [ -n "$input_bw" ]; then + new_bandwidth="$input_bw" + fi + fi + + # Validate max-clients + if [ -n "$new_clients" ]; then + if [[ "$new_clients" =~ ^[0-9]+$ ]] && [ "$new_clients" -ge 1 ] && [ "$new_clients" -le 1000 ]; then + MAX_CLIENTS=$new_clients + else + echo -e "${YELLOW}Invalid max-clients. Keeping current: ${MAX_CLIENTS}${NC}" + fi + fi + + # Validate bandwidth + if [ -n "$new_bandwidth" ]; then + if [ "$new_bandwidth" = "-1" ]; then + BANDWIDTH="-1" + elif [[ "$new_bandwidth" =~ ^[0-9]+$ ]] && [ "$new_bandwidth" -ge 1 ] && [ "$new_bandwidth" -le 40 ]; then + BANDWIDTH=$new_bandwidth + elif [[ "$new_bandwidth" =~ ^[0-9]*\.[0-9]+$ ]]; then + local float_ok=$(awk -v val="$new_bandwidth" 'BEGIN { print (val >= 1 && val <= 40) ? "yes" : "no" }') + if [ "$float_ok" = "yes" ]; then + BANDWIDTH=$new_bandwidth + else + echo -e "${YELLOW}Invalid bandwidth. Keeping current: ${BANDWIDTH}${NC}" + fi + else + echo -e "${YELLOW}Invalid bandwidth. Keeping current: ${BANDWIDTH}${NC}" + fi + fi + + # Save settings + cat > $INSTALL_DIR/settings.conf << EOF +MAX_CLIENTS=$MAX_CLIENTS +BANDWIDTH=$BANDWIDTH +EOF + + echo "" + echo "Updating and recreating Conduit container with new settings..." + docker rm -f conduit 2>/dev/null || true + sleep 2 # Wait for container cleanup to complete + echo "Pulling latest image..." + docker pull $CONDUIT_IMAGE 2>/dev/null || echo -e "${YELLOW}Could not pull latest image, using cached version${NC}" + docker run -d \ + --name conduit \ + --restart unless-stopped \ + -v conduit-data:/home/conduit/data \ + --network host \ + $CONDUIT_IMAGE \ + start --max-clients "$MAX_CLIENTS" --bandwidth "$BANDWIDTH" -v + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Settings updated and Conduit restarted${NC}" + echo -e " Max Clients: ${MAX_CLIENTS}" + if [ "$BANDWIDTH" == "-1" ]; then + echo -e " Bandwidth: Unlimited" + else + echo -e " Bandwidth: ${BANDWIDTH} Mbps" + fi + else + echo -e "${RED}✗ Failed to restart Conduit${NC}" + fi +} + +show_logs() { + if ! docker ps -a 2>/dev/null | grep -q conduit; then + echo -e "${RED}Conduit container not found.${NC}" + return 1 + fi + # Filter out noisy 'context deadline exceeded' and 'port mapping: closed' errors + docker logs -f --tail 100 conduit 2>&1 | grep -vE "context deadline exceeded|port mapping: closed" +} + +uninstall_all() { + echo "" + echo -e "${RED}╔═══════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${RED}║ ⚠️ UNINSTALL CONDUIT ║${NC}" + echo -e "${RED}╚═══════════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo "This will completely remove:" + echo " • Conduit Docker container" + echo " • Conduit Docker image" + echo " • Conduit data volume (all stored data)" + echo " • Auto-start service (systemd/OpenRC/SysVinit)" + echo " • Configuration files" + echo " • Management CLI" + echo "" + echo -e "${RED}WARNING: This action cannot be undone!${NC}" + echo "" + read -p "Are you sure you want to uninstall? (type 'yes' to confirm): " confirm < /dev/tty || true + + if [ "$confirm" != "yes" ]; then + echo "Uninstall cancelled." + return 0 + fi + + echo "" + echo "[INFO] Stopping Conduit container..." + docker stop conduit 2>/dev/null || true + + echo "[INFO] Removing Conduit container..." + docker rm -f conduit 2>/dev/null || true + + echo "[INFO] Removing Conduit Docker image..." + docker rmi $CONDUIT_IMAGE 2>/dev/null || true + + echo "[INFO] Removing Conduit data volume..." + docker volume rm conduit-data 2>/dev/null || true + + echo "[INFO] Removing auto-start service..." + # Systemd + systemctl stop conduit.service 2>/dev/null || true + systemctl disable conduit.service 2>/dev/null || true + rm -f /etc/systemd/system/conduit.service + systemctl daemon-reload 2>/dev/null || true + # OpenRC / SysVinit + rc-service conduit stop 2>/dev/null || true + rc-update del conduit 2>/dev/null || true + service conduit stop 2>/dev/null || true + update-rc.d conduit remove 2>/dev/null || true + chkconfig conduit off 2>/dev/null || true + rm -f /etc/init.d/conduit + + echo "[INFO] Removing configuration files..." + rm -rf /opt/conduit + rm -f /usr/local/bin/conduit + + echo "" + echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ ✅ UNINSTALL COMPLETE! ║${NC}" + echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo "Conduit and all related components have been removed." + echo "" + echo "Note: Docker itself was NOT removed. To remove Docker:" + echo " apt-get purge docker-ce docker-ce-cli containerd.io" + echo "" +} + +show_menu() { + local redraw=true + while true; do + if [ "$redraw" = true ]; then + clear + print_header + + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo -e "${CYAN} MANAGEMENT OPTIONS${NC}" + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo -e " 1. 📈 View status dashboard" + echo -e " 2. 📜 View traffic history (Scrolling Logs)" + echo -e " 3. 📋 View raw logs (Filtered)" + echo -e " 4. ⚙️ Change settings (max-clients, bandwidth)" + echo "" + echo -e " 5. ▶️ Start Conduit" + echo -e " 6. ⏹️ Stop Conduit" + echo -e " 7. 🔁 Restart Conduit" + echo "" + echo -e " 8. 🌍 View live peers by country (Live Map)" + echo "" + echo -e " u. 🗑️ Uninstall (remove everything)" + echo -e " 0. 🚪 Exit" + echo -e "${CYAN}─────────────────────────────────────────────────────────────────${NC}" + echo "" + redraw=false + fi + + read -p " Enter choice: " choice < /dev/tty || { echo "Input error. Exiting."; exit 1; } + + case $choice in + 1) + show_dashboard + redraw=true + ;; + 2) + show_live_stats + read -n 1 -s -r -p "Press any key to return..." < /dev/tty || true + redraw=true + ;; + 3) + show_logs + redraw=true + ;; + 4) + change_settings + redraw=true + ;; + 5) + start_conduit + read -n 1 -s -r -p "Press any key to return..." < /dev/tty || true + redraw=true + ;; + 6) + stop_conduit + read -n 1 -s -r -p "Press any key to return..." < /dev/tty || true + redraw=true + ;; + 7) + restart_conduit + read -n 1 -s -r -p "Press any key to return..." < /dev/tty || true + redraw=true + ;; + 8) + show_peers + redraw=true + ;; + u) + uninstall_all + exit 0 + ;; + 0) + echo "Exiting." + exit 0 + ;; + "") + # Ignore empty Enter key + ;; + *) + echo -e "${RED}Invalid choice: ${NC}${YELLOW}$choice${NC}" + echo -e "${CYAN}Choose an option from 0-8, or 'u' to uninstall.${NC}" + ;; + esac + done +} + +# Command line interface +show_help() { + echo "Usage: conduit [command]" + echo "" + echo "Commands:" + echo " status Show current status with resource usage" + echo " stats View live statistics" + echo " logs View raw Docker logs" + echo " start Start Conduit container" + echo " stop Stop Conduit container" + echo " restart Restart Conduit container" + echo " settings Change max-clients/bandwidth" + echo " uninstall Remove everything (container, data, service)" + echo " menu Open interactive menu (default)" + echo " help Show this help" +} + +case "${1:-menu}" in + status) show_status ;; + stats) show_live_stats ;; + logs) show_logs ;; + start) start_conduit ;; + stop) stop_conduit ;; + restart) restart_conduit ;; + peers) show_peers ;; + settings) change_settings ;; + uninstall) uninstall_all ;; + help|-h|--help) show_help ;; + menu|*) show_menu ;; +esac +MANAGEMENT + + chmod +x $INSTALL_DIR/conduit + # Force create symlink + rm -f /usr/local/bin/conduit 2>/dev/null || true + ln -s $INSTALL_DIR/conduit /usr/local/bin/conduit + + log_success "Management script installed: conduit" +} + +#═══════════════════════════════════════════════════════════════════════ +# Summary +#═══════════════════════════════════════════════════════════════════════ + +print_summary() { + local init_type="Enabled" + if [ "$HAS_SYSTEMD" = "true" ]; then + init_type="Enabled (systemd)" + elif command -v rc-update &>/dev/null; then + init_type="Enabled (OpenRC)" + elif [ -d /etc/init.d ]; then + init_type="Enabled (SysVinit)" + fi + + echo "" + echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ ✅ INSTALLATION COMPLETE! ║${NC}" + echo -e "${GREEN}╠═══════════════════════════════════════════════════════════════════╣${NC}" + echo -e "${GREEN}║${NC} Conduit is running and ready to help users! ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} 📊 Settings: ${GREEN}║${NC}" + printf "${GREEN}║${NC} Max Clients: ${CYAN}%-4s${NC} ${GREEN}║${NC}\n" "${MAX_CLIENTS}" + if [ "$BANDWIDTH" == "-1" ]; then + echo -e "${GREEN}║${NC} Bandwidth: ${CYAN}Unlimited${NC} ${GREEN}║${NC}" + else + printf "${GREEN}║${NC} Bandwidth: ${CYAN}%-4s${NC} Mbps ${GREEN}║${NC}\n" "${BANDWIDTH}" + fi + printf "${GREEN}║${NC} Auto-start: ${CYAN}%-20s${NC} ${GREEN}║${NC}\n" "${init_type}" + echo -e "${GREEN}║${NC} ${GREEN}║${NC}" + echo -e "${GREEN}╠═══════════════════════════════════════════════════════════════════╣${NC}" + echo -e "${GREEN}║${NC} COMMANDS: ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${CYAN}conduit${NC} # Open management menu ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${CYAN}conduit stats${NC} # View live statistics + CPU/RAM ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${CYAN}conduit status${NC} # Quick status with resource usage ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${CYAN}conduit logs${NC} # View raw logs ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${CYAN}conduit settings${NC} # Change max-clients/bandwidth ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${CYAN}conduit uninstall${NC} # Remove everything ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} ${GREEN}║${NC}" + echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e " ${YELLOW}View live stats now:${NC} conduit stats" + echo "" +} + +#═══════════════════════════════════════════════════════════════════════ +# Uninstall Function +#═══════════════════════════════════════════════════════════════════════ + +uninstall() { + echo -e "${CYAN}" + echo "╔═══════════════════════════════════════════════════════════════════╗" + echo "║ ⚠️ UNINSTALL CONDUIT ║" + echo "╚═══════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" + echo "" + echo "This will completely remove:" + echo " • Conduit Docker container" + echo " • Conduit Docker image" + echo " • Conduit data volume (all stored data)" + echo " • Auto-start service (systemd/OpenRC/SysVinit)" + echo " • Configuration files" + echo " • Management CLI" + echo "" + echo -e "${RED}WARNING: This action cannot be undone!${NC}" + echo "" + read -p "Are you sure you want to uninstall? (type 'yes' to confirm): " confirm < /dev/tty || true + + if [ "$confirm" != "yes" ]; then + echo "Uninstall cancelled." + exit 0 + fi + + echo "" + log_info "Stopping Conduit container..." + docker stop conduit 2>/dev/null || true + + log_info "Removing Conduit container..." + docker rm -f conduit 2>/dev/null || true + + log_info "Removing Conduit Docker image..." + docker rmi ghcr.io/ssmirr/conduit/conduit:latest 2>/dev/null || true + + log_info "Removing Conduit data volume..." + docker volume rm conduit-data 2>/dev/null || true + + log_info "Removing auto-start service..." + # Systemd + systemctl stop conduit.service 2>/dev/null || true + systemctl disable conduit.service 2>/dev/null || true + rm -f /etc/systemd/system/conduit.service + systemctl daemon-reload 2>/dev/null || true + # OpenRC / SysVinit + rc-service conduit stop 2>/dev/null || true + rc-update del conduit 2>/dev/null || true + service conduit stop 2>/dev/null || true + update-rc.d conduit remove 2>/dev/null || true + chkconfig conduit off 2>/dev/null || true + rm -f /etc/init.d/conduit + + log_info "Removing configuration files..." + rm -rf /opt/conduit + rm -f /usr/local/bin/conduit + + echo "" + echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ ✅ UNINSTALL COMPLETE! ║${NC}" + echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo "Conduit and all related components have been removed." + echo "" + echo "Note: Docker itself was NOT removed. To remove Docker:" + echo " apt-get purge docker-ce docker-ce-cli containerd.io" + echo "" +} + +#═══════════════════════════════════════════════════════════════════════ +# Main +#═══════════════════════════════════════════════════════════════════════ + +show_usage() { + echo "Psiphon Conduit Manager v${VERSION}" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " (no args) Install or open management menu if already installed" + echo " --reinstall Force fresh reinstall" + echo " --uninstall Completely remove Conduit and all components" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " sudo bash $0 # Install or open menu" + echo " sudo bash $0 --reinstall # Fresh install" + echo " sudo bash $0 --uninstall # Remove everything" + echo "" + echo "After install, use: conduit" +} + +main() { + # Handle command line arguments + case "${1:-}" in + --uninstall|-u) + check_root + uninstall + exit 0 + ;; + --help|-h) + show_usage + exit 0 + ;; + --reinstall) + # Force reinstall + FORCE_REINSTALL=true + ;; + esac + + print_header + check_root + detect_os + + # Ensure all tools (including new ones like tcpdump) are present + check_dependencies + + # Check if already installed + if [ -f "$INSTALL_DIR/conduit" ] && [ "$FORCE_REINSTALL" != "true" ]; then + echo -e "${GREEN}Conduit is already installed!${NC}" + echo "" + echo "What would you like to do?" + echo "" + echo " 1. 📊 Open management menu" + echo " 2. 🔄 Reinstall (fresh install)" + echo " 3. 🗑️ Uninstall" + echo " 0. 🚪 Exit" + echo "" + read -p " Enter choice: " choice < /dev/tty || true + + case $choice in + 1) + echo -e "${CYAN}Opening management menu...${NC}" + create_management_script >/dev/null 2>&1 + exec /opt/conduit/conduit menu + ;; + 2) + echo "" + log_info "Starting fresh reinstall..." + ;; + 3) + uninstall + exit 0 + ;; + 0) + echo "Exiting." + exit 0 + ;; + *) + echo -e "${RED}Invalid choice: ${NC}${YELLOW}$choice${NC}" + echo -e "${CYAN}Returning to installer...${NC}" + sleep 1 + main "$@" + ;; + esac + fi + + # Interactive settings prompt + prompt_settings + + echo "" + echo -e "${CYAN}Starting installation...${NC}" + echo "" + + # Installation steps + log_info "Step 1/4: Installing Docker..." + install_docker + + echo "" + log_info "Step 2/4: Starting Conduit..." + run_conduit + + echo "" + log_info "Step 3/4: Setting up auto-start..." + save_settings + setup_autostart + + echo "" + log_info "Step 4/4: Creating management script..." + create_management_script + + print_summary + + read -p "View live statistics now? [Y/n] " view_stats < /dev/tty || true + if [[ ! "$view_stats" =~ ^[Nn] ]]; then + /opt/conduit/conduit stats + fi +} +# +# REACHED END OF SCRIPT - VERSION 1.0.1 +# ############################################################################### +main "$@" diff --git a/conduitmenu.png b/conduitmenu.png new file mode 100644 index 0000000000000000000000000000000000000000..a5ee867f71481dbbd827efb821e189302b365cab GIT binary patch literal 39114 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