Skip to main content

Raytunnel 🚀

Raytunnel là một giải pháp reverse tunnel (HTTP và SSH forwarding) siêu nhẹ, bảo mật và được thiết kế tối giản nhằm thay thế cho Gradio/Ngrok. Thích hợp nhất cho việc tương tác giữa máy cá nhân và các remote instance (Kaggle, Colab, Cloud Server) kết nối trực tiếp về hệ thống Homelab.


🌟 Tính năng nổi bật

  • Tối giản & Tự chủ: Hoạt động hoàn toàn trong user-space, không cần cấu hình tài khoản Unix hay SSH keys phức tạp trên Host OS của server.
  • Tự động cấu hình SSH Daemon: Client tự phát hiện, cấu hình và khởi chạy SSH Daemon (sshd) ở cổng 2222, tạo cặp khóa SSH động giúp bạn SSH trực tiếp vào remote instance (ví dụ: Kaggle) cực kỳ tiện lợi.
  • Dynamic HTTP Host Routing: Tự động phân tích trường Host trong HTTP request để điều hướng tới đúng client đăng ký (ví dụ: my-worker.s.yourdomain.com).
  • Thân thiện với Notebook: Có sẵn API bất đồng bộ chạy ngầm (background=True) để bạn gọi trực tiếp trong cell của Jupyter/Kaggle notebook mà không làm nghẽn luồng xử lý chính.
  • Bảo mật tuyệt đối:
    • Xác thực kết nối điều khiển bằng Token bí mật.
    • SSH daemon được cấu hình chỉ nhận khóa public key, cấm đăng nhập bằng mật khẩu (PasswordAuthentication no).

📐 Kiến trúc hoạt động

sequenceDiagram
    participant Client as Kaggle / Remote Worker
    participant Server as Raytunnel Server (s.yourdomain.com)
    participant Proxy as Reverse Proxy (Traefik / NPM)
    participant User as Terminal / Trình duyệt

    Client->>Server: Kết nối wss://s.yourdomain.com/ws/control + Auth Token
    Server-->>Client: Trả về Subdomain: client-abc, SSH Port: 2200
    
    Note over User,Client: Chuyển tiếp HTTP Web App
    User->>Proxy: Truy cập https://client-abc.s.yourdomain.com
    Proxy->>Server: Forward HTTP → cổng 8000 (TCP router)
    Server->>Client: Yêu cầu mở kênh HTTP
    Client->>Server: Kết nối wss://s.yourdomain.com/ws/data?channel_id=xxx
    Client->>Client: Forward tới web app local (cổng 8000)
    Server-->>Client: Chuyển tiếp dữ liệu hai chiều (TCP <-> WS)

    Note over User,Client: Kết nối SSH Terminal
    User->>Server: Chạy ssh -p 2200 user@s.yourdomain.com
    Server->>Client: Yêu cầu mở kênh SSH
    Client->>Server: Kết nối wss://s.yourdomain.com/ws/data?channel_id=yyy
    Client->>Client: Forward tới SSH Daemon nội bộ (cổng 2222)
    Server-->>Client: Chuyển tiếp luồng terminal hai chiều

Phân vai các cổng

Cổng Mục đích
8001 FastAPI API + WebSocket control (/ws/control, /ws/data) + Dashboard
8000 TCP raw proxy — đọc Host header và route HTTP traffic đến đúng client
2200-2300 Dải cổng SSH động được server cấp phát cho từng tunnel

[!IMPORTANT] Reverse proxy cần 2 route riêng biệt: s.yourdomain.com → cổng 8001 (control WebSocket + Dashboard), *.s.yourdomain.com → cổng 8000 (HTTP tunnel traffic). Nếu route tất cả về 8000, client sẽ gặp lỗi HTTP 404 khi kết nối WebSocket control.


📦 Cấu trúc dự án

raytunnel/
├── src/                        # Source code Python
├── Dockerfile                  # Docker image definition
├── compose.yaml                # Docker Compose config
├── example-env                 # Template file biến môi trường
├── raytunnel-server.service    # Systemd service file
├── pyproject.toml
└── README.md

