Spaces:
Running
Running
Download tunnel.sh from VinciTutorial/cadence: direct link, hf CLI and curl.
- Browser
- Download file 13.3 kB
-
https://huggingface.co/spaces/VinciTutorial/cadence/resolve/main/tunnel.sh
- Command line
-
hf download hf://spaces/VinciTutorial/cadence/tunnel.sh
-
curl -L -o tunnel.sh https://huggingface.co/spaces/VinciTutorial/cadence/resolve/main/tunnel.sh
13.3 kB
| # Cadence β put your local backend on a free public https URL. | |
| # No account, no credit card, no domain. All tooling is open source (Apache-2.0 / MIT). | |
| # | |
| # ./tunnel.sh normal run: checks everything, prints your https URL | |
| # bash tunnel.sh same thing (works even if the file isn't executable) | |
| # ./tunnel.sh --check diagnose only, print status, exit | |
| # ./tunnel.sh --install download cloudflared into ./bin (no sudo) | |
| # ./tunnel.sh --http2 force HTTP/2 (use when QUIC/UDP 7844 is blocked) | |
| # ./tunnel.sh --ssh fallback tunnel via ssh (localhost.run) β no cloudflared | |
| # ./tunnel.sh --with-backend also start the backend if it isn't running (installs deps if needed) | |
| # PORT=8010 ./tunnel.sh different backend port | |
| set -uo pipefail | |
| cd "$(dirname "$0")" | |
| PORT="${PORT:-8000}" | |
| BASE="http://127.0.0.1:${PORT}" | |
| BIN_DIR="./bin" | |
| MODE="run" | |
| PROTOCOL="" | |
| WITH_BACKEND=0 | |
| for arg in "$@"; do | |
| case "$arg" in | |
| --check) MODE="check" ;; | |
| --install) MODE="install" ;; | |
| --http2) PROTOCOL="--protocol http2" ;; | |
| --ssh) MODE="ssh" ;; | |
| --with-backend|--serve) WITH_BACKEND=1 ;; | |
| -h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; | |
| *) echo "unknown option: $arg (try --help)"; exit 2 ;; | |
| esac | |
| done | |
| bold() { printf '\033[1m%s\033[0m\n' "$*"; } | |
| ok() { printf ' \033[32mβ\033[0m %s\n' "$*"; } | |
| bad() { printf ' \033[31mβ\033[0m %s\n' "$*"; } | |
| warn() { printf ' \033[33m!\033[0m %s\n' "$*"; } | |
| # ββ find cloudflared: PATH, then ./bin ββββββββββββββββββββββββββββββββββββββββ | |
| find_cloudflared() { | |
| if command -v cloudflared >/dev/null 2>&1; then command -v cloudflared; return 0; fi | |
| if [ -x "${BIN_DIR}/cloudflared" ]; then echo "${BIN_DIR}/cloudflared"; return 0; fi | |
| return 1 | |
| } | |
| install_cloudflared() { | |
| os="$(uname -s)"; arch="$(uname -m)" | |
| case "$arch" in x86_64|amd64) a=amd64 ;; arm64|aarch64) a=arm64 ;; *) bad "unsupported CPU: $arch"; return 1 ;; esac | |
| mkdir -p "$BIN_DIR" | |
| if [ "$os" = "Darwin" ]; then | |
| if command -v brew >/dev/null 2>&1; then brew install cloudflared && return 0; fi | |
| url="https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-${a}.tgz" | |
| curl -fsSL "$url" | tar -xz -C "$BIN_DIR" cloudflared || return 1 | |
| elif [ "$os" = "Linux" ]; then | |
| url="https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${a}" | |
| curl -fsSL -o "${BIN_DIR}/cloudflared" "$url" || return 1 | |
| else | |
| bad "unsupported OS: $os β install cloudflared manually, or use --ssh" | |
| return 1 | |
| fi | |
| chmod +x "${BIN_DIR}/cloudflared" | |
| return 0 | |
| } | |
| backend_up() { curl -fsS -m 3 "${BASE}/healthz" >/dev/null 2>&1; } | |
| py_bin() { if [ -x ./.venv/bin/python ]; then echo "./.venv/bin/python"; else echo "python3"; fi; } | |
| deps_ok() { "$(py_bin)" -c 'import flask, yt_dlp, ytmusicapi, requests' >/dev/null 2>&1; } | |
| install_deps() { | |
| if deps_ok; then return 0; fi | |
| bold "Installing python dependencies (first run only)" | |
| if python3 -m pip install -q -r requirements.txt >/dev/null 2>&1 && \ | |
| python3 -c 'import flask, yt_dlp, ytmusicapi, requests' >/dev/null 2>&1; then | |
| ok "installed into system python"; return 0 | |
| fi | |
| warn "system pip refused (PEP 668) β using ./.venv" | |
| [ -d .venv ] || python3 -m venv .venv || { bad "could not create .venv (install python3-venv)"; return 1; } | |
| ./.venv/bin/pip install -q -r requirements.txt || { bad "pip install failed β are you offline?"; return 1; } | |
| ok "installed into ./.venv"; return 0 | |
| } | |
| start_backend() { | |
| bold "Starting the backend" | |
| install_deps || return 1 | |
| : > .cadence-backend.log | |
| PORT="$PORT" "$(py_bin)" app.py >>.cadence-backend.log 2>&1 & | |
| BACKEND_PID=$! | |
| printf ' waiting for %s' "$BASE" | |
| for _ in $(seq 1 40); do | |
| if backend_up; then printf '\n'; ok "backend up (pid ${BACKEND_PID})"; return 0; fi | |
| if ! kill -0 "$BACKEND_PID" 2>/dev/null; then | |
| printf '\n'; bad "the backend exited immediately β log:"; sed 's/^/ /' .cadence-backend.log | tail -12; return 1 | |
| fi | |
| printf '.'; sleep 1 | |
| done | |
| printf '\n'; bad "backend did not answer after 40 s β log:"; sed 's/^/ /' .cadence-backend.log | tail -12; return 1 | |
| } | |
| # why is nothing on the port? Say so specifically instead of hand-waving. | |
| diagnose_backend() { | |
| if command -v ss >/dev/null 2>&1 && ss -ltn 2>/dev/null | grep -q ":${PORT} "; then | |
| warn "something else is already listening on port ${PORT} β try: PORT=$((PORT+1)) bash tunnel.sh --with-backend" | |
| return | |
| fi | |
| if command -v lsof >/dev/null 2>&1 && lsof -iTCP:"${PORT}" -sTCP:LISTEN >/dev/null 2>&1; then | |
| warn "port ${PORT} is held by another program β try: PORT=$((PORT+1)) bash tunnel.sh --with-backend" | |
| return | |
| fi | |
| if ! deps_ok; then | |
| warn "python dependencies are not installed yet (./run.sh does that on first start)" | |
| fi | |
| } | |
| # ββ --install βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if [ "$MODE" = "install" ]; then | |
| bold "Installing cloudflared (Apache-2.0) into ${BIN_DIR}/" | |
| if install_cloudflared; then ok "installed: $($BIN_DIR/cloudflared --version 2>/dev/null | head -1)"; exit 0; fi | |
| bad "download failed β check your connection, or use: ./tunnel.sh --ssh" | |
| exit 1 | |
| fi | |
| # ββ --check βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if [ "$MODE" = "check" ]; then | |
| bold "Cadence tunnel diagnostics" | |
| python3 -c 'import flask, yt_dlp, ytmusicapi, requests' 2>/dev/null \ | |
| && ok "python dependencies present" \ | |
| || bad "python dependencies missing β run ./run.sh once" | |
| if backend_up; then ok "backend answering on ${BASE}" | |
| else bad "backend NOT answering on ${BASE} β start it with bash run.sh, or run: bash tunnel.sh --with-backend"; diagnose_backend; fi | |
| if cf="$(find_cloudflared)"; then ok "cloudflared found: $cf ($("$cf" --version 2>/dev/null | head -1))" | |
| else warn "cloudflared not installed β ./tunnel.sh will download it, or use --ssh"; fi | |
| if curl -fsS -m 6 https://api.cloudflare.com/client/v4/ips >/dev/null 2>&1; then ok "cloudflare reachable (https 443)" | |
| else bad "can't reach Cloudflare β a firewall/proxy may be blocking the tunnel"; fi | |
| if command -v ssh >/dev/null 2>&1; then ok "ssh available (fallback tunnel)"; else warn "no ssh (fallback unavailable)"; fi | |
| exit 0 | |
| fi | |
| # ββ --ssh fallback ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if [ "$MODE" = "ssh" ]; then | |
| backend_up || { bad "Backend isn't running on ${BASE} β start ./run.sh first"; exit 1; } | |
| command -v ssh >/dev/null 2>&1 || { bad "ssh is not installed"; exit 1; } | |
| bold "Opening an ssh tunnel via localhost.run (no account)" | |
| echo " The URL appears below as https://β¦.lhr.life β press Ctrl+C to stop." | |
| echo | |
| exec ssh -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30 \ | |
| -R 80:localhost:"${PORT}" nokey@localhost.run | |
| fi | |
| # ββ normal run ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| bold "1/3 Checking the backend" | |
| if backend_up; then | |
| ok "answering on ${BASE}" | |
| elif [ "$WITH_BACKEND" = "1" ]; then | |
| start_backend || { echo; bad "could not start the backend"; exit 1; } | |
| elif [ -t 0 ]; then | |
| # interactive: just offer to do it, instead of making the user open a 2nd terminal | |
| bad "nothing is running on ${BASE}" | |
| diagnose_backend | |
| printf '\n Start it now? [Y/n] ' | |
| read -r answer </dev/tty || answer="n" | |
| case "$answer" in | |
| ""|y|Y|yes|YES) | |
| start_backend || { echo; bad "could not start the backend"; exit 1; } ;; | |
| *) | |
| cat <<EOF | |
| Start it first, in another terminal: | |
| bash run.sh # macOS / Linux (creates .venv and installs deps) | |
| python app.py # if dependencies are already installed | |
| β¦or let this script handle it: bash tunnel.sh --with-backend | |
| Different port? PORT=8010 bash tunnel.sh --with-backend | |
| EOF | |
| exit 1 ;; | |
| esac | |
| else | |
| # non-interactive (piped/CI): never block on a prompt | |
| bad "nothing is running on ${BASE}" | |
| diagnose_backend | |
| cat <<EOF | |
| Start it first, in another terminal: | |
| bash run.sh # macOS / Linux (creates .venv and installs deps) | |
| python app.py # if dependencies are already installed | |
| β¦or let this script handle it: bash tunnel.sh --with-backend | |
| Different port? PORT=8010 bash tunnel.sh --with-backend | |
| EOF | |
| exit 1 | |
| fi | |
| bold "2/3 Checking cloudflared" | |
| if cf="$(find_cloudflared)"; then | |
| ok "found: $cf" | |
| else | |
| warn "not installed β downloading it now (Apache-2.0, no sudo, goes in ./bin)" | |
| if install_cloudflared; then | |
| cf="${BIN_DIR}/cloudflared"; ok "installed" | |
| else | |
| bad "download failed. Either:" | |
| echo " brew install cloudflared # macOS" | |
| echo " ./tunnel.sh --ssh # fallback, no cloudflared needed" | |
| exit 1 | |
| fi | |
| fi | |
| bold "3/3 Opening the tunnel" | |
| echo " (first run takes 5β20 s; press Ctrl+C to stop)" | |
| echo | |
| TUNNEL_PID="" | |
| run_tunnel() { # $1 = logfile β sets TUNNEL_PID (no subshell, so signals reach it) | |
| # shellcheck disable=SC2086 | |
| "$cf" tunnel --url "${BASE}" --no-autoupdate $PROTOCOL >"$1" 2>&1 & | |
| TUNNEL_PID=$! | |
| } | |
| cleanup() { | |
| [ -n "${TUNNEL_PID:-}" ] && kill "$TUNNEL_PID" 2>/dev/null | |
| if [ -n "${BACKEND_PID:-}" ]; then kill "$BACKEND_PID" 2>/dev/null; echo " (backend stopped β it was started by this script)"; fi | |
| } | |
| trap cleanup EXIT INT TERM | |
| LOG="${TMPDIR:-/tmp}/cadence-tunnel.$$.log" | |
| run_tunnel "$LOG" | |
| PID="$TUNNEL_PID" | |
| URL="" | |
| for _ in $(seq 1 60); do | |
| sleep 1 | |
| URL="$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' "$LOG" 2>/dev/null | head -1)" | |
| [ -n "$URL" ] && break | |
| if ! kill -0 "$PID" 2>/dev/null; then break; fi | |
| done | |
| if [ -z "$URL" ]; then | |
| # QUIC (UDP 7844) is blocked on many networks β retry over HTTP/2 before giving up | |
| warn "no URL yet β retrying over HTTP/2 (QUIC is often blocked by firewalls/routers)" | |
| kill "$PID" 2>/dev/null; wait "$PID" 2>/dev/null | |
| PROTOCOL="--protocol http2" | |
| run_tunnel "$LOG" | |
| PID="$TUNNEL_PID" | |
| for _ in $(seq 1 60); do | |
| sleep 1 | |
| URL="$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' "$LOG" 2>/dev/null | head -1)" | |
| [ -n "$URL" ] && break | |
| kill -0 "$PID" 2>/dev/null || break | |
| done | |
| fi | |
| if [ -z "$URL" ]; then | |
| bad "couldn't get a tunnel URL. Here's what cloudflared said:" | |
| echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| tail -20 "$LOG" 2>/dev/null | sed 's/^/ /' | |
| echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| cat <<EOF | |
| Common causes: | |
| β’ corporate / school network blocking outbound 7844/udp and 443 β ./tunnel.sh --ssh | |
| β’ VPN or firewall intercepting TLS β turn it off, retry | |
| β’ cloudflared too old β rm -rf bin && ./tunnel.sh | |
| Alternative that needs no cloudflared: ./tunnel.sh --ssh | |
| EOF | |
| kill "$PID" 2>/dev/null | |
| exit 1 | |
| fi | |
| printf '\n' | |
| bold " ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| printf ' β \033[36mCadence is live at:\033[0m\n' | |
| printf ' β \033[1;4m%s\033[0m\n' "$URL" | |
| bold " ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| echo | |
| echo " Open that URL on your phone β Install Cadence." | |
| echo " Audio streams from this machine, so playback avoids the datacenter block." | |
| echo | |
| # prove it end-to-end before handing it over (DNS for a new subdomain takes a moment) | |
| verified="" | |
| for i in $(seq 1 10); do | |
| # IPv6-only DNS answers are common on fresh subdomains; try both stacks | |
| if curl -fsS -m 6 --ipv4 -o /dev/null "${URL}/healthz" 2>/dev/null \ | |
| || curl -fsS -m 6 -o /dev/null "${URL}/healthz" 2>/dev/null; then verified="yes"; break; fi | |
| if [ "$i" = 1 ]; then printf ' waiting for the tunnel to settleβ¦'; fi | |
| printf '.' | |
| sleep 2 | |
| done | |
| [ -n "$verified" ] && printf '\n' | |
| if [ -n "$verified" ]; then | |
| ok "verified from the public internet (healthz answered)" | |
| else | |
| printf '\n' | |
| echo " (the tunnel is running; the first request can take ~10 s β just open the URL)" | |
| fi | |
| echo | |
| if [ -n "${BACKEND_PID:-}" ]; then | |
| echo " Keep this terminal open. Ctrl+C closes the tunnel and the backend." | |
| else | |
| echo " Keep this terminal open. Ctrl+C closes the tunnel." | |
| fi | |
| echo | |
| wait "$PID" | |