#!/usr/bin/env python3 import socket import threading import subprocess import os import sys import urllib.request import shutil import re import time import argparse from datetime import datetime # ================= CONFIG ================= PORT = 12345 # Port aplikasi kamu PORT_DASHBOARD = 12346 # Port dashboard status (cuma lokal) BIN = "./cloudflared" URL_DL = "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64" # Pesan yang dibalas ke client RESPONSE_TEMPLATE = """Halo dari VPS IPv6-only Lampung! Waktu: {time} Kamu dari: {client} Server: [::]:{port} Jembatan: Cloudflare Tunnel (IPv4 OK) Status: ONLINE - Bisa diakses user Telkomsel / IndiHome VPS ini cuma punya IPv6 tapi kamu bisa akses via IPv4 karena tunnel. Powered by single-file bridge. """ # ================= WARNA TERMINAL ================= GREEN = "\033[92m" YELLOW = "\033[93m" RED = "\033[91m" CYAN = "\033[96m" RESET = "\033[0m" def log(msg, color=RESET): print(f"{color}[{datetime.now().strftime('%H:%M:%S')}] {msg}{RESET}") # ================= SERVER IPv6 ================= def handle_client(conn, addr): try: data = conn.recv(4096) client_ip = addr[0] # Deteksi apakah request HTTP atau raw TCP is_http = data.startswith(b'GET') or data.startswith(b'POST') or b'HTTP' in data[:50] if is_http: body = RESPONSE_TEMPLATE.format( time=datetime.now(), client=addr, port=PORT ).encode('utf-8') # Cek path path = "/" try: path = data.split(b' ')[1].decode()[:50] except: pass if path == "/" or path.startswith("/?"): resp_body = body elif path == "/status": status_info = f"""STATUS VPS Port: {PORT} Time: {datetime.now()} Active connections: OK Tunnel: Running IPv6: OK IPv4 Bridge: OK via Cloudflare """ resp_body = status_info.encode() else: resp_body = f"Path {path} tidak ada. Coba / atau /status\n".encode() response = ( b"HTTP/1.1 200 OK\r\n" b"Content-Type: text/plain; charset=utf-8\r\n" b"Access-Control-Allow-Origin: *\r\n" b"Content-Length: " + str(len(resp_body)).encode() + b"\r\n" b"\r\n" + resp_body ) conn.sendall(response) else: # Raw TCP - bales simple conn.sendall(f"Hello dari VPS IPv6-only! Kamu {addr}\n".encode()) except Exception as e: log(f"Error handle {addr}: {e}", RED) finally: conn.close() def run_ipv6_server(): while True: try: s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) except: pass s.bind(('::', PORT)) s.listen(200) log(f"SERVER IPv6 jalan di [::]:{PORT}", GREEN) while True: conn, addr = s.accept() threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start() except OSError as e: log(f"SERVER gagal bind: {e} - retry 3 detik", RED) time.sleep(3) except Exception as e: log(f"SERVER crash: {e} - restart", RED) time.sleep(2) # ================= CLOUDFLARED ================= def ensure_cloudflared(): if shutil.which("cloudflared"): return "cloudflared" if os.path.exists(BIN): return BIN log("Download cloudflared...", YELLOW) try: urllib.request.urlretrieve(URL_DL, BIN) os.chmod(BIN, 0o755) log("cloudflared terdownload", GREEN) return BIN except Exception as e: log(f"Gagal download cloudflared: {e}", RED) log("Install manual: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/", YELLOW) sys.exit(1) def run_tunnel_loop(): bin_path = ensure_cloudflared() while True: try: cmd = [bin_path, "tunnel", "--url", f"http://localhost:{PORT}"] log(f"Menjalankan tunnel: {' '.join(cmd)}", CYAN) proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1 ) public_url = None for line in proc.stdout: print(line, end="") # Deteksi URL publik m = re.search(r"https://[a-z0-9-]+\.trycloudflare\.com", line) if m and not public_url: public_url = m.group(0) print("") print(f"{GREEN}{'='*70}") print(f" 🚀 VPS IPv6 KAMU SUDAH BISA DIAKSES IPv4!") print(f"{'='*70}{RESET}") print(f"{CYAN} URL PUBLIK (bisa dibuka Telkomsel/IndiHome):{RESET}") print(f" {GREEN} {public_url}{RESET}") print(f" {GREEN} {public_url.replace('https://', 'http://')}{RESET}") print("") print(f"{YELLOW} Test dari HP:") print(f" curl {public_url}") print(f" curl {public_url}/status") print("") print(f" Dashboard lokal: http://[::1]:{PORT_DASHBOARD} (cuma di VPS)") print("") print(f" Kalau install via systemd, cek URL pakai:") print(f" sudo journalctl -u vps-ipv6-bridge -f") print(f"{GREEN}{'='*70}{RESET}") print("") log(f"Tunnel mati dengan code {proc.returncode}, restart 5 detik...", RED) time.sleep(5) except Exception as e: log(f"Tunnel error: {e} - retry 5 detik", RED) time.sleep(5) # ================= DASHBOARD LOKAL (BONUS) ================= def run_dashboard(): """Dashboard kecil di port 12346 buat cek status dari VPS""" def dashboard_handle(conn, addr): try: conn.recv(1024) body = f""" === DASHBOARD VPS IPv6 POWERFULL === Time: {datetime.now()} Main App: [::]:{PORT} - RUNNING Tunnel: Cloudflare Quick Tunnel - RUNNING IPv6: OK IPv4 Bridge: OK (via Cloudflare) Endpoint: - / -> Halaman utama - /status -> Status server Public URL cek di log tunnel di terminal utama. ===================================== """.encode() resp = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n" % len(body) + body conn.sendall(resp) except: pass finally: conn.close() try: s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('::', PORT_DASHBOARD)) s.listen(10) log(f"Dashboard jalan di [::]:{PORT_DASHBOARD}", CYAN) while True: c, a = s.accept() threading.Thread(target=dashboard_handle, args=(c,a), daemon=True).start() except: pass # ================= INSTALL SYSTEMD ================= def install_systemd(): print("=== INSTALL PERMANEN ===") current_path = os.path.abspath(__file__) service_content = f"""[Unit] Description=VPS IPv6 Powerfull Bridge After=network.target [Service] WorkingDirectory={os.getcwd()} ExecStart=/usr/bin/python3 {current_path} Restart=always RestartSec=5 [Install] WantedBy=multi-user.target """ service_file = "/etc/systemd/system/vps-ipv6-bridge.service" try: with open(service_file, 'w') as f: f.write(service_content) os.system("systemctl daemon-reload") os.system("systemctl enable vps-ipv6-bridge.service") os.system("systemctl restart vps-ipv6-bridge.service") print(f"{GREEN}Service terinstall!{RESET}") print(f"Cek log: journalctl -u vps-ipv6-bridge -f") except PermissionError: print(f"{RED}Butuh sudo: sudo python3 {current_path} --install{RESET}") sys.exit(1) # ================= MAIN ================= def main(): parser = argparse.ArgumentParser(description="VPS IPv6-only -> IPv4 Bridge Powerfull") parser.add_argument("--install", action="store_true", help="Install jadi systemd service") parser.add_argument("--port", type=int, default=PORT, help="Port aplikasi") args = parser.parse_args() global PORT PORT = args.port if args.install: install_systemd() return print(f"{CYAN}") print(" _ ______ _____ __ ___ ____ ____ _ __ _") print(" | | / / _ \/ ___/ / / / _ \___ / / /__ _____ ___ ___ ___ / / /_ __/ /__ / /_(_)") print(" | |/ / ___/\__ \ / / / ___/ _ \/ / / _ `/ __/ / _ \/ _ \/ -_) / __/ / / / _ \/ __/ / ") print(" |___/_/ /____/ /_/ /_/ \___/_/_/\_,_/_/ \___/_//_/\__/_/\__/ \_,_/\___/\__/_/ ") print(f"{RESET}") print(f"{YELLOW}Single-file powerfull untuk VPS IPv6-only Lampung{RESET}") print("") # Jalanin 3 thread: server utama + dashboard + tunnel threading.Thread(target=run_ipv6_server, daemon=True).start() threading.Thread(target=run_dashboard, daemon=True).start() # Tunnel jalan di main thread biar log keliatan run_tunnel_loop() if __name__ == "__main__": main()