📥 Cài đặt

Cài từ PyPI (khuyên dùng)

uv pip install raytunnel
# hoặc: pip install raytunnel

Cài từ source (GitHub)

uv pip install git+https://github.com/mrtruongleo/raytunnel.git
# hoặc: pip install git+https://github.com/mrtruongleo/raytunnel.git

Cài ở chế độ editable (phát triển local)

git clone https://github.com/mrtruongleo/raytunnel.git
cd raytunnel
uv pip install -e .

🛠 Hướng dẫn Cấu hình & Triển khai

1. Triển khai Server (Trên Proxmox Container / VPS)

Bạn có ba cách để chạy server:

Cách A: Chạy trực tiếp qua Systemd Service (Khuyên dùng)

  1. Cài đặt package raytunnel trên container:
    uv pip install raytunnel
    # hoặc: pip install raytunnel
    
  2. Copy file raytunnel-server.service vào /etc/systemd/system/:
    cp raytunnel-server.service /etc/systemd/system/
    
  3. Chỉnh sửa dòng ExecStart trong file service — thêm token và domain thực:
    ExecStart=/usr/local/bin/raytunnel server --host 0.0.0.0 --port 8001 --tcp-port 8000 --token your_very_strong_token --domain s.yourdomain.com
    
  4. Kích hoạt và chạy dịch vụ:
    sudo systemctl daemon-reload
    sudo systemctl enable --now raytunnel-server
    sudo systemctl status raytunnel-server
    

Cách B: Chạy qua Docker Compose

  1. Copy file cấu hình môi trường:
    cp example-env .env
    
  2. Chỉnh sửa .env với token và domain thực của bạn:
    RAYTUNNEL_TOKEN=your_very_strong_token
    DOMAIN=s.yourdomain.com
    
  3. Build và chạy từ thư mục gốc của project:
    docker compose up -d --build
    

Verify sau khi start:

docker logs raytunnel-server --tail 20
curl http://localhost:8001   # → Dashboard hiện "No active tunnels"

[!WARNING] Không dùng shell variable ${RAYTUNNEL_TOKEN:-default} trong compose.yaml nếu bạn không set env trước khi chạy — sẽ dùng giá trị default thay vì token thực. Luôn dùng file .env hoặc hardcode trực tiếp.

Cách C: Chạy qua Docker CLI trực tiếp

docker build -t raytunnel-server .
docker run -d --name raytunnel \
  -p 8000:8000 -p 8001:8001 -p 2200-2300:2200-2300 \
  -e RAYTUNNEL_TOKEN="your_very_strong_token" \
  -e DOMAIN="s.yourdomain.com" \
  raytunnel-server

2. Cấu hình DNS & Reverse Proxy

2.1 DNS trên Cloudflare

Tạo 2 bản ghi CNAME (hoặc A) trỏ về IP public của homelab:

Type Name Target Proxy
CNAME s ip.yourdomain.com Proxied ☁️
CNAME *.s ip.yourdomain.com DNS only 🔘

[!WARNING] Cloudflare Free plan không hỗ trợ Proxied wildcard — bắt buộc để *.sDNS only (grey cloud). Verify bằng: dig rvc.s.yourdomain.com @1.1.1.1 +short

2.2 Port Forwarding trên Router

External Port Internal IP Internal Port Protocol
443 IP của reverse proxy 443 TCP
2200-2300 IP của raytunnel server 2200-2300 TCP

[!NOTE] Cổng 2200-2300 chỉ thực sự listen sau khi có client kết nối. Connection refused trên cổng này là bình thường khi chưa có tunnel nào hoạt động.


2.3 Cấu hình Traefik (Nếu bạn dùng Traefik)

Thêm service raytunnel vào Traefik bằng cách tạo một file config động trong thư mục dynamic config của Traefik (ví dụ: ./external/raytunnel.yaml):

