100 lines
3.2 KiB
Python
100 lines
3.2 KiB
Python
"""Cloudflared binary discovery and download helpers."""
|
|
|
|
import os
|
|
import platform
|
|
import shutil
|
|
import stat
|
|
from urllib import error as urlerror
|
|
from urllib import request
|
|
from urllib.parse import urlsplit
|
|
|
|
from ..logging import debug_log
|
|
|
|
|
|
def _get_project_root():
|
|
return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
|
|
|
|
|
def _get_cloudflared_dir():
|
|
return os.path.join(_get_project_root(), "bin")
|
|
|
|
|
|
def _get_platform_binary_name():
|
|
system = platform.system().lower()
|
|
machine = platform.machine().lower()
|
|
|
|
if system == "windows":
|
|
if "arm" in machine:
|
|
return "cloudflared-windows-arm64.exe"
|
|
return "cloudflared-windows-amd64.exe"
|
|
if system == "darwin":
|
|
if machine in ("arm64", "aarch64"):
|
|
return "cloudflared-darwin-arm64"
|
|
return "cloudflared-darwin-amd64"
|
|
if system == "linux":
|
|
if machine in ("arm64", "aarch64"):
|
|
return "cloudflared-linux-arm64"
|
|
return "cloudflared-linux-amd64"
|
|
|
|
raise RuntimeError(f"Unsupported platform for cloudflared: {system}/{machine}")
|
|
|
|
|
|
def _get_binary_path(bin_dir=None):
|
|
bin_dir = bin_dir or _get_cloudflared_dir()
|
|
binary_name = "cloudflared.exe" if platform.system().lower() == "windows" else "cloudflared"
|
|
return os.path.join(bin_dir, binary_name)
|
|
|
|
|
|
def _get_release_download_base() -> str:
|
|
"""Resolve cloudflared release download base URL from environment/config."""
|
|
configured = (os.environ.get("CLOUDFLARED_RELEASE_BASE") or "").strip()
|
|
if configured:
|
|
return configured.rstrip("/")
|
|
return "github.com/cloudflare/cloudflared/releases/latest/download"
|
|
|
|
|
|
def _download_cloudflared():
|
|
asset = _get_platform_binary_name()
|
|
base = _get_release_download_base()
|
|
if "://" not in base:
|
|
scheme = (os.environ.get("CLOUDFLARED_RELEASE_SCHEME") or "https").strip() or "https"
|
|
base = f"{scheme}://{base}"
|
|
url = f"{base}/{asset}"
|
|
parsed = urlsplit(url)
|
|
if parsed.scheme not in {"http", "https"}:
|
|
raise RuntimeError(f"Unsupported cloudflared download URL scheme: {parsed.scheme}")
|
|
|
|
bin_dir = _get_cloudflared_dir()
|
|
os.makedirs(bin_dir, exist_ok=True)
|
|
target_path = _get_binary_path(bin_dir)
|
|
|
|
debug_log(f"Downloading cloudflared from {url}")
|
|
try:
|
|
with request.urlopen(url, timeout=30) as resp: # nosec B310 - scheme is allowlisted above
|
|
with open(target_path, "wb") as f:
|
|
shutil.copyfileobj(resp, f)
|
|
except urlerror.URLError as exc:
|
|
raise RuntimeError(f"Failed to download cloudflared: {exc}") from exc
|
|
|
|
st = os.stat(target_path)
|
|
os.chmod(target_path, st.st_mode | stat.S_IEXEC)
|
|
debug_log(f"Downloaded cloudflared to {target_path}")
|
|
return target_path
|
|
|
|
|
|
def ensure_binary() -> str:
|
|
"""Return a usable cloudflared binary path, downloading if necessary."""
|
|
env_path = os.environ.get("CLOUDFLARED_PATH")
|
|
if env_path and os.path.exists(env_path):
|
|
return env_path
|
|
|
|
local_candidate = _get_binary_path()
|
|
if os.path.exists(local_candidate):
|
|
return local_candidate
|
|
|
|
path_binary = shutil.which("cloudflared")
|
|
if path_binary:
|
|
return path_binary
|
|
|
|
return _download_cloudflared()
|