's'
This commit is contained in:
152
scripts/dev-tunnel.py
Normal file
152
scripts/dev-tunnel.py
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Windows / macOS 本地开发:SSH 隧道把线上 MinIO/PocketBase 映射到本机。
|
||||
|
||||
默认映射:
|
||||
localhost:9100 -> 服务器 127.0.0.1:9100 (MinIO S3 API)
|
||||
localhost:8090 -> 服务器 127.0.0.1:8090 (PocketBase,可选)
|
||||
|
||||
用法:
|
||||
python scripts/dev-tunnel.py
|
||||
|
||||
环境变量(可写在 .env.local):
|
||||
DEV_SSH_HOST=82.157.112.245
|
||||
DEV_SSH_USER=ubuntu
|
||||
DEV_SSH_PASSWORD=你的密码
|
||||
DEV_TUNNEL_MINIO_PORT=9100
|
||||
DEV_TUNNEL_POCKETBASE=0 # 设为 1 时同时映射 8090
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import paramiko
|
||||
except ImportError:
|
||||
print("请先安装 paramiko: pip install paramiko", file=sys.stderr)
|
||||
raise
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _read_env_file(path: Path) -> dict[str, str]:
|
||||
values: dict[str, str] = {}
|
||||
if not path.exists():
|
||||
return values
|
||||
for raw in path.read_text(encoding="utf-8").splitlines():
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
values[key.strip()] = value.strip().strip('"').strip("'")
|
||||
return values
|
||||
|
||||
|
||||
def load_env() -> None:
|
||||
merged: dict[str, str] = {}
|
||||
for path in [ROOT / ".env.local", ROOT / ".env", ROOT / "backend" / ".env"]:
|
||||
merged.update(_read_env_file(path))
|
||||
for key, value in merged.items():
|
||||
os.environ.setdefault(key, value)
|
||||
|
||||
|
||||
def forward_tunnel(local_port: int, remote_host: str, remote_port: int, transport) -> None:
|
||||
def handle(client: socket.socket, addr) -> None:
|
||||
try:
|
||||
chan = transport.open_channel("direct-tcpip", (remote_host, remote_port), addr)
|
||||
except Exception as exc:
|
||||
print(f"[tunnel] open_channel failed {local_port}->{remote_host}:{remote_port}: {exc}")
|
||||
client.close()
|
||||
return
|
||||
|
||||
def relay(src, dst) -> None:
|
||||
try:
|
||||
while True:
|
||||
data = src.recv(1024 * 64)
|
||||
if not data:
|
||||
break
|
||||
dst.sendall(data)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
src.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
dst.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Thread(target=relay, args=(client, chan), daemon=True).start()
|
||||
threading.Thread(target=relay, args=(chan, client), daemon=True).start()
|
||||
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind(("127.0.0.1", local_port))
|
||||
server.listen(32)
|
||||
print(f"[tunnel] listening 127.0.0.1:{local_port} -> {remote_host}:{remote_port}")
|
||||
while True:
|
||||
client_sock, addr = server.accept()
|
||||
threading.Thread(
|
||||
target=handle,
|
||||
args=(client_sock, addr),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
load_env()
|
||||
|
||||
host = os.getenv("DEV_SSH_HOST", "82.157.112.245")
|
||||
user = os.getenv("DEV_SSH_USER", "ubuntu")
|
||||
password = os.getenv("DEV_SSH_PASSWORD", "")
|
||||
if not password:
|
||||
print("缺少 DEV_SSH_PASSWORD,请在 .env.local 中配置", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
minio_local = int(os.getenv("DEV_TUNNEL_MINIO_PORT", "9100"))
|
||||
pb_enabled = os.getenv("DEV_TUNNEL_POCKETBASE", "0").lower() in {"1", "true", "yes"}
|
||||
pb_local = int(os.getenv("DEV_TUNNEL_POCKETBASE_PORT", "8090"))
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
print(f"[tunnel] connecting {user}@{host} ...")
|
||||
client.connect(host, username=user, password=password, timeout=20)
|
||||
transport = client.get_transport()
|
||||
if transport is None:
|
||||
print("SSH transport unavailable", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
threading.Thread(
|
||||
target=forward_tunnel,
|
||||
args=(minio_local, "127.0.0.1", 9100, transport),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
if pb_enabled:
|
||||
threading.Thread(
|
||||
target=forward_tunnel,
|
||||
args=(pb_local, "127.0.0.1", 8090, transport),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
print("[tunnel] ready. Ctrl+C to stop.")
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\n[tunnel] stopping")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user