http:
  routers:
    # Control plane: WebSocket /ws/control + Dashboard — cổng 8001 (FastAPI)
    raytunnel-control:
      rule: "Host(`s.yourdomain.com`)"
      entryPoints:
        - websecure
      service: raytunnel-api
      tls:
        certResolver: production

    # Tunnel traffic: worker.s.yourdomain.com — cổng 8000 (TCP router)
    raytunnel-tunnel:
      rule: "HostRegexp(`{subdomain:[a-z0-9-]+}.s.yourdomain.com`)"
      entryPoints:
        - websecure
      service: raytunnel-tcp
      tls:
        certResolver: production

  services:
    raytunnel-api:
      loadBalancer:
        servers:
          - url: "http://<IP_RAYTUNNEL_SERVER>:8001"   # Control/WS API

    raytunnel-tcp:
      loadBalancer:
        servers:
          - url: "http://<IP_RAYTUNNEL_SERVER>:8000"   # HTTP tunnel routing

[!NOTE] Thay <IP_RAYTUNNEL_SERVER> bằng IP thực của container/VPS đang chạy raytunnel server. Đảm bảo thư mục dynamic config của Traefik đang được watch (thường cấu hình qua providers.file.directory trong traefik.yaml).


2.4 Cấu hình Nginx Proxy Manager (Nếu bạn dùng NPM)

Nginx Proxy Manager cần 2 Proxy Host riêng biệt. Cả hai đều cần bật SSL với Let's Encrypt.

Proxy Host 1 — Control Plane (s.yourdomain.com)

Vào Proxy Hosts → Add Proxy Host, điền như sau:

Trường Giá trị
Domain Names s.yourdomain.com
Scheme http
Forward Hostname/IP <IP_RAYTUNNEL_SERVER>
Forward Port 8001
Websockets Support ✅ Bật
Block Common Exploits ✅ Bật

Tab SSL: Chọn Request a new SSL Certificate, bật Force SSLHTTP/2 Support.

Proxy Host 2 — Tunnel Traffic (*.s.yourdomain.com)

Vào Proxy Hosts → Add Proxy Host, điền như sau:

Trường Giá trị
Domain Names *.s.yourdomain.com
Scheme http
Forward Hostname/IP <IP_RAYTUNNEL_SERVER>
Forward Port 8000
Websockets Support ✅ Bật
Block Common Exploits ✅ Bật

Tab SSL: Chọn Request a new SSL Certificate — NPM sẽ issue wildcard cert qua DNS Challenge.

[!IMPORTANT] Wildcard SSL (*.s.yourdomain.com) bắt buộc phải dùng DNS Challenge (HTTP Challenge không hỗ trợ wildcard). Trong NPM, tab SSL → chọn DNS Challenge provider (ví dụ Cloudflare) → nhập API Token Cloudflare có quyền Zone:DNS:Edit.

[!WARNING] NPM không thực hiện routing theo subdomain ở tầng ứng dụng — toàn bộ *.s.yourdomain.com được forward thẳng về cổng 8000 của raytunnel server, và raytunnel server tự xử lý routing dựa vào Host header. Đây là hành vi đúng và mong muốn.

Tab Advanced của Proxy Host 2, thêm custom Nginx config để đảm bảo Host header được truyền đúng:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

3. Cài đặt & Sử dụng Client (Trên Kaggle / Colab / Máy trạm)

# Cài đặt từ PyPI (khuyên dùng)
uv pip install raytunnel
# hoặc: pip install raytunnel

# Hoặc cài từ GitHub (lấy code mới nhất chưa release)
uv pip install git+https://github.com/mrtruongleo/raytunnel.git

Cách A: Sử dụng Command Line (CLI)

raytunnel client --server s.yourdomain.com --token your_very_strong_token --port 8000 --ssh --subdomain my-kaggle-job

Cách B: Chạy Programmatic trong Python / Jupyter Notebook

