commit 77bda1117d027b55cfae667d6079443074819131 Author: Sam Date: Sat Jan 24 06:33:39 2026 -0600 Initial release: Psiphon Conduit Manager v1.0.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb58d0f --- /dev/null +++ b/README.md @@ -0,0 +1,212 @@ +# 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 + +## 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 | Bandwidth limit per peer (Mbps) | + +**Recommended values based on server RAM:** + +| RAM | Max Clients | +|-----|-------------| +| 8GB+ | 1000 | +| 4-8GB | 700 | +| 2-4GB | 400 | +| <2GB | 200 | + +## 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 | محدودیت پهنای باند (Mbps) | + +**مقادیر پیشنهادی:** + +| رم سرور | حداکثر کاربران | +|---------|----------------| +| +8 گیگ | 1000 | +| 4-8 گیگ | 700 | +| 2-4 گیگ | 400 | +| زیر 2 گیگ | 200 | + +## پیش‌نیازها + +- سرور لینوکس +- دسترسی 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..de79576 --- /dev/null +++ b/conduit.sh @@ -0,0 +1,1280 @@ +#!/bin/bash +# Updated: 2026-01-24 06:01 UTC +# +# ╔═══════════════════════════════════════════════════════════════════╗ +# ║ 🚀 PSIPHON CONDUIT MANAGER v1.0.0 ║ +# ║ ║ +# ║ 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 ║ +# ╚═══════════════════════════════════════════════════════════════════╝ +# +# Usage: +# curl -sL https://your-url/conduit.sh | sudo bash +# +# Reference: https://github.com/ssmirr/conduit/releases/tag/87cc1a3 +# 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) (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.0" +CONDUIT_IMAGE="ghcr.io/ssmirr/conduit/conduit:87cc1a3" +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' # No Color + +#═══════════════════════════════════════════════════════════════════════ +# 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 (most modern distros) + 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 (Alpine uses ash by default) + 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 (part of procps) + 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 +} + +get_ram_gb() { + # Get RAM in GB, minimum 1 for safety + local ram="" + + # Try free command first + if command -v free &>/dev/null; then + ram=$(free -g 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 / 1024)) + fi + fi + fi + + # Ensure minimum of 1 + if [ -z "$ram" ] || [ "$ram" -lt 1 ] 2>/dev/null; then + echo 1 + else + echo "$ram" + fi +} + +calculate_recommended_clients() { + local ram_gb=$(get_ram_gb) + + if [ "$ram_gb" -ge 8 ]; then + echo 1000 + elif [ "$ram_gb" -ge 4 ]; then + echo 700 + elif [ "$ram_gb" -ge 2 ]; then + echo 400 + else + echo 200 + fi +} + +#═══════════════════════════════════════════════════════════════════════ +# Interactive Setup +#═══════════════════════════════════════════════════════════════════════ + +prompt_settings() { + local ram_gb=$(get_ram_gb) + 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 " RAM: ${GREEN}${ram_gb}GB${NC}" + 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)" + 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 " 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 + # Handle decimal - validate the whole number is in range + 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 + + echo "" + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + echo -e " ${BOLD}Your Settings:${NC}" + echo -e " Max Clients: ${GREEN}${MAX_CLIENTS}${NC}" + echo -e " Bandwidth: ${GREEN}${BANDWIDTH}${NC} Mbps" + 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 uses a different method + 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 script for most distros + 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 (up to 30 seconds for slow systems) + 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 latest image + log_info "Pulling Conduit image from ghcr.io/ssmirr/conduit..." + 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" + log_success "Settings: max-clients=$MAX_CLIENTS, bandwidth=${BANDWIDTH}Mbps" + 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 + + # Verify write succeeded + 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/87cc1a3 +# + +VERSION="1.0.0" +INSTALL_DIR="/opt/conduit" +CONDUIT_IMAGE="ghcr.io/ssmirr/conduit/conduit:87cc1a3" + +# 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} + +# 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 "╔═══════════════════════════════════════════════════════════════════╗" + echo "║ 🚀 PSIPHON CONDUIT MANAGER v${VERSION} ║" + echo "╚═══════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" +} + +print_live_stats_header() { + echo -e "${CYAN}" + echo "╔═══════════════════════════════════════════════════════════════════╗" + echo "║ CONDUIT LIVE STATISTICS ║" + echo "╠═══════════════════════════════════════════════════════════════════╣" + echo -e "║ Max Clients: ${GREEN}${MAX_CLIENTS}${CYAN} ║" + echo -e "║ Bandwidth: ${GREEN}${BANDWIDTH} Mbps${CYAN} ║" + echo "║ ║" + echo "║ Press Ctrl+C to exit ║" + echo "╚═══════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" +} + +get_container_resources() { + # Get CPU and memory usage from docker stats + if docker ps 2>/dev/null | grep -q "[[:space:]]conduit$"; then + local stats=$(docker stats conduit --no-stream --format "{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}" 2>/dev/null) + if [ -n "$stats" ]; then + CPU_USAGE=$(echo "$stats" | cut -d'|' -f1) + MEM_USAGE=$(echo "$stats" | cut -d'|' -f2) + MEM_PERC=$(echo "$stats" | cut -d'|' -f3) + else + CPU_USAGE="N/A" + MEM_USAGE="N/A" + MEM_PERC="N/A" + fi + else + CPU_USAGE="N/A" + MEM_USAGE="N/A" + MEM_PERC="N/A" + fi +} + +show_dashboard() { + local stop_dashboard=0 + # Setup trap to catch signals gracefully + trap 'stop_dashboard=1' SIGINT SIGTERM + + echo -ne "\033[?25l" # Hide cursor + clear + + while [ $stop_dashboard -eq 0 ]; do + echo -ne "\033[H" # Move cursor to top-left + print_live_stats_header + + show_status + + # Get and show resource usage + get_container_resources + echo "" + echo -e "${CYAN}═══ RESOURCE USAGE ═══${NC}" + echo -e " CPU: ${YELLOW}${CPU_USAGE}${NC}" + echo -e " Memory: ${YELLOW}${MEM_USAGE}${NC} (${MEM_PERC})" + echo "" + echo -e "${BOLD}Refreshes every 10 seconds. Press any key to return to menu...${NC}" + + # Clear any leftover content below + tput ed 2>/dev/null || true + + # Wait 10 seconds for keypress. Signal will interrupt this read. + if read -t 10 -n 1; then + stop_dashboard=1 + fi + done + + echo -ne "\033[?25h" # Show cursor + trap - SIGINT SIGTERM # Reset traps +} + +show_live_stats() { + print_header + echo -e "${YELLOW}Reading traffic history...${NC}" + echo -e "${CYAN}Press Ctrl+C to return to menu${NC}" + echo "" + # Stream logs, filter for [STATS], and strip everything before [STATS] + docker logs -f --tail 200 conduit 2>&1 | grep --line-buffered "\[STATS\]" | sed -u -e 's/.*\[STATS\]/[STATS]/' +} + +show_status() { + echo "" + echo -e "${CYAN}═══ CONDUIT STATUS ═══${NC}" + + if docker ps 2>/dev/null | grep -q "[[:space:]]conduit$"; then + echo -e " Container: ${GREEN}Running${NC}" + + # Get last stats from logs + local stats=$(docker logs --tail 100 conduit 2>&1 | grep "STATS" | tail -1) + if [ -n "$stats" ]; then + local connected=$(echo "$stats" | sed -n 's/.*Connected:[[:space:]]*\([0-9]*\).*/\1/p') + local upload=$(echo "$stats" | sed -n 's/.*Up:[[:space:]]*\([^|]*\).*/\1/p' | tr -d ' ') + local download=$(echo "$stats" | sed -n 's/.*Down:[[:space:]]*\([^|]*\).*/\1/p' | tr -d ' ') + [ -n "$connected" ] && echo -e " Clients: ${GREEN}${connected}${NC} connected" + [ -n "$upload" ] && echo -e " Upload: ${upload}" + [ -n "$download" ] && echo -e " Download: ${download}" + else + echo -e " Stats: ${YELLOW}Waiting for first stats...${NC}" + fi + + else + echo -e " Container: ${RED}Stopped${NC}" + fi + + echo "" + echo -e "${CYAN}═══ SETTINGS ═══${NC}" + echo -e " Max Clients: ${MAX_CLIENTS}" + echo -e " Bandwidth: ${BANDWIDTH} Mbps" + + 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}" + echo -e " Bandwidth: ${BANDWIDTH} Mbps" + echo "" + + read -p "New max-clients (1-1000) [${MAX_CLIENTS}]: " new_clients < /dev/tty || true + read -p "New bandwidth in Mbps (1-40) [${BANDWIDTH}]: " new_bandwidth < /dev/tty || true + + # 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" =~ ^[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}" + echo -e " Bandwidth: ${BANDWIDTH} Mbps" + 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 (Live CPU/RAM)" + 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 " 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 + ;; + 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-7, 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 ;; + settings) change_settings ;; + uninstall) uninstall_all ;; + help|-h|--help) show_help ;; + menu|*) show_menu ;; +esac +MANAGEMENT + + chmod +x $INSTALL_DIR/conduit + # Force create symlink (remove existing first) + 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() { + # Determine which init system was used + 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}" + echo -e "${GREEN}║${NC} Max Clients: ${CYAN}${MAX_CLIENTS}${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} Bandwidth: ${CYAN}${BANDWIDTH} Mbps${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} Auto-start: ${CYAN}${init_type}${NC} ${GREEN}║${NC}" + 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 + + # 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..." + # Continue with installation below + ;; + 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 + + + check_dependencies + + # 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 + + # Ask if user wants to view live stats + 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.0 +# ############################################################################### +main "$@" diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..de79576 --- /dev/null +++ b/install.sh @@ -0,0 +1,1280 @@ +#!/bin/bash +# Updated: 2026-01-24 06:01 UTC +# +# ╔═══════════════════════════════════════════════════════════════════╗ +# ║ 🚀 PSIPHON CONDUIT MANAGER v1.0.0 ║ +# ║ ║ +# ║ 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 ║ +# ╚═══════════════════════════════════════════════════════════════════╝ +# +# Usage: +# curl -sL https://your-url/conduit.sh | sudo bash +# +# Reference: https://github.com/ssmirr/conduit/releases/tag/87cc1a3 +# 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) (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.0" +CONDUIT_IMAGE="ghcr.io/ssmirr/conduit/conduit:87cc1a3" +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' # No Color + +#═══════════════════════════════════════════════════════════════════════ +# 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 (most modern distros) + 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 (Alpine uses ash by default) + 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 (part of procps) + 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 +} + +get_ram_gb() { + # Get RAM in GB, minimum 1 for safety + local ram="" + + # Try free command first + if command -v free &>/dev/null; then + ram=$(free -g 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 / 1024)) + fi + fi + fi + + # Ensure minimum of 1 + if [ -z "$ram" ] || [ "$ram" -lt 1 ] 2>/dev/null; then + echo 1 + else + echo "$ram" + fi +} + +calculate_recommended_clients() { + local ram_gb=$(get_ram_gb) + + if [ "$ram_gb" -ge 8 ]; then + echo 1000 + elif [ "$ram_gb" -ge 4 ]; then + echo 700 + elif [ "$ram_gb" -ge 2 ]; then + echo 400 + else + echo 200 + fi +} + +#═══════════════════════════════════════════════════════════════════════ +# Interactive Setup +#═══════════════════════════════════════════════════════════════════════ + +prompt_settings() { + local ram_gb=$(get_ram_gb) + 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 " RAM: ${GREEN}${ram_gb}GB${NC}" + 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)" + 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 " 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 + # Handle decimal - validate the whole number is in range + 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 + + echo "" + echo -e "${CYAN}───────────────────────────────────────────────────────────────${NC}" + echo -e " ${BOLD}Your Settings:${NC}" + echo -e " Max Clients: ${GREEN}${MAX_CLIENTS}${NC}" + echo -e " Bandwidth: ${GREEN}${BANDWIDTH}${NC} Mbps" + 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 uses a different method + 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 script for most distros + 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 (up to 30 seconds for slow systems) + 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 latest image + log_info "Pulling Conduit image from ghcr.io/ssmirr/conduit..." + 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" + log_success "Settings: max-clients=$MAX_CLIENTS, bandwidth=${BANDWIDTH}Mbps" + 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 + + # Verify write succeeded + 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/87cc1a3 +# + +VERSION="1.0.0" +INSTALL_DIR="/opt/conduit" +CONDUIT_IMAGE="ghcr.io/ssmirr/conduit/conduit:87cc1a3" + +# 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} + +# 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 "╔═══════════════════════════════════════════════════════════════════╗" + echo "║ 🚀 PSIPHON CONDUIT MANAGER v${VERSION} ║" + echo "╚═══════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" +} + +print_live_stats_header() { + echo -e "${CYAN}" + echo "╔═══════════════════════════════════════════════════════════════════╗" + echo "║ CONDUIT LIVE STATISTICS ║" + echo "╠═══════════════════════════════════════════════════════════════════╣" + echo -e "║ Max Clients: ${GREEN}${MAX_CLIENTS}${CYAN} ║" + echo -e "║ Bandwidth: ${GREEN}${BANDWIDTH} Mbps${CYAN} ║" + echo "║ ║" + echo "║ Press Ctrl+C to exit ║" + echo "╚═══════════════════════════════════════════════════════════════════╝" + echo -e "${NC}" +} + +get_container_resources() { + # Get CPU and memory usage from docker stats + if docker ps 2>/dev/null | grep -q "[[:space:]]conduit$"; then + local stats=$(docker stats conduit --no-stream --format "{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}" 2>/dev/null) + if [ -n "$stats" ]; then + CPU_USAGE=$(echo "$stats" | cut -d'|' -f1) + MEM_USAGE=$(echo "$stats" | cut -d'|' -f2) + MEM_PERC=$(echo "$stats" | cut -d'|' -f3) + else + CPU_USAGE="N/A" + MEM_USAGE="N/A" + MEM_PERC="N/A" + fi + else + CPU_USAGE="N/A" + MEM_USAGE="N/A" + MEM_PERC="N/A" + fi +} + +show_dashboard() { + local stop_dashboard=0 + # Setup trap to catch signals gracefully + trap 'stop_dashboard=1' SIGINT SIGTERM + + echo -ne "\033[?25l" # Hide cursor + clear + + while [ $stop_dashboard -eq 0 ]; do + echo -ne "\033[H" # Move cursor to top-left + print_live_stats_header + + show_status + + # Get and show resource usage + get_container_resources + echo "" + echo -e "${CYAN}═══ RESOURCE USAGE ═══${NC}" + echo -e " CPU: ${YELLOW}${CPU_USAGE}${NC}" + echo -e " Memory: ${YELLOW}${MEM_USAGE}${NC} (${MEM_PERC})" + echo "" + echo -e "${BOLD}Refreshes every 10 seconds. Press any key to return to menu...${NC}" + + # Clear any leftover content below + tput ed 2>/dev/null || true + + # Wait 10 seconds for keypress. Signal will interrupt this read. + if read -t 10 -n 1; then + stop_dashboard=1 + fi + done + + echo -ne "\033[?25h" # Show cursor + trap - SIGINT SIGTERM # Reset traps +} + +show_live_stats() { + print_header + echo -e "${YELLOW}Reading traffic history...${NC}" + echo -e "${CYAN}Press Ctrl+C to return to menu${NC}" + echo "" + # Stream logs, filter for [STATS], and strip everything before [STATS] + docker logs -f --tail 200 conduit 2>&1 | grep --line-buffered "\[STATS\]" | sed -u -e 's/.*\[STATS\]/[STATS]/' +} + +show_status() { + echo "" + echo -e "${CYAN}═══ CONDUIT STATUS ═══${NC}" + + if docker ps 2>/dev/null | grep -q "[[:space:]]conduit$"; then + echo -e " Container: ${GREEN}Running${NC}" + + # Get last stats from logs + local stats=$(docker logs --tail 100 conduit 2>&1 | grep "STATS" | tail -1) + if [ -n "$stats" ]; then + local connected=$(echo "$stats" | sed -n 's/.*Connected:[[:space:]]*\([0-9]*\).*/\1/p') + local upload=$(echo "$stats" | sed -n 's/.*Up:[[:space:]]*\([^|]*\).*/\1/p' | tr -d ' ') + local download=$(echo "$stats" | sed -n 's/.*Down:[[:space:]]*\([^|]*\).*/\1/p' | tr -d ' ') + [ -n "$connected" ] && echo -e " Clients: ${GREEN}${connected}${NC} connected" + [ -n "$upload" ] && echo -e " Upload: ${upload}" + [ -n "$download" ] && echo -e " Download: ${download}" + else + echo -e " Stats: ${YELLOW}Waiting for first stats...${NC}" + fi + + else + echo -e " Container: ${RED}Stopped${NC}" + fi + + echo "" + echo -e "${CYAN}═══ SETTINGS ═══${NC}" + echo -e " Max Clients: ${MAX_CLIENTS}" + echo -e " Bandwidth: ${BANDWIDTH} Mbps" + + 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}" + echo -e " Bandwidth: ${BANDWIDTH} Mbps" + echo "" + + read -p "New max-clients (1-1000) [${MAX_CLIENTS}]: " new_clients < /dev/tty || true + read -p "New bandwidth in Mbps (1-40) [${BANDWIDTH}]: " new_bandwidth < /dev/tty || true + + # 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" =~ ^[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}" + echo -e " Bandwidth: ${BANDWIDTH} Mbps" + 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 (Live CPU/RAM)" + 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 " 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 + ;; + 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-7, 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 ;; + settings) change_settings ;; + uninstall) uninstall_all ;; + help|-h|--help) show_help ;; + menu|*) show_menu ;; +esac +MANAGEMENT + + chmod +x $INSTALL_DIR/conduit + # Force create symlink (remove existing first) + 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() { + # Determine which init system was used + 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}" + echo -e "${GREEN}║${NC} Max Clients: ${CYAN}${MAX_CLIENTS}${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} Bandwidth: ${CYAN}${BANDWIDTH} Mbps${NC} ${GREEN}║${NC}" + echo -e "${GREEN}║${NC} Auto-start: ${CYAN}${init_type}${NC} ${GREEN}║${NC}" + 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 + + # 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..." + # Continue with installation below + ;; + 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 + + + check_dependencies + + # 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 + + # Ask if user wants to view live stats + 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.0 +# ############################################################################### +main "$@"