#!/bin/bash # # ╔═══════════════════════════════════════════════════════════════════╗ # ║ 🚀 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://raw.githubusercontent.com/SamNet-dev/conduit-manager/main/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 "╔═══════════════════════════════════════════════════════════════════╗" printf "║ 🚀 PSIPHON CONDUIT MANAGER v%-5s ║\n" "${VERSION}" echo "╚═══════════════════════════════════════════════════════════════════╝" echo -e "${NC}" } print_live_stats_header() { echo -e "${CYAN}" echo "╔═══════════════════════════════════════════════════════════════════╗" echo "║ CONDUIT LIVE STATISTICS ║" echo "╠═══════════════════════════════════════════════════════════════════╣" printf "║ Max Clients: ${GREEN}%-52s${CYAN}║\n" "${MAX_CLIENTS}" printf "║ Bandwidth: ${GREEN}%-52s${CYAN}║\n" "${BANDWIDTH} Mbps" 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 # 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) instead of just home escape code tput cup 0 0 2>/dev/null || echo -ne "\033[H" print_live_stats_header show_status "live" # Get and show resource usage get_container_resources echo "" echo -e "${CYAN}═══ RESOURCE USAGE ═══${NC}\033[K" echo -e " CPU: ${YELLOW}${CPU_USAGE}${NC}\033[K" echo -e " Memory: ${YELLOW}${MEM_USAGE}${NC} (${MEM_PERC})\033[K" echo "" echo -e "${BOLD}Refreshes every 10 seconds. Press any key to return to menu...${NC}\033[K" # Clear any leftover content below (Erase Down) 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 # Restore main screen buffer tput rmcup 2>/dev/null || true 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() { local mode="${1:-normal}" # 'live' mode adds line clearing local EL="" if [ "$mode" == "live" ]; then EL="\033[K" # Erase Line escape code fi echo "" echo -e "${CYAN}═══ CONDUIT STATUS ═══${NC}${EL}" if docker ps 2>/dev/null | grep -q "[[:space:]]conduit$"; then if [ -n "$stats" ]; then # Already have stats from check below? # Actually, let's fetch it here with deeper history local stats=$(docker logs --tail 1000 conduit 2>&1 | grep "STATS" | tail -1) else local stats=$(docker logs --tail 1000 conduit 2>&1 | grep "STATS" | tail -1) fi if [ -n "$stats" ]; then local connecting=$(echo "$stats" | sed -n 's/.*Connecting:[[:space:]]*\([0-9]*\).*/\1/p') local connected=$(echo "$stats" | sed -n 's/.*Connected:[[:space:]]*\([0-9]*\).*/\1/p') local upload=$(echo "$stats" | sed -n 's/.*Up:[[:space:]]*\([^|]*\).*/\1/p' | xargs) local download=$(echo "$stats" | sed -n 's/.*Down:[[:space:]]*\([^|]*\).*/\1/p' | xargs) local uptime=$(echo "$stats" | sed -n 's/.*Uptime:[[:space:]]*\(.*\)/\1/p' | xargs) [ -n "$uptime" ] && echo -e " Container: ${GREEN}Running${NC} (${CYAN}Uptime: ${uptime}${NC})${EL}" || echo -e " Container: ${GREEN}Running${NC}${EL}" # Default to 0 if missing/empty connecting=${connecting:-0} connected=${connected:-0} echo -e " Clients: ${GREEN}${connected}${NC} connected, ${YELLOW}${connecting}${NC} connecting${EL}" [ -n "$upload" ] && echo -e " Upload: ${CYAN}${upload}${NC}${EL}" [ -n "$download" ] && echo -e " Download: ${CYAN}${download}${NC}${EL}" else echo -e " Container: ${GREEN}Running${NC}${EL}" echo -e " Stats: ${YELLOW}Waiting for first stats...${NC}${EL}" fi else echo -e " Container: ${RED}Stopped${NC}${EL}" fi echo "" echo -e "${CYAN}═══ SETTINGS ═══${NC}${EL}" echo -e " Max Clients: ${MAX_CLIENTS}${EL}" echo -e " Bandwidth: ${BANDWIDTH} Mbps${EL}" 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 "$@"