[!WARNING] Kaggle không hỗ trợ background process qua !command & — sẽ gặp lỗi OSError: Background processes not supported. Phải dùng subprocess.Popen.

import subprocess, time

proc = subprocess.Popen(
    [
        "raytunnel", "client",
        "--server", "s.yourdomain.com",
        "--token", "your_very_strong_token",
        "--port", "8000",          # Cổng local của Web App (RVC, Gradio, v.v.)
        "--ssh",                   # Bật SSH Terminal
        "--subdomain", "my-worker" # Subdomain mong muốn
    ],
    stdout=open("raytunnel.log", "w"),
    stderr=subprocess.STDOUT
)
print(f"Raytunnel PID: {proc.pid}")

# Chờ kết nối thiết lập
time.sleep(8)
with open("raytunnel.log") as f:
    print(f.read())

Kết quả mong đợi trong log:

✔ Raytunnel Established Successfully!
HTTP Web App URL: https://my-worker.s.yourdomain.com
SSH Command: ssh -i ~/.ssh/raytunnel_id_ed25519 -p 2200 root@s.yourdomain.com

Dừng tunnel:

proc.terminate()

🔒 Cơ chế Bảo mật của SSH

Khi bạn khởi chạy client với tùy chọn --ssh:

  1. Client kiểm tra và tự động cài đặt openssh-server nếu môi trường chưa có.
  2. Sinh một cặp khóa SSH tạm thời: Private key ~/.ssh/raytunnel_id_ed25519 và Public key được tự động thêm vào ~/.ssh/authorized_keys.
  3. Khởi chạy sshd ở cổng 2222 trong phạm vi user-space.
  4. Server cấp phát một cổng SSH ngẫu nhiên trong dải 2200-2300 chuyển tiếp về client.
  5. Để kết nối SSH vào Kaggle từ máy cá nhân:
    # Copy private key từ Kaggle về máy, sau đó:
    ssh -i raytunnel_id_ed25519 -p 2200 root@s.yourdomain.com
    
    Server cấm đăng nhập bằng mật khẩu và chỉ nhận đúng khóa đã đăng ký — đảm bảo không ai brute-force được.

🌐 Sử dụng Tunnel sau khi kết nối

Sau khi client kết nối thành công, log sẽ in ra thông tin cần dùng:

✔ Raytunnel Established Successfully!
HTTP Web App URL : https://my-worker.s.yourdomain.com
SSH Command      : ssh -i ~/.ssh/raytunnel_id_ed25519 -p 2200 root@s.yourdomain.com

🖥 Truy cập Web App qua HTTP Tunnel

Mở trình duyệt và truy cập URL được in ra — web app chạy trên remote instance (Gradio, FastAPI, RVC, v.v.) sẽ hiển thị như một trang web bình thường:

https://my-worker.s.yourdomain.com

Tất cả HTTP/HTTPS request (kể cả WebSocket) đều được tunnel tự động. Không cần cấu hình thêm bất cứ điều gì phía ứng dụng.


🔌 Tích hợp API từ máy local

Raytunnel là tunnel trong suốt — toàn bộ HTTP traffic được chuyển tiếp nguyên vẹn đến app của bạn đang chạy trên remote. Raytunnel không tự cung cấp bất kỳ endpoint nghiệp vụ nào.

Endpoint nào có thể gọi hoàn toàn phụ thuộc vào web app bạn đang chạy trên Kaggle/Colab (FastAPI, Gradio, Flask, v.v.):

import requests

# URL do raytunnel cung cấp, nhưng /your-endpoint là do app của bạn định nghĩa
BASE_URL = "https://my-worker.s.yourdomain.com"

response = requests.post(f"{BASE_URL}/your-endpoint", json={"key": "value"})
print(response.json())

Ví dụ nếu app của bạn là một FastAPI server expose /predict:

# Trên remote (Kaggle) — app FastAPI của bạn
# GET /health  →  {"status": "ok"}
# POST /predict  →  {"result": ...}

# Trên máy local — gọi qua tunnel bình thường
response = requests.get(f"{BASE_URL}/health")
response = requests.post(f"{BASE_URL}/predict", json={"data": [1, 2, 3]})

Với WebSocket:

import asyncio, websockets

async def stream():
    async with websockets.connect("wss://my-worker.s.yourdomain.com/ws") as ws:
        await ws.send("hello")
        msg = await ws.recv()
        print(msg)

asyncio.run(stream())

🔑 Kết nối SSH Terminal

Sau khi client in ra SSH Command, copy private key từ remote về máy local trước:

# 1. Copy private key từ remote về máy local qua SCP
scp -i ~/.ssh/raytunnel_id_ed25519 -P 2200 \
    root@s.yourdomain.com:~/.ssh/raytunnel_id_ed25519 \
    ~/raytunnel_id_ed25519

# 2. Set quyền đúng cho key
chmod 600 ~/raytunnel_id_ed25519

Hoặc nếu đang dùng Kaggle Notebook, lấy key bằng Python:

# Chạy trong Kaggle cell để in ra private key
with open("/root/.ssh/raytunnel_id_ed25519") as f:
    print(f.read())
# → Copy nội dung, paste vào file ~/raytunnel_id_ed25519 trên máy local

Sau đó SSH vào:

ssh -i ~/raytunnel_id_ed25519 -p 2200 root@s.yourdomain.com

Thêm vào ~/.ssh/config để gõ lệnh ngắn hơn:

Host kaggle-worker
    HostName       s.yourdomain.com
    Port           2200
    User           root
    IdentityFile   ~/raytunnel_id_ed25519
    StrictHostKeyChecking no
# Sau đó chỉ cần:
ssh kaggle-worker

💻 Kết nối VS Code Remote SSH

  1. Cài extension Remote - SSH trong VS Code.
  2. Thêm config SSH như trên vào ~/.ssh/config.
  3. Nhấn Ctrl+Shift+PRemote-SSH: Connect to Host → chọn kaggle-worker.
  4. VS Code sẽ mở một cửa sổ editor hoàn toàn chạy trên remote instance — có thể duyệt file, chỉnh sửa code, dùng terminal như trên máy local.

📁 Truyền File qua SCP

# Upload file từ máy local lên remote
scp -i ~/raytunnel_id_ed25519 -P 2200 \
    ./local_file.wav \
    root@s.yourdomain.com:/kaggle/working/

# Download file từ remote về máy local
scp -i ~/raytunnel_id_ed25519 -P 2200 \
    root@s.yourdomain.com:/kaggle/working/output.wav \
    ./output.wav

Hoặc dùng rsync để đồng bộ thư mục:

rsync -avz -e "ssh -i ~/raytunnel_id_ed25519 -p 2200" \
    ./local_dir/ \
    root@s.yourdomain.com:/kaggle/working/

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

raytunnel-0.1.4.tar.gz (58.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

raytunnel-0.1.4-py3-none-any.whl (22.3 kB view details)

Uploaded Python 3

File details

Details for the file raytunnel-0.1.4.tar.gz.

File metadata

  • Download URL: raytunnel-0.1.4.tar.gz
  • Upload date:
  • Size: 58.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for raytunnel-0.1.4.tar.gz
Algorithm Hash digest
SHA256 cdc2b3f177497c8732f8917298ee31a903fb2e0f5bb201f12d9ca5bcf4e04425
MD5 9c2dbb9f4da88f6d7cb8275ece458bf8
BLAKE2b-256 1896a9dbeda59c52a7131d56678262f26d3c3b7d63f703f13f2242e78d802851

See more details on using hashes here.

File details

Details for the file raytunnel-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: raytunnel-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 22.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for raytunnel-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 ea5b80183b47d69989432c76c2da70a5337f24b7b9c77b77ed375a8ea1f7f7af
MD5 699abc2f8ae2d2b819662203bb2fc7f0
BLAKE2b-256 b8d93035503239628c44a39cb8b4a99d41322acd6255ba09e94fdea15f4ff96a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page